| [ Index ] |
PHP Cross Reference of Akelos Framework |
[Summary view] [Print] [Text view]
1 <?php 2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 3 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 // +----------------------------------------------------------------------+ 10 11 require_once (AK_LIB_DIR.DS.'AkObject.php'); 12 13 defined('AK_HIGH_LOAD_MODE') ? null : define('AK_HIGH_LOAD_MODE', false); 14 defined('AK_APP_NAME') ? null : define('AK_APP_NAME', 'Application'); 15 16 /** 17 * @package ActionController 18 * @subpackage Base 19 * @author Bermi Ferrer <bermi a.t akelos c.om> 20 * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org 21 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html> 22 */ 23 24 class AkActionController extends AkObject 25 { 26 var $_high_load_mode = AK_HIGH_LOAD_MODE; 27 var $_enable_plugins = true; 28 var $_auto_instantiate_models = true; 29 var $validate_output = false; 30 31 var $_ssl_requirement = false; 32 var $_ssl_allowed_actions = array(); 33 var $ssl_for_all_actions = true; 34 35 /** 36 * Determines whether the view has access to controller internals $this->Request, $this->Response, $this->session, and $this->Template. 37 * By default, it does. 38 */ 39 var $_view_controller_internals = true; 40 41 /** 42 * Protected instance variable cache 43 */ 44 var $_protected_variables_cache = array(); 45 46 /** 47 * Prepends all the URL-generating helpers from AssetHelper. 48 * This makes it possible to easily move javascripts, stylesheets, 49 * and images to a dedicated asset server away from the main web server. 50 * Example: 51 * $this->_asset_host = 'http://assets.example.com'; 52 */ 53 var $asset_host = AK_ASSET_HOST; 54 55 56 var $_Logger; 57 58 /** 59 * Determines which template class should be used by AkActionController. 60 */ 61 var $TemplateClass; 62 63 /** 64 * Turn on +_ignore_missing_templates+ if you want to unit test actions without 65 * making the associated templates. 66 */ 67 var $_ignore_missing_templates; 68 69 /** 70 * Holds the Request object that's primarily used to get environment variables 71 */ 72 var $Request; 73 74 /** 75 * Holds an array of all the GET, POST, and Url parameters passed to the action. 76 * Accessed like <tt>$this->params['post_id'];</tt> 77 * to get the post_id. 78 */ 79 var $params = array(); 80 81 /** 82 * Holds the Response object that's primarily used to set additional HTTP _headers 83 * through access like <tt>$this->Response->_headers['Cache-Control'] = 'no-cache';</tt>. 84 * Can also be used to access the final body HTML after a template 85 * has been rendered through $this->Response->body -- useful for <tt>after_filter</tt>s 86 * that wants to manipulate the output, such as a OutputCompressionFilter. 87 */ 88 var $Response; 89 90 /** 91 * Holds an array of objects in the session. Accessed like <tt>$this->session['person']</tt> 92 * to get the object tied to the 'person' key. The session will hold any type of object 93 * as values, but the key should be a string. 94 */ 95 var $session; 96 97 /** 98 * Holds an array of header names and values. Accessed like <tt>$this->_headers['Cache-Control']</tt> 99 * to get the value of the Cache-Control directive. Values should always be specified as strings. 100 */ 101 var $_headers = array(); 102 103 /** 104 * Holds the array of variables that are passed on to the template class to be 105 * made available to the view. This array is generated by taking a snapshot of 106 * all the instance variables in the current scope just before a template is rendered. 107 */ 108 var $_assigns = array(); 109 110 /** 111 * Holds the name of the action this controller is processing. 112 */ 113 var $_action_name; 114 115 var $cookies; 116 117 var $helpers = 'default'; 118 119 var $app_helpers; 120 var $plugin_helpers = 'all'; 121 122 var $web_service; 123 var $web_services = array(); 124 125 var $web_service_api; 126 var $web_service_apis = array(); 127 128 var $module_name; 129 var $_module_path; 130 131 var $_request_id = -1; 132 133 /** 134 * Old fashioned way of dispatching requests. Please use AkDispatcher or roll your own. 135 * 136 * @deprecated 137 */ 138 function handleRequest() 139 { 140 141 AK_LOG_EVENTS && empty($this->_Logger) ? ($this->_Logger =& Ak::getLogger()) : null; 142 AK_LOG_EVENTS && !empty($this->_Logger) ? $this->_Logger->warning('Using deprecated request dispatcher AkActionController::handleRequest. Use to AkDispatcher + AkDispatcher::dispatch instead.') : null; 143 require_once (AK_LIB_DIR.DS.'AkDispatcher.php'); 144 $Dispatcher =& new AkDispatcher(); 145 $Dispatcher->dispatch(); 146 } 147 148 149 150 function process(&$Request, &$Response) 151 { 152 AK_LOG_EVENTS && empty($this->_Logger) ? ($this->_Logger =& Ak::getLogger()) : null; 153 154 $this->Request =& $Request; 155 $this->Response =& $Response; 156 $this->params = $this->Request->getParams(); 157 $this->_action_name = $this->Request->getAction(); 158 159 $actionExists = $this->_ensureActionExists(); 160 161 if (!$actionExists) { 162 $this->handleResponse(); 163 return false; 164 } 165 166 Ak::t('Akelos'); // We need to get locales ready 167 168 if($this->_high_load_mode !== true){ 169 if(!empty($this->_auto_instantiate_models)){ 170 $this->instantiateIncludedModelClasses(); 171 } 172 if(!empty($this->_enable_plugins)){ 173 $this->loadPlugins(); 174 } 175 if(!empty($this->helpers)){ 176 $this->instantiateHelpers(); 177 } 178 }else{ 179 $this->_enableLayoutOnRender = false; 180 } 181 182 $this->_ensureProperProtocol(); 183 184 // After filters 185 $this->afterFilter('_handleFlashAttribute'); 186 187 $this->_initExtensions(); 188 189 $this->_loadActionView(); 190 191 if(isset($this->api)){ 192 require_once (AK_LIB_DIR.DS.'AkActionWebService.php'); 193 $this->aroundFilter(new AkActionWebService($this)); 194 } 195 196 $this->_identifyRequest(); 197 198 199 $this->performActionWithFilters($this->_action_name); 200 201 202 $this->handleResponse(); 203 } 204 205 function _sendMimeContentType() 206 { 207 $this->Response->setContentTypeForFormat($this->Request->getFormat()); 208 } 209 210 /** 211 * Used to respond to multiple formats on the same action. 212 * The format gets detected by the requested file extension or the 213 * accept headers. 214 * 215 * Example 1: 216 * If you need to perform some calculations inste 217 * ---- 218 * 219 * function listing() 220 * { 221 * $this->listings = $this->listing->find(..); 222 * if (!$this->respondToFormat()) { 223 * // default html response here 224 * } 225 * } 226 * // handles action listing in format xml 227 * function _handleListingAsXml() 228 * { 229 * $this->renderText($this->listing->toXml($this->listings)); 230 * } 231 * 232 * 233 * Example 2: 234 * If you just render a standard template by default 235 * 236 * function listing() 237 * { 238 * // if its the standard format it will render the template post.tpl 239 * $this->respondToFormat(); 240 * } 241 * // handles action listing in format xml 242 * function _handleListingAsXml() 243 * { 244 * $this->renderText($this->listing->toXml($this->listings)); 245 * } 246 * 247 * @param array $options 248 * @return boolean true if there is a format action, false if not 249 */ 250 function respondToFormat($options = array()) 251 { 252 $default_options = array('default'=>'html'); 253 254 $options = array_merge($default_options,$options); 255 256 $format = $this->Request->getFormat(); 257 $action = $this->getActionName(); 258 $formatAction = '_handle'.$action.'As'.ucfirst($format); 259 $isDefaultAction = $format == null || $format == $options['default']; 260 $formatActionExists = method_exists($this,$formatAction); 261 if (!$formatActionExists && !$isDefaultAction) { 262 $this->renderText('404 Not found',404); 263 264 } 265 if (!$isDefaultAction && $formatActionExists) { 266 $this->performActionWithoutFilters($formatAction); 267 return true; 268 } 269 return false; 270 } 271 272 function _identifyRequest() 273 { 274 if (AK_ENVIRONMENT != 'testing') { 275 /** 276 * for AkTestApplication we need to identify if handleResponse rendered 277 * the output already. 278 * Since AkTestApplication performs multiple requests 279 * on one instance of AkActionController, each Request needs 280 * to be identified separately 281 */ 282 $this->_request_id++; 283 } else { 284 $this->_request_id = md5(time().microtime(true).rand(0,10000)); 285 } 286 } 287 function handleResponse() 288 { 289 static $handled; 290 if (empty($handled)) { 291 $handled = array(); 292 } 293 if (!isset($handled[$this->_request_id])) { 294 if (!$this->_hasPerformed()){ 295 $this->_enableLayoutOnRender ? $this->renderWithLayout() : $this->renderWithoutLayout(); 296 } 297 $this->_sendMimeContentType(); 298 if(!empty($this->validate_output)){ 299 $this->_validateGeneratedXhtml(); 300 } 301 if (!isset($this->Response->_headers['Status']) && !empty($this->_default_render_status_code)) { 302 $this->Response->_headers['Status'] = $this->_default_render_status_code; 303 } 304 $this->Response->outputResults(); 305 $handled[$this->_request_id]=true; 306 } 307 } 308 309 function _loadActionView() 310 { 311 empty($this->_assigns) ? ($this->_assigns = array()) : null; 312 $this->_enableLayoutOnRender = !isset($this->_enableLayoutOnRender) ? true : $this->_enableLayoutOnRender; 313 $this->passed_args = !isset($this->Request->pass)? array() : $this->Request->pass; 314 empty($this->cookies) && isset($_COOKIE) ? ($this->cookies =& $_COOKIE) : null; 315 316 if(empty($this->Template)){ 317 require_once (AK_LIB_DIR.DS.'AkActionView.php'); 318 require_once (AK_LIB_DIR.DS.'AkActionView'.DS.'AkPhpTemplateHandler.php'); 319 $this->Template =& new AkActionView($this->_getTemplateBasePath(), 320 $this->Request->getParameters(),$this->Request->getController()); 321 322 $this->Template->_controllerInstance =& $this; 323 $this->Template->_registerTemplateHandler('tpl','AkPhpTemplateHandler'); 324 } 325 } 326 327 function loadPlugins() 328 { 329 Ak::loadPlugins(); 330 } 331 332 /** 333 * Creates an instance of each available helper and links it into into current controller. 334 * 335 * Per example, if a helper TextHelper is located into the file text_helper.php. 336 * An instance is created on current controller 337 * at $this->text_helper. This instance is also available on the view by calling $text_helper. 338 * 339 * Helpers can be found at lib/AkActionView/helpers (this might change in a future) 340 */ 341 function instantiateHelpers() 342 { 343 require_once (AK_LIB_DIR.DS.'AkActionView'.DS.'AkHelperLoader.php'); 344 $HelperLoader = new AkHelperLoader(); 345 $HelperLoader->setController(&$this); 346 $HelperLoader->instantiateHelpers(); 347 } 348 349 function getCurrentControllerHelper() 350 { 351 $helper = $this->getControllerName(); 352 $helper = AkInflector::is_plural($helper)?AkInflector::singularize($helper):$helper; 353 $helper_file_name = AK_HELPERS_DIR.DS.$this->_module_path.AkInflector::underscore($helper).'_helper.php'; 354 355 if(file_exists($helper_file_name)){ 356 return array($helper_file_name => $helper); 357 } 358 return array(); 359 } 360 361 function getModuleHelper() 362 { 363 $this->getControllerName(); // module name is set when we first retrieve the controller name 364 if(!empty($this->module_name)){ 365 $helper_file_name = AK_HELPERS_DIR.DS.AkInflector::underscore($this->module_name).'_helper.php'; 366 if(file_exists($helper_file_name)){ 367 return array($helper_file_name => $this->module_name); 368 } 369 } 370 return array(); 371 } 372 373 374 function _validateGeneratedXhtml() 375 { 376 require_once (AK_LIB_DIR.DS.'AkXhtmlValidator.php'); 377 $XhtmlValidator = new AkXhtmlValidator(); 378 if($XhtmlValidator->validate($this->Response->body) === false){ 379 $this->Response->sendHeaders(); 380 echo '<h1>'.Ak::t('Ooops! There are some errors on current XHTML page').'</h1>'; 381 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"; 382 $XhtmlValidator->showErrors(); 383 echo "<hr /><h2>".Ak::t('Showing XHTML code')."</h2><hr /><div style='border:5px solid red;margin:5px;padding:15px;'>".$this->Response->body."</pre>"; 384 die(); 385 } 386 } 387 388 389 /** 390 * Methods for loading desired models into this controller 391 */ 392 function setModel($model) 393 { 394 $this->instantiateIncludedModelClasses(array($model)); 395 } 396 397 function setModels($models) 398 { 399 $this->instantiateIncludedModelClasses($models); 400 } 401 402 function instantiateIncludedModelClasses($models = array()) 403 { 404 require_once (AK_LIB_DIR.DS.'AkActiveRecord.php'); 405 require_once(AK_APP_DIR.DS.'shared_model.php'); 406 407 empty($this->model) ? ($this->model = $this->params['controller']) : null; 408 empty($this->models) ? ($this->models = array()) : null; 409 410 $models = array_unique(array_merge(Ak::import($this->model), Ak::import($this->models), Ak::import($models), (empty($this->app_models)?array(): Ak::import($this->app_models)))); 411 412 unset($this->model, $this->models); 413 414 foreach ($models as $model){ 415 $this->instantiateModelClass($model, (empty($this->finder_options[$model])?array():$this->finder_options[$model])); 416 } 417 } 418 419 function instantiateModelClass($model_class_name, $finder_options = array()) 420 { 421 $underscored_model_class_name = AkInflector::underscore($model_class_name); 422 $controller_name = isset($this->controller_name)?$this->controller_name:$this->getControllerName(); 423 $id = empty($this->params[$underscored_model_class_name]['id']) ? 424 (empty($this->params['id']) ? false : 425 (($model_class_name == $controller_name || $model_class_name == $this->singularized_controller_name) ? $this->params['id'] : false)) : 426 $this->params[$underscored_model_class_name]['id']; 427 428 if(class_exists($model_class_name)){ 429 $underscored_model_class_name = AkInflector::underscore($model_class_name); 430 431 if(!isset($this->$model_class_name) || !isset($this->$underscored_model_class_name)){ 432 if($finder_options !== false && is_numeric($id)){ 433 $model =& new $model_class_name(); 434 if(empty($finder_options)){ 435 $model =& $model->find($id); 436 }else{ 437 $model =& $model->find($id, $finder_options); 438 } 439 }else{ 440 $model =& new $model_class_name(); 441 } 442 if(!isset($this->$model_class_name)){ 443 $this->$model_class_name =& $model; 444 } 445 if(!isset($this->$underscored_model_class_name)){ 446 $this->$underscored_model_class_name =& $model; 447 } 448 } 449 } 450 } 451 452 453 454 /** 455 Rendering content 456 ==================================================================== 457 */ 458 459 /** 460 * Renders the content that will be returned to the browser as the Response body. 461 * 462 * === Rendering an action 463 * 464 * Action rendering is the most common form and the type used automatically by 465 * Action Controller when nothing else is specified. By default, actions are 466 * rendered within the current layout (if one exists). 467 * 468 * * Renders the template for the action "goal" within the current controller 469 * 470 * $this->render(array('action'=>'goal')); 471 * 472 * * Renders the template for the action "short_goal" within the current controller, 473 * but without the current active layout 474 * 475 * $this->render(array('action'=>'short_goal','layout'=>false)); 476 * 477 * * Renders the template for the action "long_goal" within the current controller, 478 * but with a custom layout 479 * 480 * $this->render(array('action'=>'long_goal','layout'=>'spectacular')); 481 * 482 * === Rendering partials 483 * 484 * Partial rendering is most commonly used together with Ajax calls that only update 485 * one or a few elements on a page without reloading. Rendering of partials from 486 * the controller makes it possible to use the same partial template in 487 * both the full-page rendering (by calling it from within the template) and when 488 * sub-page updates happen (from the controller action responding to Ajax calls). 489 * By default, the current layout is not used. 490 * 491 * * Renders the partial located at app/views/controller/_win.tpl 492 * 493 * $this->render(array('partial'=>'win')); 494 * 495 * * Renders the partial with a status code of 500 (internal error) 496 * 497 * $this->render(array('partial'=>'broken','status'=>500)); 498 * 499 * * Renders the same partial but also makes a local variable available to it 500 * 501 * $this->render(array('partial' => 'win', 'locals' => array('name'=>'david'))); 502 * 503 * * Renders a collection of the same partial by making each element of $wins available through 504 * the local variable "win" as it builds the complete Response 505 * 506 * $this->render(array('partial'=>'win','collection'=>$wins)); 507 * 508 * * Renders the same collection of partials, but also renders the win_divider partial in between 509 * each win partial. 510 * 511 * $this->render(array('partial'=>'win','collection'=>$wins,'spacer_template'=>'win_divider')); 512 * 513 * === Rendering a template 514 * 515 * Template rendering works just like action rendering except that it takes a 516 * path relative to the template root. 517 * The current layout is automatically applied. 518 * 519 * * Renders the template located in app/views/weblog/show.tpl 520 * $this->render(array('template'=>'weblog/show')); 521 * 522 * === Rendering a file 523 * 524 * File rendering works just like action rendering except that it takes a 525 * filesystem path. By default, the path is assumed to be absolute, and the 526 * current layout is not applied. 527 * 528 * * Renders the template located at the absolute filesystem path 529 * $this->render(array('file'=>'/path/to/some/template.tpl')); 530 * $this->render(array('file'=>'c:/path/to/some/template.tpl')); 531 * 532 * * Renders a template within the current layout, and with a 404 status code 533 * $this->render(array('file' => '/path/to/some/template.tpl', 'layout' => true, 'status' => 404)); 534 * $this->render(array('file' => 'c:/path/to/some/template.tpl', 'layout' => true, 'status' => 404)); 535 * 536 * * Renders a template relative to the template root and chooses the proper file extension 537 * $this->render(array('file' => 'some/template', 'use_full_path' => true)); 538 * 539 * 540 * === Rendering text 541 * 542 * Rendering of text is usually used for tests or for rendering prepared content, 543 * such as a cache. By default, text 544 * rendering is not done within the active layout. 545 * 546 * * Renders the clear text "hello world" with status code 200 547 * $this->render(array('text' => 'hello world!')); 548 * 549 * * Renders the clear text "Explosion!" with status code 500 550 * $this->render(array('text' => "Explosion!", 'status' => 500 )); 551 * 552 * * Renders the clear text "Hi there!" within the current active layout (if one exists) 553 * $this->render(array('text' => "Explosion!", 'layout' => true)); 554 * 555 * * Renders the clear text "Hi there!" within the layout 556 * * placed in "app/views/layouts/special.tpl" 557 * $this->render(array('text' => "Explosion!", 'layout => "special")); 558 * 559 * 560 * === Rendering an inline template 561 * 562 * Rendering of an inline template works as a cross between text and action 563 * rendering where the source for the template 564 * is supplied inline, like text, but its evaled by PHP, like action. By default, 565 * PHP is used for rendering and the current layout is not used. 566 * 567 * * Renders "hello, hello, hello, again" 568 * $this->render(array('inline' => "<?php echo str_repeat('hello, ', 3).'again'?>" )); 569 * 570 * * Renders "hello david" 571 * $this->render(array('inline' => "<?php echo 'hello ' . $name ?>", 'locals' => array('name' => 'david'))); 572 * 573 * 574 * === Rendering nothing 575 * 576 * Rendering nothing is often convenient in combination with Ajax calls that 577 * perform their effect client-side or 578 * when you just want to communicate a status code. Due to a bug in Safari, nothing 579 * actually means a single space. 580 * 581 * * Renders an empty Response with status code 200 582 * $this->render(array('nothing' => true)); 583 * 584 * * Renders an empty Response with status code 401 (access denied) 585 * $this->render(array('nothing' => true, 'status' => 401)); 586 */ 587 function render($options = null, $status = 200) 588 { 589 if(empty($options['partial']) && $this->_hasPerformed()){ 590 $this->_doubleRenderError(Ak::t("Can only render or redirect once per action")); 591 return false; 592 } 593 /** 594 * need to check this with the caching!!! 595 */ 596 $this->_flash_handled ? null : $this->_handleFlashAttribute(); 597 598 if(!is_array($options)){ 599 return $this->renderFile(empty($options) ? $this->getDefaultTemplateName() : $options, $status, true); 600 } 601 602 if(!empty($options['text'])){ 603 return $this->renderText($options['text'], @$options['status']); 604 }else{ 605 606 if(!empty($options['file'])){ 607 return $this->renderFile($options['file'], @$options['status'], @$options['use_full_path'], @(array)$options['locals']); 608 }elseif(!empty($options['template'])){ 609 return $this->renderFile($options['template'], @$options['status'], true); 610 }elseif(!empty($options['inline'])){ 611 return $this->renderTemplate($options['inline'], @$options['status'], @$options['type'], @(array)$options['locals']); 612 }elseif(!empty($options['action'])){ 613 return $this->renderAction($options['action'], @$options['status'], @$options['layout']); 614 }elseif(!empty($options['partial'])){ 615 if($options['partial'] === true){ 616 $options['partial'] = !empty($options['template']) ? $options['template'] : $this->getDefaultTemplateName(); 617 } 618 if(!empty($options['collection'])){ 619 return $this->renderPartialCollection($options['partial'], $options['collection'], @$options['spacer_template'], @$options['locals'], @$options['status']); 620 }else{ 621 return $this->renderPartial($options['partial'], @$options['object'], @$options['locals'], @$options['status']); 622 } 623 }elseif(!empty($options['nothing'])){ 624 // Safari doesn't pass the _headers of the return if the Response is zero length 625 return $this->renderText(' ', @$options['status']); 626 }else{ 627 return $this->renderFile($this->getDefaultTemplateName(), @$options['status'], true); 628 } 629 return true; 630 } 631 } 632 633 /** 634 * Renders according to the same rules as <tt>render</tt>, but returns the result in a string instead 635 * of sending it as the Response body to the browser. 636 */ 637 function renderToString($options = null) 638 { 639 $result = $this->render($options); 640 $this->eraseRenderResults(); 641 $this->variables_added = null; 642 $this->Template->_assigns_added = null; 643 return $result; 644 } 645 646 function renderAction($_action_name, $status = null, $with_layout = true) 647 { 648 $template = $this->getDefaultTemplateName($_action_name); 649 if(!empty($with_layout) && !$this->_isTemplateExemptFromLayout($template)){ 650 return $this->renderWithLayout($template, $status, $with_layout); 651 }else{ 652 return $this->renderWithoutLayout($template, $status); 653 } 654 } 655 656 function renderFile($template_path, $status = null, $use_full_path = false, $locals = array()) 657 { 658 $this->_addVariablesToAssigns(); 659 $locals = array_merge($locals,$this->_assigns); 660 661 if($use_full_path){ 662 $this->_assertExistanceOfTemplateFile($template_path); 663 } 664 665 AK_LOG_EVENTS && !empty($this->_Logger) ? $this->_Logger->message("Rendering $this->full_template_path" . (!empty($status) ? " ($status)":'')) : null; 666 return $this->renderText($this->Template->renderFile($template_path, $use_full_path, $locals), $status); 667 } 668 669 function renderTemplate($template, $status = null, $type = 'tpl', $local_assigns = array()) 670 { 671 $this->_addVariablesToAssigns(); 672 $local_assigns = array_merge($local_assigns,$this->_assigns); 673 return $this->renderText($this->Template->renderTemplate($type, $template, null, $local_assigns), $status); 674 } 675 676 function renderText($text = null, $status = null) 677 { 678 $this->performed_render = true; 679 if($status != null) { 680 $this->Response->_headers['Status'] = $status; 681 } 682 $this->Response->body = $text; 683 return $text; 684 } 685 686 function renderNothing($status = null) 687 { 688 return $this->renderText(' ', $status); 689 } 690 691 function renderPartial($partial_path = null, $object = null, $local_assigns = null, $status = null) 692 { 693 $partial_path = empty($partial_path) ? $this->getDefaultTemplateName() : $partial_path; 694 $this->variables_added = false; 695 $this->performed_render = false; 696 $this->_addVariablesToAssigns(); 697 $this->Template->controller =& $this; 698 $this->$partial_path = $this->renderText($this->Template->renderPartial($partial_path, $object, array_merge($this->_assigns, (array)$local_assigns)), $status); 699 return $this->$partial_path; 700 } 701 702 function renderPartialCollection($partial_name, $collection, $partial_spacer_template = null, $local_assigns = null, $status = null) 703 { 704 $this->_addVariablesToAssigns(); 705 $collection_name = AkInflector::pluralize($partial_name).'_collection'; 706 $result = $this->Template->renderPartialCollection($partial_name, $collection, $partial_spacer_template, $local_assigns); 707 if(empty($this->$collection_name)){ 708 $this->$collection_name = $result; 709 } 710 $this->variables_added = false; 711 $this->performed_render = false; 712 713 return $result; 714 } 715 716 function renderWithLayout($template_name = null, $status = null, $layout = null) 717 { 718 $template_name = empty($template_name) ? $this->getDefaultTemplateName() : $template_name; 719 return $this->renderWithALayout($template_name, $status, $layout); 720 } 721 722 function renderWithoutLayout($template_name = null, $status = null) 723 { 724 $template_name = empty($template_name) ? $this->getDefaultTemplateName() : $template_name; 725 return $this->render($template_name, $status); 726 } 727 728 /** 729 * Clears the rendered results, allowing for another render to be performed. 730 */ 731 function eraseRenderResults() 732 { 733 $this->Response->body = ''; 734 $this->performed_render = false; 735 $this->variables_added = false; 736 } 737 738 function _addVariablesToAssigns() 739 { 740 if(empty($this->variables_added)){ 741 $this->_addInstanceVariablesToAssigns(); 742 $this->variables_added = true; 743 } 744 } 745 746 function _addInstanceVariablesToAssigns() 747 { 748 $this->_protected_variables_cache = array_merge($this->_protected_variables_cache, $this->_getProtectedInstanceVariables()); 749 750 foreach (array_diff(array_keys(get_object_vars($this)), $this->_protected_variables_cache) as $attribute){ 751 if($attribute[0] != '_'){ 752 $this->_assigns[$attribute] =& $this->$attribute; 753 } 754 } 755 } 756 757 function _getProtectedInstanceVariables() 758 { 759 return !empty($this->_view_controller_internals) ? 760 array('_assigns', 'performed_redirect', 'performed_render','db') : 761 array('_assigns', 'performed_redirect', 'performed_render', 'session', 'cookies', 762 'Template','db','helpers','models','layout','Response','Request', 763 'params','passed_args'); 764 } 765 766 767 /** 768 * Use this to translate strings in the scope of your controller 769 * 770 * @see Ak::t 771 */ 772 function t($string, $array = null) 773 { 774 return Ak::t($string, $array, AkInflector::underscore($this->getControllerName())); 775 } 776 777 778 779 /** 780 Redirects 781 ==================================================================== 782 */ 783 784 /** 785 * Redirects the browser to the target specified in +options+. This parameter can take one of three forms: 786 * 787 * * <tt>Array</tt>: The URL will be generated by calling $this->UrlFor with the +options+. 788 * * <tt>String starting with protocol:// (like http://)</tt>: Is passed straight through 789 * as the target for redirection. 790 * * <tt>String not containing a protocol</tt>: The current protocol and host is prepended to the string. 791 * * <tt>back</tt>: Back to the page that issued the Request-> Useful for forms that are 792 * triggered from multiple places. 793 * Short-hand for redirectTo(Request->env["HTTP_REFERER"]) 794 * 795 * Examples: 796 * redirectTo(array('action' => 'show', 'id' => 5)); 797 * redirectTo('http://www.akelos.com'); 798 * redirectTo('/images/screenshot.jpg'); 799 * redirectTo('back'); 800 * 801 * The redirection happens as a "302 Moved" header. 802 */ 803 function redirectTo($options = array(), $parameters_for_method_reference = null) 804 { 805 if(is_string($options)) { 806 if(preg_match('/^\w+:\/\/.*/',$options)){ 807 if($this->_hasPerformed()){ 808 $this->_doubleRenderError(); 809 } 810 AK_LOG_EVENTS && !empty($this->_Logger) ? $this->_Logger->message('Redirected to '.$options) : null; 811 $this->_handleFlashAttribute(); 812 $this->Response->redirect($options); 813 $this->Response->redirected_to = $options; 814 $this->performed_redirect = true; 815 }elseif ($options == 'back'){ 816 $this->redirectTo($this->Request->env['HTTP_REFERER']); 817 }else{ 818 $this->redirectTo($this->Request->getProtocol(). $this->Request->getHostWithPort(). $options); 819 } 820 }else{ 821 if(empty($parameters_for_method_reference)){ 822 $this->redirectTo($this->UrlFor($options)); 823 $this->Response->redirected_to = $options; 824 }else{ 825 $this->redirectTo($this->UrlFor($options, $parameters_for_method_reference)); 826 $this->Response->redirected_to = $options; 827 $this->Response->redirected_to_method_params = $parameters_for_method_reference; 828 } 829 } 830 } 831 832 function redirectToAction($action, $options = array()) 833 { 834 $this->redirectTo(array_merge(array('action'=>$action), $options)); 835 } 836 837 838 /** 839 * This methods are required for retrieving available controllers for URL Routing 840 */ 841 function rewriteOptions($options) 842 { 843 $defaults = $this->defaultUrlOptions($options); 844 if(!empty($this->module_name)){ 845 $defaults['module'] = $this->getModuleName(); 846 } 847 if(!empty($options['controller']) && strstr($options['controller'], '/')){ 848 $defaults['module'] = substr($options['controller'], 0, strrpos($options['controller'], '/')); 849 $options['controller'] = substr($options['controller'], strrpos($options['controller'], '/') + 1); 850 } 851 $options = !empty($defaults) ? array_merge($defaults, $options) : $options; 852 $options['controller'] = empty($options['controller']) ? AkInflector::underscore($this->getControllerName()) : $options['controller']; 853 return $options; 854 } 855 856 function getControllerName() 857 { 858 if(!isset($this->controller_name)){ 859 $current_class_name = str_replace('_', '::', get_class($this)); 860 if (!AK_PHP5){ 861 $current_class_name = $this->__getControllerName_PHP4_fix($current_class_name); 862 } 863 $controller_name = substr($current_class_name,0,-10); 864 $this->controller_name = $this->_removeModuleNameFromControllerName($controller_name); 865 $this->singularized_controller_name = AkInflector::singularize($this->controller_name); 866 } 867 return $this->controller_name; 868 } 869 870 function __getControllerName_PHP4_fix($class_name) 871 { 872 $included_controllers = $this->_getIncludedControllerNames(); 873 $lowercase_included_controllers = array_map('strtolower', $included_controllers); 874 $key = array_search(strtolower($class_name), $lowercase_included_controllers, true); 875 return $included_controllers[$key]; 876 } 877 878 function getModuleName() 879 { 880 return $this->module_name; 881 } 882 883 function setModuleName($module_name) 884 { 885 return $this->module_name = $module_name; 886 } 887 888 /** 889 * Removes the modules name from the controller if exists and sets it. 890 * 891 * @return $controller_name 892 */ 893 function _removeModuleNameFromControllerName($controller_name) 894 { 895 if(strstr($controller_name, '::')){ 896 $module_parts = explode ('::',$controller_name); 897 $controller_name = array_pop($module_parts); 898 $this->setModuleName(join('/', array_map(array('AkInflector','underscore'), $module_parts))); 899 } 900 return $controller_name; 901 } 902 903 function _getTemplateBasePath() 904 { 905 return AK_APP_DIR.DS.'views'.DS.(empty($this->_module_path)?'':$this->_module_path).$this->Request->getController(); 906 } 907 908 function _getIncludedControllerNames() 909 { 910 $controllers = array(); 911 foreach (get_included_files() as $file_name){ 912 if(strstr($file_name,AK_CONTROLLERS_DIR)){ 913 $controllers[] = AkInflector::classify(str_replace(array(AK_CONTROLLERS_DIR.DS,'.php', DS, '//'),array('','','/', '/'),$file_name)); 914 } 915 } 916 return $controllers; 917 } 918 919 920 921 922 /** 923 URL generation/rewriting 924 ==================================================================== 925 */ 926 927 928 /** 929 * Overwrite to implement a number of default options that all urlFor-based methods will use. 930 * The default options should come in 931 * the form of a an array, just like the one you would use for $this->UrlFor directly. Example: 932 * 933 * function defaultUrlOptions($options) 934 * { 935 * return array('project' => ($this->Project->isActive() ? $this->Project->url_name : 'unknown')); 936 * } 937 * 938 * As you can infer from the example, this is mostly useful for situations where you want to 939 * centralize dynamic decisions about the urls as they stem from the business domain. 940 * Please note that any individual $this->UrlFor call can always override the defaults set 941 * by this method. 942 */ 943 function defaultUrlOptions($options) 944 { 945 } 946 947 948 /** 949 * Returns a URL that has been rewritten according to the options array and the defined Routes. 950 * (For doing a complete redirect, use redirectTo). 951 * 952 * <tt>$this->UrlFor</tt> is used to: 953 * 954 * All keys given to $this->UrlFor are forwarded to the Route module, save for the following: 955 * * <tt>anchor</tt> -- specifies the anchor name to be appended to the path. For example, 956 * <tt>$this->UrlFor(array('controller' => 'posts', 'action' => 'show', 'id' => 10, 'anchor' => 'comments'</tt> 957 * will produce "/posts/show/10#comments". 958 * * <tt>only_path</tt> -- if true, returns the absolute URL (omitting the protocol, host name, and port) 959 * * <tt>trailing_slash</tt> -- if true, adds a trailing slash, as in "/archive/2005/". Note that this 960 * is currently not recommended since it breaks caching. 961 * * <tt>host</tt> -- overrides the default (current) host if provided 962 * * <tt>protocol</tt> -- overrides the default (current) protocol if provided 963 * 964 * The URL is generated from the remaining keys in the array. A URL contains two key parts: the <base> and a query string. 965 * Routes composes a query string as the key/value pairs not included in the <base>. 966 * 967 * The default Routes setup supports a typical Akelos Framework path of "controller/action/id" 968 * where action and id are optional, with 969 * action defaulting to 'index' when not given. Here are some typical $this->UrlFor statements 970 * and their corresponding URLs: 971 * 972 * $this->UrlFor(array('controller'=>'posts','action'=>'recent')); // 'proto://host.com/posts/recent' 973 * $this->UrlFor(array('controller'=>'posts','action'=>'index')); // 'proto://host.com/posts' 974 * $this->UrlFor(array('controller'=>'posts','action'=>'show','id'=>10)); // 'proto://host.com/posts/show/10' 975 * 976 * When generating a new URL, missing values may be filled in from the current 977 * Request's parameters. For example, 978 * <tt>$this->UrlFor(array('action'=>'some_action'));</tt> will retain the current controller, 979 * as expected. This behavior extends to other parameters, including <tt>controller</tt>, 980 * <tt>id</tt>, and any other parameters that are placed into a Route's path. 981 * 982 * The URL helpers such as <tt>$this->UrlFor</tt> have a limited form of memory: 983 * when generating a new URL, they can look for missing values in the current Request's parameters. 984 * Routes attempts to guess when a value should and should not be 985 * taken from the defaults. There are a few simple rules on how this is performed: 986 * 987 * * If the controller name begins with a slash, no defaults are used: <tt>$this->UrlFor(array('controller'=>'/home'));</tt> 988 * * If the controller changes, the action will default to index unless provided 989 * 990 * The final rule is applied while the URL is being generated and is best illustrated by an example. Let us consider the 991 * route given by <tt>map->connect('people/:last/:first/:action', array('action' => 'bio', 'controller' => 'people'))</tt>. 992 * 993 * Suppose that the current URL is "people/hh/david/contacts". Let's consider a few 994 * different cases of URLs which are generated from this page. 995 * 996 * * <tt>$this->UrlFor(array('action'=>'bio'));</tt> -- During the generation of this URL, 997 * default values will be used for the first and 998 * last components, and the action shall change. The generated URL will be, "people/hh/david/bio". 999 * * <tt>$this->UrlFor(array('first'=>'davids-little-brother'));</tt> This 1000 * generates the URL 'people/hh/davids-little-brother' -- note 1001 * that this URL leaves out the assumed action of 'bio'. 1002 * 1003 * However, you might ask why the action from the current Request, 'contacts', isn't 1004 * carried over into the new URL. The answer has to do with the order in which 1005 * the parameters appear in the generated path. In a nutshell, since the 1006 * value that appears in the slot for <tt>first</tt> is not equal to default value 1007 * for <tt>first</tt> we stop using defaults. On it's own, this rule can account 1008 * for much of the typical Akelos Framework URL behavior. 1009 * 1010 * Although a convienence, defaults can occasionaly get in your way. In some cases 1011 * a default persists longer than desired. 1012 * The default may be cleared by adding <tt>'name' => null</tt> to <tt>$this->UrlFor</tt>'s options. 1013 * This is often required when writing form helpers, since the defaults in play 1014 * may vary greatly depending upon where the helper is used from. The following line 1015 * will redirect to PostController's default action, regardless of the page it is 1016 * displayed on: 1017 * 1018 * $this->UrlFor(array('controller' => 'posts', 'action' => null)); 1019 * 1020 * If you explicitly want to create a URL that's almost the same as the current URL, you can do so using the 1021 * overwrite_params options. Say for your posts you have different views for showing and printing them. 1022 * Then, in the show view, you get the URL for the print view like this 1023 * 1024 * $this->UrlFor(array('overwrite_params' => array('action' => 'print'))); 1025 * 1026 * This takes the current URL as is and only exchanges the action. In contrast, 1027 * <tt>$this->UrlFor(array('action'=>'print'));</tt> 1028 * would have slashed-off the path components after the changed action. 1029 */ 1030 function urlFor($options = array(), $parameters_for_method_reference = null) 1031 { 1032 return $this->rewrite($this->rewriteOptions($options)); 1033 } 1034 1035 function addToUrl($options = array(), $options_to_exclude = array()) 1036 { 1037 $options_to_exclude = array_merge(array('ak','lang',AK_SESSION_NAME,'AK_SESSID','PHPSESSID'), $options_to_exclude); 1038 $options = array_merge(array_merge(array('action'=>$this->Request->getAction()),$this->params),$options); 1039 foreach ($options_to_exclude as $option_to_exclude){ 1040 unset($options[$option_to_exclude]); 1041 } 1042 return $this->urlFor($options); 1043 } 1044 1045 function getActionName() 1046 { 1047 return $this->Request->getAction(); 1048 } 1049 1050 1051 function _doubleRenderError($message = null) 1052 { 1053 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); 1054 } 1055 1056 function _hasPerformed() 1057 { 1058 return !empty($this->performed_render) || !empty($this->performed_redirect); 1059 } 1060 1061 function _getRequestOrigin() 1062 { 1063 return $this->Request->remote_ip.' at '.Ak::getDate(); 1064 } 1065 1066 function _getCompleteRequestUri() 1067 { 1068 return $this->Request->protocol . $this->Request->host . $this->Request->request_uri; 1069 } 1070 1071 function _closeSession() 1072 { 1073 !empty($this->session) ? session_write_close() : null; 1074 } 1075 1076 1077 function _hasTemplate($template_name = null) 1078 { 1079 return file_exists(empty($template_name) ? $this->getDefaultTemplateName() : $template_name); 1080 } 1081 1082 function _templateIsPublic($template_name = null) 1083 { 1084 $template_name = empty($template_name) ? $this->getDefaultTemplateName() : $template_name; 1085 return $this->Template->fileIsPublic($template_name); 1086 } 1087 1088 function _isTemplateExemptFromLayout($template_name = null) 1089 { 1090 $template_name = empty($template_name) ? $this->getDefaultTemplateName() : $template_name; 1091 return $this->Template->_javascriptTemplateExists($template_name); 1092 } 1093 1094 function _assertExistanceOfTemplateFile($template_name) 1095 { 1096 $extension = $this->Template->delegateTemplateExists($template_name); 1097 $this->full_template_path = $this->Template->getFullTemplatePath($template_name, $extension ? $extension : 'tpl'); 1098 if(!$this->_hasTemplate($this->full_template_path)){ 1099 if(!empty($this->_ignore_missing_templates) && $this->_ignore_missing_templates === true){ 1100 return; 1101 } 1102 $template_type = strstr($template_name,'layouts') ? 'layout' : 'template'; 1103 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); 1104 } 1105 } 1106 1107 function getDefaultTemplateName($default_action_name = null) 1108 { 1109 return empty($default_action_name) ? (empty($this->_default_template_name) ? $this->_action_name : $this->_default_template_name) : $default_action_name; 1110 } 1111 1112 function setDefaultTemplateName($template_name) 1113 { 1114 $this->_default_template_name = $template_name; 1115 } 1116 1117 1118 1119 function rewrite($options = array()) 1120 { 1121 return $this->_rewriteUrl($this->_rewritePath($options), $options); 1122 } 1123 1124 1125 function toString() 1126 { 1127 return $this->Request->getProtocol().$this->Request->getHostWithPort(). 1128 $this->Request->getPath().@$this->parameters['controller']. 1129 @$this->parameters['action'].@$this->parameters['inspect']; 1130 } 1131 1132 /** 1133 * Given a path and options, returns a rewritten URL string 1134 */ 1135 function _rewriteUrl($path, $options) 1136 { 1137 $rewritten_url = ''; 1138 if(empty($options['only_path'])){ 1139 $rewritten_url .= !empty($options['protocol']) ? $options['protocol'] : $this->Request->getProtocol(); 1140 $rewritten_url .= empty($rewritten_url) || strpos($rewritten_url,'://') ? '' : '://'; 1141 $rewritten_url .= $this->_rewriteAuthentication($options); 1142 $rewritten_url .= !empty($options['host']) ? $options['host'] : $this->Request->getHostWithPort(); 1143 $options = Ak::delete($options, array('user','password','host','protocol')); 1144 } 1145 1146 $rewritten_url .= empty($options['skip_relative_url_root']) ? $this->Request->getRelativeUrlRoot() : ''; 1147 1148 if(empty($options['skip_url_locale'])){ 1149 $locale = $this->Request->getLocaleFromUrl(); 1150 if(empty($options['lang'])){ 1151 $rewritten_url .= (empty($locale) ? '' : '/').$locale; 1152 } 1153 1154 } 1155 1156 $rewritten_url .= (substr($rewritten_url,-1) == '/' ? '' : (AK_URL_REWRITE_ENABLED ? '' : (!empty($path[0]) && $path[0] != '/' ? '/' : ''))); 1157 $rewritten_url .= $path; 1158 $rewritten_url .= empty($options['trailing_slash']) ? '' : '/'; 1159 $rewritten_url .= empty($options['anchor']) ? '' : '#'.$options['anchor']; 1160 1161 return $rewritten_url; 1162 } 1163 1164 function _rewriteAuthentication($options) 1165 { 1166 if(!isset($options['user']) && isset($options['password'])){ 1167 return urlencode($options['user']).':'.urlencode($options['password']).'@'; 1168 }else{ 1169 return ''; 1170 } 1171 } 1172 1173 function _rewritePath($options) 1174 { 1175 if(!empty($options['params'])){ 1176 foreach ($options['params'] as $k=>$v){ 1177 $options[$k] = $v; 1178 } 1179 unset($options['params']); 1180 } 1181 if(!empty($options['overwrite_params'])){ 1182 foreach ($options['overwrite_params'] as $k=>$v){ 1183 $options[$k] = $v; 1184 } 1185 unset($options['overwrite_params']); 1186 } 1187 foreach (array('anchor', 'params', 'only_path', 'host', 'protocol', 'trailing_slash', 'skip_relative_url_root') as $k){ 1188 unset($options[$k]); 1189 } 1190 $path = Ak::toUrl($options); 1191 return $path; 1192 } 1193 1194 /** 1195 * Returns a query string with escaped keys and values from the passed array. If the passed 1196 * array contains an 'id' it'll 1197 * be added as a path element instead of a regular parameter pair. 1198 */ 1199 function buildQueryString($array, $only_keys = null) 1200 { 1201 $array = !empty($only_keys) ? array_keys($array) : $array; 1202 return Ak::toUrl($array); 1203 } 1204 1205 1206 1207 1208 /** 1209 Layouts 1210 ==================================================================== 1211 * 1212 * Layouts reverse the common pattern of including shared headers and footers in many templates 1213 * to isolate changes in repeated setups. The inclusion pattern has pages that look like this: 1214 * 1215 * <?php echo $controller->render('shared/header') ?> 1216 * Hello World 1217 * <?php echo $controller->render('shared/footer') ?> 1218 * 1219 * This approach is a decent way of keeping common structures isolated from the 1220 * changing content, but it's verbose and if( you ever want to change the structure 1221 * of these two includes, you'll have to change all the templates. 1222 * 1223 * With layouts, you can flip it around and have the common structure know where 1224 * to insert changing content. This means that the header and footer are only 1225 * mentioned in one place, like this: 1226 * 1227 * <!-- The header part of this layout --> 1228 * <?php echo $content_for_layout ?> 1229 * <!-- The footer part of this layout --> 1230 * 1231 * And then you have content pages that look like this: 1232 * 1233 * hello world 1234 * 1235 * Not a word about common structures. At rendering time, the content page is 1236 * computed and then inserted in the layout, 1237 * like this: 1238 * 1239 * <!-- The header part of this layout --> 1240 * hello world 1241 * <!-- The footer part of this layout --> 1242 * 1243 * == Accessing shared variables 1244 * 1245 * Layouts have access to variables specified in the content pages and vice versa. 1246 * This allows you to have layouts with references that won't materialize before 1247 * rendering time: 1248 * 1249 * <h1><?php echo $page_title ?></h1> 1250 * <?php echo $content_for_layout ?> 1251 * 1252 * ...and content pages that fulfill these references _at_ rendering time: 1253 * 1254 * <?php $page_title = 'Welcome'; ?> 1255 * Off-world colonies offers you a chance to start a new life 1256 * 1257 * The result after rendering is: 1258 * 1259 * <h1>Welcome</h1> 1260 * Off-world colonies offers you a chance to start a new life 1261 * 1262 * == Automatic layout assignment 1263 * 1264 * If there is a template in <tt>app/views/layouts/</tt> with the same name as 1265 * the current controller then it will be automatically 1266 * set as that controller's layout unless explicitly told otherwise. Say you have 1267 * a WeblogController, for example. If a template named <tt>app/views/layouts/weblog.tpl</tt> 1268 * exists then it will be automatically set as the layout for your WeblogController. 1269 * You can create a layout with the name <tt>application.tpl</tt> 1270 * and this will be set as the default controller if there is no layout with 1271 * the same name as the current controller and there is no layout explicitly 1272 * assigned on the +layout+ attribute. Setting a layout explicitly will always 1273 * override the automatic behaviour 1274 * for the controller where the layout is set. Explicitly setting the layout 1275 * in a parent class, though, will not override the 1276 * child class's layout assignement if the child class has a layout with the same name. 1277 * 1278 * == Inheritance for layouts 1279 * 1280 * Layouts are shared downwards in the inheritance hierarchy, but not upwards. Examples: 1281 * 1282 * class BankController extends AkActionController 1283 * { 1284 * var $layout = 'bank_standard'; 1285 * } 1286 * 1287 * class InformationController extends BankController 1288 * { 1289 * } 1290 * 1291 * class VaultController extends BankController 1292 * { 1293 * var $layout = 'access_level_layout'; 1294 * } 1295 * 1296 * class EmployeeController extends BankController 1297 * { 1298 * var $layout = null; 1299 * } 1300 * 1301 * The InformationController uses 'bank_standard' inherited from the BankController, the VaultController 1302 * and picks the layout 'access_level_layout', and the EmployeeController doesn't want to use a layout at all. 1303 * 1304 * == Types of layouts 1305 * 1306 * Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes 1307 * you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can 1308 * be done either by an inline method. 1309 * 1310 * The method reference is the preferred approach to variable layouts and is used like this: 1311 * 1312 * class WeblogController extends AkActionController 1313 * { 1314 * function __construct() 1315 * { 1316 * $this->setLayout(array(&$this, '_writersAndReaders')); 1317 * } 1318 * 1319 * function index() 1320 * { 1321 * // fetching posts 1322 * } 1323 * 1324 * function _writersAndReaders() 1325 * { 1326 * return is_logged_in() ? 'writer_layout' : 'reader_layout'; 1327 * } 1328 * } 1329 * 1330 * Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing 1331 * is logged in or not. 1332 * 1333 * The most common way of specifying a layout is still just as a plain template name: 1334 * 1335 * class WeblogController extends AkActionController 1336 * { 1337 * var $layout = 'weblog_standard'; 1338 * } 1339 * 1340 * If no directory is specified for the template name, the template will by default by looked for in +app/views/layouts/+. 1341 * 1342 * == Conditional layouts 1343 * 1344 * If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering 1345 * 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 1346 * <tt>only</tt> and <tt>except</tt> options can be passed to the layout call. For example: 1347 * 1348 * class WeblogController extends AkActionController 1349 * { 1350 * function __construct() 1351 * { 1352 * $this->setLayout('weblog_standard', array('except' => 'rss')); 1353 * } 1354 * 1355 * // ... 1356 * 1357 * } 1358 * 1359 * This will assign 'weblog_standard' as the WeblogController's layout except for the +rss+ action, which will not wrap a layout 1360 * around the rendered view. 1361 * 1362 * Both the <tt>only</tt> and <tt>except</tt> condition can accept an arbitrary number of method names, so 1363 * <tt>'except' => array('rss', 'text_only')</tt> is valid, as is <tt>'except' => 'rss'</tt>. 1364 * 1365 * == Using a different layout in the action render call 1366 * 1367 * If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above. 1368 * Some times you'll have exceptions, though, where one action wants to use a different layout than the rest of the controller. 1369 * This is possible using the <tt>render</tt> method. It's just a bit more manual work as you'll have to supply fully 1370 * qualified template and layout names as this example shows: 1371 * 1372 * class WeblogController extends AkActionController 1373 * { 1374 * function help() 1375 * { 1376 * $this->render(array('action'=>'help/index','layout'=>'help')); 1377 * } 1378 * } 1379 */ 1380 1381 /** 1382 * If a layout is specified, all actions rendered through render and render_action will have their result assigned 1383 * to <tt>$this->content_for_layout</tt>, which can then be used by the layout to insert their contents with 1384 * <tt><?php echo $$this->content_for_layout ?></tt>. This layout can itself depend on instance variables assigned during action 1385 * performance and have access to them as any normal template would. 1386 */ 1387 function setLayout($template_name, $conditions = array()) 1388 { 1389 $this->_addLayoutConditions($conditions); 1390 $this->layout = $template_name; 1391 } 1392 1393 function getLayoutConditions() 1394 { 1395 return empty($this->_layout_conditions) ? array() : $this->_layout_conditions; 1396 } 1397 1398 function _addLayoutConditions($conditions) 1399 { 1400 $this->_layout_conditions = $conditions; 1401 } 1402 1403 1404 1405 /** 1406 * Returns the name of the active layout. If the layout was specified as a method reference, this method 1407 * is called and the return value is used. Likewise if( the layout was specified as an inline method (through a method 1408 * object). If the layout was defined without a directory, layouts is assumed. So <tt>setLayout('weblog/standard')</tt> will return 1409 * weblog/standard, but <tt>setLayout('standard')</tt> will return layouts/standard. 1410 */ 1411 function getActiveLayout($passed_layout = null) 1412 { 1413 if(empty($passed_layout)){ 1414 $layout = !isset($this->layout) ? AkInflector::underscore($this->getControllerName()) : $this->layout; 1415 }else{ 1416 $layout =& $passed_layout; 1417 } 1418 if(is_array($layout) && is_object($layout[0]) && method_exists($layout[0], $layout[1])){ 1419 $this->active_layout = $layout[0]->{$layout[1]}(); 1420 }elseif(method_exists($this,$layout) && strtolower(get_class($this)) !== strtolower($layout)){ 1421 $this->active_layout = $this->$layout(); 1422 }else{ 1423 $this->active_layout = $layout; 1424 } 1425 1426 if(!empty($this->active_layout)){ 1427 return strstr($this->active_layout,DS) ? $this->active_layout : 'layouts'.DS.$this->active_layout; 1428 } 1429 } 1430 1431 1432 function renderWithALayout($options = null, $status = null, $layout = null) 1433 { 1434 $template_with_options = !empty($options) && is_array($options); 1435 1436 if($this->_canApplyLayout($template_with_options, $options) && ($layout = $this->_pickLayout($template_with_options, $options, $layout))){ 1437 1438 $options = $template_with_options? array_merge((array)$options,array('layout'=>false)) : $options; 1439 1440 $this->content_for_layout = $this->render($options, $status); 1441 1442 if($template_with_options){ 1443 $status = empty($options['status']) ? $status : $options['status']; 1444 } 1445 1446 $this->eraseRenderResults(); 1447 $this->_addVariablesToAssigns(); 1448 1449 return $this->renderText($this->Template->renderFile($layout, true, &$this->_assigns), $status); 1450 }else{ 1451 return $this->render($options, $status, &$this->_assigns); 1452 } 1453 } 1454 1455 function _canApplyLayout($template_with_options, $options) 1456 { 1457 return !empty($template_with_options) ? $this->_isCandidateForLayout($options) : !$this->_isTemplateExemptFromLayout(); 1458 } 1459 1460 function _isCandidateForLayout($options) 1461 { 1462 return !empty($options['layout']) || 1463 (empty($options['text']) && empty($options['file']) && empty($options['inline']) && empty($options['partial']) && empty($options['nothing'])) && 1464 !$this->_isTemplateExemptFromLayout($this->_getDefaultTemplateName(empty($options['action']) ? $options['template'] : $options['action'])); 1465 } 1466 1467 function _pickLayout($template_with_options, $options, $layout = null) 1468 { 1469 if(!empty($template_with_options)){ 1470 $layout = empty($options['layout']) ? ($this->_doesActionHasLayout() ? $this->getActiveLayout(): false) : $this->getActiveLayout($options['layout']); 1471 }elseif(empty($layout) || $layout === true){ 1472 $layout = $this->_doesActionHasLayout() ? $this->getActiveLayout() : false; 1473 } 1474 if(!empty($layout)){ 1475 1476 $layout = strstr($layout,'/') || strstr($layout,DS) ? $layout : 'layouts'.DS.$layout; 1477 $layout = preg_replace('/\.tpl$/', '', $layout); 1478 1479 $layout = substr($layout,0,7) === 'layouts' ? 1480 (empty($this->_module_path) || !empty($this->layout) ? AK_VIEWS_DIR.DS.$layout.'.tpl' : AK_VIEWS_DIR.DS.'layouts'.DS.trim($this->_module_path, DS).'.tpl') : 1481 $layout.'.tpl'; 1482 1483 if (file_exists($layout)) { 1484 return $layout; 1485 } 1486 $layout = null; 1487 } 1488 if(empty($layout) && $layout !== false && defined('AK_DEFAULT_LAYOUT')){ 1489 $layout = AK_VIEWS_DIR.DS.'layouts'.DS.AK_DEFAULT_LAYOUT.'.tpl'; 1490 } 1491 return file_exists($layout) ? $layout : false; 1492 } 1493 1494 function _doesActionHasLayout() 1495 { 1496 $conditions = $this->getLayoutConditions(); 1497 1498 $action_name = $this->Request->getAction(); 1499 if(!empty($conditions['only']) && ((is_array($conditions['only']) && in_array($action_name,$conditions['only'])) || 1500 (is_string($conditions['only']) && $action_name == $conditions['only']))){ 1501 return true; 1502 }elseif (!empty($conditions['only'])){ 1503 return false; 1504 } 1505 if(!empty($conditions['except']) && ((is_array($conditions['except']) && in_array($action_name,$conditions['except'])) || 1506 (is_string($conditions['except']) && $action_name == $conditions['except']))){ 1507 return false; 1508 } 1509 1510 return true; 1511 } 1512 1513 1514 1515 1516 /** 1517 Filters 1518 ==================================================================== 1519 * 1520 * Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do 1521 * authentication, caching, or auditing before the intended action is performed. Or to do localization or output 1522 * compression after the action has been performed. 1523 * 1524 * Filters have access to the request, response, and all the instance variables set by other filters in the chain 1525 * or by the action (in the case of after filters). Additionally, it's possible for a pre-processing <tt>beforeFilter</tt> 1526 * to halt the processing before the intended action is processed by returning false or performing a redirect or render. 1527 * This is especially useful for filters like authentication where you're not interested in allowing the action to be 1528 * performed if the proper credentials are not in order. 1529 * 1530 * == Filter inheritance 1531 * 1532 * Controller inheritance hierarchies share filters downwards, but subclasses can also add new filters without 1533 * affecting the superclass. For example: 1534 * 1535 * class BankController extends AkActionController 1536 * { 1537 * function __construct() 1538 * { 1539 * $this->beforeFilter('_audit'); 1540 * } 1541 * 1542 * function _audit(&$controller) 1543 * { 1544 * // record the action and parameters in an audit log 1545 * } 1546 * } 1547 * 1548 * class VaultController extends BankController 1549 * { 1550 * function __construct() 1551 * { 1552 * $this->beforeFilter('_verifyCredentials'); 1553 * } 1554 * 1555 * function _verifyCredentials(&$controller) 1556 * { 1557 * // make sure the user is allowed into the vault 1558 * } 1559 * } 1560 * 1561 * Now any actions performed on the BankController will have the audit method called before. On the VaultController, 1562 * first the audit method is called, then the _verifyCredentials method. If the _audit method returns false, then 1563 * _verifyCredentials and the intended action are never called. 1564 * 1565 * == Filter types 1566 * 1567 * A filter can take one of three forms: method reference, external class, or inline method. The first 1568 * is the most common and works by referencing a method somewhere in the inheritance hierarchy of 1569 * the controller by use of a method name. In the bank example above, both BankController and VaultController use this form. 1570 * 1571 * Using an external class makes for more easily reused generic filters, such as output compression. External filter classes 1572 * are implemented by having a static +filter+ method on any class and then passing this class to the filter method. Example: 1573 * 1574 * class OutputCompressionFilter 1575 * { 1576 * function filter(&$controller) 1577 * { 1578 * $controller->response->body = compress($controller->response->body); 1579 * } 1580 * } 1581 * 1582 * class NewspaperController extends AkActionController 1583 * { 1584 * function __construct() 1585 * { 1586 * $this->afterFilter(new OutputCompressionFilter()); 1587 * } 1588 * } 1589 * 1590 * The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can 1591 * manipulate them as it sees fit. 1592 * 1593 * 1594 * == Filter chain ordering 1595 * 1596 * Using <tt>beforeFilter</tt> and <tt>afterFilter</tt> appends the specified filters to the existing chain. That's usually 1597 * just fine, but some times you care more about the order in which the filters are executed. When that's the case, you 1598 * can use <tt>prependBeforeFilter</tt> and <tt>prependAfterFilter</tt>. Filters added by these methods will be put at the 1599 * beginning of their respective chain and executed before the rest. For example: 1600 * 1601 * class ShoppingController extends AkActionController 1602 * { 1603 * function __construct() 1604 * { 1605 * $this->beforeFilter('verifyOpenShop'); 1606 * } 1607 * } 1608 * 1609 * 1610 * class CheckoutController extends AkActionController 1611 * { 1612 * function __construct() 1613 * { 1614 * $this->prependBeforeFilter('ensureItemsInCart', 'ensureItemsInStock'); 1615 * } 1616 * } 1617 * 1618 * The filter chain for the CheckoutController is now <tt>ensureItemsInCart, ensureItemsInStock,</tt> 1619 * <tt>verifyOpenShop</tt>. So if either of the ensure filters return false, we'll never get around to see if the shop 1620 * is open or not. 1621 * 1622 * You may pass multiple filter arguments of each type. 1623 * 1624 * == Around filters 1625 * 1626 * In addition to the individual before and after filters, it's also possible to specify that a single object should handle 1627 * both the before and after call. That's especially useful when you need to keep state active between the before and after, 1628 * such as the example of a benchmark filter below: 1629 * 1630 * class WeblogController extends AkActionController 1631 * { 1632 * function __construct() 1633 * { 1634 * $this->aroundFilter(new BenchmarkingFilter()); 1635 * } 1636 * 1637 * // Before this action is performed, BenchmarkingFilter->before($controller) is executed 1638 * function index() 1639 * { 1640 * } 1641 * // After this action has been performed, BenchmarkingFilter->after($controller) is executed 1642 * } 1643 * 1644 * class BenchmarkingFilter 1645 * { 1646 * function before(&$controller) 1647 * { 1648 * start_timer(); 1649 * } 1650 * 1651 * function after(&$controller) 1652 * { 1653 * stop_timer(); 1654 * report_result(); 1655 * } 1656 * } 1657 * 1658 * == Filter chain skipping 1659 * 1660 * Some times its convenient to specify a filter chain in a superclass that'll hold true for the majority of the 1661 * subclasses, but not necessarily all of them. The subclasses that behave in exception can then specify which filters 1662 * they would like to be relieved of. Examples 1663 * 1664 * class ApplicationController extends AkActionController 1665 * { 1666 * function __construct() 1667 * { 1668 * $this->beforeFilter('authenticate'); 1669 * } 1670 * } 1671 * 1672 * class WeblogController extends ApplicationController 1673 * { 1674 * // will run the authenticate filter 1675 * } 1676 * 1677 * class SignupController extends AkActionController 1678 * { 1679 * function __construct() 1680 * { 1681 * $this->skipBeforeFilter('authenticate'); 1682 * } 1683 * // will not run the authenticate filter 1684 * } 1685 * 1686 * == Filter conditions 1687 * 1688 * Filters can be limited to run for only specific actions. This can be expressed either by listing the actions to 1689 * exclude or the actions to include when executing the filter. Available conditions are +only+ or +except+, both 1690 * of which accept an arbitrary number of method references. For example: 1691 * 1692 * class Journal extends AkActionController 1693 * { 1694 * function __construct() 1695 * { // only require authentication if the current action is edit or delete 1696 * $this->beforeFilter(array('_authorize'=>array('only'=>array('edit','delete'))); 1697 * } 1698 * 1699 * function _authorize(&$controller) 1700 * { 1701 * // redirect to login unless authenticated 1702 * } 1703 * } 1704 */ 1705 1706 var $_includedActions = array(), $_beforeFilters = array(), $_afterFilters = array(), $_excludedActions = array(); 1707 /** 1708 * The passed <tt>filters</tt> will be appended to the array of filters that's run _before_ actions 1709 * on this controller are performed. 1710 */ 1711 function appendBeforeFilter() 1712 { 1713 $filters = array_reverse(func_get_args()); 1714 foreach (array_keys($filters) as $k){ 1715 $conditions = $this->_extractConditions(&$filters[$k]); 1716 $this->_addActionConditions($filters[$k], $conditions); 1717 $this->_appendFilterToChain('before', $filters[$k]); 1718 } 1719 } 1720 1721 /** 1722 * The passed <tt>filters</tt> will be prepended to the array of filters that's run _before_ actions 1723 * on this controller are performed. 1724 */ 1725 function prependBeforeFilter() 1726 { 1727 $filters = array_reverse(func_get_args()); 1728 foreach (array_keys($filters) as $k){ 1729 $conditions = $this->_extractConditions(&$filters[$k]); 1730 $this->_addActionConditions($filters[$k], $conditions); 1731 $this->_prependFilterToChain('before', $filters[$k]); 1732 } 1733 } 1734 1735 /** 1736 * Short-hand for appendBeforeFilter since that's the most common of the two. 1737 */ 1738 function beforeFilter() 1739 { 1740 $filters = func_get_args(); 1741 foreach (array_keys($filters) as $k){ 1742 $this->appendBeforeFilter($filters[$k]); 1743 } 1744 } 1745 1746 /** 1747 * The passed <tt>filters</tt> will be appended to the array of filters that's run _after_ actions 1748 * on this controller are performed. 1749 */ 1750 function appendAfterFilter() 1751 { 1752 $filters = array_reverse(func_get_args()); 1753 foreach (array_keys($filters) as $k){ 1754 $conditions = $this->_extractConditions(&$filters[$k]); 1755 $this->_addActionConditions(&$filters[$k], $conditions); 1756 $this->_appendFilterToChain('after', &$filters[$k]); 1757 } 1758 1759 } 1760 1761 /** 1762 * The passed <tt>filters</tt> will be prepended to the array of filters that's run _after_ actions 1763 * on this controller are performed. 1764 */ 1765 function prependAfterFilter() 1766 { 1767 $filters = array_reverse(func_get_args()); 1768 foreach (array_keys($filters) as $k){ 1769 $conditions = $this->_extractConditions(&$filters[$k]); 1770 $this->_addActionConditions(&$filters[$k], $conditions); 1771 $this->_prependFilterToChain('after', &$filters[$k]); 1772 } 1773 } 1774 1775 /** 1776 * Short-hand for appendAfterFilter since that's the most common of the two. 1777 * */ 1778 function afterFilter() 1779 { 1780 $filters = func_get_args(); 1781 foreach (array_keys($filters) as $k){ 1782 $this->appendAfterFilter($filters[$k]); 1783 } 1784 } 1785 1786 /** 1787 * The passed <tt>filters</tt> will have their +before+ method appended to the array of filters that's run both before actions 1788 * on this controller are performed and have their +after+ method prepended to the after actions. The filter objects must all 1789 * respond to both +before+ and +after+. So if you do appendAroundFilter(new A(), new B()), the callstack will look like: 1790 * 1791 * B::before() 1792 * A::before() 1793 * A::after() 1794 * B::after() 1795 */ 1796 function appendAroundFilter() 1797 { 1798 $filters = func_get_args(); 1799 foreach (array_keys($filters) as $k){ 1800 $this->_ensureFilterRespondsToBeforeAndAfter(&$filters[$k]); 1801 $this->appendBeforeFilter(array(&$filters[$k],'before')); 1802 } 1803 $filters = array_reverse($filters); 1804 foreach (array_keys($filters) as $k){ 1805 $this->prependAfterFilter(array(&$filters[$k],'after')); 1806 } 1807 } 1808 1809 /** 1810 * The passed <tt>filters</tt> will have their +before+ method prepended to the array of filters that's run both before actions 1811 * on this controller are performed and have their +after+ method appended to the after actions. The filter objects must all 1812 * respond to both +before+ and +after+. So if you do appendAroundFilter(new A(), new B()), the callstack will look like: 1813 * 1814 * A::before() 1815 * B::before() 1816 * B::after() 1817 * A::after() 1818 */ 1819 function prependAroundFilter() 1820 { 1821 $filters = func_get_args(); 1822 foreach (array_keys($filters) as $k){ 1823 $this->_ensureFilterRespondsToBeforeAndAfter(&$filters[$k]); 1824 $this->prependBeforeFilter(array(&$filters[$k],'before')); 1825 } 1826 $filters = array_reverse($filters); 1827 foreach (array_keys($filters) as $k){ 1828 $this->appendAfterFilter(array(&$filters[$k],'after')); 1829 } 1830 } 1831 1832 /** 1833 * Short-hand for appendAroundFilter since that's the most common of the two. 1834 */ 1835 function aroundFilter() 1836 { 1837 $filters = func_get_args(); 1838 call_user_func_array(array(&$this,'appendAroundFilter'), $filters); 1839 } 1840 1841 /** 1842 * Removes the specified filters from the +before+ filter chain. 1843 * This is especially useful for managing the chain in inheritance hierarchies where only one out 1844 * of many sub-controllers need a different hierarchy. 1845 */ 1846 function skipBeforeFilter($filters) 1847 { 1848 $filters = func_get_args(); 1849 $this->_skipFilter($filters, 'before'); 1850 } 1851 1852 /** 1853 * Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference 1854 * filters, not instances. This is especially useful for managing the chain in inheritance hierarchies where only one out 1855 * of many sub-controllers need a different hierarchy. 1856 */ 1857 function skipAfterFilter($filters) 1858 { 1859 $filters = func_get_args(); 1860 $this->_skipFilter($filters, 'after'); 1861 } 1862 1863 function _skipFilter(&$filters, $type) 1864 { 1865 $_filters =& $this->{'_'.$type.'Filters'}; 1866 // array_diff doesn't play nice with some PHP5 releases when it comes to 1867 // Objects as it only diff equal references, not object types 1868 foreach (array_keys($filters) as $k){ 1869 if(AK_PHP5){ 1870 if(is_object($filters[$k])){ 1871 foreach (array_keys($_filters) as $k2){ 1872 if(is_object($_filters[$k2]) && get_class($_filters[$k2]) == get_class($filters[$k])){ 1873 $pos = $k2; 1874 break; 1875 } 1876 } 1877 }else{ 1878 $pos = array_search($filters[$k], $_filters); 1879 } 1880 1881 array_splice($_filters, $pos, 1, null); 1882 return ; 1883 } 1884 $_filters = array_diff($_filters,array($filters[$k])); 1885 } 1886 } 1887 1888 /** 1889 * Returns all the before filters for this class. 1890 */ 1891 function beforeFilters() 1892 { 1893 return $this->_beforeFilters; 1894 } 1895 1896 /** 1897 * Returns all the after filters for this class and all its ancestors. 1898 */ 1899 function afterFilters() 1900 { 1901 return $this->_afterFilters; 1902 } 1903 1904 /** 1905 * Returns a mapping between filters and the actions that may run them. 1906 */ 1907 function includedActions() 1908 { 1909 return $this->_includedActions; 1910 } 1911 1912 /** 1913 * Returns a mapping between filters and actions that may not run them. 1914 */ 1915 function excludedActions() 1916 { 1917 return $this->_excludedActions; 1918 } 1919 1920 1921 function _appendFilterToChain($condition, $filters) 1922 { 1923 $this->{"_{$condition}Filters"}[] =& $filters; 1924 } 1925 1926 function _prependFilterToChain($condition, $filters) 1927 { 1928 array_unshift($this->{"_{$condition}Filters"}, $filters); 1929 } 1930 1931 function _ensureFilterRespondsToBeforeAndAfter(&$filter_object) 1932 { 1933 if(!method_exists(&$filter_object,'before') && !method_exists(&$filter_object,'after')){ 1934 trigger_error(Ak::t('Filter object must respond to both before and after'), E_USER_ERROR); 1935 } 1936 } 1937 1938 function _extractConditions(&$filters) 1939 { 1940 if(is_array($filters) && !isset($filters[0])){ 1941 $keys = array_keys($filters); 1942 $conditions = $filters[$keys[0]]; 1943 $filters = $keys[0]; 1944 return $conditions; 1945 } 1946 } 1947 1948 function _addActionConditions($filters, $conditions) 1949 { 1950 if(!empty($conditions['only'])){ 1951 $this->_includedActions[$this->_filterId($filters)] = $this->_conditionArray($this->_includedActions, $conditions['only']); 1952 } 1953 if(!empty($conditions['except'])){ 1954 $this->_excludedActions[$this->_filterId($filters)] = $this->_conditionArray($this->_excludedActions, $conditions['except']); 1955 } 1956 } 1957 1958 function _conditionArray($actions, $filter_actions) 1959 { 1960 $filter_actions = is_array($filter_actions) ? $filter_actions : array($filter_actions); 1961 $filter_actions = array_map(array(&$this,'_filterId'),$filter_actions); 1962 return array_unique(array_merge($actions, $filter_actions)); 1963 } 1964 1965 1966 function _filterId($filters) 1967 { 1968 return is_string($filters) ? $filters : md5(serialize($filters)); 1969 } 1970 1971 function performActionWithoutFilters($action) 1972 { 1973 if(method_exists(&$this, $action)){ 1974 call_user_func_array(array(&$this, $action), @(array)$this->passed_args); 1975 } 1976 } 1977 1978 function performActionWithFilters($method = '') 1979 { 1980 if ($this->beforeAction($method) !== false && !$this->_hasPerformed()){ 1981 $this->performActionWithoutFilters($method); 1982 $this->afterAction($method); 1983 return true; 1984 } 1985 return false; 1986 } 1987 1988 function performAction($method = '') 1989 { 1990 $this->performActionWithFilters($method); 1991 } 1992 1993 1994 /** 1995 * Calls all the defined before-filter filters, which are added by using "beforeFilter($method)". 1996 * If any of the filters return false, no more filters will be executed and the action is aborted. 1997 */ 1998 function beforeAction($method = '') 1999 { 2000 Ak::profile('Running before controller action filters '.__CLASS__.'::'.__FUNCTION__.' '.__LINE__); 2001 return $this->_callFilters($this->_beforeFilters, $method); 2002 } 2003 2004 /** 2005 * Calls all the defined after-filter filters, which are added by using "afterFilter($method)". 2006 * If any of the filters return false, no more filters will be executed. 2007 */ 2008 function afterAction($method = '') 2009 { 2010 Ak::profile('Running after controller action filters '.__CLASS__.'::'.__FUNCTION__.' '.__LINE__); 2011 return $this->_callFilters(&$this->_afterFilters, $method); 2012 } 2013 2014 2015 function _callFilters(&$filters, $method = '') 2016 { 2017 $filter_result = null; 2018 foreach (array_keys($filters) as $k){ 2019 $filter =& $filters[$k]; 2020 if(!$this->_actionIsExempted($filter, $method)){ 2021 if(is_array($filter) && is_object($filter[0]) && method_exists($filter[0], $filter[1])){ 2022 $filter_result = $filter[0]->$filter[1]($this); 2023 }elseif(!is_object($filter) && method_exists($this, $filter)){ 2024 $filter_result = $this->$filter($this); 2025 }elseif(is_object($filter) && method_exists($filter, 'filter')){ 2026 $filter_result = $filter->filter($this); 2027 }else{ 2028 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); 2029 } 2030 2031 } 2032 if($filter_result === false){ 2033 !empty($this->_Logger) ? $this->_Logger->info(Ak::t('Filter chain halted as '.$filter.' returned false')) : null; 2034 return false; 2035 } 2036 } 2037 return $filter_result; 2038 } 2039 2040 2041 function _actionIsExempted($filter, $method = '') 2042 { 2043 $method_id = is_string($method) ? $method : $this->_filterId($method); 2044 $filter_id = $this->_filterId($filter); 2045 2046 if((!empty($this->_includedActions[$filter_id]) && !in_array($method_id, $this->_includedActions[$filter_id])) || 2047 (!empty($this->_excludedActions[$filter_id]) && in_array($method_id, $this->_excludedActions[$filter_id]))){ 2048 return true; 2049 } 2050 2051 return false; 2052 } 2053 2054 2055 2056 /** 2057 Flash communication between actions 2058 ==================================================================== 2059 * 2060 * The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed 2061 * to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create action 2062 * that sets <tt>flash['notice] = 'Successfully created'</tt> before redirecting to a display action that can then expose 2063 * the flash to its template. Actually, that exposure is automatically done. Example: 2064 * 2065 * class WeblogController extends ActionController 2066 * { 2067 * function create() 2068 * { 2069 * // save post 2070 * $this->flash['notice] = 'Successfully created post'; 2071 * $this->redirectTo(array('action'=>'display','params' => array('id' =>$Post->id))); 2072 * } 2073 * 2074 * function display() 2075 * { 2076 * // doesn't need to assign the flash notice to the template, that's done automatically 2077 * } 2078 * } 2079 * 2080 * display.tpl 2081 * <?php if($flash['notice']) : ?><div class='notice'><?php echo $flash['notice'] ?></div><?php endif; ?> 2082 * 2083 * 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 2084 * as you like at a time too. Just remember: They'll be gone by the time the next action has been performed. 2085 * 2086 * ==flash_now 2087 * 2088 * Sets a flash that will not be available to the next action, only to the current. 2089 * 2090 * $this->flash_now['message] = 'Hello current action'; 2091 * 2092 * This method enables you to use the flash as a central messaging system in your app. 2093 * When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>). 2094 * When you need to pass an object to the current action, you use <tt>now</tt>, and your object will 2095 * vanish when the current action is done. 2096 * 2097 * Entries set via <tt>flash_now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>. 2098 */ 2099 var $flash = array(); 2100 var $flash_now = array(); 2101 var $flash_options = array(); 2102 var $_flash_handled = false; 2103 2104 function _handleFlashAttribute() 2105 { 2106 $this->_flash_handled = true; 2107 2108 $next_flash = empty($this->flash) ? false : $this->flash; 2109 $this->flash = array(); 2110 if(isset($_SESSION['__flash'])){ 2111 $this->flash = $_SESSION['__flash']; 2112 } 2113 $_SESSION['__flash'] = $next_flash; 2114 if(!empty($this->flash_now)){ 2115 $this->flash = array_merge((array)$this->flash,(array)$this->flash_now); 2116 } 2117 $this->_handleFlashOptions(); 2118 } 2119 2120 function _handleFlashOptions() 2121 { 2122 $next_flash_options = empty($this->flash_options) ? false : $this->flash_options; 2123 $this->flash_options = array(); 2124 if(isset($_SESSION['__flash_options'])){ 2125 $this->flash_options = $_SESSION['__flash_options']; 2126 } 2127 $_SESSION['__flash_options'] = $next_flash_options; 2128 if(!empty($this->flash_now_options)){ 2129 $this->flash_options = array_merge((array)$this->flash_options,(array)$this->flash_now_options); 2130 } 2131 } 2132 2133 2134 function _mergeFlashOnFlashNow() 2135 { 2136 $this->flash_now = array_merge($this->flash_now,$this->flash); 2137 } 2138 2139 2140 /** 2141 Pagination for Active Record collections 2142 ==================================================================== 2143 * 2144 * The Pagination module aids in the process of paging large collections of 2145 * Active Record objects. It offers macro-style automatic fetching of your 2146 * model for multiple views, or explicit fetching for single actions. And if 2147 * the magic isn't flexible enough for your needs, you can create your own 2148 * paginators with a minimal amount of code. 2149 * 2150 * The Pagination module can handle as much or as little as you wish. In the 2151 * controller, have it automatically query your model for pagination; or, 2152 * if you prefer, create Paginator objects yourself 2153 * 2154 * Pagination is included automatically for all controllers. 2155 * 2156 * For help rendering pagination links, see 2157 * Helpers/PaginationHelper. 2158 * 2159 * ==== Automatic pagination for every action in a controller 2160 * 2161 * class PersonController extends ApplicationController 2162 * { 2163 * var $model = 'person'; 2164 * var $paginate = array('people'=>array('order' => 'last_name, first_name', 2165 * 'per_page' => 20)); 2166 * } 2167 * 2168 * Each action in this controller now has access to a <tt>$this->people</tt> 2169 * instance variable, which is an ordered collection of model objects for the 2170 * current page (at most 20, sorted by last name and first name), and a 2171 * <tt>$this->person_pages</tt> Paginator instance. The current page is determined 2172 * by the <tt>$params['page']</tt> variable. 2173 * 2174 * ==== Pagination for a single action 2175 * 2176 * function show_all() 2177 * { 2178 * list($this->person_pages, $this->people) = 2179 * $this->paginate('people', array('order' => 'last_name, first_name')); 2180 * } 2181 * 2182 * Like the previous example, but explicitly creates <tt>$this->person_pages</tt> 2183 * and <tt>$this->people</tt> for a single action, and uses the default of 10 items