sfController.class.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. <?php
  2. /*
  3. * This file is part of the symfony package.
  4. * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
  5. * (c) 2004-2006 Sean Kerr <sean@code-box.org>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. /**
  11. * sfController directs application flow.
  12. *
  13. * @package symfony
  14. * @subpackage controller
  15. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  16. * @author Sean Kerr <sean@code-box.org>
  17. * @version SVN: $Id: sfController.class.php 11286 2008-09-02 10:27:36Z fabien $
  18. */
  19. abstract class sfController
  20. {
  21. protected
  22. $context = null,
  23. $dispatcher = null,
  24. $controllerClasses = array(),
  25. $maxForwards = 5,
  26. $renderMode = sfView::RENDER_CLIENT;
  27. /**
  28. * Class constructor.
  29. *
  30. * @see initialize()
  31. */
  32. public function __construct($context)
  33. {
  34. $this->initialize($context);
  35. }
  36. /**
  37. * Initializes this controller.
  38. *
  39. * @param sfContext $context A sfContext implementation instance
  40. */
  41. public function initialize($context)
  42. {
  43. $this->context = $context;
  44. $this->dispatcher = $context->getEventDispatcher();
  45. // set max forwards
  46. $this->maxForwards = sfConfig::get('sf_max_forwards');
  47. }
  48. /**
  49. * Indicates whether or not a module has a specific component.
  50. *
  51. * @param string $moduleName A module name
  52. * @param string $componentName An component name
  53. *
  54. * @return bool true, if the component exists, otherwise false
  55. */
  56. public function componentExists($moduleName, $componentName)
  57. {
  58. return $this->controllerExists($moduleName, $componentName, 'component', false);
  59. }
  60. /**
  61. * Indicates whether or not a module has a specific action.
  62. *
  63. * @param string $moduleName A module name
  64. * @param string $actionName An action name
  65. *
  66. * @return bool true, if the action exists, otherwise false
  67. */
  68. public function actionExists($moduleName, $actionName)
  69. {
  70. return $this->controllerExists($moduleName, $actionName, 'action', false);
  71. }
  72. /**
  73. * Looks for a controller and optionally throw exceptions if existence is required (i.e.
  74. * in the case of {@link getController()}).
  75. *
  76. * @param string $moduleName The name of the module
  77. * @param string $controllerName The name of the controller within the module
  78. * @param string $extension Either 'action' or 'component' depending on the type of controller to look for
  79. * @param boolean $throwExceptions Whether to throw exceptions if the controller doesn't exist
  80. *
  81. * @throws sfConfigurationException thrown if the module is not enabled
  82. * @throws sfControllerException thrown if the controller doesn't exist and the $throwExceptions parameter is set to true
  83. *
  84. * @return boolean true if the controller exists, false otherwise
  85. */
  86. protected function controllerExists($moduleName, $controllerName, $extension, $throwExceptions)
  87. {
  88. $dirs = $this->context->getConfiguration()->getControllerDirs($moduleName);
  89. foreach ($dirs as $dir => $checkEnabled)
  90. {
  91. // plugin module enabled?
  92. if ($checkEnabled && !in_array($moduleName, sfConfig::get('sf_enabled_modules')) && is_readable($dir))
  93. {
  94. throw new sfConfigurationException(sprintf('The module "%s" is not enabled.', $moduleName));
  95. }
  96. // one action per file or one file for all actions
  97. $classFile = strtolower($extension);
  98. $classSuffix = ucfirst(strtolower($extension));
  99. $file = $dir.'/'.$controllerName.$classSuffix.'.class.php';
  100. if (is_readable($file))
  101. {
  102. // action class exists
  103. require_once($file);
  104. $this->controllerClasses[$moduleName.'_'.$controllerName.'_'.$classSuffix] = $controllerName.$classSuffix;
  105. return true;
  106. }
  107. $module_file = $dir.'/'.$classFile.'s.class.php';
  108. if (is_readable($module_file))
  109. {
  110. // module class exists
  111. require_once($module_file);
  112. if (!class_exists($moduleName.$classSuffix.'s', false))
  113. {
  114. if ($throwExceptions)
  115. {
  116. throw new sfControllerException(sprintf('There is no "%s" class in your action file "%s".', $moduleName.$classSuffix.'s', $module_file));
  117. }
  118. return false;
  119. }
  120. // action is defined in this class?
  121. if (!in_array('execute'.ucfirst($controllerName), get_class_methods($moduleName.$classSuffix.'s')))
  122. {
  123. if ($throwExceptions)
  124. {
  125. throw new sfControllerException(sprintf('There is no "%s" method in your action class "%s".', 'execute'.ucfirst($controllerName), $moduleName.$classSuffix.'s'));
  126. }
  127. return false;
  128. }
  129. $this->controllerClasses[$moduleName.'_'.$controllerName.'_'.$classSuffix] = $moduleName.$classSuffix.'s';
  130. return true;
  131. }
  132. }
  133. // send an exception if debug
  134. if ($throwExceptions && sfConfig::get('sf_debug'))
  135. {
  136. $dirs = array_keys($dirs);
  137. // remove sf_root_dir from dirs
  138. foreach ($dirs as &$dir)
  139. {
  140. $dir = str_replace(sfConfig::get('sf_root_dir'), '%SF_ROOT_DIR%', $dir);
  141. }
  142. throw new sfControllerException(sprintf('Controller "%s/%s" does not exist in: %s.', $moduleName, $controllerName, implode(', ', $dirs)));
  143. }
  144. return false;
  145. }
  146. /**
  147. * Forwards the request to another action.
  148. *
  149. * @param string $moduleName A module name
  150. * @param string $actionName An action name
  151. *
  152. * @throws <b>sfConfigurationException</b> If an invalid configuration setting has been found
  153. * @throws <b>sfForwardException</b> If an error occurs while forwarding the request
  154. * @throws <b>sfInitializationException</b> If the action could not be initialized
  155. * @throws <b>sfSecurityException</b> If the action requires security but the user implementation is not of type sfSecurityUser
  156. */
  157. public function forward($moduleName, $actionName)
  158. {
  159. // replace unwanted characters
  160. $moduleName = preg_replace('/[^a-z0-9_]+/i', '', $moduleName);
  161. $actionName = preg_replace('/[^a-z0-9_]+/i', '', $actionName);
  162. if ($this->getActionStack()->getSize() >= $this->maxForwards)
  163. {
  164. // let's kill this party before it turns into cpu cycle hell
  165. throw new sfForwardException(sprintf('Too many forwards have been detected for this request (> %d).', $this->maxForwards));
  166. }
  167. // check for a module generator config file
  168. $this->context->getConfigCache()->import('modules/'.$moduleName.'/config/generator.yml', false, true);
  169. if (!$this->actionExists($moduleName, $actionName))
  170. {
  171. // the requested action doesn't exist
  172. if (sfConfig::get('sf_logging_enabled'))
  173. {
  174. $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Action "%s/%s" does not exist', $moduleName, $actionName))));
  175. }
  176. throw new sfError404Exception(sprintf('Action "%s/%s" does not exist.', $moduleName, $actionName));
  177. }
  178. // create an instance of the action
  179. $actionInstance = $this->getAction($moduleName, $actionName);
  180. // add a new action stack entry
  181. $this->getActionStack()->addEntry($moduleName, $actionName, $actionInstance);
  182. // include module configuration
  183. require($this->context->getConfigCache()->checkConfig('modules/'.$moduleName.'/config/module.yml'));
  184. // check if this module is internal
  185. if ($this->getActionStack()->getSize() == 1 && sfConfig::get('mod_'.strtolower($moduleName).'_is_internal') && !sfConfig::get('sf_test'))
  186. {
  187. throw new sfConfigurationException(sprintf('Action "%s" from module "%s" cannot be called directly.', $actionName, $moduleName));
  188. }
  189. // module enabled?
  190. if (sfConfig::get('mod_'.strtolower($moduleName).'_enabled'))
  191. {
  192. // check for a module config.php
  193. $moduleConfig = sfConfig::get('sf_app_module_dir').'/'.$moduleName.'/config/config.php';
  194. if (is_readable($moduleConfig))
  195. {
  196. require_once($moduleConfig);
  197. }
  198. // create a new filter chain
  199. $filterChain = new sfFilterChain();
  200. $filterChain->loadConfiguration($actionInstance);
  201. $this->context->getEventDispatcher()->notify(new sfEvent($this, 'controller.change_action', array('module' => $moduleName, 'action' => $actionName)));
  202. if ($moduleName == sfConfig::get('sf_error_404_module') && $actionName == sfConfig::get('sf_error_404_action'))
  203. {
  204. $this->context->getResponse()->setStatusCode(404);
  205. $this->context->getResponse()->setHttpHeader('Status', '404 Not Found');
  206. $this->dispatcher->notify(new sfEvent($this, 'controller.page_not_found', array('module' => $moduleName, 'action' => $actionName)));
  207. }
  208. // process the filter chain
  209. $filterChain->execute();
  210. }
  211. else
  212. {
  213. $moduleName = sfConfig::get('sf_module_disabled_module');
  214. $actionName = sfConfig::get('sf_module_disabled_action');
  215. if (!$this->actionExists($moduleName, $actionName))
  216. {
  217. // cannot find mod disabled module/action
  218. throw new sfConfigurationException(sprintf('Invalid configuration settings: [sf_module_disabled_module] "%s", [sf_module_disabled_action] "%s".', $moduleName, $actionName));
  219. }
  220. $this->forward($moduleName, $actionName);
  221. }
  222. }
  223. /**
  224. * Retrieves an sfAction implementation instance.
  225. *
  226. * @param string $moduleName A module name
  227. * @param string $actionname An action name
  228. *
  229. * @return sfAction An sfAction implementation instance, if the action exists, otherwise null
  230. */
  231. public function getAction($moduleName, $actionName)
  232. {
  233. return $this->getController($moduleName, $actionName, 'action');
  234. }
  235. /**
  236. * Retrieves a sfComponent implementation instance.
  237. *
  238. * @param string $moduleName A module name
  239. * @param string $componentName A component name
  240. *
  241. * @return sfComponent A sfComponent implementation instance, if the component exists, otherwise null
  242. */
  243. public function getComponent($moduleName, $componentName)
  244. {
  245. return $this->getController($moduleName, $componentName, 'component');
  246. }
  247. /**
  248. * Retrieves a controller implementation instance.
  249. *
  250. * @param string $moduleName A module name
  251. * @param string $controllerName A component name
  252. * @param string $extension Either 'action' or 'component' depending on the type of controller to look for
  253. *
  254. * @return object A controller implementation instance, if the controller exists, otherwise null
  255. *
  256. * @see getComponent(), getAction()
  257. */
  258. protected function getController($moduleName, $controllerName, $extension)
  259. {
  260. $classSuffix = ucfirst(strtolower($extension));
  261. if (!isset($this->controllerClasses[$moduleName.'_'.$controllerName.'_'.$classSuffix]))
  262. {
  263. $this->controllerExists($moduleName, $controllerName, $extension, true);
  264. }
  265. $class = $this->controllerClasses[$moduleName.'_'.$controllerName.'_'.$classSuffix];
  266. // fix for same name classes
  267. $moduleClass = $moduleName.'_'.$class;
  268. if (class_exists($moduleClass, false))
  269. {
  270. $class = $moduleClass;
  271. }
  272. return new $class($this->context, $moduleName, $controllerName);
  273. }
  274. /**
  275. * Retrieves the action stack.
  276. *
  277. * @return sfActionStack An sfActionStack instance, if the action stack is enabled, otherwise null
  278. */
  279. public function getActionStack()
  280. {
  281. return $this->context->getActionStack();
  282. }
  283. /**
  284. * Retrieves the presentation rendering mode.
  285. *
  286. * @return int One of the following:
  287. * - sfView::RENDER_CLIENT
  288. * - sfView::RENDER_VAR
  289. */
  290. public function getRenderMode()
  291. {
  292. return $this->renderMode;
  293. }
  294. /**
  295. * Retrieves a sfView implementation instance.
  296. *
  297. * @param string $moduleName A module name
  298. * @param string $actionName An action name
  299. * @param string $viewName A view name
  300. *
  301. * @return sfView A sfView implementation instance, if the view exists, otherwise null
  302. */
  303. public function getView($moduleName, $actionName, $viewName)
  304. {
  305. // user view exists?
  306. $file = sfConfig::get('sf_app_module_dir').'/'.$moduleName.'/view/'.$actionName.$viewName.'View.class.php';
  307. if (is_readable($file))
  308. {
  309. require_once($file);
  310. $class = $actionName.$viewName.'View';
  311. // fix for same name classes
  312. $moduleClass = $moduleName.'_'.$class;
  313. if (class_exists($moduleClass, false))
  314. {
  315. $class = $moduleClass;
  316. }
  317. }
  318. else
  319. {
  320. // view class (as configured in module.yml or defined in action)
  321. $class = sfConfig::get('mod_'.strtolower($moduleName).'_view_class', 'sfPHP').'View';
  322. }
  323. return new $class($this->context, $moduleName, $actionName, $viewName);
  324. }
  325. /**
  326. * [DEPRECATED] Sends and email.
  327. *
  328. * This methods calls a module/action with the sfMailView class.
  329. *
  330. * @param string $module A module name
  331. * @param string $action An action name
  332. *
  333. * @return string The generated mail content
  334. *
  335. * @see sfMailView, getPresentationFor(), sfController
  336. * @deprecated 1.1
  337. */
  338. public function sendEmail($module, $action)
  339. {
  340. if (sfConfig::get('sf_logging_enabled'))
  341. {
  342. $this->dispatcher->notify(new sfEvent($this, 'application.log', array('sendEmail method is deprecated', 'priority' => sfLogger::ERR)));
  343. }
  344. return $this->getPresentationFor($module, $action, 'sfMail');
  345. }
  346. /**
  347. * Returns the rendered view presentation of a given module/action.
  348. *
  349. * @param string $module A module name
  350. * @param string $action An action name
  351. * @param string $viewName A View class name
  352. *
  353. * @return string The generated content
  354. */
  355. public function getPresentationFor($module, $action, $viewName = null)
  356. {
  357. if (sfConfig::get('sf_logging_enabled'))
  358. {
  359. $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Get presentation for action "%s/%s" (view class: "%s")', $module, $action, $viewName))));
  360. }
  361. // get original render mode
  362. $renderMode = $this->getRenderMode();
  363. // set render mode to var
  364. $this->setRenderMode(sfView::RENDER_VAR);
  365. // grab the action stack
  366. $actionStack = $this->getActionStack();
  367. // grab this next forward's action stack index
  368. $index = $actionStack->getSize();
  369. // set viewName if needed
  370. if ($viewName)
  371. {
  372. $currentViewName = sfConfig::get('mod_'.strtolower($module).'_view_class');
  373. sfConfig::set('mod_'.strtolower($module).'_view_class', $viewName);
  374. }
  375. try
  376. {
  377. // forward to the mail action
  378. $this->forward($module, $action);
  379. }
  380. catch (Exception $e)
  381. {
  382. // put render mode back
  383. $this->setRenderMode($renderMode);
  384. // remove viewName
  385. if ($viewName)
  386. {
  387. sfConfig::set('mod_'.strtolower($module).'_view_class', $currentViewName);
  388. }
  389. throw $e;
  390. }
  391. // grab the action entry from this forward
  392. $actionEntry = $actionStack->getEntry($index);
  393. // get raw email content
  394. $presentation =& $actionEntry->getPresentation();
  395. // put render mode back
  396. $this->setRenderMode($renderMode);
  397. // remove the action entry
  398. $nb = $actionStack->getSize() - $index;
  399. while ($nb-- > 0)
  400. {
  401. $actionEntry = $actionStack->popEntry();
  402. if ($actionEntry->getModuleName() == sfConfig::get('sf_login_module') && $actionEntry->getActionName() == sfConfig::get('sf_login_action'))
  403. {
  404. throw new sfException('Your action is secured, but the user is not authenticated.');
  405. }
  406. else if ($actionEntry->getModuleName() == sfConfig::get('sf_secure_module') && $actionEntry->getActionName() == sfConfig::get('sf_secure_action'))
  407. {
  408. throw new sfException('Your action is secured, but the user does not have access.');
  409. }
  410. }
  411. // remove viewName
  412. if ($viewName)
  413. {
  414. sfConfig::set('mod_'.strtolower($module).'_view_class', $currentViewName);
  415. }
  416. return $presentation;
  417. }
  418. /**
  419. * Sets the presentation rendering mode.
  420. *
  421. * @param int $mode A rendering mode
  422. *
  423. * @throws sfRenderException If an invalid render mode has been set
  424. */
  425. public function setRenderMode($mode)
  426. {
  427. if ($mode == sfView::RENDER_CLIENT || $mode == sfView::RENDER_VAR || $mode == sfView::RENDER_NONE)
  428. {
  429. $this->renderMode = $mode;
  430. return;
  431. }
  432. // invalid rendering mode type
  433. throw new sfRenderException(sprintf('Invalid rendering mode: %s.', $mode));
  434. }
  435. /**
  436. * Indicates whether or not we were called using the CLI version of PHP.
  437. *
  438. * @return bool true, if using cli, otherwise false.
  439. */
  440. public function inCLI()
  441. {
  442. return 0 == strncasecmp(PHP_SAPI, 'cli', 3);
  443. }
  444. /**
  445. * Calls methods defined via sfEventDispatcher.
  446. *
  447. * @param string $method The method name
  448. * @param array $arguments The method arguments
  449. *
  450. * @return mixed The returned value of the called method
  451. */
  452. public function __call($method, $arguments)
  453. {
  454. $event = $this->dispatcher->notifyUntil(new sfEvent($this, 'controller.method_not_found', array('method' => $method, 'arguments' => $arguments)));
  455. if (!$event->isProcessed())
  456. {
  457. throw new sfException(sprintf('Call to undefined method %s::%s.', get_class($this), $method));
  458. }
  459. return $event->getReturnValue();
  460. }
  461. }