From 3f859026e6bba0dfa55ba5bf034eadc7bd64a875 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 10 Feb 2009 15:54:13 -0500 Subject: [PATCH] Add Net_URL_Mapper to extlib --- extlib/Mapper.php | 324 ++++++++++++++++++++++++ extlib/Mapper/Exception.php | 104 ++++++++ extlib/Mapper/Part.php | 142 +++++++++++ extlib/Mapper/Part/Dynamic.php | 81 ++++++ extlib/Mapper/Part/Fixed.php | 70 ++++++ extlib/Mapper/Part/Wildcard.php | 80 ++++++ extlib/Mapper/Path.php | 430 ++++++++++++++++++++++++++++++++ 7 files changed, 1231 insertions(+) create mode 100644 extlib/Mapper.php create mode 100644 extlib/Mapper/Exception.php create mode 100644 extlib/Mapper/Part.php create mode 100644 extlib/Mapper/Part/Dynamic.php create mode 100644 extlib/Mapper/Part/Fixed.php create mode 100644 extlib/Mapper/Part/Wildcard.php create mode 100644 extlib/Mapper/Path.php diff --git a/extlib/Mapper.php b/extlib/Mapper.php new file mode 100644 index 0000000000..65e38818bb --- /dev/null +++ b/extlib/Mapper.php @@ -0,0 +1,324 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Net + * @package Net_URL_Mapper + * @author Bertrand Mansion + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Mapper.php,v 1.1 2007/03/28 10:23:04 mansion Exp $ + * @link http://pear.php.net/package/Net_URL_Mapper + */ + +require_once 'Net/URL/Mapper/Path.php'; +require_once 'Net/URL/Mapper/Exception.php'; + +/** + * URL parser and mapper class + * + * This class takes an URL and a configuration and returns formatted data + * about the request according to a configuration parameter + * + * @category Net + * @package Net_URL_Mapper + * @author Bertrand Mansion + * @version Release: @package_version@ + */ +class Net_URL_Mapper +{ + /** + * Array of Net_URL_Mapper instances + * @var array + */ + private static $instances = array(); + + /** + * Mapped paths collection + * @var array + */ + protected $paths = array(); + + /** + * Prefix used for url mapping + * @var string + */ + protected $prefix = ''; + + /** + * Optional scriptname if mod_rewrite is not available + * @var string + */ + protected $scriptname = ''; + + /** + * Mapper instance id + * @var string + */ + protected $id = '__default__'; + + /** + * Class constructor + * Constructor is private, you should use getInstance() instead. + */ + private function __construct() { } + + /** + * Returns a singleton object corresponding to the requested instance id + * @param string Requested instance name + * @return Object Net_URL_Mapper Singleton + */ + public static function getInstance($id = '__default__') + { + if (!isset(self::$instances[$id])) { + $m = new Net_URL_Mapper(); + $m->id = $id; + self::$instances[$id] = $m; + } + return self::$instances[$id]; + } + + /** + * Returns the instance id + * @return string Mapper instance id + */ + public function getId() + { + return $this->id; + } + + /** + * Parses a path and creates a connection + * @param string The path to connect + * @param array Default values for path parts + * @param array Regular expressions for path parts + * @return object Net_URL_Mapper_Path + */ + public function connect($path, $defaults = array(), $rules = array()) + { + $pathObj = new Net_URL_Mapper_Path($path, $defaults, $rules); + $this->addPath($pathObj); + return $pathObj; + } + + /** + * Set the url prefix if needed + * + * Example: using the prefix to differenciate mapper instances + * + * $fr = Net_URL_Mapper::getInstance('fr'); + * $fr->setPrefix('/fr'); + * $en = Net_URL_Mapper::getInstance('en'); + * $en->setPrefix('/en'); + * + * + * @param string URL prefix + */ + public function setPrefix($prefix) + { + $this->prefix = '/'.trim($prefix, '/'); + } + + /** + * Set the scriptname if mod_rewrite not available + * + * Example: will match and generate url like + * - index.php/view/product/1 + * + * $m = Net_URL_Mapper::getInstance(); + * $m->setScriptname('index.php'); + * + * @param string URL prefix + */ + public function setScriptname($scriptname) + { + $this->scriptname = $scriptname; + } + + /** + * Will attempt to match an url with a defined path + * + * If an url corresponds to a path, the resulting values are returned + * in an array. If none is found, null is returned. In case an url is + * matched but its content doesn't validate the path rules, an exception is + * thrown. + * + * @param string URL + * @return array|null array if match found, null otherwise + * @throws Net_URL_Mapper_InvalidException + */ + public function match($url) + { + $nurl = '/'.trim($url, '/'); + + // Remove scriptname if needed + + if (!empty($this->scriptname) && + strpos($nurl, $this->scriptname) === 0) { + $nurl = substr($nurl, strlen($this->scriptname)); + if (empty($nurl)) { + $nurl = '/'; + } + } + + // Remove prefix + + if (!empty($this->prefix)) { + if (strpos($nurl, $this->prefix) !== 0) { + return null; + } + $nurl = substr($nurl, strlen($this->prefix)); + if (empty($nurl)) { + $nurl = '/'; + } + } + + // Remove query string + + if (($pos = strpos($nurl, '?')) !== false) { + $nurl = substr($nurl, 0, $pos); + } + + $paths = array(); + $values = null; + + // Make a list of paths that conform to route format + + foreach ($this->paths as $path) { + $regex = $path->getFormat(); + if (preg_match($regex, $nurl)) { + $paths[] = $path; + } + } + + // Make sure one of the paths found is valid + + foreach ($paths as $path) { + $regex = $path->getRule(); + if (preg_match($regex, $nurl, $matches)) { + $values = $path->getDefaults(); + array_shift($matches); + $clean = array(); + foreach ($matches as $k => $v) { + $v = trim($v, '/'); + if (!is_int($k) && $v !== '') { + $values[$k] = $v; + } + } + break; + } + } + + // A path conforms but does not validate + + if (is_null($values) && !empty($paths)) { + $e = new Net_URL_Mapper_InvalidException('A path was found but is invalid.'); + $e->setPath($paths[0]); + $e->setUrl($url); + throw $e; + } + + return $values; + } + + /** + * Generate an url based on given parameters + * + * Will attempt to find a path definition that matches the given parameters and + * will generate an url based on this path. + * + * @param array Values to be used for the url generation + * @param array Key/value pairs for query string if needed + * @param string Anchor (fragment) if needed + * @return string|false String if a rule was found, false otherwise + */ + public function generate($values = array(), $qstring = array(), $anchor = '') + { + // Use root path if any + + if (empty($values) && isset($this->paths['/'])) { + return $this->scriptname.$this->prefix.$this->paths['/']->generate($values, $qstring, $anchor); + } + + foreach ($this->paths as $path) { + $set = array(); + foreach ($values as $k => $v) { + if ($path->hasKey($k, $v)) { + $set[$k] = $v; + } + } + + if (count($set) == count($values) && + count($set) <= $path->getMaxKeys()) { + + $req = $path->getRequired(); + if (count(array_intersect(array_keys($set), $req)) != count($req)) { + continue; + } + $gen = $path->generate($set, $qstring, $anchor); + return $this->scriptname.$this->prefix.$gen; + } + } + return false; + } + + /** + * Returns defined paths + * @return array Array of paths + */ + public function getPaths() + { + return $this->paths; + } + + /** + * Reset all paths + * This is probably only useful for testing + */ + public function reset() + { + $this->paths = array(); + $this->prefix = ''; + } + + /** + * Add a new path to the mapper + * @param object Net_URL_Mapper_Path object + */ + public function addPath(Net_URL_Mapper_Path $path) + { + $this->paths[$path->getPath()] = $path; + } + +} +?> \ No newline at end of file diff --git a/extlib/Mapper/Exception.php b/extlib/Mapper/Exception.php new file mode 100644 index 0000000000..ac3ad172b1 --- /dev/null +++ b/extlib/Mapper/Exception.php @@ -0,0 +1,104 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Net + * @package Net_URL_Mapper + * @author Bertrand Mansion + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Exception.php,v 1.1 2007/03/28 10:23:04 mansion Exp $ + * @link http://pear.php.net/package/Net_URL_Mapper + */ + +/** + * Base class for exceptions in PEAR + */ +require_once 'PEAR/Exception.php'; + +/** + * Base class for exceptions in Net_URL_Mapper package + * + * Such a base class is required by the Exception RFC: + * http://pear.php.net/pepr/pepr-proposal-show.php?id=132 + * It will rarely be thrown directly, its specialized subclasses will be + * thrown most of the time. + * + * @category Net + * @package Net_URL_Mapper + * @version Release: @package_version@ + */ +class Net_URL_Mapper_Exception extends PEAR_Exception +{ +} + +/** + * Exception thrown when a path is invalid + * + * A path can conform to a given structure, but contain invalid parameters. + * + * $m = Net_URL_Mapper::getInstance(); + * $m->connect('hi/:name', null, array('name'=>'[a-z]+')); + * $m->match('/hi/FOXY'); // Will throw the exception + * + * + * @category Net + * @package Net_URL_Mapper + * @version Release: @package_version@ + */ +class Net_URL_Mapper_InvalidException extends Net_URL_Mapper_Exception +{ + protected $path; + protected $url; + + public function setPath($path) + { + $this->path = $path; + } + + public function getPath() + { + return $this->path; + } + + public function setUrl($url) + { + $this->url = $url; + } + + public function getUrl() + { + return $this->url; + } +} +?> \ No newline at end of file diff --git a/extlib/Mapper/Part.php b/extlib/Mapper/Part.php new file mode 100644 index 0000000000..2f15b2c162 --- /dev/null +++ b/extlib/Mapper/Part.php @@ -0,0 +1,142 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Net + * @package Net_URL_Mapper + * @author Bertrand Mansion + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Part.php,v 1.1 2007/03/28 10:23:04 mansion Exp $ + * @link http://pear.php.net/package/Net_URL_Mapper + */ + +abstract class Net_URL_Mapper_Part +{ + protected $defaults; + protected $rule; + protected $public; + protected $type; + protected $required = false; + + /** + * Part name if dynamic or content, generated from path + * @var string + */ + public $content; + + const DYNAMIC = 1; + const WILDCARD = 2; + const FIXED = 3; + + public function __construct($content, $path) + { + $this->content = $content; + $this->path = $path; + } + + public function setRule($rule) + { + $this->rule = $rule; + } + + abstract public function getFormat(); + + abstract public function getRule(); + + public function addSlash($str) + { + $str = trim($str, '/'); + if (($pos = strpos($this->path, '/')) !== false) { + if ($pos == 0) { + $str = '/'.$str; + } else { + $str .= '/'; + } + } + return $str; + } + + public function addSlashRegex($str) + { + $str = trim($str, '/'); + if (($pos = strpos($this->path, '/')) !== false) { + if ($pos == 0) { + $str = '\/'.$str; + } else { + $str .= '\/'; + } + } + if (!$this->isRequired()) { + $str = '('.$str.'|)'; + } + return $str; + } + + public function setDefaults($defaults) + { + $this->defaults = (string)$defaults; + } + + public function getType() + { + return $this->type; + } + + public function accept($visitor, $method = null) + { + $args = func_get_args(); + $visitor->$method($this, $args); + } + + public function setRequired($required) + { + $this->required = $required; + } + + public function isRequired() + { + return $this->required; + } + + abstract public function generate($value = null); + + public function match($value) + { + $rule = $this->getRule(); + return preg_match('/^'.$rule.'$/', $this->addSlash($value)); + } + +} + +?> \ No newline at end of file diff --git a/extlib/Mapper/Part/Dynamic.php b/extlib/Mapper/Part/Dynamic.php new file mode 100644 index 0000000000..349d87338c --- /dev/null +++ b/extlib/Mapper/Part/Dynamic.php @@ -0,0 +1,81 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Net + * @package Net_URL_Mapper + * @author Bertrand Mansion + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Dynamic.php,v 1.1 2007/03/28 10:23:04 mansion Exp $ + * @link http://pear.php.net/package/Net_URL_Mapper + */ + +require_once 'Net/URL/Mapper/Part.php'; + +class Net_URL_Mapper_Part_Dynamic extends Net_URL_Mapper_Part +{ + + public function __construct($content, $path) + { + $this->type = Net_URL_Mapper_Part::DYNAMIC; + $this->setRequired(true); + parent::__construct($content, $path); + } + + public function getFormat() + { + return $this->addSlashRegex('[^\/]+'); + } + + public function getRule() + { + if (!empty($this->rule)) { + return '(?P<'.$this->content.'>'.$this->addSlashRegex($this->rule).')'; + } + return '(?P<'.$this->content.'>'.$this->addSlashRegex('[^\/]+').')'; + } + + public function generate($value = null) + { + if (is_array($value) && isset($value[$this->content])) { + $val = $value[$this->content]; + } elseif (!is_array($value) && !is_null($value)) { + $val = $value; + } else { + $val = $this->defaults; + } + return $this->addSlash(urlencode($val)); + } +} +?> \ No newline at end of file diff --git a/extlib/Mapper/Part/Fixed.php b/extlib/Mapper/Part/Fixed.php new file mode 100644 index 0000000000..b315b442db --- /dev/null +++ b/extlib/Mapper/Part/Fixed.php @@ -0,0 +1,70 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Net + * @package Net_URL_Mapper + * @author Bertrand Mansion + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Fixed.php,v 1.1 2007/03/28 10:23:04 mansion Exp $ + * @link http://pear.php.net/package/Net_URL_Mapper + */ + +require_once 'Net/URL/Mapper/Part.php'; + +class Net_URL_Mapper_Part_Fixed extends Net_URL_Mapper_Part +{ + + public function __construct($content, $path) + { + $this->type = Net_URL_Mapper_Part::FIXED; + parent::__construct($content, $path); + } + + public function getFormat() + { + return $this->getRule(); + } + + public function getRule() + { + return preg_quote($this->path, '/'); + } + + public function generate($value = null) + { + return $this->path; + } +} +?> \ No newline at end of file diff --git a/extlib/Mapper/Part/Wildcard.php b/extlib/Mapper/Part/Wildcard.php new file mode 100644 index 0000000000..6085ff6489 --- /dev/null +++ b/extlib/Mapper/Part/Wildcard.php @@ -0,0 +1,80 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Net + * @package Net_URL_Mapper + * @author Bertrand Mansion + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Wildcard.php,v 1.1 2007/03/28 10:23:04 mansion Exp $ + * @link http://pear.php.net/package/Net_URL_Mapper + */ + +require_once 'Net/URL/Mapper/Part.php'; + +class Net_URL_Mapper_Part_Wildcard extends Net_URL_Mapper_Part +{ + + public function __construct($content, $path) + { + $this->type = Net_URL_Mapper_Part::WILDCARD; + $this->setRequired(true); + parent::__construct($content, $path); + } + + public function getFormat() + { + return $this->addSlashRegex('.*');; + } + + public function getRule() + { + return '(?P<'.$this->content.'>'.$this->addSlashRegex('.*').')'; + } + + public function generate($value = null) + { + if (is_array($value) && isset($value[$this->content])) { + $val = $value[$this->content]; + } elseif (!is_array($value) && !is_null($value)) { + $val = $value; + } else { + $val = $this->defaults; + } + return $this->addSlash(str_replace( + array('%2F', '%23'), + array('/', '#'), urlencode($val))); + } +} +?> \ No newline at end of file diff --git a/extlib/Mapper/Path.php b/extlib/Mapper/Path.php new file mode 100644 index 0000000000..b541002c7a --- /dev/null +++ b/extlib/Mapper/Path.php @@ -0,0 +1,430 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The names of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Net + * @package Net_URL_Mapper + * @author Bertrand Mansion + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Path.php,v 1.1 2007/03/28 10:23:04 mansion Exp $ + * @link http://pear.php.net/package/Net_URL_Mapper + */ + +require_once 'Net/URL.php'; +require_once 'Net/URL/Mapper/Part/Dynamic.php'; +require_once 'Net/URL/Mapper/Part/Wildcard.php'; +require_once 'Net/URL/Mapper/Part/Fixed.php'; + +class Net_URL_Mapper_Path +{ + private $path = ''; + private $N = 0; + public $token; + public $value; + private $line = 1; + private $state = 1; + + + protected $alias; + protected $rules = array(); + protected $defaults = array(); + protected $parts = array(); + protected $rule; + protected $format; + protected $minKeys; + protected $maxKeys; + protected $fixed = true; + protected $required; + + public function __construct($path = '', $defaults = array(), $rules = array()) + { + $this->path = '/'.trim(Net_URL::resolvePath($path), '/'); + $this->setDefaults($defaults); + $this->setRules($rules); + + try { + $this->parsePath(); + } catch (Exception $e) { + // The path could not be parsed correctly, treat it as fixed + $this->fixed = true; + $part = self::createPart(Net_URL_Mapper_Part::FIXED, $this->path, $this->path); + $this->parts = array($part); + } + $this->getRequired(); + } + + public function getPath() + { + return $this->path; + } + + protected function parsePath() + { + while ($this->yylex()) { } + } + + /** + * Get the path alias + * Path aliases can be used instead of full path + * @return null|string + */ + public function getAlias() + { + return $this->alias; + } + + /** + * Set the path name + * @param string Set the path name + * @see getAlias() + */ + public function setAlias($alias) + { + $this->alias = $alias; + return $this; + } + + /** + * Get the path parts default values + * @return null|array + */ + public function getDefaults() + { + return $this->defaults; + } + + /** + * Set the path parts default values + * @param array Associative array with format partname => value + */ + public function setDefaults($defaults) + { + if (is_array($defaults)) { + $this->defaults = $defaults; + } else { + $this->defaults = array(); + } + } + + /** + * Set the path parts default values + * @param array Associative array with format partname => value + */ + public function setRules($rules) + { + if (is_array($rules)) { + $this->rules = $rules; + } else { + $this->rules = array(); + } + } + + /** + * Returns the regular expression used to match this path + * @return string PERL Regular expression + */ + public function getRule() + { + if (is_null($this->rule)) { + $this->rule = '/^'; + foreach ($this->parts as $path => $part) { + $this->rule .= $part->getRule(); + } + $this->rule .= '$/'; + } + return $this->rule; + } + + public function getFormat() + { + if (is_null($this->format)) { + $this->format = '/^'; + foreach ($this->parts as $path => $part) { + $this->format .= $part->getFormat(); + } + $this->format .= '$/'; + } + return $this->format; + } + + protected function addPart($part) + { + if (array_key_exists($part->content, $this->defaults)) { + $part->setRequired(false); + $part->setDefaults($this->defaults[$part->content]); + } + if (isset($this->rules[$part->content])) { + $part->setRule($this->rules[$part->content]); + } + $this->rule = null; + if ($part->getType() != Net_URL_Mapper_Part::FIXED) { + $this->fixed = false; + $this->parts[$part->content] = $part; + } else { + $this->parts[] = $part; + } + return $part; + } + + public static function createPart($type, $content, $path) + { + switch ($type) { + case Net_URL_Mapper_Part::DYNAMIC: + return new Net_URL_Mapper_Part_Dynamic($content, $path); + break; + case Net_URL_Mapper_Part::WILDCARD: + return new Net_URL_Mapper_Part_Wildcard($content, $path); + break; + default: + return new Net_URL_Mapper_Part_Fixed($content, $path); + } + } + + /** + * Checks whether the path contains the given part by name + * If value parameter is given, the part also checks if the + * given value conforms to the part rule. + * @param string Part name + * @param mixed The value to check against + */ + public function hasKey($partName, $value = null) + { + if (array_key_exists($partName, $this->parts)) { + if (!is_null($value) && $value !== false) { + return $this->parts[$partName]->match($value); + } else { + return true; + } + } elseif (array_key_exists($partName, $this->defaults) && + $value == $this->defaults[$partName]) { + return true; + } + return false; + } + + public function generate($values = array(), $qstring = array(), $anchor = '') + { + $path = ''; + foreach ($this->parts as $part) { + $path .= $part->generate($values); + } + $path = '/'.trim(Net_URL::resolvePath($path), '/'); + if (!empty($qstring)) { + $path .= '?'.http_build_query($qstring); + } + if (!empty($anchor)) { + $path .= '#'.ltrim($anchor, '#'); + } + return $path; + } + + public function getRequired() + { + if (!isset($this->required)) { + $req = array(); + foreach ($this->parts as $part) { + if ($part->isRequired()) { + $req[] = $part->content; + } + } + $this->required = $req; + } + return $this->required; + } + + public function getMaxKeys() + { + if (is_null($this->maxKeys)) { + $this->maxKeys = count($this->required); + $this->maxKeys += count($this->defaults); + } + return $this->maxKeys; + } + + + + + private $_yy_state = 1; + private $_yy_stack = array(); + + function yylex() + { + return $this->{'yylex' . $this->_yy_state}(); + } + + function yypushstate($state) + { + array_push($this->_yy_stack, $this->_yy_state); + $this->_yy_state = $state; + } + + function yypopstate() + { + $this->_yy_state = array_pop($this->_yy_stack); + } + + function yybegin($state) + { + $this->_yy_state = $state; + } + + + + function yylex1() + { + $tokenMap = array ( + 1 => 1, + 3 => 1, + 5 => 1, + 7 => 1, + 9 => 1, + ); + if ($this->N >= strlen($this->path)) { + return false; // end of input + } + $yy_global_pattern = "/^(\/?:\/?\\(([a-zA-Z0-9_]+)\\))|^(\/?\\*\/?\\(([a-zA-Z0-9_]+)\\))|^(\/?:([a-zA-Z0-9_]+))|^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))/"; + + do { + if (preg_match($yy_global_pattern, substr($this->path, $this->N), $yymatches)) { + $yysubmatches = $yymatches; + $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns + if (!count($yymatches)) { + throw new Exception('Error: lexing failed because a rule matched' . + 'an empty string. Input "' . substr($this->path, + $this->N, 5) . '... state START'); + } + next($yymatches); // skip global match + $this->token = key($yymatches); // token number + if ($tokenMap[$this->token]) { + // extract sub-patterns for passing to lex function + $yysubmatches = array_slice($yysubmatches, $this->token + 1, + $tokenMap[$this->token]); + } else { + $yysubmatches = array(); + } + $this->value = current($yymatches); // token value + $r = $this->{'yy_r1_' . $this->token}($yysubmatches); + if ($r === null) { + $this->N += strlen($this->value); + $this->line += substr_count("\n", $this->value); + // accept this token + return true; + } elseif ($r === true) { + // we have changed state + // process this token in the new state + return $this->yylex(); + } elseif ($r === false) { + $this->N += strlen($this->value); + $this->line += substr_count("\n", $this->value); + if ($this->N >= strlen($this->path)) { + return false; // end of input + } + // skip this token + continue; + } else { $yy_yymore_patterns = array( + 1 => "^(\/?\\*\/?\\(([a-zA-Z0-9_]+)\\))|^(\/?:([a-zA-Z0-9_]+))|^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))", + 3 => "^(\/?:([a-zA-Z0-9_]+))|^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))", + 5 => "^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))", + 7 => "^(\/?([^\/:*]+))", + 9 => "", + ); + + // yymore is needed + do { + if (!strlen($yy_yymore_patterns[$this->token])) { + throw new Exception('cannot do yymore for the last token'); + } + if (preg_match($yy_yymore_patterns[$this->token], + substr($this->path, $this->N), $yymatches)) { + $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns + next($yymatches); // skip global match + $this->token = key($yymatches); // token number + $this->value = current($yymatches); // token value + $this->line = substr_count("\n", $this->value); + } + } while ($this->{'yy_r1_' . $this->token}() !== null); + // accept + $this->N += strlen($this->value); + $this->line += substr_count("\n", $this->value); + return true; + } + } else { + throw new Exception('Unexpected input at line' . $this->line . + ': ' . $this->path[$this->N]); + } + break; + } while (true); + } // end function + + + const START = 1; + function yy_r1_1($yy_subpatterns) + { + + $c = $yy_subpatterns[0]; + $part = self::createPart(Net_URL_Mapper_Part::DYNAMIC, $c, $this->value); + $this->addPart($part); + } + function yy_r1_3($yy_subpatterns) + { + + $c = $yy_subpatterns[0]; + $part = self::createPart(Net_URL_Mapper_Part::WILDCARD, $c, $this->value); + $this->addPart($part); + } + function yy_r1_5($yy_subpatterns) + { + + $c = $yy_subpatterns[0]; + $part = self::createPart(Net_URL_Mapper_Part::DYNAMIC, $c, $this->value); + $this->addPart($part); + } + function yy_r1_7($yy_subpatterns) + { + + $c = $yy_subpatterns[0]; + $part = self::createPart(Net_URL_Mapper_Part::WILDCARD, $c, $this->value); + $this->addPart($part); + } + function yy_r1_9($yy_subpatterns) + { + + $c = $yy_subpatterns[0]; + $part = self::createPart(Net_URL_Mapper_Part::FIXED, $c, $this->value); + $this->addPart($part); + } + +} + +?> \ No newline at end of file