dataRecord. Any unrecognised method calls, for example, Title() * and Content(), will be passed along to the data record, * * Subclasses of ContentController are generally instantiated by ModelAsController; this will create * a controller based on the URLSegment action variable, by looking in the SiteTree table. * * @todo Can this be used for anything other than SiteTree controllers? * * @package cms * @subpackage control */ class ContentController extends Controller { protected $dataRecord; private static $extensions = array('OldPageRedirector'); private static $allowed_actions = array( 'successfullyinstalled', 'deleteinstallfiles', // secured through custom code 'LoginForm' ); /** * The ContentController will take the URLSegment parameter from the URL and use that to look * up a SiteTree record. */ public function __construct($dataRecord = null) { if(!$dataRecord) { $dataRecord = new Page(); if($this->hasMethod("Title")) $dataRecord->Title = $this->Title(); $dataRecord->URLSegment = get_class($this); $dataRecord->ID = -1; } $this->dataRecord = $dataRecord; $this->failover = $this->dataRecord; parent::__construct(); } /** * Return the link to this controller, but force the expanded link to be returned so that form methods and * similar will function properly. * * @param string|null $action Action to link to. * @return string */ public function Link($action = null) { return $this->data()->Link(($action ? $action : true)); } //----------------------------------------------------------------------------------// // These flexible data methods remove the need for custom code to do simple stuff /** * Return the children of a given page. The parent reference can either be a page link or an ID. * * @param string|int $parentRef * @return SS_List */ public function ChildrenOf($parentRef) { $parent = SiteTree::get_by_link($parentRef); if(!$parent && is_numeric($parentRef)) { $parent = DataObject::get_by_id('SiteTree', $parentRef); } if($parent) return $parent->Children(); } /** * @param string $link * @return SiteTree */ public function Page($link) { return SiteTree::get_by_link($link); } public function init() { parent::init(); // If we've accessed the homepage as /home/, then we should redirect to /. if($this->dataRecord && $this->dataRecord instanceof SiteTree && RootURLController::should_be_on_root($this->dataRecord) && (!isset($this->urlParams['Action']) || !$this->urlParams['Action'] ) && !$_POST && !$_FILES && !$this->redirectedTo() ) { $getVars = $_GET; unset($getVars['url']); if($getVars) $url = "?" . http_build_query($getVars); else $url = ""; $this->redirect($url, 301); return; } if($this->dataRecord) $this->dataRecord->extend('contentcontrollerInit', $this); else singleton('SiteTree')->extend('contentcontrollerInit', $this); if($this->redirectedTo()) return; // Check page permissions if($this->dataRecord && $this->URLSegment != 'Security' && !$this->dataRecord->canView()) { return Security::permissionFailure($this); } // Draft/Archive security check - only CMS users should be able to look at stage/archived content if( $this->URLSegment != 'Security' && !Session::get('unsecuredDraftSite') && ( Versioned::current_archived_date() || (Versioned::current_stage() && Versioned::current_stage() != 'Live') ) ) { if(!$this->dataRecord->canView()) { Session::clear('currentStage'); Session::clear('archiveDate'); $permissionMessage = sprintf( _t( "ContentController.DRAFT_SITE_ACCESS_RESTRICTION", 'You must log in with your CMS password in order to view the draft or archived content. '. 'Click here to go back to the published site.' ), Controller::join_links($this->Link(), "?stage=Live") ); return Security::permissionFailure($this, $permissionMessage); } } // Use theme from the site config if(($config = SiteConfig::current_site_config()) && $config->Theme) { Config::inst()->update('SSViewer', 'theme', $config->Theme); } } /** * This acts the same as {@link Controller::handleRequest()}, but if an action cannot be found this will attempt to * fall over to a child controller in order to provide functionality for nested URLs. * * @param SS_HTTPRequest $request * @param DataModel $model * @return SS_HTTPResponse * @throws SS_HTTPResponse_Exception */ public function handleRequest(SS_HTTPRequest $request, DataModel $model = null) { $child = null; $action = $request->param('Action'); $this->setDataModel($model); // If nested URLs are enabled, and there is no action handler for the current request then attempt to pass // control to a child controller. This allows for the creation of chains of controllers which correspond to a // nested URL. if($action && SiteTree::config()->nested_urls && !$this->hasAction($action)) { // See ModelAdController->getNestedController() for similar logic if(class_exists('Translatable')) Translatable::disable_locale_filter(); // look for a page with this URLSegment $child = $this->model->SiteTree->filter(array( 'ParentID' => $this->ID, 'URLSegment' => rawurlencode($action) ))->first(); if(class_exists('Translatable')) Translatable::enable_locale_filter(); } // we found a page with this URLSegment. if($child) { $request->shiftAllParams(); $request->shift(); $response = ModelAsController::controller_for($child)->handleRequest($request, $model); } else { // If a specific locale is requested, and it doesn't match the page found by URLSegment, // look for a translation and redirect (see #5001). Only happens on the last child in // a potentially nested URL chain. if(class_exists('Translatable')) { if($request->getVar('locale') && $this->dataRecord && $this->dataRecord->Locale != $request->getVar('locale')) { $translation = $this->dataRecord->getTranslation($request->getVar('locale')); if($translation) { $response = new SS_HTTPResponse(); $response->redirect($translation->Link(), 301); throw new SS_HTTPResponse_Exception($response); } } } Director::set_current_page($this->data()); try { $response = parent::handleRequest($request, $model); Director::set_current_page(null); } catch(SS_HTTPResponse_Exception $e) { $this->popCurrent(); Director::set_current_page(null); throw $e; } } return $response; } /** * Get the project name * * @return string */ public function project() { global $project; return $project; } /** * Returns the associated database record */ public function data() { return $this->dataRecord; } /*--------------------------------------------------------------------------------*/ /** * Returns a fixed navigation menu of the given level. * @param int $level Menu level to return. * @return ArrayList */ public function getMenu($level = 1) { if($level == 1) { $result = SiteTree::get()->filter(array( "ShowInMenus" => 1, "ParentID" => 0 )); } else { $parent = $this->data(); $stack = array($parent); if($parent) { while($parent = $parent->Parent) { array_unshift($stack, $parent); } } if(isset($stack[$level-2])) $result = $stack[$level-2]->Children(); } $visible = array(); // Remove all entries the can not be viewed by the current user // We might need to create a show in menu permission if(isset($result)) { foreach($result as $page) { if($page->canView()) { $visible[] = $page; } } } return new ArrayList($visible); } public function Menu($level) { return $this->getMenu($level); } /** * Returns the default log-in form. * * @todo Check if here should be returned just the default log-in form or * all available log-in forms (also OpenID...) */ public function LoginForm() { return MemberAuthenticator::get_login_form($this); } public function SilverStripeNavigator() { $member = Member::currentUser(); $items = ''; $message = ''; if(Director::isDev() || Permission::check('CMS_ACCESS_CMSMain') || Permission::check('VIEW_DRAFT_CONTENT')) { if($this->dataRecord) { Requirements::css(CMS_DIR . '/css/SilverStripeNavigator.css'); Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/jquery/jquery.js'); Requirements::javascript(CMS_DIR . '/javascript/SilverStripeNavigator.js'); $return = $nav = SilverStripeNavigator::get_for_record($this->dataRecord); $items = $return['items']; $message = $return['message']; } if($member) { $firstname = Convert::raw2xml($member->FirstName); $surname = Convert::raw2xml($member->Surname); $logInMessage = _t('ContentController.LOGGEDINAS', 'Logged in as') ." {$firstname} {$surname} - ". _t('ContentController.LOGOUT', 'Log out'). ""; } else { $logInMessage = sprintf( '%s - %s' , _t('ContentController.NOTLOGGEDIN', 'Not logged in') , Config::inst()->get('Security', 'login_url'), _t('ContentController.LOGIN', 'Login') ."" ); } $viewPageIn = _t('ContentController.VIEWPAGEIN', 'View Page in:'); return <<
$logInMessage
$viewPageIn $items
$message HTML; // On live sites we should still see the archived message } else { if($date = Versioned::current_archived_date()) { Requirements::css(CMS_DIR . '/css/SilverStripeNavigator.css'); $dateObj = Datetime::create($date, null); // $dateObj->setVal($date); return "
". _t('ContentController.ARCHIVEDSITEFROM') ."
" . $dateObj->Nice() . "
"; } } } public function SiteConfig() { if(method_exists($this->dataRecord, 'getSiteConfig')) { return $this->dataRecord->getSiteConfig(); } else { return SiteConfig::current_site_config(); } } /** * Returns an RFC1766 compliant locale string, e.g. 'fr-CA'. * Inspects the associated {@link dataRecord} for a {@link SiteTree->Locale} value if present, * and falls back to {@link Translatable::get_current_locale()} or {@link i18n::default_locale()}, * depending if Translatable is enabled. * * Suitable for insertion into lang= and xml:lang= * attributes in HTML or XHTML output. * * @return string */ public function ContentLocale() { if($this->dataRecord && $this->dataRecord->hasExtension('Translatable')) { $locale = $this->dataRecord->Locale; } elseif(class_exists('Translatable') && SiteTree::has_extension('Translatable')) { $locale = Translatable::get_current_locale(); } else { $locale = i18n::get_locale(); } return i18n::convert_rfc1766($locale); } /** * Return an SSViewer object to render the template for the current page. * * @param $action string * * @return SSViewer */ public function getViewer($action) { // Manually set templates should be dealt with by Controller::getViewer() if(isset($this->templates[$action]) && $this->templates[$action] || (isset($this->templates['index']) && $this->templates['index']) || $this->template ) { return parent::getViewer($action); } // Prepare action for template search if($action == "index") $action = ""; else $action = '_' . $action; $templates = array_merge( // Find templates by dataRecord SSViewer::get_templates_by_class(get_class($this->dataRecord), $action, "SiteTree"), // Next, we need to add templates for all controllers SSViewer::get_templates_by_class(get_class($this), $action, "Controller"), // Fail-over to the same for the "index" action SSViewer::get_templates_by_class(get_class($this->dataRecord), "", "SiteTree"), SSViewer::get_templates_by_class(get_class($this), "", "Controller") ); return new SSViewer($templates); } /** * This action is called by the installation system */ public function successfullyinstalled() { // Return 410 Gone if this site is not actually a fresh installation if (!file_exists(BASE_PATH . '/install.php')) { $this->httpError(410); } // TODO Allow this to work when allow_url_fopen=0 if(isset($_SESSION['StatsID']) && $_SESSION['StatsID']) { $url = 'http://ss2stat.silverstripe.com/Installation/installed?ID=' . $_SESSION['StatsID']; @file_get_contents($url); } global $project; $data = new ArrayData(array( 'Project' => Convert::raw2xml($project), 'Username' => Convert::raw2xml(Session::get('username')), 'Password' => Convert::raw2xml(Session::get('password')), )); return array( "Title" => _t("ContentController.INSTALL_SUCCESS", "Installation Successful!"), "Content" => $data->renderWith('Install_successfullyinstalled'), ); } public function deleteinstallfiles() { if(!Permission::check("ADMIN")) return Security::permissionFailure($this); $title = new Varchar("Title"); $content = new HTMLText('Content'); // We can't delete index.php as it might be necessary for URL routing without mod_rewrite. // There's no safe way to detect usage of mod_rewrite across webservers, // so we have to assume the file is required. $installfiles = array( 'install.php', 'config-form.css', 'config-form.html', 'index.html' ); $unsuccessful = new ArrayList(); foreach($installfiles as $installfile) { if(file_exists(BASE_PATH . '/' . $installfile)) { @unlink(BASE_PATH . '/' . $installfile); } if(file_exists(BASE_PATH . '/' . $installfile)) { $unsuccessful->push(new ArrayData(array('File' => $installfile))); } } $data = new ArrayData(array( 'Username' => Convert::raw2xml(Session::get('username')), 'Password' => Convert::raw2xml(Session::get('password')), 'UnsuccessfulFiles' => $unsuccessful )); $content->setValue($data->renderWith('Install_deleteinstallfiles')); return array( "Title" => $title, "Content" => $content, ); } }