[ Index ]

PHP Cross Reference of Akelos Framework

title

Body

[close]

/ -> AkActionController.php (source)

   1  <?php
   2  /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
   3  
   4  // +----------------------------------------------------------------------+
   5  // | Akelos Framework - http://www.akelos.org                             |
   6  // +----------------------------------------------------------------------+
   7  // | Copyright (c) 2002-2006, Akelos Media, S.L.  & Bermi Ferrer Martinez |
   8  // | Released under the GNU Lesser General Public License, see LICENSE.txt|
   9  // +----------------------------------------------------------------------+
  10  
  11  require_once (AK_LIB_DIR.DS.'AkObject.php');
  12  
  13  defined('AK_HIGH_LOAD_MODE') ? null : define('AK_HIGH_LOAD_MODE', false);
  14  defined('AK_APP_NAME') ? null : define('AK_APP_NAME', 'Application');
  15  
  16  /**
  17   * @package ActionController
  18   * @subpackage Base
  19   * @author Bermi Ferrer <bermi a.t akelos c.om>
  20   * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
  21   * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
  22   */
  23  
  24  class AkActionController extends AkObject
  25  {
  26      var $_high_load_mode = AK_HIGH_LOAD_MODE;
  27      var $_enable_plugins = true;
  28      var $_auto_instantiate_models = true;
  29      var $validate_output = false;
  30  
  31      var $_ssl_requirement = false;
  32      var $_ssl_allowed_actions = array();
  33      var $ssl_for_all_actions = true;
  34  
  35      /**
  36      * Determines whether the view has access to controller internals $this->Request, $this->Response, $this->session, and $this->Template.
  37      * By default, it does.
  38      */
  39      var $_view_controller_internals = true;
  40  
  41      /**
  42      * Protected instance variable cache
  43      */
  44      var $_protected_variables_cache = array();
  45  
  46      /**
  47      * Prepends all the URL-generating helpers from AssetHelper. 
  48      * This makes it possible to easily move javascripts, stylesheets, 
  49      * and images to a dedicated asset server away from the main web server. 
  50      * Example: 
  51      *  $this->_asset_host = 'http://assets.example.com';
  52      */
  53      var $asset_host = AK_ASSET_HOST;
  54  
  55  
  56      var $_Logger;
  57  
  58      /**
  59      * Determines which template class should be used by AkActionController.
  60      */
  61      var $TemplateClass;
  62  
  63      /**
  64      * Turn on +_ignore_missing_templates+ if you want to unit test actions without 
  65      * making the associated templates.
  66      */
  67      var $_ignore_missing_templates;
  68  
  69      /**
  70      * Holds the Request object that's primarily used to get environment variables
  71      */
  72      var $Request;
  73  
  74      /**
  75      * Holds an array of all the GET, POST, and Url parameters passed to the action. 
  76      * Accessed like <tt>$this->params['post_id'];</tt>
  77      * to get the post_id. 
  78      */
  79      var $params = array();
  80  
  81      /**
  82      * Holds the Response object that's primarily used to set additional HTTP _headers 
  83      * through access like <tt>$this->Response->_headers['Cache-Control'] = 'no-cache';</tt>. 
  84      * Can also be used to access the final body HTML after a template
  85      * has been rendered through $this->Response->body -- useful for <tt>after_filter</tt>s 
  86      * that wants to manipulate the output, such as a OutputCompressionFilter.
  87      */
  88      var $Response;
  89  
  90      /**
  91      * Holds an array of objects in the session. Accessed like <tt>$this->session['person']</tt> 
  92      * to get the object tied to the 'person' key. The session will hold any type of object 
  93      * as values, but the key should be a string.
  94      */
  95      var $session;
  96  
  97      /**
  98      * Holds an array of header names and values. Accessed like <tt>$this->_headers['Cache-Control']</tt> 
  99      * to get the value of the Cache-Control directive. Values should always be specified as strings.
 100      */
 101      var $_headers = array();
 102  
 103      /**
 104      * Holds the array of variables that are passed on to the template class to be 
 105      * made available to the view. This array is generated by taking a snapshot of 
 106      * all the instance variables in the current scope just before a template is rendered.
 107      */
 108      var $_assigns = array();
 109  
 110      /**
 111      * Holds the name of the action this controller is processing.
 112      */
 113      var $_action_name;
 114  
 115      var $cookies;
 116  
 117      var $helpers = 'default';
 118  
 119      var $app_helpers;
 120      var $plugin_helpers = 'all';
 121  
 122      var $web_service;
 123      var $web_services = array();
 124  
 125      var $web_service_api;
 126      var $web_service_apis = array();
 127  
 128      var $module_name;
 129      var $_module_path;
 130      
 131      var $_request_id = -1;
 132  
 133      /**
 134       * Old fashioned way of dispatching requests. Please use AkDispatcher or roll your own.
 135       * 
 136       * @deprecated 
 137       */
 138      function handleRequest()
 139      {
 140  
 141          AK_LOG_EVENTS && empty($this->_Logger) ? ($this->_Logger =& Ak::getLogger()) : null;
 142          AK_LOG_EVENTS && !empty($this->_Logger) ? $this->_Logger->warning('Using deprecated request dispatcher AkActionController::handleRequest. Use  to AkDispatcher + AkDispatcher::dispatch instead.') : null;
 143          require_once (AK_LIB_DIR.DS.'AkDispatcher.php');
 144          $Dispatcher =& new AkDispatcher();
 145          $Dispatcher->dispatch();
 146      }
 147      
 148      
 149      
 150      function process(&$Request, &$Response)
 151      {
 152          AK_LOG_EVENTS && empty($this->_Logger) ? ($this->_Logger =& Ak::getLogger()) : null;
 153  
 154          $this->Request =& $Request;
 155          $this->Response =& $Response;
 156          $this->params = $this->Request->getParams();
 157          $this->_action_name = $this->Request->getAction();
 158  
 159          $actionExists = $this->_ensureActionExists();
 160          
 161          if (!$actionExists) {
 162              $this->handleResponse();
 163              return false;
 164          }
 165          
 166          Ak::t('Akelos'); // We need to get locales ready
 167  
 168          if($this->_high_load_mode !== true){
 169              if(!empty($this->_auto_instantiate_models)){
 170                  $this->instantiateIncludedModelClasses();
 171              }
 172              if(!empty($this->_enable_plugins)){
 173                  $this->loadPlugins();
 174              }
 175              if(!empty($this->helpers)){
 176                  $this->instantiateHelpers();
 177              }
 178          }else{
 179              $this->_enableLayoutOnRender = false;
 180          }
 181  
 182          $this->_ensureProperProtocol();
 183  
 184          // After filters
 185          $this->afterFilter('_handleFlashAttribute');
 186  
 187          $this->_initExtensions();
 188  
 189          $this->_loadActionView();
 190  
 191          if(isset($this->api)){
 192              require_once (AK_LIB_DIR.DS.'AkActionWebService.php');
 193              $this->aroundFilter(new AkActionWebService($this));
 194          }
 195          
 196          $this->_identifyRequest();
 197          
 198          
 199          $this->performActionWithFilters($this->_action_name);
 200  
 201          
 202          $this->handleResponse();
 203      }
 204      
 205      function _sendMimeContentType()
 206      {
 207          $this->Response->setContentTypeForFormat($this->Request->getFormat());
 208      }
 209      
 210      /**
 211       * Used to respond to multiple formats on the same action.
 212       * The format gets detected by the requested file extension or the
 213       * accept headers.
 214       * 
 215       * Example 1:
 216       * If you need to perform some calculations inste
 217       * ----
 218       * 
 219       * function listing()
 220       * {
 221       *    $this->listings = $this->listing->find(..);
 222       *    if (!$this->respondToFormat()) {
 223       *        // default html response here
 224       *    }
 225       * }
 226       * // handles action listing in format xml
 227       * function _handleListingAsXml()
 228       * {
 229       *    $this->renderText($this->listing->toXml($this->listings));
 230       * }
 231       * 
 232       * 
 233       * Example 2:
 234       * If you just render a standard template by default
 235       * 
 236       * function listing()
 237       * {  
 238       *    // if its the standard format it will render the template post.tpl
 239       *    $this->respondToFormat();
 240       * }
 241       * // handles action listing in format xml
 242       * function _handleListingAsXml()
 243       * {
 244       *    $this->renderText($this->listing->toXml($this->listings));
 245       * }
 246       *
 247       * @param array $options
 248       * @return boolean true if there is a format action, false if not
 249       */
 250      function respondToFormat($options = array())
 251      {
 252          $default_options = array('default'=>'html');
 253          
 254          $options = array_merge($default_options,$options);
 255          
 256          $format = $this->Request->getFormat();
 257          $action = $this->getActionName();
 258          $formatAction = '_handle'.$action.'As'.ucfirst($format);
 259          $isDefaultAction = $format == null || $format == $options['default'];
 260          $formatActionExists = method_exists($this,$formatAction);
 261          if (!$formatActionExists && !$isDefaultAction) {
 262              $this->renderText('404 Not found',404);
 263              
 264          }
 265          if (!$isDefaultAction && $formatActionExists) {
 266                  $this->performActionWithoutFilters($formatAction);
 267                  return true;
 268          }
 269          return false;
 270      }
 271      
 272      function _identifyRequest()
 273      {
 274          if (AK_ENVIRONMENT != 'testing') {
 275              /**
 276               * for AkTestApplication we need to identify if handleResponse rendered
 277               * the output already.
 278               * Since AkTestApplication performs multiple requests
 279               * on one instance of AkActionController, each Request needs
 280               * to be identified separately
 281               */
 282              $this->_request_id++;
 283          } else {
 284              $this->_request_id = md5(time().microtime(true).rand(0,10000));
 285          }
 286      }
 287      function handleResponse()
 288      {
 289          static $handled;
 290          if (empty($handled)) {
 291              $handled = array();
 292          }
 293          if (!isset($handled[$this->_request_id])) {
 294              if (!$this->_hasPerformed()){
 295                  $this->_enableLayoutOnRender ? $this->renderWithLayout() : $this->renderWithoutLayout();
 296              }
 297              $this->_sendMimeContentType();
 298              if(!empty($this->validate_output)){
 299                  $this->_validateGeneratedXhtml();
 300              }
 301              if (!isset($this->Response->_headers['Status']) && !empty($this->_default_render_status_code)) {
 302                  $this->Response->_headers['Status'] = $this->_default_render_status_code;
 303              }
 304              $this->Response->outputResults();
 305              $handled[$this->_request_id]=true;
 306          }
 307      }
 308      
 309      function _loadActionView()
 310      {
 311          empty($this->_assigns) ? ($this->_assigns = array()) : null;
 312          $this->_enableLayoutOnRender = !isset($this->_enableLayoutOnRender) ? true : $this->_enableLayoutOnRender;
 313          $this->passed_args = !isset($this->Request->pass)? array() : $this->Request->pass;
 314          empty($this->cookies) && isset($_COOKIE) ? ($this->cookies =& $_COOKIE) : null;
 315  
 316          if(empty($this->Template)){
 317              require_once (AK_LIB_DIR.DS.'AkActionView.php');
 318              require_once (AK_LIB_DIR.DS.'AkActionView'.DS.'AkPhpTemplateHandler.php');
 319              $this->Template =& new AkActionView($this->_getTemplateBasePath(),
 320              $this->Request->getParameters(),$this->Request->getController());
 321  
 322              $this->Template->_controllerInstance =& $this;
 323              $this->Template->_registerTemplateHandler('tpl','AkPhpTemplateHandler');
 324          }
 325      }
 326  
 327      function loadPlugins()
 328      {
 329          Ak::loadPlugins();
 330      }
 331  
 332      /**
 333       * Creates an instance of each available helper and links it into into current controller.
 334       * 
 335       * Per example, if a helper TextHelper is located into the file text_helper.php. 
 336       * An instance is created on current controller
 337       * at $this->text_helper. This instance is also available on the view by calling $text_helper.
 338       * 
 339       * Helpers can be found at lib/AkActionView/helpers (this might change in a future)
 340       */
 341      function instantiateHelpers()
 342      {
 343          require_once (AK_LIB_DIR.DS.'AkActionView'.DS.'AkHelperLoader.php');
 344          $HelperLoader = new AkHelperLoader();
 345          $HelperLoader->setController(&$this);
 346          $HelperLoader->instantiateHelpers();
 347      }
 348  
 349      function getCurrentControllerHelper()
 350      {
 351          $helper = $this->getControllerName();
 352          $helper = AkInflector::is_plural($helper)?AkInflector::singularize($helper):$helper;
 353          $helper_file_name = AK_HELPERS_DIR.DS.$this->_module_path.AkInflector::underscore($helper).'_helper.php';
 354  
 355          if(file_exists($helper_file_name)){
 356              return array($helper_file_name => $helper);
 357          }
 358          return array();
 359      }
 360  
 361      function getModuleHelper()
 362      {
 363          $this->getControllerName(); // module name is set when we first retrieve the controller name
 364          if(!empty($this->module_name)){
 365              $helper_file_name = AK_HELPERS_DIR.DS.AkInflector::underscore($this->module_name).'_helper.php';
 366              if(file_exists($helper_file_name)){
 367                  return array($helper_file_name => $this->module_name);
 368              }
 369          }
 370          return array();
 371      }
 372  
 373      
 374      function _validateGeneratedXhtml()
 375      {
 376          require_once (AK_LIB_DIR.DS.'AkXhtmlValidator.php');
 377          $XhtmlValidator = new AkXhtmlValidator();
 378          if($XhtmlValidator->validate($this->Response->body) === false){
 379              $this->Response->sendHeaders();
 380              echo '<h1>'.Ak::t('Ooops! There are some errors on current XHTML page').'</h1>';
 381              echo '<small>'.Ak::t('In order to disable XHTML validation, set the <b>AK_ENABLE_STRICT_XHTML_VALIDATION</b> constant to false on your config/development.php file')."</small><hr />\n";
 382              $XhtmlValidator->showErrors();
 383              echo "<hr /><h2>".Ak::t('Showing XHTML code')."</h2><hr /><div style='border:5px solid red;margin:5px;padding:15px;'>".$this->Response->body."</pre>";
 384              die();
 385          }
 386      }
 387  
 388  
 389      /**
 390       * Methods for loading desired models into this controller
 391       */
 392      function setModel($model)
 393      {
 394          $this->instantiateIncludedModelClasses(array($model));
 395      }
 396  
 397      function setModels($models)
 398      {
 399          $this->instantiateIncludedModelClasses($models);
 400      }
 401  
 402      function instantiateIncludedModelClasses($models = array())
 403      {
 404          require_once (AK_LIB_DIR.DS.'AkActiveRecord.php');
 405          require_once(AK_APP_DIR.DS.'shared_model.php');
 406  
 407          empty($this->model) ? ($this->model = $this->params['controller']) : null;
 408          empty($this->models) ? ($this->models = array()) : null;
 409  
 410          $models = array_unique(array_merge(Ak::import($this->model), Ak::import($this->models), Ak::import($models), (empty($this->app_models)?array(): Ak::import($this->app_models))));
 411  
 412          unset($this->model, $this->models);
 413          
 414          foreach ($models as $model){
 415              $this->instantiateModelClass($model, (empty($this->finder_options[$model])?array():$this->finder_options[$model]));
 416          }
 417      }
 418  
 419      function instantiateModelClass($model_class_name, $finder_options = array())
 420      {
 421          $underscored_model_class_name = AkInflector::underscore($model_class_name);
 422          $controller_name = isset($this->controller_name)?$this->controller_name:$this->getControllerName();
 423          $id = empty($this->params[$underscored_model_class_name]['id']) ?
 424          (empty($this->params['id']) ? false :
 425          (($model_class_name == $controller_name || $model_class_name == $this->singularized_controller_name) ? $this->params['id'] : false)) :
 426          $this->params[$underscored_model_class_name]['id'];
 427  
 428          if(class_exists($model_class_name)){
 429              $underscored_model_class_name = AkInflector::underscore($model_class_name);
 430  
 431              if(!isset($this->$model_class_name) || !isset($this->$underscored_model_class_name)){
 432                  if($finder_options !== false && is_numeric($id)){
 433                      $model =& new $model_class_name();
 434                      if(empty($finder_options)){
 435                          $model =& $model->find($id);
 436                      }else{
 437                          $model =& $model->find($id, $finder_options);
 438                      }
 439                  }else{
 440                      $model =& new $model_class_name();
 441                  }
 442                  if(!isset($this->$model_class_name)){
 443                      $this->$model_class_name =& $model;
 444                  }
 445                  if(!isset($this->$underscored_model_class_name)){
 446                      $this->$underscored_model_class_name =& $model;
 447                  }
 448              }
 449          }
 450      }
 451  
 452  
 453  
 454      /**
 455                              Rendering content
 456      ====================================================================
 457      */
 458  
 459      /**
 460      * Renders the content that will be returned to the browser as the Response body.
 461      * 
 462      * === Rendering an action
 463      * 
 464      * Action rendering is the most common form and the type used automatically by 
 465      * Action Controller when nothing else is specified. By default, actions are 
 466      * rendered within the current layout (if one exists).
 467      * 
 468      * * Renders the template for the action "goal" within the current controller
 469      *   
 470      *       $this->render(array('action'=>'goal'));
 471      * 
 472      * * Renders the template for the action "short_goal" within the current controller,
 473      *   but without the current active layout
 474      *   
 475      *       $this->render(array('action'=>'short_goal','layout'=>false));
 476      * 
 477      * * Renders the template for the action "long_goal" within the current controller,
 478      *   but with a custom layout
 479      *   
 480      *       $this->render(array('action'=>'long_goal','layout'=>'spectacular'));
 481      * 
 482      * === Rendering partials
 483      * 
 484      * Partial rendering is most commonly used together with Ajax calls that only update 
 485      * one or a few elements on a page without reloading. Rendering of partials from 
 486      * the controller makes it possible to use the same partial template in
 487      * both the full-page rendering (by calling it from within the template) and when 
 488      * sub-page updates happen (from the controller action responding to Ajax calls). 
 489      * By default, the current layout is not used.
 490      * 
 491      * * Renders the partial located at app/views/controller/_win.tpl
 492      * 
 493      *       $this->render(array('partial'=>'win'));
 494      * 
 495      * * Renders the partial with a status code of 500 (internal error)
 496      * 
 497      *       $this->render(array('partial'=>'broken','status'=>500));
 498      * 
 499      * * Renders the same partial but also makes a local variable available to it
 500      *   
 501      *       $this->render(array('partial' => 'win', 'locals' => array('name'=>'david')));
 502      * 
 503      * * Renders a collection of the same partial by making each element of $wins available through 
 504      *   the local variable "win" as it builds the complete Response
 505      * 
 506      *       $this->render(array('partial'=>'win','collection'=>$wins));
 507      * 
 508      * * Renders the same collection of partials, but also renders the win_divider partial in between
 509      *   each win partial.
 510      *   
 511      *       $this->render(array('partial'=>'win','collection'=>$wins,'spacer_template'=>'win_divider'));
 512      * 
 513      * === Rendering a template
 514      * 
 515      * Template rendering works just like action rendering except that it takes a 
 516      * path relative to the template root. 
 517      * The current layout is automatically applied.
 518      * 
 519      * * Renders the template located in app/views/weblog/show.tpl
 520      *   $this->render(array('template'=>'weblog/show'));
 521      * 
 522      * === Rendering a file
 523      * 
 524      * File rendering works just like action rendering except that it takes a 
 525      * filesystem path. By default, the path is assumed to be absolute, and the 
 526      * current layout is not applied.
 527      * 
 528      * * Renders the template located at the absolute filesystem path
 529      *   $this->render(array('file'=>'/path/to/some/template.tpl'));
 530      *   $this->render(array('file'=>'c:/path/to/some/template.tpl'));
 531      * 
 532      * * Renders a template within the current layout, and with a 404 status code
 533      *   $this->render(array('file' => '/path/to/some/template.tpl', 'layout' => true, 'status' => 404));
 534      *   $this->render(array('file' => 'c:/path/to/some/template.tpl', 'layout' => true, 'status' => 404));
 535      * 
 536      * * Renders a template relative to the template root and chooses the proper file extension
 537      *   $this->render(array('file' => 'some/template', 'use_full_path' => true));
 538      * 
 539      * 
 540      * === Rendering text
 541      * 
 542      * Rendering of text is usually used for tests or for rendering prepared content, 
 543      * such as a cache. By default, text
 544      * rendering is not done within the active layout.
 545      * 
 546      * * Renders the clear text "hello world" with status code 200
 547      *   $this->render(array('text' => 'hello world!'));
 548      * 
 549      * * Renders the clear text "Explosion!"  with status code 500
 550      *   $this->render(array('text' => "Explosion!", 'status' => 500 ));
 551      * 
 552      * * Renders the clear text "Hi there!" within the current active layout (if one exists)
 553      *   $this->render(array('text' => "Explosion!", 'layout' => true));
 554      * 
 555      * * Renders the clear text "Hi there!" within the layout 
 556      * * placed in "app/views/layouts/special.tpl"
 557      *   $this->render(array('text' => "Explosion!", 'layout => "special"));
 558      * 
 559      * 
 560      * === Rendering an inline template
 561      * 
 562      * Rendering of an inline template works as a cross between text and action 
 563      * rendering where the source for the template
 564      * is supplied inline, like text, but its evaled by PHP, like action. By default, 
 565      * PHP is used for rendering and the current layout is not used.
 566      * 
 567      * * Renders "hello, hello, hello, again"
 568      *   $this->render(array('inline' => "<?php echo str_repeat('hello, ', 3).'again'?>" ));
 569      * 
 570      * * Renders "hello david"
 571      *   $this->render(array('inline' => "<?php echo  'hello ' . $name ?>", 'locals' => array('name' => 'david')));
 572      *