protected function init() { parent::init(); $isRunningTests = class_exists('SilverStripe\\Dev\\SapphireTest', false) && SapphireTest::is_running_test(); $canAccess = Director::isDev() || Director::is_cli() && !$isRunningTests || Permission::check("ADMIN"); if (!$canAccess) { Security::permissionFailure($this); } }
protected function init() { parent::init(); // We allow access to this controller regardless of live-status or ADMIN permission only // if on CLI or with the database not ready. The latter makes it less errorprone to do an // initial schema build without requiring a default-admin login. // Access to this controller is always allowed in "dev-mode", or of the user is ADMIN. $isRunningTests = class_exists('SilverStripe\\Dev\\SapphireTest', false) && SapphireTest::is_running_test(); $canAccess = Director::isDev() || !Security::database_is_ready() || Director::is_cli() && !$isRunningTests || Permission::check("ADMIN"); if (!$canAccess) { Security::permissionFailure($this, "This page is secured and you need administrator rights to access it. " . "Enter your credentials below and we will send you right along."); } }
protected function init() { parent::init(); // Special case for dev/build: Defer permission checks to DatabaseAdmin->init() (see #4957) $requestedDevBuild = stripos($this->getRequest()->getURL(), 'dev/build') === 0 && stripos($this->getRequest()->getURL(), 'dev/build/defaults') === false; // We allow access to this controller regardless of live-status or ADMIN permission only // if on CLI. Access to this controller is always allowed in "dev-mode", or of the user is ADMIN. $canAccess = $requestedDevBuild || Director::isDev() || Director::is_cli() || Permission::check("ADMIN"); if (!$canAccess) { Security::permissionFailure($this); return; } // check for valid url mapping // lacking this information can cause really nasty bugs, // e.g. when running Director::test() from a FunctionalTest instance global $_FILE_TO_URL_MAPPING; if (Director::is_cli()) { if (isset($_FILE_TO_URL_MAPPING)) { $testPath = BASE_PATH; $matched = false; while ($testPath && $testPath != "/" && !preg_match('/^[A-Z]:\\\\$/', $testPath)) { if (isset($_FILE_TO_URL_MAPPING[$testPath])) { $matched = true; break; } $testPath = dirname($testPath); } if (!$matched) { echo 'Warning: You probably want to define ' . 'an entry in $_FILE_TO_URL_MAPPING that covers "' . Director::baseFolder() . '"' . "\n"; } } else { echo 'Warning: You probably want to define $_FILE_TO_URL_MAPPING in ' . 'your _ss_environment.php as instructed on the "sake" page of the doc.silverstripe.org wiki' . "\n"; } } // Backwards compat: Default to "draft" stage, which is important // for tasks like dev/build which call DataObject->requireDefaultRecords(), // but also for other administrative tasks which have assumptions about the default stage. Versioned::set_stage(Versioned::DRAFT); }
/** * Add the appropriate caching headers to the response, including If-Modified-Since / 304 handling. * Note that setting HTTP::$cache_age will overrule any cache headers set by PHP's * session_cache_limiter functionality. It is your responsibility to ensure only cacheable data * is in fact cached, and HTTP::$cache_age isn't set when the HTTP body contains session-specific * content. * * Omitting the $body argument or passing a string is deprecated; in these cases, the headers are * output directly. * * @param HTTPResponse $body */ public static function add_cache_headers($body = null) { $cacheAge = self::$cache_age; // Validate argument if ($body && !$body instanceof HTTPResponse) { user_error("HTTP::add_cache_headers() must be passed an HTTPResponse object", E_USER_WARNING); $body = null; } // Development sites have frequently changing templates; this can get stuffed up by the code // below. if (Director::isDev()) { $cacheAge = 0; } // The headers have been sent and we don't have an HTTPResponse object to attach things to; no point in // us trying. if (headers_sent() && !$body) { return; } // Populate $responseHeaders with all the headers that we want to build $responseHeaders = array(); $cacheControlHeaders = Config::inst()->get(__CLASS__, 'cache_control'); // currently using a config setting to cancel this, seems to be so that the CMS caches ajax requests if (function_exists('apache_request_headers') && Config::inst()->get(__CLASS__, 'cache_ajax_requests')) { $requestHeaders = array_change_key_case(apache_request_headers(), CASE_LOWER); if (isset($requestHeaders['x-requested-with']) && $requestHeaders['x-requested-with'] == 'XMLHttpRequest') { $cacheAge = 0; } } if ($cacheAge > 0) { $cacheControlHeaders['max-age'] = self::$cache_age; // Set empty pragma to avoid PHP's session_cache_limiter adding conflicting caching information, // defaulting to "nocache" on most PHP configurations (see http://php.net/session_cache_limiter). // Since it's a deprecated HTTP 1.0 option, all modern HTTP clients and proxies should // prefer the caching information indicated through the "Cache-Control" header. $responseHeaders["Pragma"] = ""; // To do: User-Agent should only be added in situations where you *are* actually // varying according to user-agent. $vary = Config::inst()->get(__CLASS__, 'vary'); if ($vary && strlen($vary)) { $responseHeaders['Vary'] = $vary; } } else { $contentDisposition = null; if ($body) { // Grab header for checking. Unfortunately HTTPRequest uses a mistyped variant. $contentDisposition = $body->getHeader('Content-disposition'); if (!$contentDisposition) { $contentDisposition = $body->getHeader('Content-Disposition'); } } if ($body && Director::is_https() && isset($_SERVER['HTTP_USER_AGENT']) && strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE') == true && strstr($contentDisposition, 'attachment;') == true) { // IE6-IE8 have problems saving files when https and no-cache are used // (http://support.microsoft.com/kb/323308) // Note: this is also fixable by ticking "Do not save encrypted pages to disk" in advanced options. $cacheControlHeaders['max-age'] = 3; // Set empty pragma to avoid PHP's session_cache_limiter adding conflicting caching information, // defaulting to "nocache" on most PHP configurations (see http://php.net/session_cache_limiter). // Since it's a deprecated HTTP 1.0 option, all modern HTTP clients and proxies should // prefer the caching information indicated through the "Cache-Control" header. $responseHeaders["Pragma"] = ""; } else { $cacheControlHeaders['no-cache'] = "true"; $cacheControlHeaders['no-store'] = "true"; } } foreach ($cacheControlHeaders as $header => $value) { if (is_null($value)) { unset($cacheControlHeaders[$header]); } elseif (is_bool($value) && $value || $value === "true") { $cacheControlHeaders[$header] = $header; } else { $cacheControlHeaders[$header] = $header . "=" . $value; } } $responseHeaders['Cache-Control'] = implode(', ', $cacheControlHeaders); unset($cacheControlHeaders, $header, $value); if (self::$modification_date && $cacheAge > 0) { $responseHeaders["Last-Modified"] = self::gmt_date(self::$modification_date); // Chrome ignores Varies when redirecting back (http://code.google.com/p/chromium/issues/detail?id=79758) // which means that if you log out, you get redirected back to a page which Chrome then checks against // last-modified (which passes, getting a 304) // when it shouldn't be trying to use that page at all because it's the "logged in" version. // By also using and etag that includes both the modification date and all the varies // values which we also check against we can catch this and not return a 304 $etagParts = array(self::$modification_date, serialize($_COOKIE)); $etagParts[] = Director::is_https() ? 'https' : 'http'; if (isset($_SERVER['HTTP_USER_AGENT'])) { $etagParts[] = $_SERVER['HTTP_USER_AGENT']; } if (isset($_SERVER['HTTP_ACCEPT'])) { $etagParts[] = $_SERVER['HTTP_ACCEPT']; } $etag = sha1(implode(':', $etagParts)); $responseHeaders["ETag"] = $etag; // 304 response detection if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { $ifModifiedSince = strtotime(stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE'])); // As above, only 304 if the last request had all the same varies values // (or the etag isn't passed as part of the request - but with chrome it always is) $matchesEtag = !isset($_SERVER['HTTP_IF_NONE_MATCH']) || $_SERVER['HTTP_IF_NONE_MATCH'] == $etag; if ($ifModifiedSince >= self::$modification_date && $matchesEtag) { if ($body) { $body->setStatusCode(304); $body->setBody(''); } else { header('HTTP/1.0 304 Not Modified'); die; } } } $expires = time() + $cacheAge; $responseHeaders["Expires"] = self::gmt_date($expires); } if (self::$etag) { $responseHeaders['ETag'] = self::$etag; } // etag needs to be a quoted string according to HTTP spec if (!empty($responseHeaders['ETag']) && 0 !== strpos($responseHeaders['ETag'], '"')) { $responseHeaders['ETag'] = sprintf('"%s"', $responseHeaders['ETag']); } // Now that we've generated them, either output them or attach them to the HTTPResponse as appropriate foreach ($responseHeaders as $k => $v) { if ($body) { // Set the header now if it's not already set. if ($body->getHeader($k) === null) { $body->addHeader($k, $v); } } elseif (!headers_sent()) { header("{$k}: {$v}"); } } }
public function parseTemplateContent($content, $template = "") { return $this->getParser()->compileString($content, $template, Director::isDev() && SSViewer::config()->get('source_file_comments')); }
/** * Allows the display and benchmarking of queries as they are being run * * @param string $sql Query to run, and single parameter to callback * @param callable $callback Callback to execute code * @param array $parameters Parameters for any parameterised query * @return mixed Result of query */ protected function benchmarkQuery($sql, $callback, $parameters = array()) { if (isset($_REQUEST['showqueries']) && Director::isDev()) { $this->queryCount++; $starttime = microtime(true); $result = $callback($sql); $endtime = round(microtime(true) - $starttime, 4); // replace parameters as closely as possible to what we'd expect the DB to put in if (strtolower($_REQUEST['showqueries']) == 'inline') { $sql = DB::inline_parameters($sql, $parameters); } Debug::message("\n{$sql}\n{$endtime}s\n", false); return $result; } else { return $callback($sql); } }
/** * @uses LeftAndMainExtension->init() * @uses LeftAndMainExtension->accessedCMS() * @uses CMSMenu */ protected function init() { parent::init(); SSViewer::config()->update('rewrite_hash_links', false); ContentNegotiator::config()->update('enabled', false); // set language $member = Member::currentUser(); if (!empty($member->Locale)) { i18n::set_locale($member->Locale); } if (!empty($member->DateFormat)) { i18n::config()->date_format = $member->DateFormat; } if (!empty($member->TimeFormat)) { i18n::config()->time_format = $member->TimeFormat; } // can't be done in cms/_config.php as locale is not set yet CMSMenu::add_link('Help', _t('LeftAndMain.HELP', 'Help', 'Menu title'), $this->config()->help_link, -2, array('target' => '_blank')); // Allow customisation of the access check by a extension // Also all the canView() check to execute Controller::redirect() if (!$this->canView() && !$this->getResponse()->isFinished()) { // When access /admin/, we should try a redirect to another part of the admin rather than be locked out $menu = $this->MainMenu(); foreach ($menu as $candidate) { if ($candidate->Link && $candidate->Link != $this->Link() && $candidate->MenuItem->controller && singleton($candidate->MenuItem->controller)->canView()) { $this->redirect($candidate->Link); return; } } if (Member::currentUser()) { Session::set("BackURL", null); } // if no alternate menu items have matched, return a permission error $messageSet = array('default' => _t('LeftAndMain.PERMDEFAULT', "You must be logged in to access the administration area; please enter your credentials below."), 'alreadyLoggedIn' => _t('LeftAndMain.PERMALREADY', "I'm sorry, but you can't access that part of the CMS. If you want to log in as someone else, do" . " so below."), 'logInAgain' => _t('LeftAndMain.PERMAGAIN', "You have been logged out of the CMS. If you would like to log in again, enter a username and" . " password below.")); Security::permissionFailure($this, $messageSet); return; } // Don't continue if there's already been a redirection request. if ($this->redirectedTo()) { return; } // Audit logging hook if (empty($_REQUEST['executeForm']) && !$this->getRequest()->isAjax()) { $this->extend('accessedCMS'); } // Set the members html editor config if (Member::currentUser()) { HTMLEditorConfig::set_active_identifier(Member::currentUser()->getHtmlEditorConfigForCMS()); } // Set default values in the config if missing. These things can't be defined in the config // file because insufficient information exists when that is being processed $htmlEditorConfig = HTMLEditorConfig::get_active(); $htmlEditorConfig->setOption('language', i18n::get_tinymce_lang()); Requirements::customScript("\n\t\t\twindow.ss = window.ss || {};\n\t\t\twindow.ss.config = " . $this->getCombinedClientConfig() . ";\n\t\t"); Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/vendor.js'); Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/bundle.js'); Requirements::css(ltrim(FRAMEWORK_ADMIN_DIR . '/client/dist/styles/bundle.css', '/')); Requirements::add_i18n_javascript(ltrim(FRAMEWORK_DIR . '/client/lang', '/'), false, true); Requirements::add_i18n_javascript(FRAMEWORK_ADMIN_DIR . '/client/lang', false, true); if ($this->config()->session_keepalive_ping) { Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/LeftAndMain.Ping.js'); } if (Director::isDev()) { // TODO Confuses jQuery.ondemand through document.write() Requirements::javascript(ADMIN_THIRDPARTY_DIR . '/jquery-entwine/src/jquery.entwine.inspector.js'); Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/client/dist/js/leaktools.js'); } // Custom requirements $extraJs = $this->stat('extra_requirements_javascript'); if ($extraJs) { foreach ($extraJs as $file => $config) { if (is_numeric($file)) { $file = $config; } Requirements::javascript($file); } } $extraCss = $this->stat('extra_requirements_css'); if ($extraCss) { foreach ($extraCss as $file => $config) { if (is_numeric($file)) { $file = $config; $config = array(); } Requirements::css($file, isset($config['media']) ? $config['media'] : null); } } $extraThemedCss = $this->stat('extra_requirements_themedCss'); if ($extraThemedCss) { foreach ($extraThemedCss as $file => $config) { if (is_numeric($file)) { $file = $config; $config = array(); } Requirements::themedCSS($file, isset($config['media']) ? $config['media'] : null); } } $dummy = null; $this->extend('init', $dummy); // Assign default cms theme and replace user-specified themes SSViewer::set_themes($this->config()->admin_themes); //set the reading mode for the admin to stage Versioned::set_stage(Versioned::DRAFT); }
/** * Called once per test case ({@link SapphireTest} subclass). * This is different to {@link setUp()}, which gets called once * per method. Useful to initialize expensive operations which * don't change state for any called method inside the test, * e.g. dynamically adding an extension. See {@link tearDownOnce()} * for tearing down the state again. */ public function setUpOnce() { //nest config and injector for each suite so they are effectively sandboxed Config::nest(); Injector::nest(); $isAltered = false; if (!Director::isDev()) { user_error('Tests can only run in "dev" mode', E_USER_ERROR); } // Remove any illegal extensions that are present foreach ($this->illegalExtensions as $class => $extensions) { foreach ($extensions as $extension) { if ($class::has_extension($extension)) { if (!isset($this->extensionsToReapply[$class])) { $this->extensionsToReapply[$class] = array(); } $this->extensionsToReapply[$class][] = $extension; $class::remove_extension($extension); $isAltered = true; } } } // Add any required extensions that aren't present foreach ($this->requiredExtensions as $class => $extensions) { $this->extensionsToRemove[$class] = array(); foreach ($extensions as $extension) { if (!$class::has_extension($extension)) { if (!isset($this->extensionsToRemove[$class])) { $this->extensionsToReapply[$class] = array(); } $this->extensionsToRemove[$class][] = $extension; $class::add_extension($extension); $isAltered = true; } } } // If we have made changes to the extensions present, then migrate the database schema. if ($isAltered || $this->extensionsToReapply || $this->extensionsToRemove || $this->extraDataObjects) { if (!self::using_temp_db()) { self::create_temp_db(); } $this->resetDBSchema(true); } // clear singletons, they're caching old extension info // which is used in DatabaseAdmin->doBuild() Injector::inst()->unregisterAllObjects(); // Set default timezone consistently to avoid NZ-specific dependencies date_default_timezone_set('UTC'); }
/** * This function will return true if the site is in a live environment. For information about * environment types, see {@link Director::set_environment_type()}. * * @return bool */ public static function isLive() { return !(Director::isDev() || Director::isTest()); }
/** * Check if the user has permissions to run URL debug tools, * else redirect them to log in. */ public static function require_developer_login() { if (Director::isDev()) { return; } if (isset($_SESSION['loggedInAs'])) { // We have to do some raw SQL here, because this method is called in Object::defineMethods(). // This means we have to be careful about what objects we create, as we don't want Object::defineMethods() // being called again. // This basically calls Permission::checkMember($_SESSION['loggedInAs'], 'ADMIN'); // @TODO - Rewrite safely using DataList::filter $memberID = $_SESSION['loggedInAs']; $permission = DB::prepared_query(' SELECT "ID" FROM "Permission" INNER JOIN "Group_Members" ON "Permission"."GroupID" = "Group_Members"."GroupID" WHERE "Permission"."Code" = ? AND "Permission"."Type" = ? AND "Group_Members"."MemberID" = ?', array('ADMIN', Permission::GRANT_PERMISSION, $memberID))->value(); if ($permission) { return; } } // This basically does the same as // Security::permissionFailure(null, "You need to login with developer access to make use of debugging tools.") // We have to do this because of how early this method is called in execution. $_SESSION['SilverStripe\\Security\\Security']['Message']['message'] = "You need to login with developer access to make use of debugging tools."; $_SESSION['SilverStripe\\Security\\Security']['Message']['type'] = 'warning'; $_SESSION['BackURL'] = $_SERVER['REQUEST_URI']; header($_SERVER['SERVER_PROTOCOL'] . " 302 Found"); header("Location: " . Director::baseURL() . Security::login_url()); die; }
require_once 'Core/Core.php'; // Connect to database global $databaseConfig; if ($databaseConfig) { DB::connect($databaseConfig); } // Check if a token is requesting a redirect if (!$reloadToken) { return; } // Otherwise, we start up the session if needed if (!isset($_SESSION) && Session::request_contains_session_id()) { Session::start(); } // Next, check if we're in dev mode, or the database doesn't have any security data, or we are admin if (Director::isDev() || !Security::database_is_ready() || Permission::check('ADMIN')) { return $reloadToken->reloadWithToken(); } // Fail and redirect the user to the login page $loginPage = Director::absoluteURL(Security::config()->login_url); $loginPage .= "?BackURL=" . urlencode($_SERVER['REQUEST_URI']); header('location: ' . $loginPage, true, 302); die; })->thenIfErrored(function () use($reloadToken) { if ($reloadToken) { $reloadToken->reloadWithToken(); } })->execute(); global $databaseConfig; // Redirect to the installer if no database is selected if (!isset($databaseConfig) || !isset($databaseConfig['database']) || !$databaseConfig['database']) {
/** * Send this HTTPReponse to the browser */ public function output() { // Attach appropriate X-Include-JavaScript and X-Include-CSS headers if (Director::is_ajax()) { Requirements::include_in_response($this); } if (in_array($this->statusCode, self::$redirect_codes) && headers_sent($file, $line)) { $url = Director::absoluteURL($this->headers['Location'], true); $urlATT = Convert::raw2htmlatt($url); $urlJS = Convert::raw2js($url); $title = Director::isDev() ? "{$urlATT}... (output started on {$file}, line {$line})" : "{$urlATT}..."; echo <<<EOT <p>Redirecting to <a href="{$urlATT}" title="Click this link if your browser does not redirect you">{$title}</a></p> <meta http-equiv="refresh" content="1; url={$urlATT}" /> <script type="application/javascript">setTimeout(function(){ \twindow.location.href = "{$urlJS}"; }, 50);</script> EOT; } else { $line = $file = null; if (!headers_sent($file, $line)) { header($_SERVER['SERVER_PROTOCOL'] . " {$this->statusCode} " . $this->getStatusDescription()); foreach ($this->headers as $header => $value) { //etags need to be quoted if (strcasecmp('etag', $header) === 0 && 0 !== strpos($value, '"')) { $value = sprintf('"%s"', $value); } header("{$header}: {$value}", true, $this->statusCode); } } else { // It's critical that these status codes are sent; we need to report a failure if not. if ($this->statusCode >= 300) { user_error("Couldn't set response type to {$this->statusCode} because " . "of output on line {$line} of {$file}", E_USER_WARNING); } } // Only show error pages or generic "friendly" errors if the status code signifies // an error, and the response doesn't have any body yet that might contain // a more specific error description. if (Director::isLive() && $this->isError() && !$this->body) { $formatter = Injector::inst()->get('FriendlyErrorFormatter'); echo $formatter->format(array('code' => $this->statusCode)); } else { echo $this->body; } } }
/** * Returns false if the non-prefilterable parts of the rule aren't met, and true if they are * * @param array $rules * @return bool|string */ public function matchesVariantRules($rules) { $matches = "undefined"; // Needs to be truthy, but not true foreach ($rules as $k => $v) { switch (strtolower($k)) { case 'classexists': case 'moduleexists': break; case 'environment': switch (strtolower($v)) { case 'live': $matches = $matches && Director::isLive(); break; case 'test': $matches = $matches && Director::isTest(); break; case 'dev': $matches = $matches && Director::isDev(); break; default: user_error('Unknown environment ' . $v . ' in config fragment', E_USER_ERROR); } break; case 'envvarset': $matches = $matches && isset($_ENV[$v]); break; case 'constantdefined': $matches = $matches && defined($v); break; default: $matches = $matches && (isset($_ENV[$k]) && $_ENV[$k] == $v || defined($k) && constant($k) == $v); break; } if ($matches === false) { return $matches; } } return $matches; }
/** * Determine if deprecation notices should be displayed * * @return bool */ public static function get_enabled() { // Deprecation is only available on dev if (!Director::isDev()) { return false; } if (isset(self::$enabled)) { return self::$enabled; } if (defined('SS_DEPRECATION_ENABLED')) { return SS_DEPRECATION_ENABLED; } return true; }
/** * Tests isDev, isTest, isLive set from querystring */ public function testQueryIsEnvironment() { // Reset unset($_SESSION['isDev']); unset($_SESSION['isLive']); unset($_GET['isTest']); unset($_GET['isDev']); $_SESSION = $_SESSION ?: array(); // Test isDev=1 $_GET['isDev'] = '1'; $this->assertTrue(Director::isDev()); $this->assertFalse(Director::isTest()); $this->assertFalse(Director::isLive()); // Test persistence unset($_GET['isDev']); $this->assertTrue(Director::isDev()); $this->assertFalse(Director::isTest()); $this->assertFalse(Director::isLive()); // Test change to isTest $_GET['isTest'] = '1'; $this->assertFalse(Director::isDev()); $this->assertTrue(Director::isTest()); $this->assertFalse(Director::isLive()); // Test persistence unset($_GET['isTest']); $this->assertFalse(Director::isDev()); $this->assertTrue(Director::isTest()); $this->assertFalse(Director::isLive()); }