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