<?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 [email protected] so we can send you a copy immediately.
14 *
15 * @category Zend
16 * @package Zend_Controller
17 * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
18 * @license http://framework.zend.com/license/new-bsd New BSD License
19 * @version $Id: Action.php 24593 2012-01-05 20:35:02Z matthew $
20 */
21
22 /**
23 * @see Zend_Controller_Action_HelperBroker
24 */
25 require_once 'Zend/Controller/Action/HelperBroker.php';
26
27 /**
28 * @see Zend_Controller_Action_Interface
29 */
30 require_once 'Zend/Controller/Action/Interface.php';
31
32 /**
33 * @see Zend_Controller_Front
34 */
35 require_once 'Zend/Controller/Front.php';
36
37 /**
38 * @category Zend
39 * @package Zend_Controller
40 * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
41 * @license http://framework.zend.com/license/new-bsd New BSD License
42 */
43 abstract class Zend_Controller_Action implements Zend_Controller_Action_Interface
44 {
45 /**
46 * @var array of existing class methods
47 */
48 protected $_classMethods;
49
50 /**
51 * Word delimiters (used for normalizing view script paths)
52 * @var array
53 */
54 protected $_delimiters;
55
56 /**
57 * Array of arguments provided to the constructor, minus the
58 * {@link $_request Request object}.
59 * @var array
60 */
61 protected $_invokeArgs = array();
62
63 /**
64 * Front controller instance
65 * @var Zend_Controller_Front
66 */
67 protected $_frontController;
68
69 /**
70 * Zend_Controller_Request_Abstract object wrapping the request environment
71 * @var Zend_Controller_Request_Abstract
72 */
73 protected $_request = null;
74
75 /**
76 * Zend_Controller_Response_Abstract object wrapping the response
77 * @var Zend_Controller_Response_Abstract
78 */
79 protected $_response = null;
80
81 /**
82 * View script suffix; defaults to 'phtml'
83 * @see {render()}
84 * @var string
85 */
86 public $viewSuffix = 'phtml';
87
88 /**
89 * View object
90 * @var Zend_View_Interface
91 */
92 public $view;
93
94 /**
95 * Helper Broker to assist in routing help requests to the proper object
96 *
97 * @var Zend_Controller_Action_HelperBroker
98 */
99 protected $_helper = null;
100
101 /**
102 * Class constructor
103 *
104 * The request and response objects should be registered with the
105 * controller, as should be any additional optional arguments; these will be
106 * available via {@link getRequest()}, {@link getResponse()}, and
107 * {@link getInvokeArgs()}, respectively.
108 *
109 * When overriding the constructor, please consider this usage as a best
110 * practice and ensure that each is registered appropriately; the easiest
111 * way to do so is to simply call parent::__construct($request, $response,
112 * $invokeArgs).
113 *
114 * After the request, response, and invokeArgs are set, the
115 * {@link $_helper helper broker} is initialized.
116 *
117 * Finally, {@link init()} is called as the final action of
118 * instantiation, and may be safely overridden to perform initialization
119 * tasks; as a general rule, override {@link init()} instead of the
120 * constructor to customize an action controller's instantiation.
121 *
122 * @param Zend_Controller_Request_Abstract $request
123 * @param Zend_Controller_Response_Abstract $response
124 * @param array $invokeArgs Any additional invocation arguments
125 * @return void
126 */
127 public function __construct(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response, array $invokeArgs = array())
128 {
129 $this->setRequest($request)
130 ->setResponse($response)
131 ->_setInvokeArgs($invokeArgs);
132 $this->_helper = new Zend_Controller_Action_HelperBroker($this);
133 $this->init();
134 }
135
136 /**
137 * Initialize object
138 *
139 * Called from {@link __construct()} as final step of object instantiation.
140 *
141 * @return void
142 */
143 public function init()
144 {
145 }
146
147 /**
148 * Initialize View object
149 *
150 * Initializes {@link $view} if not otherwise a Zend_View_Interface.
151 *
152 * If {@link $view} is not otherwise set, instantiates a new Zend_View
153 * object, using the 'views' subdirectory at the same level as the
154 * controller directory for the current module as the base directory.
155 * It uses this to set the following:
156 * - script path = views/scripts/
157 * - helper path = views/helpers/
158 * - filter path = views/filters/
159 *
160 * @return Zend_View_Interface
161 * @throws Zend_Controller_Exception if base view directory does not exist
162 */
163 public function initView()
164 {
165 if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) {
166 return $this->view;
167 }
168
169 require_once 'Zend/View/Interface.php';
170 if (isset($this->view) && ($this->view instanceof Zend_View_Interface)) {
171 return $this->view;
172 }
173
174 $request = $this->getRequest();
175 $module = $request->getModuleName();
176 $dirs = $this->getFrontController()->getControllerDirectory();
177 if (empty($module) || !isset($dirs[$module])) {
178 $module = $this->getFrontController()->getDispatcher()->getDefaultModule();
179 }
180 $baseDir = dirname($dirs[$module]) . DIRECTORY_SEPARATOR . 'views';
181 if (!file_exists($baseDir) || !is_dir($baseDir)) {
182 require_once 'Zend/Controller/Exception.php';
183 throw new Zend_Controller_Exception('Missing base view directory ("' . $baseDir . '")');
184 }
185
186 require_once 'Zend/View.php';
187 $this->view = new Zend_View(array('basePath' => $baseDir));
188
189 return $this->view;
190 }
191
192 /**
193 * Render a view
194 *
195 * Renders a view. By default, views are found in the view script path as
196 * <controller>/<action>.phtml. You may change the script suffix by
197 * resetting {@link $viewSuffix}. You may omit the controller directory
198 * prefix by specifying boolean true for $noController.
199 *
200 * By default, the rendered contents are appended to the response. You may
201 * specify the named body content segment to set by specifying a $name.
202 *
203 * @see Zend_Controller_Response_Abstract::appendBody()
204 * @param string|null $action Defaults to action registered in request object
205 * @param string|null $name Response object named path segment to use; defaults to null
206 * @param bool $noController Defaults to false; i.e. use controller name as subdir in which to search for view script
207 * @return void
208 */
209 public function render($action = null, $name = null, $noController = false)
210 {
211 if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) {
212 return $this->_helper->viewRenderer->render($action, $name, $noController);
213 }
214
215 $view = $this->initView();
216 $script = $this->getViewScript($action, $noController);
217
218 $this->getResponse()->appendBody(
219 $view->render($script),
220 $name
221 );
222 }
223
224 /**
225 * Render a given view script
226 *
227 * Similar to {@link render()}, this method renders a view script. Unlike render(),
228 * however, it does not autodetermine the view script via {@link getViewScript()},
229 * but instead renders the script passed to it. Use this if you know the
230 * exact view script name and path you wish to use, or if using paths that do not
231 * conform to the spec defined with getViewScript().
232 *
233 * By default, the rendered contents are appended to the response. You may
234 * specify the named body content segment to set by specifying a $name.
235 *
236 * @param string $script
237 * @param string $name
238 * @return void
239 */
240 public function renderScript($script, $name = null)
241 {
242 if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) {
243 return $this->_helper->viewRenderer->renderScript($script, $name);
244 }
245
246 $view = $this->initView();
247 $this->getResponse()->appendBody(
248 $view->render($script),
249 $name
250 );
251 }
252
253 /**
254 * Construct view script path
255 *
256 * Used by render() to determine the path to the view script.
257 *
258 * @param string $action Defaults to action registered in request object
259 * @param bool $noController Defaults to false; i.e. use controller name as subdir in which to search for view script
260 * @return string
261 * @throws Zend_Controller_Exception with bad $action
262 */
263 public function getViewScript($action = null, $noController = null)
264 {
265 if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) {
266 $viewRenderer = $this->_helper->getHelper('viewRenderer');
267 if (null !== $noController) {
268 $viewRenderer->setNoController($noController);
269 }
270 return $viewRenderer->getViewScript($action);
271 }
272
273 $request = $this->getRequest();
274 if (null === $action) {
275 $action = $request->getActionName();
276 } elseif (!is_string($action)) {
277 require_once 'Zend/Controller/Exception.php';
278 throw new Zend_Controller_Exception('Invalid action specifier for view render');
279 }
280
281 if (null === $this->_delimiters) {
282 $dispatcher = Zend_Controller_Front::getInstance()->getDispatcher();
283 $wordDelimiters = $dispatcher->getWordDelimiter();
284 $pathDelimiters = $dispatcher->getPathDelimiter();
285 $this->_delimiters = array_unique(array_merge($wordDelimiters, (array) $pathDelimiters));
286 }
287
288 $action = str_replace($this->_delimiters, '-', $action);
289 $script = $action . '.' . $this->viewSuffix;
290
291 if (!$noController) {
292 $controller = $request->getControllerName();
293 $controller = str_replace($this->_delimiters, '-', $controller);
294 $script = $controller . DIRECTORY_SEPARATOR . $script;
295 }
296
297 return $script;
298 }
299
300 /**
301 * Return the Request object
302 *
303 * @return Zend_Controller_Request_Abstract
304 */
305 public function getRequest()
306 {
307 return $this->_request;
308 }
309
310 /**
311 * Set the Request object
312 *
313 * @param Zend_Controller_Request_Abstract $request
314 * @return Zend_Controller_Action
315 */
316 public function setRequest(Zend_Controller_Request_Abstract $request)
317 {
318 $this->_request = $request;
319 return $this;
320 }
321
322 /**
323 * Return the Response object
324 *
325 * @return Zend_Controller_Response_Abstract
326 */
327 public function getResponse()
328 {
329 return $this->_response;
330 }
331
332 /**
333 * Set the Response object
334 *
335 * @param Zend_Controller_Response_Abstract $response
336 * @return Zend_Controller_Action
337 */
338 public function setResponse(Zend_Controller_Response_Abstract $response)
339 {
340 $this->_response = $response;
341 return $this;
342 }
343
344 /**
345 * Set invocation arguments
346 *
347 * @param array $args
348 * @return Zend_Controller_Action
349 */
350 protected function _setInvokeArgs(array $args = array())
351 {
352 $this->_invokeArgs = $args;
353 return $this;
354 }
355
356 /**
357 * Return the array of constructor arguments (minus the Request object)
358 *
359 * @return array
360 */
361 public function getInvokeArgs()
362 {
363 return $this->_invokeArgs;
364 }
365
366 /**
367 * Return a single invocation argument
368 *
369 * @param string $key
370 * @return mixed
371 */
372 public function getInvokeArg($key)
373 {
374 if (isset($this->_invokeArgs[$key])) {
375 return $this->_invokeArgs[$key];
376 }
377
378 return null;
379 }
380
381 /**
382 * Get a helper by name
383 *
384 * @param string $helperName
385 * @return Zend_Controller_Action_Helper_Abstract
386 */
387 public function getHelper($helperName)
388 {
389 return $this->_helper->{$helperName};
390 }
391
392 /**
393 * Get a clone of a helper by name
394 *
395 * @param string $helperName
396 * @return Zend_Controller_Action_Helper_Abstract
397 */
398 public function getHelperCopy($helperName)
399 {
400 return clone $this->_helper->{$helperName};
401 }
402
403 /**
404 * Set the front controller instance
405 *
406 * @param Zend_Controller_Front $front
407 * @return Zend_Controller_Action
408 */
409 public function setFrontController(Zend_Controller_Front $front)
410 {
411 $this->_frontController = $front;
412 return $this;
413 }
414
415 /**
416 * Retrieve Front Controller
417 *
418 * @return Zend_Controller_Front
419 */
420 public function getFrontController()
421 {
422 // Used cache version if found
423 if (null !== $this->_frontController) {
424 return $this->_frontController;
425 }
426
427 // Grab singleton instance, if class has been loaded
428 if (class_exists('Zend_Controller_Front')) {
429 $this->_frontController = Zend_Controller_Front::getInstance();
430 return $this->_frontController;
431 }
432
433 // Throw exception in all other cases
434 require_once 'Zend/Controller/Exception.php';
435 throw new Zend_Controller_Exception('Front controller class has not been loaded');
436 }
437
438 /**
439 * Pre-dispatch routines
440 *
441 * Called before action method. If using class with
442 * {@link Zend_Controller_Front}, it may modify the
443 * {@link $_request Request object} and reset its dispatched flag in order
444 * to skip processing the current action.
445 *
446 * @return void
447 */
448 public function preDispatch()
449 {
450 }
451
452 /**
453 * Post-dispatch routines
454 *
455 * Called after action method execution. If using class with
456 * {@link Zend_Controller_Front}, it may modify the
457 * {@link $_request Request object} and reset its dispatched flag in order
458 * to process an additional action.
459 *
460 * Common usages for postDispatch() include rendering content in a sitewide
461 * template, link url correction, setting headers, etc.
462 *
463 * @return void
464 */
465 public function postDispatch()
466 {
467 }
468
469 /**
470 * Proxy for undefined methods. Default behavior is to throw an
471 * exception on undefined methods, however this function can be
472 * overridden to implement magic (dynamic) actions, or provide run-time
473 * dispatching.
474 *
475 * @param string $methodName
476 * @param array $args
477 * @return void
478 * @throws Zend_Controller_Action_Exception
479 */
480 public function __call($methodName, $args)
481 {
482 require_once 'Zend/Controller/Action/Exception.php';
483 if ('Action' == substr($methodName, -6)) {
484 $action = substr($methodName, 0, strlen($methodName) - 6);
485 throw new Zend_Controller_Action_Exception(sprintf('Action "%s" does not exist and was not trapped in __call()', $action), 404);
486 }
487
488 throw new Zend_Controller_Action_Exception(sprintf('Method "%s" does not exist and was not trapped in __call()', $methodName), 500);
489 }
490
491 /**
492 * Dispatch the requested action
493 *
494 * @param string $action Method name of action
495 * @return void
496 */
497 public function dispatch($action)
498 {
499 // Notify helpers of action preDispatch state
500 $this->_helper->notifyPreDispatch();
501
502 $this->preDispatch();
503 if ($this->getRequest()->isDispatched()) {
504 if (null === $this->_classMethods) {
505 $this->_classMethods = get_class_methods($this);
506 }
507
508 // If pre-dispatch hooks introduced a redirect then stop dispatch
509 // @see ZF-7496
510 if (!($this->getResponse()->isRedirect())) {
511 // preDispatch() didn't change the action, so we can continue
512 if ($this->getInvokeArg('useCaseSensitiveActions') || in_array($action, $this->_classMethods)) {
513 if ($this->getInvokeArg('useCaseSensitiveActions')) {
514 trigger_error('Using case sensitive actions without word separators is deprecated; please do not rely on this "feature"');
515 }
516 $this->$action();
517 } else {
518 $this->__call($action, array());
519 }
520 }
521 $this->postDispatch();
522 }
523
524 // whats actually important here is that this action controller is
525 // shutting down, regardless of dispatching; notify the helpers of this
526 // state
527 $this->_helper->notifyPostDispatch();
528 }
529
530 /**
531 * Call the action specified in the request object, and return a response
532 *
533 * Not used in the Action Controller implementation, but left for usage in
534 * Page Controller implementations. Dispatches a method based on the
535 * request.
536 *
537 * Returns a Zend_Controller_Response_Abstract object, instantiating one
538 * prior to execution if none exists in the controller.
539 *
540 * {@link preDispatch()} is called prior to the action,
541 * {@link postDispatch()} is called following it.
542 *
543 * @param null|Zend_Controller_Request_Abstract $request Optional request
544 * object to use
545 * @param null|Zend_Controller_Response_Abstract $response Optional response
546 * object to use
547 * @return Zend_Controller_Response_Abstract
548 */
549 public function run(Zend_Controller_Request_Abstract $request = null, Zend_Controller_Response_Abstract $response = null)
550 {
551 if (null !== $request) {
552 $this->setRequest($request);
553 } else {
554 $request = $this->getRequest();
555 }
556
557 if (null !== $response) {
558 $this->setResponse($response);
559 }
560
561 $action = $request->getActionName();
562 if (empty($action)) {
563 $action = 'index';
564 }
565 $action = $action . 'Action';
566
567 $request->setDispatched(true);
568 $this->dispatch($action);
569
570 return $this->getResponse();
571 }
572
573 /**
574 * Gets a parameter from the {@link $_request Request object}. If the
575 * parameter does not exist, NULL will be returned.
576 *
577 * If the parameter does not exist and $default is set, then
578 * $default will be returned instead of NULL.
579 *
580 * @param string $paramName
581 * @param mixed $default
582 * @return mixed
583 */
584 protected function _getParam($paramName, $default = null)
585 {
586 return $this->getParam($paramName, $default);
587 }
588
589 /**
590 * Gets a parameter from the {@link $_request Request object}. If the
591 * parameter does not exist, NULL will be returned.
592 *
593 * If the parameter does not exist and $default is set, then
594 * $default will be returned instead of NULL.
595 *
596 * @param string $paramName
597 * @param mixed $default
598 * @return mixed
599 */
600 public function getParam($paramName, $default = null)
601 {
602 $value = $this->getRequest()->getParam($paramName);
603 if ((null === $value || '' === $value) && (null !== $default)) {
604 $value = $default;
605 }
606
607 return $value;
608 }
609
610 /**
611 * Set a parameter in the {@link $_request Request object}.
612 *
613 * @param string $paramName
614 * @param mixed $value
615 * @return Zend_Controller_Action
616 * @deprecated Deprecated as of Zend Framework 1.7. Use
617 * setParam() instead.
618 */
619 protected function _setParam($paramName, $value)
620 {
621 return $this->setParam($paramName, $value);
622 }
623
624 /**
625 * Set a parameter in the {@link $_request Request object}.
626 *
627 * @param string $paramName
628 * @param mixed $value
629 * @return Zend_Controller_Action
630 */
631 public function setParam($paramName, $value)
632 {
633 $this->getRequest()->setParam($paramName, $value);
634
635 return $this;
636 }
637
638 /**
639 * Determine whether a given parameter exists in the
640 * {@link $_request Request object}.
641 *
642 * @param string $paramName
643 * @return boolean
644 * @deprecated Deprecated as of Zend Framework 1.7. Use
645 * hasParam() instead.
646 */
647 protected function _hasParam($paramName)
648 {
649 return $this->hasParam($paramName);
650 }
651
652 /**
653 * Determine whether a given parameter exists in the
654 * {@link $_request Request object}.
655 *
656 * @param string $paramName
657 * @return boolean
658 */
659 public function hasParam($paramName)
660 {
661 return null !== $this->getRequest()->getParam($paramName);
662 }
663
664 /**
665 * Return all parameters in the {@link $_request Request object}
666 * as an associative array.
667 *
668 * @return array
669 * @deprecated Deprecated as of Zend Framework 1.7. Use
670 * getAllParams() instead.
671 */
672 protected function _getAllParams()
673 {
674 return $this->getAllParams();
675 }
676
677 /**
678 * Return all parameters in the {@link $_request Request object}
679 * as an associative array.
680 *
681 * @return array
682 */
683 public function getAllParams()
684 {
685 return $this->getRequest()->getParams();
686 }
687
688
689 /**
690 * Forward to another controller/action.
691 *
692 * It is important to supply the unformatted names, i.e. "article"
693 * rather than "ArticleController". The dispatcher will do the
694 * appropriate formatting when the request is received.
695 *
696 * If only an action name is provided, forwards to that action in this
697 * controller.
698 *
699 * If an action and controller are specified, forwards to that action and
700 * controller in this module.
701 *
702 * Specifying an action, controller, and module is the most specific way to
703 * forward.
704 *
705 * A fourth argument, $params, will be used to set the request parameters.
706 * If either the controller or module are unnecessary for forwarding,
707 * simply pass null values for them before specifying the parameters.
708 *
709 * @param string $action
710 * @param string $controller
711 * @param string $module
712 * @param array $params
713 * @return void
714 * @deprecated Deprecated as of Zend Framework 1.7. Use
715 * forward() instead.
716 */
717 final protected function _forward($action, $controller = null, $module = null, array $params = null)
718 {
719 $this->forward($action, $controller, $module, $params);
720 }
721
722 /**
723 * Forward to another controller/action.
724 *
725 * It is important to supply the unformatted names, i.e. "article"
726 * rather than "ArticleController". The dispatcher will do the
727 * appropriate formatting when the request is received.
728 *
729 * If only an action name is provided, forwards to that action in this
730 * controller.
731 *
732 * If an action and controller are specified, forwards to that action and
733 * controller in this module.
734 *
735 * Specifying an action, controller, and module is the most specific way to
736 * forward.
737 *
738 * A fourth argument, $params, will be used to set the request parameters.
739 * If either the controller or module are unnecessary for forwarding,
740 * simply pass null values for them before specifying the parameters.
741 *
742 * @param string $action
743 * @param string $controller
744 * @param string $module
745 * @param array $params
746 * @return void
747 */
748 final public function forward($action, $controller = null, $module = null, array $params = null)
749 {
750 $request = $this->getRequest();
751
752 if (null !== $params) {
753 $request->setParams($params);
754 }
755
756 if (null !== $controller) {
757 $request->setControllerName($controller);
758
759 // Module should only be reset if controller has been specified
760 if (null !== $module) {
761 $request->setModuleName($module);
762 }
763 }
764
765 $request->setActionName($action)
766 ->setDispatched(false);
767 }
768
769 /**
770 * Redirect to another URL
771 *
772 * Proxies to {@link Zend_Controller_Action_Helper_Redirector::gotoUrl()}.
773 *
774 * @param string $url
775 * @param array $options Options to be used when redirecting
776 * @return void
777 * @deprecated Deprecated as of Zend Framework 1.7. Use
778 * redirect() instead.
779 */
780 protected function _redirect($url, array $options = array())
781 {
782 $this->redirect($url, $options);
783 }
784
785 /**
786 * Redirect to another URL
787 *
788 * Proxies to {@link Zend_Controller_Action_Helper_Redirector::gotoUrl()}.
789 *
790 * @param string $url
791 * @param array $options Options to be used when redirecting
792 * @return void
793 */
794 public function redirect($url, array $options = array())
795 {
796 $this->_helper->redirector->gotoUrl($url, $options);
797 }
798 }
799