ILIAS  Release_5_0_x_branch Revision 61816
 All Data Structures Namespaces Files Functions Variables Groups Pages
Slim.php
Go to the documentation of this file.
1 <?php
33 //Ensure PHP session IDs only use the characters [a-z0-9]
34 ini_set('session.hash_bits_per_character', 4);
35 ini_set('session.hash_function', 0);
36 
37 //Slim's Encryted Cookies rely on libmcyrpt and these two constants.
38 //If libmycrpt is unavailable, we ensure the expected constants
39 //are available to avoid errors.
40 if ( !defined('MCRYPT_RIJNDAEL_256') ) {
41  define('MCRYPT_RIJNDAEL_256', 0);
42 }
43 if ( !defined('MCRYPT_MODE_CBC') ) {
44  define('MCRYPT_MODE_CBC', 0);
45 }
46 
47 //This determines which errors are reported by PHP. By default, all
48 //errors (including E_STRICT) are reported.
49 //error_reporting(E_ALL | E_STRICT);
50 
51 //This tells PHP to auto-load classes using Slim's autoloader; this will
52 //only auto-load a class file located in the same directory as Slim.php
53 //whose file name (excluding the final dot and extension) is the same
54 //as its class name (case-sensitive). For example, "View.php" will be
55 //loaded when Slim uses the "View" class for the first time.
56 spl_autoload_register(array('Slim', 'autoload'));
57 
58 //PHP 5.3 will complain if you don't set a timezone. If you do not
59 //specify your own timezone before requiring Slim, this tells PHP to use UTC.
60 if ( @date_default_timezone_set(date_default_timezone_get()) === false ) {
61  date_default_timezone_set('UTC');
62 }
63 
71 class Slim {
72 
76  protected static $apps = array();
77 
81  protected $name;
82 
86  protected $request;
87 
91  protected $response;
92 
96  protected $router;
97 
101  protected $view;
102 
106  protected $log;
107 
111  protected $settings;
112 
116  protected $mode;
117 
121  protected $hooks = array(
122  'slim.before' => array(array()),
123  'slim.before.router' => array(array()),
124  'slim.before.dispatch' => array(array()),
125  'slim.after.dispatch' => array(array()),
126  'slim.after.router' => array(array()),
127  'slim.after' => array(array())
128  );
129 
139  public static function autoload( $class ) {
140  if ( strpos($class, 'Slim') !== 0 ) {
141  return;
142  }
143  $file = dirname(__FILE__) . '/' . str_replace('_', DIRECTORY_SEPARATOR, substr($class,5)) . '.php';
144  if ( file_exists($file) ) {
145  require $file;
146  }
147  }
148 
149  /***** INITIALIZATION *****/
150 
156  public function __construct( $userSettings = array() ) {
157  //Merge application settings
158  $this->settings = array_merge(array(
159  //Mode
160  'mode' => 'development',
161  //Logging
162  'log.enable' => false,
163  'log.logger' => null,
164  'log.path' => './logs',
165  'log.level' => 4,
166  //Debugging
167  'debug' => true,
168  //View
169  'templates.path' => './templates',
170  'view' => 'Slim_View',
171  //Settings for all cookies
172  'cookies.lifetime' => '20 minutes',
173  'cookies.path' => '/',
174  'cookies.domain' => '',
175  'cookies.secure' => false,
176  'cookies.httponly' => false,
177  //Settings for encrypted cookies
178  'cookies.secret_key' => 'CHANGE_ME',
179  'cookies.cipher' => MCRYPT_RIJNDAEL_256,
180  'cookies.cipher_mode' => MCRYPT_MODE_CBC,
181  'cookies.encrypt' => true,
182  'cookies.user_id' => 'DEFAULT',
183  //Session handler
184  'session.handler' => new Slim_Session_Handler_Cookies(),
185  'session.flash_key' => 'flash',
186  //HTTP
187  'http.version' => null
188  ), $userSettings);
189 
190  //Determine application mode
191  $this->getMode();
192 
193  //Setup HTTP request and response handling
194  $this->request = new Slim_Http_Request();
195  $this->response = new Slim_Http_Response($this->request);
196  $this->response->setCookieJar(new Slim_Http_CookieJar($this->settings['cookies.secret_key'], array(
197  'high_confidentiality' => $this->settings['cookies.encrypt'],
198  'mcrypt_algorithm' => $this->settings['cookies.cipher'],
199  'mcrypt_mode' => $this->settings['cookies.cipher_mode'],
200  'enable_ssl' => $this->settings['cookies.secure']
201  )));
202  $this->response->httpVersion($this->settings['http.version']);
203  $this->router = new Slim_Router($this->request);
204 
205  //Start session if not already started
206  if ( session_id() === '' ) {
207  $sessionHandler = $this->config('session.handler');
208  if ( $sessionHandler instanceof Slim_Session_Handler ) {
209  $sessionHandler->register($this);
210  }
211  session_cache_limiter(false);
212  session_start();
213  }
214 
215  //Setup view with flash messaging
216  $this->view($this->config('view'))->setData('flash', new Slim_Session_Flash($this->config('session.flash_key')));
217 
218  //Set app name
219  if ( !isset(self::$apps['default']) ) {
220  $this->setName('default');
221  }
222 
223  //Set global Error handler after Slim app instantiated
224  set_error_handler(array('Slim', 'handleErrors'));
225  }
226 
231  public function getMode() {
232  if ( !isset($this->mode) ) {
233  if ( isset($_ENV['SLIM_MODE']) ) {
234  $this->mode = (string)$_ENV['SLIM_MODE'];
235  } else {
236  $envMode = getenv('SLIM_MODE');
237  if ( $envMode !== false ) {
238  $this->mode = $envMode;
239  } else {
240  $this->mode = (string)$this->config('mode');
241  }
242  }
243  }
244  return $this->mode;
245  }
246 
247  /***** NAMING *****/
248 
254  public static function getInstance( $name = 'default' ) {
255  return isset(self::$apps[(string)$name]) ? self::$apps[(string)$name] : null;
256  }
257 
263  public function setName( $name ) {
264  $this->name = $name;
265  self::$apps[$name] = $this;
266  }
267 
272  public function getName() {
273  return $this->name;
274  }
275 
276  /***** LOGGING *****/
277 
282  public function getLog() {
283  if ( !isset($this->log) ) {
284  $this->log = new Slim_Log();
285  $this->log->setEnabled($this->config('log.enable'));
286  $logger = $this->config('log.logger');
287  if ( $logger ) {
288  $this->log->setLogger($logger);
289  } else {
290  $this->log->setLogger(new Slim_Logger($this->config('log.path'), $this->config('log.level')));
291  }
292  }
293  return $this->log;
294  }
295 
296  /***** CONFIGURATION *****/
297 
310  public function configureMode( $mode, $callable ) {
311  if ( $mode === $this->getMode() && is_callable($callable) ) {
312  call_user_func($callable);
313  }
314  }
315 
335  public function config( $name, $value = null ) {
336  if ( func_num_args() === 1 ) {
337  if ( is_array($name) ) {
338  $this->settings = array_merge($this->settings, $name);
339  } else {
340  return in_array($name, array_keys($this->settings)) ? $this->settings[$name] : null;
341  }
342  } else {
343  $this->settings[$name] = $value;
344  }
345  }
346 
347  /***** ROUTING *****/
348 
379  protected function mapRoute($args) {
380  $pattern = array_shift($args);
381  $callable = array_pop($args);
382  $route = $this->router->map($pattern, $callable);
383  if ( count($args) > 0 ) {
384  $route->setMiddleware($args);
385  }
386  return $route;
387  }
388 
394  public function map() {
395  $args = func_get_args();
396  return $this->mapRoute($args);
397  }
398 
404  public function get() {
405  $args = func_get_args();
407  }
408 
414  public function post() {
415  $args = func_get_args();
416  return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_POST);
417  }
418 
424  public function put() {
425  $args = func_get_args();
426  return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_PUT);
427  }
428 
434  public function delete() {
435  $args = func_get_args();
436  return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_DELETE);
437  }
438 
444  public function options() {
445  $args = func_get_args();
446  return $this->mapRoute($args)->via(Slim_Http_Request::METHOD_OPTIONS);
447  }
448 
471  public function notFound( $callable = null ) {
472  if ( !is_null($callable) ) {
473  $this->router->notFound($callable);
474  } else {
475  ob_start();
476  $customNotFoundHandler = $this->router->notFound();
477  if ( is_callable($customNotFoundHandler) ) {
478  call_user_func($customNotFoundHandler);
479  } else {
480  call_user_func(array($this, 'defaultNotFound'));
481  }
482  $this->halt(404, ob_get_clean());
483  }
484  }
485 
510  public function error( $argument = null ) {
511  if ( is_callable($argument) ) {
512  //Register error handler
513  $this->router->error($argument);
514  } else {
515  //Invoke error handler
516  ob_start();
517  $customErrorHandler = $this->router->error();
518  if ( is_callable($customErrorHandler) ) {
519  call_user_func_array($customErrorHandler, array($argument));
520  } else {
521  call_user_func_array(array($this, 'defaultError'), array($argument));
522  }
523  $this->halt(500, ob_get_clean());
524  }
525  }
526 
527  /***** ACCESSORS *****/
528 
533  public function request() {
534  return $this->request;
535  }
536 
541  public function response() {
542  return $this->response;
543  }
544 
549  public function router() {
550  return $this->router;
551  }
552 
569  public function view( $viewClass = null ) {
570  if ( !is_null($viewClass) ) {
571  $existingData = is_null($this->view) ? array() : $this->view->getData();
572  if ( $viewClass instanceOf Slim_View ) {
573  $this->view = $viewClass;
574  } else {
575  $this->view = new $viewClass();
576  }
577  $this->view->appendData($existingData);
578  $this->view->setTemplatesDirectory($this->config('templates.path'));
579  }
580  return $this->view;
581  }
582 
583  /***** RENDERING *****/
584 
598  public function render( $template, $data = array(), $status = null ) {
599  if ( !is_null($status) ) {
600  $this->response->status($status);
601  }
602  $this->view->appendData($data);
603  $this->view->display($template);
604  }
605 
606  /***** HTTP CACHING *****/
607 
623  public function lastModified( $time ) {
624  if ( is_integer($time) ) {
625  $this->response->header('Last-Modified', date(DATE_RFC1123, $time));
626  if ( $time === strtotime($this->request->headers('IF_MODIFIED_SINCE')) ) $this->halt(304);
627  } else {
628  throw new InvalidArgumentException('Slim::lastModified only accepts an integer UNIX timestamp value.');
629  }
630  }
631 
649  public function etag( $value, $type = 'strong' ) {
650 
651  //Ensure type is correct
652  if ( !in_array($type, array('strong', 'weak')) ) {
653  throw new InvalidArgumentException('Invalid Slim::etag type. Expected "strong" or "weak".');
654  }
655 
656  //Set etag value
657  $value = '"' . $value . '"';
658  if ( $type === 'weak' ) $value = 'W/'.$value;
659  $this->response->header('ETag', $value);
660 
661  //Check conditional GET
662  if ( $etagsHeader = $this->request->headers('IF_NONE_MATCH')) {
663  $etags = preg_split('@\s*,\s*@', $etagsHeader);
664  if ( in_array($value, $etags) || in_array('*', $etags) ) $this->halt(304);
665  }
666 
667  }
668 
669  /***** COOKIES *****/
670 
686  public function setCookie( $name, $value, $time = null, $path = null, $domain = null, $secure = null, $httponly = null ) {
687  $time = is_null($time) ? $this->config('cookies.lifetime') : $time;
688  $path = is_null($path) ? $this->config('cookies.path') : $path;
689  $domain = is_null($domain) ? $this->config('cookies.domain') : $domain;
690  $secure = is_null($secure) ? $this->config('cookies.secure') : $secure;
691  $httponly = is_null($httponly) ? $this->config('cookies.httponly') : $httponly;
692  $this->response->getCookieJar()->setClassicCookie($name, $value, $time, $path, $domain, $secure, $httponly);
693  }
694 
705  public function getCookie( $name ) {
706  return $this->request->cookies($name);
707  }
708 
724  public function setEncryptedCookie( $name, $value, $time = null, $path = null, $domain = null, $secure = null, $httponly = null ) {
725  $time = is_null($time) ? $this->config('cookies.lifetime') : $time;
726  $path = is_null($path) ? $this->config('cookies.path') : $path;
727  $domain = is_null($domain) ? $this->config('cookies.domain') : $domain;
728  $secure = is_null($secure) ? $this->config('cookies.secure') : $secure;
729  $httponly = is_null($httponly) ? $this->config('cookies.httponly') : $httponly;
730  $userId = $this->config('cookies.user_id');
731  $this->response->getCookieJar()->setCookie($name, $value, $userId, $time, $path, $domain, $secure, $httponly);
732  }
733 
744  public function getEncryptedCookie( $name ) {
745  $value = $this->response->getCookieJar()->getCookieValue($name);
746  return ($value === false) ? null : $value;
747  }
748 
766  public function deleteCookie( $name, $path = null, $domain = null, $secure = null, $httponly = null ) {
767  $path = is_null($path) ? $this->config('cookies.path') : $path;
768  $domain = is_null($domain) ? $this->config('cookies.domain') : $domain;
769  $secure = is_null($secure) ? $this->config('cookies.secure') : $secure;
770  $httponly = is_null($httponly) ? $this->config('cookies.httponly') : $httponly;
771  $this->response->getCookieJar()->deleteCookie( $name, $path, $domain, $secure, $httponly );
772  }
773 
774  /***** HELPERS *****/
775 
786  public function root() {
787  return rtrim($_SERVER['DOCUMENT_ROOT'], '/') . rtrim($this->request->getRootUri(), '/') . '/';
788  }
789 
800  public function stop() {
801  $flash = $this->view->getData('flash');
802  if ( $flash ) {
803  $flash->save();
804  }
805  session_write_close();
806  $this->response->send();
807  throw new Slim_Exception_Stop();
808  }
809 
823  public function halt( $status, $message = '') {
824  if ( ob_get_level() !== 0 ) {
825  ob_clean();
826  }
827  $this->response->status($status);
828  $this->response->body($message);
829  $this->stop();
830  }
831 
843  public function pass() {
844  if ( ob_get_level() !== 0 ) {
845  ob_clean();
846  }
847  throw new Slim_Exception_Pass();
848  }
849 
855  public function contentType( $type ) {
856  $this->response->header('Content-Type', $type);
857  }
858 
864  public function status( $code ) {
865  $this->response->status($code);
866  }
867 
875  public function urlFor( $name, $params = array() ) {
876  return $this->router->urlFor($name, $params);
877  }
878 
894  public function redirect( $url, $status = 302 ) {
895  if ( $status >= 300 && $status <= 307 ) {
896  $this->response->header('Location', (string)$url);
897  $this->halt($status, (string)$url);
898  } else {
899  throw new InvalidArgumentException('Slim::redirect only accepts HTTP 300-307 status codes.');
900  }
901  }
902 
903  /***** FLASH *****/
904 
911  public function flash( $key, $value ) {
912  $this->view->getData('flash')->set($key, $value);
913  }
914 
921  public function flashNow( $key, $value ) {
922  $this->view->getData('flash')->now($key, $value);
923  }
924 
929  public function flashKeep() {
930  $this->view->getData('flash')->keep();
931  }
932 
933  /***** HOOKS *****/
934 
942  public function hook( $name, $callable, $priority = 10 ) {
943  if ( !isset($this->hooks[$name]) ) {
944  $this->hooks[$name] = array(array());
945  }
946  if ( is_callable($callable) ) {
947  $this->hooks[$name][(int)$priority][] = $callable;
948  }
949  }
950 
957  public function applyHook( $name, $hookArg = null ) {
958  if ( !isset($this->hooks[$name]) ) {
959  $this->hooks[$name] = array(array());
960  }
961  if( !empty($this->hooks[$name]) ) {
962  // Sort by priority, low to high, if there's more than one priority
963  if ( count($this->hooks[$name]) > 1 ) {
964  ksort($this->hooks[$name]);
965  }
966  foreach( $this->hooks[$name] as $priority ) {
967  if( !empty($priority) ) {
968  foreach($priority as $callable) {
969  $hookArg = call_user_func($callable, $hookArg);
970  }
971  }
972  }
973  return $hookArg;
974  }
975  }
976 
988  public function getHooks( $name = null ) {
989  if ( !is_null($name) ) {
990  return isset($this->hooks[(string)$name]) ? $this->hooks[(string)$name] : null;
991  } else {
992  return $this->hooks;
993  }
994  }
995 
1006  public function clearHooks( $name = null ) {
1007  if ( !is_null($name) && isset($this->hooks[(string)$name]) ) {
1008  $this->hooks[(string)$name] = array(array());
1009  } else {
1010  foreach( $this->hooks as $key => $value ) {
1011  $this->hooks[$key] = array(array());
1012  }
1013  }
1014  }
1015 
1016  /***** RUN SLIM *****/
1017 
1034  public function run() {
1035  try {
1036  try {
1037  $this->applyHook('slim.before');
1038  ob_start();
1039  $this->applyHook('slim.before.router');
1040  $dispatched = false;
1041  $httpMethod = $this->request()->getMethod();
1042  $httpMethodsAllowed = array();
1043  foreach ( $this->router as $route ) {
1044  if ( $route->supportsHttpMethod($httpMethod) ) {
1045  try {
1046  $this->applyHook('slim.before.dispatch');
1047  $dispatched = $route->dispatch();
1048  $this->applyHook('slim.after.dispatch');
1049  if ( $dispatched ) {
1050  break;
1051  }
1052  } catch ( Slim_Exception_Pass $e ) {
1053  continue;
1054  }
1055  } else {
1056  $httpMethodsAllowed = array_merge($httpMethodsAllowed, $route->getHttpMethods());
1057  }
1058  }
1059  if ( !$dispatched ) {
1060  if ( $httpMethodsAllowed ) {
1061  $this->response()->header('Allow', implode(' ', $httpMethodsAllowed));
1062  $this->halt(405);
1063  } else {
1064  $this->notFound();
1065  }
1066  }
1067  $this->response()->write(ob_get_clean());
1068  $this->applyHook('slim.after.router');
1069  $this->view->getData('flash')->save();
1070  session_write_close();
1071  $this->response->send();
1072  $this->applyHook('slim.after');
1073  } catch ( Slim_Exception_RequestSlash $e ) {
1074  $this->redirect($this->request->getRootUri() . $this->request->getResourceUri() . '/', 301);
1075  } catch ( Exception $e ) {
1076  if ( $e instanceof Slim_Exception_Stop ) throw $e;
1077  $this->getLog()->error($e);
1078  if ( $this->config('debug') === true ) {
1079  $this->halt(500, self::generateErrorMarkup($e->getMessage(), $e->getFile(), $e->getLine(), $e->getTraceAsString()));
1080  } else {
1081  $this->error($e);
1082  }
1083  }
1084  } catch ( Slim_Exception_Stop $e ) {
1085  //Exit application context
1086  }
1087  }
1088 
1089  /***** EXCEPTION AND ERROR HANDLING *****/
1090 
1105  public static function handleErrors( $errno, $errstr = '', $errfile = '', $errline = '' ) {
1106  if ( error_reporting() & $errno ) {
1107  throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
1108  }
1109  return true;
1110  }
1111 
1125  protected static function generateErrorMarkup( $message, $file = '', $line = '', $trace = '' ) {
1126  $body = '<p>The application could not run because of the following error:</p>';
1127  $body .= "<h2>Details:</h2><strong>Message:</strong> $message<br/>";
1128  if ( $file !== '' ) $body .= "<strong>File:</strong> $file<br/>";
1129  if ( $line !== '' ) $body .= "<strong>Line:</strong> $line<br/>";
1130  if ( $trace !== '' ) $body .= '<h2>Stack Trace:</h2>' . nl2br($trace);
1131  return self::generateTemplateMarkup('Slim Application Error', $body);
1132  }
1133 
1145  protected static function generateTemplateMarkup( $title, $body ) {
1146  $html = "<html><head><title>$title</title><style>body{margin:0;padding:30px;font:12px/1.5 Helvetica,Arial,Verdana,sans-serif;}h1{margin:0;font-size:48px;font-weight:normal;line-height:48px;}strong{display:inline-block;width:65px;}</style></head><body>";
1147  $html .= "<h1>$title</h1>";
1148  $html .= $body;
1149  $html .= '</body></html>';
1150  return $html;
1151  }
1152 
1157  protected function defaultNotFound() {
1158  echo self::generateTemplateMarkup('404 Page Not Found', '<p>The page you are looking for could not be found. Check the address bar to ensure your URL is spelled correctly. If all else fails, you can visit our home page at the link below.</p><a href="' . $this->request->getRootUri() . '">Visit the Home Page</a>');
1159  }
1160 
1165  protected function defaultError() {
1166  echo self::generateTemplateMarkup('Error', '<p>A website error has occured. The website administrator has been notified of the issue. Sorry for the temporary inconvenience.</p>');
1167  }
1168 
1169 }