[ Index ]

PHP Cross Reference of Akelos Framework

title

Body

[close]

/ -> AkActionController.php (source)

   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