1 <?php
2 /**
3 * Director is responsible for processing URLs, and providing environment information.
4 *
5 * The most important part of director is {@link Director::direct()}, which is passed a URL and will execute the appropriate
6 * controller.
7 *
8 * Director also has a number of static methods that provide information about the environment, such as {@link Director::set_environment_type()}.
9 *
10 * @package sapphire
11 * @subpackage control
12 * @see Director::direct(),Director::addRules(),Director::set_environment_type()
13 */
14 class Director {
15
16 static private $urlParams;
17
18 static private $rules = array();
19
20 /**
21 * @var SiteTree
22 */
23 private static $current_page;
24
25 /**
26 * @deprecated 2.4
27 */
28 static $siteMode;
29
30 static $alternateBaseFolder;
31
32 static $alternateBaseURL;
33
34 static $dev_servers = array(
35 'localhost',
36 '127.0.0.1'
37 );
38
39 static $test_servers = array();
40
41 static protected $environment_type;
42
43 /**
44 * @deprecated 2.4
45 */
46 static protected $callbacks;
47
48 function __construct() {
49 if(isset($_GET['debug_profile'])) Profiler::mark("Director", "construct");
50 Session::addToArray('history', substr($_SERVER['REQUEST_URI'], strlen(Director::baseURL())));
51 if(isset($_GET['debug_profile'])) Profiler::unmark("Director", "construct");
52 }
53
54 /**
55 * Return a URL from this user's navigation history.
56 * @param pagesBack The number of pages back to go. The default, 1, returns the previous
57 * page.
58 */
59 static function history($pagesBack = 1) {
60 return Session::get('history.' . intval(sizeof(Session::get('history')) - $pagesBack - 1));
61 }
62
63
64 /**
65 * Add URL matching rules to the Director.
66 *
67 * The director is responsible for turning URLs into Controller objects. It does thi
68 *
69 * @param $priority The priority of the rules; higher values will get your rule checked first.
70 * We recommend priority 100 for your site's rules. The built-in rules are priority 10, standard modules are priority 50.
71 */
72 static function addRules($priority, $rules) {
73 Director::$rules[$priority] = isset(Director::$rules[$priority]) ? array_merge($rules, (array)Director::$rules[$priority]) : $rules;
74 }
75
76 /**
77 * Process the given URL, creating the appropriate controller and executing it.
78 *
79 * Request processing is handled as folows:
80 * - Director::direct() creates a new SS_HTTPResponse object and passes this to Director::handleRequest().
81 * - Director::handleRequest($request) checks each of the Director rules and identifies a controller to handle this
82 * request.
83 * - Controller::handleRequest($request) is then called. This will find a rule to handle the URL, and call the rule
84 * handling method.
85 * - RequestHandler::handleRequest($request) is recursively called whenever a rule handling method returns a
86 * RequestHandler object.
87 *
88 * In addition to request processing, Director will manage the session, and perform the output of the actual response
89 * to the browser.
90 *
91 * @param $url String, the URL the user is visiting, without the querystring.
92 * @uses handleRequest() rule-lookup logic is handled by this.
93 * @uses Controller::run() Controller::run() handles the page logic for a Director::direct() call.
94 */
95 static function direct($url) {
96 // Validate $_FILES array before merging it with $_POST
97 foreach($_FILES as $k => $v) {
98 if(is_array($v['tmp_name'])) {
99 foreach($v['tmp_name'] as $tmpFile) {
100 if($tmpFile && !is_uploaded_file($tmpFile)) {
101 user_error("File upload '$k' doesn't appear to be a valid upload", E_USER_ERROR);
102 }
103 }
104 } else {
105 if($v['tmp_name'] && !is_uploaded_file($v['tmp_name'])) {
106 user_error("File upload '$k' doesn't appear to be a valid upload", E_USER_ERROR);
107 }
108 }
109 }
110
111 $req = new SS_HTTPRequest(
112 (isset($_SERVER['X-HTTP-Method-Override'])) ? $_SERVER['X-HTTP-Method-Override'] : $_SERVER['REQUEST_METHOD'],
113 $url,
114 $_GET,
115 array_merge((array)$_POST, (array)$_FILES),
116 @file_get_contents('php://input')
117 );
118
119 // @todo find better way to extract HTTP headers
120 if(isset($_SERVER['HTTP_ACCEPT'])) $req->addHeader("Accept", $_SERVER['HTTP_ACCEPT']);
121 if(isset($_SERVER['CONTENT_TYPE'])) $req->addHeader("Content-Type", $_SERVER['CONTENT_TYPE']);
122 if(isset($_SERVER['HTTP_REFERER'])) $req->addHeader("Referer", $_SERVER['HTTP_REFERER']);
123
124 // Load the session into the controller
125 $session = new Session(Session::get_all());
126
127
128 $result = Director::handleRequest($req, $session);
129 $session->inst_save();
130
131 // Return code for a redirection request
132 if(is_string($result) && substr($result,0,9) == 'redirect:') {
133 $response = new SS_HTTPResponse();
134 $response->redirect(substr($result, 9));
135 $response->output();
136
137 // Handle a controller
138 } else if($result) {
139 if($result instanceof SS_HTTPResponse) {
140 $response = $result;
141
142 } else {
143 $response = new SS_HTTPResponse();
144 $response->setBody($result);
145 }
146
147 // ?debug_memory=1 will output the number of bytes of memory used for this request
148 if(isset($_REQUEST['debug_memory']) && $_REQUEST['debug_memory']) {
149 Debug::message(sprintf(
150 "Peak memory usage in bytes: %s",
151 number_format(memory_get_peak_usage(),0)
152 ));
153 } else {
154 $response->output();
155 }
156
157 //$controllerObj->getSession()->inst_save();
158 }
159 }
160
161 /**
162 * Test a URL request, returning a response object.
163 *
164 * This method is the counterpart of Director::direct() that is used in functional testing. It will execute the URL given,
165 *
166 * @param string $url The URL to visit
167 * @param array $postVars The $_POST & $_FILES variables
168 * @param Session $session The {@link Session} object representing the current session. By passing the same object to multiple
169 * calls of Director::test(), you can simulate a peristed session.
170 * @param string $httpMethod The HTTP method, such as GET or POST. It will default to POST if postVars is set, GET otherwise.
171 * Overwritten by $postVars['_method'] if present.
172 * @param string $body The HTTP body
173 * @param array $headers HTTP headers with key-value pairs
174 * @param array $cookies to populate $_COOKIE
175 * @return SS_HTTPResponse
176 *
177 * @uses getControllerForURL() The rule-lookup logic is handled by this.
178 * @uses Controller::run() Controller::run() handles the page logic for a Director::direct() call.
179 */
180 static function test($url, $postVars = null, $session = null, $httpMethod = null, $body = null, $headers = null, $cookies = null) {
181 // These are needed so that calling Director::test() doesnt muck with whoever is calling it.
182 // Really, it's some inapproriate coupling and should be resolved by making less use of statics
183 $oldStage = Versioned::current_stage();
184 $getVars = array();
185
186 if(!$httpMethod) $httpMethod = ($postVars || is_array($postVars)) ? "POST" : "GET";
187
188 if(!$session) $session = new Session(null);
189
190 // Back up the current values of the superglobals
191 $existingRequestVars = $_REQUEST;
192 $existingGetVars = $_GET;
193 $existingPostVars = $_POST;
194 $existingSessionVars = $_SESSION;
195 $existingCookies = $_COOKIE;
196 $existingServer = $_SERVER;
197 $existingCookieReportErrors = Cookie::report_errors();
198 $existingRequirementsBackend = Requirements::backend();
199
200 Cookie::set_report_errors(false);
201 Requirements::set_backend(new Requirements_Backend());
202
203 // Handle absolute URLs
204 if (@parse_url($url, PHP_URL_HOST) != '') {
205 $bits = parse_url($url);
206 $_SERVER['HTTP_HOST'] = $bits['host'];
207 $url = Director::makeRelative($url);
208 }
209
210 $urlWithQuerystring = $url;
211 if(strpos($url, '?') !== false) {
212 list($url, $getVarsEncoded) = explode('?', $url, 2);
213 parse_str($getVarsEncoded, $getVars);
214 }
215
216 // Replace the superglobals with appropriate test values
217 $_REQUEST = array_merge((array)$getVars, (array)$postVars);
218 $_GET = (array)$getVars;
219 $_POST = (array)$postVars;
220 $_SESSION = $session ? $session->inst_getAll() : array();
221 $_COOKIE = (array) $cookies;
222 $_SERVER['REQUEST_URI'] = Director::baseURL() . $urlWithQuerystring;
223
224 $req = new SS_HTTPRequest($httpMethod, $url, $getVars, $postVars, $body);
225 if($headers) foreach($headers as $k => $v) $req->addHeader($k, $v);
226 $result = Director::handleRequest($req, $session);
227
228 // Restore the superglobals
229 $_REQUEST = $existingRequestVars;
230 $_GET = $existingGetVars;
231 $_POST = $existingPostVars;
232 $_SESSION = $existingSessionVars;
233 $_COOKIE = $existingCookies;
234 $_SERVER = $existingServer;
235
236 Cookie::set_report_errors($existingCookieReportErrors);
237 Requirements::set_backend($existingRequirementsBackend);
238
239 // These are needed so that calling Director::test() doesnt muck with whoever is calling it.
240 // Really, it's some inapproriate coupling and should be resolved by making less use of statics
241 Versioned::reading_stage($oldStage);
242
243 return $result;
244 }
245
246 /**
247 * Handle an HTTP request, defined with a SS_HTTPRequest object.
248 *
249 * @return SS_HTTPResponse|string
250 */
251 protected static function handleRequest(SS_HTTPRequest $request, Session $session) {
252 krsort(Director::$rules);
253
254 if(isset($_REQUEST['debug'])) Debug::show(Director::$rules);
255 foreach(Director::$rules as $priority => $rules) {
256 foreach($rules as $pattern => $controllerOptions) {
257 if(is_string($controllerOptions)) {
258 if(substr($controllerOptions,0,2) == '->') $controllerOptions = array('Redirect' => substr($controllerOptions,2));
259 else $controllerOptions = array('Controller' => $controllerOptions);
260 }
261
262 if(($arguments = $request->match($pattern, true)) !== false) {
263 // controllerOptions provide some default arguments
264 $arguments = array_merge($controllerOptions, $arguments);
265
266 // Find the controller name
267 if(isset($arguments['Controller'])) $controller = $arguments['Controller'];
268
269 // Pop additional tokens from the tokeniser if necessary
270 if(isset($controllerOptions['_PopTokeniser'])) {
271 $request->shift($controllerOptions['_PopTokeniser']);
272 }
273
274 // Handle redirections
275 if(isset($arguments['Redirect'])) {
276 return "redirect:" . Director::absoluteURL($arguments['Redirect'], true);
277
278 } else {
279 Director::$urlParams = $arguments;
280
281 $controllerObj = new $controller();
282 $controllerObj->setSession($session);
283
284 $result = $controllerObj->handleRequest($request);
285 if(!is_object($result) || $result instanceof SS_HTTPResponse) return $result;
286
287 user_error("Bad result from url " . $request->getURL() . " handled by " .
288 get_class($controllerObj)." controller: ".get_class($result), E_USER_WARNING);
289
290 }
291 }
292 }
293 }
294 }
295
296 /**
297 * Returns the urlParam with the given name
298 */
299 static function urlParam($name) {
300 if(isset(Director::$urlParams[$name])) return Director::$urlParams[$name];
301 }
302
303 /**
304 * Returns an array of urlParams
305 */
306 static function urlParams() {
307 return Director::$urlParams;
308 }
309
310 /**
311 * Return the {@link SiteTree} object that is currently being viewed. If there is no sitetree object to return,
312 * then this will return the current controller.
313 *
314 * @return SiteTree
315 */
316 public static function get_current_page() {
317 return self::$current_page ? self::$current_page : Controller::curr();
318 }
319
320 /**
321 * Set the currently active {@link SiteTree} object that is being used to respond to the request.
322 *
323 * @param SiteTree $page
324 */
325 public static function set_current_page($page) {
326 self::$current_page = $page;
327 }
328
329 /**
330 * @deprecated 2.4 Use {@link Director::get_current_page()}.
331 */
332 static function currentPage() {
333 return self::get_current_page();
334 }
335
336 /**
337 * Turns the given URL into an absolute URL.
338 * @todo Document how relativeToSiteBase works
339 */
340 static function absoluteURL($url, $relativeToSiteBase = false) {
341 if(strpos($url,'/') === false && !$relativeToSiteBase) $url = dirname($_SERVER['REQUEST_URI'] . 'x') . '/' . $url;
342
343 if(substr($url,0,4) != "http") {
344 if($url[0] != "/") $url = Director::baseURL() . $url;
345 // Sometimes baseURL() can return a full URL instead of just a path
346 if(substr($url,0,4) != "http") $url = self::protocolAndHost() . $url;
347 }
348
349 return $url;
350 }
351
352 /**
353 * Returns the part of the URL, 'http://www.mysite.com'.
354 *
355 * @return boolean|string The domain from the PHP environment. Returns FALSE is this environment variable isn't set.
356 */
357 static function protocolAndHost() {
358 if(self::$alternateBaseURL) {
359 if(preg_match('/^(http[^:]*:\/\/[^\/]+)(\/|$)/', self::$alternateBaseURL, $matches)) {
360 return $matches[1];
361 }
362 }
363
364 $s = (isset($_SERVER['SSL']) || (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] && $_SERVER['HTTPS'] != 'off')) ? 's' : '';
365
366 if(isset($_SERVER['HTTP_HOST'])) {
367 return "http$s://" . $_SERVER['HTTP_HOST'];
368 } else {
369 global $_FILE_TO_URL_MAPPING;
370 if(Director::is_cli() && isset($_FILE_TO_URL_MAPPING)) $errorSuggestion = ' You probably want to define '.
371 'an entry in $_FILE_TO_URL_MAPPING that covers "' . Director::baseFolder() . '"';
372 else if(Director::is_cli()) $errorSuggestion = ' You probably want to define $_FILE_TO_URL_MAPPING in '.
373 'your _ss_environment.php as instructed on the "sake" page of the doc.silverstripe.com wiki';
374 else $errorSuggestion = "";
375
376 user_error("Director::protocolAndHost() lacks sufficient information - HTTP_HOST not set.$errorSuggestion", E_USER_WARNING);
377 return false;
378
379 }
380 }
381
382
383 /**
384 * Redirect to another page.
385 * - $url can be an absolute URL
386 * - or it can be a URL relative to the "site base"
387 * - if it is just a word without an slashes, then it redirects to another action on the current controller.
388 */
389 static function redirect($url, $code=302) {
390 Controller::curr()->redirect($url, $code);
391 }
392
393 /**
394 * Tests whether a redirection has been requested.
395 * @return string If redirect() has been called, it will return the URL redirected to. Otherwise, it will return null;
396 */
397 static function redirected_to() {
398 return Controller::curr()->redirectedTo();
399 }
400
401 /**
402 * Sets the HTTP status code
403 */
404 static function set_status_code($code) {
405 return Controller::curr()->getResponse()->setStatusCode($code);
406 }
407
408 /**
409 * Returns the current HTTP status code
410 */
411 static function get_status_code() {
412 return Controller::curr()->getResponse()->getStatusCode();
413 }
414
415 /**
416 * @deprecated 2.5 Use Controller->redirectBack()
417 */
418 static function redirectBack() {
419 Controller::curr()->redirectBack();
420 }
421
422 /**
423 * Returns the root URL for the site.
424 * It will be automatically calculated unless it is overridden with {@link setBaseURL()}.
425 */
426 static function baseURL() {
427 if(self::$alternateBaseURL) return self::$alternateBaseURL;
428 else {
429 $base = BASE_URL;
430 if($base == '/' || $base == '/.' || $base == '\\') $baseURL = '/';
431 else $baseURL = $base . '/';
432
433 if(defined('BASE_SCRIPT_URL')) return $baseURL . BASE_SCRIPT_URL;
434 else return $baseURL;
435 }
436 }
437
438 /**
439 * Sets the root URL for the website.
440 * If the site isn't accessible from the URL you provide, weird things will happen.
441 */
442 static function setBaseURL($baseURL) {
443 self::$alternateBaseURL = $baseURL;
444 }
445
446 /**
447 * Returns the root filesystem folder for the site.
448 * It will be automatically calculated unless it is overridden with {@link setBaseFolder()}.
449 */
450 static function baseFolder() {
451 if(self::$alternateBaseFolder) return self::$alternateBaseFolder;
452 else return BASE_PATH;
453 }
454
455 /**
456 * Sets the root folder for the website.
457 * If the site isn't accessible from the folder you provide, weird things will happen.
458 */
459 static function setBaseFolder($baseFolder) {
460 self::$alternateBaseFolder = $baseFolder;
461 }
462
463 /**
464 * Turns an absolute URL or folder into one that's relative to the root of the site.
465 * This is useful when turning a URL into a filesystem reference, or vice versa.
466 *
467 * @todo Implement checking across http/https protocols
468 *
469 * @param string $url Accepts both a URL or a filesystem path
470 * @return string Either a relative URL if the checks succeeded, or the original (possibly absolute) URL.
471 */
472 static function makeRelative($url) {
473 // Allow for the accidental inclusion of a // in the URL
474 $url = ereg_replace('([^:])//','\\1/',$url);
475 $url = trim($url);
476
477 // Only bother comparing the URL to the absolute version if $url looks like a URL.
478 if(preg_match('/^https?[^:]*:\/\//',$url)) {
479 $base1 = self::absoluteBaseURL();
480 if(substr($url,0,strlen($base1)) == $base1) return substr($url,strlen($base1));
481 // Convert http://www.mydomain.com/mysitedir to ''
482 else if(substr($base1,-1)=="/" && $url == substr($base1,0,-1)) return "";
483 }
484
485 // test for base folder, e.g. /var/www
486 $base2 = self::baseFolder();
487 if(substr($url,0,strlen($base2)) == $base2) return substr($url,strlen($base2));
488
489 // Test for relative base url, e.g. mywebsite/ if the full URL is http://localhost/mywebsite/
490 $base3 = self::baseURL();
491 if(substr($url,0,strlen($base3)) == $base3) return substr($url,strlen($base3));
492
493 // Nothing matched, fall back to returning the original URL
494 return $url;
495 }
496
497 /**
498 * Returns true if a given path is absolute. Works under both *nix and windows
499 * systems
500 *
501 * @param string $path
502 * @return bool
503 */
504 public static function is_absolute($path) {
505 if($path[0] == '/' || $path[0] == '\\') return true;
506 return preg_match('/^[a-zA-Z]:[\\\\\/]/', $path) == 1;
507 }
508
509 /**
510 * Checks if a given URL is absolute (e.g. starts with 'http://' etc.).
511 *
512 * @param string $url
513 * @return boolean
514 */
515 public static function is_absolute_url($url) {
516 $url = trim($url);
517 // remove all query strings to avoid parse_url choking on URLs like 'test.com?url=http://test.com'
518 $url = preg_replace('/(.*)\?.*/', '$1', $url);
519 $parsed = parse_url($url);
520 return (isset($parsed['scheme']));
521 }
522
523 /**
524 * Checks if a given URL is relative by checking
525 * {@link is_absolute_url()}.
526 *
527 * @param string $url
528 * @return boolean
529 */
530 public static function is_relative_url($url) {
531 return (!Director::is_absolute_url($url));
532 }
533
534 /**
535 * Checks if the given URL is belonging to this "site",
536 * as defined by {@link makeRelative()} and {@link absoluteBaseUrl()}.
537 * Useful to check before redirecting based on a URL from user submissions
538 * through $_GET or $_POST, and avoid phishing attacks by redirecting
539 * to an attackers server.
540 *
541 * @param string $url
542 * @return boolean
543 */
544 public static function is_site_url($url) {
545 $relativeUrl = Director::makeRelative($url);
546 return (bool)self::is_relative_url($relativeUrl);
547 }
548
549 /**
550 * Given a filesystem reference relative to the site root, return the full file-system path.
551 *
552 * @param string $file
553 * @return string
554 */
555 public static function getAbsFile($file) {
556 return self::is_absolute($file) ? $file : Director::baseFolder() . '/' . $file;
557 }
558
559 /**
560 * Returns true if the given file exists.
561 * @param $file Filename specified relative to the site root
562 */
563 static function fileExists($file) {
564 // replace any appended query-strings, e.g. /path/to/foo.php?bar=1 to /path/to/foo.php
565 $file = preg_replace('/([^\?]*)?.*/','$1',$file);
566 return file_exists(Director::getAbsFile($file));
567 }
568
569 /**
570 * Returns the Absolute URL of the site root.
571 */
572 static function absoluteBaseURL() {
573 if(Object::has_extension('SiteTree', 'SiteTreeSubsites')){
574 return Director::absoluteURL('/');
575 }
576 return Director::absoluteURL(Director::baseURL());
577 }
578
579 /**
580 * Returns the Absolute URL of the site root, embedding the current basic-auth credentials into the URL.
581 */
582 static function absoluteBaseURLWithAuth() {
583 $s = "";
584 $login = "";
585
586 if(isset($_SERVER['PHP_AUTH_USER'])) $login = "$_SERVER[PHP_AUTH_USER]:$_SERVER[PHP_AUTH_PW]@";
587 if(isset($_SERVER['SSL']) && $_SERVER['SSL'] != 'Off') $s = "s";
588
589 return "http$s://" . $login . $_SERVER['HTTP_HOST'] . Director::baseURL();
590 }
591
592 /**
593 * Force the site to run on SSL. To use, call from _config.php.
594 *
595 * For example:
596 * <code>
597 * if(Director::isLive()) Director::forceSSL();
598 * </code>
599 */
600 static function forceSSL() {
601 if((!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] == 'off') && !Director::isDev() && !Director::is_cli()) {
602 $destURL = str_replace('http:', 'https:', Director::absoluteURL($_SERVER['REQUEST_URI']));
603
604 header("Location: $destURL", true, 301);
605 die("<h1>Your browser is not accepting header redirects</h1><p>Please <a href=\"$destURL\">click here</a>");
606 }
607 }
608
609 /**
610 * Force a redirect to a domain starting with "www."
611 */
612 static function forceWWW() {
613 if(!Director::isDev() && !Director::isTest() && strpos($_SERVER['HTTP_HOST'], 'www') !== 0) {
614 if(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') {
615 $destURL = str_replace('https://', 'https://www.', Director::absoluteURL($_SERVER['REQUEST_URI']));
616 } else {
617 $destURL = str_replace('http://', 'http://www.', Director::absoluteURL($_SERVER['REQUEST_URI']));
618 }
619
620 header("Location: $destURL", true, 301);
621 die("<h1>Your browser is not accepting header redirects</h1><p>Please <a href=\"$destURL\">click here</a>");
622 }
623 }
624
625 /**
626 * Checks if the current HTTP-Request is an "Ajax-Request"
627 * by checking for a custom header set by prototype.js or
628 * wether a manually set request-parameter 'ajax' is present.
629 *
630 * @return boolean
631 */
632 static function is_ajax() {
633 if(Controller::has_curr()) {
634 return Controller::curr()->isAjax();
635 } else {
636 return (
637 isset($_REQUEST['ajax']) ||
638 (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == "XMLHttpRequest")
639 );
640 }
641 }
642
643 /**
644 * Returns true if this script is being run from the command line rather than the webserver.
645 *
646 * @return boolean
647 */
648 public static function is_cli() {
649 return (php_sapi_name() == "cli");
650 }
651
652 ////////////////////////////////////////////////////////////////////////////////////////////
653 // Site mode methods
654 ////////////////////////////////////////////////////////////////////////////////////////////
655
656 /**
657 * @deprecated 2.4
658 */
659 static function set_site_mode($mode) {
660 user_error (
661 'Director::set_site_mode() is deprecated as the functionality is no longer neccesary.', E_USER_NOTICE
662 );
663
664 Director::$siteMode = $mode;
665
666 if(isset(self::$callbacks[$mode])) {
667 foreach(self::$callbacks[$mode] as $extension) {
668 call_user_func($extension);
669 }
670 }
671 }
672
673 /**
674 * @deprecated 2.4
675 */
676 static function get_site_mode() {
677 user_error (
678 'Director::set_site_mode() is deprecated as the functionality is no longer neccesary.', E_USER_NOTICE
679 );
680
681 return Director::$siteMode;
682 }
683
684 /**
685 * @deprecated 2.4 Use a custom extension on your controller.
686 */
687 static function add_callback($function, $mode = 'site') {
688 user_error (
689 'Director::add_callback() is deprecated, please use a custom extension on your controller', E_USER_NOTICE
690 );
691
692 self::$callbacks[$mode][] = $function;
693 }
694
695 ////////////////////////////////////////////////////////////////////////////////////////////
696 // Environment type methods
697 ////////////////////////////////////////////////////////////////////////////////////////////
698
699 /**
700 * Set the environment type of the current site.
701 *
702 * Typically, a SilverStripe site have a number of environments:
703 * - development environments, such a copy on your local machine.
704 * - test sites, such as the one you show the client before going live.
705 * - the live site itself.
706 *
707 * The behaviour of these environments often varies slightly. For example, development sites may have errors dumped to the screen,
708 * and order confirmation emails might be sent to the developer instead of the client.
709 *
710 * To help with this, Sapphire support the notion of an environment type. The environment type can be dev, test, or live.
711 *
712 * You can set it explicitly with Director::set_environment_tpye(). Or you can use {@link Director::set_dev_servers()} and {@link Director::set_test_servers()}
713 * to set it implicitly, based on the value of $_SERVER['HTTP_HOST']. If the HTTP_HOST value is one of the servers listed, then
714 * the environment type will be test or dev. Otherwise, the environment type will be live.
715 *
716 * Dev mode can also be forced by putting ?isDev=1 in your URL, which will ask you to log in and then push the site into dev
717 * mode for the remainder of the session. Putting ?isDev=0 onto the URL can turn it back.
718 *
719 * Test mode can also be forced by putting ?isTest=1 in your URL, which will ask you to log in and then push the site into test
720 * mode for the remainder of the session. Putting ?isTest=0 onto the URL can turn it back.
721 *
722 * Generally speaking, these methods will be called from your _config.php file.
723 *
724 * Once the environment type is set, it can be checked with {@link Director::isDev()}, {@link Director::isTest()}, and
725 * {@link Director::isLive()}.
726 *
727 * @param $et string The environment type: dev, test, or live.
728 */
729 static function set_environment_type($et) {
730 if($et != 'dev' && $et != 'test' && $et != 'live') {
731 SS_Backtrace::backtrace();
732 user_error("Director::set_environment_type passed '$et'. It should be passed dev, test, or live", E_USER_WARNING);
733 } else {
734 self::$environment_type = $et;
735 }
736 }
737
738 /**
739 * Can also be checked with {@link Director::isDev()}, {@link Director::isTest()}, and {@link Director::isLive()}.
740 *
741 * @return string 'dev', 'test' or 'live'
742 */
743 static function get_environment_type() {
744 if(Director::isLive()) {
745 return 'live';
746 } elseif(Director::isTest()) {
747 return 'test';
748 } elseif(Director::isDev()) {
749 return 'dev';
750 } else {
751 return false;
752 }
753 }
754
755 /**
756 * Specify HTTP_HOST values that are development environments.
757 * For information about environment types, see {@link Director::set_environment_type()}.
758 * @param $servers array An array of HTTP_HOST values that should be treated as development environments.
759 */
760 static function set_dev_servers($servers) {
761 Director::$dev_servers = $servers;
762 }
763
764 /**
765 * Specify HTTP_HOST values that are test environments.
766 * For information about environment types, see {@link Director::set_environment_type()}.
767 * @param $servers array An array of HTTP_HOST values that should be treated as test environments.
768 */
769 static function set_test_servers($servers) {
770 Director::$test_servers = $servers;
771 }
772
773 /*
774 * This function will return true if the site is in a live environment.
775 * For information about environment types, see {@link Director::set_environment_type()}.
776 */
777 static function isLive() {
778 return !(Director::isDev() || Director::isTest());
779 }
780
781 /**
782 * This function will return true if the site is in a development environment.
783 * For information about environment types, see {@link Director::set_environment_type()}.
784 */
785 static function isDev() {
786 // This variable is used to supress repetitions of the isDev security message below.
787 static $firstTimeCheckingGetVar = true;
788
789 // Use ?isDev=1 to get development access on the live server
790 if (isset($_GET['isDev']) && $firstTimeCheckingGetVar) {
791 if(Security::database_is_ready()) {
792 $firstTimeCheckingGetVar = false;
793 if(!Permission::check('ADMIN')){
794 BasicAuth::requireLogin("SilverStripe developer access. Use your CMS login", "ADMIN");
795 }
796 $_SESSION['isDev'] = $_GET['isDev'];
797 }
798 elseif(DB::connection_attempted()) {
799 $firstTimeCheckingGetVar = false;
800 echo "<p style=\"padding: 3px; margin: 3px; background-color: orange;
801 color: white; font-weight: bold\">Sorry, you can't use ?isDev=1 until your
802 Member and Group tables database are available. Perhaps your database
803 connection is failing?</p>";
804 }
805 }
806
807 if(isset($_SESSION['isDev']) && $_SESSION['isDev']) return true;
808
809 if(self::$environment_type) return self::$environment_type == 'dev';
810
811 // Check if we are running on one of the development servers
812 if(isset($_SERVER['HTTP_HOST']) && in_array($_SERVER['HTTP_HOST'], Director::$dev_servers)) {
813 return true;
814 }
815
816 return false;
817 }
818
819 /**
820 * This function will return true if the site is in a test environment.
821 * For information about environment types, see {@link Director::set_environment_type()}.
822 */
823 static function isTest() {
824 // Use ?isTest=1 to get test access on the live server, or explicitly set your environment
825 if(isset($_GET['isTest'])) {
826 if(Security::database_is_ready()) {
827 BasicAuth::requireLogin("SilverStripe developer access. Use your CMS login", "ADMIN");
828 $_SESSION['isTest'] = $_GET['isTest'];
829 } else {
830 return true;
831 }
832 }
833 if(self::isDev()) return false;
834
835 if(self::$environment_type) {
836 return self::$environment_type == 'test';
837 }
838
839 // Check if we are running on one of the test servers
840 if(isset($_SERVER['HTTP_HOST']) && in_array($_SERVER['HTTP_HOST'], Director::$test_servers)) {
841 return true;
842 }
843
844 return false;
845 }
846
847 }
848 ?>
849