ISSO is no longer a product regularly released so we'll remove the issoversion tag...
[isso.git] / Router.php
1 <?php
2 /*=====================================================================*
3 || ###################################################################
4 || # Blue Static ISSO Framework
5 || # Copyright ©2002-[#]year[#] Blue Static
6 || #
7 || # This program is free software; you can redistribute it and/or modify
8 || # it under the terms of the GNU General Public License as published by
9 || # the Free Software Foundation; version [#]gpl[#] of the License.
10 || #
11 || # This program is distributed in the hope that it will be useful, but
12 || # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 || # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 || # more details.
15 || #
16 || # You should have received a copy of the GNU General Public License along
17 || # with this program; if not, write to the Free Software Foundation, Inc.,
18 || # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
19 || ###################################################################
20 \*=====================================================================*/
21
22 /**
23 * Request Router (Router.php)
24 *
25 * @package ISSO
26 */
27
28 require_once('ISSO/Functions.php');
29 require_once('ISSO/RouterController.php');
30
31 /**
32 * Router
33 *
34 * Run dispatch() in a file with a defined routing pattern and send all
35 * requests to that file, this will then load and appropriatley run
36 * the right source files. You can use mod_rewrite like this:
37 *
38 * RewriteEngine on
39 * RewriteCond %{REQUEST_URI} !static/(.*)$
40 * RewriteRule ^(.*)$ index.php
41 *
42 * This will prevent the rewriting on any file in the static/ directory.
43 * If your server cannot do this rewriting, then simply define this constant:
44 * ISSO_ROUTER_NO_REWRITE as TRUE and it will switch to standard URLs
45 *
46 * @author Blue Static
47 * @copyright Copyright (c)2002 - [#]year[#], Blue Static
48 * @version $Revision$
49 * @package ISSO
50 *
51 */
52 class BSRouter
53 {
54 const MAP_LOADER = 1;
55 const MAP_PARAMS = 2;
56
57 /**
58 * Routing map of request:(controller,:method,args...)
59 * @var array
60 */
61 private $map = array();
62
63 /**
64 * The base URL
65 * @var string
66 */
67 private $basePath;
68
69 /**
70 * Map action for errors
71 * @var string
72 */
73 private $errorAction = ':undefined:';
74
75 /**
76 * Router controller requested
77 * @var string
78 */
79 private $controller;
80
81 /**
82 * Router action requsted
83 * @var string
84 */
85 private $action;
86
87 /**
88 * Request paramaters
89 * @var array
90 */
91 private $params = array();
92
93 // ###################################################################
94 /**
95 * Constructor
96 */
97 public function __construct()
98 {
99 $this->basePath = str_replace(basename($_SERVER['PHP_SELF']), '', $_SERVER['SCRIPT_NAME']);
100 }
101
102 // ###################################################################
103 /**
104 * Listens for routing requests and handles them appropriately
105 */
106 public function dispatch()
107 {
108 if ((defined('ISSO_ROUTER_NO_REWRITE') AND constant('ISSO_ROUTER_NO_REWRITE')) OR isset($_REQUEST['controller']))
109 {
110 $this->request = (isset($_REQUEST['controller']) ? $_REQUEST['controller'] : '');
111 $this->action = (isset($_REQUEST['action']) ? $_REQUEST['action'] : '');
112 $this->params = $_GET;
113 }
114 else
115 {
116 $request = str_replace($this->basePath, '', $_SERVER['REQUEST_URI']);
117
118 $params = explode('/', $request);
119 $request = explode('.', $params[0]);
120 $this->request = $request[0];
121 $this->action = (isset($request[1]) ? $request[1] : '');
122 unset($params[0]);
123
124 if (sizeof($params) == 0)
125 {
126 // do nothing
127 }
128 else if (sizeof($params) == 1)
129 {
130 $this->params[':id'] = $params[1];
131 }
132 else
133 {
134 $key = '';
135 for ($i = 1; $i <= sizeof($params); $i++)
136 {
137 if ($i == 1 AND $params["$i"][0] != '+')
138 {
139 $this->params[':id'] = $params["$i"];
140 continue;
141 }
142
143 if ($params["$i"] == '')
144 {
145 continue;
146 }
147
148 if ($params["$i"][0] == '+')
149 {
150 if (!empty($key))
151 {
152 $this->_error(sprintf(_('The paramter "%1$s" does not have a value'), $key));
153 }
154 $key = substr($params["$i"], 1);
155 }
156 else
157 {
158 if (empty($key))
159 {
160 $this->_error(sprintf(_('The value "%1$s" does not have a paramatized key'), $params["$i"]));
161 }
162 $this->params["$key"] = $params["$i"];
163 $key = '';
164 }
165 }
166 }
167 }
168
169 $this->request = (empty($this->request) ? 'Index' : $this->request);
170 $this->action = (empty($this->action) ? 'Index' : $this->action);
171
172 if (isset($this->map[ $this->request ]))
173 {
174 call_user_func_array(array($this, $this->map[ $this->request ][self::MAP_LOADER]), $this->map[ $this->request ][self::MAP_PARAMS]);
175 }
176 else
177 {
178 $this->_error(sprintf(_('Routing request "%1$s" not found in map'), $this->request));
179 }
180 }
181
182 // ###################################################################
183 /**
184 * Sets the error handler of the router; when an error occurs while
185 * routing, this method is called
186 *
187 * @param string Error handler name
188 */
189 public function setErrorAction($name)
190 {
191 $this->errorAction = $name;
192 }
193
194 // ###################################################################
195 /**
196 * Adds a directory of files to the maps, with the file names (without .php)
197 * as the map request name
198 *
199 * @param string Directory path
200 */
201 public function addFileDirectory($path)
202 {
203 $path = BSFunctions::FetchSourcePath($path);
204 $controllers = BSFunctions::ScanDirectory($path);
205
206 foreach ($controllers AS $file)
207 {
208 $controller = str_replace('.php', '', $file);
209 $this->addFileRequest($controller, $path . $file);
210 }
211 }
212
213 // ###################################################################
214 /**
215 * Routes a request to the loading of a file; this is the most common
216 * routing request and it performs no extra functions
217 *
218 * @param string Request name
219 * @param string File name
220 */
221 public function addFileRequest($request, $file)
222 {
223 $this->map["$request"] = array(self::MAP_LOADER => '_loadFile', self::MAP_PARAMS => array($file));
224 }
225
226 // ###################################################################
227 /**
228 * Adds a list of files to the mapping; the controller will become
229 * the file name without the .php extension
230 *
231 * @param string Search path
232 */
233 public function addControllerDirectory($path)
234 {
235 $path = BSFunctions::FetchSourcePath($path);
236 $controllers = BSFunctions::ScanDirectory($path);
237
238 foreach ($controllers AS $file)
239 {
240 $controller = str_replace('.php', '', $file);
241 $this->addController($controller, $path . $controller);
242 }
243 }
244
245 // ###################################################################
246 /**
247 * Routes a request to the controller system
248 *
249 * @param string Request name
250 * @param string Controller name; the file must be named <X>.php and define a class <X>Controller.php
251 */
252 public function addController($request, $file)
253 {
254 $this->map["$request"] = array(self::MAP_LOADER => '_invokeController', self::MAP_PARAMS => array($file));
255 }
256
257 // ###################################################################
258 /**
259 * Includes a given file name
260 *
261 * @param string File name
262 */
263 private function _loadFile($filename)
264 {
265 if (!file_exists($filename))
266 {
267 $this->_error(sprintf(_('Cannot find the file "%1$s"'), $filename));
268 }
269 include($filename);
270 }
271
272 // ###################################################################
273 /**
274 * Loads and then invokes a controller
275 *
276 * @param string File name of the controller
277 */
278 public function _invokeController($controller)
279 {
280 $this->_loadFile($controller);
281
282 $controllerName = basename($controller, '.php');
283 $className = "{$controllerName}Controller";
284
285 if (!class_exists($className))
286 {
287 trigger_error('The controller "' . $className . '" does not exist and therfore cannot have actions forwarded to it');
288 }
289
290 if (!is_subclass_of($className, 'BSRouterController'))
291 {
292 trigger_error('Cannot invoke controller-style actions on a non-controller: requested controller is not of the type BSRouterController');
293 }
294
295 // these go out the door so BSInput will process them and merge them into BSInput->in[]
296 $_GET = $this->params;
297 $request = new $className(BSRegister::LoadModule('Input'));
298
299 if (!method_exists($className, $this->action))
300 {
301 trigger_error('Invalid action called on controller: ' . $className . ' does not respond to ' . $this->action . '()');
302 }
303
304 $request->{$this->action}();
305 }
306
307 // ###################################################################
308 /**
309 * Sends an error to the error action handler
310 *
311 * @param string Error message
312 */
313 private function _error($errMsg)
314 {
315 if (function_exists($this->errorAction) OR (is_array($this->errorAction) AND method_exists($this->errorAction[0], $this->errorAction[1])))
316 {
317 $this->errorAction($errMsg);
318 }
319 else
320 {
321 trigger_error($errMsg);
322 }
323 exit;
324 }
325 }
326
327 /*=====================================================================*
328 || ###################################################################
329 || # $HeadURL$
330 || # $Id$
331 || ###################################################################
332 \*=====================================================================*/
333 ?>