]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/urlmapper.php
Merge branch '1.0.x' into nummedout
[quix0rs-gnu-social.git] / lib / urlmapper.php
1 <?php
2 /**
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2011, StatusNet, Inc.
5  *
6  * URL mapper
7  * 
8  * PHP version 5
9  *
10  * This program is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU Affero General Public License as published by
12  * the Free Software Foundation, either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Affero General Public License for more details.
19  *
20  * You should have received a copy of the GNU Affero General Public License
21  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  *
23  * @category  Cache
24  * @package   StatusNet
25  * @author    Evan Prodromou <evan@status.net>
26  * @copyright 2011 StatusNet, Inc.
27  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
28  * @link      http://status.net/
29  */
30
31 if (!defined('STATUSNET')) {
32     // This check helps protect against security problems;
33     // your code file can't be executed directly from the web.
34     exit(1);
35 }
36
37 /**
38  * URL mapper
39  *
40  * Converts a path into a set of parameters, and vice versa
41  *
42  * We used to use Net_URL_Mapper, so there's a wrapper class at Router, q.v.
43  * 
44  * NUM's vagaries are the main reason we have weirdnesses here.
45  *
46  * @category  URL
47  * @package   StatusNet
48  * @author    Evan Prodromou <evan@status.net>
49  * @copyright 2011 StatusNet, Inc.
50  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
51  * @link      http://status.net/
52  */
53
54 class URLMapper
55 {
56     const ACTION = 'action';
57
58     protected $statics = array();
59     protected $variables = array();
60     protected $reverse = array();
61
62     function connect($path, $args, $paramPatterns=null)
63     {
64         if (!array_key_exists(self::ACTION, $args)) {
65             throw new Exception(sprintf("Can't connect %s; path has no action.", $path));
66         }
67
68         $action = $args[self::ACTION];
69
70         $paramNames = $this->getParamNames($path);
71
72         if (empty($paramNames)) {
73             $this->statics[$path] = $args;
74             if (array_key_exists($action, $this->reverse)) {
75                 $this->reverse[$args[self::ACTION]][] = array($args, $path);
76             } else {
77                 $this->reverse[$args[self::ACTION]] = array(array($args, $path));
78             }
79         } else {
80
81             // Eff if I understand why some go here and some go there.
82             // Anyways, fixup my preconceptions
83
84             foreach ($paramNames as $name) {
85                 if (!array_key_exists($name, $paramPatterns) &&
86                     array_key_exists($name, $args)) {
87                     $paramPatterns[$name] = $args[$name];
88                     unset($args[$name]);
89                 }
90             }
91
92             $regex = $this->makeRegex($path, $paramPatterns);
93
94             $this->variables[] = array($args, $regex, $paramNames);
95
96             $format = $this->makeFormat($path, $paramPatterns);
97
98             if (array_key_exists($action, $this->reverse)) {
99                 $this->reverse[$args[self::ACTION]][] = array($args, $format, $paramNames);
100             } else {
101                 $this->reverse[$args[self::ACTION]] = array(array($args, $format, $paramNames));
102             }
103         }
104     }
105
106     function match($path)
107     {
108         if (array_key_exists($path, $this->statics)) {
109             return $this->statics[$path];
110         }
111
112         foreach ($this->variables as $pattern) {
113             list($args, $regex, $paramNames) = $pattern;
114             if (preg_match($regex, $path, $match)) {
115                 $results = $args;
116                 foreach ($paramNames as $name) {
117                     $results[$name] = $match[$name];
118                 }
119                 return $results;
120             }
121         }
122         
123         throw new Exception(sprintf('No match for path "%s"', $path));
124     }
125
126     function generate($args, $qstring, $fragment)
127     {
128         if (!array_key_exists(self::ACTION, $args)) {
129             throw new Exception("Every path needs an action.");
130         }
131
132         $action = $args[self::ACTION];
133
134         if (!array_key_exists($action, $this->reverse)) {
135             throw new Exception(sprintf('No candidate paths for action "%s"', $action));
136         }
137
138         $candidates = $this->reverse[$action];
139
140         foreach ($candidates as $candidate) {
141             if (count($candidate) == 2) { // static
142                 list($tryArgs, $tryPath) = $candidate;
143                 foreach ($tryArgs as $key => $value) {
144                     if (!array_key_exists($key, $args) || $args[$key] != $value) {
145                         // next candidate
146                         continue 2;
147                     }
148                 }
149                 // success
150                 return $tryPath;
151             } else {
152                 list($tryArgs, $format, $paramNames) = $candidate;
153
154                 foreach ($tryArgs as $key => $value) {
155                     if (!array_key_exists($key, $args) || $args[$key] != $value) {
156                         // next candidate
157                         continue 2;
158                     }
159                 }
160
161                 // success
162
163                 $toFormat = array();
164
165                 foreach ($paramNames as $name) {
166                     if (!array_key_exists($name, $args)) {
167                         // next candidate
168                         continue 2;
169                     }
170                     $toFormat[] = $args[$name];
171                 }
172
173                 $path = vsprintf($format, $toFormat);
174
175                 if (!empty($qstring)) {
176                     $path .= '?' . http_build_query($qstring);
177                 }
178
179                 return $path;
180             }
181         }
182
183         unset($args['action']);
184
185         if (empty($args)) {
186             throw new Exception(sprintf('No matches for action "%s"', $action));
187         }
188
189         $argstring = '';
190
191         foreach ($args as $key => $value) {
192             $argstring .= "$key=$value ";
193         }
194
195         throw new Exception(sprintf('No matches for action "%s" with arguments "%s"', $action, $argstring));
196     }
197
198     protected function getParamNames($path)
199     {
200         preg_match_all('/:(?P<name>\w+)/', $path, $match);
201         return $match['name'];
202     }
203
204     protected function makeRegex($path, $paramPatterns)
205     {
206         $pr = new PatternReplacer($paramPatterns);
207
208         $regex = preg_replace_callback('/:(\w+)/',
209                                        array($pr, 'toPattern'),
210                                        $path);
211
212         $regex = '#' . str_replace('#', '\#', $regex) . '#';
213
214         return $regex;
215     }
216
217     protected function makeFormat($path, $paramPatterns)
218     {
219         $format = preg_replace('/(:\w+)/', '%s', $path);
220
221         return $format;
222     }
223 }
224
225 class PatternReplacer
226 {
227     private $patterns;
228
229     function __construct($patterns)
230     {
231         $this->patterns = $patterns;
232     }
233
234     function toPattern($matches)
235     {
236         // trim out the :
237         $name = $matches[1];
238         if (array_key_exists($name, $this->patterns)) {
239             $pattern = $this->patterns[$name];
240         } else {
241             // ???
242             $pattern = '\w+';
243         }
244         return '(?P<'.$name.'>'.$pattern.')';
245     }
246 }