⚝
One Hat Cyber Team
⚝
Your IP:
216.73.216.89
Server IP:
41.128.143.86
Server:
Linux host.raqmix.cloud 6.8.0-1025-azure #30~22.04.1-Ubuntu SMP Wed Mar 12 15:28:20 UTC 2025 x86_64
Server Software:
Apache
PHP Version:
8.3.23
Buat File
|
Buat Folder
Eksekusi
Dir :
~
/
usr
/
share
/
psa-pear
/
pear
/
php
/
Horde
/
Routes
/
Edit File: Mapper.php
<?php /** * Horde Routes package * * This package is heavily inspired by the Python "Routes" library * by Ben Bangert (http://routes.groovie.org). Routes is based * largely on ideas from Ruby on Rails (http://www.rubyonrails.org). * * @author Maintainable Software, LLC. (http://www.maintainable.com) * @author Mike Naberezny <mike@maintainable.com> * @license http://www.horde.org/licenses/bsd BSD * @package Routes */ /** * The mapper class handles URL generation and recognition for web applications * * The mapper class is built by handling associated arrays of information and passing * associated arrays back to the application for it to handle and dispatch the * appropriate scripts. * * @package Routes */ class Horde_Routes_Mapper { /** * Filtered request environment with keys like SCRIPT_NAME * @var array */ public $environ = array(); /** * Callback function used to get array of controller names * @var callback */ public $controllerScan; /** * Path to controller directory passed to controllerScan function * @var string */ public $directory; /** * Call controllerScan callback before every route match? * @var boolean */ public $alwaysScan; /** * Disable route memory and implicit defaults? * @var boolean */ public $explicit; /** * Collect debug information during route match? * @var boolean */ public $debug = false; /** * Use sub-domain support? * @var boolean */ public $subDomains = false; /** * Array of sub-domains to ignore if using sub-domain support * @var array */ public $subDomainsIgnore = array(); /** * Append trailing slash ('/') to generated routes? * @var boolean */ public $appendSlash = false; /** * Prefix to strip during matching and to append during generation * @var null|string */ public $prefix = null; /** * Array of connected routes * @var array */ public $matchList = array(); /** * Array of connected named routes, indexed by name * @var array */ public $routeNames = array(); /** * Cache of URLs used in generate() * @var array */ public $urlCache = array(); /** * Encoding of routes URLs (not yet supported) * @var string */ public $encoding = 'utf-8'; /** * What to do on decoding errors? 'ignore' or 'replace' * @var string */ public $decodeErrors = 'ignore'; /** * Partial regexp used to match domain part of the end of URLs to match * @var string */ public $domainMatch = '[^\.\/]+?\.[^\.\/]+'; /** * Array of all connected routes, indexed by the serialized array of all * keys that each route could utilize. * @var array */ public $maxKeys = array(); /** * Array of all connected routes, indexed by the serialized array of the * minimum keys that each route needs. * @var array */ public $minKeys = array(); /** * Utility functions like urlFor() and redirectTo() for this Mapper * @var Horde_Routes_Utils */ public $utils; /** * Cache * @var Horde_Cache */ public $cache; /** * Cache lifetime for the same value of $this->matchList * @var integer */ public $cacheLifetime = 86400; /** * Have regular expressions been created for all connected routes? * @var boolean */ protected $_createdRegs = false; /** * Have generation hashes been created for all connected routes? * @var boolean */ protected $_createdGens = false; /** * Generation hashes created for all connected routes * @var array */ protected $_gendict; /** * Temporary variable used to pass array of keys into _keysort() callback * @var array */ protected $_keysortTmp; /** * Regular expression generated to match after the prefix * @var string */ protected $_regPrefix = null; /** * Constructor. * * Keyword arguments ($kargs): * ``controllerScan`` (callback) * Function to return an array of valid controllers * * ``redirect`` (callback) * Function to perform a redirect for Horde_Routes_Utils->redirectTo() * * ``directory`` (string) * Path to the directory that will be passed to the * controllerScan callback * * ``alwaysScan`` (boolean) * Should the controllerScan callback be called * before every URL match? * * ``explicit`` (boolean) * Should routes be connected with the implicit defaults of * array('controller'=>'content', 'action'=>'index', 'id'=>null)? * When set to True, these will not be added to route connections. */ public function __construct($kargs = array()) { $callback = array('Horde_Routes_Utils', 'controllerScan'); $defaultKargs = array('controllerScan' => $callback, 'directory' => null, 'alwaysScan' => false, 'explicit' => false); $kargs = array_merge($defaultKargs, $kargs); // Most default assignments that were in the construct in the Python // version have been moved to outside the constructor unless they were variable $this->directory = $kargs['directory']; $this->alwaysScan = $kargs['alwaysScan']; $this->controllerScan = $kargs['controllerScan']; $this->explicit = $kargs['explicit']; $this->utils = new Horde_Routes_Utils($this); } /** * Create and connect a new Route to the Mapper. * * Usage: * $m = new Horde_Routes_Mapper(); * $m->connect(':controller/:action/:id'); * $m->connect('date/:year/:month/:day', array('controller' => "blog", 'action' => 'view'); * $m->connect('archives/:page', array('controller' => 'blog', 'action' => 'by_page', * ' requirements' => array('page' => '\d{1,2}'))); * $m->connect('category_list', * 'archives/category/:section', array('controller' => 'blog', 'action' => 'category', * 'section' => 'home', 'type' => 'list')); * $m->connect('home', * '', * array('controller' => 'blog', 'action' => 'view', 'section' => 'home')); * * @param mixed $first First argument in vargs, see usage above. * @param mixed $second Second argument in varags * @param mixed $third Third argument in varargs * @return void */ public function connect($first, $second = null, $third = null) { if ($third !== null) { // 3 args given // connect('route_name', ':/controller/:action/:id', array('kargs'=>'here')) $routeName = $first; $routePath = $second; $kargs = $third; } else if ($second !== null) { // 2 args given if (is_array($second)) { // connect(':/controller/:action/:id', array('kargs'=>'here')) $routeName = null; $routePath = $first; $kargs = $second; } else { // connect('route_name', ':/controller/:action/:id') $routeName = $first; $routePath = $second; $kargs = array(); } } else { // 1 arg given // connect('/:controller/:action/:id') $routeName = null; $routePath = $first; $kargs = array(); } if (!in_array('_explicit', $kargs)) { $kargs['_explicit'] = $this->explicit; } $route = new Horde_Routes_Route($routePath, $kargs); if ($this->encoding != 'utf-8' || $this->decodeErrors != 'ignore') { $route->encoding = $this->encoding; $route->decodeErrors = $this->decodeErrors; } $this->matchList[] = $route; if (isset($routeName)) { $this->routeNames[$routeName] = $route; } if ($route->static) { return; } $exists = false; foreach ($this->maxKeys as $key => $value) { if (unserialize($key) == $route->maxKeys) { $this->maxKeys[$key][] = $route; $exists = true; break; } } if (!$exists) { $this->maxKeys[serialize($route->maxKeys)] = array($route); } $this->_createdGens = false; } /** * Set an optional Horde_Cache object for the created rules. * * @param Horde_Cache $cache Cache object */ public function setCache(Horde_Cache $cache) { $this->cache = $cache; } /** * Create the generation hashes (arrays) for route lookups * * @return void */ protected function _createGens() { // Checked for a cached generator dictionary for $this->matchList if ($this->cache) { $cacheKey = 'horde.routes.' . sha1(serialize($this->matchList)); $cachedDict = $cache->get($cacheKey, $this->cacheLifetime); if ($gendict = @unserialize($cachedDict)) { $this->_gendict = $gendict; $this->_createdGens = true; return; } } // Use keys temporarily to assemble the list to avoid excessive // list iteration testing with foreach. We include the '*' in the // case that a generate contains a controller/action that has no // hardcodes. $actionList = $controllerList = array('*' => true); // Assemble all the hardcoded/defaulted actions/controllers used foreach ($this->matchList as $route) { if ($route->static) { continue; } if (isset($route->defaults['controller'])) { $controllerList[$route->defaults['controller']] = true; } if (isset($route->defaults['action'])) { $actionList[$route->defaults['action']] = true; } } $actionList = array_keys($actionList); $controllerList = array_keys($controllerList); // Go through our list again, assemble the controllers/actions we'll // add each route to. If its hardcoded, we only add it to that dict key. // Otherwise we add it to every hardcode since it can be changed. $gendict = array(); // Our generated two-deep hash foreach ($this->matchList as $route) { if ($route->static) { continue; } $clist = $controllerList; $alist = $actionList; if (in_array('controller', $route->hardCoded)) { $clist = array($route->defaults['controller']); } if (in_array('action', $route->hardCoded)) { $alist = array($route->defaults['action']); } foreach ($clist as $controller) { foreach ($alist as $action) { if (in_array($controller, array_keys($gendict))) { $actiondict = &$gendict[$controller]; } else { $gendict[$controller] = array(); $actiondict = &$gendict[$controller]; } if (in_array($action, array_keys($actiondict))) { $tmp = $actiondict[$action]; } else { $tmp = array(array(), array()); } $tmp[0][] = $route; $actiondict[$action] = $tmp; } } } if (!isset($gendict['*'])) { $gendict['*'] = array(); } // Write to the cache if ($this->cache) { $this->cache->set($cacheKey, serialize($gendict), $this->cacheLifetime); } $this->_gendict = $gendict; $this->_createdGens = true; } /** * Creates the regexes for all connected routes * * @param array $clist controller list, controller_scan will be used otherwise * @return void */ public function createRegs($clist = null) { if ($clist === null) { if ($this->directory === null) { $clist = call_user_func($this->controllerScan); } else { $clist = call_user_func($this->controllerScan, $this->directory); } } foreach ($this->maxKeys as $key => $val) { foreach ($val as $route) { $route->makeRegexp($clist); } } // Create our regexp to strip the prefix if (!empty($this->prefix)) { $this->_regPrefix = $this->prefix . '(.*)'; } $this->_createdRegs = true; } /** * Internal Route matcher * * Matches a URL against a route, and returns a tuple (array) of the * match dict (array) and the route object if a match is successful, * otherwise it returns null. * * @param string $url URL to match * @return null|array Match data if matched, otherwise null */ protected function _match($url) { if (!$this->_createdRegs && !empty($this->controllerScan)) { $this->createRegs(); } elseif (!$this->_createdRegs) { $msg = 'You must generate the regular expressions before matching.'; throw new Horde_Routes_Exception($msg); } if ($this->alwaysScan) { $this->createRegs(); } $matchLog = array(); if (!empty($this->prefix)) { if (preg_match('@' . $this->_regPrefix . '@', $url)) { $url = preg_replace('@' . $this->_regPrefix . '@', '$1', $url); if (empty($url)) { $url = '/'; } } else { return array(null, null, $matchLog); } } foreach ($this->matchList as $route) { if ($route->static) { if ($this->debug) { $matchLog[] = array('route' => $route, 'static' => true); } continue; } $match = $route->match($url, array('environ' => $this->environ, 'subDomains' => $this->subDomains, 'subDomainsIgnore' => $this->subDomainsIgnore, 'domainMatch' => $this->domainMatch)); if ($this->debug) { $matchLog[] = array('route' => $route, 'regexp' => (bool)$match); } if ($match) { return array($match, $route, $matchLog); } } return array(null, null, $matchLog); } /** * Match a URL against one of the routes contained. * It will return null if no valid match is found. * * Usage: * $resultdict = $m->match('/joe/sixpack'); * * @param string $url URL to match * @param array|null Array if matched, otherwise null */ public function match($url) { if (!strlen($url)) { $msg = 'No URL provided, the minimum URL necessary to match is "/"'; throw new Horde_Routes_Exception($msg); } $result = $this->_match($url); if ($this->debug) { return array($result[0], $result[1], $result[2]); } return ($result[0]) ? $result[0] : null; } /** * Match a URL against one of the routes contained. * It will return null if no valid match is found, otherwise * a result dict (array) and a route object is returned. * * Usage: * list($resultdict, $resultobj) = $m->match('/joe/sixpack'); * * @param string $url URL to match * @param array|null Array if matched, otherwise null */ public function routematch($url) { $result = $this->_match($url); if ($this->debug) { return array($result[0], $result[1], $result[2]); } return ($result[0]) ? array($result[0], $result[1]) : null; } /** * Generates the URL from a given set of keywords * Returns the URL text, or null if no URL could be generated. * * Usage: * $m->generate(array('controller' => 'content', 'action' => 'view', 'id' => 10)); * * @param array $routeArgs Optional explicit route list * @param array $kargs Keyword arguments (key/value pairs) * @return null|string URL text or null */ public function generate($first = null, $second = null) { if ($second) { $routeArgs = $first; $kargs = is_null($second) ? array() : $second; } else { $routeArgs = array(); $kargs = is_null($first) ? array() : $first; } // Generate ourself if we haven't already if (!$this->_createdGens) { $this->_createGens(); } if ($this->appendSlash) { $kargs['_appendSlash'] = true; } if (!$this->explicit) { if (!in_array('controller', array_keys($kargs))) { $kargs['controller'] = 'content'; } if (!in_array('action', array_keys($kargs))) { $kargs['action'] = 'index'; } } $environ = $this->environ; $controller = isset($kargs['controller']) ? $kargs['controller'] : null; $action = isset($kargs['action']) ? $kargs['action'] : null; // If the URL didn't depend on the SCRIPT_NAME, we'll cache it // keyed by just the $kargs; otherwise we need to cache it with // both SCRIPT_NAME and $kargs: $cacheKey = serialize($kargs); if (!empty($environ['SCRIPT_NAME'])) { $cacheKeyScriptName = sprintf('%s:%s', $environ['SCRIPT_NAME'], $cacheKey); } else { $cacheKeyScriptName = $cacheKey; } // Check the URL cache to see if it exists, use it if it does. foreach (array($cacheKey, $cacheKeyScriptName) as $key) { if (in_array($key, array_keys($this->urlCache))) { return $this->urlCache[$key]; } } if ($routeArgs) { $keyList = $routeArgs; } else { $actionList = isset($this->_gendict[$controller]) ? $this->_gendict[$controller] : $this->_gendict['*']; list($keyList, $sortCache) = (isset($actionList[$action])) ? $actionList[$action] : ((isset($actionList['*'])) ? $actionList['*'] : array(null, null)); if ($keyList === null) { return null; } } $keys = array_keys($kargs); // necessary to pass $keys to _keysort() callback used by PHP's usort() $this->_keysortTmp = $keys; $newList = array(); foreach ($keyList as $route) { $tmp = Horde_Routes_Utils::arraySubtract($route->minKeys, $keys); if (count($tmp) == 0) { $newList[] = $route; } } $keyList = $newList; // inline python function keysort() moved below as _keycmp() $this->_keysort($keyList); foreach ($keyList as $route) { $fail = false; foreach ($route->hardCoded as $key) { $kval = isset($kargs[$key]) ? $kargs[$key] : null; if ($kval == null) { continue; } if ($kval != $route->defaults[$key]) { $fail = true; break; } } if ($fail) { continue; } $path = $route->generate($kargs); if ($path) { if ($this->prefix) { $path = $this->prefix . $path; } if (!empty($environ['SCRIPT_NAME']) && !$route->absolute) { $path = $environ['SCRIPT_NAME'] . $path; $key = $cacheKeyScriptName; } else { $key = $cacheKey; } if ($this->urlCache != null) { $this->urlCache[$key] = $path; } return $path; } else { continue; } } return null; } /** * Generate routes for a controller resource * * The $memberName name should be the appropriate singular version of the * resource given your locale and used with members of the collection. * * The $collectionName name will be used to refer to the resource * collection methods and should be a plural version of the $memberName * argument. By default, the $memberName name will also be assumed to map * to a controller you create. * * The concept of a web resource maps somewhat directly to 'CRUD' * operations. The overlying things to keep in mind is that mapping a * resource is about handling creating, viewing, and editing that * resource. * * All keyword arguments ($kargs) are optional. * * ``controller`` * If specified in the keyword args, the controller will be the actual * controller used, but the rest of the naming conventions used for * the route names and URL paths are unchanged. * * ``collection`` * Additional action mappings used to manipulate/view the entire set of * resources provided by the controller. * * Example:: * * $map->resource('message', 'messages', * array('collection' => array('rss' => 'GET))); * # GET /message;rss (maps to the rss action) * # also adds named route "rss_message" * * ``member`` * Additional action mappings used to access an individual 'member' * of this controllers resources. * * Example:: * * $map->resource('message', 'messages', * array('member' => array('mark' => 'POST'))); * # POST /message/1;mark (maps to the mark action) * # also adds named route "mark_message" * * ``new`` * Action mappings that involve dealing with a new member in the * controller resources. * * Example:: * * $map->resource('message', 'messages', * array('new' => array('preview' => 'POST'))); * # POST /message/new;preview (maps to the preview action) * # also adds a url named "preview_new_message" * * ``pathPrefix`` * Prepends the URL path for the Route with the pathPrefix given. * This is most useful for cases where you want to mix resources * or relations between resources. * * ``namePrefix`` * Perpends the route names that are generated with the namePrefix * given. Combined with the pathPrefix option, it's easy to * generate route names and paths that represent resources that are * in relations. * * Example:: * * map.resource('message', 'messages', * array('controller' => 'categories', * 'pathPrefix' => '/category/:category_id', * 'namePrefix' => 'category_'))); * # GET /category/7/message/1 * # has named route "category_message" * * ``parentResource`` * An assoc. array containing information about the parent resource, * for creating a nested resource. It should contain the ``$memberName`` * and ``collectionName`` of the parent resource. This assoc. array will * be available via the associated ``Route`` object which can be * accessed during a request via ``request.environ['routes.route']`` * * If ``parentResource`` is supplied and ``pathPrefix`` isn't, * ``pathPrefix`` will be generated from ``parentResource`` as * "<parent collection name>/:<parent member name>_id". * * If ``parentResource`` is supplied and ``namePrefix`` isn't, * ``namePrefix`` will be generated from ``parentResource`` as * "<parent member name>_". * * Example:: * * $m = new Horde_Routes_Mapper(); * $utils = $m->utils; * * $m->resource('location', 'locations', * array('parentResource' => * array('memberName' => 'region', * 'collectionName' => 'regions')))); * # pathPrefix is "regions/:region_id" * # namePrefix is "region_" * * $utils->urlFor('region_locations', array('region_id'=>13)); * # '/regions/13/locations' * * $utils->urlFor('region_new_location', array('region_id'=>13)); * # '/regions/13/locations/new' * * $utils->urlFor('region_location', * array('region_id'=>13, 'id'=>60)); * # '/regions/13/locations/60' * * $utils->urlFor('region_edit_location', * array('region_id'=>13, 'id'=>60)); * # '/regions/13/locations/60/edit' * * Overriding generated ``pathPrefix``:: * * $m = new Horde_Routes_Mapper(); * $utils = new Horde_Routes_Utils(); * * $m->resource('location', 'locations', * array('parentResource' => * array('memberName' => 'region', * 'collectionName' => 'regions'), * 'pathPrefix' => 'areas/:area_id'))); * # name prefix is "region_" * * $utils->urlFor('region_locations', array('area_id'=>51)); * # '/areas/51/locations' * * Overriding generated ``namePrefix``:: * * $m = new Horde_Routes_Mapper * $m->resource('location', 'locations', * array('parentResource' => * array('memberName' => 'region', * 'collectionName' => 'regions'), * 'namePrefix' => ''))); * # pathPrefix is "regions/:region_id" * * $utils->urlFor('locations', array('region_id'=>51)); * # '/regions/51/locations' * * Note: Since Horde Routes 0.2.0 and Python Routes 1.8, this method is * not compatible with earlier versions inasmuch as the semicolon is no * longer used to delimit custom actions. This was a change in Rails * itself (http://dev.rubyonrails.org/changeset/6485) and adopting it * here allows us to keep parity with Rails and ActiveResource. * * @param string $memberName Singular version of the resource name * @param string $collectionName Collection name (plural of $memberName) * @param array $kargs Keyword arguments (see above) * @return void */ public function resource($memberName, $collectionName, $kargs = array()) { $defaultKargs = array('collection' => array(), 'member' => array(), 'new' => array(), 'pathPrefix' => null, 'namePrefix' => null, 'parentResource' => null); $kargs = array_merge($defaultKargs, $kargs); // Generate ``pathPrefix`` if ``pathPrefix`` wasn't specified and // ``parentResource`` was. Likewise for ``namePrefix``. Make sure // that ``pathPrefix`` and ``namePrefix`` *always* take precedence if // they are specified--in particular, we need to be careful when they // are explicitly set to "". if ($kargs['parentResource'] !== null) { if ($kargs['pathPrefix'] === null) { $kargs['pathPrefix'] = $kargs['parentResource']['collectionName'] . '/:' . $kargs['parentResource']['memberName'] . '_id'; } if ($kargs['namePrefix'] === null) { $kargs['namePrefix'] = $kargs['parentResource']['memberName'] . '_'; } } else { if ($kargs['pathPrefix'] === null) { $kargs['pathPrefix'] = ''; } if ($kargs['namePrefix'] === null) { $kargs['namePrefix'] = ''; } } // Ensure the edit and new actions are in and GET $kargs['member']['edit'] = 'GET'; $kargs['new']['new'] = 'GET'; // inline python method swap() moved below as _swap() $collectionMethods = $this->_swap($kargs['collection'], array()); $memberMethods = $this->_swap($kargs['member'], array()); $newMethods = $this->_swap($kargs['new'], array()); // Insert create, update, and destroy methods if (!isset($collectionMethods['POST'])) { $collectionMethods['POST'] = array(); } array_unshift($collectionMethods['POST'], 'create'); if (!isset($memberMethods['PUT'])) { $memberMethods['PUT'] = array(); } array_unshift($memberMethods['PUT'], 'update'); if (!isset($memberMethods['DELETE'])) { $memberMethods['DELETE'] = array(); } array_unshift($memberMethods['DELETE'], 'delete'); // If there's a path prefix option, use it with the controller $controller = $this->_stripSlashes($collectionName); $kargs['pathPrefix'] = $this->_stripSlashes($kargs['pathPrefix']); if ($kargs['pathPrefix']) { $path = $kargs['pathPrefix'] . '/' . $controller; } else { $path = $controller; } $collectionPath = $path; $newPath = $path . '/new'; $memberPath = $path . '/:(id)'; $options = array( 'controller' => (isset($kargs['controller']) ? $kargs['controller'] : $controller), '_memberName' => $memberName, '_collectionName' => $collectionName, '_parentResource' => $kargs['parentResource'] ); // inline python method requirements_for() moved below as _requirementsFor() // Add the routes for handling collection methods foreach ($collectionMethods as $method => $lst) { $primary = ($method != 'GET' && isset($lst[0])) ? array_shift($lst) : null; $routeOptions = $this->_requirementsFor($method, $options); foreach ($lst as $action) { $routeOptions['action'] = $action; $routeName = sprintf('%s%s_%s', $kargs['namePrefix'], $action, $collectionName); $this->connect($routeName, sprintf("%s/%s", $collectionPath, $action), $routeOptions); $this->connect('formatted_' . $routeName, sprintf("%s/%s.:(format)", $collectionPath, $action), $routeOptions); } if ($primary) { $routeOptions['action'] = $primary; $this->connect($collectionPath, $routeOptions); $this->connect($collectionPath . '.:(format)', $routeOptions); } } // Specifically add in the built-in 'index' collection method and its // formatted version $connectkargs = array('action' => 'index', 'conditions' => array('method' => array('GET'))); $this->connect($kargs['namePrefix'] . $collectionName, $collectionPath, array_merge($connectkargs, $options)); $this->connect('formatted_' . $kargs['namePrefix'] . $collectionName, $collectionPath . '.:(format)', array_merge($connectkargs, $options)); // Add the routes that deal with new resource methods foreach ($newMethods as $method => $lst) { $routeOptions = $this->_requirementsFor($method, $options); foreach ($lst as $action) { if ($action == 'new' && $newPath) { $path = $newPath; } else { $path = sprintf('%s/%s', $newPath, $action); } $name = 'new_' . $memberName; if ($action != 'new') { $name = $action . '_' . $name; } $routeOptions['action'] = $action; $this->connect($kargs['namePrefix'] . $name, $path, $routeOptions); if ($action == 'new' && $newPath) { $path = $newPath . '.:(format)'; } else { $path = sprintf('%s/%s.:(format)', $newPath, $action); } $this->connect('formatted_' . $kargs['namePrefix'] . $name, $path, $routeOptions); } } $requirementsRegexp = '[\w\-_]+'; // Add the routes that deal with member methods of a resource foreach ($memberMethods as $method => $lst) { $routeOptions = $this->_requirementsFor($method, $options); $routeOptions['requirements'] = array('id' => $requirementsRegexp); if (!in_array($method, array('POST', 'GET', 'any'))) { $primary = array_shift($lst); } else { $primary = null; } foreach ($lst as $action) { $routeOptions['action'] = $action; $this->connect(sprintf('%s%s_%s', $kargs['namePrefix'], $action, $memberName), sprintf('%s/%s', $memberPath, $action), $routeOptions); $this->connect(sprintf('formatted_%s%s_%s', $kargs['namePrefix'], $action, $memberName), sprintf('%s/%s.:(format)', $memberPath, $action), $routeOptions); } if ($primary) { $routeOptions['action'] = $primary; $this->connect($memberPath, $routeOptions); $this->connect($memberPath . '.:(format)', $routeOptions); } } // Specifically add the member 'show' method $routeOptions = $this->_requirementsFor('GET', $options); $routeOptions['action'] = 'show'; $routeOptions['requirements'] = array('id' => $requirementsRegexp); $this->connect($kargs['namePrefix'] . $memberName, $memberPath, $routeOptions); $this->connect('formatted_' . $kargs['namePrefix'] . $memberName, $memberPath . '.:(format)', $routeOptions); } /** * Returns a new dict to be used for all route creation as * the route options. * @see resource() * * @param string $method Request method ('get', 'post', etc.) or 'any' * @param array $options Assoc. array to populate with 'conditions' key * @return $options populated */ protected function _requirementsFor($meth, $options) { if ($meth != 'any') { $options['conditions'] = array('method' => array(Horde_String::upper($meth))); } return $options; } /** * Swap the keys and values in the dict, and uppercase the values * from the dict during the swap. * @see resource() * * @param array $dct Input dict (assoc. array) * @param array $newdct Output dict to populate * @return array $newdct populated */ protected function _swap($dct, $newdct) { foreach ($dct as $key => $val) { $newkey = Horde_String::upper($val); if (!isset($newdct[$newkey])) { $newdct[$newkey] = array(); } $newdct[$newkey][] = $key; } return $newdct; } /** * Sort an array of Horde_Routes_Routes to using _keycmp() for the comparision * to order them ideally for matching. * * An unfortunate property of PHP's usort() is that if two members compare * equal, their order in the sorted array is undefined (see PHP manual). * This is unsuitable for us because the order that the routes were * connected to the mapper is significant. * * Uses this method uses merge sort algorithm based on the * comments in http://www.php.net/usort * * @param array $array Array Horde_Routes_Route objects to sort (by reference) * @return void */ protected function _keysort(&$array) { // arrays of size < 2 require no action. if (count($array) < 2) { return; } // split the array in half $halfway = count($array) / 2; $array1 = array_slice($array, 0, $halfway); $array2 = array_slice($array, $halfway); // recurse to sort the two halves $this->_keysort($array1); $this->_keysort($array2); // if all of $array1 is <= all of $array2, just append them. if ($this->_keycmp(end($array1), $array2[0]) < 1) { $array = array_merge($array1, $array2); return; } // merge the two sorted arrays into a single sorted array $array = array(); $ptr1 = 0; $ptr2 = 0; while ($ptr1 < count($array1) && $ptr2 < count($array2)) { if ($this->_keycmp($array1[$ptr1], $array2[$ptr2]) < 1) { $array[] = $array1[$ptr1++]; } else { $array[] = $array2[$ptr2++]; } } // merge the remainder while ($ptr1 < count($array1)) { $array[] = $array1[$ptr1++]; } while ($ptr2 < count($array2)) { $array[] = $array2[$ptr2++]; } return; } /** * Compare two Horde_Route_Routes objects by their keys against * the instance variable $keysortTmp. Used by _keysort(). * * @param array $a First dict (assoc. array) * @param array $b Second dict * @return integer */ protected function _keycmp($a, $b) { $keys = $this->_keysortTmp; $am = $a->minKeys; $a = $a->maxKeys; $b = $b->maxKeys; $lendiffa = count(array_diff($keys, $a)); $lendiffb = count(array_diff($keys, $b)); // If they both match, don't switch them if ($lendiffa == 0 && $lendiffb == 0) { return 0; } // First, if $a matches exactly, use it if ($lendiffa == 0) { return -1; } // Or $b matches exactly, use it if ($lendiffb == 0) { return 1; } // Neither matches exactly, return the one with the most in common if ($this->_cmp($lendiffa, $lendiffb) != 0) { return $this->_cmp($lendiffa, $lendiffb); } // Neither matches exactly, but if they both have just as much in common if (count($this->_arrayUnion($keys, $b)) == count($this->_arrayUnion($keys, $a))) { return $this->_cmp(count($a), count($b)); // Otherwise, we return the one that has the most in common } else { return $this->_cmp(count($this->_arrayUnion($keys, $b)), count($this->_arrayUnion($keys, $a))); } } /** * Create a union of two arrays. * * @param array $a First array * @param array $b Second array * @return array Union of $a and $b */ protected function _arrayUnion($a, $b) { return array_merge(array_diff($a, $b), array_diff($b, $a), array_intersect($a, $b)); } /** * Equivalent of Python's cmp() function. * * @param integer|float $a First item to compare * @param integer|flot $b Second item to compare * @param integer Result of comparison */ protected function _cmp($a, $b) { if ($a < $b) { return -1; } if ($a == $b) { return 0; } return 1; } /** * Trims slashes from the beginning or end of a part/URL. * * @param string $name Part or URL with slash at begin/end * @return string Part or URL with begin/end slashes removed */ protected function _stripSlashes($name) { if (substr($name, 0, 1) == '/') { $name = substr($name, 1); } if (substr($name, -1, 1) == '/') { $name = substr($name, 0, -1); } return $name; } }
Simpan