PageRenderTime 57ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/AkActionController.php

https://repo.or.cz/akelos.git
PHP | 2846 lines | 1355 code | 290 blank | 1201 comment | 177 complexity | d9d05d2b37031605d95621cb9c8f3024 MD5 | raw file
Possible License(s): LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  3. // +----------------------------------------------------------------------+
  4. // | Akelos Framework - http://www.akelos.org |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 2002-2006, Akelos Media, S.L. & Bermi Ferrer Martinez |
  7. // | Released under the GNU Lesser General Public License, see LICENSE.txt|
  8. // +----------------------------------------------------------------------+
  9. require_once(AK_LIB_DIR.DS.'AkObject.php');
  10. defined('AK_HIGH_LOAD_MODE') ? null : define('AK_HIGH_LOAD_MODE', false);
  11. defined('AK_APP_NAME') ? null : define('AK_APP_NAME', 'Application');
  12. /**
  13. * @package ActionController
  14. * @subpackage Base
  15. * @author Bermi Ferrer <bermi a.t akelos c.om>
  16. * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
  17. * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
  18. */
  19. class AkActionController extends AkObject
  20. {
  21. var $_high_load_mode = AK_HIGH_LOAD_MODE;
  22. var $_enable_plugins = true;
  23. var $_auto_instantiate_models = true;
  24. var $validate_output = false;
  25. var $_ssl_requirement = false;
  26. var $_ssl_allowed_actions = array();
  27. var $ssl_for_all_actions = true;
  28. /**
  29. * Determines whether the view has access to controller internals $this->Request, $this->Response, $this->session, and $this->Template.
  30. * By default, it does.
  31. */
  32. var $_view_controller_internals = true;
  33. /**
  34. * Protected instance variable cache
  35. */
  36. var $_protected_variables_cache = array();
  37. /**
  38. * Prepends all the URL-generating helpers from AssetHelper.
  39. * This makes it possible to easily move javascripts, stylesheets,
  40. * and images to a dedicated asset server away from the main web server.
  41. * Example:
  42. * $this->_asset_host = 'http://assets.example.com';
  43. */
  44. var $asset_host = AK_ASSET_HOST;
  45. var $_Logger;
  46. /**
  47. * Determines which template class should be used by AkActionController.
  48. */
  49. var $TemplateClass;
  50. /**
  51. * Turn on +_ignore_missing_templates+ if you want to unit test actions without
  52. * making the associated templates.
  53. */
  54. var $_ignore_missing_templates;
  55. /**
  56. * Holds the Request object that's primarily used to get environment variables
  57. */
  58. var $Request;
  59. /**
  60. * Holds an array of all the GET, POST, and Url parameters passed to the action.
  61. * Accessed like <tt>$this->params['post_id'];</tt>
  62. * to get the post_id.
  63. */
  64. var $params = array();
  65. /**
  66. * Holds the Response object that's primarily used to set additional HTTP _headers
  67. * through access like <tt>$this->Response->_headers['Cache-Control'] = 'no-cache';</tt>.
  68. * Can also be used to access the final body HTML after a template
  69. * has been rendered through $this->Response->body -- useful for <tt>after_filter</tt>s
  70. * that wants to manipulate the output, such as a OutputCompressionFilter.
  71. */
  72. var $Response;
  73. /**
  74. * Holds an array of objects in the session. Accessed like <tt>$this->session['person']</tt>
  75. * to get the object tied to the 'person' key. The session will hold any type of object
  76. * as values, but the key should be a string.
  77. */
  78. var $session;
  79. /**
  80. * Holds an array of header names and values. Accessed like <tt>$this->_headers['Cache-Control']</tt>
  81. * to get the value of the Cache-Control directive. Values should always be specified as strings.
  82. */
  83. var $_headers = array();
  84. /**
  85. * Holds the array of variables that are passed on to the template class to be
  86. * made available to the view. This array is generated by taking a snapshot of
  87. * all the instance variables in the current scope just before a template is rendered.
  88. */
  89. var $_assigns = array();
  90. /**
  91. * Holds the name of the action this controller is processing.
  92. */
  93. var $_action_name;
  94. var $cookies;
  95. var $helpers = 'default';
  96. var $app_helpers;
  97. var $plugin_helpers = 'all';
  98. var $web_service;
  99. var $web_services = array();
  100. var $web_service_api;
  101. var $web_service_apis = array();
  102. var $module_name;
  103. var $_module_path;
  104. /**
  105. * Old fashioned way of dispatching requests. Please use AkDispatcher or roll your own.
  106. *
  107. * @deprecated
  108. */
  109. function handleRequest()
  110. {
  111. AK_LOG_EVENTS && empty($this->_Logger) ? ($this->_Logger =& Ak::getLogger()) : null;
  112. AK_LOG_EVENTS && !empty($this->_Logger) ? $this->_Logger->warning('Using deprecated request dispatcher AkActionController::handleRequest. Use to AkDispatcher + AkDispatcher::dispatch instead.') : null;
  113. require_once(AK_LIB_DIR.DS.'AkDispatcher.php');
  114. $Dispatcher =& new AkDispatcher();
  115. $Dispatcher->dispatch();
  116. }
  117. function process(&$Request, &$Response)
  118. {
  119. AK_LOG_EVENTS && empty($this->_Logger) ? ($this->_Logger =& Ak::getLogger()) : null;
  120. $this->Request =& $Request;
  121. $this->Response =& $Response;
  122. $this->params = $this->Request->getParams();
  123. $this->_action_name = $this->Request->getAction();
  124. $this->_ensureActionExists();
  125. Ak::t('Akelos'); // We need to get locales ready
  126. if($this->_high_load_mode !== true){
  127. if(!empty($this->_auto_instantiate_models)){
  128. $this->instantiateIncludedModelClasses();
  129. }
  130. if(!empty($this->_enable_plugins)){
  131. $this->loadPlugins();
  132. }
  133. if(!empty($this->helpers)){
  134. $this->instantiateHelpers();
  135. }
  136. }else{
  137. $this->_enableLayoutOnRender = false;
  138. }
  139. $this->_ensureProperProtocol();
  140. // After filters
  141. $this->afterFilter('_handleFlashAttribute');
  142. $this->_loadActionView();
  143. if(isset($this->api)){
  144. require_once(AK_LIB_DIR.DS.'AkActionWebService.php');
  145. $this->aroundFilter(new AkActionWebService($this));
  146. }
  147. $this->performActionWithFilters($this->_action_name);
  148. if (!$this->_hasPerformed()){
  149. $this->_enableLayoutOnRender ? $this->renderWithLayout() : $this->renderWithoutLayout();
  150. }
  151. if(!empty($this->validate_output)){
  152. $this->_validateGeneratedXhtml();
  153. }
  154. $this->Response->outputResults();
  155. }
  156. function _loadActionView()
  157. {
  158. empty($this->_assigns) ? ($this->_assigns = array()) : null;
  159. empty($this->_default_render_status_code) ? ($this->_default_render_status_code = 200) : null;
  160. $this->_enableLayoutOnRender = !isset($this->_enableLayoutOnRender) ? true : $this->_enableLayoutOnRender;
  161. $this->passed_args = !isset($this->Request->pass)? array() : $this->Request->pass;
  162. empty($this->cookies) && isset($_COOKIE) ? ($this->cookies =& $_COOKIE) : null;
  163. if(empty($this->Template)){
  164. require_once(AK_LIB_DIR.DS.'AkActionView.php');
  165. require_once(AK_LIB_DIR.DS.'AkActionView'.DS.'AkPhpTemplateHandler.php');
  166. $this->Template =& new AkActionView($this->_getTemplateBasePath(),
  167. $this->Request->getParameters(),$this->Request->getController());
  168. $this->Template->_controllerInstance =& $this;
  169. $this->Template->_registerTemplateHandler('tpl','AkPhpTemplateHandler');
  170. }
  171. }
  172. function loadPlugins()
  173. {
  174. Ak::loadPlugins();
  175. }
  176. /**
  177. * Creates an instance of each available helper and links it into into current controller.
  178. *
  179. * Per example, if a helper TextHelper is located into the file text_helper.php.
  180. * An instance is created on current controller
  181. * at $this->text_helper. This instance is also available on the view by calling $text_helper.
  182. *
  183. * Helpers can be found at lib/AkActionView/helpers (this might change in a future)
  184. */
  185. function instantiateHelpers()
  186. {
  187. $helpers = $this->getDefaultHelpers();
  188. $helpers = array_merge($helpers, $this->getApplicationHelpers());
  189. $helpers = array_merge($helpers, $this->getPluginHelpers());
  190. $helpers = array_merge($helpers, $this->getModuleHelper());
  191. $helpers = array_merge($helpers, $this->getCurrentControllerHelper());
  192. require_once(AK_LIB_DIR.DS.'AkActionView'.DS.'AkActionViewHelper.php');
  193. $available_helpers = array();
  194. foreach ($helpers as $file=>$helper){
  195. $helper_class_name = AkInflector::camelize(AkInflector::demodulize(strstr($helper, 'Helper') ? $helper : $helper.'Helper'));
  196. $full_path = preg_match('/[\\\\\/]+/',$file);
  197. $file_path = $full_path ? $file : AK_LIB_DIR.DS.'AkActionView'.DS.'helpers'.DS.$file;
  198. include_once($file_path);
  199. if(class_exists($helper_class_name)){
  200. $attribute_name = $full_path ? AkInflector::underscore($helper_class_name) : substr($file,0,-4);
  201. $available_helpers[] = $attribute_name;
  202. $this->$attribute_name =& new $helper_class_name(&$this);
  203. if(method_exists($this->$attribute_name,'setController')){
  204. $this->$attribute_name->setController(&$this);
  205. }
  206. if(method_exists($this->$attribute_name,'init')){
  207. $this->$attribute_name->init();
  208. }
  209. }
  210. }
  211. defined('AK_ACTION_CONTROLLER_AVAILABLE_HELPERS') ? null : define('AK_ACTION_CONTROLLER_AVAILABLE_HELPERS', join(',',$available_helpers));
  212. }
  213. function getCurrentControllerHelper()
  214. {
  215. $helper = $this->getControllerName();
  216. $helper = AkInflector::is_plural($helper)?AkInflector::singularize($helper):$helper;
  217. $helper_file_name = AK_HELPERS_DIR.DS.$this->_module_path.AkInflector::underscore($helper).'_helper.php';
  218. if(file_exists($helper_file_name)){
  219. return array($helper_file_name => $helper);
  220. }
  221. return array();
  222. }
  223. function getModuleHelper()
  224. {
  225. $this->getControllerName(); // module name is set when we first retrieve the controller name
  226. if(!empty($this->module_name)){
  227. $helper_file_name = AK_HELPERS_DIR.DS.AkInflector::underscore($this->module_name).'_helper.php';
  228. if(file_exists($helper_file_name)){
  229. return array($helper_file_name => $this->module_name);
  230. }
  231. }
  232. return array();
  233. }
  234. function getDefaultHelpers()
  235. {
  236. if($this->helpers == 'default'){
  237. $available_helpers = Ak::dir(AK_LIB_DIR.DS.'AkActionView'.DS.'helpers',array('dirs'=>false));
  238. $helper_names = array();
  239. foreach ($available_helpers as $available_helper){
  240. $helper_names[$available_helper] = AkInflector::classify(substr($available_helper,0,-10));
  241. }
  242. return $helper_names;
  243. }else{
  244. $this->helpers = Ak::toArray($this->helpers);
  245. }
  246. return $this->helpers;
  247. }
  248. function getApplicationHelpers()
  249. {
  250. $helper_names = array();
  251. if ($this->app_helpers == 'all'){
  252. $available_helpers = Ak::dir(AK_HELPERS_DIR,array('dirs'=>false));
  253. $helper_names = array();
  254. foreach ($available_helpers as $available_helper){
  255. $helper_names[AK_HELPERS_DIR.DS.$available_helper] = AkInflector::classify(substr($available_helper,0,-10));
  256. }
  257. } elseif (!empty($this->app_helpers)){
  258. foreach (Ak::toArray($this->app_helpers) as $helper_name){
  259. $helper_names[AK_HELPERS_DIR.DS.AkInflector::underscore($helper_name).'_helper.php'] = AkInflector::camelize($helper_name);
  260. }
  261. }
  262. return $helper_names;
  263. }
  264. function getPluginHelpers()
  265. {
  266. $helper_names = AkActionController::addPluginHelper(false); // Trick for getting helper names set by AkPlugin::addHelper
  267. if(empty($helper_names)){
  268. return array();
  269. }elseif ($this->plugin_helpers == 'all'){
  270. return $helper_names;
  271. }else {
  272. $selected_helper_names = array();
  273. foreach (Ak::toArray($this->plugin_helpers) as $helper_name){
  274. $helper_name = AkInflector::camelize($helper_name);
  275. if($path = array_shift(array_keys($helper_names, AkInflector::camelize($helper_name)))){
  276. $selected_helper_names[$path] = $helper_names[$path];
  277. }
  278. }
  279. return $selected_helper_names;
  280. }
  281. }
  282. /**
  283. * Used for adding helpers to the base class like those added by the plugins engine.
  284. *
  285. * @param string $helper_name Helper class name like CalendarHelper
  286. * @param array $options - path: Path to the helper class, defaults to AK_PLUGINS_DIR/helper_name/lib/helper_name.php
  287. */
  288. function addPluginHelper($helper_name, $options = array())
  289. {
  290. static $helpers = array();
  291. if($helper_name === false){
  292. return $helpers;
  293. }
  294. $underscored_helper_name = AkInflector::underscore($helper_name);
  295. $default_options = array(
  296. 'path' => AK_PLUGINS_DIR.DS.$underscored_helper_name.DS.'lib'.DS.$underscored_helper_name.'.php'
  297. );
  298. $options = array_merge($default_options, $options);
  299. $helpers[$options['path']] = $helper_name;
  300. }
  301. function _validateGeneratedXhtml()
  302. {
  303. require_once(AK_LIB_DIR.DS.'AkXhtmlValidator.php');
  304. $XhtmlValidator = new AkXhtmlValidator();
  305. if($XhtmlValidator->validate($this->Response->body) === false){
  306. $this->Response->sendHeaders();
  307. echo '<h1>'.Ak::t('Ooops! There are some errors on current XHTML page').'</h1>';
  308. 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";
  309. $XhtmlValidator->showErrors();
  310. echo "<hr /><h2>".Ak::t('Showing XHTML code')."</h2><hr /><div style='border:5px solid red;margin:5px;padding:15px;'>".$this->Response->body."</pre>";
  311. die();
  312. }
  313. }
  314. /**
  315. * Methods for loading desired models into this controller
  316. */
  317. function setModel($model)
  318. {
  319. $this->instantiateIncludedModelClasses(array($model));
  320. }
  321. function setModels($models)
  322. {
  323. $this->instantiateIncludedModelClasses($models);
  324. }
  325. function instantiateIncludedModelClasses($models = array())
  326. {
  327. require_once(AK_LIB_DIR.DS.'AkActiveRecord.php');
  328. require_once(AK_APP_DIR.DS.'shared_model.php');
  329. empty($this->model) ? ($this->model = $this->params['controller']) : null;
  330. empty($this->models) ? ($this->models = array()) : null;
  331. $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))));
  332. foreach ($models as $model){
  333. $this->instantiateModelClass($model, (empty($this->finder_options[$model])?array():$this->finder_options[$model]));
  334. }
  335. }
  336. function instantiateModelClass($model_class_name, $finder_options = array())
  337. {
  338. $underscored_model_class_name = AkInflector::underscore($model_class_name);
  339. $controller_name = $this->getControllerName();
  340. $id = empty($this->params[$underscored_model_class_name]['id']) ?
  341. (empty($this->params['id']) ? false :
  342. (($model_class_name == $controller_name || $model_class_name == AkInflector::singularize($controller_name)) ? $this->params['id'] : false)) :
  343. $this->params[$underscored_model_class_name]['id'];
  344. if(class_exists($model_class_name)){
  345. $underscored_model_class_name = AkInflector::underscore($model_class_name);
  346. if(!isset($this->$model_class_name) || !isset($this->$underscored_model_class_name)){
  347. if($finder_options !== false && is_numeric($id)){
  348. $model =& new $model_class_name();
  349. if(empty($finder_options)){
  350. $model =& $model->find($id);
  351. }else{
  352. $model =& $model->find($id, $finder_options);
  353. }
  354. }else{
  355. $model =& new $model_class_name();
  356. }
  357. if(!isset($this->$model_class_name)){
  358. $this->$model_class_name =& $model;
  359. }
  360. if(!isset($this->$underscored_model_class_name)){
  361. $this->$underscored_model_class_name =& $model;
  362. }
  363. }
  364. }
  365. }
  366. /**
  367. Rendering content
  368. ====================================================================
  369. */
  370. /**
  371. * Renders the content that will be returned to the browser as the Response body.
  372. *
  373. * === Rendering an action
  374. *
  375. * Action rendering is the most common form and the type used automatically by
  376. * Action Controller when nothing else is specified. By default, actions are
  377. * rendered within the current layout (if one exists).
  378. *
  379. * * Renders the template for the action "goal" within the current controller
  380. *
  381. * $this->render(array('action'=>'goal'));
  382. *
  383. * * Renders the template for the action "short_goal" within the current controller,
  384. * but without the current active layout
  385. *
  386. * $this->render(array('action'=>'short_goal','layout'=>false));
  387. *
  388. * * Renders the template for the action "long_goal" within the current controller,
  389. * but with a custom layout
  390. *
  391. * $this->render(array('action'=>'long_goal','layout'=>'spectacular'));
  392. *
  393. * === Rendering partials
  394. *
  395. * Partial rendering is most commonly used together with Ajax calls that only update
  396. * one or a few elements on a page without reloading. Rendering of partials from
  397. * the controller makes it possible to use the same partial template in
  398. * both the full-page rendering (by calling it from within the template) and when
  399. * sub-page updates happen (from the controller action responding to Ajax calls).
  400. * By default, the current layout is not used.
  401. *
  402. * * Renders the partial located at app/views/controller/_win.tpl
  403. *
  404. * $this->render(array('partial'=>'win'));
  405. *
  406. * * Renders the partial with a status code of 500 (internal error)
  407. *
  408. * $this->render(array('partial'=>'broken','status'=>500));
  409. *
  410. * * Renders the same partial but also makes a local variable available to it
  411. *
  412. * $this->render(array('partial' => 'win', 'locals' => array('name'=>'david')));
  413. *
  414. * * Renders a collection of the same partial by making each element of $wins available through
  415. * the local variable "win" as it builds the complete Response
  416. *
  417. * $this->render(array('partial'=>'win','collection'=>$wins));
  418. *
  419. * * Renders the same collection of partials, but also renders the win_divider partial in between
  420. * each win partial.
  421. *
  422. * $this->render(array('partial'=>'win','collection'=>$wins,'spacer_template'=>'win_divider'));
  423. *
  424. * === Rendering a template
  425. *
  426. * Template rendering works just like action rendering except that it takes a
  427. * path relative to the template root.
  428. * The current layout is automatically applied.
  429. *
  430. * * Renders the template located in app/views/weblog/show.tpl
  431. * $this->render(array('template'=>'weblog/show'));
  432. *
  433. * === Rendering a file
  434. *
  435. * File rendering works just like action rendering except that it takes a
  436. * filesystem path. By default, the path is assumed to be absolute, and the
  437. * current layout is not applied.
  438. *
  439. * * Renders the template located at the absolute filesystem path
  440. * $this->render(array('file'=>'/path/to/some/template.tpl'));
  441. * $this->render(array('file'=>'c:/path/to/some/template.tpl'));
  442. *
  443. * * Renders a template within the current layout, and with a 404 status code
  444. * $this->render(array('file' => '/path/to/some/template.tpl', 'layout' => true, 'status' => 404));
  445. * $this->render(array('file' => 'c:/path/to/some/template.tpl', 'layout' => true, 'status' => 404));
  446. *
  447. * * Renders a template relative to the template root and chooses the proper file extension
  448. * $this->render(array('file' => 'some/template', 'use_full_path' => true));
  449. *
  450. *
  451. * === Rendering text
  452. *
  453. * Rendering of text is usually used for tests or for rendering prepared content,
  454. * such as a cache. By default, text
  455. * rendering is not done within the active layout.
  456. *
  457. * * Renders the clear text "hello world" with status code 200
  458. * $this->render(array('text' => 'hello world!'));
  459. *
  460. * * Renders the clear text "Explosion!" with status code 500
  461. * $this->render(array('text' => "Explosion!", 'status' => 500 ));
  462. *
  463. * * Renders the clear text "Hi there!" within the current active layout (if one exists)
  464. * $this->render(array('text' => "Explosion!", 'layout' => true));
  465. *
  466. * * Renders the clear text "Hi there!" within the layout
  467. * * placed in "app/views/layouts/special.tpl"
  468. * $this->render(array('text' => "Explosion!", 'layout => "special"));
  469. *
  470. *
  471. * === Rendering an inline template
  472. *
  473. * Rendering of an inline template works as a cross between text and action
  474. * rendering where the source for the template
  475. * is supplied inline, like text, but its evaled by PHP, like action. By default,
  476. * PHP is used for rendering and the current layout is not used.
  477. *
  478. * * Renders "hello, hello, hello, again"
  479. * $this->render(array('inline' => "<?php echo str_repeat('hello, ', 3).'again'?>" ));
  480. *
  481. * * Renders "hello david"
  482. * $this->render(array('inline' => "<?php echo 'hello ' . $name ?>", 'locals' => array('name' => 'david')));
  483. *
  484. *
  485. * === Rendering nothing
  486. *
  487. * Rendering nothing is often convenient in combination with Ajax calls that
  488. * perform their effect client-side or
  489. * when you just want to communicate a status code. Due to a bug in Safari, nothing
  490. * actually means a single space.
  491. *
  492. * * Renders an empty Response with status code 200
  493. * $this->render(array('nothing' => true));
  494. *
  495. * * Renders an empty Response with status code 401 (access denied)
  496. * $this->render(array('nothing' => true, 'status' => 401));
  497. */
  498. function render($options = null, $status = 200)
  499. {
  500. if(empty($options['partial']) && $this->_hasPerformed()){
  501. $this->_doubleRenderError(Ak::t("Can only render or redirect once per action"));
  502. return false;
  503. }
  504. $this->_flash_handled ? null : $this->_handleFlashAttribute();
  505. if(!is_array($options)){
  506. return $this->renderFile(empty($options) ? $this->getDefaultTemplateName() : $options, $status, true);
  507. }
  508. if(!empty($options['text'])){
  509. return $this->renderText($options['text'], @$options['status']);
  510. }else{
  511. if(!empty($options['file'])){
  512. return $this->renderFile($options['file'], @$options['status'], @$options['use_full_path'], @(array)$options['locals']);
  513. }elseif(!empty($options['template'])){
  514. return $this->renderFile($options['template'], @$options['status'], true);
  515. }elseif(!empty($options['inline'])){
  516. return $this->renderTemplate($options['inline'], @$options['status'], @$options['type'], @(array)$options['locals']);
  517. }elseif(!empty($options['action'])){
  518. return $this->renderAction($options['action'], @$options['status'], @$options['layout']);
  519. }elseif(!empty($options['partial'])){
  520. if($options['partial'] === true){
  521. $options['partial'] = !empty($options['template']) ? $options['template'] : $this->getDefaultTemplateName();
  522. }
  523. if(!empty($options['collection'])){
  524. return $this->renderPartialCollection($options['partial'], $options['collection'], @$options['spacer_template'], @$options['locals'], @$options['status']);
  525. }else{
  526. return $this->renderPartial($options['partial'], @$options['object'], @$options['locals'], @$options['status']);
  527. }
  528. }elseif(!empty($options['nothing'])){
  529. // Safari doesn't pass the _headers of the return if the Response is zero length
  530. return $this->renderText(' ', @$options['status']);
  531. }else{
  532. return $this->renderFile($this->getDefaultTemplateName(), @$options['status'], true);
  533. }
  534. return true;
  535. }
  536. }
  537. /**
  538. * Renders according to the same rules as <tt>render</tt>, but returns the result in a string instead
  539. * of sending it as the Response body to the browser.
  540. */
  541. function renderToString($options = null)
  542. {
  543. $result = $this->render($options);
  544. $this->eraseRenderResults();
  545. $this->variables_added = null;
  546. $this->Template->_assigns_added = null;
  547. return $result;
  548. }
  549. function renderAction($_action_name, $status = null, $with_layout = true)
  550. {
  551. $template = $this->getDefaultTemplateName($_action_name);
  552. if(!empty($with_layout) && !$this->_isTemplateExemptFromLayout($template)){
  553. return $this->renderWithLayout($template, $status, $with_layout);
  554. }else{
  555. return $this->renderWithoutLayout($template, $status);
  556. }
  557. }
  558. function renderFile($template_path, $status = null, $use_full_path = false, $locals = array())
  559. {
  560. $this->_addVariablesToAssigns();
  561. $locals = array_merge($locals,$this->_assigns);
  562. if($use_full_path){
  563. $this->_assertExistanceOfTemplateFile($template_path);
  564. }
  565. AK_LOG_EVENTS && !empty($this->_Logger) ? $this->_Logger->message("Rendering $this->full_template_path" . (!empty($status) ? " ($status)":'')) : null;
  566. return $this->renderText($this->Template->renderFile($template_path, $use_full_path, $locals), $status);
  567. }
  568. function renderTemplate($template, $status = null, $type = 'tpl', $local_assigns = array())
  569. {
  570. $this->_addVariablesToAssigns();
  571. $local_assigns = array_merge($local_assigns,$this->_assigns);
  572. return $this->renderText($this->Template->renderTemplate($type, $template, null, $local_assigns), $status);
  573. }
  574. function renderText($text = null, $status = null)
  575. {
  576. $this->performed_render = true;
  577. $this->Response->_headers['Status'] = !empty($status) ? $status : $this->_default_render_status_code;
  578. $this->Response->body = $text;
  579. return $text;
  580. }
  581. function renderNothing($status = null)
  582. {
  583. return $this->renderText(' ', $status);
  584. }
  585. function renderPartial($partial_path = null, $object = null, $local_assigns = null, $status = null)
  586. {
  587. $partial_path = empty($partial_path) ? $this->getDefaultTemplateName() : $partial_path;
  588. $this->variables_added = false;
  589. $this->performed_render = false;
  590. $this->_addVariablesToAssigns();
  591. $this->Template->controller =& $this;
  592. $this->$partial_path = $this->renderText($this->Template->renderPartial($partial_path, $object, array_merge($this->_assigns, (array)$local_assigns)), $status);
  593. return $this->$partial_path;
  594. }
  595. function renderPartialCollection($partial_name, $collection, $partial_spacer_template = null, $local_assigns = null, $status = null)
  596. {
  597. $this->_addVariablesToAssigns();
  598. $collection_name = AkInflector::pluralize($partial_name).'_collection';
  599. $result = $this->Template->renderPartialCollection($partial_name, $collection, $partial_spacer_template, $local_assigns);
  600. if(empty($this->$collection_name)){
  601. $this->$collection_name = $result;
  602. }
  603. $this->variables_added = false;
  604. $this->performed_render = false;
  605. return $result;
  606. }
  607. function renderWithLayout($template_name = null, $status = null, $layout = null)
  608. {
  609. $template_name = empty($template_name) ? $this->getDefaultTemplateName() : $template_name;
  610. return $this->renderWithALayout($template_name, $status, $layout);
  611. }
  612. function renderWithoutLayout($template_name = null, $status = null)
  613. {
  614. $template_name = empty($template_name) ? $this->getDefaultTemplateName() : $template_name;
  615. return $this->render($template_name, $status);
  616. }
  617. /**
  618. * Clears the rendered results, allowing for another render to be performed.
  619. */
  620. function eraseRenderResults()
  621. {
  622. $this->Response->body = '';
  623. $this->performed_render = false;
  624. $this->variables_added = false;
  625. }
  626. function _addVariablesToAssigns()
  627. {
  628. if(empty($this->variables_added)){
  629. $this->_addInstanceVariablesToAssigns();
  630. $this->variables_added = true;
  631. }
  632. }
  633. function _addInstanceVariablesToAssigns()
  634. {
  635. $this->_protected_variables_cache = array_merge($this->_protected_variables_cache, $this->_getProtectedInstanceVariables());
  636. foreach (array_diff(array_keys(get_object_vars($this)), $this->_protected_variables_cache) as $attribute){
  637. if($attribute[0] != '_'){
  638. $this->_assigns[$attribute] =& $this->$attribute;
  639. }
  640. }
  641. }
  642. function _getProtectedInstanceVariables()
  643. {
  644. return !empty($this->_view_controller_internals) ?
  645. array('_assigns', 'performed_redirect', 'performed_render','db') :
  646. array('_assigns', 'performed_redirect', 'performed_render', 'session', 'cookies',
  647. 'Template','db','helpers','models','layout','Response','Request',
  648. 'params','passed_args');
  649. }
  650. /**
  651. * Use this to translate strings in the scope of your controller
  652. *
  653. * @see Ak::t
  654. */
  655. function t($string, $array = null)
  656. {
  657. return Ak::t($string, $array, AkInflector::underscore($this->getControllerName()));
  658. }
  659. /**
  660. Redirects
  661. ====================================================================
  662. */
  663. /**
  664. * Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
  665. *
  666. * * <tt>Array</tt>: The URL will be generated by calling $this->UrlFor with the +options+.
  667. * * <tt>String starting with protocol:// (like http://)</tt>: Is passed straight through
  668. * as the target for redirection.
  669. * * <tt>String not containing a protocol</tt>: The current protocol and host is prepended to the string.
  670. * * <tt>back</tt>: Back to the page that issued the Request-> Useful for forms that are
  671. * triggered from multiple places.
  672. * Short-hand for redirectTo(Request->env["HTTP_REFERER"])
  673. *
  674. * Examples:
  675. * redirectTo(array('action' => 'show', 'id' => 5));
  676. * redirectTo('http://www.akelos.com');
  677. * redirectTo('/images/screenshot.jpg');
  678. * redirectTo('back');
  679. *
  680. * The redirection happens as a "302 Moved" header.
  681. */
  682. function redirectTo($options = array(), $parameters_for_method_reference = null)
  683. {
  684. if(is_string($options)) {
  685. if(preg_match('/^\w+:\/\/.*/',$options)){
  686. if($this->_hasPerformed()){
  687. $this->_doubleRenderError();
  688. }
  689. AK_LOG_EVENTS && !empty($this->_Logger) ? $this->_Logger->message('Redirected to '.$options) : null;
  690. $this->_handleFlashAttribute();
  691. $this->Response->redirect($options);
  692. $this->Response->redirected_to = $options;
  693. $this->performed_redirect = true;
  694. }elseif ($options == 'back'){
  695. $this->redirectTo($this->Request->env['HTTP_REFERER']);
  696. }else{
  697. $this->redirectTo($this->Request->getProtocol(). $this->Request->getHostWithPort(). $options);
  698. }
  699. }else{
  700. if(empty($parameters_for_method_reference)){
  701. $this->redirectTo($this->UrlFor($options));
  702. $this->Response->redirected_to = $options;
  703. }else{
  704. $this->redirectTo($this->UrlFor($options, $parameters_for_method_reference));
  705. $this->Response->redirected_to = $options;
  706. $this->Response->redirected_to_method_params = $parameters_for_method_reference;
  707. }
  708. }
  709. }
  710. function redirectToAction($action, $options = array())
  711. {
  712. $this->redirectTo(array_merge(array('action'=>$action), $options));
  713. }
  714. /**
  715. * This methods are required for retrieving available controllers for URL Routing
  716. */
  717. function rewriteOptions($options)
  718. {
  719. $defaults = $this->defaultUrlOptions($options);
  720. if(!empty($this->module_name)){
  721. $defaults['module'] = $this->getModuleName();
  722. }
  723. if(!empty($options['controller']) && strstr($options['controller'], '/')){
  724. $defaults['module'] = substr($options['controller'], 0, strrpos($options['controller'], '/'));
  725. $options['controller'] = substr($options['controller'], strrpos($options['controller'], '/') + 1);
  726. }
  727. $options = !empty($defaults) ? array_merge($defaults, $options) : $options;
  728. $options['controller'] = empty($options['controller']) ? AkInflector::underscore($this->getControllerName()) : $options['controller'];
  729. return $options;
  730. }
  731. function getControllerName()
  732. {
  733. if(!isset($this->controller_name)){
  734. $current_class_name = str_replace('_', '::', get_class($this));
  735. if (!AK_PHP5){
  736. $current_class_name = $this->__getControllerName_PHP4_fix($current_class_name);
  737. }
  738. $controller_name = substr($current_class_name,0,-10);
  739. $this->controller_name = $this->_removeModuleNameFromControllerName($controller_name);
  740. }
  741. return $this->controller_name;
  742. }
  743. function __getControllerName_PHP4_fix($class_name)
  744. {
  745. $included_controllers = $this->_getIncludedControllerNames();
  746. $lowercase_included_controllers = array_map('strtolower', $included_controllers);
  747. $key = array_search(strtolower($class_name), $lowercase_included_controllers, true);
  748. return $included_controllers[$key];
  749. }
  750. function getModuleName()
  751. {
  752. return $this->module_name;
  753. }
  754. function setModuleName($module_name)
  755. {
  756. return $this->module_name = $module_name;
  757. }
  758. /**
  759. * Removes the modules name from the controller if exists and sets it.
  760. *
  761. * @return $controller_name
  762. */
  763. function _removeModuleNameFromControllerName($controller_name)
  764. {
  765. if(strstr($controller_name, '::')){
  766. $module_parts = explode ('::',$controller_name);
  767. $controller_name = array_pop($module_parts);
  768. $this->setModuleName(join('/', array_map(array('AkInflector','underscore'), $module_parts)));
  769. }
  770. return $controller_name;
  771. }
  772. function _getTemplateBasePath()
  773. {
  774. return AK_APP_DIR.DS.'views'.DS.(empty($this->_module_path)?'':$this->_module_path).$this->Request->getController();
  775. }
  776. function _getIncludedControllerNames()
  777. {
  778. $controllers = array();
  779. foreach (get_included_files() as $file_name){
  780. if(strstr($file_name,AK_CONTROLLERS_DIR)){
  781. $controllers[] = AkInflector::classify(str_replace(array(AK_CONTROLLERS_DIR.DS,'.php', DS, '//'),array('','','/', '/'),$file_name));
  782. }
  783. }
  784. return $controllers;
  785. }
  786. /**
  787. URL generation/rewriting
  788. ====================================================================
  789. */
  790. /**
  791. * Overwrite to implement a number of default options that all urlFor-based methods will use.
  792. * The default options should come in
  793. * the form of a an array, just like the one you would use for $this->UrlFor directly. Example:
  794. *
  795. * function defaultUrlOptions($options)
  796. * {
  797. * return array('project' => ($this->Project->isActive() ? $this->Project->url_name : 'unknown'));
  798. * }
  799. *
  800. * As you can infer from the example, this is mostly useful for situations where you want to
  801. * centralize dynamic decisions about the urls as they stem from the business domain.
  802. * Please note that any individual $this->UrlFor call can always override the defaults set
  803. * by this method.
  804. */
  805. function defaultUrlOptions($options)
  806. {
  807. }
  808. /**
  809. * Returns a URL that has been rewritten according to the options array and the defined Routes.
  810. * (For doing a complete redirect, use redirectTo).
  811. *
  812. * <tt>$this->UrlFor</tt> is used to:
  813. *
  814. * All keys given to $this->UrlFor are forwarded to the Route module, save for the following:
  815. * * <tt>anchor</tt> -- specifies the anchor name to be appended to the path. For example,
  816. * <tt>$this->UrlFor(array('controller' => 'posts', 'action' => 'show', 'id' => 10, 'anchor' => 'comments'</tt>
  817. * will produce "/posts/show/10#comments".
  818. * * <tt>only_path</tt> -- if true, returns the absolute URL (omitting the protocol, host name, and port)
  819. * * <tt>trailing_slash</tt> -- if true, adds a trailing slash, as in "/archive/2005/". Note that this
  820. * is currently not recommended since it breaks caching.
  821. * * <tt>host</tt> -- overrides the default (current) host if provided
  822. * * <tt>protocol</tt> -- overrides the default (current) protocol if provided
  823. *
  824. * The URL is generated from the remaining keys in the array. A URL contains two key parts: the <base> and a query string.
  825. * Routes composes a query string as the key/value pairs not included in the <base>.
  826. *
  827. * The default Routes setup supports a typical Akelos Framework path of "controller/action/id"
  828. * where action and id are optional, with
  829. * action defaulting to 'index' when not given. Here are some typical $this->UrlFor statements
  830. * and their corresponding URLs:
  831. *
  832. * $this->UrlFor(array('controller'=>'posts','action'=>'recent')); // 'proto://host.com/posts/recent'
  833. * $this->UrlFor(array('controller'=>'posts','action'=>'index')); // 'proto://host.com/posts'
  834. * $this->UrlFor(array('controller'=>'posts','action'=>'show','id'=>10)); // 'proto://host.com/posts/show/10'
  835. *
  836. * When generating a new URL, missing values may be filled in from the current
  837. * Request's parameters. For example,
  838. * <tt>$this->UrlFor(array('action'=>'some_action'));</tt> will retain the current controller,
  839. * as expected. This behavior extends to other parameters, including <tt>controller</tt>,
  840. * <tt>id</tt>, and any other parameters that are placed into a Route's path.
  841. *
  842. * The URL helpers such as <tt>$this->UrlFor</tt> have a limited form of memory:
  843. * when generating a new URL, they can look for missing values in the current Request's parameters.
  844. * Routes attempts to guess when a value should and should not be
  845. * taken from the defaults. There are a few simple rules on how this is performed:
  846. *
  847. * * If the controller name begins with a slash, no defaults are used: <tt>$this->UrlFor(array('controller'=>'/home'));</tt>
  848. * * If the controller changes, the action will default to index unless provided
  849. *
  850. * The final rule is applied while the URL is being generated and is best illustrated by an example. Let us consider the
  851. * route given by <tt>map->connect('people/:last/:first/:action', array('action' => 'bio', 'controller' => 'people'))</tt>.
  852. *
  853. * Suppose that the current URL is "people/hh/david/contacts". Let's consider a few
  854. * different cases of URLs which are generated from this page.
  855. *
  856. * * <tt>$this->UrlFor(array('action'=>'bio'));</tt> -- During the generation of this URL,
  857. * default values will be used for the first and
  858. * last components, and the action shall change. The generated URL will be, "people/hh/david/bio".
  859. * * <tt>$this->UrlFor(array('first'=>'davids-little-brother'));</tt> This
  860. * generates the URL 'people/hh/davids-little-brother' -- note
  861. * that this URL leaves out the assumed action of 'bio'.
  862. *
  863. * However, you might ask why the action from the current Request, 'contacts', isn't
  864. * carried over into the new URL. The answer has to do with the order in which
  865. * the parameters appear in the generated path. In a nutshell, since the
  866. * value that appears in the slot for <tt>first</tt> is not equal to default value
  867. * for <tt>first</tt> we stop using defaults. On it's own, this rule can account
  868. * for much of the typical Akelos Framework URL behavior.
  869. *
  870. * Although a convienence, defaults can occasionaly get in your way. In some cases
  871. * a default persists longer than desired.
  872. * The default may be cleared by adding <tt>'name' => null</tt> to <tt>$this->UrlFor</tt>'s options.
  873. * This is often required when writing form helpers, since the defaults in play
  874. * may vary greatly depending upon where the helper is used from. The following line
  875. * will redirect to PostController's default action, regardless of the page it is
  876. * displayed on:
  877. *
  878. * $this->UrlFor(array('controller' => 'posts', 'action' => null));
  879. *
  880. * If you explicitly want to create a URL that's almost the same as the current URL, you can do so using the
  881. * overwrite_params options. Say for your posts you have different views for showing and printing them.
  882. * Then, in the show view, you get the URL for the print view like this
  883. *
  884. * $this->UrlFor(array('overwrite_params' => array('action' => 'print')));
  885. *
  886. * This takes the current URL as is and only exchanges the action. In contrast,
  887. * <tt>$this->UrlFor(array('action'=>'print'));</tt>
  888. * would have slashed-off the path components after the changed action.
  889. */
  890. function urlFor($options = array(), $parameters_for_method_reference = null)
  891. {
  892. return $this->rewrite($this->rewriteOptions($options));
  893. }
  894. function addToUrl($options = array(), $options_to_exclude = array())
  895. {
  896. $options_to_exclude = array_merge(array('ak','lang',AK_SESSION_NAME,'AK_SESSID','PHPSESSID'), $options_to_exclude);
  897. $options = array_merge(array_merge(array('action'=>$this->Request->getAction()),$this->params),$options);
  898. foreach ($options_to_exclude as $option_to_exclude){
  899. unset($options[$option_to_exclude]);
  900. }
  901. return $this->urlFor($options);
  902. }
  903. function getActionName()
  904. {
  905. return $this->Request->getAction();
  906. }
  907. function _doubleRenderError($message = null)
  908. {
  909. 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);
  910. }
  911. function _hasPerformed()
  912. {
  913. return !empty($this->performed_render) || !empty($this->performed_redirect);
  914. }
  915. function _getRequestOrigin()
  916. {
  917. return $this->Request->remote_ip.' at '.Ak::getDate();
  918. }
  919. function _getCompleteRequestUri()
  920. {
  921. return $this->Request->protocol . $this->Request->host . $this->Request->request_uri;
  922. }
  923. function _closeSession()
  924. {
  925. !empty($this->session) ? session_write_close() : null;
  926. }
  927. function _hasTemplate($template_name = null)
  928. {
  929. return file_exists(empty($template_name) ? $this->getDefaultTemplateName() : $template_name);
  930. }
  931. function _templateIsPublic($template_name = null)
  932. {
  933. $template_name = empty($template_name) ? $this->getDefaultTemplateName() : $template_name;
  934. return $this->Template->fileIsPublic($template_name);
  935. }
  936. function _isTemplateExemptFromLayout($template_name = null)
  937. {
  938. $template_name = empty($template_name) ? $this->getDefaultTemplateName() : $template_name;
  939. return $this->Template->_javascriptTemplateExists($template_name);
  940. }
  941. function _assertExistanceOfTemplateFile($template_name)
  942. {
  943. $extension = $this->Template->delegateTemplateExists($template_name);
  944. $this->full_template_path = $this->Template->getFullTemplatePath($template_name, $extension ? $extension : 'tpl');
  945. if(!$this->_hasTemplate($this->full_template_path)){
  946. if(!empty($this->_ignore_missing_templates) && $this->_ignore_missing_templates === true){
  947. return;
  948. }
  949. $template_type = strstr($template_name,'layouts') ? 'layout' : 'template';
  950. 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);
  951. }
  952. }
  953. function getDefaultTemplateName($default_action_name = null)
  954. {
  955. return empty($default_action_name) ? (empty($this->_default_template_name) ? $this->_action_name : $this->_default_template_name) : $default_action_name;
  956. }
  957. function setDefaultTemplateName($template_name)
  958. {
  959. $this->_default_template_name = $template_name;
  960. }
  961. function rewrite($options = array())
  962. {
  963. return $this->_rewriteUrl($this->_rewritePath($options), $options);
  964. }
  965. function toString()
  966. {
  967. return $this->Request->getProtocol().$this->Request->getHostWithPort().
  968. $this->Request->getPath().@$this->parameters['controller'].
  969. @$this->parameters['action'].@$this->parameters['inspect'];
  970. }
  971. /**
  972. * Given a path and options, returns a rewritten URL string
  973. */
  974. function _rewriteUrl($path, $options)
  975. {
  976. $rewritten_url = '';
  977. if(empty($options['only_path'])){
  978. $rewritten_url .= !empty($options['protocol']) ? $options['protocol'] : $this->Request->getProtocol();
  979. $rewritten_url .= empty($rewritten_url) || strpos($rewritten_url,'://') ? '' : '://';
  980. $rewritten_url .= $this->_rewriteAuthentication($options);
  981. $rewritten_url .= !empty($options['host']) ? $options['host'] : $this->Request->getHostWithPort();
  982. $options = Ak::delete($options, array('user','password','host','protocol'));
  983. }
  984. $rewritten_url .= empty($options['skip_relative_url_root']) ? $this->Request->getRelativeUrlRoot() : '';
  985. if(empty($options['skip_url_locale'])){
  986. $locale = $this->Request->getLocaleFromUrl();
  987. if(empty($options['lang'])){
  988. $rewritten_url .= (empty($locale) ? '' : '/').$locale;
  989. }
  990. }
  991. $rewritten_url .= (substr($rewritten_url,-1) == '/' ? '' : (AK_URL_REWRITE_ENABLED ? '' : (!empty($path[0]) && $path[0] != '/' ? '/' : '')));
  992. $rewritten_url .= $path;
  993. $rewritten_url .= empty($options['trailing_slash']) ? '' : '/';
  994. $rewritten_url .= empty($options['anchor']) ? '' : '#'.$options['anchor'];
  995. return $rewritten_url;
  996. }
  997. function _rewriteAuthentication($options)
  998. {
  999. if(!isset($options['user']) && isset($options['password'])){
  1000. return urlencode($options['user']).':'.urlencode($options['password']).'@';
  1001. }else{
  1002. return '';
  1003. }
  1004. }
  1005. function _rewritePath($options)
  1006. {
  1007. if(!empty($options['params'])){
  1008. foreach ($options['params'] as $k=>$v){
  1009. $options[$k] = $v;
  1010. }
  1011. unset($options['params']);
  1012. }
  1013. if(!empty($options['overwrite_params'])){
  1014. foreach ($options['overwrite_params'] as $k=>$v){
  1015. $options[$k] = $v;
  1016. }
  1017. unset($options['overwrite_params']);
  1018. }
  1019. foreach (array('anchor', 'params', 'only_path', 'host', 'protocol', 'trailing_slash', 'skip_relative_url_root') as $k){
  1020. unset($options[$k]);
  1021. }
  1022. $path = Ak::toUrl($options);
  1023. return $path;
  1024. }
  1025. /**
  1026. * Returns a query string with escaped keys and values from the passed array. If the passed
  1027. * array contains an 'id' it'll
  1028. * be added as a path element instead of a regular parameter pair.
  1029. */
  1030. function buildQueryString($array, $only_keys = null)
  1031. {
  1032. $array = !empty($only_keys) ? array_keys($array) : $array;
  1033. return Ak::toUrl($array);
  1034. }
  1035. /**
  1036. Layouts
  1037. ====================================================================
  1038. *
  1039. * Layouts reverse the common pattern of including shared headers and footers in many templates
  1040. * to isolate changes in repeated setups. The inclusion pattern has pages that look like this:
  1041. *
  1042. * <?php echo $controller->render('shared/header') ?>
  1043. * Hello World
  1044. * <?php echo $controller->render('shared/footer') ?>
  1045. *
  1046. * This approach is a decent way of keeping common structures isolated from the
  1047. * changing content, but it's verbose and if( you ever want to change the structure
  1048. * of these two includes, you'll have to change all the templates.
  1049. *
  1050. * With layouts, you can flip it around and have the common structure know where
  1051. * to insert changing …

Large files files are truncated, but you can click here to view the full file