exit(1);
}
-require_once 'Net/URL/Mapper.php';
-
-class StatusNet_URL_Mapper extends Net_URL_Mapper
-{
- private static $_singleton = null;
- private $_actionToPath = array();
-
- private function __construct()
- {
- }
-
- public static function getInstance($id = '__default__')
- {
- if (empty(self::$_singleton)) {
- self::$_singleton = new StatusNet_URL_Mapper();
- }
- return self::$_singleton;
- }
-
- public function connect($path, $defaults = array(), $rules = array())
- {
- $result = null;
- if (Event::handle('StartConnectPath', array(&$path, &$defaults, &$rules, &$result))) {
- $result = parent::connect($path, $defaults, $rules);
- if (array_key_exists('action', $defaults)) {
- $action = $defaults['action'];
- } elseif (array_key_exists('action', $rules)) {
- $action = $rules['action'];
- } else {
- $action = null;
- }
- $this->_mapAction($action, $result);
- Event::handle('EndConnectPath', array($path, $defaults, $rules, $result));
- }
- return $result;
- }
-
- protected function _mapAction($action, $path)
- {
- if (!array_key_exists($action, $this->_actionToPath)) {
- $this->_actionToPath[$action] = array();
- }
- $this->_actionToPath[$action][] = $path;
- return;
- }
-
- public function generate($values = array(), $qstring = array(), $anchor = '')
- {
- if (!array_key_exists('action', $values)) {
- return parent::generate($values, $qstring, $anchor);
- }
-
- $action = $values['action'];
-
- if (!array_key_exists($action, $this->_actionToPath)) {
- return parent::generate($values, $qstring, $anchor);
- }
-
- $oldPaths = $this->paths;
- $this->paths = $this->_actionToPath[$action];
- $result = parent::generate($values, $qstring, $anchor);
- $this->paths = $oldPaths;
-
- return $result;
- }
-}
-
/**
* URL Router
*
* you're running and the plugins that are enabled. To avoid having bad routes
* get stuck in the cache, the key includes a list of plugins and the software
* version.
- *
- * There can still be problems with a) differences in versions of the plugins and
+ *
+ * There can still be problems with a) differences in versions of the plugins and
* b) people running code between official versions, but these tend to be more
* sophisticated users who can grok what's going on and clear their caches.
*
function initialize()
{
- $m = StatusNet_URL_Mapper::getInstance();
+ $m = new URLMapper();
if (Event::handle('StartInitializeRouter', array(&$m))) {
{
try {
$match = $this->m->match($path);
- } catch (Net_URL_Mapper_InvalidException $e) {
+ } catch (Exception $e) {
common_log(LOG_ERR, "Problem getting route for $path - " .
$e->getMessage());
// TRANS: Client error on action trying to visit a non-existing page.
}
$url = $this->m->generate($args, $params, $fragment);
-
// Due to a bug in the Net_URL_Mapper code, the returned URL may
// contain a malformed query of the form ?p1=v1?p2=v2?p3=v3. We
// repair that here rather than modifying the upstream code...
--- /dev/null
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * URL mapper
+ *
+ * PHP version 5
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Cache
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2011 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+ // This check helps protect against security problems;
+ // your code file can't be executed directly from the web.
+ exit(1);
+}
+
+/**
+ * URL mapper
+ *
+ * Converts a path into a set of parameters, and vice versa
+ *
+ * We used to use Net_URL_Mapper, so there's a wrapper class at Router, q.v.
+ *
+ * NUM's vagaries are the main reason we have weirdnesses here.
+ *
+ * @category URL
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @copyright 2011 StatusNet, Inc.
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link http://status.net/
+ */
+
+class URLMapper
+{
+ const ACTION = 'action';
+
+ protected $statics = array();
+ protected $variables = array();
+ protected $reverse = array();
+
+ function connect($path, $args, $paramPatterns=null)
+ {
+ if (!array_key_exists(self::ACTION, $args)) {
+ throw new Exception(sprintf("Can't connect %s; path has no action.", $path));
+ }
+
+ $action = $args[self::ACTION];
+
+ $paramNames = $this->getParamNames($path);
+
+ if (empty($paramNames)) {
+ $this->statics[$path] = $args;
+ if (array_key_exists($action, $this->reverse)) {
+ $this->reverse[$args[self::ACTION]][] = array($args, $path);
+ } else {
+ $this->reverse[$args[self::ACTION]] = array(array($args, $path));
+ }
+ } else {
+
+ // Eff if I understand why some go here and some go there.
+ // Anyways, fixup my preconceptions
+
+ foreach ($paramNames as $name) {
+ if (!array_key_exists($name, $paramPatterns) &&
+ array_key_exists($name, $args)) {
+ $paramPatterns[$name] = $args[$name];
+ unset($args[$name]);
+ }
+ }
+
+ $regex = $this->makeRegex($path, $paramPatterns);
+
+ $this->variables[] = array($args, $regex, $paramNames);
+
+ $format = $this->makeFormat($path, $paramPatterns);
+
+ if (array_key_exists($action, $this->reverse)) {
+ $this->reverse[$args[self::ACTION]][] = array($args, $format, $paramNames);
+ } else {
+ $this->reverse[$args[self::ACTION]] = array(array($args, $format, $paramNames));
+ }
+ }
+ }
+
+ function match($path)
+ {
+ if (array_key_exists($path, $this->statics)) {
+ return $this->statics[$path];
+ }
+
+ foreach ($this->variables as $pattern) {
+ list($args, $regex, $paramNames) = $pattern;
+ if (preg_match($regex, $path, $match)) {
+ $results = $args;
+ foreach ($paramNames as $name) {
+ $results[$name] = $match[$name];
+ }
+ return $results;
+ }
+ }
+
+ throw new Exception(sprintf('No match for path "%s"', $path));
+ }
+
+ function generate($args, $qstring, $fragment)
+ {
+ if (!array_key_exists(self::ACTION, $args)) {
+ throw new Exception("Every path needs an action.");
+ }
+
+ $action = $args[self::ACTION];
+
+ if (!array_key_exists($action, $this->reverse)) {
+ throw new Exception(sprintf('No candidate paths for action "%s"', $action));
+ }
+
+ $candidates = $this->reverse[$action];
+
+ foreach ($candidates as $candidate) {
+ if (count($candidate) == 2) { // static
+ list($tryArgs, $tryPath) = $candidate;
+ foreach ($tryArgs as $key => $value) {
+ if (!array_key_exists($key, $args) || $args[$key] != $value) {
+ // next candidate
+ continue 2;
+ }
+ }
+ // success
+ return $tryPath;
+ } else {
+ list($tryArgs, $format, $paramNames) = $candidate;
+
+ foreach ($tryArgs as $key => $value) {
+ if (!array_key_exists($key, $args) || $args[$key] != $value) {
+ // next candidate
+ continue 2;
+ }
+ }
+
+ // success
+
+ $toFormat = array();
+
+ foreach ($paramNames as $name) {
+ if (!array_key_exists($name, $args)) {
+ // next candidate
+ continue 2;
+ }
+ $toFormat[] = $args[$name];
+ }
+
+ $path = vsprintf($format, $toFormat);
+
+ if (!empty($qstring)) {
+ $path .= '?' . http_build_query($qstring);
+ }
+
+ return $path;
+ }
+ }
+
+ unset($args['action']);
+
+ if (empty($args)) {
+ throw new Exception(sprintf('No matches for action "%s"', $action));
+ }
+
+ $argstring = '';
+
+ foreach ($args as $key => $value) {
+ $argstring .= "$key=$value ";
+ }
+
+ throw new Exception(sprintf('No matches for action "%s" with arguments "%s"', $action, $argstring));
+ }
+
+ protected function getParamNames($path)
+ {
+ preg_match_all('/:(?P<name>\w+)/', $path, $match);
+ return $match['name'];
+ }
+
+ protected function makeRegex($path, $paramPatterns)
+ {
+ $pr = new PatternReplacer($paramPatterns);
+
+ $regex = preg_replace_callback('/:(\w+)/',
+ array($pr, 'toPattern'),
+ $path);
+
+ $regex = '#' . str_replace('#', '\#', $regex) . '#';
+
+ return $regex;
+ }
+
+ protected function makeFormat($path, $paramPatterns)
+ {
+ $format = preg_replace('/(:\w+)/', '%s', $path);
+
+ return $format;
+ }
+}
+
+class PatternReplacer
+{
+ private $patterns;
+
+ function __construct($patterns)
+ {
+ $this->patterns = $patterns;
+ }
+
+ function toPattern($matches)
+ {
+ // trim out the :
+ $name = $matches[1];
+ if (array_key_exists($name, $this->patterns)) {
+ $pattern = $this->patterns[$name];
+ } else {
+ // ???
+ $pattern = '\w+';
+ }
+ return '(?P<'.$name.'>'.$pattern.')';
+ }
+}