|
1 <?php |
|
2 /** |
|
3 * Zend Framework |
|
4 * |
|
5 * LICENSE |
|
6 * |
|
7 * This source file is subject to the new BSD license that is bundled |
|
8 * with this package in the file LICENSE.txt. |
|
9 * It is also available through the world-wide-web at this URL: |
|
10 * http://framework.zend.com/license/new-bsd |
|
11 * If you did not receive a copy of the license and are unable to |
|
12 * obtain it through the world-wide-web, please send an email |
|
13 * to license@zend.com so we can send you a copy immediately. |
|
14 * |
|
15 * @category Zend |
|
16 * @package Zend_Controller |
|
17 * @subpackage Dispatcher |
|
18 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
19 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
20 * @version $Id: Standard.php 22038 2010-04-28 18:54:22Z matthew $ |
|
21 */ |
|
22 |
|
23 /** Zend_Loader */ |
|
24 require_once 'Zend/Loader.php'; |
|
25 |
|
26 /** Zend_Controller_Dispatcher_Abstract */ |
|
27 require_once 'Zend/Controller/Dispatcher/Abstract.php'; |
|
28 |
|
29 /** |
|
30 * @category Zend |
|
31 * @package Zend_Controller |
|
32 * @subpackage Dispatcher |
|
33 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
34 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
35 */ |
|
36 class Zend_Controller_Dispatcher_Standard extends Zend_Controller_Dispatcher_Abstract |
|
37 { |
|
38 /** |
|
39 * Current dispatchable directory |
|
40 * @var string |
|
41 */ |
|
42 protected $_curDirectory; |
|
43 |
|
44 /** |
|
45 * Current module (formatted) |
|
46 * @var string |
|
47 */ |
|
48 protected $_curModule; |
|
49 |
|
50 /** |
|
51 * Controller directory(ies) |
|
52 * @var array |
|
53 */ |
|
54 protected $_controllerDirectory = array(); |
|
55 |
|
56 /** |
|
57 * Constructor: Set current module to default value |
|
58 * |
|
59 * @param array $params |
|
60 * @return void |
|
61 */ |
|
62 public function __construct(array $params = array()) |
|
63 { |
|
64 parent::__construct($params); |
|
65 $this->_curModule = $this->getDefaultModule(); |
|
66 } |
|
67 |
|
68 /** |
|
69 * Add a single path to the controller directory stack |
|
70 * |
|
71 * @param string $path |
|
72 * @param string $module |
|
73 * @return Zend_Controller_Dispatcher_Standard |
|
74 */ |
|
75 public function addControllerDirectory($path, $module = null) |
|
76 { |
|
77 if (null === $module) { |
|
78 $module = $this->_defaultModule; |
|
79 } |
|
80 |
|
81 $module = (string) $module; |
|
82 $path = rtrim((string) $path, '/\\'); |
|
83 |
|
84 $this->_controllerDirectory[$module] = $path; |
|
85 return $this; |
|
86 } |
|
87 |
|
88 /** |
|
89 * Set controller directory |
|
90 * |
|
91 * @param array|string $directory |
|
92 * @return Zend_Controller_Dispatcher_Standard |
|
93 */ |
|
94 public function setControllerDirectory($directory, $module = null) |
|
95 { |
|
96 $this->_controllerDirectory = array(); |
|
97 |
|
98 if (is_string($directory)) { |
|
99 $this->addControllerDirectory($directory, $module); |
|
100 } elseif (is_array($directory)) { |
|
101 foreach ((array) $directory as $module => $path) { |
|
102 $this->addControllerDirectory($path, $module); |
|
103 } |
|
104 } else { |
|
105 require_once 'Zend/Controller/Exception.php'; |
|
106 throw new Zend_Controller_Exception('Controller directory spec must be either a string or an array'); |
|
107 } |
|
108 |
|
109 return $this; |
|
110 } |
|
111 |
|
112 /** |
|
113 * Return the currently set directories for Zend_Controller_Action class |
|
114 * lookup |
|
115 * |
|
116 * If a module is specified, returns just that directory. |
|
117 * |
|
118 * @param string $module Module name |
|
119 * @return array|string Returns array of all directories by default, single |
|
120 * module directory if module argument provided |
|
121 */ |
|
122 public function getControllerDirectory($module = null) |
|
123 { |
|
124 if (null === $module) { |
|
125 return $this->_controllerDirectory; |
|
126 } |
|
127 |
|
128 $module = (string) $module; |
|
129 if (array_key_exists($module, $this->_controllerDirectory)) { |
|
130 return $this->_controllerDirectory[$module]; |
|
131 } |
|
132 |
|
133 return null; |
|
134 } |
|
135 |
|
136 /** |
|
137 * Remove a controller directory by module name |
|
138 * |
|
139 * @param string $module |
|
140 * @return bool |
|
141 */ |
|
142 public function removeControllerDirectory($module) |
|
143 { |
|
144 $module = (string) $module; |
|
145 if (array_key_exists($module, $this->_controllerDirectory)) { |
|
146 unset($this->_controllerDirectory[$module]); |
|
147 return true; |
|
148 } |
|
149 return false; |
|
150 } |
|
151 |
|
152 /** |
|
153 * Format the module name. |
|
154 * |
|
155 * @param string $unformatted |
|
156 * @return string |
|
157 */ |
|
158 public function formatModuleName($unformatted) |
|
159 { |
|
160 if (($this->_defaultModule == $unformatted) && !$this->getParam('prefixDefaultModule')) { |
|
161 return $unformatted; |
|
162 } |
|
163 |
|
164 return ucfirst($this->_formatName($unformatted)); |
|
165 } |
|
166 |
|
167 /** |
|
168 * Format action class name |
|
169 * |
|
170 * @param string $moduleName Name of the current module |
|
171 * @param string $className Name of the action class |
|
172 * @return string Formatted class name |
|
173 */ |
|
174 public function formatClassName($moduleName, $className) |
|
175 { |
|
176 return $this->formatModuleName($moduleName) . '_' . $className; |
|
177 } |
|
178 |
|
179 /** |
|
180 * Convert a class name to a filename |
|
181 * |
|
182 * @param string $class |
|
183 * @return string |
|
184 */ |
|
185 public function classToFilename($class) |
|
186 { |
|
187 return str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php'; |
|
188 } |
|
189 |
|
190 /** |
|
191 * Returns TRUE if the Zend_Controller_Request_Abstract object can be |
|
192 * dispatched to a controller. |
|
193 * |
|
194 * Use this method wisely. By default, the dispatcher will fall back to the |
|
195 * default controller (either in the module specified or the global default) |
|
196 * if a given controller does not exist. This method returning false does |
|
197 * not necessarily indicate the dispatcher will not still dispatch the call. |
|
198 * |
|
199 * @param Zend_Controller_Request_Abstract $action |
|
200 * @return boolean |
|
201 */ |
|
202 public function isDispatchable(Zend_Controller_Request_Abstract $request) |
|
203 { |
|
204 $className = $this->getControllerClass($request); |
|
205 if (!$className) { |
|
206 return false; |
|
207 } |
|
208 |
|
209 $finalClass = $className; |
|
210 if (($this->_defaultModule != $this->_curModule) |
|
211 || $this->getParam('prefixDefaultModule')) |
|
212 { |
|
213 $finalClass = $this->formatClassName($this->_curModule, $className); |
|
214 } |
|
215 if (class_exists($finalClass, false)) { |
|
216 return true; |
|
217 } |
|
218 |
|
219 $fileSpec = $this->classToFilename($className); |
|
220 $dispatchDir = $this->getDispatchDirectory(); |
|
221 $test = $dispatchDir . DIRECTORY_SEPARATOR . $fileSpec; |
|
222 return Zend_Loader::isReadable($test); |
|
223 } |
|
224 |
|
225 /** |
|
226 * Dispatch to a controller/action |
|
227 * |
|
228 * By default, if a controller is not dispatchable, dispatch() will throw |
|
229 * an exception. If you wish to use the default controller instead, set the |
|
230 * param 'useDefaultControllerAlways' via {@link setParam()}. |
|
231 * |
|
232 * @param Zend_Controller_Request_Abstract $request |
|
233 * @param Zend_Controller_Response_Abstract $response |
|
234 * @return void |
|
235 * @throws Zend_Controller_Dispatcher_Exception |
|
236 */ |
|
237 public function dispatch(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response) |
|
238 { |
|
239 $this->setResponse($response); |
|
240 |
|
241 /** |
|
242 * Get controller class |
|
243 */ |
|
244 if (!$this->isDispatchable($request)) { |
|
245 $controller = $request->getControllerName(); |
|
246 if (!$this->getParam('useDefaultControllerAlways') && !empty($controller)) { |
|
247 require_once 'Zend/Controller/Dispatcher/Exception.php'; |
|
248 throw new Zend_Controller_Dispatcher_Exception('Invalid controller specified (' . $request->getControllerName() . ')'); |
|
249 } |
|
250 |
|
251 $className = $this->getDefaultControllerClass($request); |
|
252 } else { |
|
253 $className = $this->getControllerClass($request); |
|
254 if (!$className) { |
|
255 $className = $this->getDefaultControllerClass($request); |
|
256 } |
|
257 } |
|
258 |
|
259 /** |
|
260 * Load the controller class file |
|
261 */ |
|
262 $className = $this->loadClass($className); |
|
263 |
|
264 /** |
|
265 * Instantiate controller with request, response, and invocation |
|
266 * arguments; throw exception if it's not an action controller |
|
267 */ |
|
268 $controller = new $className($request, $this->getResponse(), $this->getParams()); |
|
269 if (!($controller instanceof Zend_Controller_Action_Interface) && |
|
270 !($controller instanceof Zend_Controller_Action)) { |
|
271 require_once 'Zend/Controller/Dispatcher/Exception.php'; |
|
272 throw new Zend_Controller_Dispatcher_Exception( |
|
273 'Controller "' . $className . '" is not an instance of Zend_Controller_Action_Interface' |
|
274 ); |
|
275 } |
|
276 |
|
277 /** |
|
278 * Retrieve the action name |
|
279 */ |
|
280 $action = $this->getActionMethod($request); |
|
281 |
|
282 /** |
|
283 * Dispatch the method call |
|
284 */ |
|
285 $request->setDispatched(true); |
|
286 |
|
287 // by default, buffer output |
|
288 $disableOb = $this->getParam('disableOutputBuffering'); |
|
289 $obLevel = ob_get_level(); |
|
290 if (empty($disableOb)) { |
|
291 ob_start(); |
|
292 } |
|
293 |
|
294 try { |
|
295 $controller->dispatch($action); |
|
296 } catch (Exception $e) { |
|
297 // Clean output buffer on error |
|
298 $curObLevel = ob_get_level(); |
|
299 if ($curObLevel > $obLevel) { |
|
300 do { |
|
301 ob_get_clean(); |
|
302 $curObLevel = ob_get_level(); |
|
303 } while ($curObLevel > $obLevel); |
|
304 } |
|
305 throw $e; |
|
306 } |
|
307 |
|
308 if (empty($disableOb)) { |
|
309 $content = ob_get_clean(); |
|
310 $response->appendBody($content); |
|
311 } |
|
312 |
|
313 // Destroy the page controller instance and reflection objects |
|
314 $controller = null; |
|
315 } |
|
316 |
|
317 /** |
|
318 * Load a controller class |
|
319 * |
|
320 * Attempts to load the controller class file from |
|
321 * {@link getControllerDirectory()}. If the controller belongs to a |
|
322 * module, looks for the module prefix to the controller class. |
|
323 * |
|
324 * @param string $className |
|
325 * @return string Class name loaded |
|
326 * @throws Zend_Controller_Dispatcher_Exception if class not loaded |
|
327 */ |
|
328 public function loadClass($className) |
|
329 { |
|
330 $finalClass = $className; |
|
331 if (($this->_defaultModule != $this->_curModule) |
|
332 || $this->getParam('prefixDefaultModule')) |
|
333 { |
|
334 $finalClass = $this->formatClassName($this->_curModule, $className); |
|
335 } |
|
336 if (class_exists($finalClass, false)) { |
|
337 return $finalClass; |
|
338 } |
|
339 |
|
340 $dispatchDir = $this->getDispatchDirectory(); |
|
341 $loadFile = $dispatchDir . DIRECTORY_SEPARATOR . $this->classToFilename($className); |
|
342 |
|
343 if (Zend_Loader::isReadable($loadFile)) { |
|
344 include_once $loadFile; |
|
345 } else { |
|
346 require_once 'Zend/Controller/Dispatcher/Exception.php'; |
|
347 throw new Zend_Controller_Dispatcher_Exception('Cannot load controller class "' . $className . '" from file "' . $loadFile . "'"); |
|
348 } |
|
349 |
|
350 if (!class_exists($finalClass, false)) { |
|
351 require_once 'Zend/Controller/Dispatcher/Exception.php'; |
|
352 throw new Zend_Controller_Dispatcher_Exception('Invalid controller class ("' . $finalClass . '")'); |
|
353 } |
|
354 |
|
355 return $finalClass; |
|
356 } |
|
357 |
|
358 /** |
|
359 * Get controller class name |
|
360 * |
|
361 * Try request first; if not found, try pulling from request parameter; |
|
362 * if still not found, fallback to default |
|
363 * |
|
364 * @param Zend_Controller_Request_Abstract $request |
|
365 * @return string|false Returns class name on success |
|
366 */ |
|
367 public function getControllerClass(Zend_Controller_Request_Abstract $request) |
|
368 { |
|
369 $controllerName = $request->getControllerName(); |
|
370 if (empty($controllerName)) { |
|
371 if (!$this->getParam('useDefaultControllerAlways')) { |
|
372 return false; |
|
373 } |
|
374 $controllerName = $this->getDefaultControllerName(); |
|
375 $request->setControllerName($controllerName); |
|
376 } |
|
377 |
|
378 $className = $this->formatControllerName($controllerName); |
|
379 |
|
380 $controllerDirs = $this->getControllerDirectory(); |
|
381 $module = $request->getModuleName(); |
|
382 if ($this->isValidModule($module)) { |
|
383 $this->_curModule = $module; |
|
384 $this->_curDirectory = $controllerDirs[$module]; |
|
385 } elseif ($this->isValidModule($this->_defaultModule)) { |
|
386 $request->setModuleName($this->_defaultModule); |
|
387 $this->_curModule = $this->_defaultModule; |
|
388 $this->_curDirectory = $controllerDirs[$this->_defaultModule]; |
|
389 } else { |
|
390 require_once 'Zend/Controller/Exception.php'; |
|
391 throw new Zend_Controller_Exception('No default module defined for this application'); |
|
392 } |
|
393 |
|
394 return $className; |
|
395 } |
|
396 |
|
397 /** |
|
398 * Determine if a given module is valid |
|
399 * |
|
400 * @param string $module |
|
401 * @return bool |
|
402 */ |
|
403 public function isValidModule($module) |
|
404 { |
|
405 if (!is_string($module)) { |
|
406 return false; |
|
407 } |
|
408 |
|
409 $module = strtolower($module); |
|
410 $controllerDir = $this->getControllerDirectory(); |
|
411 foreach (array_keys($controllerDir) as $moduleName) { |
|
412 if ($module == strtolower($moduleName)) { |
|
413 return true; |
|
414 } |
|
415 } |
|
416 |
|
417 return false; |
|
418 } |
|
419 |
|
420 /** |
|
421 * Retrieve default controller class |
|
422 * |
|
423 * Determines whether the default controller to use lies within the |
|
424 * requested module, or if the global default should be used. |
|
425 * |
|
426 * By default, will only use the module default unless that controller does |
|
427 * not exist; if this is the case, it falls back to the default controller |
|
428 * in the default module. |
|
429 * |
|
430 * @param Zend_Controller_Request_Abstract $request |
|
431 * @return string |
|
432 */ |
|
433 public function getDefaultControllerClass(Zend_Controller_Request_Abstract $request) |
|
434 { |
|
435 $controller = $this->getDefaultControllerName(); |
|
436 $default = $this->formatControllerName($controller); |
|
437 $request->setControllerName($controller) |
|
438 ->setActionName(null); |
|
439 |
|
440 $module = $request->getModuleName(); |
|
441 $controllerDirs = $this->getControllerDirectory(); |
|
442 $this->_curModule = $this->_defaultModule; |
|
443 $this->_curDirectory = $controllerDirs[$this->_defaultModule]; |
|
444 if ($this->isValidModule($module)) { |
|
445 $found = false; |
|
446 if (class_exists($default, false)) { |
|
447 $found = true; |
|
448 } else { |
|
449 $moduleDir = $controllerDirs[$module]; |
|
450 $fileSpec = $moduleDir . DIRECTORY_SEPARATOR . $this->classToFilename($default); |
|
451 if (Zend_Loader::isReadable($fileSpec)) { |
|
452 $found = true; |
|
453 $this->_curDirectory = $moduleDir; |
|
454 } |
|
455 } |
|
456 if ($found) { |
|
457 $request->setModuleName($module); |
|
458 $this->_curModule = $this->formatModuleName($module); |
|
459 } |
|
460 } else { |
|
461 $request->setModuleName($this->_defaultModule); |
|
462 } |
|
463 |
|
464 return $default; |
|
465 } |
|
466 |
|
467 /** |
|
468 * Return the value of the currently selected dispatch directory (as set by |
|
469 * {@link getController()}) |
|
470 * |
|
471 * @return string |
|
472 */ |
|
473 public function getDispatchDirectory() |
|
474 { |
|
475 return $this->_curDirectory; |
|
476 } |
|
477 |
|
478 /** |
|
479 * Determine the action name |
|
480 * |
|
481 * First attempt to retrieve from request; then from request params |
|
482 * using action key; default to default action |
|
483 * |
|
484 * Returns formatted action name |
|
485 * |
|
486 * @param Zend_Controller_Request_Abstract $request |
|
487 * @return string |
|
488 */ |
|
489 public function getActionMethod(Zend_Controller_Request_Abstract $request) |
|
490 { |
|
491 $action = $request->getActionName(); |
|
492 if (empty($action)) { |
|
493 $action = $this->getDefaultAction(); |
|
494 $request->setActionName($action); |
|
495 } |
|
496 |
|
497 return $this->formatActionName($action); |
|
498 } |
|
499 } |