canView()) { return Security::permissionFailure($this); } if ($this->config()->apply_default_theme) { Requirements::javascript('//use.typekit.net/emt4dhq.js'); Requirements::customScript('try{Typekit.load();}catch(e){}'); Requirements::combine_files( 'docs.js', array( THIRDPARTY_DIR .'/jquery/jquery.js', DOCSVIEWER_DIR .'/thirdparty/highlight/highlight.pack.js', DOCSVIEWER_DIR .'/javascript/DocumentationViewer.js', ) ); Requirements::combine_files( 'docs.css', array( DOCSVIEWER_DIR .'/css/highlight.css', DOCSVIEWER_DIR .'/css/normalize.css', DOCSVIEWER_DIR .'/css/utilities.css', DOCSVIEWER_DIR .'/css/typography.css', DOCSVIEWER_DIR .'/css/forms.css', DOCSVIEWER_DIR .'/css/layout.css', DOCSVIEWER_DIR .'/css/small.css' ) ); } } /** * Permission provider to allow more control over who can view docs * * @return array */ public function providePermissions() { return array( 'CMS_VIEW_DEVDOCS' => array( 'name' => 'View Site Documentation', 'category' => 'Documentation', ) ); } /** * Can the user view this documentation. Hides all functionality for private * wikis. * * @return bool */ public function canView() { return (Director::isDev() || Director::is_cli() || !$this->config()->get('check_permission') || Permission::check($this->config()->get('check_permission')) ); } public function hasAction($action) { return true; } public function checkAccessAction($action) { return true; } /** * Overloaded to avoid "action doesn't exist" errors - all URL parts in * this controller are virtual and handled through handleRequest(), not * controller methods. * * @param $request * @param $action * * @return SS_HTTPResponse */ public function handleAction($request, $action) { // if we submitted a form, let that pass if (!$request->isGET() && !$request->isHEAD()) { return parent::handleAction($request, $action); } $url = $request->getURL(); // // If the current request has an extension attached to it, strip that // off and redirect the user to the page without an extension. // if (DocumentationHelper::get_extension($url)) { $this->response = new SS_HTTPResponse(); $this->response->redirect( Director::absoluteURL(DocumentationHelper::trim_extension_off($url)) .'/', 301 ); $request->shift(); $request->shift(); return $this->response; } // // Strip off the base url // $base = ltrim( Config::inst()->get('DocumentationViewer', 'link_base'), '/' ); if ($base && strpos($url, $base) !== false) { $url = substr( ltrim($url, '/'), strlen($base) ); } // // Handle any permanent redirections that the developer has defined. // if ($link = DocumentationPermalinks::map($url)) { // the first param is a shortcode for a page so redirect the user to // the short code. $this->response = new SS_HTTPResponse(); $this->response->redirect($link, 301); $request->shift(); $request->shift(); return $this->response; } // // Validate the language provided. Language is a required URL parameter. // as we use it for generic interfaces and language selection. If // language is not set, redirects to 'en' // $languages = i18n::get_common_languages(); if (!$lang = $request->param('Lang')) { $lang = $request->param('Action'); $action = $request->param('ID'); } else { $action = $request->param('Action'); } if (!$lang) { return $this->redirect($this->Link('en')); } elseif (!isset($languages[$lang])) { return $this->httpError(404); } $request->shift(10); $allowed = $this->config()->allowed_actions; if (in_array($action, $allowed)) { // // if it's one of the allowed actions such as search or all then the // URL must be prefixed with one of the allowed languages and versions // return parent::handleAction($request, $action); } else { // // look up the manifest to see find the nearest match against the // list of the URL. If the URL exists then set that as the current // page to match against. // strip off any extensions. // if($cleaned !== $url) { // $redirect = new SS_HTTPResponse(); // return $redirect->redirect($cleaned, 302); // } if ($record = $this->getManifest()->getPage($url)) { // In SS 3 offsetSet() isn't implemented for some reason... this is a workaround $routeParams = $this->request->routeParams(); $routeParams['Lang'] = $lang; $this->request->setRouteParams($routeParams); $this->record = $record; $this->init(); $type = get_class($this->record); $body = $this->renderWith( array( "DocumentationViewer_{$type}", 'DocumentationViewer' ) ); return new SS_HTTPResponse($body, 200); } elseif ($redirect = $this->getManifest()->getRedirect($url)) { $response = new SS_HTTPResponse(); $to = Controller::join_links(Director::baseURL(), $base, $redirect); return $response->redirect($to, 301); } elseif (!$url || $url == $lang) { $body = $this->renderWith( array( 'DocumentationViewer_DocumentationFolder', 'DocumentationViewer' ) ); return new SS_HTTPResponse($body, 200); } } return $this->httpError(404); } /** * @param int $status * @param string $message * * @return SS_HTTPResponse */ public function httpError($status, $message = null) { $this->init(); $class = get_class($this); $body = $this->customise( new ArrayData( array( 'Message' => $message ) ) )->renderWith(array("{$class}_error", $class)); return new SS_HTTPResponse($body, $status); } /** * @return DocumentationManifest */ public function getManifest() { if (!$this->manifest) { $flush = SapphireTest::is_running_test() || (isset($_GET['flush'])); $this->manifest = new DocumentationManifest($flush); } return $this->manifest; } /** * @return string */ public function getLanguage() { if (!$lang = $this->request->param('Lang')) { $lang = $this->request->param('Action'); } return $lang; } /** * Generate a list of {@link Documentation } which have been registered and which can * be documented. * * @return DataObject */ public function getMenu() { $entities = $this->getManifest()->getEntities(); $output = new ArrayList(); $record = $this->getPage(); $current = $this->getEntity(); foreach ($entities as $entity) { $checkLang = $entity->getLanguage(); $checkVers = $entity->getVersion(); // only show entities with the same language or any entity that // isn't registered under any particular language (auto detected) if ($checkLang && $checkLang !== $this->getLanguage()) { continue; } if ($current && $checkVers) { if ($entity->getVersion() !== $current->getVersion()) { continue; } } $mode = 'link'; $children = new ArrayList(); if ($entity->hasRecord($record) || $entity->getIsDefaultEntity()) { $mode = 'current'; // add children $children = $this->getManifest()->getChildrenFor( $entity->getPath(), ($record) ? $record->getPath() : $entity->getPath() ); } else { if ($current && $current->getKey() == $entity->getKey()) { continue; } } $link = $entity->Link(); $output->push( new ArrayData( array( 'Title' => $entity->getTitle(), 'Link' => $link, 'LinkingMode' => $mode, 'DefaultEntity' => $entity->getIsDefaultEntity(), 'Children' => $children ) ) ); } return $output; } /** * Return the content for the page. If its an actual documentation page then * display the content from the page, otherwise display the contents from * the index.md file if its a folder * * @return HTMLText */ public function getContent() { $page = $this->getPage(); $html = $page->getHTML(); $html = $this->replaceChildrenCalls($html); return $html; } public function replaceChildrenCalls($html) { $codes = new ShortcodeParser(); $codes->register('CHILDREN', array($this, 'includeChildren')); return $codes->parse($html); } /** * Short code parser */ public function includeChildren($args) { if (isset($args['Folder'])) { $children = $this->getManifest()->getChildrenFor( Controller::join_links(dirname($this->record->getPath()), $args['Folder']) ); } else { $children = $this->getManifest()->getChildrenFor( dirname($this->record->getPath()) ); } if (isset($args['Exclude'])) { $exclude = explode(',', $args['Exclude']); foreach ($children as $k => $child) { foreach ($exclude as $e) { if ($child->Link == Controller::join_links($this->record->Link(), strtolower($e), '/')) { unset($children[$k]); } } } } return $this->customise( new ArrayData( array( 'Children' => $children ) ) )->renderWith('Includes/DocumentationPages'); } /** * @return ArrayList */ public function getChildren() { if ($this->record instanceof DocumentationFolder) { return $this->getManifest()->getChildrenFor( $this->record->getPath() ); } elseif ($this->record) { return $this->getManifest()->getChildrenFor( dirname($this->record->getPath()) ); } return new ArrayList(); } /** * Generate a list of breadcrumbs for the user. * * @return ArrayList */ public function getBreadcrumbs() { if ($this->record) { return $this->getManifest()->generateBreadcrumbs( $this->record, $this->record->getEntity() ); } } /** * @return DocumentationPage */ public function getPage() { return $this->record; } /** * @return DocumentationEntity */ public function getEntity() { return ($this->record) ? $this->record->getEntity() : null; } /** * @return ArrayList */ public function getVersions() { return $this->getManifest()->getVersions($this->getEntity()); } /** * Generate a string for the title tag in the URL. * * @return string */ public function getTitle() { return ($this->record) ? $this->record->getTitle() : null; } /** * @return string */ public function AbsoluteLink($action) { return Controller::join_links( Director::absoluteBaseUrl(), $this->Link($action) ); } /** * Return the base link to this documentation location. * * @return string */ public function Link($action = '') { $link = Controller::join_links( Director::baseURL(), Config::inst()->get('DocumentationViewer', 'link_base'), $this->getLanguage(), $action, '/' ); return $link; } /** * Return the canonical URL from the page * * @return string */ public function getCanonicalUrl() { if (!$this->getPage()) { return ''; } return $this->getPage()->getCanonicalUrl(); } /** * Generate a list of all the pages in the documentation grouped by the * first letter of the page. * * @param string|int|null $version * @return GroupedList */ public function AllPages($version = null) { $pages = $this->getManifest()->getPages(); $output = new ArrayList(); $baseLink = $this->getDocumentationBaseHref(); foreach ($pages as $url => $page) { // Option to skip Pages that do not belong to the current version if (!is_null($version) && (string) $page['version'] !== (string) $version) { continue; } $first = strtoupper(trim(substr($page['title'], 0, 1))); if ($first) { $output->push( new ArrayData( array( 'Link' => Controller::join_links($baseLink, $url), 'Title' => $page['title'], 'FirstLetter' => $first ) ) ); } } return GroupedList::create($output->sort('Title', 'ASC')); } /** * Return all Pages that apply to the current version (from the route) * * @return GroupedList */ public function getAllVersionPages() { return $this->AllPages($this->getRequestedVersion()); } /** * Documentation Search Form. Allows filtering of the results by many entities * and multiple versions. * * @return Form */ public function DocumentationSearchForm() { if (!Config::inst()->get('DocumentationSearch', 'enabled')) { return false; } return new DocumentationSearchForm($this); } /** * Sets the mapping between a entity name and the link for the end user * to jump into editing the documentation. * * Some variables are replaced: * - %version% * - %entity% * - %path% * - %lang% * * For example to provide an edit link to the framework module in github: * * * DocumentationViewer::set_edit_link( * 'framework', * 'https://github.com/silverstripe/%entity%/edit/%version%/docs/%lang%/%path%', * $opts * )); * * * @param string module name * @param string link * @param array options ('rewritetrunktomaster') */ public static function set_edit_link($module, $link, $options = array()) { self::$edit_links[$module] = array( 'url' => $link, 'options' => $options ); } /** * Returns an edit link to the current page (optional). * * @return string|false */ public function getEditLink() { $editLink = false; $entity = null; $page = $this->getPage(); if ($page) { $entity = $page->getEntity(); if ($entity && isset(self::$edit_links[strtolower($entity->title)])) { // build the edit link, using the version defined $url = self::$edit_links[strtolower($entity->title)]; $version = $entity->getVersion(); if ($entity->getBranch()) { $version = $entity->getBranch(); } if ($version === 'trunk' && (isset($url['options']['rewritetrunktomaster']))) { if ($url['options']['rewritetrunktomaster']) { $version = "master"; } } $editLink = str_replace( array('%entity%', '%lang%', '%version%', '%path%'), array( $entity->title, $this->getLanguage(), $version, ltrim($page->getRelativePath(), '/') ), $url['url'] ); } } $this->extend('updateDocumentationEditLink', $editLink, $entity); return $editLink; } /** * Returns the next page. Either retrieves the sibling of the current page * or return the next sibling of the parent page. * * @return DocumentationPage|null */ public function getNextPage() { return ($this->record) ? $this->getManifest()->getNextPage( $this->record->getPath(), $this->getEntity()->getPath() ) : null; } /** * Returns the previous page. Either returns the previous sibling or the * parent of this page * * @return DocumentationPage|null */ public function getPreviousPage() { return ($this->record) ? $this->getManifest()->getPreviousPage( $this->record->getPath(), $this->getEntity()->getPath() ) : null; } /** * @return string|void */ public function getGoogleAnalyticsCode() { $code = $this->config()->get('google_analytics_code'); if ($code) { return $code; } } /** * @return string */ public function getDocumentationTitle() { return $this->config()->get('documentation_title'); } /** * @return string */ public function getDocumentationBaseHref() { return Controller::join_links( Director::baseURL(), Config::inst()->get('DocumentationViewer', 'link_base') ); } /** * Gets whether there is a default entity or not * * @return boolean * @see DocumentationManifest::getHasDefaultEntity() */ public function getHasDefaultEntity() { return $this->getManifest()->getHasDefaultEntity(); } /** * Gets the requested version from the URL * * @return string */ public function getRequestedVersion() { return (string) $this->request->param('Version'); } /** * Gets the link to the "documentation index" containing the currently requested version * * @return string */ public function getDocumentationIndexLink() { return $this->Link($this->getRequestedVersion() . '/all'); } }