PageRenderTime 119ms CodeModel.GetById 4ms app.highlight 93ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/AkActionController.php

https://repo.or.cz/akelos.git
PHP | 2846 lines | 1355 code | 290 blank | 1201 comment | 177 complexity | d9d05d2b37031605d95621cb9c8f3024 MD5 | raw file
   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
  11require_once(AK_LIB_DIR.DS.'AkObject.php');
  12
  13defined('AK_HIGH_LOAD_MODE') ? null : define('AK_HIGH_LOAD_MODE', false);
  14defined('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
  24class 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    /**
 132     * Old fashioned way of dispatching requests. Please use AkDispatcher or roll your own.
 133     * 
 134     * @deprecated 
 135     */
 136    function handleRequest()
 137    {
 138        AK_LOG_EVENTS && empty($this->_Logger) ? ($this->_Logger =& Ak::getLogger()) : null;
 139        AK_LOG_EVENTS && !empty($this->_Logger) ? $this->_Logger->warning('Using deprecated request dispatcher AkActionController::handleRequest. Use  to AkDispatcher + AkDispatcher::dispatch instead.') : null;
 140        require_once(AK_LIB_DIR.DS.'AkDispatcher.php');
 141        $Dispatcher =& new AkDispatcher();
 142        $Dispatcher->dispatch();
 143    }
 144
 145    function process(&$Request, &$Response)
 146    {
 147        AK_LOG_EVENTS && empty($this->_Logger) ? ($this->_Logger =& Ak::getLogger()) : null;
 148
 149        $this->Request =& $Request;
 150        $this->Response =& $Response;
 151        $this->params = $this->Request->getParams();
 152        $this->_action_name = $this->Request->getAction();
 153
 154        $this->_ensureActionExists();
 155
 156        Ak::t('Akelos'); // We need to get locales ready
 157
 158        if($this->_high_load_mode !== true){
 159            if(!empty($this->_auto_instantiate_models)){
 160                $this->instantiateIncludedModelClasses();
 161            }
 162            if(!empty($this->_enable_plugins)){
 163                $this->loadPlugins();
 164            }
 165            if(!empty($this->helpers)){
 166                $this->instantiateHelpers();
 167            }
 168        }else{
 169            $this->_enableLayoutOnRender = false;
 170        }
 171
 172        $this->_ensureProperProtocol();
 173
 174        // After filters
 175        $this->afterFilter('_handleFlashAttribute');
 176
 177        $this->_loadActionView();
 178
 179        if(isset($this->api)){
 180            require_once(AK_LIB_DIR.DS.'AkActionWebService.php');
 181            $this->aroundFilter(new AkActionWebService($this));
 182        }
 183
 184        $this->performActionWithFilters($this->_action_name);
 185
 186        if (!$this->_hasPerformed()){
 187            $this->_enableLayoutOnRender ? $this->renderWithLayout() : $this->renderWithoutLayout();
 188        }
 189
 190        if(!empty($this->validate_output)){
 191            $this->_validateGeneratedXhtml();
 192        }
 193
 194        $this->Response->outputResults();
 195    }
 196
 197    function _loadActionView()
 198    {
 199        empty($this->_assigns) ? ($this->_assigns = array()) : null;
 200        empty($this->_default_render_status_code) ? ($this->_default_render_status_code = 200) : null;
 201        $this->_enableLayoutOnRender = !isset($this->_enableLayoutOnRender) ? true : $this->_enableLayoutOnRender;
 202        $this->passed_args = !isset($this->Request->pass)? array() : $this->Request->pass;
 203        empty($this->cookies) && isset($_COOKIE) ? ($this->cookies =& $_COOKIE) : null;
 204
 205        if(empty($this->Template)){
 206            require_once(AK_LIB_DIR.DS.'AkActionView.php');
 207            require_once(AK_LIB_DIR.DS.'AkActionView'.DS.'AkPhpTemplateHandler.php');
 208            $this->Template =& new AkActionView($this->_getTemplateBasePath(),
 209            $this->Request->getParameters(),$this->Request->getController());
 210
 211            $this->Template->_controllerInstance =& $this;
 212            $this->Template->_registerTemplateHandler('tpl','AkPhpTemplateHandler');
 213        }
 214    }
 215
 216    function loadPlugins()
 217    {
 218        Ak::loadPlugins();
 219    }
 220
 221    /**
 222     * Creates an instance of each available helper and links it into into current controller.
 223     * 
 224     * Per example, if a helper TextHelper is located into the file text_helper.php. 
 225     * An instance is created on current controller
 226     * at $this->text_helper. This instance is also available on the view by calling $text_helper.
 227     * 
 228     * Helpers can be found at lib/AkActionView/helpers (this might change in a future)
 229     */
 230    function instantiateHelpers()
 231    {
 232        $helpers = $this->getDefaultHelpers();
 233        $helpers = array_merge($helpers, $this->getApplicationHelpers());
 234        $helpers = array_merge($helpers, $this->getPluginHelpers());
 235        $helpers = array_merge($helpers, $this->getModuleHelper());
 236        $helpers = array_merge($helpers, $this->getCurrentControllerHelper());
 237
 238        require_once(AK_LIB_DIR.DS.'AkActionView'.DS.'AkActionViewHelper.php');
 239
 240        $available_helpers = array();
 241        foreach ($helpers as $file=>$helper){
 242            $helper_class_name = AkInflector::camelize(AkInflector::demodulize(strstr($helper, 'Helper') ? $helper : $helper.'Helper'));
 243            $full_path = preg_match('/[\\\\\/]+/',$file);
 244            $file_path = $full_path ? $file : AK_LIB_DIR.DS.'AkActionView'.DS.'helpers'.DS.$file;
 245            include_once($file_path);
 246
 247            if(class_exists($helper_class_name)){
 248                $attribute_name = $full_path ? AkInflector::underscore($helper_class_name) : substr($file,0,-4);
 249                $available_helpers[] = $attribute_name;
 250                $this->$attribute_name =& new $helper_class_name(&$this);
 251                if(method_exists($this->$attribute_name,'setController')){
 252                    $this->$attribute_name->setController(&$this);
 253                }
 254                if(method_exists($this->$attribute_name,'init')){
 255                    $this->$attribute_name->init();
 256                }
 257            }
 258        }
 259        defined('AK_ACTION_CONTROLLER_AVAILABLE_HELPERS') ? null : define('AK_ACTION_CONTROLLER_AVAILABLE_HELPERS', join(',',$available_helpers));
 260    }
 261
 262    function getCurrentControllerHelper()
 263    {
 264        $helper = $this->getControllerName();
 265        $helper = AkInflector::is_plural($helper)?AkInflector::singularize($helper):$helper;
 266        $helper_file_name = AK_HELPERS_DIR.DS.$this->_module_path.AkInflector::underscore($helper).'_helper.php';
 267
 268        if(file_exists($helper_file_name)){
 269            return array($helper_file_name => $helper);
 270        }
 271        return array();
 272    }
 273
 274    function getModuleHelper()
 275    {
 276        $this->getControllerName(); // module name is set when we first retrieve the controller name
 277        if(!empty($this->module_name)){
 278            $helper_file_name = AK_HELPERS_DIR.DS.AkInflector::underscore($this->module_name).'_helper.php';
 279            if(file_exists($helper_file_name)){
 280                return array($helper_file_name => $this->module_name);
 281            }
 282        }
 283        return array();
 284    }
 285
 286    function getDefaultHelpers()
 287    {
 288        if($this->helpers == 'default'){
 289            $available_helpers = Ak::dir(AK_LIB_DIR.DS.'AkActionView'.DS.'helpers',array('dirs'=>false));
 290            $helper_names = array();
 291            foreach ($available_helpers as $available_helper){
 292                $helper_names[$available_helper] = AkInflector::classify(substr($available_helper,0,-10));
 293            }
 294            return $helper_names;
 295        }else{
 296            $this->helpers = Ak::toArray($this->helpers);
 297        }
 298        return $this->helpers;
 299    }
 300
 301    function getApplicationHelpers()
 302    {
 303        $helper_names = array();
 304        if ($this->app_helpers == 'all'){
 305            $available_helpers = Ak::dir(AK_HELPERS_DIR,array('dirs'=>false));
 306            $helper_names = array();
 307            foreach ($available_helpers as $available_helper){
 308                $helper_names[AK_HELPERS_DIR.DS.$available_helper] = AkInflector::classify(substr($available_helper,0,-10));
 309            }
 310
 311        } elseif (!empty($this->app_helpers)){
 312            foreach (Ak::toArray($this->app_helpers) as $helper_name){
 313                $helper_names[AK_HELPERS_DIR.DS.AkInflector::underscore($helper_name).'_helper.php'] = AkInflector::camelize($helper_name);
 314            }
 315        }
 316        return $helper_names;
 317    }
 318
 319    function getPluginHelpers()
 320    {
 321        $helper_names = AkActionController::addPluginHelper(false); // Trick for getting helper names set by AkPlugin::addHelper
 322        if(empty($helper_names)){
 323            return array();
 324        }elseif ($this->plugin_helpers == 'all'){
 325            return $helper_names;
 326        }else {
 327            $selected_helper_names = array();
 328            foreach (Ak::toArray($this->plugin_helpers) as $helper_name){
 329                $helper_name = AkInflector::camelize($helper_name);
 330                if($path = array_shift(array_keys($helper_names, AkInflector::camelize($helper_name)))){
 331                    $selected_helper_names[$path] = $helper_names[$path];
 332                }
 333            }
 334            return $selected_helper_names;
 335        }
 336    }
 337
 338    /**
 339     * Used for adding helpers to the base class like those added by the plugins engine.
 340     *
 341     * @param string $helper_name Helper class name like CalendarHelper
 342     * @param array $options - path: Path to the helper class, defaults to AK_PLUGINS_DIR/helper_name/lib/helper_name.php
 343     */
 344    function addPluginHelper($helper_name, $options = array())
 345    {
 346        static $helpers = array();
 347        if($helper_name === false){
 348            return $helpers;
 349        }
 350        $underscored_helper_name = AkInflector::underscore($helper_name);
 351        $default_options = array(
 352        'path' => AK_PLUGINS_DIR.DS.$underscored_helper_name.DS.'lib'.DS.$underscored_helper_name.'.php'
 353        );
 354        $options = array_merge($default_options, $options);
 355        $helpers[$options['path']] = $helper_name;
 356    }
 357
 358    function _validateGeneratedXhtml()
 359    {
 360        require_once(AK_LIB_DIR.DS.'AkXhtmlValidator.php');
 361        $XhtmlValidator = new AkXhtmlValidator();
 362        if($XhtmlValidator->validate($this->Response->body) === false){
 363            $this->Response->sendHeaders();
 364            echo '<h1>'.Ak::t('Ooops! There are some errors on current XHTML page').'</h1>';
 365            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";
 366            $XhtmlValidator->showErrors();
 367            echo "<hr /><h2>".Ak::t('Showing XHTML code')."</h2><hr /><div style='border:5px solid red;margin:5px;padding:15px;'>".$this->Response->body."</pre>";
 368            die();
 369        }
 370    }
 371
 372
 373    /**
 374     * Methods for loading desired models into this controller
 375     */
 376    function setModel($model)
 377    {
 378        $this->instantiateIncludedModelClasses(array($model));
 379    }
 380
 381    function setModels($models)
 382    {
 383        $this->instantiateIncludedModelClasses($models);
 384    }
 385
 386    function instantiateIncludedModelClasses($models = array())
 387    {
 388        require_once(AK_LIB_DIR.DS.'AkActiveRecord.php');
 389        require_once(AK_APP_DIR.DS.'shared_model.php');
 390
 391        empty($this->model) ? ($this->model = $this->params['controller']) : null;
 392        empty($this->models) ? ($this->models = array()) : null;
 393
 394        $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))));
 395
 396        foreach ($models as $model){
 397            $this->instantiateModelClass($model, (empty($this->finder_options[$model])?array():$this->finder_options[$model]));
 398        }
 399    }
 400
 401    function instantiateModelClass($model_class_name, $finder_options = array())
 402    {
 403        $underscored_model_class_name = AkInflector::underscore($model_class_name);
 404        $controller_name = $this->getControllerName();
 405        $id = empty($this->params[$underscored_model_class_name]['id']) ?
 406        (empty($this->params['id']) ? false :
 407        (($model_class_name == $controller_name || $model_class_name == AkInflector::singularize($controller_name)) ? $this->params['id'] : false)) :
 408        $this->params[$underscored_model_class_name]['id'];
 409
 410        if(class_exists($model_class_name)){
 411            $underscored_model_class_name = AkInflector::underscore($model_class_name);
 412
 413            if(!isset($this->$model_class_name) || !isset($this->$underscored_model_class_name)){
 414                if($finder_options !== false && is_numeric($id)){
 415                    $model =& new $model_class_name();
 416                    if(empty($finder_options)){
 417                        $model =& $model->find($id);
 418                    }else{
 419                        $model =& $model->find($id, $finder_options);
 420                    }
 421                }else{
 422                    $model =& new $model_class_name();
 423                }
 424                if(!isset($this->$model_class_name)){
 425                    $this->$model_class_name =& $model;
 426                }
 427                if(!isset($this->$underscored_model_class_name)){
 428                    $this->$underscored_model_class_name =& $model;
 429                }
 430            }
 431        }
 432    }
 433
 434
 435
 436    /**
 437                            Rendering content
 438    ====================================================================
 439    */
 440
 441    /**
 442    * Renders the content that will be returned to the browser as the Response body.
 443    * 
 444    * === Rendering an action
 445    * 
 446    * Action rendering is the most common form and the type used automatically by 
 447    * Action Controller when nothing else is specified. By default, actions are 
 448    * rendered within the current layout (if one exists).
 449    * 
 450    * * Renders the template for the action "goal" within the current controller
 451    *   
 452    *       $this->render(array('action'=>'goal'));
 453    * 
 454    * * Renders the template for the action "short_goal" within the current controller,
 455    *   but without the current active layout
 456    *   
 457    *       $this->render(array('action'=>'short_goal','layout'=>false));
 458    * 
 459    * * Renders the template for the action "long_goal" within the current controller,
 460    *   but with a custom layout
 461    *   
 462    *       $this->render(array('action'=>'long_goal','layout'=>'spectacular'));
 463    * 
 464    * === Rendering partials
 465    * 
 466    * Partial rendering is most commonly used together with Ajax calls that only update 
 467    * one or a few elements on a page without reloading. Rendering of partials from 
 468    * the controller makes it possible to use the same partial template in
 469    * both the full-page rendering (by calling it from within the template) and when 
 470    * sub-page updates happen (from the controller action responding to Ajax calls). 
 471    * By default, the current layout is not used.
 472    * 
 473    * * Renders the partial located at app/views/controller/_win.tpl
 474    * 
 475    *       $this->render(array('partial'=>'win'));
 476    * 
 477    * * Renders the partial with a status code of 500 (internal error)
 478    * 
 479    *       $this->render(array('partial'=>'broken','status'=>500));
 480    * 
 481    * * Renders the same partial but also makes a local variable available to it
 482    *   
 483    *       $this->render(array('partial' => 'win', 'locals' => array('name'=>'david')));
 484    * 
 485    * * Renders a collection of the same partial by making each element of $wins available through 
 486    *   the local variable "win" as it builds the complete Response
 487    * 
 488    *       $this->render(array('partial'=>'win','collection'=>$wins));
 489    * 
 490    * * Renders the same collection of partials, but also renders the win_divider partial in between
 491    *   each win partial.
 492    *   
 493    *       $this->render(array('partial'=>'win','collection'=>$wins,'spacer_template'=>'win_divider'));
 494    * 
 495    * === Rendering a template
 496    * 
 497    * Template rendering works just like action rendering except that it takes a 
 498    * path relative to the template root. 
 499    * The current layout is automatically applied.
 500    * 
 501    * * Renders the template located in app/views/weblog/show.tpl
 502    *   $this->render(array('template'=>'weblog/show'));
 503    * 
 504    * === Rendering a file
 505    * 
 506    * File rendering works just like action rendering except that it takes a 
 507    * filesystem path. By default, the path is assumed to be absolute, and the 
 508    * current layout is not applied.
 509    * 
 510    * * Renders the template located at the absolute filesystem path
 511    *   $this->render(array('file'=>'/path/to/some/template.tpl'));
 512    *   $this->render(array('file'=>'c:/path/to/some/template.tpl'));
 513    * 
 514    * * Renders a template within the current layout, and with a 404 status code
 515    *   $this->render(array('file' => '/path/to/some/template.tpl', 'layout' => true, 'status' => 404));
 516    *   $this->render(array('file' => 'c:/path/to/some/template.tpl', 'layout' => true, 'status' => 404));
 517    * 
 518    * * Renders a template relative to the template root and chooses the proper file extension
 519    *   $this->render(array('file' => 'some/template', 'use_full_path' => true));
 520    * 
 521    * 
 522    * === Rendering text
 523    * 
 524    * Rendering of text is usually used for tests or for rendering prepared content, 
 525    * such as a cache. By default, text
 526    * rendering is not done within the active layout.
 527    * 
 528    * * Renders the clear text "hello world" with status code 200
 529    *   $this->render(array('text' => 'hello world!'));
 530    * 
 531    * * Renders the clear text "Explosion!"  with status code 500
 532    *   $this->render(array('text' => "Explosion!", 'status' => 500 ));
 533    * 
 534    * * Renders the clear text "Hi there!" within the current active layout (if one exists)
 535    *   $this->render(array('text' => "Explosion!", 'layout' => true));
 536    * 
 537    * * Renders the clear text "Hi there!" within the layout 
 538    * * placed in "app/views/layouts/special.tpl"
 539    *   $this->render(array('text' => "Explosion!", 'layout => "special"));
 540    * 
 541    * 
 542    * === Rendering an inline template
 543    * 
 544    * Rendering of an inline template works as a cross between text and action 
 545    * rendering where the source for the template
 546    * is supplied inline, like text, but its evaled by PHP, like action. By default, 
 547    * PHP is used for rendering and the current layout is not used.
 548    * 
 549    * * Renders "hello, hello, hello, again"
 550    *   $this->render(array('inline' => "<?php echo str_repeat('hello, ', 3).'again'?>" ));
 551    * 
 552    * * Renders "hello david"
 553    *   $this->render(array('inline' => "<?php echo  'hello ' . $name ?>", 'locals' => array('name' => 'david')));
 554    * 
 555    * 
 556    * === Rendering nothing
 557    * 
 558    * Rendering nothing is often convenient in combination with Ajax calls that 
 559    * perform their effect client-side or
 560    * when you just want to communicate a status code. Due to a bug in Safari, nothing 
 561    * actually means a single space.
 562    * 
 563    * * Renders an empty Response with status code 200
 564    *   $this->render(array('nothing' => true));
 565    * 
 566    * * Renders an empty Response with status code 401 (access denied)
 567    *   $this->render(array('nothing' => true, 'status' => 401));
 568    */
 569    function render($options = null, $status = 200)
 570    {
 571        if(empty($options['partial']) && $this->_hasPerformed()){
 572            $this->_doubleRenderError(Ak::t("Can only render or redirect once per action"));
 573            return false;
 574        }
 575
 576        $this->_flash_handled ? null : $this->_handleFlashAttribute();
 577
 578        if(!is_array($options)){
 579            return $this->renderFile(empty($options) ? $this->getDefaultTemplateName() : $options, $status, true);
 580        }
 581
 582        if(!empty($options['text'])){
 583            return $this->renderText($options['text'], @$options['status']);
 584        }else{
 585
 586            if(!empty($options['file'])){
 587                return $this->renderFile($options['file'], @$options['status'], @$options['use_full_path'], @(array)$options['locals']);
 588            }elseif(!empty($options['template'])){
 589                return $this->renderFile($options['template'], @$options['status'], true);
 590            }elseif(!empty($options['inline'])){
 591                return $this->renderTemplate($options['inline'], @$options['status'], @$options['type'], @(array)$options['locals']);
 592            }elseif(!empty($options['action'])){
 593                return $this->renderAction($options['action'], @$options['status'], @$options['layout']);
 594            }elseif(!empty($options['partial'])){
 595                if($options['partial'] === true){
 596                    $options['partial'] = !empty($options['template']) ? $options['template'] : $this->getDefaultTemplateName();
 597                }
 598                if(!empty($options['collection'])){
 599                    return $this->renderPartialCollection($options['partial'], $options['collection'], @$options['spacer_template'], @$options['locals'], @$options['status']);
 600                }else{
 601                    return $this->renderPartial($options['partial'], @$options['object'], @$options['locals'], @$options['status']);
 602                }
 603            }elseif(!empty($options['nothing'])){
 604                // Safari doesn't pass the _headers of the return if the Response is zero length
 605                return $this->renderText(' ', @$options['status']);
 606            }else{
 607                return $this->renderFile($this->getDefaultTemplateName(), @$options['status'], true);
 608            }
 609            return true;
 610        }
 611    }
 612
 613    /**
 614    * Renders according to the same rules as <tt>render</tt>, but returns the result in a string instead
 615    * of sending it as the Response body to the browser.
 616    */
 617    function renderToString($options = null)
 618    {
 619        $result = $this->render($options);
 620        $this->eraseRenderResults();
 621        $this->variables_added = null;
 622        $this->Template->_assigns_added = null;
 623        return $result;
 624    }
 625
 626    function renderAction($_action_name, $status = null, $with_layout = true)
 627    {
 628        $template = $this->getDefaultTemplateName($_action_name);
 629        if(!empty($with_layout) && !$this->_isTemplateExemptFromLayout($template)){
 630            return $this->renderWithLayout($template, $status, $with_layout);
 631        }else{
 632            return $this->renderWithoutLayout($template, $status);
 633        }
 634    }
 635
 636    function renderFile($template_path, $status = null, $use_full_path = false, $locals = array())
 637    {
 638        $this->_addVariablesToAssigns();
 639        $locals = array_merge($locals,$this->_assigns);
 640
 641        if($use_full_path){
 642            $this->_assertExistanceOfTemplateFile($template_path);
 643        }
 644
 645        AK_LOG_EVENTS && !empty($this->_Logger) ? $this->_Logger->message("Rendering $this->full_template_path" . (!empty($status) ? " ($status)":'')) : null;
 646        return $this->renderText($this->Template->renderFile($template_path, $use_full_path, $locals), $status);
 647    }
 648
 649    function renderTemplate($template, $status = null, $type = 'tpl', $local_assigns = array())
 650    {
 651        $this->_addVariablesToAssigns();
 652        $local_assigns = array_merge($local_assigns,$this->_assigns);
 653        return $this->renderText($this->Template->renderTemplate($type, $template, null, $local_assigns), $status);
 654    }
 655
 656    function renderText($text = null, $status = null)
 657    {
 658        $this->performed_render = true;
 659        $this->Response->_headers['Status'] = !empty($status) ? $status : $this->_default_render_status_code;
 660        $this->Response->body = $text;
 661        return $text;
 662    }
 663
 664    function renderNothing($status = null)
 665    {
 666        return $this->renderText(' ', $status);
 667    }
 668
 669    function renderPartial($partial_path = null, $object = null, $local_assigns = null, $status = null)
 670    {
 671        $partial_path = empty($partial_path) ? $this->getDefaultTemplateName() : $partial_path;
 672        $this->variables_added = false;
 673        $this->performed_render = false;
 674        $this->_addVariablesToAssigns();
 675        $this->Template->controller =& $this;
 676        $this->$partial_path = $this->renderText($this->Template->renderPartial($partial_path, $object, array_merge($this->_assigns, (array)$local_assigns)), $status);
 677        return $this->$partial_path;
 678    }
 679
 680    function renderPartialCollection($partial_name, $collection, $partial_spacer_template = null, $local_assigns = null, $status = null)
 681    {
 682        $this->_addVariablesToAssigns();
 683        $collection_name = AkInflector::pluralize($partial_name).'_collection';
 684        $result = $this->Template->renderPartialCollection($partial_name, $collection, $partial_spacer_template, $local_assigns);
 685        if(empty($this->$collection_name)){
 686            $this->$collection_name = $result;
 687        }
 688        $this->variables_added = false;
 689        $this->performed_render = false;
 690
 691        return $result;
 692    }
 693
 694    function renderWithLayout($template_name = null, $status = null, $layout = null)
 695    {
 696        $template_name = empty($template_name) ? $this->getDefaultTemplateName() : $template_name;
 697        return $this->renderWithALayout($template_name, $status, $layout);
 698    }
 699
 700    function renderWithoutLayout($template_name = null, $status = null)
 701    {
 702        $template_name = empty($template_name) ? $this->getDefaultTemplateName() : $template_name;
 703        return $this->render($template_name, $status);
 704    }
 705
 706    /**
 707    * Clears the rendered results, allowing for another render to be performed.
 708    */
 709    function eraseRenderResults()
 710    {
 711        $this->Response->body = '';
 712        $this->performed_render = false;
 713        $this->variables_added = false;
 714    }
 715
 716    function _addVariablesToAssigns()
 717    {
 718        if(empty($this->variables_added)){
 719            $this->_addInstanceVariablesToAssigns();
 720            $this->variables_added = true;
 721        }
 722    }
 723
 724    function _addInstanceVariablesToAssigns()
 725    {
 726        $this->_protected_variables_cache = array_merge($this->_protected_variables_cache, $this->_getProtectedInstanceVariables());
 727
 728        foreach (array_diff(array_keys(get_object_vars($this)), $this->_protected_variables_cache) as $attribute){
 729            if($attribute[0] != '_'){
 730                $this->_assigns[$attribute] =& $this->$attribute;
 731            }
 732        }
 733    }
 734
 735    function _getProtectedInstanceVariables()
 736    {
 737        return !empty($this->_view_controller_internals) ?
 738        array('_assigns', 'performed_redirect', 'performed_render','db') :
 739        array('_assigns', 'performed_redirect', 'performed_render', 'session', 'cookies',
 740        'Template','db','helpers','models','layout','Response','Request',
 741        'params','passed_args');
 742    }
 743
 744
 745    /**
 746     * Use this to translate strings in the scope of your controller
 747     * 
 748     * @see Ak::t
 749     */
 750    function t($string, $array = null)
 751    {
 752        return Ak::t($string, $array, AkInflector::underscore($this->getControllerName()));
 753    }
 754
 755
 756
 757    /**
 758                            Redirects
 759    ====================================================================
 760    */
 761
 762    /**
 763    * Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
 764    * 
 765    * * <tt>Array</tt>: The URL will be generated by calling $this->UrlFor with the +options+.
 766    * * <tt>String starting with protocol:// (like http://)</tt>: Is passed straight through 
 767    * as the target for redirection.
 768    * * <tt>String not containing a protocol</tt>: The current protocol and host is prepended to the string.
 769    * * <tt>back</tt>: Back to the page that issued the Request-> Useful for forms that are 
 770    * triggered from multiple places.
 771    *   Short-hand for redirectTo(Request->env["HTTP_REFERER"])
 772    * 
 773    * Examples:
 774    *   redirectTo(array('action' => 'show', 'id' => 5));
 775    *   redirectTo('http://www.akelos.com');
 776    *   redirectTo('/images/screenshot.jpg');
 777    *   redirectTo('back');
 778    * 
 779    * The redirection happens as a "302 Moved" header.
 780    */
 781    function redirectTo($options = array(), $parameters_for_method_reference = null)
 782    {
 783        if(is_string($options)) {
 784            if(preg_match('/^\w+:\/\/.*/',$options)){
 785                if($this->_hasPerformed()){
 786                    $this->_doubleRenderError();
 787                }
 788                AK_LOG_EVENTS && !empty($this->_Logger) ? $this->_Logger->message('Redirected to '.$options) : null;
 789                $this->_handleFlashAttribute();
 790                $this->Response->redirect($options);
 791                $this->Response->redirected_to = $options;
 792                $this->performed_redirect = true;
 793            }elseif ($options == 'back'){
 794                $this->redirectTo($this->Request->env['HTTP_REFERER']);
 795            }else{
 796                $this->redirectTo($this->Request->getProtocol(). $this->Request->getHostWithPort(). $options);
 797            }
 798        }else{
 799            if(empty($parameters_for_method_reference)){
 800                $this->redirectTo($this->UrlFor($options));
 801                $this->Response->redirected_to = $options;
 802            }else{
 803                $this->redirectTo($this->UrlFor($options, $parameters_for_method_reference));
 804                $this->Response->redirected_to = $options;
 805                $this->Response->redirected_to_method_params = $parameters_for_method_reference;
 806            }
 807        }
 808    }
 809
 810    function redirectToAction($action, $options = array())
 811    {
 812        $this->redirectTo(array_merge(array('action'=>$action), $options));
 813    }
 814
 815
 816    /**
 817     * This methods are required for retrieving available controllers for URL Routing
 818     */
 819    function rewriteOptions($options)
 820    {
 821        $defaults = $this->defaultUrlOptions($options);
 822        if(!empty($this->module_name)){
 823            $defaults['module'] = $this->getModuleName();
 824        }
 825        if(!empty($options['controller']) && strstr($options['controller'], '/')){
 826            $defaults['module'] = substr($options['controller'], 0, strrpos($options['controller'], '/'));
 827            $options['controller'] = substr($options['controller'], strrpos($options['controller'], '/') + 1);
 828        }
 829        $options = !empty($defaults) ? array_merge($defaults, $options) : $options;
 830        $options['controller'] = empty($options['controller']) ? AkInflector::underscore($this->getControllerName()) : $options['controller'];
 831        return $options;
 832    }
 833
 834    function getControllerName()
 835    {
 836        if(!isset($this->controller_name)){
 837            $current_class_name = str_replace('_', '::', get_class($this));
 838            if (!AK_PHP5){
 839                $current_class_name = $this->__getControllerName_PHP4_fix($current_class_name);
 840            }
 841            $controller_name = substr($current_class_name,0,-10);
 842            $this->controller_name = $this->_removeModuleNameFromControllerName($controller_name);
 843        }
 844        return $this->controller_name;
 845    }
 846
 847    function __getControllerName_PHP4_fix($class_name)
 848    {
 849        $included_controllers = $this->_getIncludedControllerNames();
 850        $lowercase_included_controllers = array_map('strtolower', $included_controllers);
 851        $key = array_search(strtolower($class_name), $lowercase_included_controllers, true);
 852        return $included_controllers[$key];
 853    }
 854
 855    function getModuleName()
 856    {
 857        return $this->module_name;
 858    }
 859
 860    function setModuleName($module_name)
 861    {
 862        return $this->module_name = $module_name;
 863    }
 864
 865    /**
 866     * Removes the modules name from the controller if exists and sets it.
 867     *
 868     * @return $controller_name 
 869     */
 870    function _removeModuleNameFromControllerName($controller_name)
 871    {
 872        if(strstr($controller_name, '::')){
 873            $module_parts = explode ('::',$controller_name);
 874            $controller_name = array_pop($module_parts);
 875            $this->setModuleName(join('/', array_map(array('AkInflector','underscore'), $module_parts)));
 876        }
 877        return $controller_name;
 878    }
 879
 880    function _getTemplateBasePath()
 881    {
 882        return AK_APP_DIR.DS.'views'.DS.(empty($this->_module_path)?'':$this->_module_path).$this->Request->getController();
 883    }
 884
 885    function _getIncludedControllerNames()
 886    {
 887        $controllers = array();
 888        foreach (get_included_files() as $file_name){
 889            if(strstr($file_name,AK_CONTROLLERS_DIR)){
 890                $controllers[] = AkInflector::classify(str_replace(array(AK_CONTROLLERS_DIR.DS,'.php', DS, '//'),array('','','/', '/'),$file_name));
 891            }
 892        }
 893        return $controllers;
 894    }
 895
 896
 897
 898
 899    /**
 900                            URL generation/rewriting 
 901    ====================================================================
 902    */
 903
 904
 905    /**
 906    * Overwrite to implement a number of default options that all urlFor-based methods will use. 
 907    * The default options should come in
 908    * the form of a  an array, just like the one you would use for $this->UrlFor directly. Example:
 909    * 
 910    * function defaultUrlOptions($options)
 911    * {
 912    *     return array('project' => ($this->Project->isActive() ? $this->Project->url_name : 'unknown'));
 913    *  }
 914    * 
 915    * As you can infer from the example, this is mostly useful for situations where you want to 
 916    * centralize dynamic decisions about the urls as they stem from the business domain. 
 917    * Please note that any individual $this->UrlFor call can always override the defaults set
 918    * by this method.
 919    */
 920    function defaultUrlOptions($options)
 921    {
 922    }
 923
 924
 925    /**
 926    * Returns a URL that has been rewritten according to the options array and the defined Routes. 
 927    * (For doing a complete redirect, use redirectTo).
 928    * 
 929    * <tt>$this->UrlFor</tt> is used to:
 930    * 
 931    * All keys given to $this->UrlFor are forwarded to the Route module, save for the following:
 932    * * <tt>anchor</tt> -- specifies the anchor name to be appended to the path. For example, 
 933    *   <tt>$this->UrlFor(array('controller' => 'posts', 'action' => 'show', 'id' => 10, 'anchor' => 'comments'</tt> 
 934    *   will produce "/posts/show/10#comments".
 935    * * <tt>only_path</tt> --  if true, returns the absolute URL (omitting the protocol, host name, and port)
 936    * * <tt>trailing_slash</tt> --  if true, adds a trailing slash, as in "/archive/2005/". Note that this
 937    *   is currently not recommended since it breaks caching.
 938    * * <tt>host</tt> -- overrides the default (current) host if provided
 939    * * <tt>protocol</tt> -- overrides the default (current) protocol if provided
 940    * 
 941    * The URL is generated from the remaining keys in the array. A URL contains two key parts: the <base> and a query string.
 942    * Routes composes a query string as the key/value pairs not included in the <base>.
 943    * 
 944    * The default Routes setup supports a typical Akelos Framework path of "controller/action/id" 
 945    * where action and id are optional, with
 946    * action defaulting to 'index' when not given. Here are some typical $this->UrlFor statements 
 947    * and their corresponding URLs:
 948    * 
 949    *   $this->UrlFor(array('controller'=>'posts','action'=>'recent')); //  'proto://host.com/posts/recent'
 950    *   $this->UrlFor(array('controller'=>'posts','action'=>'index')); // 'proto://host.com/posts'
 951    *   $this->UrlFor(array('controller'=>'posts','action'=>'show','id'=>10)); // 'proto://host.com/posts/show/10'
 952    * 
 953    * When generating a new URL, missing values may be filled in from the current 
 954    * Request's parameters. For example,
 955    * <tt>$this->UrlFor(array('action'=>'some_action'));</tt> will retain the current controller, 
 956    * as expected. This behavior extends to other parameters, including <tt>controller</tt>, 
 957    * <tt>id</tt>, and any other parameters that are placed into a Route's path.
 958    * 
 959    * The URL helpers such as <tt>$this->UrlFor</tt> have a limited form of memory: 
 960    * when generating a new URL, they can look for missing values in the current Request's parameters. 
 961    * Routes attempts to guess when a value should and should not be
 962    * taken from the defaults. There are a few simple rules on how this is performed:
 963    * 
 964    * * If the controller name begins with a slash, no defaults are used: <tt>$this->UrlFor(array('controller'=>'/home'));</tt>
 965    * * If the controller changes, the action will default to index unless provided
 966    * 
 967    * The final rule is applied while the URL is being generated and is best illustrated by an example. Let us consider the
 968    * route given by <tt>map->connect('people/:last/:first/:action', array('action' => 'bio', 'controller' => 'people'))</tt>.
 969    * 
 970    * Suppose that the current URL is "people/hh/david/contacts". Let's consider a few 
 971    * different cases of URLs which are generated from this page.
 972    * 
 973    * * <tt>$this->UrlFor(array('action'=>'bio'));</tt> -- During the generation of this URL, 
 974    * default values will be used for the first and
 975    * last components, and the action shall change. The generated URL will be, "people/hh/david/bio".
 976    * * <tt>$this->UrlFor(array('first'=>'davids-little-brother'));</tt> This 
 977    * generates the URL 'people/hh/davids-little-brother' -- note
 978    *   that this URL leaves out the assumed action of 'bio'.
 979    * 
 980    * However, you might ask why the action from the current Request, 'contacts', isn't 
 981    * carried over into the new URL. The answer has to do with the order in which 
 982    * the parameters appear in the generated path. In a nutshell, since the
 983    * value that appears in the slot for <tt>first</tt> is not equal to default value 
 984    * for <tt>first</tt> we stop using defaults. On it's own, this rule can account 
 985    * for much of the typical Akelos Framework URL behavior.
 986    * 
 987    * Although a convienence, defaults can occasionaly get in your way. In some cases 
 988    * a default persists longer than desired.
 989    * The default may be cleared by adding <tt>'name' => null</tt> to <tt>$this->UrlFor</tt>'s options.
 990    * This is often required when writing form helpers, since the defaults in play 
 991    * may vary greatly depending upon where the helper is used from. The following line 
 992    * will redirect to PostController's default action, regardless of the page it is
 993    * displayed on:
 994    * 
 995    *   $this->UrlFor(array('controller' => 'posts', 'action' => null));
 996    *      
 997    * If you explicitly want to create a URL that's almost the same as the current URL, you can do so using the
 998    * overwrite_params options. Say for your posts you have different views for showing and printing them.
 999    * Then, in the show view, you get the URL for the print view like this
1000    * 
1001    *   $this->UrlFor(array('overwrite_params' => array('action' => 'print')));
1002    * 
1003    * This takes the current URL as is and only exchanges the action. In contrast, 
1004    * <tt>$this->UrlFor(array('action'=>'print'));</tt>
1005    * would have slashed-off the path components after the changed action.
1006    */
1007    function urlFor($options = array(), $parameters_for_method_reference = null)
1008    {
1009        return $this->rewrite($this->rewriteOptions($options));
1010    }
1011
1012    function addToUrl($options = array(), $options_to_exclude = array())
1013    {
1014        $options_to_exclude = array_merge(array('ak','lang',AK_SESSION_NAME,'AK_SESSID','PHPSESSID'), $options_to_exclude);
1015        $options = array_merge(array_merge(array('action'=>$this->Request->getAction()),$this->params),$options);
1016        foreach ($options_to_exclude as $option_to_exclude){
1017            unset($options[$option_to_exclude]);
1018        }
1019        return $this->urlFor($options);
1020    }
1021
1022    function getActionName()
1023    {
1024        return $this->Request->getAction();
1025    }
1026
1027
1028    function _doubleRenderError($message = null)
1029    {
1030        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);
1031    }
1032
1033    function _hasPerformed()
1034    {
1035        return !empty($this->performed_render) || !empty($this->performed_redirect);
1036    }
1037
1038    function _getRequestOrigin()
1039    {
1040        return $this->Request->remote_ip.' at '.Ak::getDate();
1041    }
1042
1043    function _getCompleteRequestUri()
1044    {
1045        return $this->Request->protocol . $this->Request->host . $this->Request->request_uri;
1046    }
1047
1048    function _closeSession()
1049    {
1050        !empty($this->session) ? session_write_close() : null;
1051    }
1052
1053
1054    function _hasTemplate($template_name = null)
1055    {
1056        return file_exists(empty($template_name) ? $this->getDefaultTemplateName() : $template_name);
1057    }
1058
1059    function _templateIsPublic($template_name = null)
1060    {
1061        $template_name = empty($template_name) ? $this->getDefaultTemplateName() : $template_name;
1062        return $this->Template->fileIsPublic($template_name);
1063    }
1064
1065    function _isTemplateExemptFromLayout($template_name = null)
1066    {
1067        $template_name = empty($template_name) ? $this->getDefaultTemplateName() : $template_name;
1068        return $this->Template->_javascriptTemplateExists($template_name);
1069    }
1070
1071    function _assertExistanceOfTemplateFile($template_name)
1072    {
1073        $extension = $this->Template->delegateTemplateExists($template_name);
1074        $this->full_template_path = $this->Template->getFullTemplatePath($template_name, $extension ? $extension : 'tpl');
1075        if(!$this->_hasTemplate($this->full_template_path)){
1076            if(!empty($this->_ignore_missing_templates) && $this->_ignore_missing_templates === true){
1077                return;
1078            }
1079            $template_type = strstr($template_name,'layouts') ? 'layout' : 'template';
1080            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);
1081        }
1082    }
1083
1084    function getDefaultTemplateName($default_action_name = null)
1085    {
1086        return empty($default_action_name) ? (empty($this->_default_template_name) ? $this->_action_name : $this->_default_template_name) : $default_action_name;
1087    }
1088
1089    function setDefaultTemplateName($template_name)
1090    {
1091        $this->_default_template_name = $template_name;
1092    }
1093
1094
1095
1096    function rewrite($options = array())
1097    {
1098        return $this->_rewriteUrl($this->_rewritePath($options), $options);
1099    }
1100
1101
1102    function toString()
1103    {
1104        return $this->Request->getProtocol().$this->Request->getHostWithPort().
1105        $this->Request->getPath().@$this->parameters['controller'].
1106        @$this->parameters['action'].@$this->parameters['inspect'];
1107    }
1108
1109    /**
1110     * Given a path and options, returns a rewritten URL string
1111     */
1112    function _rewriteUrl($path, $options)
1113    {
1114        $rewritten_url = '';
1115        if(empty($options['only_path'])){
1116            $rewritten_url .= !empty($options['protocol']) ? $options['protocol'] : $this->Request->getProtocol();
1117            $rewritten_url .= empty($rewritten_url) || strpos($rewritten_url,'://') ? '' : '://';
1118            $rewritten_url .= $this->_rewriteAuthentication($options);
1119            $rewritten_url .= !empty($options['host']) ? $options['host'] : $this->Request->getHostWithPort();
1120            $options = Ak::delete($options, array('user','password','host','protocol'));
1121        }
1122
1123        $rewritten_url .= empty($options['skip_relative_url_root']) ? $this->Request->getRelativeUrlRoot() : '';
1124
1125        if(empty($options['skip_url_locale'])){
1126            $locale = $this->Request->getLocaleFromUrl();
1127            if(empty($options['lang'])){
1128                $rewritten_url .= (empty($locale) ? '' : '/').$locale;
1129            }
1130
1131        }
1132
1133        $rewritten_url .= (substr($rewritten_url,-1) == '/' ? '' : (AK_URL_REWRITE_ENABLED ? '' : (!empty($path[0]) && $path[0] != '/' ? '/' : '')));
1134        $rewritten_url .= $path;
1135        $rewritten_url .= empty($options['trailing_slash']) ? '' : '/';
1136        $rewritten_url .= empty($options['anchor']) ? '' : '#'.$options['anchor'];
1137
1138        return $rewritten_url;
1139    }
1140
1141    function _rewriteAuthentication($options)
1142    {
1143        if(!isset($options['user']) && isset($options['password'])){
1144            return urlencode($options['user']).':'.urlencode($options['password']).'@';
1145        }else{
1146            return '';
1147        }
1148    }
1149
1150    function _rewritePath($options)
1151    {
1152        if(!empty($options['params'])){
1153            foreach ($options['params'] as $k=>$v){
1154                $options[$k] = $v;
1155            }
1156            unset($options['params']);
1157        }
1158        if(!empty($options['overwrite_params'])){
1159            foreach ($options['overwrite_params'] as $k=>$v){
1160                $options[$k] = $v;
1161            }
1162            unset($options['overwrite_params']);
1163        }
1164        foreach (array('anchor', 'params', 'only_path', 'host', 'protocol', 'trailing_slash', 'skip_relative_url_root') as $k){
1165            unset($options[$k]);
1166        }
1167        $path = Ak::toUrl($options);
1168        return $path;
1169    }
1170
1171    /**
1172      * Returns a query string with escaped keys and values from the passed array. If the passed 
1173      * array contains an 'id' it'll
1174      * be added as a path element instead of a regular parameter pair.
1175      */
1176    function buildQueryString($array, $only_keys = null)
1177    {
1178        $array = !empty($only_keys) ? array_keys($array) : $array;
1179        return Ak::toUrl($array);
1180    }
1181
1182
1183
1184
1185    /**
1186                            Layouts
1187    ====================================================================
1188    *
1189    * Layouts reverse the common pattern of including shared headers and footers in many templates 
1190    * to isolate changes in repeated setups. The inclusion pattern has pages that look like this:
1191    *
1192    *   <?php echo  $controller->render('shared/header') ?>
1193    *   Hello World
1194    *   <?php echo  $controller->render('shared/footer') ?>
1195    *
1196    * This approach is a decent way of keeping common structures isolated from the 
1197    * changing content, but it's verbose and if( you ever want to change the structure 
1198    * of these two includes, you'll have to change all the templates.
1199    *
1200    * With layouts, you can flip it around and have the common structure know where 
1201    * to insert changing content. This means that the header and footer are only 
1202    * mentioned in one place, like this:
1203    *
1204    *   <!-- The header part of this layout -->
1205    *   <?php echo  $content_for_layout ?>
1206    *   <!-- The footer part of this layout -->
1207    *
1208    * And then you have content pages that look like this:
1209    *
1210    *    hello world
1211    *
1212    * Not a word about common structures. At rendering time, the content page is 
1213    * computed and then inserted in the layout, 
1214    * like this:
1215    *
1216    *   <!-- The header part of this layout -->
1217    *   hello world
1218    *   <!-- The footer part of this layout -->
1219    *
1220    * == Accessing shared variables
1221    *
1222    * Layouts have access to variables specified in the content pages and vice versa. 
1223    * This allows you to have layouts with references that won't materialize before 
1224    * rendering time:
1225    *
1226    *   <h1><?php echo  $page_title ?></h1>
1227    *   <?php echo  $content_for_layout ?>
1228    *
1229    * ...and content pages that fulfill these references _at_ rendering time:
1230    *
1231    *    <?php $page_title = 'Welcome'; ?>
1232    *    Off-world colonies offers you a chance to start a new life
1233    *
1234    * The result after rendering is:
1235    *
1236    *   <h1>Welcome</h1>
1237    *   Off-world colonies offers you a chance to start a new life
1238    *
1239    * == Automatic layout assignment
1240    *
1241    * If there is a template in <tt>app/views/layouts/</tt> with the same name as 
1242    * the current controller then it will be automatically
1243    * set as that controller's layout unless explicitly told otherwise. Say you have 
1244    * a WeblogController, for example. If a template named <tt>app/views/layouts/weblog.tpl</tt> 
1245    * exists then it will be automatically set as the layout for your WeblogController. 
1246    * You can create a layout with the name <tt>application.tpl</tt> 
1247    * and this will be set as the default controller if there is no layout with 
1248    * the same name as the current controller and there is no layout explicitly 
1249    * assigned on the +layout+ attribute. Setting a layout explicitly will always 
1250    * override the automatic behaviour
1251    * for the controller where the layout is set. Explicitly setting the layout 
1252    * in a parent class, though, will not override the 
1253    * child class's layout assignement if the child class has a layout with the same name. 
1254    *
1255    * == Inheritance for layouts
1256    *
1257    * Layouts are shared downwards in the inheritance hierarchy, but not upwards. Examples:
1258    *
1259    *   class BankController extends AkActionController
1260    *   {
1261    *     var $layout = 'bank_standard';
1262    *   }
1263    *
1264    *   class InformationController extends BankController
1265    *   {
1266    *   }
1267    *
1268    *   class VaultController extends BankController
1269    *   {
1270    *      var $layout  = 'access_level_layout';
1271    *   }
1272    *
1273    *   class EmployeeController extends BankController
1274    *   {
1275    *       var $layout = null;
1276    *   }
1277    *
1278    * The InformationController uses 'bank_standard' inherited from the BankController, the VaultController 
1279    * and picks the layout 'access_level_layout', and the EmployeeController doesn't want to use a layout at all.
1280    *
1281    * == Types of layouts
1282    *
1283    * Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
1284    * you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
1285    * be done either by an inline method.
1286    *
1287    * The method reference is the preferred approach to variable layouts and is used like this:
1288    *
1289    *   class WeblogController extends AkActionController
1290    *   {
1291    *       function __construct()
1292    *       {
1293    *           $this->setLayout(array(&$this, '_writersAndReaders'));
1294    *       }
1295    *
1296    *       function index()
1297    *       {
1298    *           // fetching posts
1299    *       }
1300    * 
1301    *       function _writersAndReaders()
1302    *       {
1303    *           return is_logged_in() ? 'writer_layout' : 'reader_layout';
1304    *       }
1305    *   }
1306    *
1307    * Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing 
1308    * is logged in or not.
1309    *
1310    * The most common way of specifying a layout is still just as a plain template name:
1311    *
1312    *   class WeblogController extends AkActionController
1313    *   {
1314    *      var $layout = 'weblog_standard';
1315    *   }
1316    *
1317    * If no directory is specified for the template name, the template will by default by looked for in +app/views/layouts/+.
1318    *
1319    * == Conditional layouts
1320    *
1321    * If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
1322    * 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 
1323    * <tt>only</tt> and <tt>except</tt> options can be passed to the layout call. For example:
1324    *
1325    *   class WeblogController extends AkActionController
1326    *   {
1327    *       function __construct()
1328    *       {
1329    *           $this->setLayout('weblog_standard', array('except' => 'rss'));
1330    *       }
1331    * 
1332    *     // ...
1333    *
1334    *   }
1335    *
1336    * This will assign 'weblog_standard' as the WeblogController's layout  except for the +rss+ action, which will not wrap a layout 
1337    * around the rendered view.
1338    *
1339    * Both the <tt>only</tt> and <tt>except</tt> condition can accept an arbitrary number of method names, so 
1340    * <tt>'except' => array('rss', 'text_only')</tt> is valid, as is <tt>'except' => 'rss'</tt>.
1341    *
1342    * == Using a different layout in the action render call
1343    * 
1344    * If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
1345    * Some times you'll have exceptions, though, where one action wants to use a different layout than the rest of the controller.
1346    * This is possible using the <tt>render</tt> method. It's just a bit more manual work as you'll have to supply fully
1347    * qualified template and layout names as this example shows:
1348    *
1349    *   class WeblogController extends AkActionController
1350    *   {
1351    *       function help()
1352    *       {
1353    *           $this->render(array('action'=>'help/index','layout'=>'help'));
1354    *       }
1355    *   }
1356    */
1357
1358    /**
1359    * If a layout is specified, all actions rendered through render and render_action will have their result assigned 
1360    * to <tt>$this->content_for_layout</tt>, which can then be used by the layout to insert their contents with
1361    * <tt><?php echo  $$this->content_for_layout ?></tt>. This layout can itself depend on instance variables assigned during action
1362    * performance and have access to them as any normal template would.
1363    */
1364    function setLayout($template_name, $conditions = array())
1365    {
1366        $this->_addLayoutConditions($conditions);
1367        $this->layout = $template_name;
1368    }
1369
1370    function getLayoutConditions()
1371    {
1372        return empty($this->_layout_conditions) ? array() : $this->_layout_conditions;
1373    }
1374
1375    function _addLayoutConditions($conditions)
1376    {
1377        $this->_layout_conditions = $conditions;
1378    }
1379
1380
1381
1382    /**
1383    * Returns the name of the active layout. If the layout was specified as a method reference, this method
1384    * is called and the return value is used. Likewise if( the layout was specified as an inline method (through a method
1385    * object). If the layout was defined without a directory, layouts is assumed. So <tt>setLayout('weblog/standard')</tt> will return
1386    * weblog/standard, but <tt>setLayout('standard')</tt> will return layouts/standard.
1387    */
1388    function getActiveLayout($passed_layout = null)
1389    {
1390        if(empty($passed_layout)){
1391            $layout = !isset($this->layout) ? AkInflector::underscore($this->getControllerName()) : $this->layout;
1392        }else{
1393            $layout =& $passed_layout;
1394        }
1395        if(is_array($layout) &&  is_object($layout[0]) && method_exists($layout[0], $layout[1])){
1396            $this->active_layout = $layout[0]->{$layout[1]}();
1397        }elseif(method_exists($this,$layout) &&  strtolower(get_class($this)) !== strtolower($layout)){
1398            $this->active_layout = $this->$layout();
1399        }else{
1400            $this->active_layout = $layout;
1401        }
1402
1403        if(!empty($this->active_layout)){
1404            return strstr($this->active_layout,DS) ? $this->active_layout : 'layouts'.DS.$this->active_layout;
1405        }
1406    }
1407
1408
1409    function renderWithALayout($options = null, $status = null, $layout = null)
1410    {
1411        $template_with_options = !empty($options)  && is_array($options);
1412
1413        if($this->_canApplyLayout($template_with_options, $options) && ($layout = $this->_pickLayout($template_with_options, $options, $layout))){
1414
1415            $options = $template_with_options? array_merge((array)$options,array('layout'=>false)) : $options;
1416
1417            $this->content_for_layout = $this->render($options, $status);
1418
1419            if($template_with_options){
1420                $status = empty($options['status']) ? $status : $options['status'];
1421            }
1422
1423            $this->eraseRenderResults();
1424            $this->_addVariablesToAssigns();
1425
1426            return $this->renderText($this->Template->renderFile($layout, true, &$this->_assigns), $status);
1427        }else{
1428            return $this->render($options, $status, &$this->_assigns);
1429        }
1430    }
1431
1432    function _canApplyLayout($template_with_options, $options)
1433    {
1434        return !empty($template_with_options) ?  $this->_isCandidateForLayout($options) : !$this->_isTemplateExemptFromLayout();
1435    }
1436
1437    function _isCandidateForLayout($options)
1438    {
1439        return !empty($options['layout']) ||
1440        (empty($options['text']) && empty($options['file']) && empty($options['inline']) && empty($options['partial']) && empty($options['nothing'])) &&
1441        !$this->_isTemplateExemptFromLayout($this->_getDefaultTemplateName(empty($options['action']) ? $options['template'] : $options['action']));
1442    }
1443
1444    function _pickLayout($template_with_options, $options, $layout = null)
1445    {
1446        if(!empty($template_with_options)){
1447            $layout = empty($options['layout']) ? ($this->_doesActionHasLayout() ? $this->getActiveLayout(): false) : $this->getActiveLayout($options['layout']);
1448        }elseif(empty($layout) || $layout === true){
1449            $layout = $this->_doesActionHasLayout() ? $this->getActiveLayout() : false;
1450        }
1451        if(!empty($layout)){
1452
1453            $layout = strstr($layout,'/') || strstr($layout,DS) ? $layout : 'layouts'.DS.$layout;
1454            $layout = preg_replace('/\.tpl$/', '', $layout);
1455
1456            $layout = substr($layout,0,7) === 'layouts' ?
1457            (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') :
1458            $layout.'.tpl';
1459
1460            if (file_exists($layout)) {
1461                return $layout;
1462            }
1463            $layout = null;
1464        }
1465        if(empty($layout) && $layout !== false && defined('AK_DEFAULT_LAYOUT')){
1466            $layout = AK_VIEWS_DIR.DS.'layouts'.DS.AK_DEFAULT_LAYOUT.'.tpl';
1467        }
1468        return file_exists($layout) ? $layout : false;
1469    }
1470
1471    function _doesActionHasLayout()
1472    {
1473        $conditions = $this->getLayoutConditions();
1474
1475        $action_name = $this->Request->getAction();
1476        if(!empty($conditions['only']) && ((is_array($conditions['only']) && in_array($action_name,$conditions['only'])) ||
1477        (is_string($conditions['only']) && $action_name == $conditions['only']))){
1478            return true;
1479        }elseif (!empty($conditions['only'])){
1480            return false;
1481        }
1482        if(!empty($conditions['except']) && ((is_array($conditions['except']) && in_array($action_name,$conditions['except'])) ||
1483        (is_string($conditions['except']) && $action_name == $conditions['except']))){
1484            return false;
1485        }
1486
1487        return true;
1488    }
1489
1490
1491
1492
1493    /**
1494                        Filters
1495    ====================================================================
1496    *
1497    * Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do 
1498    * authentication, caching, or auditing before the intended action is performed. Or to do localization or output 
1499    * compression after the action has been performed.
1500    * 
1501    * Filters have access to the request, response, and all the instance variables set by other filters in the chain
1502    * or by the action (in the case of after filters). Additionally, it's possible for a pre-processing <tt>beforeFilter</tt>
1503    * to halt the processing before the intended action is processed by returning false or performing a redirect or render. 
1504    * This is especially useful for filters like authentication where you're not interested in allowing the action to be 
1505    * performed if the proper credentials are not in order.
1506    * 
1507    * == Filter inheritance
1508    * 
1509    * Controller inheritance hierarchies share filters downwards, but subclasses can also add new filters without
1510    * affecting the superclass. For example:
1511    * 
1512    *   class BankController extends AkActionController 
1513    *   {
1514    *       function __construct()
1515    *       {
1516    *           $this->beforeFilter('_audit');
1517    *       }
1518    *       
1519    *       function _audit(&$controller)
1520    *       {
1521    *           // record the action and parameters in an audit log  
1522    *       }
1523    *   }
1524    * 
1525    *   class VaultController extends BankController 
1526    *   {
1527    *       function __construct()
1528    *       {
1529    *           $this->beforeFilter('_verifyCredentials');
1530    *       }
1531    *       
1532    *       function _verifyCredentials(&$controller)
1533    *       {
1534    *           // make sure the user is allowed into the vault
1535    *       }
1536    *   }
1537    * 
1538    * Now any actions performed on the BankController will have the audit method called before. On the VaultController,
1539    * first the audit method is called, then the _verifyCredentials method. If the _audit method returns false, then 
1540    * _verifyCredentials and the intended action are never called.
1541    * 
1542    * == Filter types
1543    * 
1544    * A filter can take one of three forms: method reference, external class, or inline method. The first
1545    * is the most common and works by referencing a method somewhere in the inheritance hierarchy of
1546    * the controller by use of a method name. In the bank example above, both BankController and VaultController use this form.
1547    * 
1548    * Using an external class makes for more easily reused generic filters, such as output compression. External filter classes
1549    * are implemented by having a static +filter+ method on any class and then passing this class to the filter method. Example:
1550    * 
1551    *   class OutputCompressionFilter
1552    *   {
1553    *       function filter(&$controller)
1554    *       {
1555    *           $controller->response->body = compress($controller->response->body);
1556    *       }
1557    *   }
1558    * 
1559    *   class NewspaperController extends AkActionController 
1560    *   {
1561    *       function __construct()
1562    *       {
1563    *           $this->afterFilter(new OutputCompressionFilter());
1564    *       }
1565    *   }
1566    * 
1567    * The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can
1568    * manipulate them as it sees fit.
1569    * 
1570    * 
1571    * == Filter chain ordering
1572    * 
1573    * Using <tt>beforeFilter</tt> and <tt>afterFilter</tt> appends the specified filters to the existing chain. That's usually
1574    * just fine, but some times you care more about the order in which the filters are executed. When that's the case, you
1575    * can use <tt>prependBeforeFilter</tt> and <tt>prependAfterFilter</tt>. Filters added by these methods will be put at the
1576    * beginning of their respective chain and executed before the rest. For example:
1577    * 
1578    *   class ShoppingController extends AkActionController 
1579    *   {
1580    *       function __construct()
1581    *       {
1582    *           $this->beforeFilter('verifyOpenShop');
1583    *       }
1584    *   }
1585    * 
1586    * 
1587    *   class CheckoutController extends AkActionController 
1588    *   {
1589    *       function __construct()
1590    *       {
1591    *           $this->prependBeforeFilter('ensureItemsInCart', 'ensureItemsInStock');
1592    *       }
1593    *   }
1594    * 
1595    * The filter chain for the CheckoutController is now <tt>ensureItemsInCart, ensureItemsInStock,</tt>
1596    * <tt>verifyOpenShop</tt>. So if either of the ensure filters return false, we'll never get around to see if the shop 
1597    * is open or not.
1598    * 
1599    * You may pass multiple filter arguments of each type.
1600    * 
1601    * == Around filters
1602    * 
1603    * In addition to the individual before and after filters, it's also possible to specify that a single object should handle
1604    * both the before and after call. That's especially useful when you need to keep state active between the before and after,
1605    * such as the example of a benchmark filter below:
1606    * 
1607    *   class WeblogController extends AkActionController 
1608    *   {
1609    *       function __construct()
1610    *       {
1611    *           $this->aroundFilter(new BenchmarkingFilter());
1612    *       }
1613    *     
1614    *       // Before this action is performed, BenchmarkingFilter->before($controller) is executed
1615    *      function index()
1616    *      {
1617    *      }
1618    *       // After this action has been performed, BenchmarkingFilter->after($controller) is executed
1619    *   }
1620    * 
1621    *   class BenchmarkingFilter
1622    *   {
1623    *       function before(&$controller)
1624    *       {
1625    *           start_timer();
1626    *       }
1627    *       
1628    *       function after(&$controller)
1629    *       {
1630    *           stop_timer();
1631    *           report_result();   
1632    *       }
1633    *   }
1634    * 
1635    * == Filter chain skipping
1636    * 
1637    * Some times its convenient to specify a filter chain in a superclass that'll hold true for the majority of the 
1638    * subclasses, but not necessarily all of them. The subclasses that behave in exception can then specify which filters
1639    * they would like to be relieved of. Examples
1640    * 
1641    *   class ApplicationController extends AkActionController 
1642    *   {
1643    *       function __construct()
1644    *       {
1645    *           $this->beforeFilter('authenticate');
1646    *       }
1647    *   }
1648    * 
1649    *   class WeblogController extends ApplicationController
1650    *   {
1651    *       // will run the authenticate filter
1652    *   }
1653    * 
1654    *   class SignupController extends AkActionController 
1655    *   {
1656    *       function __construct()
1657    *       {
1658    *           $this->skipBeforeFilter('authenticate');
1659    *       }
1660    *       // will not run the authenticate filter
1661    *   }
1662    * 
1663    * == Filter conditions
1664    * 
1665    * Filters can be limited to run for only specific actions. This can be expressed either by listing the actions to
1666    * exclude or the actions to include when executing the filter. Available conditions are +only+ or +except+, both 
1667    * of which accept an arbitrary number of method references. For example:
1668    * 
1669    *   class Journal extends AkActionController 
1670    *   {
1671    *       function __construct()
1672    *       {   // only require authentication if the current action is edit or delete
1673    *           $this->beforeFilter(array('_authorize'=>array('only'=>array('edit','delete')));
1674    *       }
1675    *       
1676    *       function _authorize(&$controller)
1677    *       {
1678    *         // redirect to login unless authenticated
1679    *       }
1680    *   }
1681    */
1682
1683    var $_includedActions = array(), $_beforeFilters = array(), $_afterFilters = array(), $_excludedActions = array();
1684    /**
1685    * The passed <tt>filters</tt> will be appended to the array of filters that's run _before_ actions
1686    * on this controller are performed.
1687    */
1688    function appendBeforeFilter()
1689    {
1690        $filters = array_reverse(func_get_args());
1691        foreach (array_keys($filters) as $k){
1692            $conditions = $this->_extractConditions(&$filters[$k]);
1693            $this->_addActionConditions($filters[$k], $conditions);
1694            $this->_appendFilterToChain('before', $filters[$k]);
1695        }
1696    }
1697
1698    /**
1699    * The passed <tt>filters</tt> will be prepended to the array of filters that's run _before_ actions
1700    * on this controller are performed.
1701    */
1702    function prependBeforeFilter()
1703    {
1704        $filters = array_reverse(func_get_args());
1705        foreach (array_keys($filters) as $k){
1706            $conditions = $this->_extractConditions(&$filters[$k]);
1707            $this->_addActionConditions($filters[$k], $conditions);
1708            $this->_prependFilterToChain('before', $filters[$k]);
1709        }
1710    }
1711
1712    /**
1713    * Short-hand for appendBeforeFilter since that's the most common of the two.
1714    */
1715    function beforeFilter()
1716    {
1717        $filters = func_get_args();
1718        foreach (array_keys($filters) as $k){
1719            $this->appendBeforeFilter($filters[$k]);
1720        }
1721    }
1722
1723    /**
1724    * The passed <tt>filters</tt> will be appended to the array of filters that's run _after_ actions
1725    * on this controller are performed.
1726    */
1727    function appendAfterFilter()
1728    {
1729        $filters = array_reverse(func_get_args());
1730        foreach (array_keys($filters) as $k){
1731            $conditions = $this->_extractConditions(&$filters[$k]);
1732            $this->_addActionConditions(&$filters[$k], $conditions);
1733            $this->_appendFilterToChain('after', &$filters[$k]);
1734        }
1735
1736    }
1737
1738    /**
1739    * The passed <tt>filters</tt> will be prepended to the array of filters that's run _after_ actions
1740    * on this controller are performed.
1741    */
1742    function prependAfterFilter()
1743    {
1744        $filters = array_reverse(func_get_args());
1745        foreach (array_keys($filters) as $k){
1746            $conditions = $this->_extractConditions(&$filters[$k]);
1747            $this->_addActionConditions(&$filters[$k], $conditions);
1748            $this->_prependFilterToChain('after', &$filters[$k]);
1749        }
1750    }
1751
1752    /**
1753    * Short-hand for appendAfterFilter since that's the most common of the two.
1754    * */
1755    function afterFilter()
1756    {
1757        $filters = func_get_args();
1758        foreach (array_keys($filters) as $k){
1759            $this->appendAfterFilter($filters[$k]);
1760        }
1761    }
1762
1763    /**
1764    * The passed <tt>filters</tt> will have their +before+ method appended to the array of filters that's run both before actions
1765    * on this controller are performed and have their +after+ method prepended to the after actions. The filter objects must all 
1766    * respond to both +before+ and +after+. So if you do appendAroundFilter(new A(), new B()), the callstack will look like:
1767    * 
1768    *   B::before()
1769    *   A::before()
1770    *   A::after()
1771    *   B::after()
1772    */
1773    function appendAroundFilter()
1774    {
1775        $filters = func_get_args();
1776        foreach (array_keys($filters) as $k){
1777            $this->_ensureFilterRespondsToBeforeAndAfter(&$filters[$k]);
1778            $this->appendBeforeFilter(array(&$filters[$k],'before'));
1779        }
1780        $filters = array_reverse($filters);
1781        foreach (array_keys($filters) as $k){
1782            $this->prependAfterFilter(array(&$filters[$k],'after'));
1783        }
1784    }
1785
1786    /**
1787    * The passed <tt>filters</tt> will have their +before+ method prepended to the array of filters that's run both before actions
1788    * on this controller are performed and have their +after+ method appended 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    *   A::before()
1792    *   B::before()
1793    *   B::after()
1794    *   A::after()
1795    */
1796    function prependAroundFilter()
1797    {
1798        $filters = func_get_args();
1799        foreach (array_keys($filters) as $k){
1800            $this->_ensureFilterRespondsToBeforeAndAfter(&$filters[$k]);
1801            $this->prependBeforeFilter(array(&$filters[$k],'before'));
1802        }
1803        $filters = array_reverse($filters);
1804        foreach (array_keys($filters) as $k){
1805            $this->appendAfterFilter(array(&$filters[$k],'after'));
1806        }
1807    }
1808
1809    /**
1810    * Short-hand for appendAroundFilter since that's the most common of the two.
1811    */
1812    function aroundFilter()
1813    {
1814        $filters = func_get_args();
1815        call_user_func_array(array(&$this,'appendAroundFilter'), $filters);
1816    }
1817
1818    /**
1819    * Removes the specified filters from the +before+ filter chain.
1820    * This is especially useful for managing the chain in inheritance hierarchies where only one out
1821    * of many sub-controllers need a different hierarchy.
1822    */
1823    function skipBeforeFilter($filters)
1824    {
1825        $filters = func_get_args();
1826        $this->_skipFilter($filters, 'before');
1827    }
1828
1829    /**
1830    * Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference 
1831    * filters, not instances. This is especially useful for managing the chain in inheritance hierarchies where only one out
1832    * of many sub-controllers need a different hierarchy.
1833    */
1834    function skipAfterFilter($filters)
1835    {
1836        $filters = func_get_args();
1837        $this->_skipFilter($filters, 'after');
1838    }
1839
1840    function _skipFilter(&$filters, $type)
1841    {
1842        $_filters =& $this->{'_'.$type.'Filters'};
1843        // array_diff doesn't play nice with some PHP5 releases when it comes to
1844        // Objects as it only diff equal references, not object types
1845        foreach (array_keys($filters) as $k){
1846            if(AK_PHP5){
1847                if(is_object($filters[$k])){
1848                    foreach (array_keys($_filters) as $k2){
1849                        if(is_object($_filters[$k2]) && get_class($_filters[$k2]) == get_class($filters[$k])){
1850                            $pos = $k2;
1851                            break;
1852                        }
1853                    }
1854                }else{
1855                    $pos = array_search($filters[$k], $_filters);
1856                }
1857
1858                array_splice($_filters, $pos, 1, null);
1859                return ;
1860            }
1861            $_filters = array_diff($_filters,array($filters[$k]));
1862        }
1863    }
1864
1865    /**
1866    * Returns all the before filters for this class.
1867    */
1868    function beforeFilters()
1869    {
1870        return $this->_beforeFilters;
1871    }
1872
1873    /**
1874    * Returns all the after filters for this class and all its ancestors.
1875    */
1876    function afterFilters()
1877    {
1878        return $this->_afterFilters;
1879    }
1880
1881    /**
1882    * Returns a mapping between filters and the actions that may run them.
1883    */
1884    function includedActions()
1885    {
1886        return $this->_includedActions;
1887    }
1888
1889    /**
1890    * Returns a mapping between filters and actions that may not run them.
1891    */
1892    function excludedActions()
1893    {
1894        return $this->_excludedActions;
1895    }
1896
1897
1898    function _appendFilterToChain($condition, $filters)
1899    {
1900        $this->{"_{$condition}Filters"}[] =& $filters;
1901    }
1902
1903    function _prependFilterToChain($condition, $filters)
1904    {
1905        array_unshift($this->{"_{$condition}Filters"}, $filters);
1906    }
1907
1908    function _ensureFilterRespondsToBeforeAndAfter(&$filter_object)
1909    {
1910        if(!method_exists(&$filter_object,'before') && !method_exists(&$filter_object,'after')){
1911            trigger_error(Ak::t('Filter object must respond to both before and after'), E_USER_ERROR);
1912        }
1913    }
1914
1915    function _extractConditions(&$filters)
1916    {
1917        if(is_array($filters) && !isset($filters[0])){
1918            $keys = array_keys($filters);
1919            $conditions = $filters[$keys[0]];
1920            $filters = $keys[0];
1921            return $conditions;
1922        }
1923    }
1924
1925    function _addActionConditions($filters, $conditions)
1926    {
1927        if(!empty($conditions['only'])){
1928            $this->_includedActions[$this->_filterId($filters)] =  $this->_conditionArray($this->_includedActions, $conditions['only']);
1929        }
1930        if(!empty($conditions['except'])){
1931            $this->_excludedActions[$this->_filterId($filters)] =  $this->_conditionArray($this->_excludedActions, $conditions['except']);
1932        }
1933    }
1934
1935    function _conditionArray($actions, $filter_actions)
1936    {
1937        $filter_actions = is_array($filter_actions) ? $filter_actions : array($filter_actions);
1938        $filter_actions = array_map(array(&$this,'_filterId'),$filter_actions);
1939        return array_unique(array_merge($actions, $filter_actions));
1940    }
1941
1942
1943    function _filterId($filters)
1944    {
1945        return is_string($filters) ? $filters : md5(serialize($filters));
1946    }
1947
1948    function performActionWithoutFilters($action)
1949    {
1950        if(method_exists(&$this, $action)){
1951            call_user_func_array(array(&$this, $action), @(array)$this->passed_args);
1952        }
1953    }
1954
1955    function performActionWithFilters($method = '')
1956    {
1957        if ($this->beforeAction($method) !== false && !$this->_hasPerformed()){
1958            $this->performActionWithoutFilters($method);
1959            $this->afterAction($method);
1960            return true;
1961        }
1962        return false;
1963    }
1964
1965    function performAction($method = '')
1966    {
1967        $this->performActionWithFilters($method);
1968    }
1969
1970
1971    /**
1972    * Calls all the defined before-filter filters, which are added by using "beforeFilter($method)".
1973    * If any of the filters return false, no more filters will be executed and the action is aborted.
1974    */
1975    function beforeAction($method = '')
1976    {
1977        Ak::profile('Running before controller action filters '.__CLASS__.'::'.__FUNCTION__.' '.__LINE__);
1978        return $this->_callFilters($this->_beforeFilters, $method);
1979    }
1980
1981    /**
1982    * Calls all the defined after-filter filters, which are added by using "afterFilter($method)".
1983    * If any of the filters return false, no more filters will be executed.
1984    */
1985    function afterAction($method = '')
1986    {
1987        Ak::profile('Running after controller action filters '.__CLASS__.'::'.__FUNCTION__.' '.__LINE__);
1988        return $this->_callFilters(&$this->_afterFilters, $method);
1989    }
1990
1991
1992    function _callFilters(&$filters, $method = '')
1993    {
1994        $filter_result = null;
1995        foreach (array_keys($filters) as $k){
1996            $filter =& $filters[$k];
1997            if(!$this->_actionIsExempted($filter, $method)){
1998                if(is_array($filter) && is_object($filter[0]) && method_exists($filter[0], $filter[1])){
1999                    $filter_result = $filter[0]->$filter[1]($this);
2000                }elseif(!is_object($filter) && method_exists($this, $filter)){
2001                    $filter_result = $this->$filter($this);
2002                }elseif(is_object($filter) && method_exists($filter, 'filter')){
2003                    $filter_result = $filter->filter($this);
2004                }else{
2005                    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);
2006                }
2007
2008            }
2009            if($filter_result === false){
2010                !empty($this->_Logger) ? $this->_Logger->info(Ak::t('Filter chain halted as '.$filter.' returned false')) : null;
2011                return false;
2012            }
2013        }
2014        return $filter_result;
2015    }
2016
2017
2018    function _actionIsExempted($filter, $method = '')
2019    {
2020        $method_id = is_string($method) ? $method : $this->_filterId($method);
2021        $filter_id = $this->_filterId($filter);
2022
2023        if((!empty($this->_includedActions[$filter_id]) && !in_array($method_id, $this->_includedActions[$filter_id])) ||
2024        (!empty($this->_excludedActions[$filter_id]) && in_array($method_id, $this->_excludedActions[$filter_id]))){
2025            return true;
2026        }
2027
2028        return false;
2029    }
2030
2031
2032
2033    /**
2034                    Flash communication between actions
2035    ====================================================================
2036    *
2037    * The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
2038    * to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create action
2039    * that sets <tt>flash['notice] = 'Successfully created'</tt> before redirecting to a display action that can then expose 
2040    * the flash to its template. Actually, that exposure is automatically done. Example:
2041    *
2042    *   class WeblogController extends ActionController
2043    *   {
2044    *       function create()
2045    *       {
2046    *           // save post
2047    *           $this->flash['notice] = 'Successfully created post';
2048    *           $this->redirectTo(array('action'=>'display','params' => array('id' =>$Post->id)));
2049    *       }
2050    *
2051    *       function display()
2052    *       {
2053    *           // doesn't need to assign the flash notice to the template, that's done automatically
2054    *       }
2055    *   }
2056    *
2057    *   display.tpl
2058    *     <?php if($flash['notice']) : ?><div class='notice'><?php echo $flash['notice'] ?></div><?php endif; ?>
2059    *
2060    * 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
2061    * as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
2062    *
2063    * ==flash_now
2064    * 
2065    * Sets a flash that will not be available to the next action, only to the current.
2066    *
2067    *     $this->flash_now['message] = 'Hello current action';
2068    * 
2069    * This method enables you to use the flash as a central messaging system in your app.
2070    * When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
2071    * When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
2072    * vanish when the current action is done.
2073    *
2074    * Entries set via <tt>flash_now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
2075    */
2076    var $flash = array();
2077    var $flash_now = array();
2078    var $flash_options = array();
2079    var $_flash_handled = false;
2080
2081    function _handleFlashAttribute()
2082    {
2083        $this->_flash_handled = true;
2084
2085        $next_flash = empty($this->flash) ? false : $this->flash;
2086        $this->flash = array();
2087        if(isset($_SESSION['__flash'])){
2088            $this->flash = $_SESSION['__flash'];
2089        }
2090        $_SESSION['__flash'] = $next_flash;
2091        if(!empty($this->flash_now)){
2092            $this->flash = array_merge((array)$this->flash,(array)$this->flash_now);
2093        }
2094        $this->_handleFlashOptions();
2095    }
2096
2097    function _handleFlashOptions()
2098    {
2099        $next_flash_options = empty($this->flash_options) ? false : $this->flash_options;
2100        $this->flash_options = array();
2101        if(isset($_SESSION['__flash_options'])){
2102            $this->flash_options = $_SESSION['__flash_options'];
2103        }
2104        $_SESSION['__flash_options'] = $next_flash_options;
2105        if(!empty($this->flash_now_options)){
2106            $this->flash_options = array_merge((array)$this->flash_options,(array)$this->flash_now_options);
2107        }
2108    }
2109
2110
2111    function _mergeFlashOnFlashNow()
2112    {
2113        $this->flash_now = array_merge($this->flash_now,$this->flash);
2114    }
2115
2116
2117    /**
2118                    Pagination for Active Record collections
2119    ====================================================================
2120    *
2121    * The Pagination module aids in the process of paging large collections of
2122    * Active Record objects. It offers macro-style automatic fetching of your
2123    * model for multiple views, or explicit fetching for single actions. And if
2124    * the magic isn't flexible enough for your needs, you can create your own
2125    * paginators with a minimal amount of code.
2126    *
2127    * The Pagination module can handle as much or as little as you wish. In the
2128    * controller, have it automatically query your model for pagination; or,
2129    * if you prefer, create Paginator objects yourself
2130    *
2131    * Pagination is included automatically for all controllers.
2132    *
2133    * For help rendering pagination links, see 
2134    * Helpers/PaginationHelper.
2135    *
2136    * ==== Automatic pagination for every action in a controller
2137    *
2138    *   class PersonController extends ApplicationController
2139    *   {
2140    *       var $model = 'person';
2141    *       var $paginate = array('people'=>array('order' => 'last_name, first_name',
2142    *              'per_page' => 20));
2143    *   }
2144    *
2145    * Each action in this controller now has access to a <tt>$this->people</tt>
2146    * instance variable, which is an ordered collection of model objects for the
2147    * current page (at most 20, sorted by last name and first name), and a 
2148    * <tt>$this->person_pages</tt> Paginator instance. The current page is determined
2149    * by the <tt>$params['page']</tt> variable.
2150    *
2151    * ==== Pagination for a single action
2152    *
2153    *   function show_all()
2154    *   {
2155    *       list($this->person_pages, $this->people) =
2156    *       $this->paginate('people', array('order' => 'last_name, first_name'));
2157    *   }
2158    *
2159    * Like the previous example, but explicitly creates <tt>$this->person_pages</tt>
2160    * and <tt>$this->people</tt> for a single action, and uses the default of 10 items
2161    * per page.
2162    *
2163    * ==== Custom/"classic" pagination 
2164    *
2165    *   function list()
2166    *   {
2167    *       $this->person_pages = new AkPaginator(&$this, $Person->count(), 10, $params['page']);
2168    *       $this->people = $this->Person->find('all', array(
2169    *                       'order'=> 'last_name, first_name', 
2170    *                       'limit' =>  $this->person_pages->items_per_page,
2171    *                       'offset' =>  $this->person_pages->getOffset()));
2172    *   }
2173    * 
2174    * Explicitly creates the paginator from the previous example and uses 
2175    * AkPaginator::toSql to retrieve <tt>$this->people</tt> from the model.
2176    */
2177    // An array holding options for controllers using macro-style pagination
2178    var $_pagination_options = array(
2179    'class_name' => null,
2180    'singular_name' => null,
2181    'per_page' => 10,
2182    'conditions' => null,
2183    'order_by' => null,
2184    'order' => null,
2185    'join' => null,
2186    'joins' => null,
2187    'include' => null,
2188    'select' => null,
2189    'parameter' => 'page'
2190    );
2191
2192    // The default options for pagination
2193    var $_pagination_default_options = array(
2194    'class_name' => null,
2195    'singular_name' => null,
2196    'per_page' => 10,
2197    'conditions' => null,
2198    'order_by' => null,
2199    'order' => null,
2200    'join' => null,
2201    'joins' => null,
2202    'include' => null,
2203    'select' => null,
2204    'parameter' => 'page'
2205    );
2206
2207    var $_pagination_actions = array();
2208
2209    function _paginationValidateOptions($collection_id, $options = array(), $in_action)
2210    {
2211        $this->_pagination_options = array_merge($this->_pagination_default_options, $this->_pagination_options);
2212        $valid_options = array_keys($this->_pagination_default_options);
2213
2214        $valid_options = !in_array($in_action, $valid_options) ? array_merge($valid_options, $this->_pagination_actions) : $valid_options;
2215
2216        $unknown_option_keys = array_diff(array_keys($this->_pagination_options) , $valid_options);
2217
2218        if(!empty($unknown_option_keys)){
2219            trigger_error(Ak::t('Unknown options for pagination: %unknown_option',array('%unknown_option'=>join(', ',$unknown_option_keys))), E_USER_WARNING);
2220        }
2221
2222        $this->_pagination_options['singular_name'] = !empty($this->_pagination_options['singular_name']) ? $this->_pagination_options['singular_name'] : AkInflector::singularize($collection_id);
2223        $this->_pagination_options['class_name'] = !empty($this->_pagination_options['class_name']) ? $this->_pagination_options['class_name'] : AkInflector::camelize($this->_pagination_options['singular_name']);
2224    }
2225
2226    /**
2227    * Returns a paginator and a collection of Active Record model instances
2228    * for the paginator's current page. This is designed to be used in a
2229    * single action.
2230    *
2231    * +options+ are:
2232    * <tt>singular_name</tt>:: the singular name to use, if it can't be inferred by
2233    *                        singularizing the collection name
2234    * <tt>class_name</tt>:: the class name to use, if it can't be inferred by
2235    *                        camelizing the singular name
2236    * <tt>per_page</tt>::   the maximum number of items to include in a 
2237    *                        single page. Defaults to 10
2238    * <tt>conditions</tt>:: optional conditions passed to Model::find('all', $this->params); and
2239    *                        Model::count()
2240    * <tt>order</tt>::      optional order parameter passed to Model::find('all', $this->params);
2241    * <tt>order_by</tt>::   (deprecated, used :order) optional order parameter passed to Model::find('all', $this->params)
2242    * <tt>joins</tt>::      optional joins parameter passed to Model::find('all', $this->params)
2243    *                        and Model::count()
2244    * <tt>join</tt>::       (deprecated, used :joins or :include) optional join parameter passed to Model::find('all', $this->params)
2245    *                        and Model::count()
2246    * <tt>include</tt>::    optional eager loading parameter passed to Model::find('all', $this->params)
2247    *                        and Model::count()
2248    *
2249    * Creates a +before_filter+ which automatically paginates an Active
2250    * Record model for all actions in a controller (or certain actions if
2251    * specified with the <tt>actions</tt> option).
2252    *
2253    * +options+ are the same as PaginationHelper::paginate, with the addition 
2254    * of:
2255    * <tt>actions</tt>:: an array of actions for which the pagination is
2256    *                     active. Defaults to +null+ (i.e., every action)
2257    */
2258    function paginate($collection_id, $options = array())
2259    {
2260        $this->_paginationValidateOptions($collection_id, $options, true);
2261        $this->_paginationLoadPaginatorAndCollection($collection_id, $this->_pagination_options);
2262        $this->beforeFilter('_paginationCreateAndRetrieveCollections');
2263    }
2264
2265
2266    function _paginationCreateAndRetrieveCollections()
2267    {
2268        foreach($this->_pagination_options[$this->class]  as $collection_id=>$options){
2269            if(!empty($options['actions']) && in_array($options['actions'], $action_name)){
2270                continue;
2271            }
2272
2273            list($paginator, $collection) = $this->_paginationLoadPaginatorAndCollection($collection_id, $this->_pagination_options);
2274
2275            $this->{$options['singular_name'].'_pages'} =& $paginator;
2276            $this->$collection_name =& $collection;
2277        }
2278    }
2279
2280    /**
2281    * Returns the total number of items in the collection to be paginated for
2282    * the +model+ and given +conditions+. Override this method to implement a
2283    * custom counter.
2284    */
2285    function _paginationCountCollection(&$model, $conditions, $joins)
2286    {
2287        return $model->count($conditions, $joins);
2288    }
2289
2290    /**
2291    * Returns a collection of items for the given +$model+ and +$options['conditions']+,
2292    * ordered by +$options['order']+, for the current page in the given +$paginator+.
2293    * Override this method to implement a custom finder.
2294    */
2295    function _paginationFindCollection(&$model, $options, &$paginator)
2296    {
2297        return $model->find('all', array(
2298        'conditions' => $this->_pagination_options['conditions'],
2299        'order' => !empty($options['order_by']) ? $options['order_by'] : $options['order'],
2300        'joins' => !empty($options['join']) ? $options['join'] : $options['joins'],
2301        'include' => $this->_pagination_options['include'],
2302        'select' => $this->_pagination_options['select'],
2303        'limit' => $this->_pagination_options['per_page'],
2304        'offset' => $paginator->getOffset()));
2305    }
2306
2307    /**
2308     * @todo Fix this function
2309     */
2310    function _paginationLoadPaginatorAndCollection($collection_id, $options)
2311    {
2312        $page = $this->params[$options['parameter']];
2313        $count = $this->_paginationCountCollection($klass, $options['conditions'],
2314        empty($options['join']) ? $options['join'] : $options['joins']);
2315
2316        require_once(AK_LIB_DIR.DS.'AkActionController'.DS.'AkPaginator.php');
2317        $paginator =& new AkPaginator($this, $count, $options['per_page'], $page);
2318        $collection =& $this->_paginationFindCollection($options['class_name'], $options, $paginator);
2319
2320        return array(&$paginator, &$collection);
2321    }
2322
2323    /**
2324                        Protocol conformance
2325    ====================================================================
2326    */
2327
2328    /**
2329     * Specifies that the named actions requires an SSL connection to be performed (which is enforced by ensure_proper_protocol).
2330     */
2331    function setSslRequiredActions($actions)
2332    {
2333        $this->_ssl_required_actions = empty($this->_ssl_required_actions) ?
2334        (is_string($actions) ? Ak::stringToArray($actions) : $actions) :
2335        array_merge($this->_ssl_required_actions, (is_string($actions) ? Ak::stringToArray($actions) : $actions));
2336    }
2337
2338    function setSslAllowedActions($actions)
2339    {
2340        $this->_ssl_allowed_actions = empty($this->_ssl_allowed_actions) ?
2341        (is_string($actions) ? Ak::stringToArray($actions) : $actions) :
2342        array_merge($this->_ssl_allowed_actions, (is_string($actions) ? Ak::stringToArray($actions) : $actions));
2343    }
2344
2345    /**
2346     * Returns true if the current action is supposed to run as SSL
2347     */
2348    function _isSslRequired()
2349    {
2350        return !empty($this->_ssl_required_actions) && is_array($this->_ssl_required_actions) && isset($this->_action_name) ?
2351        in_array($this->_action_name, $this->_ssl_required_actions) : false;
2352    }
2353
2354    function _isSslAllowed()
2355    {
2356        return (!empty($this->ssl_for_all_actions) && empty($this->_ssl_allowed_actions)) ||
2357        (!empty($this->_ssl_allowed_actions) && is_array($this->_ssl_allowed_actions) && isset($this->_action_name) ?
2358        in_array($this->_action_name, $this->_ssl_allowed_actions) : false);
2359    }
2360
2361    function _ensureProperProtocol()
2362    {
2363        if($this->_isSslAllowed()){
2364            return true;
2365        }
2366        if ($this->_isSslRequired() && !$this->Request->isSsl()){
2367            $this->redirectTo(substr_replace(AK_CURRENT_URL,'s:',4,1));
2368            return false;
2369        }elseif($this->Request->isSsl() && !$this->_isSslRequired()){
2370            $this->redirectTo(substr_replace(AK_CURRENT_URL,'',4,1));
2371            return false;
2372        }
2373    }
2374
2375    /**
2376                            Account Location
2377    ====================================================================
2378    * 
2379    * Account location is a set of methods that supports the account-key-as-subdomain 
2380    * way of identifying the current scope. These methods allow you to easily produce URLs that
2381    * match this style and to get the current account key from the subdomain.
2382    * 
2383    * The methods are: getAccountUrl, getAccountHost, and getAccountDomain.
2384    *
2385    * Example:
2386    *
2387    * include_once('AkAccountLocation.php');
2388    * 
2389    * class ApplicationController extends AkActionController
2390    * {
2391    *   var $before_filter = '_findAccount';
2392    *   
2393    *   function _findAccount()
2394    *   {
2395    *       $this->account = Account::find(array('conditions'=>array('username = ?', $this->account_domain)));
2396    *   }
2397    *
2398    *   class AccountController extends ApplicationController
2399    *   {
2400    *       function new_account()
2401    *       {
2402    *           $this->new_account = Account::create($this->params['new_account']);
2403    *           $this->redirectTo(array('host' => $this->accountHost($this->new_account->username), 'controller' => 'weblog'));
2404    *       }
2405    *
2406    *       function authenticate()
2407    *       {
2408    *           $this->session[$this->account_domain] = 'authenticated';
2409    *           $this->redirectTo(array('controller => 'weblog'));
2410    *       }
2411    *
2412    *       function _isAuthenticated()
2413    *       {
2414    *           return !empty($this->session['account_domain']) ? $this->session['account_domain'] == 'authenticated' : false;
2415    *       }
2416    *   }
2417    *   
2418    *   // The view:
2419    *   Your domain: {account_url?}
2420    *
2421    *   By default, all the methods will query for $this->account->username as the account key, but you can
2422    *   specialize that by overwriting defaultAccountSubdomain. You can of course also pass it in
2423    *   as the first argument to all the methods.
2424    */
2425    function defaultAccountSubdomain()
2426    {
2427        if(!empty($this->account)){
2428            return $this->account->respondsTo('username');
2429        }
2430    }
2431
2432    function accountUrl($account_subdomain = null, $use_ssl = null)
2433    {
2434        $account_subdomain = empty($account_subdomain) ? 'default_account_subdomain' : $account_subdomain;
2435        $use_ssl = empty($use_ssl) ? $use_ssl : $this->Request->isSsl();
2436        return ($use_ssl ? 'https://' : 'http://') . $this->accountHost($account_subdomain);
2437    }
2438
2439    function accountHost($account_subdomain = null)
2440    {
2441        $account_subdomain = empty($account_subdomain) ? 'default_account_subdomain' : $account_subdomain;
2442        $account_host = '';
2443        $account_host .= $account_subdomain . '.';
2444        $account_host .= $this->accountDomain();
2445        return $account_host;
2446    }
2447
2448    function accountDomain()
2449    {
2450        $account_domain = '';
2451        if(count($this->Request->getSubdomains()) > 1){
2452            $account_domain .= join('.',$this->Request->getSubdomains()) . '.';
2453        }
2454        $account_domain .= $this->Request->getDomain() . $this->Request->getPortString();
2455        return $account_domain;
2456    }
2457
2458    function getAccountSubdomain()
2459    {
2460        return array_shift($this->Request->getSubdomains());
2461    }
2462
2463
2464    /**
2465                            Data streaming
2466    ====================================================================
2467    Methods for sending files and streams to the browser instead of rendering.
2468    */
2469
2470    var $default_send_file_options = array(
2471    'type' => 'application/octet-stream',
2472    'disposition' => 'attachment',
2473    'stream' => true,
2474    'buffer_size' => 4096
2475    );
2476
2477    /**
2478    * Sends the file by streaming it 4096 bytes at a time. This way the
2479    * whole file doesn't need to be read into memory at once.  This makes
2480    * it feasible to send even large files.
2481    * 
2482    * Be careful to sanitize the path parameter if it coming from a web
2483    * page.  sendFile($params['path']) allows a malicious user to
2484    * download any file on your server.
2485    * 
2486    * Options:
2487    * * <tt>filename</tt> - suggests a filename for the browser to use.
2488    *   Defaults to realpath($path).
2489    * * <tt>type</tt> - specifies an HTTP content type.
2490    *   Defaults to 'application/octet-stream'.
2491    * * <tt>disposition</tt> - specifies whether the file will be shown inline or downloaded.  
2492    *   Valid values are 'inline' and 'attachment' (default).
2493    * * <tt>stream</tt> - whether to send the file to the user agent as it is read (true)
2494    *   or to read the entire file before sending (false). Defaults to true.
2495    * * <tt>buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
2496    *   Defaults to 4096.
2497    * 
2498    * The default Content-Type and Content-Disposition headers are
2499    * set to download arbitrary binary files in as many browsers as
2500    * possible.  IE versions 4, 5, 5.5, and 6 are all known to have
2501    * a variety of quirks (especially when downloading over SSL).
2502    * 
2503    * Simple download:
2504    *   sendFile('/path/to.zip');
2505    * 
2506    * Show a JPEG in browser:
2507    *   sendFile('/path/to.jpeg', array('type' => 'image/jpeg', 'disposition' => 'inline'));
2508    * 
2509    * Read about the other Content-* HTTP headers if you'd like to
2510    * provide the user with more information (such as Content-Description).
2511    * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
2512    * 
2513    * Also be aware that the document may be cached by proxies and browsers.
2514    * The Pragma and Cache-Control headers declare how the file may be cached
2515    * by intermediaries.  They default to require clients to validate with
2516    * the server before releasing cached responses.  See
2517    * http://www.mnot.net/cache_docs/ for an overview of web caching and
2518    * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
2519    * for the Cache-Control header spec.
2520    */
2521    function sendFile($path, $options = array())
2522    {
2523        $path = realpath($path);
2524        if(!file_exists($path)){
2525            trigger_error(Ak::t('Cannot read file %path',array('%path'=>$path)), E_USER_NOTICE);
2526            return false;
2527        }
2528        $options['length'] = empty($options['length']) ? filesize($path) : $options['length'];
2529        $options['filename'] = empty($options['filename']) ? basename($path) : $options['filename'];
2530        $options['type'] = empty($options['type']) ? Ak::mime_content_type($path) : $options['type'];
2531
2532        $this->performed_render = false;
2533        $this->_sendFileHeaders($options);
2534
2535        if(!empty($options['stream'])){
2536            require_once(AK_LIB_DIR.DS.'AkStream.php');
2537            $this->render(array('text'=> new AkStream($path,$options['buffer_size'])));
2538        }else{
2539            $this->render(array('text'=> Ak::file_get_contents($path)));
2540        }
2541    }
2542
2543    /**
2544    * Send binary data to the user as a file download.  May set content type, apparent file name,
2545    * and specify whether to show data inline or download as an attachment.
2546    * 
2547    * Options:
2548    * * <tt>filename</tt> - Suggests a filename for the browser to use.
2549    * * <tt>type</tt> - specifies an HTTP content type.
2550    *   Defaults to 'application/octet-stream'.
2551    * * <tt>disposition</tt> - specifies whether the file will be shown inline or downloaded.  
2552    *   Valid values are 'inline' and 'attachment' (default).
2553    * 
2554    * Generic data download:
2555    *   sendData($buffer)
2556    * 
2557    * Download a dynamically-generated tarball:
2558    *   sendData(Ak::compress('dir','tgz'), array('filename' => 'dir.tgz'));
2559    * 
2560    * Display an image Active Record in the browser:
2561    *   sendData($image_data, array('type' =>Ak::mime_content_type('image_name.png'), 'disposition' => 'inline'));
2562    * 
2563    * See +sendFile+ for more information on HTTP Content-* headers and caching.
2564    */
2565    function sendData($data, $options = array())
2566    {
2567        $options['length'] = empty($options['length']) ? Ak::size($data) : $options['length'];
2568        $this->_sendFileHeaders($options);
2569        $this->performed_render = false;
2570        $this->renderText($data);
2571    }
2572
2573    /**
2574     * Creates a file for streaming from a file.
2575     * This way you might free memory usage is file is too large
2576     */
2577    function sendDataAsStream($data, $options)
2578    {
2579        $temp_file_name = tempnam(AK_TMP_DIR, Ak::randomString());
2580        $fp = fopen($temp_file_name, 'w');
2581        fwrite($fp, $data);
2582        fclose($fp);
2583        $this->sendFile($temp_file_name, $options);
2584    }
2585
2586
2587    function _sendFileHeaders(&$options)
2588    {
2589        $options = array_merge($this->default_send_file_options,$options);
2590        foreach (array('length', 'type', 'disposition') as $arg){
2591            empty($options[$arg]) ? trigger_error(Ak::t('%arg option required', array('%arg'=>$arg)), E_USER_ERROR) : null;
2592        }
2593        $disposition = empty($options['disposition']) ? 'attachment' : $options['disposition'];
2594        $disposition .= !empty($options['filename']) ? '; filename="'.$options['filename'].'"' : '';
2595        $this->Response->addHeader(array(
2596        'Content-Length'  => $options['length'],
2597        'Content-Type'    => trim($options['type']), // fixes a problem with extra '\r' with some browsers
2598        'Content-Disposition'       => $disposition,
2599        'Content-Transfer-Encoding' => 'binary'
2600        ));
2601
2602    }
2603
2604
2605    function redirectToLocale($locale)
2606    {
2607        if($this->Request->__internationalization_support_enabled){
2608            $lang = isset($this->params['lang']) ? $this->params['lang'] : $locale;
2609
2610            if($locale != $lang){
2611                $this->redirectTo(array_merge($this->Request->getParams(),array('lang'=>$locale)));
2612                return true;
2613            }
2614        }
2615        return false;
2616    }
2617
2618
2619    function api($protocol = 'xml_rpc')
2620    {
2621        $web_services = array_merge(Ak::toArray($this->web_services), Ak::toArray($this->web_service));
2622        if(!empty($web_services)){
2623            $web_services = array_unique($web_services);
2624            require_once(AK_LIB_DIR.DS.'AkActionWebService.php');
2625            require_once(AK_LIB_DIR.DS.'AkActionWebService'.DS.'AkActionWebServiceServer.php');
2626            $Server =& new AkActionWebServiceServer($protocol);
2627            foreach ($web_services as $web_service){
2628                $Server->addService($web_service);
2629            }
2630            $Server->init();
2631            $Server->serve();
2632            exit;
2633        }else{
2634            die(Ak::t('There is not any webservice configured at this endpoint'));
2635        }
2636    }
2637
2638
2639
2640    /**
2641                            HTTP Authentication
2642    ====================================================================
2643    * 
2644    * Simple Basic example:
2645    * 
2646    *   class PostsController extends ApplicationController
2647    *   {
2648    *       var $_authorized_users = array('bermi' => 'secret');
2649    *       
2650    *       function __construct(){
2651    *           $this->beforeFilter(array('authenticate' => array('except' => array('index'))));
2652    *       }
2653    * 
2654    *       function index() {
2655    *           $this->renderText("Everyone can see me!");
2656    *       }
2657    *   
2658    *       function edit(){
2659    *           $this->renderText("I'm only accessible if you know the password");
2660    *       }
2661    *   
2662    *       function authenticate(){
2663    *           return $this->_authenticateOrRequestWithHttpBasic('App name', $this->_authorized_users);
2664    *       }
2665    *   }
2666    * 
2667    * Here is a more advanced Basic example where only Atom feeds and the XML API is protected by HTTP authentication, 
2668    * the regular HTML interface is protected by a session approach:
2669    * 
2670    *   class ApplicationController extends AkActionController
2671    *   {
2672    *       var $models = 'account';
2673    *       
2674    *       function __construct() {
2675    *         $this->beforeFilter(array('_setAccount', 'authenticate'));
2676    *       }
2677    *       
2678    *       function _setAccount() {
2679    *         $this->Account = $this->account->findFirstBy('url_name', array_pop($this->Request->getSubdomains()));
2680    *       }
2681    *   
2682    *       function authenticate() {
2683    *           if($this->Request->isFormat('XML', 'ATOM')){
2684    *               if($User = $this->_authenticateWithHttpBasic($Account)){
2685    *                   $this->CurrentUser = $User;
2686    *               }else{
2687    *                   $this->_requestHttpBasicAuthentication();
2688    *               }
2689    *           }else{
2690    *               if($this->isSessionAuthenticated()){
2691    *                   $this->CurrentUser = $Account->user->find($_SESSION['authenticated']['user_id']);
2692    *               }else{
2693    *                   $this->redirectTo(array('controller'=>'login'));
2694    *                   return false;
2695    *               }
2696    *           }
2697    *       }
2698    *   }
2699    *
2700    * On shared hosts, Apache sometimes doesn't pass authentication headers to
2701    * FCGI instances. If your environment matches this description and you cannot
2702    * authenticate, try this rule in public/.htaccess (replace the plain one):
2703    * 
2704    *   RewriteRule ^(.*)$ index.php [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
2705    */
2706
2707    function _authenticateOrRequestWithHttpBasic($realm = AK_APP_NAME, $login_procedure)
2708    {
2709        if($Result = $this->_authenticateWithHttpBasic($login_procedure)){
2710            return $Result;
2711        }
2712        return $this->_requestHttpBasicAuthentication($realm);
2713    }
2714
2715    function _authenticateWithHttpBasic($login_procedure)
2716    {
2717        return $this->_authenticate($login_procedure);
2718    }
2719
2720    function _requestHttpBasicAuthentication($realm = AK_APP_NAME)
2721    {
2722        return $this->_authenticationRequest($realm);
2723    }
2724
2725    /**
2726     * This is method takes a $login_procedure for performing access authentication.
2727     * 
2728     * If an array is given, it will check the key for a user and the value will be verified to match given password.
2729     * 
2730     * You can pass and array like array('handler' => $Account, 'method' => 'verifyCredentials'), which will call 
2731     * 
2732     *      $Account->verifyCredentials($user_name, $password, $Controller)
2733     * 
2734     * You can also pass an object which implements an "authenticate" method. when calling
2735     * 
2736     *     $this->_authenticate(new User());
2737     * 
2738     * It will call the $User->authenticate($user_name, $password, $Controller)
2739     * 
2740     * In both cases the authentication method should return true for valid credentials or false is invalid.
2741     * 
2742     * @return bool
2743     */
2744    function _authenticate($login_procedure)
2745    {
2746        if(!$this->_authorization()){
2747            return false;
2748        }else{
2749            list($user_name, $password) = $this->_getUserNameAndPassword();
2750            if(is_array($login_procedure)){
2751                if(!isset($login_procedure['handler'])){
2752                    return isset($login_procedure[$user_name]) && $login_procedure[$user_name] == $password;
2753                }elseif(is_a($login_procedure['handler']) && method_exists($login_procedure['handler'], $login_procedure['method'])){
2754                    return $login_procedure['handler']->$login_procedure['method']($user_name, $password, $this);
2755                }
2756            }elseif(method_exists($login_procedure, 'authenticate')){
2757                return $login_procedure->authenticate($user_name, $password, $this);
2758            }
2759        }
2760        return false;
2761    }
2762
2763    function _getUserNameAndPassword()
2764    {
2765        $credentials = $this->_decodeCredentials();
2766        return !is_array($credentials) ? split('/:/', $credentials , 2) : $credentials;
2767    }
2768
2769    function _authorization()
2770    {
2771        return
2772        empty($this->Request->env['PHP_AUTH_USER']) ? (
2773        empty($this->Request->env['HTTP_AUTHORIZATION']) ? (
2774        empty($this->Request->env['X-HTTP_AUTHORIZATION']) ? (
2775        empty($this->Request->env['X_HTTP_AUTHORIZATION']) ? (
2776        isset($this->Request->env['REDIRECT_X_HTTP_AUTHORIZATION']) ?
2777        $this->Request->env['REDIRECT_X_HTTP_AUTHORIZATION'] : null
2778        ) : $this->Request->env['X_HTTP_AUTHORIZATION']
2779        ) : $this->Request->env['X-HTTP_AUTHORIZATION']
2780        ) : $this->Request->env['HTTP_AUTHORIZATION']
2781        ) : array($this->Request->env['PHP_AUTH_USER'], $this->Request->env['PHP_AUTH_PW']);
2782    }
2783
2784    function _decodeCredentials()
2785    {
2786        $authorization = $this->_authorization();
2787        if(is_array($authorization)){
2788            return $authorization;
2789        }
2790        $credentials = (array)split(' ', $authorization);
2791        return base64_decode(array_pop($credentials));
2792    }
2793
2794    function _encodeCredentials($user_name, $password)
2795    {
2796        return 'Basic '.base64_encode("$user_name:$password");
2797    }
2798
2799    function _authenticationRequest($realm)
2800    {
2801        header('WWW-Authenticate: Basic realm="' . str_replace('"','',$realm) . '"');
2802
2803        if(method_exists($this, 'access_denied')){
2804            $this->access_denied();
2805        }else{
2806            header('HTTP/1.0 401 Unauthorized');
2807            echo "HTTP Basic: Access denied.\n";
2808            exit;
2809        }
2810    }
2811
2812    function _ensureActionExists()
2813    {
2814        if(!method_exists($this, $this->_action_name)){
2815            if(AK_ENVIRONMENT == 'development'){
2816                AK_LOG_EVENTS && !empty($this->_Logger) ? $this->_Logger->error('Action '.$this->_action_name.' not found on '.$this->getControllerName()) : null;
2817                trigger_error(Ak::t('Controller <i>%controller_name</i> can\'t handle action %action_name',
2818                array(
2819                '%controller_name' => $this->getControllerName(),
2820                '%action_name' => $this->_action_name,
2821                )), E_USER_ERROR);
2822            }elseif(@include(AK_PUBLIC_DIR.DS.'405.php')){
2823                exit;
2824            }else{
2825                header("HTTP/1.1 405 Method Not Allowed");
2826                die('405 Method Not Allowed');
2827            }
2828        }
2829    }
2830}
2831
2832
2833/**
2834 * Function for getting the singleton controller;
2835 *
2836 * @return AkActionController instance
2837 */
2838function &AkActionController()
2839{
2840    $params = func_num_args() == 0 ? null : func_get_args();
2841    $AkActionController =& Ak::singleton('AkActionController', $params);
2842    return $AkActionController;
2843}
2844
2845
2846?>