2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4 // +----------------------------------------------------------------------+
5 // | Akelos Framework - http://www.akelos.org |
6 // +----------------------------------------------------------------------+
7 // | Copyright (c) 2002-2006, Akelos Media, S.L. & Bermi Ferrer Martinez |
8 // | Released under the GNU Lesser General Public License, see LICENSE.txt|
9 // +----------------------------------------------------------------------+
11 if(!class_exists('AkActionController')){
13 require_once(AK_LIB_DIR
.DS
.'AkObject.php');
15 defined('AK_HIGH_LOAD_MODE') ?
null : define('AK_HIGH_LOAD_MODE', false);
18 * @package ActionController
20 * @author Bermi Ferrer <bermi a.t akelos c.om>
21 * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
22 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
25 class AkActionController
extends AkObject
27 var $_high_load_mode = AK_HIGH_LOAD_MODE
;
28 var $_enable_plugins = true;
29 var $_auto_instantiate_models = true;
30 var $validate_output = false;
32 var $_ssl_requirement = false;
35 * Determines whether the view has access to controller internals $this->Request, $this->Response, $this->session, and $this->Template.
36 * By default, it does.
38 var $_view_controller_internals = true;
41 * Protected instance variable cache
43 var $_protected_variables_cache = array();
46 * Prepends all the URL-generating helpers from AssetHelper.
47 * This makes it possible to easily move javascripts, stylesheets,
48 * and images to a dedicated asset server away from the main web server.
50 * $this->_asset_host = 'http://assets.example.com';
52 var $asset_host = AK_ASSET_HOST
;
58 * Determines which template class should be used by AkActionController.
63 * Turn on +_ignore_missing_templates+ if you want to unit test actions without
64 * making the associated templates.
66 var $_ignore_missing_templates;
69 * Holds the Request object that's primarily used to get environment variables
74 * Holds an array of all the GET, POST, and Url parameters passed to the action.
75 * Accessed like <tt>$this->params['post_id'];</tt>
78 var $params = array();
81 * Holds the Response object that's primarily used to set additional HTTP _headers
82 * through access like <tt>$this->Response->_headers['Cache-Control'] = 'no-cache';</tt>.
83 * Can also be used to access the final body HTML after a template
84 * has been rendered through $this->Response->body -- useful for <tt>after_filter</tt>s
85 * that wants to manipulate the output, such as a OutputCompressionFilter.
90 * Holds an array of objects in the session. Accessed like <tt>$this->session['person']</tt>
91 * to get the object tied to the 'person' key. The session will hold any type of object
92 * as values, but the key should be a string.
97 * Holds an array of header names and values. Accessed like <tt>$this->_headers['Cache-Control']</tt>
98 * to get the value of the Cache-Control directive. Values should always be specified as strings.
100 var $_headers = array();
103 * Holds the array of variables that are passed on to the template class to be
104 * made available to the view. This array is generated by taking a snapshot of
105 * all the instance variables in the current scope just before a template is rendered.
107 var $_assigns = array();
110 * Holds the name of the action this controller is processing.
116 var $helpers = 'default';
121 var $web_services = array();
123 var $web_service_api;
124 var $web_service_apis = array();
130 * Old fashioned way of dispatching requests. Please use AkDispatcher or roll your own.
134 function handleRequest()
136 AK_LOG_EVENTS
&& empty($this->_Logger
) ?
($this->_Logger
=& Ak
::getLogger()) : null;
137 AK_LOG_EVENTS
&& !empty($this->_Logger
) ?
$this->_Logger
->warning('Using deprecated request dispatcher AkActionController::handleRequest. Use to AkDispatcher + AkDispatcher::dispatch instead.') : null;
138 require_once(AK_LIB_DIR
.DS
.'AkDispatcher.php');
139 $Dispatcher =& new AkDispatcher();
140 $Dispatcher->dispatch();
143 function process(&$Request, &$Response)
145 AK_LOG_EVENTS
&& empty($this->_Logger
) ?
($this->_Logger
=& Ak
::getLogger()) : null;
147 $this->Request
=& $Request;
148 $this->Response
=& $Response;
149 $this->params
= $this->Request
->getParams();
150 $this->_action_name
= $this->Request
->getAction();
152 if(!method_exists($this, $this->_action_name
)){
153 trigger_error(Ak
::t('Controller <i>%controller_name</i> can\'t handle action %action_name',
155 '%controller_name' => $this->getControllerName(),
156 '%action_name' => $this->_action_name
,
160 Ak
::t('Akelos'); // We need to get locales ready
162 if($this->_high_load_mode
!== true){
163 if(!empty($this->_auto_instantiate_models
)){
164 $this->instantiateIncludedModelClasses();
166 if(!empty($this->helpers
)){
167 $this->instantiateHelpers();
169 if(!empty($this->_enable_plugins
)){
170 $this->loadPlugins();
173 $this->_enableLayoutOnRender
= false;
176 $this->_ensureProperProtocol();
179 $this->afterFilter('_handleFlashAttribute');
181 if(!empty($this->validate_output
)){
182 $this->beforeFilter('_validateGeneratedXhtml');
186 $this->_loadActionView();
188 if(isset($this->api
)){
189 require_once(AK_LIB_DIR
.DS
.'AkActionWebService.php');
190 $this->aroundFilter(new AkActionWebService($this));
193 $this->performActionWithFilters($this->_action_name
);
195 if (!$this->_hasPerformed()){
196 $this->_enableLayoutOnRender ?
$this->renderWithLayout() : $this->renderWithoutLayout();
199 $this->Response
->outputResults();
202 function _loadActionView()
204 empty($this->_assigns
) ?
($this->_assigns
= array()) : null;
205 empty($this->_default_render_status_code
) ?
($this->_default_render_status_code
= 200) : null;
206 $this->_enableLayoutOnRender
= !isset($this->_enableLayoutOnRender
) ?
true : $this->_enableLayoutOnRender
;
207 $this->passed_args
= !isset($this->Request
->pass
)?
array() : $this->Request
->pass
;
208 empty($this->cookies
) && isset($_COOKIE) ?
($this->cookies
=& $_COOKIE) : null;
210 if(empty($this->Template
)){
211 require_once(AK_LIB_DIR
.DS
.'AkActionView.php');
212 require_once(AK_LIB_DIR
.DS
.'AkActionView'.DS
.'AkPhpTemplateHandler.php');
213 $this->Template
=& new AkActionView($this->_getTemplateBasePath(),
214 $this->Request
->getParameters(),$this->Request
->getController());
216 $this->Template
->_controllerInstance
=& $this;
217 $this->Template
->_registerTemplateHandler('tpl','AkPhpTemplateHandler');
221 function loadPlugins()
227 * Creates an instance of each available helper and links it into into current controller.
229 * Per example, if a helper TextHelper is located into the file text_helper.php.
230 * An instance is created on current controller
231 * at $this->text_helper. This instance is also available on the view by calling $text_helper.
233 * Helpers can be found at lib/AkActionView/helpers (this might change in a future)
235 function instantiateHelpers()
237 $helpers = $this->getDefaultHelpers();
238 $helpers = array_merge($helpers, $this->getApplicationHelpers());
240 require_once(AK_LIB_DIR
.DS
.'AkActionView'.DS
.'AkActionViewHelper.php');
242 $current_controller_helper = $this->getControllerName();
243 $current_controller_helper_file_name = AK_HELPERS_DIR
.DS
.$this->_module_path
.AkInflector
::underscore($current_controller_helper).'_helper.php';
245 if(file_exists($current_controller_helper_file_name)){
246 $helpers[$current_controller_helper_file_name] = $current_controller_helper;
249 $available_helpers = array();
250 foreach ($helpers as $file=>$helper){
251 $helper_class_name = $helper.'Helper';
252 $full_path = preg_match('/[\\\\\/]+/',$file);
253 $file_path = $full_path ?
$file : AK_LIB_DIR
.DS
.'AkActionView'.DS
.'helpers'.DS
.$file;
254 include_once($file_path);
256 if(class_exists($helper_class_name)){
257 $attribute_name = $full_path ? AkInflector
::underscore($helper_class_name) : substr($file,0,-4);
258 $available_helpers[] = $attribute_name;
259 $this->$attribute_name =& new $helper_class_name(&$this);
260 if(method_exists($this->$attribute_name,'setController')){
261 $this->$attribute_name->setController(&$this);
263 if(method_exists($this->$attribute_name,'init')){
264 $this->$attribute_name->init();
268 defined('AK_ACTION_CONTROLLER_AVAILABLE_HELPERS') ?
null : define('AK_ACTION_CONTROLLER_AVAILABLE_HELPERS', join(',',$available_helpers));
271 function getDefaultHelpers()
273 if($this->helpers
== 'default'){
274 $available_helpers = Ak
::dir(AK_LIB_DIR
.DS
.'AkActionView'.DS
.'helpers',array('dirs'=>false));
275 $helper_names = array();
276 foreach ($available_helpers as $available_helper){
277 $helper_names[$available_helper] = AkInflector
::classify(substr($available_helper,0,-10));
279 return $helper_names;
280 }elseif (is_string($this->helpers
)){
281 return Ak
::stringToArray($this->helpers
);
283 return $this->helpers
;
286 function getApplicationHelpers()
288 $helper_names = array();
289 if ($this->app_helpers
== 'all' ){
290 $available_helpers = Ak
::dir(AK_HELPERS_DIR
,array('dirs'=>false));
291 $helper_names = array();
292 foreach ($available_helpers as $available_helper){
293 $helper_names[AK_HELPERS_DIR
.DS
.$available_helper] = AkInflector
::classify(substr($available_helper,0,-10));
296 } elseif (!empty($this->app_helpers
)){
297 foreach (Ak
::toArray($this->app_helpers
) as $helper_name){
298 $helper_names[AK_HELPERS_DIR
.DS
.AkInflector
::underscore($helper_name).'_helper.php'] = AkInflector
::camelize($helper_name);
301 return $helper_names;
305 function _validateGeneratedXhtml()
307 require_once(AK_LIB_DIR
.DS
.'AkXhtmlValidator.php');
308 $XhtmlValidator = new AkXhtmlValidator();
309 if($XhtmlValidator->validate($this->Response
->body
) === false){
310 $this->Response
->sendHeaders();
311 echo '<h1>'.Ak
::t('Ooops! There are some errors on current XHTML page').'</h1>';
312 echo '<small>'.Ak
::t('In order to disable XHTML validation, set the <b>AK_ENABLE_STRICT_XHTML_VALIDATION</b> constant to false on your config/development.php file')."</small><hr />\n";
313 $XhtmlValidator->showErrors();
314 echo "<hr /><h2>".Ak
::t('Showing XHTML code')."</h2><hr /><div style='border:5px solid red;margin:5px;padding:15px;'>".$this->Response
->body
."</pre>";
321 * Methods for loading desired models into this controller
323 function setModel($model)
325 $this->instantiateIncludedModelClasses(array($model));
328 function setModels($models)
330 $this->instantiateIncludedModelClasses($models);
333 function instantiateIncludedModelClasses()
335 require_once(AK_LIB_DIR
.DS
.'AkActiveRecord.php');
336 require_once(AK_APP_DIR
.DS
.'shared_model.php');
338 empty($this->model
) ?
($this->model
= $this->params
['controller']) : null;
339 empty($this->models
) ?
($this->models
= array()) : null;
341 $models =array_unique(array_merge(
342 Ak
::import($this->model
),
343 Ak
::import($this->models
)));
345 foreach ($models as $model){
346 $this->instantiateModelClass($model);
350 function instantiateModelClass($model_class_name, $finder_options = array())
352 $underscored_model_class_name = AkInflector
::underscore($model_class_name);
354 $id = empty($this->params
[$underscored_model_class_name]['id']) ?
355 (empty($this->params
['id']) ?
false :
356 ($model_class_name == $this->getControllerName() ?
$this->params
['id'] : false)) :
357 $this->params
[$underscored_model_class_name]['id'];
359 if(class_exists($model_class_name)){
360 $underscored_model_class_name = AkInflector
::underscore($model_class_name);
362 if(!isset($this->$model_class_name) ||
!isset($this->$underscored_model_class_name)){
363 if($finder_options !== false && is_numeric($id)){
364 $model =& new $model_class_name();
365 if(empty($finder_options)){
366 $model =& $model->find($id);
368 $model =& $model->find($id, $finder_options);
371 $model =& new $model_class_name();
373 if(!isset($this->$model_class_name)){
374 $this->$model_class_name =& $model;
376 if(!isset($this->$underscored_model_class_name)){
377 $this->$underscored_model_class_name =& $model;
386 * Renders the content that will be returned to the browser as the Response body.
388 * === Rendering an action
390 * Action rendering is the most common form and the type used automatically by
391 * Action Controller when nothing else is specified. By default, actions are
392 * rendered within the current layout (if one exists).
394 * * Renders the template for the action "goal" within the current controller
396 * $this->render(array('action'=>'goal'));
398 * * Renders the template for the action "short_goal" within the current controller,
399 * but without the current active layout
401 * $this->render(array('action'=>'short_goal','layout'=>false));
403 * * Renders the template for the action "long_goal" within the current controller,
404 * but with a custom layout
406 * $this->render(array('action'=>'long_goal','layout'=>'spectacular'));
408 * === Rendering partials
410 * Partial rendering is most commonly used together with Ajax calls that only update
411 * one or a few elements on a page without reloading. Rendering of partials from
412 * the controller makes it possible to use the same partial template in
413 * both the full-page rendering (by calling it from within the template) and when
414 * sub-page updates happen (from the controller action responding to Ajax calls).
415 * By default, the current layout is not used.
417 * * Renders the partial located at app/views/controller/_win.tpl
419 * $this->render(array('partial'=>'win'));
421 * * Renders the partial with a status code of 500 (internal error)
423 * $this->render(array('partial'=>'broken','status'=>500));
425 * * Renders the same partial but also makes a local variable available to it
427 * $this->render(array('partial' => 'win', 'locals' => array('name'=>'david')));
429 * * Renders a collection of the same partial by making each element of $wins available through
430 * the local variable "win" as it builds the complete Response
432 * $this->render(array('partial'=>'win','collection'=>$wins));
434 * * Renders the same collection of partials, but also renders the win_divider partial in between
437 * $this->render(array('partial'=>'win','collection'=>$wins,'spacer_template'=>'win_divider'));
439 * === Rendering a template
441 * Template rendering works just like action rendering except that it takes a
442 * path relative to the template root.
443 * The current layout is automatically applied.
445 * * Renders the template located in app/views/weblog/show.tpl
446 * $this->render(array('template'=>'weblog/show'));
448 * === Rendering a file
450 * File rendering works just like action rendering except that it takes a
451 * filesystem path. By default, the path is assumed to be absolute, and the
452 * current layout is not applied.
454 * * Renders the template located at the absolute filesystem path
455 * $this->render(array('file'=>'/path/to/some/template.tpl'));
456 * $this->render(array('file'=>'c:/path/to/some/template.tpl'));
458 * * Renders a template within the current layout, and with a 404 status code
459 * $this->render(array('file' => '/path/to/some/template.tpl', 'layout' => true, 'status' => 404));
460 * $this->render(array('file' => 'c:/path/to/some/template.tpl', 'layout' => true, 'status' => 404));
462 * * Renders a template relative to the template root and chooses the proper file extension
463 * $this->render(array('file' => 'some/template', 'use_full_path' => true));
468 * Rendering of text is usually used for tests or for rendering prepared content,
469 * such as a cache. By default, text
470 * rendering is not done within the active layout.
472 * * Renders the clear text "hello world" with status code 200
473 * $this->render(array('text' => 'hello world!'));
475 * * Renders the clear text "Explosion!" with status code 500
476 * $this->render(array('text' => "Explosion!", 'status' => 500 ));
478 * * Renders the clear text "Hi there!" within the current active layout (if one exists)
479 * $this->render(array('text' => "Explosion!", 'layout' => true));
481 * * Renders the clear text "Hi there!" within the layout
482 * * placed in "app/views/layouts/special.tpl"
483 * $this->render(array('text' => "Explosion!", 'layout => "special"));
486 * === Rendering an inline template
488 * Rendering of an inline template works as a cross between text and action
489 * rendering where the source for the template
490 * is supplied inline, like text, but its evaled by PHP, like action. By default,
491 * PHP is used for rendering and the current layout is not used.
493 * * Renders "hello, hello, hello, again"
494 * $this->render(array('inline' => "<?php echo str_repeat('hello, ', 3).'again'?>" ));
496 * * Renders "hello david"
497 * $this->render(array('inline' => "<?php echo 'hello ' . $name ?>", 'locals' => array('name' => 'david')));
500 * === Rendering nothing
502 * Rendering nothing is often convenient in combination with Ajax calls that
503 * perform their effect client-side or
504 * when you just want to communicate a status code. Due to a bug in Safari, nothing
505 * actually means a single space.
507 * * Renders an empty Response with status code 200
508 * $this->render(array('nothing' => true));
510 * * Renders an empty Response with status code 401 (access denied)
511 * $this->render(array('nothing' => true, 'status' => 401));
513 function render($options = null, $status = 200)
515 if(empty($options['partial']) && $this->_hasPerformed()){
516 $this->_doubleRenderError(Ak
::t("Can only render or redirect once per action"));
520 $this->_flash_handled ?
null : $this->_handleFlashAttribute();
522 if(!is_array($options)){
523 return $this->renderFile(empty($options) ?
$this->getDefaultTemplateName() : $options, $status, true);
526 if(!empty($options['text'])){
527 return $this->renderText($options['text'], @$options['status']);
530 if(!empty($options['file'])){
531 return $this->renderFile($options['file'], @$options['status'], @$options['use_full_path'], @(array)$options['locals']);
532 }elseif(!empty($options['template'])){
533 return $this->renderFile($options['template'], @$options['status'], true);
534 }elseif(!empty($options['inline'])){
535 return $this->renderTemplate($options['inline'], @$options['status'], @$options['type'], @(array)$options['locals']);
536 }elseif(!empty($options['action'])){
537 return $this->renderAction($options['action'], @$options['status'], @$options['layout']);
538 }elseif(!empty($options['partial'])){
539 if($options['partial'] === true){
540 $options['partial'] = !empty($options['template']) ?
$options['template'] : $this->getDefaultTemplateName();
542 if(!empty($options['collection'])){
543 return $this->renderPartialCollection($options['partial'], $options['collection'], @$options['spacer_template'], @$options['locals'], @$options['status']);
545 return $this->renderPartial($options['partial'], @$options['object'], @$options['locals'], @$options['status']);
547 }elseif(!empty($options['nothing'])){
548 // Safari doesn't pass the _headers of the return if the Response is zero length
549 return $this->renderText(' ', @$options['status']);
551 return $this->renderFile($this->getDefaultTemplateName(), @$options['status'], true);
558 * Renders according to the same rules as <tt>render</tt>, but returns the result in a string instead
559 * of sending it as the Response body to the browser.
561 function renderToString($options = null)
563 $result = $this->render($options);
564 $this->eraseRenderResults();
565 $this->variables_added
= null;
566 $this->Template
->_assigns_added
= null;
570 function renderAction($_action_name, $status = null, $with_layout = true)
572 $this->$_action_name();
573 $template = $this->getDefaultTemplateName($_action_name);
574 if(!empty($with_layout) && !$this->_isTemplateExemptFromLayout($template)){
575 return $this->renderWithLayout($template, $status, $with_layout);
577 return $this->renderWithoutLayout($template, $status);
581 function renderFile($template_path, $status = null, $use_full_path = false, $locals = array())
583 $this->_addVariablesToAssigns();
584 $locals = array_merge($locals,$this->_assigns
);
587 $this->_assertExistanceOfTemplateFile($template_path);
590 AK_LOG_EVENTS
&& !empty($this->_Logger
) ?
$this->_Logger
->message("Rendering $this->full_template_path" . (!empty($status) ?
" ($status)":'')) : null;
592 return $this->renderText($this->Template
->renderFile($template_path, $use_full_path, $locals), $status);
595 function renderTemplate($template, $status = null, $type = 'tpl', $local_assigns = array())
597 $this->_addVariablesToAssigns();
598 $local_assigns = array_merge($local_assigns,$this->_assigns
);
599 return $this->renderText($this->Template
->renderTemplate($type, $template, null, $local_assigns), $status);
602 function renderText($text = null, $status = null)
604 $this->performed_render
= true;
605 $this->Response
->_headers
['Status'] = !empty($status) ?
$status : $this->_default_render_status_code
;
606 $this->Response
->body
= $text;
610 function renderNothing($status = null)
612 return $this->renderText(' ', $status);
615 function renderPartial($partial_path = null, $object = null, $local_assigns = null, $status = null)
617 $partial_path = empty($partial_path) ?
$this->getDefaultTemplateName() : $partial_path;
618 $this->variables_added
= false;
619 $this->performed_render
= false;
620 $this->_addVariablesToAssigns();
621 $this->Template
->controller
=& $this;
622 $this->$partial_path = $this->renderText($this->Template
->renderPartial($partial_path, $object, array_merge($this->_assigns
, (array)$local_assigns)), $status);
623 return $this->$partial_path;
626 function renderPartialCollection($partial_name, $collection, $partial_spacer_template = null, $local_assigns = null, $status = null)
628 $this->_addVariablesToAssigns();
629 $collection_name = AkInflector
::pluralize($partial_name).'_collection';
630 $result = $this->Template
->renderPartialCollection($partial_name, $collection, $partial_spacer_template, $local_assigns);
631 if(empty($this->$collection_name)){
632 $this->$collection_name = $result;
634 $this->variables_added
= false;
635 $this->performed_render
= false;
640 function renderWithLayout($template_name = null, $status = null, $layout = null)
642 $template_name = empty($template_name) ?
$this->getDefaultTemplateName() : $template_name;
643 return $this->renderWithALayout($template_name, $status, $layout);
646 function renderWithoutLayout($template_name = null, $status = null)
648 $template_name = empty($template_name) ?
$this->getDefaultTemplateName() : $template_name;
649 return $this->render($template_name, $status);
653 * Clears the rendered results, allowing for another render to be performed.
655 function eraseRenderResults()
657 $this->Response
->body
= '';
658 $this->performed_render
= false;
659 $this->variables_added
= false;
662 function _addVariablesToAssigns()
664 if(empty($this->variables_added
)){
665 $this->_addInstanceVariablesToAssigns();
666 $this->variables_added
= true;
670 function _addInstanceVariablesToAssigns()
672 $this->_protected_variables_cache
= array_merge($this->_protected_variables_cache
, $this->_getProtectedInstanceVariables());
674 foreach (array_diff(array_keys(get_object_vars($this)), $this->_protected_variables_cache
) as $attribute){
675 if($attribute[0] != '_'){
676 $this->_assigns
[$attribute] =& $this->$attribute;
681 function _getProtectedInstanceVariables()
683 return !empty($this->_view_controller_internals
) ?
684 array('_assigns', 'performed_redirect', 'performed_render','db') :
685 array('_assigns', 'performed_redirect', 'performed_render', 'session', 'cookies',
686 'Template','db','helpers','models','layout','Response','Request',
687 'params','passed_args');
692 * Use this to translate strings in the scope of your controller
696 function t($string, $array = null)
698 return Ak
::t($string, $array, AkInflector
::underscore($this->getControllerName()));
704 * Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
706 * * <tt>Array</tt>: The URL will be generated by calling $this->UrlFor with the +options+.
707 * * <tt>String starting with protocol:// (like http://)</tt>: Is passed straight through
708 * as the target for redirection.
709 * * <tt>String not containing a protocol</tt>: The current protocol and host is prepended to the string.
710 * * <tt>back</tt>: Back to the page that issued the Request-> Useful for forms that are
711 * triggered from multiple places.
712 * Short-hand for redirectTo(Request->env["HTTP_REFERER"])
715 * redirectTo(array('action' => 'show', 'id' => 5));
716 * redirectTo('http://www.akelos.com');
717 * redirectTo('/images/screenshot.jpg');
718 * redirectTo('back');
720 * The redirection happens as a "302 Moved" header.
722 function redirectTo($options = array(), $parameters_for_method_reference = null)
724 if(is_string($options)) {
725 if(preg_match('/^\w+:\/\/.*/',$options)){
726 if($this->_hasPerformed()){
727 $this->_doubleRenderError();
729 AK_LOG_EVENTS
&& !empty($this->_Logger
) ?
$this->_Logger
->message('Redirected to '.$options) : null;
730 $this->_handleFlashAttribute();
731 $this->Response
->redirect($options);
732 $this->Response
->redirected_to
= $options;
733 $this->performed_redirect
= true;
734 }elseif ($options == 'back'){
735 $this->redirectTo($this->Request
->env
['HTTP_REFERER']);
737 $this->redirectTo($this->Request
->getProtocol(). $this->Request
->getHostWithPort(). $options);
740 if(empty($parameters_for_method_reference)){
741 $this->redirectTo($this->UrlFor($options));
742 $this->Response
->redirected_to
= $options;
744 $this->redirectTo($this->UrlFor($options, $parameters_for_method_reference));
745 $this->Response
->redirected_to
= $options;
746 $this->Response
->redirected_to_method_params
= $parameters_for_method_reference;
751 function redirectToAction($action, $options = array())
753 $this->redirectTo(array_merge(array('action'=>$action), $options));
758 * This methods are required for retrieving available controllers for URL Routing
760 function rewriteOptions($options)
762 $defaults = $this->defaultUrlOptions($options);
763 if(!empty($this->module_name
)){
764 $defaults['module'] = $this->getModuleName();
766 if(!empty($options['controller']) && strstr($options['controller'], '/')){
767 $defaults['module'] = substr($options['controller'], 0, strrpos($options['controller'], '/'));
768 $options['controller'] = substr($options['controller'], strrpos($options['controller'], '/') +
1);
770 $options = !empty($defaults) ?
array_merge($defaults, $options) : $options;
771 $options['controller'] = empty($options['controller']) ? AkInflector
::underscore($this->getControllerName()) : $options['controller'];
775 function getControllerName()
778 if(!isset($this->controller_name
)){
779 $current_class_name = str_replace('_', '::', get_class($this));
781 $included_controllers = $this->_getIncludedControllerNames();
782 $lowercase_included_controllers = array_map('strtolower', $included_controllers);
783 $key = array_search(strtolower($current_class_name), $lowercase_included_controllers, true);
784 $found_controller = substr($included_controllers[$key], 0, -10);
785 $this->controller_name
= $found_controller;
786 $this->_removeModuleNameFromControllerName();
789 return $this->controller_name
;
792 function getModuleName()
794 return $this->module_name
;
798 * Removes the modules name from the controller if exists.
800 * Additionally it sets the default url_options for this controller and the module name scope.
802 function _removeModuleNameFromControllerName()
804 if(strstr($this->controller_name
, '::')){
805 $module_parts = substr($this->controller_name
, 0, strrpos($this->controller_name
, '::'));
806 $this->module_name
= join('/', array_map(array('AkInflector','underscore'), strstr($module_parts, '::') ?
explode('::', $module_parts) : array($module_parts)));
807 $this->controller_name
= substr($this->controller_name
, strrpos($this->controller_name
, '::')+
2);
812 function _getTemplateBasePath()
814 return AK_APP_DIR
.DS
.'views'.DS
.(empty($this->_module_path
)?
'':$this->_module_path
).$this->Request
->getController();
817 function _getIncludedControllerNames()
819 $controllers = array();
820 foreach (get_included_files() as $file_name){
821 if(strstr($file_name,AK_CONTROLLERS_DIR
)){
822 $controllers[] = AkInflector
::modulize(str_replace(array(AK_CONTROLLERS_DIR
.DS
,'.php'),'',$file_name));
833 * Overwrite to implement a number of default options that all urlFor-based methods will use.
834 * The default options should come in
835 * the form of a an array, just like the one you would use for $this->UrlFor directly. Example:
837 * function defaultUrlOptions($options)
839 * return array('project' => ($this->Project->isActive() ? $this->Project->url_name : 'unknown'));
842 * As you can infer from the example, this is mostly useful for situations where you want to
843 * centralize dynamic decisions about the urls as they stem from the business domain.
844 * Please note that any individual $this->UrlFor call can always override the defaults set
847 function defaultUrlOptions($options)
853 * Returns a URL that has been rewritten according to the options array and the defined Routes.
854 * (For doing a complete redirect, use redirectTo).
856 * <tt>$this->UrlFor</tt> is used to:
858 * All keys given to $this->UrlFor are forwarded to the Route module, save for the following:
859 * * <tt>anchor</tt> -- specifies the anchor name to be appended to the path. For example,
860 * <tt>$this->UrlFor(array('controller' => 'posts', 'action' => 'show', 'id' => 10, 'anchor' => 'comments'</tt>
861 * will produce "/posts/show/10#comments".
862 * * <tt>only_path</tt> -- if true, returns the absolute URL (omitting the protocol, host name, and port)
863 * * <tt>trailing_slash</tt> -- if true, adds a trailing slash, as in "/archive/2005/". Note that this
864 * is currently not recommended since it breaks caching.
865 * * <tt>host</tt> -- overrides the default (current) host if provided
866 * * <tt>protocol</tt> -- overrides the default (current) protocol if provided
868 * The URL is generated from the remaining keys in the array. A URL contains two key parts: the <base> and a query string.
869 * Routes composes a query string as the key/value pairs not included in the <base>.
871 * The default Routes setup supports a typical Akelos Framework path of "controller/action/id"
872 * where action and id are optional, with
873 * action defaulting to 'index' when not given. Here are some typical $this->UrlFor statements
874 * and their corresponding URLs:
876 * $this->UrlFor(array('controller'=>'posts','action'=>'recent')); // 'proto://host.com/posts/recent'
877 * $this->UrlFor(array('controller'=>'posts','action'=>'index')); // 'proto://host.com/posts'
878 * $this->UrlFor(array('controller'=>'posts','action'=>'show','id'=>10)); // 'proto://host.com/posts/show/10'
880 * When generating a new URL, missing values may be filled in from the current
881 * Request's parameters. For example,
882 * <tt>$this->UrlFor(array('action'=>'some_action'));</tt> will retain the current controller,
883 * as expected. This behavior extends to other parameters, including <tt>controller</tt>,
884 * <tt>id</tt>, and any other parameters that are placed into a Route's path.
886 * The URL helpers such as <tt>$this->UrlFor</tt> have a limited form of memory:
887 * when generating a new URL, they can look for missing values in the current Request's parameters.
888 * Routes attempts to guess when a value should and should not be
889 * taken from the defaults. There are a few simple rules on how this is performed:
891 * * If the controller name begins with a slash, no defaults are used: <tt>$this->UrlFor(array('controller'=>'/home'));</tt>
892 * * If the controller changes, the action will default to index unless provided
894 * The final rule is applied while the URL is being generated and is best illustrated by an example. Let us consider the
895 * route given by <tt>map->connect('people/:last/:first/:action', array('action' => 'bio', 'controller' => 'people'))</tt>.
897 * Suppose that the current URL is "people/hh/david/contacts". Let's consider a few
898 * different cases of URLs which are generated from this page.
900 * * <tt>$this->UrlFor(array('action'=>'bio'));</tt> -- During the generation of this URL,
901 * default values will be used for the first and
902 * last components, and the action shall change. The generated URL will be, "people/hh/david/bio".
903 * * <tt>$this->UrlFor(array('first'=>'davids-little-brother'));</tt> This
904 * generates the URL 'people/hh/davids-little-brother' -- note
905 * that this URL leaves out the assumed action of 'bio'.
907 * However, you might ask why the action from the current Request, 'contacts', isn't
908 * carried over into the new URL. The answer has to do with the order in which
909 * the parameters appear in the generated path. In a nutshell, since the
910 * value that appears in the slot for <tt>first</tt> is not equal to default value
911 * for <tt>first</tt> we stop using defaults. On it's own, this rule can account
912 * for much of the typical Akelos Framework URL behavior.
914 * Although a convienence, defaults can occasionaly get in your way. In some cases
915 * a default persists longer than desired.
916 * The default may be cleared by adding <tt>'name' => null</tt> to <tt>$this->UrlFor</tt>'s options.
917 * This is often required when writing form helpers, since the defaults in play
918 * may vary greatly depending upon where the helper is used from. The following line
919 * will redirect to PostController's default action, regardless of the page it is
922 * $this->UrlFor(array('controller' => 'posts', 'action' => null));
924 * If you explicitly want to create a URL that's almost the same as the current URL, you can do so using the
925 * overwrite_params options. Say for your posts you have different views for showing and printing them.
926 * Then, in the show view, you get the URL for the print view like this
928 * $this->UrlFor(array('overwrite_params' => array('action' => 'print')));
930 * This takes the current URL as is and only exchanges the action. In contrast,
931 * <tt>$this->UrlFor(array('action'=>'print'));</tt>
932 * would have slashed-off the path components after the changed action.
934 function urlFor($options = array(), $parameters_for_method_reference = null)
936 return $this->rewrite($this->rewriteOptions($options));
939 function addToUrl($options = array(), $options_to_exclude = array())
941 $options_to_exclude = array_merge(array('ak','lang',AK_SESSION_NAME
,'AK_SESSID','PHPSESSID'), $options_to_exclude);
942 $options = array_merge(array_merge(array('action'=>$this->Request
->getAction()),$this->params
),$options);
943 foreach ($options_to_exclude as $option_to_exclude){
944 unset($options[$option_to_exclude]);
946 return $this->urlFor($options);
949 function getActionName()
951 return $this->Request
->getAction();
955 function _doubleRenderError($message = null)
957 trigger_error(!empty($message) ?
$message : Ak
::t("Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and only once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirectTo(...); return;\". Finally, note that to cause a before filter to halt execution of the rest of the filter chain, the filter must return false, explicitly, so \"render(...); return; false\"."),E_USER_ERROR
);
960 function _hasPerformed()
962 return !empty($this->performed_render
) ||
!empty($this->performed_redirect
);
965 function _getRequestOrigin()
967 return $this->Request
->remote_ip
.' at '.Ak
::getDate();
970 function _getCompleteRequestUri()
972 return $this->Request
->protocol
. $this->Request
->host
. $this->Request
->request_uri
;
975 function _closeSession()
977 !empty($this->session
) ?
session_write_close() : null;
981 function _hasTemplate($template_name = null)
983 return file_exists(empty($template_name) ?
$this->getDefaultTemplateName() : $template_name);
986 function _templateIsPublic($template_name = null)
988 $template_name = empty($template_name) ?
$this->getDefaultTemplateName() : $template_name;
989 return $this->Template
->fileIsPublic($template_name);
992 function _isTemplateExemptFromLayout($template_name = null)
994 $template_name = empty($template_name) ?
$this->getDefaultTemplateName() : $template_name;
995 return $this->Template
->_javascriptTemplateExists($template_name);
998 function _assertExistanceOfTemplateFile($template_name)
1000 $extension = $this->Template
->delegateTemplateExists($template_name);
1001 $this->full_template_path
= $this->Template
->getFullTemplatePath($template_name, $extension ?
$extension : 'tpl');
1002 if(!$this->_hasTemplate($this->full_template_path
)){
1003 if(!empty($this->_ignore_missing_templates
) && $this->_ignore_missing_templates
=== true){
1006 $template_type = strstr($template_name,'layouts') ?
'layout' : 'template';
1007 trigger_error(Ak
::t('Missing %template_type %full_template_path',array('%template_type'=>$template_type, '%full_template_path'=>$this->full_template_path
)), E_USER_WARNING
);
1011 function getDefaultTemplateName($default_action_name = null)
1013 return empty($default_action_name) ?
(empty($this->_default_template_name
) ?
$this->_action_name
: $this->_default_template_name
) : $default_action_name;
1016 function setDefaultTemplateName($template_name)
1018 $this->_default_template_name
= $template_name;
1023 function rewrite($options = array())
1025 return $this->_rewriteUrl($this->_rewritePath($options), $options);
1031 return $this->Request
->getProtocol().$this->Request
->getHostWithPort().
1032 $this->Request
->getPath().$this->parameters
['controller'].$this->parameters
['action'].$this->parameters
['inspect'];
1036 * Given a path and options, returns a rewritten URL string
1038 function _rewriteUrl($path, $options)
1040 $rewritten_url = '';
1041 if(empty($options['only_path'])){
1042 $rewritten_url .= !empty($options['protocol']) ?
$options['protocol'] : $this->Request
->getProtocol();
1043 $rewritten_url .= empty($rewritten_url) ||
strpos($rewritten_url,'://') ?
'' : '://';
1044 $rewritten_url .= $this->_rewriteAuthentication($options);
1045 $rewritten_url .= !empty($options['host']) ?
$options['host'] : $this->Request
->getHostWithPort();
1046 $options = Ak
::delete($options, array('user','password','host','protocol'));
1049 $rewritten_url .= empty($options['skip_relative_url_root']) ?
$this->Request
->getRelativeUrlRoot() : '';
1051 if(empty($options['skip_url_locale'])){
1052 $locale = $this->Request
->getLocaleFromUrl();
1053 if(empty($options['lang'])){
1054 $rewritten_url .= (empty($locale) ?
'' : '/').$locale;
1059 $rewritten_url .= (substr($rewritten_url,-1) == '/' ?
'' : (AK_URL_REWRITE_ENABLED ?
'' : (!empty($path[0]) && $path[0] != '/' ?
'/' : '')));
1060 $rewritten_url .= $path;
1061 $rewritten_url .= empty($options['trailing_slash']) ?
'' : '/';
1062 $rewritten_url .= empty($options['anchor']) ?
'' : '#'.$options['anchor'];
1064 return $rewritten_url;
1067 function _rewriteAuthentication($options)
1069 if(!isset($options['user']) && isset($options['password'])){
1070 return urlencode($options['user']).':'.urlencode($options['password']).'@';
1076 function _rewritePath($options)
1078 if(!empty($options['params'])){
1079 foreach ($options['params'] as $k=>$v){
1082 unset($options['params']);
1084 if(!empty($options['overwrite_params'])){
1085 foreach ($options['overwrite_params'] as $k=>$v){
1088 unset($options['overwrite_params']);
1090 foreach (array('anchor', 'params', 'only_path', 'host', 'protocol', 'trailing_slash', 'skip_relative_url_root') as $k){
1091 unset($options[$k]);
1093 $path = Ak
::toUrl($options);
1098 * Returns a query string with escaped keys and values from the passed array. If the passed
1099 * array contains an 'id' it'll
1100 * be added as a path element instead of a regular parameter pair.
1102 function buildQueryString($array, $only_keys = null)
1104 $array = !empty($only_keys) ?
array_keys($array) : $array;
1105 return Ak
::toUrl($array);
1110 * Layouts reverse the common pattern of including shared headers and footers in many templates
1111 * to isolate changes in repeated setups. The inclusion pattern has pages that look like this:
1113 * <?php echo $controller->render('shared/header') ?>
1115 * <?php echo $controller->render('shared/footer') ?>
1117 * This approach is a decent way of keeping common structures isolated from the
1118 * changing content, but it's verbose and if( you ever want to change the structure
1119 * of these two includes, you'll have to change all the templates.
1121 * With layouts, you can flip it around and have the common structure know where
1122 * to insert changing content. This means that the header and footer are only
1123 * mentioned in one place, like this:
1125 * <!-- The header part of this layout -->
1126 * <?php echo $content_for_layout ?>
1127 * <!-- The footer part of this layout -->
1129 * And then you have content pages that look like this:
1133 * Not a word about common structures. At rendering time, the content page is
1134 * computed and then inserted in the layout,
1137 * <!-- The header part of this layout -->
1139 * <!-- The footer part of this layout -->
1141 * == Accessing shared variables
1143 * Layouts have access to variables specified in the content pages and vice versa.
1144 * This allows you to have layouts with references that won't materialize before
1147 * <h1><?php echo $page_title ?></h1>
1148 * <?php echo $content_for_layout ?>
1150 * ...and content pages that fulfill these references _at_ rendering time:
1152 * <?php $page_title = 'Welcome'; ?>
1153 * Off-world colonies offers you a chance to start a new life
1155 * The result after rendering is:
1158 * Off-world colonies offers you a chance to start a new life
1160 * == Automatic layout assignment
1162 * If there is a template in <tt>app/views/layouts/</tt> with the same name as
1163 * the current controller then it will be automatically
1164 * set as that controller's layout unless explicitly told otherwise. Say you have
1165 * a WeblogController, for example. If a template named <tt>app/views/layouts/weblog.tpl</tt>
1166 * exists then it will be automatically set as the layout for your WeblogController.
1167 * You can create a layout with the name <tt>application.tpl</tt>
1168 * and this will be set as the default controller if there is no layout with
1169 * the same name as the current controller and there is no layout explicitly
1170 * assigned on the +layout+ attribute. Setting a layout explicitly will always
1171 * override the automatic behaviour
1172 * for the controller where the layout is set. Explicitly setting the layout
1173 * in a parent class, though, will not override the
1174 * child class's layout assignement if the child class has a layout with the same name.
1176 * == Inheritance for layouts
1178 * Layouts are shared downwards in the inheritance hierarchy, but not upwards. Examples:
1180 * class BankController extends AkActionController
1182 * var $layout = 'bank_standard';
1185 * class InformationController extends BankController
1189 * class VaultController extends BankController
1191 * var $layout = 'access_level_layout';
1194 * class EmployeeController extends BankController
1196 * var $layout = null;
1199 * The InformationController uses 'bank_standard' inherited from the BankController, the VaultController
1200 * and picks the layout 'access_level_layout', and the EmployeeController doesn't want to use a layout at all.
1202 * == Types of layouts
1204 * Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
1205 * you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
1206 * be done either by an inline method.
1208 * The method reference is the preferred approach to variable layouts and is used like this:
1210 * class WeblogController extends AkActionController
1212 * function __construct()
1214 * $this->setLayout(array(&$this, '_writersAndReaders'));
1222 * function _writersAndReaders()
1224 * return is_logged_in() ? 'writer_layout' : 'reader_layout';
1228 * Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
1229 * is logged in or not.
1231 * The most common way of specifying a layout is still just as a plain template name:
1233 * class WeblogController extends AkActionController
1235 * var $layout = 'weblog_standard';
1238 * If no directory is specified for the template name, the template will by default by looked for in +app/views/layouts/+.
1240 * == Conditional layouts
1242 * If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
1243 * a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
1244 * <tt>only</tt> and <tt>except</tt> options can be passed to the layout call. For example:
1246 * class WeblogController extends AkActionController
1248 * function __construct()
1250 * $this->setLayout('weblog_standard', array('except' => 'rss'));
1257 * This will assign 'weblog_standard' as the WeblogController's layout except for the +rss+ action, which will not wrap a layout
1258 * around the rendered view.
1260 * Both the <tt>only</tt> and <tt>except</tt> condition can accept an arbitrary number of method names, so
1261 * <tt>'except' => array('rss', 'text_only')</tt> is valid, as is <tt>'except' => 'rss'</tt>.
1263 * == Using a different layout in the action render call
1265 * If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
1266 * Some times you'll have exceptions, though, where one action wants to use a different layout than the rest of the controller.
1267 * This is possible using the <tt>render</tt> method. It's just a bit more manual work as you'll have to supply fully
1268 * qualified template and layout names as this example shows:
1270 * class WeblogController extends AkActionController
1274 * $this->render(array('action'=>'help/index','layout'=>'help'));
1280 * If a layout is specified, all actions rendered through render and render_action will have their result assigned
1281 * to <tt>$this->content_for_layout</tt>, which can then be used by the layout to insert their contents with
1282 * <tt><?php echo $$this->content_for_layout ?></tt>. This layout can itself depend on instance variables assigned during action
1283 * performance and have access to them as any normal template would.
1285 function setLayout($template_name, $conditions = array())
1287 $this->_addLayoutConditions($conditions);
1288 $this->layout
= $template_name;
1291 function getLayoutConditions()
1293 return empty($this->_layout_conditions
) ?
array() : $this->_layout_conditions
;
1296 function _addLayoutConditions($conditions)
1298 $this->_layout_conditions
= $conditions;
1304 * Returns the name of the active layout. If the layout was specified as a method reference, this method
1305 * is called and the return value is used. Likewise if( the layout was specified as an inline method (through a method
1306 * object). If the layout was defined without a directory, layouts is assumed. So <tt>setLayout('weblog/standard')</tt> will return
1307 * weblog/standard, but <tt>setLayout('standard')</tt> will return layouts/standard.
1309 function getActiveLayout($passed_layout = null)
1311 if(empty($passed_layout)){
1312 $layout = !isset($this->layout
) ? AkInflector
::underscore($this->getControllerName()) : $this->layout
;
1314 $layout =& $passed_layout;
1316 if(is_array($layout) && is_object($layout[0]) && method_exists($layout[0], $layout[1])){
1317 $this->active_layout
= $layout[0]->{$layout[1]}();
1318 }elseif(method_exists($this,$layout) && strtolower(get_class($this)) !== strtolower($layout)){
1319 $this->active_layout
= $this->$layout();
1321 $this->active_layout
= $layout;
1324 if(!empty($this->active_layout
)){
1325 return strstr($this->active_layout
,DS
) ?
$this->active_layout
: 'layouts'.DS
.$this->active_layout
;
1330 function renderWithALayout($options = null, $status = null, $layout = null)
1332 $template_with_options = !empty($options) && is_array($options);
1334 if($this->_canApplyLayout($template_with_options, $options) && ($layout = $this->_pickLayout($template_with_options, $options, $layout))){
1336 $options = $template_with_options?
array_merge((array)$options,array('layout'=>false)) : $options;
1338 $this->content_for_layout
= $this->render($options, $status);
1340 if($template_with_options){
1341 $status = empty($options['status']) ?
$status : $options['status'];
1344 $this->eraseRenderResults();
1345 $this->_addVariablesToAssigns();
1347 return $this->renderText($this->Template
->renderFile($layout, true, &$this->_assigns
), $status);
1349 return $this->render($options, $status, &$this->_assigns
);
1353 function _canApplyLayout($template_with_options, $options)
1355 return !empty($template_with_options) ?
$this->_isCandidateForLayout($options) : !$this->_isTemplateExemptFromLayout();
1358 function _isCandidateForLayout($options)
1360 return !empty($options['layout']) ||
1361 (empty($options['text']) && empty($options['file']) && empty($options['inline']) && empty($options['partial']) && empty($options['nothing'])) &&
1362 !$this->_isTemplateExemptFromLayout($this->_getDefaultTemplateName(empty($options['action']) ?
$options['template'] : $options['action']));
1365 function _pickLayout($template_with_options, $options, $layout = null)
1367 if(!empty($template_with_options)){
1368 $layout = empty($options['layout']) ?
($this->_doesActionHasLayout() ?
$this->getActiveLayout(): false) : $this->getActiveLayout($options['layout']);
1369 }elseif(empty($layout) ||
$layout === true){
1370 $layout = $this->_doesActionHasLayout() ?
$this->getActiveLayout() : false;
1372 if(!empty($layout)){
1373 $layout = strstr($layout,'/') ||
strstr($layout,DS
) ?
$layout : 'layouts'.DS
.$layout;
1374 $layout = substr($layout,0,7) === 'layouts' ?
1375 (empty($this->_module_path
) ? AK_VIEWS_DIR
.DS
.$layout.'.tpl' : AK_VIEWS_DIR
.DS
.'layouts'.DS
.trim($this->_module_path
, DS
).'.tpl') :
1377 if (file_exists($layout)) {
1382 if(empty($layout) && $layout !== false && defined('AK_DEFAULT_LAYOUT')){
1383 $layout = AK_VIEWS_DIR
.DS
.'layouts'.DS
.AK_DEFAULT_LAYOUT
.'.tpl';
1385 return file_exists($layout) ?
$layout : false;
1388 function _doesActionHasLayout()
1390 $conditions = $this->getLayoutConditions();
1392 $action_name = $this->Request
->getAction();
1393 if(!empty($conditions['only']) && ((is_array($conditions['only']) && in_array($action_name,$conditions['only'])) ||
1394 (is_string($conditions['only']) && $action_name == $conditions['only']))){
1396 }elseif (!empty($conditions['only'])){
1399 if(!empty($conditions['except']) && ((is_array($conditions['except']) && in_array($action_name,$conditions['except'])) ||
1400 (is_string($conditions['except']) && $action_name == $conditions['except']))){
1412 * Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do
1413 * authentication, caching, or auditing before the intended action is performed. Or to do localization or output
1414 * compression after the action has been performed.
1416 * Filters have access to the request, response, and all the instance variables set by other filters in the chain
1417 * or by the action (in the case of after filters). Additionally, it's possible for a pre-processing <tt>beforeFilter</tt>
1418 * to halt the processing before the intended action is processed by returning false or performing a redirect or render.
1419 * This is especially useful for filters like authentication where you're not interested in allowing the action to be
1420 * performed if the proper credentials are not in order.
1422 * == Filter inheritance
1424 * Controller inheritance hierarchies share filters downwards, but subclasses can also add new filters without
1425 * affecting the superclass. For example:
1427 * class BankController extends AkActionController
1429 * function __construct()
1431 * $this->beforeFilter('_audit');
1434 * function _audit(&$controller)
1436 * // record the action and parameters in an audit log
1440 * class VaultController extends BankController
1442 * function __construct()
1444 * $this->beforeFilter('_verifyCredentials');
1447 * function _verifyCredentials(&$controller)
1449 * // make sure the user is allowed into the vault
1453 * Now any actions performed on the BankController will have the audit method called before. On the VaultController,
1454 * first the audit method is called, then the _verifyCredentials method. If the _audit method returns false, then
1455 * _verifyCredentials and the intended action are never called.
1459 * A filter can take one of three forms: method reference, external class, or inline method. The first
1460 * is the most common and works by referencing a method somewhere in the inheritance hierarchy of
1461 * the controller by use of a method name. In the bank example above, both BankController and VaultController use this form.
1463 * Using an external class makes for more easily reused generic filters, such as output compression. External filter classes
1464 * are implemented by having a static +filter+ method on any class and then passing this class to the filter method. Example:
1466 * class OutputCompressionFilter
1468 * function filter(&$controller)
1470 * $controller->response->body = compress($controller->response->body);
1474 * class NewspaperController extends AkActionController
1476 * function __construct()
1478 * $this->afterFilter(new OutputCompressionFilter());
1482 * The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can
1483 * manipulate them as it sees fit.
1486 * == Filter chain ordering
1488 * Using <tt>beforeFilter</tt> and <tt>afterFilter</tt> appends the specified filters to the existing chain. That's usually
1489 * just fine, but some times you care more about the order in which the filters are executed. When that's the case, you
1490 * can use <tt>prependBeforeFilter</tt> and <tt>prependAfterFilter</tt>. Filters added by these methods will be put at the
1491 * beginning of their respective chain and executed before the rest. For example:
1493 * class ShoppingController extends AkActionController
1495 * function __construct()
1497 * $this->beforeFilter('verifyOpenShop');
1502 * class CheckoutController extends AkActionController
1504 * function __construct()
1506 * $this->prependBeforeFilter('ensureItemsInCart', 'ensureItemsInStock');
1510 * The filter chain for the CheckoutController is now <tt>ensureItemsInCart, ensureItemsInStock,</tt>
1511 * <tt>verifyOpenShop</tt>. So if either of the ensure filters return false, we'll never get around to see if the shop
1514 * You may pass multiple filter arguments of each type.
1518 * In addition to the individual before and after filters, it's also possible to specify that a single object should handle
1519 * both the before and after call. That's especially useful when you need to keep state active between the before and after,
1520 * such as the example of a benchmark filter below:
1522 * class WeblogController extends AkActionController
1524 * function __construct()
1526 * $this->aroundFilter(new BenchmarkingFilter());
1529 * // Before this action is performed, BenchmarkingFilter->before($controller) is executed
1533 * // After this action has been performed, BenchmarkingFilter->after($controller) is executed
1536 * class BenchmarkingFilter
1538 * function before(&$controller)
1543 * function after(&$controller)
1550 * == Filter chain skipping
1552 * Some times its convenient to specify a filter chain in a superclass that'll hold true for the majority of the
1553 * subclasses, but not necessarily all of them. The subclasses that behave in exception can then specify which filters
1554 * they would like to be relieved of. Examples
1556 * class ApplicationController extends AkActionController
1558 * function __construct()
1560 * $this->beforeFilter('authenticate');
1564 * class WeblogController extends ApplicationController
1566 * // will run the authenticate filter
1569 * class SignupController extends AkActionController
1571 * function __construct()
1573 * $this->skipBeforeFilter('authenticate');
1575 * // will not run the authenticate filter
1578 * == Filter conditions
1580 * Filters can be limited to run for only specific actions. This can be expressed either by listing the actions to
1581 * exclude or the actions to include when executing the filter. Available conditions are +only+ or +except+, both
1582 * of which accept an arbitrary number of method references. For example:
1584 * class Journal extends AkActionController
1586 * function __construct()
1587 * { // only require authentication if the current action is edit or delete
1588 * $this->beforeFilter(array('_authorize'=>array('only'=>array('edit','delete')));
1591 * function _authorize(&$controller)
1593 * // redirect to login unless authenticated
1598 var $_includedActions = array(), $_beforeFilters = array(), $_afterFilters = array(), $_excludedActions = array();
1600 * The passed <tt>filters</tt> will be appended to the array of filters that's run _before_ actions
1601 * on this controller are performed.
1603 function appendBeforeFilter()
1605 $filters = array_reverse(func_get_args());
1606 foreach (array_keys($filters) as $k){
1607 $conditions = $this->_extractConditions(&$filters[$k]);
1608 $this->_addActionConditions($filters[$k], $conditions);
1609 $this->_appendFilterToChain('before', $filters[$k]);
1614 * The passed <tt>filters</tt> will be prepended to the array of filters that's run _before_ actions
1615 * on this controller are performed.
1617 function prependBeforeFilter()
1619 $filters = array_reverse(func_get_args());
1620 foreach (array_keys($filters) as $k){
1621 $conditions = $this->_extractConditions(&$filters[$k]);
1622 $this->_addActionConditions($filters[$k], $conditions);
1623 $this->_prependFilterToChain('before', $filters[$k]);
1628 * Short-hand for appendBeforeFilter since that's the most common of the two.
1630 function beforeFilter()
1632 $filters = func_get_args();
1633 foreach (array_keys($filters) as $k){
1634 $this->appendBeforeFilter($filters[$k]);
1639 * The passed <tt>filters</tt> will be appended to the array of filters that's run _after_ actions
1640 * on this controller are performed.
1642 function appendAfterFilter()
1644 $filters = array_reverse(func_get_args());
1645 foreach (array_keys($filters) as $k){
1646 $conditions = $this->_extractConditions(&$filters[$k]);
1647 $this->_addActionConditions(&$filters[$k], $conditions);
1648 $this->_appendFilterToChain('after', &$filters[$k]);
1654 * The passed <tt>filters</tt> will be prepended to the array of filters that's run _after_ actions
1655 * on this controller are performed.
1657 function prependAfterFilter()
1659 $filters = array_reverse(func_get_args());
1660 foreach (array_keys($filters) as $k){
1661 $conditions = $this->_extractConditions(&$filters[$k]);
1662 $this->_addActionConditions(&$filters[$k], $conditions);
1663 $this->_prependFilterToChain('after', &$filters[$k]);
1668 * Short-hand for appendAfterFilter since that's the most common of the two.
1670 function afterFilter()
1672 $filters = func_get_args();
1673 foreach (array_keys($filters) as $k){
1674 $this->appendAfterFilter($filters[$k]);
1679 * The passed <tt>filters</tt> will have their +before+ method appended to the array of filters that's run both before actions
1680 * on this controller are performed and have their +after+ method prepended to the after actions. The filter objects must all
1681 * respond to both +before+ and +after+. So if you do appendAroundFilter(new A(), new B()), the callstack will look like:
1688 function appendAroundFilter()
1690 $filters = func_get_args();
1691 foreach (array_keys($filters) as $k){
1692 $this->_ensureFilterRespondsToBeforeAndAfter(&$filters[$k]);
1693 $this->appendBeforeFilter(array(&$filters[$k],'before'));
1695 $filters = array_reverse($filters);
1696 foreach (array_keys($filters) as $k){
1697 $this->prependAfterFilter(array(&$filters[$k],'after'));
1702 * The passed <tt>filters</tt> will have their +before+ method prepended to the array of filters that's run both before actions
1703 * on this controller are performed and have their +after+ method appended to the after actions. The filter objects must all
1704 * respond to both +before+ and +after+. So if you do appendAroundFilter(new A(), new B()), the callstack will look like:
1711 function prependAroundFilter()
1713 $filters = func_get_args();
1714 foreach (array_keys($filters) as $k){
1715 $this->_ensureFilterRespondsToBeforeAndAfter(&$filters[$k]);
1716 $this->prependBeforeFilter(array(&$filters[$k],'before'));
1718 $filters = array_reverse($filters);
1719 foreach (array_keys($filters) as $k){
1720 $this->appendAfterFilter(array(&$filters[$k],'after'));
1725 * Short-hand for appendAroundFilter since that's the most common of the two.
1727 function aroundFilter()
1729 $filters = func_get_args();
1730 call_user_func_array(array(&$this,'appendAroundFilter'), $filters);
1734 * Removes the specified filters from the +before+ filter chain.
1735 * This is especially useful for managing the chain in inheritance hierarchies where only one out
1736 * of many sub-controllers need a different hierarchy.
1738 function skipBeforeFilter($filters)
1740 $filters = func_get_args();
1741 $this->_skipFilter($filters, 'before');
1745 * Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference
1746 * filters, not instances. This is especially useful for managing the chain in inheritance hierarchies where only one out
1747 * of many sub-controllers need a different hierarchy.
1749 function skipAfterFilter($filters)
1751 $filters = func_get_args();
1752 $this->_skipFilter($filters, 'after');
1755 function _skipFilter(&$filters, $type)
1757 $_filters =& $this->{'_'.$type.'Filters'};
1758 // array_diff doesn't play nice with some PHP5 releases when it comes to
1759 // Objects as it only diff equal references, not object types
1760 foreach (array_keys($filters) as $k){
1762 if(is_object($filters[$k])){
1763 foreach (array_keys($_filters) as $k2){
1764 if(is_object($_filters[$k2]) && get_class($_filters[$k2]) == get_class($filters[$k])){
1770 $pos = array_search($filters[$k], $_filters);
1773 array_splice($_filters, $pos, 1, null);
1776 $_filters = array_diff($_filters,array($filters[$k]));
1781 * Returns all the before filters for this class.
1783 function beforeFilters()
1785 return $this->_beforeFilters
;
1789 * Returns all the after filters for this class and all its ancestors.
1791 function afterFilters()
1793 return $this->_afterFilters
;
1797 * Returns a mapping between filters and the actions that may run them.
1799 function includedActions()
1801 return $this->_includedActions
;
1805 * Returns a mapping between filters and actions that may not run them.
1807 function excludedActions()
1809 return $this->_excludedActions
;
1813 function _appendFilterToChain($condition, $filters)
1815 $this->{"_{$condition}Filters"}[] =& $filters;
1818 function _prependFilterToChain($condition, $filters)
1820 array_unshift($this->{"_{$condition}Filters"}, $filters);
1823 function _ensureFilterRespondsToBeforeAndAfter(&$filter_object)
1825 if(!method_exists(&$filter_object,'before') && !method_exists(&$filter_object,'after')){
1826 trigger_error(Ak
::t('Filter object must respond to both before and after'), E_USER_ERROR
);
1830 function _extractConditions(&$filters)
1832 if(is_array($filters) && !isset($filters[0])){
1833 $keys = array_keys($filters);
1834 $conditions = $filters[$keys[0]];
1835 $filters = $keys[0];
1840 function _addActionConditions($filters, $conditions)
1842 if(!empty($conditions['only'])){
1843 $this->_includedActions
[$this->_filterId($filters)] = $this->_conditionArray($this->_includedActions
, $conditions['only']);
1845 if(!empty($conditions['except'])){
1846 $this->_excludedActions
[$this->_filterId($filters)] = $this->_conditionArray($this->_excludedActions
, $conditions['except']);
1850 function _conditionArray($actions, $filter_actions)
1852 $filter_actions = is_array($filter_actions) ?
$filter_actions : array($filter_actions);
1853 $filter_actions = array_map(array(&$this,'_filterId'),$filter_actions);
1854 return array_unique(array_merge($actions, $filter_actions));
1858 function _filterId($filters)
1860 return is_string($filters) ?
$filters : md5(serialize($filters));
1863 function performActionWithoutFilters($action)
1865 if(method_exists(&$this, $action)){
1866 call_user_func_array(array(&$this, $action), @(array)$this->passed_args
);
1870 function performActionWithFilters($method = '')
1872 if ($this->beforeAction($method) !== false ||
empty($this->_performed
)){
1873 $this->performActionWithoutFilters($method);
1874 $this->afterAction($method);
1877 return $this->performActionWithoutFilters($method);
1880 function performAction($method = '')
1882 $this->performActionWithFilters($method);
1887 * Calls all the defined before-filter filters, which are added by using "beforeFilter($method)".
1888 * If any of the filters return false, no more filters will be executed and the action is aborted.
1890 function beforeAction($method = '')
1892 Ak
::profile('Running before controller action filters '.__CLASS__
.'::'.__FUNCTION__
.' '.__LINE__
);
1893 return $this->_callFilters($this->_beforeFilters
, $method);
1897 * Calls all the defined after-filter filters, which are added by using "afterFilter($method)".
1898 * If any of the filters return false, no more filters will be executed.
1900 function afterAction($method = '')
1902 Ak
::profile('Running after controller action filters '.__CLASS__
.'::'.__FUNCTION__
.' '.__LINE__
);
1903 return $this->_callFilters(&$this->_afterFilters
, $method);
1907 function _callFilters(&$filters, $method = '')
1909 $filter_result = null;
1910 foreach (array_keys($filters) as $k){
1911 $filter =& $filters[$k];
1912 if(!$this->_actionIsExempted($filter, $method)){
1913 if(is_array($filter) && is_object($filter[0]) && method_exists($filter[0], $filter[1])){
1914 $filter_result = $filter[0]->$filter[1]($this);
1915 }elseif(!is_object($filter) && method_exists($this, $filter)){
1916 $filter_result = $this->$filter($this);
1917 }elseif(is_object($filter) && method_exists($filter, 'filter')){
1918 $filter_result = $filter->filter($this);
1920 trigger_error(Ak
::t('Invalid filter %filter. Filters need to be a method name or a class implementing a static filter method', array('%filter'=>$filter)), E_USER_WARNING
);
1924 if($filter_result === false){
1925 !empty($this->_Logger
) ?
$this->_Logger
->info(Ak
::t('Filter chain halted as '.$filter.' returned false')) : null;
1929 return $filter_result;
1933 function _actionIsExempted($filter, $method = '')
1935 $method_id = is_string($method) ?
$method : $this->_filterId($method);
1936 $filter_id = $this->_filterId($filter);
1938 if((!empty($this->_includedActions
[$filter_id]) && !in_array($method_id, $this->_includedActions
[$filter_id])) ||
1939 (!empty($this->_excludedActions
[$filter_id]) && in_array($method_id, $this->_excludedActions
[$filter_id]))){
1951 * The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
1952 * to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create action
1953 * that sets <tt>flash['notice] = 'Successfully created'</tt> before redirecting to a display action that can then expose
1954 * the flash to its template. Actually, that exposure is automatically done. Example:
1956 * class WeblogController extends ActionController
1961 * $this->flash['notice] = 'Successfully created post';
1962 * $this->redirectTo(array('action'=>'display','params' => array('id' =>$Post->id)));
1965 * function display()
1967 * // doesn't need to assign the flash notice to the template, that's done automatically
1972 * <?php if($flash['notice']) : ?><div class='notice'><?php echo $flash['notice'] ?></div><?php endif; ?>
1974 * This example just places a string in the flash, but you can put any object in there. And of course, you can put as many
1975 * as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
1979 * Sets a flash that will not be available to the next action, only to the current.
1981 * $this->flash_now['message] = 'Hello current action';
1983 * This method enables you to use the flash as a central messaging system in your app.
1984 * When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
1985 * When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
1986 * vanish when the current action is done.
1988 * Entries set via <tt>flash_now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
1990 var $flash = array();
1991 var $flash_now = array();
1992 var $flash_options = array();
1993 var $_flash_handled = false;
1995 function _handleFlashAttribute()
1997 $this->_flash_handled
= true;
1999 $next_flash = empty($this->flash
) ?
false : $this->flash
;
2000 $this->flash
= array();
2001 if(isset($_SESSION['__flash'])){
2002 $this->flash
= $_SESSION['__flash'];
2004 $_SESSION['__flash'] = $next_flash;
2005 if(!empty($this->flash_now
)){
2006 $this->flash
= array_merge((array)$this->flash
,(array)$this->flash_now
);
2008 $this->_handleFlashOptions();
2011 function _handleFlashOptions()
2013 $next_flash_options = empty($this->flash_options
) ?
false : $this->flash_options
;
2014 $this->flash_options
= array();
2015 if(isset($_SESSION['__flash_options'])){
2016 $this->flash_options
= $_SESSION['__flash_options'];
2018 $_SESSION['__flash_options'] = $next_flash_options;
2019 if(!empty($this->flash_now_options
)){
2020 $this->flash_options
= array_merge((array)$this->flash_options
,(array)$this->flash_now_options
);
2025 function _mergeFlashOnFlashNow()
2027 $this->flash_now
= array_merge($this->flash_now
,$this->flash
);
2034 * === Pagination for Active Record collections
2036 * The Pagination module aids in the process of paging large collections of
2037 * Active Record objects. It offers macro-style automatic fetching of your
2038 * model for multiple views, or explicit fetching for single actions. And if
2039 * the magic isn't flexible enough for your needs, you can create your own
2040 * paginators with a minimal amount of code.
2042 * The Pagination module can handle as much or as little as you wish. In the
2043 * controller, have it automatically query your model for pagination; or,
2044 * if you prefer, create Paginator objects yourself
2046 * Pagination is included automatically for all controllers.
2048 * For help rendering pagination links, see
2049 * Helpers/PaginationHelper.
2051 * ==== Automatic pagination for every action in a controller
2053 * class PersonController extends ApplicationController
2055 * var $model = 'person';
2056 * var $paginate = array('people'=>array('order' => 'last_name, first_name',
2057 * 'per_page' => 20));
2060 * Each action in this controller now has access to a <tt>$this->people</tt>
2061 * instance variable, which is an ordered collection of model objects for the
2062 * current page (at most 20, sorted by last name and first name), and a
2063 * <tt>$this->person_pages</tt> Paginator instance. The current page is determined
2064 * by the <tt>$params['page']</tt> variable.
2066 * ==== Pagination for a single action
2068 * function show_all()
2070 * list($this->person_pages, $this->people) =
2071 * $this->paginate('people', array('order' => 'last_name, first_name'));
2074 * Like the previous example, but explicitly creates <tt>$this->person_pages</tt>
2075 * and <tt>$this->people</tt> for a single action, and uses the default of 10 items
2078 * ==== Custom/"classic" pagination
2082 * $this->person_pages = new AkPaginator(&$this, $Person->count(), 10, $params['page']);
2083 * $this->people = $this->Person->find('all', array(
2084 * 'order'=> 'last_name, first_name',
2085 * 'limit' => $this->person_pages->items_per_page,
2086 * 'offset' => $this->person_pages->getOffset()));
2089 * Explicitly creates the paginator from the previous example and uses
2090 * AkPaginator::toSql to retrieve <tt>$this->people</tt> from the model.
2092 // An array holding options for controllers using macro-style pagination
2093 var $_pagination_options = array(
2094 'class_name' => null,
2095 'singular_name' => null,
2097 'conditions' => null,
2104 'parameter' => 'page'
2107 // The default options for pagination
2108 var $_pagination_default_options = array(
2109 'class_name' => null,
2110 'singular_name' => null,
2112 'conditions' => null,
2119 'parameter' => 'page'
2122 var $_pagination_actions = array();
2124 function _paginationValidateOptions($collection_id, $options = array(), $in_action)
2126 $this->_pagination_options
= array_merge($this->_pagination_default_options
, $this->_pagination_options
);
2127 $valid_options = array_keys($this->_pagination_default_options
);
2129 $valid_options = !in_array($in_action, $valid_options) ?
array_merge($valid_options, $this->_pagination_actions
) : $valid_options;
2131 $unknown_option_keys = array_diff(array_keys($this->_pagination_options
) , $valid_options);
2133 if(!empty($unknown_option_keys)){
2134 trigger_error(Ak
::t('Unknown options for pagination: %unknown_option',array('%unknown_option'=>join(', ',$unknown_option_keys))), E_USER_WARNING
);
2137 $this->_pagination_options
['singular_name'] = !empty($this->_pagination_options
['singular_name']) ?
$this->_pagination_options
['singular_name'] : AkInflector
::singularize($collection_id);
2138 $this->_pagination_options
['class_name'] = !empty($this->_pagination_options
['class_name']) ?
$this->_pagination_options
['class_name'] : AkInflector
::camelize($this->_pagination_options
['singular_name']);
2142 * Returns a paginator and a collection of Active Record model instances
2143 * for the paginator's current page. This is designed to be used in a
2147 * <tt>singular_name</tt>:: the singular name to use, if it can't be inferred by
2148 * singularizing the collection name
2149 * <tt>class_name</tt>:: the class name to use, if it can't be inferred by
2150 * camelizing the singular name
2151 * <tt>per_page</tt>:: the maximum number of items to include in a
2152 * single page. Defaults to 10
2153 * <tt>conditions</tt>:: optional conditions passed to Model::find('all', $this->params); and
2155 * <tt>order</tt>:: optional order parameter passed to Model::find('all', $this->params);
2156 * <tt>order_by</tt>:: (deprecated, used :order) optional order parameter passed to Model::find('all', $this->params)
2157 * <tt>joins</tt>:: optional joins parameter passed to Model::find('all', $this->params)
2158 * and Model::count()
2159 * <tt>join</tt>:: (deprecated, used :joins or :include) optional join parameter passed to Model::find('all', $this->params)
2160 * and Model::count()
2161 * <tt>include</tt>:: optional eager loading parameter passed to Model::find('all', $this->params)
2162 * and Model::count()
2164 * Creates a +before_filter+ which automatically paginates an Active
2165 * Record model for all actions in a controller (or certain actions if
2166 * specified with the <tt>actions</tt> option).
2168 * +options+ are the same as PaginationHelper::paginate, with the addition
2170 * <tt>actions</tt>:: an array of actions for which the pagination is
2171 * active. Defaults to +null+ (i.e., every action)
2173 function paginate($collection_id, $options = array())
2175 $this->_paginationValidateOptions($collection_id, $options, true);
2176 $this->_paginationLoadPaginatorAndCollection($collection_id, $this->_pagination_options
);
2177 $this->beforeFilter('_paginationCreateAndRetrieveCollections');
2183 function _paginationCreateAndRetrieveCollections()
2185 foreach($this->_pagination_options
[$this->class] as $collection_id=>$options){
2186 if(!empty($options['actions']) && in_array($options['actions'], $action_name)){
2190 list($paginator, $collection) = $this->_paginationLoadPaginatorAndCollection($collection_id, $this->_pagination_options
);
2192 $this->{$options['singular_name'].'_pages'} =& $paginator;
2193 $this->$collection_name =& $collection;
2198 * Returns the total number of items in the collection to be paginated for
2199 * the +model+ and given +conditions+. Override this method to implement a
2202 function _paginationCountCollection(&$model, $conditions, $joins)
2204 return $model->count($conditions, $joins);
2208 * Returns a collection of items for the given +$model+ and +$options['conditions']+,
2209 * ordered by +$options['order']+, for the current page in the given +$paginator+.
2210 * Override this method to implement a custom finder.
2212 function _paginationFindCollection(&$model, $options, &$paginator)
2214 return $model->find('all', array(
2215 'conditions' => $this->_pagination_options
['conditions'],
2216 'order' => !empty($options['order_by']) ?
$options['order_by'] : $options['order'],
2217 'joins' => !empty($options['join']) ?
$options['join'] : $options['joins'],
2218 'include' => $this->_pagination_options
['include'],
2219 'select' => $this->_pagination_options
['select'],
2220 'limit' => $this->_pagination_options
['per_page'],
2221 'offset' => $paginator->getOffset()));
2225 * @todo Fix this function
2227 function _paginationLoadPaginatorAndCollection($collection_id, $options)
2229 $page = $this->params
[$options['parameter']];
2230 $count = $this->_paginationCountCollection($klass, $options['conditions'],
2231 empty($options['join']) ?
$options['join'] : $options['joins']);
2233 require_once(AK_LIB_DIR
.DS
.'AkActionController'.DS
.'AkPaginator.php');
2234 $paginator =& new AkPaginator($this, $count, $options['per_page'], $page);
2235 $collection =& $this->_paginationFindCollection($options['class_name'], $options, $paginator);
2237 return array(&$paginator, &$collection);
2242 * Specifies that the named actions requires an SSL connection to be performed (which is enforced by ensure_proper_protocol).
2244 function setSslRequiredActions($actions)
2246 $this->_ssl_required_actions
= empty($this->_ssl_required_actions
) ?
2247 (is_string($actions) ? Ak
::stringToArray($actions) : $actions) :
2248 array_merge($this->_ssl_required_actions
, (is_string($actions) ? Ak
::stringToArray($actions) : $actions));
2251 function setSslAllowedActions($actions)
2253 $this->_ssl_allowed_actions
= empty($this->_ssl_allowed_actions
) ?
2254 (is_string($actions) ? Ak
::stringToArray($actions) : $actions) :
2255 array_merge($this->_ssl_allowed_actions
, (is_string($actions) ? Ak
::stringToArray($actions) : $actions));
2259 * Returns true if the current action is supposed to run as SSL
2261 function _isSslRequired()
2263 return !empty($this->_ssl_required_actions
) && is_array($this->_ssl_required_actions
) && isset($this->_action_name
) ?
2264 in_array($this->_action_name
, $this->_ssl_required_actions
) : false;
2267 function _isSslAllowed()
2269 return !empty($this->_ssl_allowed_actions
) && is_array($this->_ssl_allowed_actions
) && isset($this->_action_name
) ?
2270 in_array($this->_action_name
, $this->_ssl_allowed_actions
) : false;
2273 function _ensureProperProtocol()
2275 if($this->_isSslAllowed()){
2278 if ($this->_isSslRequired() && !$this->Request
->isSsl()){
2279 $this->redirectTo(substr_replace(AK_CURRENT_URL
,'s:',4,1));
2281 }elseif($this->Request
->isSsl() && !$this->_isSslRequired()){
2282 $this->redirectTo(substr_replace(AK_CURRENT_URL
,'',4,1));
2292 * Account location is a set of methods that supports the account-key-as-subdomain
2293 * way of identifying the current scope. These methods allow you to easily produce URLs that
2294 * match this style and to get the current account key from the subdomain.
2296 * The methods are: getAccountUrl, getAccountHost, and getAccountDomain.
2300 * include_once('AkAccountLocation.php');
2302 * class ApplicationController extends AkActionController
2304 * var $before_filter = '_findAccount';
2306 * function _findAccount()
2308 * $this->account = Account::find(array('conditions'=>array('username = ?', $this->account_domain)));
2311 * class AccountController extends ApplicationController
2313 * function new_account()
2315 * $this->new_account = Account::create($this->params['new_account']);
2316 * $this->redirectTo(array('host' => $this->accountHost($this->new_account->username), 'controller' => 'weblog'));
2319 * function _authenticate()
2321 * $this->session[$this->account_domain] = 'authenticated';
2322 * $this->redirectTo(array('controller => 'weblog'));
2325 * function _isAuthenticated()
2327 * return !empty($this->session['account_domain']) ? $this->session['account_domain'] == 'authenticated' : false;
2332 * Your domain: {account_url?}
2334 * By default, all the methods will query for $this->account->username as the account key, but you can
2335 * specialize that by overwriting defaultAccountSubdomain. You can of course also pass it in
2336 * as the first argument to all the methods.
2338 function defaultAccountSubdomain()
2340 if(!empty($this->account
)){
2341 return $this->account
->respondsTo('username');
2345 function accountUrl($account_subdomain = null, $use_ssl = null)
2347 $account_subdomain = empty($account_subdomain) ?
'default_account_subdomain' : $account_subdomain;
2348 $use_ssl = empty($use_ssl) ?
$use_ssl : $this->Request
->isSsl();
2349 return ($use_ssl ?
'https://' : 'http://') . $this->accountHost($account_subdomain);
2352 function accountHost($account_subdomain = null)
2354 $account_subdomain = empty($account_subdomain) ?
'default_account_subdomain' : $account_subdomain;
2356 $account_host .= $account_subdomain . '.';
2357 $account_host .= $this->accountDomain();
2358 return $account_host;
2361 function accountDomain()
2363 $account_domain = '';
2364 if(count($this->Request
->getSubdomains()) > 1){
2365 $account_domain .= join('.',$this->Request
->getSubdomains()) . '.';
2367 $account_domain .= $this->Request
->getDomain() . $this->Request
->getPortString();
2368 return $account_domain;
2371 function getAccountSubdomain()
2373 return array_shift($this->Request
->getSubdomains());
2378 * Methods for sending files and streams to the browser instead of rendering.
2380 var $default_send_file_options = array(
2381 'type' => 'application/octet-stream',
2382 'disposition' => 'attachment',
2384 'buffer_size' => 4096
2388 * Sends the file by streaming it 4096 bytes at a time. This way the
2389 * whole file doesn't need to be read into memory at once. This makes
2390 * it feasible to send even large files.
2392 * Be careful to sanitize the path parameter if it coming from a web
2393 * page. sendFile($params['path']) allows a malicious user to
2394 * download any file on your server.
2397 * * <tt>filename</tt> - suggests a filename for the browser to use.
2398 * Defaults to realpath($path).
2399 * * <tt>type</tt> - specifies an HTTP content type.
2400 * Defaults to 'application/octet-stream'.
2401 * * <tt>disposition</tt> - specifies whether the file will be shown inline or downloaded.
2402 * Valid values are 'inline' and 'attachment' (default).
2403 * * <tt>stream</tt> - whether to send the file to the user agent as it is read (true)
2404 * or to read the entire file before sending (false). Defaults to true.
2405 * * <tt>buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
2408 * The default Content-Type and Content-Disposition headers are
2409 * set to download arbitrary binary files in as many browsers as
2410 * possible. IE versions 4, 5, 5.5, and 6 are all known to have
2411 * a variety of quirks (especially when downloading over SSL).
2414 * sendFile('/path/to.zip');
2416 * Show a JPEG in browser:
2417 * sendFile('/path/to.jpeg', array('type' => 'image/jpeg', 'disposition' => 'inline'));
2419 * Read about the other Content-* HTTP headers if you'd like to
2420 * provide the user with more information (such as Content-Description).
2421 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
2423 * Also be aware that the document may be cached by proxies and browsers.
2424 * The Pragma and Cache-Control headers declare how the file may be cached
2425 * by intermediaries. They default to require clients to validate with
2426 * the server before releasing cached responses. See
2427 * http://www.mnot.net/cache_docs/ for an overview of web caching and
2428 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
2429 * for the Cache-Control header spec.
2431 function sendFile($path, $options = array())
2433 $path = realpath($path);
2434 if(!file_exists($path)){
2435 trigger_error(Ak
::t('Cannot read file %path',array('%path'=>$path)), E_USER_NOTICE
);
2438 $options['length'] = empty($options['length']) ?
filesize($path) : $options['length'];
2439 $options['filename'] = empty($options['filename']) ?
basename($path) : $options['filename'];
2440 $options['type'] = empty($options['type']) ? Ak
::mime_content_type($path) : $options['type'];
2442 $this->performed_render
= false;
2443 $this->_sendFileHeaders($options);
2445 if(!empty($options['stream'])){
2446 require_once(AK_LIB_DIR
.DS
.'AkStream.php');
2447 $this->render(array('text'=> new AkStream($path,$options['buffer_size'])));
2449 $this->render(array('text'=> Ak
::file_get_contents($path)));
2454 * Send binary data to the user as a file download. May set content type, apparent file name,
2455 * and specify whether to show data inline or download as an attachment.
2458 * * <tt>filename</tt> - Suggests a filename for the browser to use.
2459 * * <tt>type</tt> - specifies an HTTP content type.
2460 * Defaults to 'application/octet-stream'.
2461 * * <tt>disposition</tt> - specifies whether the file will be shown inline or downloaded.
2462 * Valid values are 'inline' and 'attachment' (default).
2464 * Generic data download:
2467 * Download a dynamically-generated tarball:
2468 * sendData(Ak::compress('dir','tgz'), array('filename' => 'dir.tgz'));
2470 * Display an image Active Record in the browser:
2471 * sendData($image_data, array('type' =>Ak::mime_content_type('image_name.png'), 'disposition' => 'inline'));
2473 * See +sendFile+ for more information on HTTP Content-* headers and caching.
2475 function sendData($data, $options = array())
2477 $options['length'] = empty($options['length']) ? Ak
::size($data) : $options['length'];
2478 $this->_sendFileHeaders($options);
2479 $this->performed_render
= false;
2480 $this->renderText($data);
2484 * Creates a file for streaming from a file.
2485 * This way you might free memory usage is file is too large
2487 function sendDataAsStream($data, $options)
2489 $temp_file_name = tempnam(AK_TMP_DIR
, Ak
::randomString());
2490 $fp = fopen($temp_file_name, 'w');
2493 $this->sendFile($temp_file_name, $options);
2497 function _sendFileHeaders(&$options)
2499 $options = array_merge($this->default_send_file_options
,$options);
2500 foreach (array('length', 'type', 'disposition') as $arg){
2501 empty($options[$arg]) ?
trigger_error(Ak
::t('%arg option required', array('%arg'=>$arg)), E_USER_ERROR
) : null;
2503 $disposition = empty($options['disposition']) ?
'attachment' : $options['disposition'];
2504 $disposition .= !empty($options['filename']) ?
'; filename="'.$options['filename'].'"' : '';
2505 $this->Response
->addHeader(array(
2506 'Content-Length' => $options['length'],
2507 'Content-Type' => trim($options['type']), // fixes a problem with extra '\r' with some browsers
2508 'Content-Disposition' => $disposition,
2509 'Content-Transfer-Encoding' => 'binary'
2515 function redirectToLocale($locale)
2517 if($this->Request
->__internationalization_support_enabled
){
2518 $lang = isset($this->params
['lang']) ?
$this->params
['lang'] : $locale;
2520 if($locale != $lang){
2521 $this->redirectTo(array_merge($this->Request
->getParams(),array('lang'=>$locale)));
2529 function api($protocol = 'xml_rpc')
2531 $web_services = array_merge(Ak
::toArray($this->web_services
), Ak
::toArray($this->web_service
));
2532 if(!empty($web_services)){
2533 $web_services = array_unique($web_services);
2534 require_once(AK_LIB_DIR
.DS
.'AkActionWebService.php');
2535 require_once(AK_LIB_DIR
.DS
.'AkActionWebService'.DS
.'AkActionWebServiceServer.php');
2536 $Server =& new AkActionWebServiceServer($protocol);
2537 foreach ($web_services as $web_service){
2538 $Server->addService($web_service);
2544 die(Ak
::t('There is not any webservice configured at this endpoint'));
2551 * Function for getting the singleton controller;
2555 function &AkActionController()
2557 $params = func_num_args() == 0 ?
null : func_get_args();
2558 $AkActionController =& Ak
::singleton('AkActionController', $params);
2559 return $AkActionController;