Development

/branches/1.0/lib/controller/sfController.class.php

You must first sign up to be able to contribute.

root/branches/1.0/lib/controller/sfController.class.php

Revision 14385, 20.4 kB (checked in by noel, 6 years ago)

[1.0] fixed i18N and open_basedir restriction problem (closes #1445)

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Rev Date
Line 
1 <?php
2
3 /*
4  * This file is part of the symfony package.
5  * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
6  * (c) 2004-2006 Sean Kerr <sean@code-box.org>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 /**
13  * sfController directs application flow.
14  *
15  * @package    symfony
16  * @subpackage controller
17  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
18  * @author     Sean Kerr <sean@code-box.org>
19  * @version    SVN: $Id$
20  */
21 abstract class sfController
22 {
23   protected
24     $context                  = null,
25     $controllerClasses        = array(),
26     $maxForwards              = 5,
27     $renderMode               = sfView::RENDER_CLIENT,
28     $viewCacheClassName       = null;
29
30   /**
31    * Indicates whether or not a module has a specific component.
32    *
33    * @param string A module name
34    * @param string An component name
35    *
36    * @return bool true, if the component exists, otherwise false
37    */
38   public function componentExists($moduleName, $componentName)
39   {
40     return $this->controllerExists($moduleName, $componentName, 'component', false);
41   }
42
43   /**
44    * Indicates whether or not a module has a specific action.
45    *
46    * @param string A module name
47    * @param string An action name
48    *
49    * @return bool true, if the action exists, otherwise false
50    */
51   public function actionExists($moduleName, $actionName)
52   {
53     return $this->controllerExists($moduleName, $actionName, 'action', false);
54   }
55
56   /**
57    * Looks for a controller and optionally throw exceptions if existence is required (i.e.
58    * in the case of {@link getController()}).
59    *
60    * @param string  The name of the module
61    * @param string  The name of the controller within the module
62    * @param string  Either 'action' or 'component' depending on the type of controller to look for
63    * @param boolean Whether to throw exceptions if the controller doesn't exist
64    *
65    * @throws sfConfigurationException thrown if the module is not enabled
66    * @throws sfControllerException thrown if the controller doesn't exist and the $throwExceptions parameter is set to true
67    *
68    * @return boolean true if the controller exists, false otherwise
69    */
70   protected function controllerExists($moduleName, $controllerName, $extension, $throwExceptions)
71   {
72     $dirs = sfLoader::getControllerDirs($moduleName);
73     foreach ($dirs as $dir => $checkEnabled)
74     {
75       // plugin module enabled?
76       if ($checkEnabled && !in_array($moduleName, sfConfig::get('sf_enabled_modules')) && is_readable($dir))
77       {
78         $error = 'The module "%s" is not enabled.';
79         $error = sprintf($error, $moduleName);
80
81         throw new sfConfigurationException($error);
82       }
83
84       // one action per file or one file for all actions
85       $classFile   = strtolower($extension);
86       $classSuffix = ucfirst(strtolower($extension));
87       $file        = $dir.'/'.$controllerName.$classSuffix.'.class.php';
88       if (is_readable($file))
89       {
90         // action class exists
91         require_once($file);
92
93         $this->controllerClasses[$moduleName.'_'.$controllerName.'_'.$classSuffix] = $controllerName.$classSuffix;
94
95         return true;
96       }
97
98       $module_file = $dir.'/'.$classFile.'s.class.php';
99       if (is_readable($module_file))
100       {
101         // module class exists
102         require_once($module_file);
103
104         if (!class_exists($moduleName.$classSuffix.'s', false))
105         {
106           if ($throwExceptions)
107           {
108             throw new sfControllerException(sprintf('There is no "%s" class in your action file "%s".', $moduleName.$classSuffix.'s', $module_file));
109           }
110
111           return false;
112         }
113
114         // action is defined in this class?
115         if (!in_array('execute'.ucfirst($controllerName), get_class_methods($moduleName.$classSuffix.'s')))
116         {
117           if ($throwExceptions)
118           {
119             throw new sfControllerException(sprintf('There is no "%s" method in your action class "%s"', 'execute'.ucfirst($controllerName), $moduleName.$classSuffix.'s'));
120           }
121
122           return false;
123         }
124
125         $this->controllerClasses[$moduleName.'_'.$controllerName.'_'.$classSuffix] = $moduleName.$classSuffix.'s';
126         return true;
127       }
128     }
129
130     // send an exception if debug
131     if ($throwExceptions && sfConfig::get('sf_debug'))
132     {
133       $dirs = array_keys($dirs);
134
135       // remove sf_root_dir from dirs
136       foreach ($dirs as &$dir)
137       {
138         $dir = str_replace(sfConfig::get('sf_root_dir'), '%SF_ROOT_DIR%', $dir);
139       }
140
141       throw new sfControllerException(sprintf('{sfController} controller "%s/%s" does not exist in: %s', $moduleName, $controllerName, implode(', ', $dirs)));
142     }
143
144     return false;
145   }
146
147   /**
148    * Forwards the request to another action.
149    *
150    * @param string  A module name
151    * @param string  An action name
152    *
153    * @throws <b>sfConfigurationException</b> If an invalid configuration setting has been found
154    * @throws <b>sfForwardException</b> If an error occurs while forwarding the request
155    * @throws <b>sfInitializationException</b> If the action could not be initialized
156    * @throws <b>sfSecurityException</b> If the action requires security but the user implementation is not of type sfSecurityUser
157    */
158   public function forward($moduleName, $actionName)
159   {
160     // replace unwanted characters
161     $moduleName = preg_replace('/[^a-z0-9\-_]+/i', '', $moduleName);
162     $actionName = preg_replace('/[^a-z0-9\-_]+/i', '', $actionName);
163
164     if ($this->getActionStack()->getSize() >= $this->maxForwards)
165     {
166       // let's kill this party before it turns into cpu cycle hell
167       $error = 'Too many forwards have been detected for this request (> %d)';
168       $error = sprintf($error, $this->maxForwards);
169
170       throw new sfForwardException($error);
171     }
172
173     $rootDir = sfConfig::get('sf_root_dir');
174     $app     = sfConfig::get('sf_app');
175     $env     = sfConfig::get('sf_environment');
176
177     if (!sfConfig::get('sf_available') || sfToolkit::hasLockFile($rootDir.'/'.$app.'_'.$env.'.lck'))
178     {
179       // application is unavailable
180       $moduleName = sfConfig::get('sf_unavailable_module');
181       $actionName = sfConfig::get('sf_unavailable_action');
182
183       if (!$this->actionExists($moduleName, $actionName))
184       {
185         // cannot find unavailable module/action
186         $error = 'Invalid configuration settings: [sf_unavailable_module] "%s", [sf_unavailable_action] "%s"';
187         $error = sprintf($error, $moduleName, $actionName);
188
189         throw new sfConfigurationException($error);
190       }
191     }
192
193     // check for a module generator config file
194     sfConfigCache::getInstance()->import(sfConfig::get('sf_app_module_dir_name').'/'.$moduleName.'/'.sfConfig::get('sf_app_module_config_dir_name').'/generator.yml', true, true);
195
196     if (!$this->actionExists($moduleName, $actionName))
197     {
198       // the requested action doesn't exist
199       if (sfConfig::get('sf_logging_enabled'))
200       {
201         $this->getContext()->getLogger()->info(sprintf('{sfController} action "%s/%s" does not exist', $moduleName, $actionName));
202       }
203
204       // track the requested module so we have access to the data in the error 404 page
205       $this->context->getRequest()->setAttribute('requested_action', $actionName);
206       $this->context->getRequest()->setAttribute('requested_module', $moduleName);
207
208       // switch to error 404 action
209       $moduleName = sfConfig::get('sf_error_404_module');
210       $actionName = sfConfig::get('sf_error_404_action');
211
212       if (!$this->actionExists($moduleName, $actionName))
213       {
214         // cannot find unavailable module/action
215         $error = 'Invalid configuration settings: [sf_error_404_module] "%s", [sf_error_404_action] "%s"';
216         $error = sprintf($error, $moduleName, $actionName);
217
218         throw new sfConfigurationException($error);
219       }
220     }
221
222     // create an instance of the action
223     $actionInstance = $this->getAction($moduleName, $actionName);
224
225     // add a new action stack entry
226     $this->getActionStack()->addEntry($moduleName, $actionName, $actionInstance);
227
228     // include module configuration
229     require(sfConfigCache::getInstance()->checkConfig(sfConfig::get('sf_app_module_dir_name').'/'.$moduleName.'/'.sfConfig::get('sf_app_module_config_dir_name').'/module.yml'));
230
231     // check if this module is internal
232     if ($this->getActionStack()->getSize() == 1 && sfConfig::get('mod_'.strtolower($moduleName).'_is_internal') && !sfConfig::get('sf_test'))
233     {
234       $error = 'Action "%s" from module "%s" cannot be called directly';
235       $error = sprintf($error, $actionName, $moduleName);
236
237       throw new sfConfigurationException($error);
238     }
239
240     if (sfConfig::get('mod_'.strtolower($moduleName).'_enabled'))
241     {
242       // module is enabled
243
244       // check for a module config.php
245       $moduleConfig = sfConfig::get('sf_app_module_dir').'/'.$moduleName.'/'.sfConfig::get('sf_app_module_config_dir_name').'/config.php';
246       if (is_readable($moduleConfig))
247       {
248         require_once($moduleConfig);
249       }
250
251       // initialize the action
252       if ($actionInstance->initialize($this->context))
253       {
254         // create a new filter chain
255         $filterChain = new sfFilterChain();
256         $this->loadFilters($filterChain, $actionInstance);
257
258         if ($moduleName == sfConfig::get('sf_error_404_module') && $actionName == sfConfig::get('sf_error_404_action'))
259         {
260           $this->getContext()->getResponse()->setStatusCode(404);
261           $this->getContext()->getResponse()->setHttpHeader('Status', '404 Not Found');
262
263           foreach (sfMixer::getCallables('sfController:forward:error404') as $callable)
264           {
265             call_user_func($callable, $this, $moduleName, $actionName);
266           }
267         }
268
269         // change i18n message source directory to our module
270         if (sfConfig::get('sf_i18n'))
271         {
272           if (sfLoader::getI18NDir($moduleName))
273           {
274             $this->context->getI18N()->setMessageSourceDir(sfLoader::getI18NDir($moduleName), $this->context->getUser()->getCulture());
275           }
276           else
277           {
278             $this->context->getI18N()->setMessageSourceDir(sfConfig::get('sf_app_i18n_dir'), $this->context->getUser()->getCulture());
279           }
280         }
281
282         // process the filter chain
283         $filterChain->execute();
284       }
285       else
286       {
287         // action failed to initialize
288         $error = 'Action initialization failed for module "%s", action "%s"';
289         $error = sprintf($error, $moduleName, $actionName);
290
291         throw new sfInitializationException($error);
292       }
293     }
294     else
295     {
296       // module is disabled
297       $moduleName = sfConfig::get('sf_module_disabled_module');
298       $actionName = sfConfig::get('sf_module_disabled_action');
299
300       if (!$this->actionExists($moduleName, $actionName))
301       {
302         // cannot find mod disabled module/action
303         $error = 'Invalid configuration settings: [sf_module_disabled_module] "%s", [sf_module_disabled_action] "%s"';
304         $error = sprintf($error, $moduleName, $actionName);
305
306         throw new sfConfigurationException($error);
307       }
308
309       $this->forward($moduleName, $actionName);
310     }
311   }
312
313   /**
314    * Retrieves an sfAction implementation instance.
315    *
316    * @param  string A module name
317    * @param  string An action name
318    *
319    * @return sfAction An sfAction implementation instance, if the action exists, otherwise null
320    */
321   public function getAction($moduleName, $actionName)
322   {
323     return $this->getController($moduleName, $actionName, 'action');
324   }
325
326   /**
327    * Retrieves a sfComponent implementation instance.
328    *
329    * @param  string A module name
330    * @param  string A component name
331    *
332    * @return sfComponent A sfComponent implementation instance, if the component exists, otherwise null
333    */
334   public function getComponent($moduleName, $componentName)
335   {
336     return $this->getController($moduleName, $componentName, 'component');
337   }
338
339   /**
340    * Retrieves a controller implementation instance.
341    *
342    * @param  string A module name
343    * @param  string A component name
344    * @param  string  Either 'action' or 'component' depending on the type of controller to look for
345    *
346    * @return object A controller implementation instance, if the controller exists, otherwise null
347    *
348    * @see getComponent(), getAction()
349    */
350   protected function getController($moduleName, $controllerName, $extension)
351   {
352     $classSuffix = ucfirst(strtolower($extension));
353     if (!isset($this->controllerClasses[$moduleName.'_'.$controllerName.'_'.$classSuffix]))
354     {
355       $this->controllerExists($moduleName, $controllerName, $extension, true);
356     }
357
358     $class = $this->controllerClasses[$moduleName.'_'.$controllerName.'_'.$classSuffix];
359
360     // fix for same name classes
361     $moduleClass = $moduleName.'_'.$class;
362
363     if (class_exists($moduleClass, false))
364     {
365       $class = $moduleClass;
366     }
367
368     return new $class();
369   }
370
371   /**
372    * Retrieves the action stack.
373    *
374    * @return sfActionStack An sfActionStack instance, if the action stack is enabled, otherwise null
375    */
376   public function getActionStack()
377   {
378     return $this->context->getActionStack();
379   }
380
381   /**
382    * Retrieves the current application context.
383    *
384    * @return sfContext A sfContext instance
385    */
386   public function getContext()
387   {
388     return $this->context;
389   }
390
391   /**
392    * Retrieves the presentation rendering mode.
393    *
394    * @return int One of the following:
395    *             - sfView::RENDER_CLIENT
396    *             - sfView::RENDER_VAR
397    */
398   public function getRenderMode()
399   {
400     return $this->renderMode;
401   }
402
403   /**
404    * Retrieves a sfView implementation instance.
405    *
406    * @param string A module name
407    * @param string An action name
408    * @param string A view name
409    *
410    * @return sfView A sfView implementation instance, if the view exists, otherwise null
411    */
412   public function getView($moduleName, $actionName, $viewName)
413   {
414     // user view exists?
415     $file = sfConfig::get('sf_app_module_dir').'/'.$moduleName.'/'.sfConfig::get('sf_app_module_view_dir_name').'/'.$actionName.$viewName.'View.class.php';
416
417     if (is_readable($file))
418     {
419       require_once($file);
420
421       $class = $actionName.$viewName.'View';
422
423       // fix for same name classes
424       $moduleClass = $moduleName.'_'.$class;
425
426       if (class_exists($moduleClass, false))
427       {
428         $class = $moduleClass;
429       }
430     }
431     else
432     {
433       // view class (as configured in module.yml or defined in action)
434       $viewName = $this->getContext()->getRequest()->getAttribute($moduleName.'_'.$actionName.'_view_name', sfConfig::get('mod_'.strtolower($moduleName).'_view_class'), 'symfony/action/view');
435       $class    = sfCore::getClassPath($viewName.'View') ? $viewName.'View' : 'sfPHPView';
436     }
437
438     return new $class();
439   }
440
441   /**
442    * Initializes this controller.
443    *
444    * @param sfContext A sfContext implementation instance
445    */
446   public function initialize($context)
447   {
448     $this->context = $context;
449
450     if (sfConfig::get('sf_logging_enabled'))
451     {
452       $this->context->getLogger()->info('{sfController} initialization');
453     }
454
455     // set max forwards
456     $this->maxForwards = sfConfig::get('sf_max_forwards');
457   }
458
459   /**
460    * Retrieves a new sfController implementation instance.
461    *
462    * @param string A sfController class name
463    *
464    * @return sfController A sfController implementation instance
465    *
466    * @throws sfFactoryException If a new controller implementation instance cannot be created
467    */
468   public static function newInstance($class)
469   {
470     try
471     {
472       // the class exists
473       $object = new $class();
474
475       if (!($object instanceof sfController))
476       {
477           // the class name is of the wrong type
478           $error = 'Class "%s" is not of the type sfController';
479           $error = sprintf($error, $class);
480
481           throw new sfFactoryException($error);
482       }
483
484       return $object;
485     }
486     catch (sfException $e)
487     {
488       $e->printStackTrace();
489     }
490   }
491
492   /**
493    * Sends and email from the current action.
494    *
495    * This methods calls a module/action with the sfMailView class.
496    *
497    * @param  string A module name
498    * @param  string An action name
499    *
500    * @return string The generated mail content
501    *
502    * @see sfMailView, getPresentationFor(), sfController
503    */
504   public function sendEmail($module, $action)
505   {
506     return $this->getPresentationFor($module, $action, 'sfMail');
507   }
508
509   /**
510    * Returns the rendered view presentation of a given module/action.
511    *
512    * @param  string A module name
513    * @param  string An action name
514    * @param  string A View class name
515    *
516    * @return string The generated content
517    */
518   public function getPresentationFor($module, $action, $viewName = null)
519   {
520     if (sfConfig::get('sf_logging_enabled'))
521     {
522       $this->getContext()->getLogger()->info('{sfController} get presentation for action "'.$module.'/'.$action.'" (view class: "'.$viewName.'")');
523     }
524
525     // get original render mode
526     $renderMode = $this->getRenderMode();
527
528     // set render mode to var
529     $this->setRenderMode(sfView::RENDER_VAR);
530
531     // grab the action stack
532     $actionStack = $this->getActionStack();
533
534     // grab this next forward's action stack index
535     $index = $actionStack->getSize();
536
537     // set viewName if needed
538     if ($viewName)
539     {
540       $this->getContext()->getRequest()->setAttribute($module.'_'.$action.'_view_name', $viewName, 'symfony/action/view');
541     }
542
543     try
544     {
545       // forward to the mail action
546       $this->forward($module, $action);
547     }
548     catch (Exception $e)
549     {
550       // put render mode back
551       $this->setRenderMode($renderMode);
552
553       // remove viewName
554       if ($viewName)
555       {
556         $this->getContext()->getRequest()->getAttributeHolder()->remove($module.'_'.$action.'_view_name', 'symfony/action/view');
557       }
558
559       throw $e;
560     }
561
562     // grab the action entry from this forward
563     $actionEntry = $actionStack->getEntry($index);
564
565     // get raw email content
566     $presentation =& $actionEntry->getPresentation();
567
568     // put render mode back
569     $this->setRenderMode($renderMode);
570
571     // remove the action entry
572     $nb = $actionStack->getSize() - $index;
573     while ($nb-- > 0)
574     {
575       $actionEntry = $actionStack->popEntry();
576
577       if ($actionEntry->getModuleName() == sfConfig::get('sf_login_module') && $actionEntry->getActionName() == sfConfig::get('sf_login_action'))
578       {
579         throw new sfException('Your action is secured, but the user is not authenticated.');
580       }
581       else if ($actionEntry->getModuleName() == sfConfig::get('sf_secure_module') && $actionEntry->getActionName() == sfConfig::get('sf_secure_action'))
582       {
583         throw new sfException('Your action is secured, but the user does not have access.');
584       }
585     }
586
587     // remove viewName
588     if ($viewName)
589     {
590       $this->getContext()->getRequest()->getAttributeHolder()->remove($module.'_'.$action.'_view_name', 'symfony/action/view');
591     }
592
593     return $presentation;
594   }
595
596   /**
597    * Sets the presentation rendering mode.
598    *
599    * @param int A rendering mode
600    *
601    * @throws sfRenderException If an invalid render mode has been set
602    */
603   public function setRenderMode($mode)
604   {
605     if ($mode == sfView::RENDER_CLIENT || $mode == sfView::RENDER_VAR || $mode == sfView::RENDER_NONE)
606     {
607       $this->renderMode = $mode;
608
609       return;
610     }
611
612     // invalid rendering mode type
613     $error = 'Invalid rendering mode: %s';
614     $error = sprintf($error, $mode);
615
616     throw new sfRenderException($error);
617   }
618
619   /**
620    * Indicates whether or not we were called using the CLI version of PHP.
621    *
622    * @return bool true, if using cli, otherwise false.
623    */
624   public function inCLI()
625   {
626     return 0 == strncasecmp(PHP_SAPI, 'cli', 3);
627   }
628
629   /**
630    * Loads application nad module filters.
631    *
632    * @param sfFilterChain A sfFilterChain instance
633    * @param sfAction      A sfAction instance
634    */
635   public function loadFilters($filterChain, $actionInstance)
636   {
637     $moduleName = $this->context->getModuleName();
638
639     require(sfConfigCache::getInstance()->checkConfig(sfConfig::get('sf_app_module_dir_name').'/'.$moduleName.'/'.sfConfig::get('sf_app_module_config_dir_name').'/filters.yml'));
640   }
641
642   /**
643    * Calls methods defined via the sfMixer class.
644    *
645    * @param string The method name
646    * @param array  The method arguments
647    *
648    * @return mixed The returned value of the called method
649    *
650    * @see sfMixer
651    */
652   public function __call($method, $arguments)
653   {
654     if (!$callable = sfMixer::getCallable('sfController:'.$method))
655     {
656       throw new sfException(sprintf('Call to undefined method sfController::%s', $method));
657     }
658
659     array_unshift($arguments, $this);
660
661     return call_user_func_array($callable, $arguments);
662   }
663 }
664
Note: See TracBrowser for help on using the browser.