PHPonTrax
[ class tree: PHPonTrax ] [ index: PHPonTrax ] [ all elements ]

Source for file action_controller.php

Documentation is available at action_controller.php

  1. <?php
  2. /**
  3. * File containing ActionController class
  4. *
  5. * (PHP 5)
  6. *
  7. * @package PHPonTrax
  8. * @version $Id: action_controller.php 198 2006-04-20 16:20:30Z haas $
  9. * @copyright (c) 2005 John Peterson
  10. *
  11. * Permission is hereby granted, free of charge, to any person obtaining
  12. * a copy of this software and associated documentation files (the
  13. * "Software"), to deal in the Software without restriction, including
  14. * without limitation the rights to use, copy, modify, merge, publish,
  15. * distribute, sublicense, and/or sell copies of the Software, and to
  16. * permit persons to whom the Software is furnished to do so, subject to
  17. * the following conditions:
  18. *
  19. * The above copyright notice and this permission notice shall be
  20. * included in all copies or substantial portions of the Software.
  21. *
  22. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  23. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  24. * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  25. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  26. * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  27. * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  28. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  29. */
  30.  
  31. /**
  32. * Action controller
  33. *
  34. * <p>The ActionController base class operates as follows:</p>
  35. * <ol>
  36. * <li>Accept a URL as input</li>
  37. * <li>Translate the URL into a controller and action</li>
  38. * <li>Create the indicated controller object (which is a subclass
  39. * of ActionController) and call its action method</li>
  40. * <li>Render the output of the action method</li>
  41. * <li>Redirect to the next URL</li>
  42. * </ol>
  43. *
  44. * For details see the
  45. * {@tutorial PHPonTrax/ActionController.cls class tutorial}
  46. */
  47. class ActionController {
  48.  
  49. /**
  50. * Name of the controller (without the _controller.php)
  51. *
  52. * Set by {@link recognize_route()} by parsing the URL and the
  53. * routes in {@link routes.php}. The value of this string is set
  54. * before any attempt is made to find the file containing the
  55. * controller.
  56. * @var string
  57. */
  58. private $controller;
  59.  
  60. /**
  61. * Name of the action method in the controller class
  62. *
  63. * Set by {@link recognize_route()}
  64. * @var string
  65. */
  66. private $action;
  67.  
  68. /**
  69. * Value of :id parsed from URL then forced to lower case
  70. *
  71. * Set by {@link recognize_route()}
  72. * @var string
  73. */
  74. private $id;
  75.  
  76. /**
  77. * Path to add to other filesystem paths
  78. *
  79. * Set by {@link recognize_route()}
  80. * @var string
  81. */
  82. private $added_path = '';
  83. /**
  84. * Parameters for the action routine
  85. *
  86. * Set by {@link recognize_route()}, passed as arguments to the
  87. * controller's action routine.
  88. * @var string[]
  89. */
  90. private $action_params = array();
  91.  
  92. /**
  93. * Filesystem path to ../app/controllers/ directory
  94. *
  95. * Set by {@link recognize_route()}
  96. * @var string
  97. */
  98. private $controllers_path;
  99.  
  100. /**
  101. * Filesystem path to ../app/helpers/<i>extras</i> directory
  102. *
  103. * Set by {@link recognize_route()}, {@link set_paths()}
  104. * @var string
  105. */
  106. private $helpers_path;
  107.  
  108. /**
  109. * Filesystem path to ../app/helpers/ directory
  110. *
  111. * Set by {@link recognize_route()}
  112. * @var string
  113. */
  114. private $helpers_base_path;
  115.  
  116. /**
  117. * Filesystem path to ../app/views/layouts/<i>extras</i> directory
  118. *
  119. * Set by {@link recognize_route()}, {@link set_paths()}
  120. * @var string
  121. */
  122. private $layouts_path;
  123.  
  124. /**
  125. * Filesystem path to ../app/views/layouts/ directory
  126. *
  127. * Set by {@link recognize_route()}
  128. * @var string
  129. */
  130. private $layouts_base_path;
  131.  
  132. /**
  133. * User's URL in components
  134. *
  135. * Contains user's URL stripped of TRAX_URL_PREFIX and leading
  136. * and trailing slashes, then exploded into an array on slash
  137. * boundaries.
  138. * @var string[]
  139. */
  140. private $url_path;
  141.  
  142. /**
  143. * Filesystem path to the controllername_helper.php file
  144. *
  145. * Set by {@link recognize_route()}
  146. * @var string
  147. */
  148. private $helper_file;
  149.  
  150. /**
  151. * Filesystem path to application.php file
  152. *
  153. * Set by {@link recognize_route()}
  154. * @see $controller_file
  155. * @var string
  156. */
  157. private $application_controller_file;
  158.  
  159. /**
  160. * Filesystem path to application_helper.php file
  161. *
  162. * Set by {@link recognize_route()}
  163. * @var string
  164. */
  165. private $application_helper_file;
  166.  
  167. /**
  168. * URL recognized, paths resoved, controller file found
  169. *
  170. * Set by {@link recognize_route()}
  171. * @var boolean
  172. */
  173. private $loaded = false;
  174.  
  175. /**
  176. * Whether a Router object was loaded
  177. *
  178. * @var boolean
  179. * <ul>
  180. * <li>true => $router points to the Router object</li>
  181. * <li>false => no Router object exists</li>
  182. * </ul>
  183. * @todo <b>FIXME:</b> No declaration of $router so no place to hang
  184. * its documentation.
  185. */
  186. private $router_loaded = false;
  187.  
  188. /**
  189. * List of additional helper files for this controller object
  190. *
  191. * Set by {@link add_helper()}
  192. * @var string[]
  193. */
  194. private $helpers = array();
  195.  
  196. /**
  197. * List of filters to execute before calling action method
  198. *
  199. * Set by {@link add_before_filters()}
  200. * @var string[]
  201. */
  202. private $before_filters = array();
  203.  
  204. /**
  205. * List of filters to execute after calling action method
  206. *
  207. * Set by {@link add_after_filters()}
  208. * @var string[]
  209. */
  210. private $after_filters = array();
  211.  
  212. /**
  213. * @todo Document this attribute
  214. */
  215. private $render_performed = false;
  216.  
  217. /**
  218. * @todo Document this attribute
  219. */
  220. private $action_called = false;
  221.  
  222. /**
  223. * @todo Document this attribute
  224. */
  225. protected $before_filter = null;
  226.  
  227. /**
  228. * @todo Document this attribute
  229. */
  230. protected $after_filter = null;
  231.  
  232. /**
  233. * Filesystem path to the PHP program file for this controller
  234. *
  235. * Set by {@link recognize_route()}
  236. * @see $application_controller_file
  237. * @var string
  238. */
  239. public $controller_file;
  240.  
  241. /**
  242. * Filesystem path to the view file selected for this action
  243. *
  244. * Set by {@link process_route()}
  245. * @var string
  246. */
  247. public $view_file;
  248.  
  249. /**
  250. * Filesystem path to the ../app/views/ directory
  251. *
  252. * Set by {@link recognize_route()}
  253. * @var string
  254. */
  255. public $views_path;
  256.  
  257. /**
  258. * Class name of the controller
  259. *
  260. * Set by {@link recognize_route()}.
  261. * Derived from contents of {@link $controller}.
  262. * @var string
  263. */
  264. public $controller_class;
  265.  
  266. /**
  267. * Instance of the controller class
  268. *
  269. * Set by {@link process_route()}
  270. * @var object
  271. */
  272. public $controller_object;
  273.  
  274. /**
  275. * @todo Document this attribute
  276. * @todo <b>FIXME:</b> Not referenced in this class - is it used
  277. * by subclasses? If so, for what?
  278. * @var string
  279. */
  280. public $asset_host = null;
  281.  
  282. /**
  283. * File extension appended to view files
  284. *
  285. * Set from a define in {@link environment.php}. Usually phtml
  286. * @var string
  287. */
  288. public $views_file_extention = TRAX_VIEWS_EXTENTION;
  289.  
  290. /**
  291. * Render controllers layout
  292. *
  293. * Can be overridden in the child controller to false
  294. * @var boolean
  295. */
  296. public $render_layout = true;
  297. /**
  298. * Whether to keep flash message after displaying it
  299. * @var boolean
  300. */
  301. public $keep_flash = false;
  302.  
  303. /**
  304. * Build a Router object and load routes from config/route.php
  305. * @uses load_router()
  306. */
  307. function __construct() {
  308. if(!isset($this->router) || !is_object($this->router)) {
  309. $this->load_router();
  310. }
  311. }
  312.  
  313. /**
  314. * @todo Document this method
  315. * @uses add_after_filter()
  316. * @uses add_before_filter()
  317. * @uses add_helper()
  318. */
  319. function __set($key, $value) {
  320. //error_log("__set($key, $value)");
  321. if($key == "before_filter") {
  322. $this->add_before_filter($value);
  323. } elseif($key == "after_filter") {
  324. $this->add_after_filter($value);
  325. } elseif($key == "helper") {
  326. $this->add_helper($value);
  327. } elseif($key == "render_text") {
  328. $this->render_text($value);
  329. } elseif($key == "redirect_to") {
  330. $this->redirect_to($value);
  331. } else {
  332. $this->$key = $value;
  333. }
  334. }
  335.  
  336. /**
  337. * @todo Document this method
  338. * Implement before_filter(), after_filter(), helper()
  339. */
  340. function __call($method_name, $parameters) {
  341. if(method_exists($this, $method_name)) {
  342. # If the method exists, just call it
  343. $result = call_user_func(array($this, $method_name), $parameters);
  344. } else {
  345. if($method_name == "before_filter") {
  346. $result = call_user_func(array($this, 'add_before_filter'), $parameters);
  347. } elseif($method_name == "after_filter") {
  348. $result = call_user_func(array($this, 'add_after_filter'), $parameters);
  349. } elseif($method_name == "helper") {
  350. $result = call_user_func(array($this, 'add_helper'), $parameters);
  351. }
  352. }
  353. return $result;
  354. }
  355.  
  356. /**
  357. * Load routes from configuration file config/routes.php
  358. *
  359. * Routes are loaded by requiring {@link routes.php} from the
  360. * configuration directory. The file routes.php contains
  361. * statements of the form "$router->connect(path,params);" where
  362. * (path,params) describes the route being added by the
  363. * statement. Route syntax is described in
  364. * {@tutorial PHPonTrax/Router.cls the Router class tutorial}.
  365. *
  366. * @uses Router
  367. * @uses $router
  368. * @uses $router_loaded
  369. */
  370. function load_router() {
  371. $this->router_loaded = false;
  372. $router = new Router();
  373.  
  374. // Load the routes.
  375. require(TRAX_ROOT.$GLOBALS['TRAX_INCLUDES']['config']."/routes.php");
  376. $this->router = $router;
  377. if(is_object($this->router)) {
  378. $this->router_loaded = true;
  379. }
  380. }
  381.  
  382. /**
  383. * Convert URL to controller, action and id
  384. *
  385. * Parse the URL in
  386. * {@link }
  387. * http://www.php.net/manual/en/reserved.variables.php#reserved.variables.server $_SERVER}['REDIRECT_URL']
  388. * into elements.
  389. * Compute filesystem paths to the various components used by the
  390. * URL and store the paths in object private variables.
  391. * Verify that the controller exists.
  392. *
  393. * @uses load_router()
  394. * @uses $action
  395. * @uses $action_params
  396. * @uses $application_controller_file
  397. * @uses $controller
  398. * @uses $controller_class
  399. * @uses $controller_file
  400. * @uses $controllers_path
  401. * @uses $helper_file
  402. * @uses $helpers_path
  403. * @uses $id
  404. * @uses $layouts_path
  405. * @uses $loaded
  406. * @uses $router
  407. * @uses $router_loaded
  408. * @uses set_paths()
  409. * @uses $url_path
  410. * @uses $views_file_extention
  411. * @uses $views_path
  412. * @return boolean
  413. * <ul>
  414. * <li>true => route recognized, controller found.</li>
  415. * <li>false => failed, route not recognized.</li>
  416. * </ul>
  417. */
  418. function recognize_route() {
  419. if(!$this->router_loaded) {
  420. $this->load_router();
  421. }
  422.  
  423. # current url
  424. $browser_url = $_SERVER['REDIRECT_URL'];
  425. #if(strstr($_SERVER['REQUEST_URI'], "?"))
  426. # $browser_url = substr($_SERVER['REQUEST_URI'], 0, strpos($_SERVER['REQUEST_URI'], "?"));
  427. #else
  428. # $browser_url = $_SERVER['REQUEST_URI'];
  429. //error_log('browser url='.$browser_url);
  430. # strip off url prefix, if any
  431. if(!is_null(TRAX_URL_PREFIX)) {
  432. $browser_url = str_replace(TRAX_URL_PREFIX,"",$browser_url);
  433. }
  434.  
  435. # strip leading slash
  436. // FIXME: Do we know for sure that the
  437. // initial '/' will be there?
  438. $browser_url = substr($browser_url,1);
  439.  
  440. # strip trailing slash (if any)
  441. if(substr($browser_url, -1) == "/") {
  442. $browser_url = substr($browser_url, 0, -1);
  443. }
  444.  
  445. if($browser_url) {
  446. $this->url_path = explode("/", $browser_url);
  447. } else {
  448. $this->url_path = array();
  449. }
  450.  
  451. if($this->router->routes_count > 0) {
  452. $this->controllers_path = TRAX_ROOT . $GLOBALS['TRAX_INCLUDES']['controllers'];
  453. $this->helpers_path = $this->helpers_base_path = TRAX_ROOT . $GLOBALS['TRAX_INCLUDES']['helpers'];
  454. $this->application_controller_file = $this->controllers_path . "/application.php";
  455. $this->application_helper_file = $this->helpers_path . "/application_helper.php";
  456. $this->layouts_path = TRAX_ROOT . $GLOBALS['TRAX_INCLUDES']['layouts'];
  457. $this->views_path = TRAX_ROOT . $GLOBALS['TRAX_INCLUDES']['views'];
  458.  
  459. $route = $this->router->find_route($browser_url);
  460.  
  461. // find_route() returns an array if it finds a path that
  462. // matches the URL, null if no match found
  463. if(is_array($route)) {
  464.  
  465. // Matching route found. Try to get
  466. // controller and action from route and URL
  467. $this->set_paths();
  468. $route_path = explode("/",$route['path']);
  469. $route_params = $route['params'];
  470.  
  471. // Find the controller from the route and URL
  472. if(is_array($route_params)
  473. && array_key_exists(":controller",$route_params)) {
  474.  
  475. // ':controller' in route params overrides URL
  476. $this->controller = $route_params[":controller"];
  477. } elseif(is_array($route_path)
  478. && in_array(":controller",$route_path)
  479. && (count($this->url_path)>0)) {
  480.  
  481. // Set controller from URL if that field exists
  482. $this->controller = strtolower($this->url_path[array_search(":controller", $route_path)]);
  483. }
  484. //error_log('controller='.$this->controller);
  485.  
  486. // Find the action from the route and URL
  487. if(is_array($route_params)
  488. && array_key_exists(":action",$route_params)) {
  489.  
  490. // ':action' in route params overrides URL
  491. $this->action = $route_params[':action'];
  492. } elseif(is_array($route_path)
  493. && in_array(":action",$route_path)
  494. && array_key_exists(@array_search(":action",
  495. $route_path),
  496. $this->url_path)) {
  497.  
  498. // Get action from URL if that field exists
  499. $this->action = strtolower($this->url_path[@array_search(":action", $route_path)]);
  500. }
  501. //error_log('action='.$this->action);
  502. // FIXME: RoR uses :name as a keyword parameter, id
  503. // is not treated as a special case.
  504. // Do we want to do the same?
  505. if(@in_array(":id",$route_path)
  506. && array_key_exists(@array_search(":id", $route_path),
  507. $this->url_path)) {
  508. $this->id = strtolower($this->url_path[@array_search(":id", $route_path)]);
  509. // Parameters for the action routine.
  510. // FIXME: make more general than just id
  511. if($this->id != "") {
  512. $this->action_params['id'] = $this->id;
  513. }
  514. // For historical reasons, continue to pass id
  515. // in $_REQUEST
  516. if($this->id != "") {
  517. $_REQUEST['id'] = $this->id;
  518. }
  519. }
  520. $this->views_path .= "/" . $this->controller;
  521. $this->controller_file = $this->controllers_path . "/" . $this->controller . "_controller.php";
  522. $this->controller_class = Inflector::camelize($this->controller) . "Controller";
  523. $this->helper_file = $this->helpers_path . "/" . $this->controller . "_helper.php";
  524. }
  525. }
  526.  
  527. if(file_exists($this->controller_file)) {
  528. $this->loaded = true;
  529. return true;
  530. } else {
  531. $this->loaded = false;
  532. return false;
  533. }
  534. }
  535.  
  536. /**
  537. * Parse URL, extract controller and action and execute them
  538. *
  539. * @uses $action
  540. * @uses $action_params
  541. * @uses $application_controller_file
  542. * @uses $application_helper_file
  543. * @uses $controller
  544. * @uses $controller_class
  545. * @uses $controller_file
  546. * @uses $controller_object
  547. * @uses determine_layout()
  548. * @uses execute_after_filters()
  549. * @uses $helpers
  550. * @uses $helper_file
  551. * @uses $helpers_base_path
  552. * @uses $keep_flash
  553. * @uses $loaded
  554. * @uses recognize_route()
  555. * @uses raise()
  556. * @uses ScaffoldController
  557. * @uses Session::unset_var()
  558. * @uses $view_file
  559. * @uses $views_file_extention
  560. * @uses $views_path
  561. * @return boolean true
  562. */
  563. function process_route() {
  564. # First try to load the routes and setup the paths to everything
  565. if(!$this->loaded) {
  566. if(!$this->recognize_route()) {
  567. $this->raise("Failed to load any defined routes",
  568. "Controller ".$this->controller." not found",
  569. "404");
  570. }
  571. }
  572. //error_log('process_route(): controller="'.$this->controller
  573. // .'" action="'.$this->action.'" id="'.$this->id.'"');
  574.  
  575. # Include main application controller file
  576. if(file_exists($this->application_controller_file)) {
  577. include_once($this->application_controller_file);
  578. }
  579.  
  580. # If controller is loaded then start processing
  581. if($this->loaded) {
  582. include_once($this->controller_file);
  583. if(class_exists($this->controller_class, false)) {
  584. $class = $this->controller_class;
  585. $this->controller_object = new $class();
  586. if(is_object($this->controller_object)) {
  587. $this->controller_object->controller = $this->controller;
  588. $this->controller_object->action = $this->action;
  589. $this->controller_object->controller_path = "$this->added_path/$this->controller";
  590. $this->controller_object->views_path = $this->views_path;
  591. $this->controller_object->layouts_path = $this->layouts_path;
  592. $GLOBALS['current_controller_path'] = "$this->added_path/$this->controller";
  593. $GLOBALS['current_controller_name'] = $this->controller;
  594. $GLOBALS['current_action_name'] = $this->action;
  595. $GLOBALS['current_controller_object'] =& $this->controller_object;
  596. # Which layout should we use?
  597. $layout_file = $this->controller_object->determine_layout();
  598. //error_log('using layout_file "'.$layout_file.'"');
  599. # Check if there is any defined scaffolding to load
  600. if(isset($this->controller_object->scaffold)) {
  601. $scaffold = $this->controller_object->scaffold;
  602. if(file_exists(TRAX_LIB_ROOT."/scaffold_controller.php")) {
  603. include_once(TRAX_LIB_ROOT."/scaffold_controller.php");
  604. $this->controller_object = new ScaffoldController($scaffold);
  605. $GLOBALS['current_controller_object'] =& $this->controller_object;
  606. $render_options['scaffold'] = true;
  607. if(!file_exists($layout_file)) {
  608. # the generic scaffold layout
  609. $layout_file = TRAX_LIB_ROOT . "/templates/scaffolds/layout.phtml";
  610. }
  611. }
  612. }
  613. }
  614. }
  615.  
  616. # Include main application helper file
  617. if(file_exists($this->application_helper_file)) {
  618. include_once($this->application_helper_file);
  619. }
  620.  
  621. # Include helper file for this controller
  622. if(file_exists($this->helper_file)) {
  623. include_once($this->helper_file);
  624. }
  625. if(is_object($this->controller_object)) {
  626.  
  627. # Include any extra helper files defined in this controller
  628. if(count($this->controller_object->helpers) > 0) {
  629. foreach($this->controller_object->helpers as $helper) {
  630. if(strstr($helper, "/")) {
  631. $file = substr(strrchr($helper, "/"), 1);
  632. $path = substr($helper, 0, strripos($helper, "/"));
  633. $helper_path_with_file = $this->helpers_base_path."/".$path."/".$file."_helper.php";
  634. } else {
  635. $helper_path_with_file = $this->helpers_base_path."/".$helper."_helper.php";
  636. }
  637.  
  638. if(file_exists($helper_path_with_file)) {
  639. # Include the helper file
  640. include($helper_path_with_file);
  641. }
  642. }
  643. }
  644.  
  645. # Suppress output
  646. ob_start();
  647. //error_log('started capturing HTML');
  648. # Call the controller method based on the URL
  649. if($this->controller_object->execute_before_filters()) {
  650. if(method_exists($this->controller_object, $this->action)) {
  651. //error_log('method '.$this->action.' exists, calling it');
  652. $action = $this->action;
  653. //error_log('calling action routine '
  654. // . get_class($this->controller_object)
  655. // .'::'.$action.'() with params '
  656. // .var_export($this->action_params,true));
  657. $this->controller_object->$action($this->action_params);
  658. } elseif(file_exists($this->views_path . "/" . $this->action . "." . $this->views_file_extention)) {
  659. //error_log('views file "'.$this->action.'"');
  660. $action = $this->action;
  661. } elseif(method_exists($this->controller_object, "index")) {
  662. //error_log('calling action routine '
  663. // . get_class($this->controller_object)
  664. // .'::index() with params '
  665. // .var_export($this->action_params,true));
  666. $action = "index";
  667. $this->controller_object->index($this->action_params);
  668. } else {
  669. //error_log('no action');
  670. $this->raise("No action responded to ".$this->action, "Unknown action", "404");
  671. }
  672. $this->controller_object->execute_after_filters();
  673. $this->controller_object->action_called = true;
  674. # Find out if there was a redirect to some other page
  675. if(isset($this->controller_object->redirect_to)
  676. && $this->controller_object->redirect_to != '') {
  677. $this->redirect_to($this->controller_object->redirect_to);
  678. # execution will end here redirecting to new page
  679. }
  680. # If render_text was defined as a string render it
  681. if(isset($this->controller_object->render_text)
  682. && $this->controller_object->render_text != "") {
  683. $this->render_text($this->controller_object->render_text);
  684. # execution will end here rendering only the text no layout
  685. }
  686. # If defined string render_action use that instead
  687. if(isset($this->controller_object->render_action)
  688. && $this->controller_object->render_action != '') {
  689. $action = $this->controller_object->render_action;
  690. }
  691. # Render the action / view
  692. if(!$this->controller_object->render_action($action,
  693. isset($render_options) ? $render_options : null )) {
  694. $this->raise("No view file found $action ($this->view_file).", "Unknown view", "404");
  695. }
  696. # Grab all the html from the view to put into the layout
  697. $content_for_layout = ob_get_contents();
  698. ob_end_clean();
  699. //error_log("captured ".strlen($content_for_layout)." bytes\n");
  700. if(isset($this->controller_object->render_layout)
  701. && ($this->controller_object->render_layout !== false)
  702. && $layout_file) {
  703. $locals['content_for_layout'] = $content_for_layout;
  704. # render the layout
  705. //error_log("rendering layout: $layout_file");
  706. if(!$this->controller_object->render_file($layout_file, false, $locals)) {
  707. # No layout template so just echo out whatever is in $content_for_layout
  708. echo "HERE";
  709. echo $content_for_layout;
  710. }
  711. } else {
  712. # Can't find any layout so throw an exception
  713. # $this->raise("No layout file found.", "Unknown layout", "404");
  714. # No layout template so just echo out whatever is in $content_for_layout
  715. echo $content_for_layout;
  716. }
  717. }
  718. } else {
  719. $this->raise("Failed to instantiate controller object \"".$this->controller."\".", "ActionController Error", "500");
  720. }
  721. } else {
  722. $this->raise("No controller found.", "Unknown controller", "404");
  723. }
  724.  
  725. // error_log('keep flash='.var_export($this->keep_flash,true));
  726. if(!$this->keep_flash) {
  727. # Nuke the flash
  728. Session::unset_var('flash');
  729. }
  730.  
  731. return true;
  732. } // function process_route()
  733.  
  734. /**
  735. * Extend the search path for components
  736. *
  737. * On entry, $url_path is set according to the browser's URL and
  738. * $controllers_path has been set according to the configuration
  739. * in {@link environment.php config/environment.php} . Examine
  740. * the $controllers_path directory for files or directories that
  741. * match any component of the URL. If one is found, add that
  742. * component to all paths. Replace the contents of $url_path
  743. * with the list of URL components that did NOT match any files
  744. * or directories.
  745. * @uses $added_path
  746. * @uses $controllers_path
  747. * @uses $helpers_path
  748. * @uses $layouts_path
  749. * @uses $views_path
  750. * @uses $url_path
  751. * @todo <b>FIXME:</b> Creating a file or directory in
  752. * app/controllers with the same name as a controller, action or
  753. * other URL element will hijack the browser!
  754. */
  755. function set_paths() {
  756. if(is_array($this->url_path)) {
  757. foreach($this->url_path as $path) {
  758. if(file_exists($this->controllers_path . "/$path")) {
  759. $extra_path[] = $path;
  760. } else {
  761. $new_path[] = $path;
  762. }
  763. }
  764. if(isset($extra_path) && is_array($extra_path)) {
  765. $extra_path = implode("/", $extra_path);
  766. $this->added_path = $extra_path;
  767. $this->controllers_path .= "/$extra_path";
  768. $this->helpers_path .= "/$extra_path";
  769. $this->views_path .= "/$extra_path";
  770. $this->layouts_path .= "/$extra_path";
  771. }
  772. if(isset($new_path)
  773. && is_array($new_path)) {
  774. $this->url_path = $new_path;
  775. }
  776. }
  777. }
  778.  
  779. /**
  780. * Execute the before filters
  781. * @uses $before_filters
  782. */
  783. function execute_before_filters() {
  784. $return = true;
  785. if(count($this->before_filters) > 0) {
  786. foreach($this->before_filters as $filter_function) {
  787. if(method_exists($this, $filter_function)) {
  788. if(false === $this->$filter_function()) {
  789. //error_log("execute_before_filters(): returning false");
  790. $return = false;
  791. }
  792. }
  793. }
  794. }
  795. return $return;
  796. }
  797.  
  798. /**
  799. * Append a before filter to the filter chain
  800. *
  801. * @param mixed $filter_function_name String with the name of
  802. * one filter function, or array of strings with the names of
  803. * several filter functions.
  804. * @uses $before_filters
  805. */
  806. function add_before_filter($filter_function_name) {
  807. //error_log("adding before filter: $filter_function_name");
  808. if(is_string($filter_function_name) && !empty($filter_function_name)) {
  809. if(!in_array($filter_function_name, $this->before_filters)) {
  810. $this->before_filters[] = $filter_function_name;
  811. }
  812. } elseif(is_array($filter_function_name)) {
  813. if(count($this->before_filters) > 0) {
  814. $this->before_filters = array_merge($this->before_filters, $filter_function_name);
  815. } else {
  816. $this->before_filters = $filter_function_name;
  817. }
  818. }
  819. }
  820.  
  821. /**
  822. * Execute the after filters
  823. * @uses $after_filters
  824. */
  825. function execute_after_filters() {
  826. if(count($this->after_filters) > 0) {
  827. foreach($this->after_filters as $filter_function) {
  828. if(method_exists($this, $filter_function)) {
  829. $this->$filter_function();
  830. }
  831. }
  832. }
  833. }
  834.  
  835.  
  836. /**
  837. * Append an after filter to the filter chain
  838. *
  839. * @param mixed $filter_function_name String with the name of
  840. * one filter function, or array of strings with the names of
  841. * several filter functions.
  842. * @uses $after_filters
  843. */
  844. function add_after_filter($filter_function_name) {
  845. if(is_string($filter_function_name) && !empty($filter_function_name)) {
  846. if(!in_array($filter_function_name, $this->after_filters)) {
  847. $this->after_filters[] = $filter_function_name;
  848. }
  849. } elseif(is_array($filter_function_name)) {
  850. if(count($this->after_filters) > 0) {
  851. $this->after_filters = array_merge($this->after_filters, $filter_function_name);
  852. } else {
  853. $this->after_filters = $filter_function_name;
  854. }
  855. }
  856. }
  857.  
  858. /**
  859. * Add a helper to the list of helpers used by a controller
  860. * object
  861. *
  862. * @param $helper_name string Name of a helper to add to the list
  863. * @uses $helpers
  864. * @uses $controller_object
  865. */
  866. function add_helper($helper_name) {
  867. if(!in_array($helper_name, $this->helpers)) {
  868. $this->helpers[] = $helper_name;
  869. }
  870. }
  871.  
  872. /**
  873. *
  874. * Renders the content that will be returned to the browser as the response body.
  875. *
  876. */
  877. function render($options = array(), $locals = array(), $return_as_string = false) {
  878. if($this->render_performed && !$this->action_called) {
  879. return true;
  880. }
  881. if($return_as_string) {
  882. # start to buffer output
  883. ob_start();
  884. }
  885. if(is_string($options)) {
  886. $this->render_file($options, true, $locals);
  887. } elseif(is_array($options)) {
  888. $options['locals'] = $options['locals'] ? $options['locals'] : array();
  889. $options['use_full_path'] = !$options['use_full_path'] ? true : $options['use_full_path'];
  890. if($options['text']) {
  891. $this->render_text($options['text']);
  892. } else {
  893. if($options['action']) {
  894. $this->render_action($options['action'], $options);
  895. } elseif($options['file']) {
  896. $this->render_file($options['file'], $options['use_full_path'], $options['locals']);
  897. } elseif($options['partial']) {
  898. $this->render_partial($options['partial'], $options);
  899. } elseif($options['nothing']) {
  900. # Safari doesn't pass the headers of the return if the response is zero length
  901. $this->render_text(" ");
  902. }
  903. }
  904. }
  905. $this->render_performed = true;
  906.  
  907. if($return_as_string) {
  908. $result = ob_get_contents();
  909. ob_end_clean();
  910. return $result;
  911. }
  912. }
  913. /**
  914. *
  915. * Rendering of text is usually used for tests or for rendering prepared content.
  916. * By default, text rendering is not done within the active layout.
  917. *
  918. * # Renders the clear text "hello world"
  919. * render(array("text" => "hello world!"))
  920. *
  921. * # Renders the clear text "Explosion!"
  922. * render(array("text" => "Explosion!"))
  923. *
  924. * # Renders the clear text "Hi there!" within the current active layout (if one exists)
  925. * render(array("text" => "Explosion!", "layout" => true))
  926. *
  927. * # Renders the clear text "Hi there!" within the layout
  928. * # placed in "app/views/layouts/special.phtml"
  929. * render(array("text" => "Explosion!", "layout" => "special"))
  930. *
  931. */
  932. function render_text($text, $options = array()) {
  933. if($options['layout']) {
  934. $locals['content_for_layout'] = $text;
  935. $layout = $this->determine_layout();
  936. $this->render_file($layout, false, $locals);
  937. } else {
  938. echo $text;
  939. }
  940. exit;
  941. }
  942.  
  943. /**
  944. *
  945. * Action rendering is the most common form and the type used automatically by
  946. * Action Controller when nothing else is specified. By default, actions are
  947. * rendered within the current layout (if one exists).
  948. *
  949. * # Renders the template for the action "goal" within the current controller
  950. * render(array("action" => "goal"))
  951. *
  952. * # Renders the template for the action "short_goal" within the current controller,
  953. * # but without the current active layout
  954. * render(array("action" => "short_goal", "layout" => false))
  955. *
  956. * # Renders the template for the action "long_goal" within the current controller,
  957. * # but with a custom layout
  958. * render(array("action" => "long_goal", "layout" => "spectacular"))
  959. *
  960. */
  961. function render_action($action, $options = array()) {
  962. if($this->render_performed) {
  963. return true;
  964. }
  965. if($options['layout']) {
  966. $this->layout = $options['layout'];
  967. }
  968. if($options['scaffold']) {
  969. $this->view_file = TRAX_LIB_ROOT."/templates/scaffolds/".$action.".phtml";
  970. } else {
  971. $this->view_file = $this->views_path . "/" . $action . "." . $this->views_file_extention;
  972. }
  973. //error_log(get_class($this)." - render_action() view_file: $this->view_file");
  974. return $this->render_file($this->view_file);
  975. }
  976. /**
  977. *
  978. * Renders according to the same rules as render, but returns the result in a string
  979. * instead of sending it as the response body to the browser.
  980. *
  981. */
  982. function render_to_string($options = array(), $locals = array()) {
  983. return $this->render($options, $locals, true);
  984. }
  985.  
  986. /**
  987. *
  988. * File rendering works just like action rendering except that it takes a filesystem path.
  989. * By default, the path is assumed to be absolute, and the current layout is not applied.
  990. *
  991. * # Renders the template located at the absolute filesystem path
  992. * render(array("file" => "/path/to/some/template.phtml"))
  993. * render(array("file" => "c:/path/to/some/template.phtml"))
  994. *
  995. * # Renders a template within the current layout
  996. * render(array("file" => "/path/to/some/template.rhtml", "layout" => true))
  997. * render(array("file" => "c:/path/to/some/template.rhtml", "layout" => true))
  998. *
  999. * # Renders a template relative to app/views
  1000. * render(array("file" => "some/template", "use_full_path" => true))
  1001. */
  1002. function render_file($path, $use_full_path = false, $locals = array()) {
  1003. if($this->render_performed && !$this->action_called) {
  1004. return true;
  1005. }
  1006. # Renders a template relative to app/views
  1007. if($use_full_path) {
  1008. $path = $this->views_path."/".$path.".".$this->views_file_extention;
  1009. }
  1010.  
  1011. //error_log("render_file() path:$path");
  1012. if(file_exists($path)) {
  1013.  
  1014. # Pull all the class vars out and turn them from $this->var to $var
  1015. if(is_object($this)) {
  1016. $controller_locals = get_object_vars($this);
  1017. }
  1018. if(is_array($controller_locals)) {
  1019. $locals = array_merge($controller_locals, $locals);
  1020. }
  1021. if(count($locals)) {
  1022. foreach($locals as $tmp_key => $tmp_value) {
  1023. ${$tmp_key} = $tmp_value;
  1024. }
  1025. unset($tmp_key);
  1026. unset($tmp_value);
  1027. }
  1028. include($path);
  1029. $this->render_performed = true;
  1030. return true;
  1031. }
  1032. return false;
  1033. }
  1034.  
  1035. /**
  1036. * Rendering partials
  1037. *
  1038. * <p>Partial rendering is most commonly used together with Ajax
  1039. * calls that only update one or a few elements on a page without
  1040. * reloading. Rendering of partials from the controller makes it
  1041. * possible to use the same partial template in both the
  1042. * full-page rendering (by calling it from within the template)
  1043. * and when sub-page updates happen (from the controller action
  1044. * responding to Ajax calls). By default, the current layout is
  1045. * not used.</p>
  1046. *
  1047. * <ul>
  1048. * <li><samp>render_partial("win");</samp><br>
  1049. * Renders the partial
  1050. * located at app/views/controller/_win.phtml</li>
  1051. *
  1052. * <li><samp>render_partial("win",
  1053. * array("locals" => array("name" => "david")));</samp><br>
  1054. * Renders the same partial but also makes a local variable
  1055. * available to it</li>
  1056. *
  1057. * <li><samp>render_partial("win",
  1058. * array("collection" => array(...)));</samp><br>
  1059. * Renders a collection of the same partial by making each
  1060. * element of the collection available through the local variable
  1061. * "win" as it builds the complete response </li>
  1062. *
  1063. * <li><samp>render_partial("win", array("collection" => $wins,
  1064. * "spacer_template" => "win_divider"));</samp><br>
  1065. * Renders the same collection of partials, but also renders
  1066. * the win_divider partial in between each win partial.</li>
  1067. * </ul>
  1068. * @param string $path Path to file containing partial view
  1069. * @param string[] $options Options array
  1070. */
  1071. function render_partial($path, $options = array()) {
  1072. if(strstr($path, "/")) {
  1073. $file = substr(strrchr($path, "/"), 1);
  1074. $path = substr($path, 0, strripos($path, "/"));
  1075. $file_with_path = TRAX_ROOT.$GLOBALS['TRAX_INCLUDES']['views']."/".$path."/_".$file.".".$this->views_file_extention;
  1076. } else {
  1077. $file = $path;
  1078. $file_with_path = $this->views_path."/_".$file.".".$this->views_file_extention;
  1079. }
  1080.  
  1081. if(file_exists($file_with_path)) {
  1082. if(array_key_exists("spacer_template", $options)) {
  1083. $spacer_path = $options['spacer_template'];
  1084. if(strstr($spacer_path, "/")) {
  1085. $spacer_file = substr(strrchr($spacer_path, "/"), 1);
  1086. $spacer_path = substr($spacer_path, 0, strripos($spacer_path, "/"));
  1087. $spacer_file_with_file = TRAX_ROOT.$GLOBALS['TRAX_INCLUDES']['views']."/".$spacer_path."/_".$spacer_file.".".$this->views_file_extention;
  1088. } else {
  1089. $spacer_file = $spacer_path;
  1090. $spacer_file_with_file = $this->views_path."/_".$spacer_file.".".$this->views_file_extention;
  1091. }
  1092. if(file_exists($spacer_file_with_file)) {
  1093. $add_spacer = true;
  1094. }
  1095. }
  1096.  
  1097. $locals = array_key_exists("locals", $options) ? $options['locals'] : array();
  1098. if(array_key_exists("collection", $options)) {
  1099. foreach($options['collection'] as $tmp_value) {
  1100. ${$file."_counter"}++;
  1101. $locals[$file] = $tmp_value;
  1102. $locals[$file."_counter"] = ${$file."_counter"};
  1103. unset($tmp_value);
  1104. $this->render_performed = false;
  1105. $this->render_file($file_with_path, false, $locals);
  1106. if($add_spacer && (${$file."_counter"} < count($options['collection']))) {
  1107. $this->render_performed = false;
  1108. $this->render_file($spacer_file_with_file, false, $locals);
  1109. }
  1110. }
  1111. $this->render_performed = true;
  1112. } else {
  1113. $this->render_file($file_with_path, false, $locals);
  1114. }
  1115. }
  1116. }
  1117.  
  1118. /**
  1119. * Select a layout file based on the controller object
  1120. *
  1121. * @uses $controller_object
  1122. * @uses $layouts_base_path
  1123. * @uses $layouts_path
  1124. * @return mixed Layout file or null if none
  1125. * @todo <b>FIXME:</b> Should this method be private?
  1126. */
  1127. function determine_layout($full_path = true) {
  1128.  
  1129. // If the controller defines $layout and sets it
  1130. // to NULL, that indicates it doesn't want a layout
  1131. if(isset($this->layout) && is_null($this->layout)) {
  1132. //error_log('controller->layout absent');
  1133. return null;
  1134. }
  1135. # $layout will be the layout defined in the current controller
  1136. # or try to use the controller name for the layout
  1137. $layout = (isset($this->layout)
  1138. && $this->layout != '')
  1139. ? $this->layout : $this->controller;
  1140. # Check if a method has been defined to determine the layout at runtime
  1141. if(method_exists($this, $layout)) {
  1142. $layout = $this->$layout();
  1143. }
  1144.  
  1145. # Default settings
  1146. $layouts_base_path = TRAX_ROOT . $GLOBALS['TRAX_INCLUDES']['layouts'];
  1147. $default_layout_file = $layouts_base_path . "/application." . $this->views_file_extention;
  1148. if(!$full_path && $layout) {
  1149. return $layout;
  1150. } elseif($layout) {
  1151. # Is this layout for from a different controller
  1152. if(strstr($layout, "/")) {
  1153. $file = substr(strrchr($layout, "/"), 1);
  1154. $path = substr($layout, 0, strripos($layout, "/"));
  1155. $layout = $layouts_base_path."/".$path."/".$file.".".$this->views_file_extention;
  1156. } elseif( isset($this->layout) && ($this->layout != '') ) {
  1157. # Is there a layout for the current controller
  1158. $layout = $layouts_base_path."/".$this->layout.".".$this->views_file_extention;
  1159. } else {
  1160. # Is there a layout for the current controller
  1161. $layout = $this->layouts_path."/".$layout.".".$this->views_file_extention;
  1162. }
  1163.  
  1164. if(file_exists($layout)) {
  1165. $layout_file = $layout;
  1166. }
  1167. }
  1168. # No defined layout found so just use the default layout
  1169. # app/views/layouts/application.phtml
  1170. if(!isset($layout_file)) {
  1171. $layout_file = $default_layout_file;
  1172. }
  1173. return $layout_file;
  1174. }
  1175.  
  1176. /**
  1177. * Redirect the browser to a specified target
  1178. *
  1179. * Redirect the browser to the target specified in $options. This
  1180. * parameter can take one of three forms:
  1181. * <ul>
  1182. * <li>Array: The URL will be generated by calling
  1183. * {@link url_for()} with the options.</li>
  1184. * <li>String starting with a protocol:// (like http://): Is
  1185. * passed straight through as the target for redirection.</li>
  1186. * <li>String not containing a protocol: The current protocol
  1187. * and host is prepended to the string.</li>
  1188. * <li>back: Back to the page that issued the request. Useful
  1189. * for forms that are triggered from multiple
  1190. * places. Short-hand for redirect_to(request.env["HTTP_REFERER"])
  1191. * </ul>
  1192. *
  1193. * Examples:
  1194. * <ul>
  1195. * <li>redirect_to(array(":action" => "show", ":id" => 5))</li>
  1196. * <li>redirect_to("http://www.rubyonrails.org")</li>
  1197. * <li>redirect_to("/images/screenshot.jpg")</li>
  1198. * <li>redirect_to("back")</li>
  1199. * </ul>
  1200. *
  1201. * @param mixed $options array or string url
  1202. * @todo <b>FIXME:</b> Make header configurable
  1203. */
  1204. function redirect_to($options = null) {
  1205. if($options == "back") {
  1206. $url = $_SERVER["HTTP_REFERER"];
  1207. } else {
  1208. $url = url_for($options);
  1209. }
  1210. echo "<html><head><META HTTP-EQUIV=\"REFRESH\" CONTENT=\"0; URL=".$url."\"></head></html>";
  1211. #header("Location: ".$url);
  1212. exit;
  1213. }
  1214.  
  1215. /**
  1216. * Raise an ActionControllerError exception
  1217. *
  1218. * @param string $error_message Error message
  1219. * @param string $error_heading Error heading
  1220. * @param string $error_code Error code
  1221. * @throws ActionControllerError
  1222. */
  1223. function raise($error_message, $error_heading, $error_code = "404") {
  1224. throw new ActionControllerError("Error Message: ".$error_message, $error_heading, $error_code);
  1225. }
  1226.  
  1227. /**
  1228. * Generate an HTML page describing an error
  1229. */
  1230. function process_with_exception(&$exception) {
  1231. $error_code = $exception->error_code;
  1232. $error_heading = $exception->error_heading;
  1233. $error_message = $exception->error_message;
  1234. $trace = $exception->getTraceAsString();
  1235. header('HTTP/1.0 {$error_code} {$error_heading}');
  1236. header('status: {$error_code} {$error_heading}');
  1237. # check for user's layout for errors
  1238. if(DEBUG && file_exists(TRAX_ROOT.$GLOBALS['TRAX_INCLUDES']['layouts']."/trax_error.".TRAX_VIEWS_EXTENTION)) {
  1239. include(TRAX_ROOT.$GLOBALS['TRAX_INCLUDES']['layouts']."/trax_error.".TRAX_VIEWS_EXTENTION);
  1240. } elseif(DEBUG && file_exists(TRAX_LIB_ROOT."/templates/error.phtml")) {
  1241. # use default layout for errors
  1242. include(TRAX_LIB_ROOT."/templates/error.phtml");
  1243. } elseif(DEBUG) {
  1244. echo "<font face=\"verdana, arial, helvetica, sans-serif\">\n";
  1245. echo "<h1>$error_heading</h1>\n";
  1246. echo "<p>$error_message</p>\n";
  1247. if($trace) {
  1248. echo "<pre style=\"background-color: #eee;padding:10px;font-size: 11px;\">";
  1249. echo "<code>$trace</code></pre>\n";
  1250. }
  1251. echo "</font>\n";
  1252. } else {
  1253. echo "<font face=\"verdana, arial, helvetica, sans-serif\">\n";
  1254. echo "<h2>Application Error</h2>Trax application failed to start properly";
  1255. echo "</font>\n";
  1256. }
  1257. }
  1258.  
  1259. }
  1260.  
  1261. // -- set Emacs parameters --
  1262. // Local variables:
  1263. // tab-width: 4
  1264. // c-basic-offset: 4
  1265. // c-hanging-comment-ender-p: nil
  1266. // indent-tabs-mode: nil
  1267. // End:
  1268. ?>

Documentation generated on Thu, 04 May 2006 19:47:01 -0600 by phpDocumentor 1.3.0RC4