Fixes for unit tests with the latest manifest

This commit is contained in:
Will Rossiter 2014-09-15 21:47:45 +12:00
parent 1e04aaed28
commit b489d5120a
38 changed files with 1700 additions and 1555 deletions

View File

@ -82,7 +82,7 @@ class DocumentationHelper {
* @return string
*/
public static function get_extension($name) {
if(strrpos($name,'.') !== false) {
if(preg_match('/\.[a-z]+$/', $name)) {
return substr($name, strrpos($name,'.') + 1);
}

View File

@ -43,13 +43,20 @@ class DocumentationManifest {
*/
private static $register_entities = array();
protected $base;
protected $cache;
protected $cacheKey;
protected $inited;
protected $forceRegen;
/**
* @var array $pages
*/
protected $pages = array();
/**
* @var DocumentationEntity
*/
private $entity;
/**
@ -65,7 +72,6 @@ class DocumentationManifest {
* @param bool $forceRegen Force the manifest to be regenerated.
*/
public function __construct($forceRegen = false) {
$this->setupEntities();
$this->cacheKey = 'manifest';
$this->forceRegen = $forceRegen;
@ -73,6 +79,8 @@ class DocumentationManifest {
'automatic_serialization' => true,
'lifetime' => null
));
$this->setupEntities();
}
/**
@ -90,7 +98,7 @@ class DocumentationManifest {
foreach($registered as $details) {
// validate the details provided through the YAML configuration
$required = array('Path', 'Version', 'Title');
$required = array('Path', 'Title');
foreach($required as $require) {
if(!isset($details[$require])) {
@ -98,32 +106,57 @@ class DocumentationManifest {
}
}
if(isset($this->registeredEntities[$details['Title']])) {
$entity = $this->registeredEntities[$details['Title']];
} else {
$entity = new DocumentationEntity(
$details['Path'],
$details['Title']
);
// if path is not an absolute value then assume it is relative from
// the BASE_PATH.
$path = $this->getRealPath($details['Path']);
$this->registeredEntities[$details['Title']] = $entity;
$key = (isset($details['Key'])) ? $details['Key'] : $details['Title'];
if(!is_dir($path)) {
throw new Exception($path . ' is not a valid documentation directory');
}
$version = new DocumentationEntityVersion(
$entity,
Controller::join_links(BASE_PATH, $details['Path']),
$details['Version'],
(isset($details['Stable'])) ? $details['Stable'] : false
);
$version = (isset($details['Version'])) ? $details['Version'] : '';
$entity->addVersion($version);
$langs = scandir($path);
if($langs) {
$possible = i18n::get_common_languages(true);
foreach($langs as $k => $lang) {
if(isset($possible[$lang])) {
$entity = Injector::inst()->create(
'DocumentationEntity', $key
);
if(isset($details['DefaultEntity']) && $details['DefaultEntity']) {
$entity->setDefaultEntity(true);
$entity->setPath(Controller::join_links($path, $lang, '/'));
$entity->setTitle($details['Title']);
$entity->setLanguage($lang);
$entity->setVersion($version);
if(isset($details['Stable'])) {
$entity->setIsStable($details['Stable']);
}
if(isset($details['DefaultEntity'])) {
$entity->setIsDefaultEntity($details['DefaultEntity']);
}
$this->registeredEntities[] = $entity;
}
}
}
}
}
public function getRealPath($path) {
if(substr($path, 0, 1) != '/') {
$path = realpath(Controller::join_links(BASE_PATH, $path));
}
return $path;
}
/**
* @return array
*/
@ -200,7 +233,7 @@ class DocumentationManifest {
*/
public function getPage($url) {
$pages = $this->getPages();
$url = rtrim($url, '/') . '/';
$url = $this->normalizeUrl($url);
if(!isset($pages[$url])) {
return null;
@ -210,19 +243,15 @@ class DocumentationManifest {
$record = $pages[$url];
foreach($this->getEntities() as $entity) {
foreach($entity->getVersions() as $version) {
foreach($version->getSupportedLanguages() as $language) {
if(strpos($record['filepath'], $language->getPath()) !== false) {
$page = Injector::inst()->create(
$record['type'],
$language,
$record['basename'],
$record['filepath']
);
if(strpos($record['filepath'], $entity->getPath()) !== false) {
$page = Injector::inst()->create(
$record['type'],
$entity,
$record['basename'],
$record['filepath']
);
return $page;
}
}
return $page;
}
}
}
@ -240,15 +269,10 @@ class DocumentationManifest {
));
foreach($this->getEntities() as $entity) {
foreach($entity->getVersions() as $version) {
foreach($version->getSupportedLanguages() as $k => $v) {
$this->entity = $v;
$this->handleFolder('', $this->entity->getPath(), 0);
$finder->find($this->entity->getPath());
}
}
$this->entity = $entity;
$this->handleFolder('', $this->entity->getPath(), 0);
$finder->find($this->entity->getPath());
}
if ($cache) {
@ -266,7 +290,13 @@ class DocumentationManifest {
'DocumentationFolder', $this->entity, $basename, $path
);
$this->pages[$folder->Link()] = array(
$link = ltrim(str_replace(
Config::inst()->get('DocumentationViewer', 'link_base'),
'',
$folder->Link()
), '/');
$this->pages[$link] = array(
'title' => $folder->getTitle(),
'basename' => $basename,
'filepath' => $path,
@ -295,7 +325,13 @@ class DocumentationManifest {
// populate any meta data
$page->getMarkdown();
$this->pages[$page->Link()] = array(
$link = ltrim(str_replace(
Config::inst()->get('DocumentationViewer', 'link_base'),
'',
$page->Link()
), '/');
$this->pages[$link] = array(
'title' => $page->getTitle(),
'filepath' => $path,
'basename' => $basename,
@ -315,7 +351,13 @@ class DocumentationManifest {
public function generateBreadcrumbs($record, $base) {
$output = new ArrayList();
$parts = explode('/', $record->getRelativeLink());
$parts = explode('/', trim($record->getRelativeLink(), '/'));
// the first part of the URL should be the language, so shift that off
// so we just have the core pages.
array_shift($parts);
// Add the base link.
$output->push(new ArrayData(array(
'Link' => $base->Link(),
'Title' => $base->Title
@ -396,6 +438,15 @@ class DocumentationManifest {
return null;
}
/**
* @param string
*
* @return string
*/
public function normalizeUrl($url) {
return trim($url, '/') .'/';
}
/**
* Return the children of the provided record path.
*
@ -405,39 +456,112 @@ class DocumentationManifest {
*
* @return ArrayList
*/
public function getChildrenFor($base, $record, $recursive = true) {
public function getChildrenFor($path, $recursive = true) {
$output = new ArrayList();
$depth = substr_count($base, '/');
$base = Config::inst()->get('DocumentationViewer', 'link_base');
$path = $this->normalizeUrl($path);
$depth = substr_count($path, '/');
foreach($this->getPages() as $url => $page) {
if(strstr($url, $base) !== false) {
if(substr_count($url, '/') == ($depth + 1)) {
// found a child
if($base !== $record) {
$mode = (strstr($url, $record) !== false) ? 'current' : 'link';
} else {
$mode = 'link';
}
$pagePath = $this->normalizeUrl($page['filepath']);
$children = new ArrayList();
// check to see if this page is under the given path
if(strpos($pagePath, $path) === false) {
continue;
}
if($mode == 'current') {
if($recursive) {
$children = $this->getChildrenFor($url, $url, false);
}
}
// if the page is the index page then hide it from the menu
if(strpos(strtolower($pagePath), '/index.md/')) {
continue;
}
$output->push(new ArrayData(array(
'Link' => $url,
'Title' => $page['title'],
'LinkingMode' => $mode,
'Children' => $children
)));
// only pull it up if it's one more level depth
if(substr_count($pagePath, DIRECTORY_SEPARATOR) == ($depth + 1)) {
// found a child
$mode = ($pagePath == $path) ? 'current' : 'link';
$children = new ArrayList();
if($mode == 'current' && $recursive) {
// $children = $this->getChildrenFor($url, false);
}
$output->push(new ArrayData(array(
'Link' => Controller::join_links($base, $url, '/'),
'Title' => $page['title'],
'LinkingMode' => $mode,
'Children' => $children
)));
}
}
return $output;
}
/**
* @param DocumentationEntity
*
* @return ArrayList
*/
public function getAllVersions(DocumentationEntity $entity) {
$all = new ArrayList();
foreach($this->getEntities() as $check) {
if($check->getKey() == $entity->getKey()) {
if($check->getLanguage() == $entity->getLanguage()) {
$all->push($check);
}
}
}
return $all;
}
/**
* @param DocumentationEntity
*
* @return DocumentationEntity
*/
public function getStableVersion(DocumentationEntity $entity) {
foreach($this->getEntities() as $check) {
if($check->getKey() == $entity->getKey()) {
if($check->getLanguage() == $entity->getLanguage()) {
if($check->getIsStable()) {
return $check;
}
}
}
}
return $entity;
}
/**
* @param DocumentationEntity
*
* @return ArrayList
*/
public function getVersions($entity) {
if(!$entity) {
return null;
}
$output = new ArrayList();
foreach($this->getEntities() as $check) {
if($check->getKey() == $entity->getKey()) {
if($check->getLanguage() == $entity->getLanguage()) {
$same = ($check->getVersion() == $entity->getVersion());
$output->push(new ArrayList(array(
'Title' => $entity->getTitle(),
'Link' => $entity->getLink(),
'LinkingMode' => ($same) ? 'current' : 'link'
)));
}
}
}
return $output;
}
}

View File

@ -42,7 +42,9 @@ class DocumentationParser {
* @return String
*/
public static function parse(DocumentationPage $page, $baselink = null) {
if(!$page || (!$page instanceof DocumentationPage)) return false;
if(!$page || (!$page instanceof DocumentationPage)) {
return false;
}
$md = $page->getMarkdown(true);
@ -55,6 +57,7 @@ class DocumentationParser {
$md = self::rewrite_code_blocks($md);
$parser = new ParsedownExtra();
return $parser->text($md);
}
@ -179,36 +182,55 @@ class DocumentationParser {
\)
/x';
preg_match_all($re, $md, $images);
if($images) foreach($images[0] as $i => $match) {
$title = $images[1][$i];
$url = $images[2][$i];
// Don't process absolute links (based on protocol detection)
$urlParts = parse_url($url);
if($urlParts && isset($urlParts['scheme'])) continue;
// Rewrite URL (relative or absolute)
$baselink = Director::makeRelative(dirname($page->getPath(false, false)));
$relativeUrl = rtrim($baselink, '/') . '/' . ltrim($url, '/');
// Resolve relative paths
while(strpos($relativeUrl, '/..') !== FALSE) {
$relativeUrl = preg_replace('/\w+\/\.\.\//', '', $relativeUrl);
if($images) {
foreach($images[0] as $i => $match) {
$title = $images[1][$i];
$url = $images[2][$i];
// Don't process absolute links (based on protocol detection)
$urlParts = parse_url($url);
if($urlParts && isset($urlParts['scheme'])) {
continue;
}
// Rewrite URL (relative or absolute)
$baselink = Director::makeRelative(
dirname($page->getPath())
);
// if the image starts with a slash, it's absolute
if(substr($url, 0, 1) == '/') {
$relativeUrl = str_replace(BASE_PATH, '', Controller::join_links(
$page->getEntity()->getPath(),
$url
));
} else {
$relativeUrl = rtrim($baselink, '/') . '/' . ltrim($url, '/');
}
// Resolve relative paths
while(strpos($relativeUrl, '/..') !== FALSE) {
$relativeUrl = preg_replace('/\w+\/\.\.\//', '', $relativeUrl);
}
// Make it absolute again
$absoluteUrl = Controller::join_links(
Director::absoluteBaseURL(),
$relativeUrl
);
// Replace any double slashes (apart from protocol)
// $absoluteUrl = preg_replace('/([^:])\/{2,}/', '$1/', $absoluteUrl);
// Replace in original content
$md = str_replace(
$match,
sprintf('![%s](%s)', $title, $absoluteUrl),
$md
);
}
// Replace any double slashes (apart from protocol)
$relativeUrl = preg_replace('/([^:])\/{2,}/', '$1/', $relativeUrl);
// Make it absolute again
$absoluteUrl = Director::absoluteBaseURL() . $relativeUrl;
// Replace in original content
$md = str_replace(
$match,
sprintf('![%s](%s)', $title, $absoluteUrl),
$md
);
}
return $md;
@ -244,7 +266,14 @@ class DocumentationParser {
foreach($linksWithTitles[0] as $i => $match) {
$title = $linksWithTitles[1][$i];
$subject = $linksWithTitles[2][$i];
$url = sprintf(self::$api_link_base, $subject, $page->getVersion(), $page->getEntity()->getFolder());
$url = sprintf(
self::$api_link_base,
$subject,
$page->getVersion(),
$page->getEntity()->getKey()
);
$md = str_replace(
$match,
sprintf('<code>[%s](%s)</code>', $title, $url),
@ -265,7 +294,13 @@ class DocumentationParser {
if($links) {
foreach($links[0] as $i => $match) {
$subject = $links[1][$i];
$url = sprintf(self::$api_link_base, $subject, $page->getVersion(), $page->getEntity()->getFolder());
$url = sprintf(
self::$api_link_base,
$subject,
$page->getVersion(),
$page->getEntity()->getKey()
);
$md = str_replace(
$match,
sprintf('<code>[%s](%s)</code>', $subject, $url),
@ -329,12 +364,12 @@ class DocumentationParser {
*
* @param String $md Markdown content
* @param DocumentationPage $page
* @param String $baselink
*
* @return String Markdown
*/
public static function rewrite_relative_links($md, $page, $baselink = null) {
if(!$baselink) $baselink = $page->getEntity()->getRelativeLink();
public static function rewrite_relative_links($md, $page) {
$baselink = $page->getEntity()->Link();
$re = '/
([^\!]?) # exclude image format
\[
@ -348,8 +383,11 @@ class DocumentationParser {
// relative path (relative to module base folder), without the filename.
// For "sapphire/en/current/topics/templates", this would be "templates"
$relativePath = dirname($page->Link());
if($relativePath == '.') $relativePath = '';
$relativePath = dirname($page->getRelativeLink());
if($relativePath == '.') {
$relativePath = '';
}
// file base link
$fileBaseLink = Director::makeRelative(dirname($page->getPath()));
@ -392,7 +430,7 @@ class DocumentationParser {
// Replace any double slashes (apart from protocol)
$relativeUrl = preg_replace('/([^:])\/{2,}/', '$1/', $relativeUrl);
// Replace in original content
$md = str_replace(
$match,

View File

@ -1,5 +1,12 @@
<?php
set_include_path(
dirname(dirname(__FILE__)) . '/thirdparty/'. PATH_SEPARATOR .
get_include_path()
);
require_once 'Zend/Search/Lucene.php';
/**
* Documentation Search powered by Lucene. You will need Zend_Lucene installed
* on your path.
@ -112,82 +119,31 @@ class DocumentationSearch {
}
/**
* Folder name for indexes (in the temp folder). You can override it using
* {@link DocumentationSearch::set_index_location($)}
* Folder name for indexes (in the temp folder).
*
* @config
* @var string
*/
private static $index_location;
/**
* Enable searching documentation
*/
public static function enable($enabled = true) {
self::$enabled = $enabled;
if($enabled) {
// include the zend search functionality
set_include_path(
dirname(dirname(__FILE__)) . '/thirdparty/'. PATH_SEPARATOR .
get_include_path()
);
require_once 'Zend/Search/Lucene.php';
}
}
/**
* @return bool
*/
public static function enabled() {
return self::$enabled;
}
/**
* Enable advanced documentation search
*/
public static function enable_advanced_search($enabled = true) {
self::$advanced_search_enabled = ($enabled)? true: false;
}
/**
* @return bool
*/
public static function advanced_search_enabled() {
return self::$advanced_search_enabled;
}
/**
* @param string
*/
public static function set_index($index) {
self::$index_location = $index;
}
/**
* @return string
*/
public static function get_index_location() {
if(!self::$index_location) {
self::$index_location = DOCSVIEWER_DIR;
}
if(file_exists(self::$index_location)) {
return self::$index_location;
} else {
return Controller::join_links(
TEMP_FOLDER,
trim(self::$index_location, '/')
);
$location = Config::inst()->get('DocumentationSearch', 'index_location');
if(!$location) {
return Controller::join_links(TEMP_FOLDER, 'RebuildLuceneDocsIndex');
}
return $location;
}
/**
* Perform a search query on the index
*/
public function performSearch() {
try {
$index = Zend_Search_Lucene::open(self::get_index_location());
@ -252,25 +208,32 @@ class DocumentationSearch {
);
$start = ($request->requestVar('start')) ? (int)$request->requestVar('start') : 0;
$query = ($request->requestVar('Search')) ? $request->requestVar('Search') : '';
$query = ($request->requestVar('q')) ? $request->requestVar('q') : '';
$currentPage = floor( $start / $pageLength ) + 1;
$totalPages = ceil(count($this->results) / $pageLength );
if ($totalPages == 0) $totalPages = 1;
if ($currentPage > $totalPages) $currentPage = $totalPages;
if ($totalPages == 0) {
$totalPages = 1;
}
if ($currentPage > $totalPages) {
$currentPage = $totalPages;
}
$results = new ArrayList();
if($this->results) {
foreach($this->results as $k => $hit) {
if($k < ($currentPage-1)*$pageLength || $k >= ($currentPage*$pageLength)) continue;
if($k < ($currentPage-1)*$pageLength || $k >= ($currentPage*$pageLength)) {
continue;
}
$doc = $hit->getDocument();
$content = $hit->content;
$obj = new ArrayData(array(
'Title' => DBField::create_field('Varchar', $doc->getFieldValue('Title')),
'BreadcrumbTitle' => DBField::create_field('HTMLText', $doc->getFieldValue('BreadcrumbTitle')),
@ -414,23 +377,29 @@ class DocumentationSearch {
}
/**
* Renders the search results into a template. Either
* the search results template or the Atom feed
* Renders the search results into a template. Either the search results
* template or the Atom feed.
*/
public function renderResults() {
if(!$this->results && $this->query) $this->performSearch();
if(!$this->outputController) return user_error('Call renderResults() on a DocumentationViewer instance.', E_USER_ERROR);
if(!$this->results && $this->query) {
$this->performSearch();
}
if(!$this->outputController) {
return user_error('Call renderResults() on a DocumentationViewer instance.', E_USER_ERROR);
}
$request = $this->outputController->getRequest();
$data = $this->getSearchResults($request);
$templates = array('DocumentationViewer_results', 'DocumentationViewer');
$templates = array('DocumentationViewer_search');
if($request->requestVar('format') && $request->requestVar('format') == "atom") {
// alter the fields for the opensearch xml.
$title = ($title = $this->getTitle()) ? ' - '. $title : "";
$link = Controller::join_links($this->outputController->Link(), 'DocumentationOpenSearchController/description/');
$link = Controller::join_links(
$this->outputController->Link(), 'DocumentationOpenSearchController/description/'
);
$data->setField('Title', $data->Title . $title);
$data->setField('DescriptionURL', $link);

View File

@ -20,8 +20,13 @@ class DocumentationOpenSearchController extends Controller {
public function description() {
$viewer = new DocumentationViewer();
if(!$viewer->canView()) return Security::permissionFailure($this);
if(!DocumentationSearch::enabled()) return $this->httpError('404');
if(!$viewer->canView()) {
return Security::permissionFailure($this);
}
if(!Config::inst()->get('DocumentationSearch', 'enabled')) {
return $this->httpError('404');
}
$data = DocumentationSearch::get_meta_data();
$link = Director::absoluteBaseUrl() .

View File

@ -18,22 +18,10 @@ class DocumentationViewer extends Controller {
* @var array
*/
private static $extensions = array(
'DocumentationViewerVersionWarning'
'DocumentationViewerVersionWarning',
'DocumentationSearchExtension'
);
/**
* @var array
*/
private static $allowed_actions = array(
'home',
'all',
'LanguageForm',
'doLanguageForm',
'handleRequest',
'DocumentationSearchForm',
'results'
);
/**
* @var string
*/
@ -44,6 +32,22 @@ class DocumentationViewer extends Controller {
*/
private static $documentation_title = 'SilverStripe Documentation';
/**
* @var array
*/
private static $url_handlers = array(
'$Lang/$Action' => 'handleAction'
);
/**
* @var array
*/
private static $allowed_actions = array(
'all',
'results',
'handleAction'
);
/**
* The string name of the currently accessed {@link DocumentationEntity}
* object. To access the entire object use {@link getEntity()}
@ -77,13 +81,6 @@ class DocumentationViewer extends Controller {
*/
private static $edit_links = array();
/**
* @var array
*/
private static $url_handlers = array(
'$Action' => 'handleAction'
);
/**
*
*/
@ -134,6 +131,14 @@ class DocumentationViewer extends Controller {
);
}
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
@ -145,48 +150,103 @@ class DocumentationViewer extends Controller {
* @return SS_HTTPResponse
*/
public function handleAction($request, $action) {
$action = $request->param('Action');
// if we submitted a form, let that pass
if(!$request->isGET()) {
return parent::handleAction($request, $action);
}
try {
if(preg_match('/DocumentationSearchForm/', $request->getURL())) {
$action = 'results';
}
$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(
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)
);
} else {
$response = parent::handleAction($request, $action);
} catch(SS_HTTPResponse_Exception $e) {
if(strpos($e->getMessage(), 'does not exist') !== FALSE) {
return $this;
} else {
throw $e;
}
}
return $response;
}
/**
* Handle the url parsing for the documentation. In order to make this
* user friendly this does some tricky things..
*
* @return SS_HTTPResponse
*/
public function handleRequest(SS_HTTPRequest $request, DataModel $model) {
$response = parent::handleRequest($request, $model);
//
// 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();
// if we submitted a form, let that pass
if(!$request->isGET() || isset($_GET['action_results'])) {
return $response;
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(!$request->param('Lang')) {
return $this->redirect($this->Link('en'));
} else if(!isset($languages[$request->param('Lang')])) {
return $this->httpError(404);
}
$action = $request->param('Action');
$allowed = $this->config()->allowed_actions;
if(!in_array($action, $allowed) || $response->getStatusCode() !== 200) {
$request->shift();
$request->shift();
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.
//
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.
if($record = $this->getManifest()->getPage($this->request->getURL())) {
// strip off any extensions.
// if($cleaned !== $url) {
// $redirect = new SS_HTTPResponse();
// return $redirect->redirect($cleaned, 302);
// }
if($record = $this->getManifest()->getPage($url)) {
$this->record = $record;
$this->init();
$type = get_class($this->record);
$body = $this->renderWith(array(
@ -195,110 +255,96 @@ class DocumentationViewer extends Controller {
));
return new SS_HTTPResponse($body, 200);
}
else {
$this->init();
$class = get_class($this);
$body = $this->renderWith(array("{$class}_error", $class));
} else if(!$url || $url == $request->param('Lang')) {
$body = $this->renderWith(array(
"DocumentationViewer_DocumentationFolder",
"DocumentationViewer"
));
return new SS_HTTPResponse($body, 404);
return new SS_HTTPResponse($body, 200);
}
}
return $response;
return $this->httpError(404);
}
/**
* Returns the current version. If no version is set then it is the current
* set version so need to pull that from the {@link Entity}.
* @param int $status
* @param string $message
*
* @return string
* @return SS_HTTPResponse
*/
public function getVersion() {
return ($this->record) ? $this->record->getEntity()->getVersion() : null;
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);
}
/**
* Returns the current language.
*
* @return DocumentationEntityLanguage
*/
public function getLanguage() {
return ($this->record) ? $this->record->getEntity() : null;
}
/**
* @return DocumentationManifest
*/
public function getManifest() {
return new DocumentationManifest((isset($_GET['flush'])));
$flush = SapphireTest::is_running_test() || (isset($_GET['flush']));
return new DocumentationManifest($flush);
}
/**
* Return all the available languages for the {@link Entity}.
*
* @return array
* @return string
*/
public function getLanguages() {
return ($this->record) ? $this->record->getEntity()->getSupportedLanguages() : null;
public function getLanguage() {
return $this->request->param('Lang');
}
/**
* Get all the versions loaded for the current {@link DocumentationEntity}.
* the file system then they are loaded under the 'Current' name space.
*
* @param String $entity name of {@link Entity} to limit it to eg sapphire
* @return ArrayList
*/
public function getVersions() {
return ($this->record) ? $this->record->getEntity()->getVersions() : null;
}
/**
* @return DocumentationEntityVersion
*/
public function getStableVersion() {
return ($this->record) ? $this->record->getEntity()->getStableVersion() : null;
}
/**
* Generate a list of entities which have been registered and which can
* Generate a list of {@link Documentation } which have been registered and which can
* be documented.
*
* @return DataObject
*/
public function getEntities() {
public function getMenu() {
$entities = $this->getManifest()->getEntities();
$output = new ArrayList();
$record = $this->getPage();
$current = $this->getEntity();
if($entities) {
foreach($entities as $entity) {
$mode = 'link';
$children = new ArrayList();
if($this->record) {
if($entity->hasRecord($this->record)) {
$mode = 'current';
// add children
$children = $this->getManifest()->getChildrenFor(
$this->getLanguage()->Link(),
$this->record->Link()
);
}
}
$link = $entity->Link();
$output->push(new ArrayData(array(
'Title' => $entity->getTitle(),
'Link' => $link,
'LinkingMode' => $mode,
'DefaultEntity' => $entity,
'Children' => $children
)));
foreach($entities as $entity) {
// only show entities with the same language
if($entity->getLanguage() !== $this->getLanguage()) {
continue;
}
$mode = 'link';
$children = new ArrayList();
if($entity->hasRecord($record) || $entity->getIsDefaultEntity()) {
$mode = 'current';
// add children
$children = $this->getManifest()->getChildrenFor(
$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;
@ -337,6 +383,18 @@ class DocumentationViewer extends Controller {
public function getPage() {
return $this->record;
}
/**
* @return DocumentationEntity
*/
public function getEntity() {
return ($this->record) ? $this->record->getEntity() : null;
}
public function getVersions() {
return $this->manifest->getVersions($this->getEntity);
}
/**
* Generate a string for the title tag in the URL.
*
@ -346,6 +404,16 @@ class DocumentationViewer extends Controller {
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.
*
@ -353,65 +421,40 @@ class DocumentationViewer extends Controller {
*/
public function Link($action = '') {
$link = Controller::join_links(
Director::absoluteBaseURL(),
Config::inst()->get('DocumentationViewer', 'link_base'),
$action
$this->getLanguage(),
$action,
'/'
);
return $link;
}
/**
* Generate a list of all the pages in the documentation grouped by the
* first letter of the page.
*
* @return GroupedList
*/
public function AllPages() {
$pages = $this->getManifest()->getPages();
$output = new ArrayList();
foreach($pages as $url => $page) {
$output->push(new ArrayData(array(
'Link' => $url,
'Title' => $page['title'],
'FirstLetter' => strtoupper(substr($page['title'], 0, 1))
)));
$first = strtoupper(trim(substr($page['title'], 0, 1)));
if($first) {
$output->push(new ArrayData(array(
'Link' => $url,
'Title' => $page['title'],
'FirstLetter' => $first
)));
}
}
return GroupedList::create($output->sort('Title', 'ASC'));
}
/**
* Build the language dropdown.
*
* @todo do this on a page by page rather than global
*
* @return Form
*/
public function LanguageForm() {
$langs = $this->getLanguages();
$fields = new FieldList(
$dropdown = new DropdownField(
'LangCode',
_t('DocumentationViewer.LANGUAGE', 'Language'),
$langs,
$this->Lang
)
);
$actions = new FieldList(
new FormAction('doLanguageForm', _t('DocumentationViewer.CHANGE', 'Change'))
);
return new Form($this, 'LanguageForm', $fields, $actions);
}
/**
* Process the language change
*
*/
public function doLanguageForm($data, $form) {
$this->Lang = (isset($data['LangCode'])) ? $data['LangCode'] : 'en';
return $this->redirect($this->Link());
}
/**
* Documentation Search Form. Allows filtering of the results by many entities
* and multiple versions.
@ -419,7 +462,7 @@ class DocumentationViewer extends Controller {
* @return Form
*/
public function DocumentationSearchForm() {
if(!DocumentationSearch::enabled()) {
if(!Config::inst()->get('DocumentationSearch','enabled')) {
return false;
}
@ -497,6 +540,7 @@ class DocumentationViewer extends Controller {
}
/**
* Returns the next page. Either retrieves the sibling of the current page
* or return the next sibling of the parent page.
@ -504,7 +548,9 @@ class DocumentationViewer extends Controller {
* @return DocumentationPage
*/
public function getNextPage() {
return ($this->record) ? $this->getManifest()->getNextPage($this->record->getPath()) : null;
return ($this->record)
? $this->getManifest()->getNextPage($this->record->getPath())
: null;
}
/**
@ -514,14 +560,16 @@ class DocumentationViewer extends Controller {
* @return DocumentationPage
*/
public function getPreviousPage() {
return ($this->record) ? $this->getManifest()->getPreviousPage($this->record->getPath()) : null;
return ($this->record)
? $this->getManifest()->getPreviousPage($this->record->getPath())
: null;
}
/**
* @return string
*/
public function getGoogleAnalyticsCode() {
$code = Config::inst()->get('DocumentationViewer', 'google_analytics_code');
$code = $this->config()->get('google_analytics_code');
if($code) {
return $code;
@ -532,6 +580,6 @@ class DocumentationViewer extends Controller {
* @return string
*/
public function getDocumentationTitle() {
return Config::inst()->get('DocumentationViewer', 'documentation_title');
return $this->config()->get('documentation_title');
}
}

View File

@ -1,6 +1,6 @@
<?php
class DocumentationSearchController extends DocumentationViewer {
class DocumentationSearchExtension extends Extension {
/**
* Return an array of folders and titles
@ -19,9 +19,6 @@ class DocumentationSearchController extends DocumentationViewer {
$entities = array_combine($entities, $entities);
}
}
else if($entity = $this->getEntity()) {
$entities[$entity->getFolder()] = Convert::raw2att($entity->getTitle());
}
return $entities;
}
@ -44,36 +41,34 @@ class DocumentationSearchController extends DocumentationViewer {
$versions[$version] = $version;
}
}
else if($version = $this->getVersion()) {
$version = Convert::raw2att($version);
$versions[$version] = $version;
}
return $versions;
}
/**
* Return the current search query
* Return the current search query.
*
* @return HTMLText|null
*/
public function getSearchQuery() {
if(isset($_REQUEST['Search'])) {
return DBField::create_field('HTMLText', $_REQUEST['Search']);
} else if(isset($_REQUEST['q'])) {
return DBField::create_field('HTMLText', $_REQUEST['q']);
}
}
/**
* Past straight to results, display and encode the query
* Past straight to results, display and encode the query.
*/
public function results($data, $form = false) {
$query = (isset($_REQUEST['Search'])) ? $_REQUEST['Search'] : false;
public function getSearchResults() {
$query = $this->getSearchQuery();
$search = new DocumentationSearch();
$search->setQuery($query);
$search->setVersions($this->getSearchedVersions());
$search->setModules($this->getSearchedEntities());
$search->setOutputController($this);
$search->setOutputController($this->owner);
return $search->renderResults();
}
@ -82,26 +77,17 @@ class DocumentationSearchController extends DocumentationViewer {
* Returns an search form which allows people to express more complex rules
* and options than the plain search form.
*
* @todo client side filtering of checkable option based on the module selected.
*
* @return Form
*/
public function AdvancedSearchForm() {
return new DocumentationAdvancedSearchForm($this);
return new DocumentationAdvancedSearchForm($this->owner);
}
/**
* Check if the Advanced SearchForm can be displayed. It is enabled by
* default, to disable use:
*
* <code>
* DocumentationSearch::enable_advanced_search(false);
* </code>
*
* @return bool
*/
public function getAdvancedSearchEnabled() {
return DocumentationSearch::advanced_search_enabled();
return Config::inst()->get("DocumentationSearch", 'advanced_search_enabled');
}
}

View File

@ -1,8 +1,8 @@
<?php
/**
* Check to see if the currently accessed version is out of date or
* perhaps a future version rather than the stable edition
* Check to see if the currently accessed version is out of date or perhaps a
* future version rather than the stable edition.
*
* @return false|ArrayData
*/
@ -11,33 +11,39 @@ class DocumentationViewerVersionWarning extends Extension {
public function VersionWarning() {
$page = $this->owner->getPage();
$version = $this->owner->getVersion();
$versions = $this->owner->getVersions();
if($version && $page && $versions->count() > 0) {
$stable = $this->owner->getStableVersion();
$compare = $version->compare($stable);
// same
if($version == $stable) {
return false;
}
if($version == "master" || $compare > 0) {
return $this->customise(new ArrayData(array(
'FutureRelease' => true,
'StableVersion' => DBField::create_field('HTMLText', $stable)
)));
}
else {
return $this->customise(new ArrayData(array(
'OutdatedRelease' => true,
'StableVersion' => DBField::create_field('HTMLText', $stable)
)));
}
if(!$page) {
return false;
}
$entity = $page->getEntity();
if(!$entity) {
return false;
}
$versions = $this->owner->getManifest()->getAllVersions($entity);
if($entity->getIsStable()) {
return false;
}
$stable = $this->owner->getManifest()->getStableVersion($entity);
$compare = $entity->compare($stable);
if($entity->getVersion() == "master" || $compare > 0) {
return $this->owner->customise(new ArrayData(array(
'FutureRelease' => true,
'StableVersion' => DBField::create_field('HTMLText', $stable)
)));
}
else {
return $this->owner->customise(new ArrayData(array(
'OutdatedRelease' => true,
'StableVersion' => DBField::create_field('HTMLText', $stable)
)));
}
return false;
}
}

View File

@ -6,19 +6,25 @@
class DocumentationAdvancedSearchForm extends Form {
public function __construct($controller) {
$entities = $controller->getEntities();
$entities = $controller->getManifest()->getEntities();
$versions = array();
foreach($entities as $entity) {
$versions[$entity->getFolder()] = $entity->getVersions();
foreach($entity->getVersions() as $version) {
$versions[$version->getVersion()] = $version->getVersion();
}
}
// get a list of all the unique versions
$uniqueVersions = array_unique(ArrayLib::flatten(array_values($versions)));
$uniqueVersions = array_unique(
ArrayLib::flatten(array_values($versions))
);
asort($uniqueVersions);
$uniqueVersions = array_combine($uniqueVersions,$uniqueVersions);
$q = ($q = $this->getSearchQuery()) ? $q->NoHTML() : "";
$q = ($q = $controller->getSearchQuery()) ? $q->NoHTML() : "";
// klude to take an array of objects down to a simple map
$entities = new ArrayList($entities);
@ -40,11 +46,13 @@ class DocumentationAdvancedSearchForm extends Form {
$fields = new FieldList(
new TextField('Search', _t('DocumentationViewer.KEYWORDS', 'Keywords'), $q),
new CheckboxSetField('Entities', _t('DocumentationViewer.MODULES', 'Modules'), $entities, $searchedEntities),
new CheckboxSetField('Versions', _t('DocumentationViewer.VERSIONS', 'Versions'),
new CheckboxSetField(
'Versions',
_t('DocumentationViewer.VERSIONS', 'Versions'),
$uniqueVersions, $searchedVersions
)
);
$actions = new FieldList(
new FormAction('results', _t('DocumentationViewer.SEARCH', 'Search'))
);
@ -61,6 +69,6 @@ class DocumentationAdvancedSearchForm extends Form {
$this->disableSecurityToken();
$this->setFormMethod('GET');
$this->setFormAction(self::$link_base . 'DocumentationSearchForm');
}
$this->setFormAction($controller->Link('search'));
}
}

View File

@ -3,30 +3,26 @@
class DocumentationSearchForm extends Form {
public function __construct($controller) {
$q = ($q = $controller->getSearchQuery()) ? $q->NoHTML() : "";
$entities = $controller->getSearchedEntities();
$versions = $controller->getSearchedVersions();
$fields = new FieldList(
new TextField('Search', _t('DocumentationViewer.SEARCH', 'Search'), $q)
TextField::create('q', _t('DocumentationViewer.SEARCH', 'Search'), '')
->setAttribute('placeholder', _t('DocumentationViewer.SEARCH', 'Search'))
);
if ($entities) $fields->push(
new HiddenField('Entities', '', implode(',', array_keys($entities)))
);
if ($versions) $fields->push(
new HiddenField('Versions', '', implode(',', $versions))
);
$actions = new FieldList(
new FormAction('results', 'Search')
new FormAction('results', _t('DocumentationViewer.SEARCH', 'Search'))
);
parent::__construct($controller, 'DocumentationSearchForm', $fields, $actions);
$this->disableSecurityToken();
$this->setFormMethod('GET');
$this->setFormAction($controller->Link('DocumentationSearchForm'));
}
if($controller->getPage()) {
$this->setFormAction($controller->getPage()->getEntity()->Link());
} else {
$this->setFormAction($controller->Link());
}
$this->addExtraClass('search');
}
}

View File

@ -1,17 +1,15 @@
<?php
/**
* A {@link DocumentationEntity} represents a module or folder with
* documentation. An entity not an individual page but a `section` of
* documentation.
* A {@link DocumentationEntity} represents a module or folder with stored
* documentation files. An entity not an individual page but a `section` of
* documentation arranged by version and language.
*
* Each section must have a version (defaults to `master`) which stores the
* actual path to the documentation (i.e framework 3.0, 3.1 docs point to
* different paths).
*
* Under each {@link DocumentationEntityVersion} contains languages. Most people
* will just have the one `en` folder but that translates to the
* {@link DocumentationEntityLanguage} instance to which the page relates to.
* Each entity has a version assigned to it (i.e master) and folders can be
* labeled with a specific version. For instance, doc.silverstripe.org has three
* DocumentEntities for Framework - versions 2.4, 3.0 and 3.1. In addition an
* entity can have a language attached to it. So for an instance with en, de and
* fr documentation you may have three {@link DocumentationEntities} registered.
*
*
* @package docsviewer
@ -21,153 +19,121 @@
class DocumentationEntity extends ViewableData {
/**
* @var array
* The key to match entities with that is not localized. For instance, you
* may have three entities (en, de, fr) that you want to display a nice
* title for, but matching needs to occur on a specific key.
*
* @var string $key
*/
private static $casting = array(
'Title' => 'Text'
);
protected $key;
/**
* The human readable title of this entity. Set when the module is
* registered.
*
* @var string $title
*/
protected $title;
/**
* @var string $folder
*/
protected $folder;
/**
* @var ArrayList $versions
*/
protected $versions;
/**
* If the system is setup to only document one entity then you may only
* want to show a single entity in the URL and the sidebar. Set this when
* you register the entity with the key `DefaultEntity`
* you register the entity with the key `DefaultEntity` and the URL will
* not include any version or language information.
*
* @var boolean $default_entity
*/
protected $defaultEntity;
/**
* Constructor. You do not need to pass the langs to this as
* it will work out the languages from the filesystem
*
* @param string $folder folder name
* @param string $title
* @var mixed
*/
public function __construct($folder, $title = false) {
$this->versions = new ArrayList();
$this->folder = $folder;
$this->title = (!$title) ? $folder : $title;
protected $path;
/**
* @see {@link http://php.net/manual/en/function.version-compare.php}
* @var float $version
*/
protected $version;
/**
* If this entity is a stable release or not. If it is not stable (i.e it
* could be a past or future release) then a warning message will be shown.
*
* @var boolean $stable
*/
protected $stable;
/**
* @var string
*/
protected $language;
/**
*
*/
public function __construct($key) {
$this->key = DocumentationHelper::clean_page_url($key);
}
/**
* Get the title of this module.
*
* @return String
* @return string
*/
public function getTitle() {
return $this->title;
}
/**
* Return the versions which have been registered for this entity.
*
* @return array
*/
public function getVersions() {
return $this->versions;
}
/**
* @return string|boo
*/
public function getStableVersion() {
if(!$this->hasVersions()) {
return false;
if(!$this->title) {
$this->title = DocumentationHelper::clean_page_name($this->key);
}
$sortedVersions = $this->getVersions()->toArray();
usort($sortedVersions, create_function('$a,$b', 'return version_compare($a,$b);'));
return array_pop($sortedVersions);
return $this->title;
}
/**
* Return whether we have a given version of this entity
*
* @return bool
* @param string $title
* @return this
*/
public function hasVersion($version) {
return $this->versions->find('Version', $version);
}
/**
* Return whether we have any versions at all0
*
* @return bool
*/
public function hasVersions() {
return $this->versions->count() > 0;
}
/**
* Add another version to this entity
*
* @param DocumentationEntityVersion
*/
public function addVersion($version) {
$this->versions->push($version);
public function setTitle($title) {
$this->title = $title;
return $this;
}
/**
* Remove a version from this entity
*
* @param float $version
*
*/
public function removeVersion($version) {
$this->versions->remove('Version', $version);
return $this;
}
/**
* Return the absolute path to this documentation entity.
*
* @return string
*/
public function getPath() {
return $this->path;
}
/**
* @return string
*/
public function getFolder() {
return $this->folder;
}
/**
* Returns the web accessible link to this entity. This does not include any
* of the language information, the URL without the language should be a
* permanent direct to 'en' documentation or the first language.
* Returns the web accessible link to this entity.
*
* Includes the version information
*
* @return string
*/
public function Link() {
return ($this->getDefaultEntity())
? Config::inst()->get('DocumentationViewer', 'link_base')
: Controller::join_links(
Config::inst()->get('DocumentationViewer', 'link_base'),
$this->getFolder()
if($this->getIsDefaultEntity()) {
$base = Controller::join_links(
Config::inst()->get('DocumentationViewer', 'link_base'),
$this->getLanguage(),
'/'
);
} else {
$base = Controller::join_links(
Config::inst()->get('DocumentationViewer', 'link_base'),
$this->getLanguage(),
$this->getKey(),
'/'
);
}
$base = ltrim(str_replace('//', '/', $base), '/');
if($this->stable) {
return $base;
}
return Controller::join_links(
$base,
$this->getVersion(),
'/'
);
}
/**
@ -182,24 +148,116 @@ class DocumentationEntity extends ViewableData {
*
* @return boolean
*/
public function hasRecord(DocumentationPage $page) {
foreach($this->getVersions() as $version) {
if(strstr($page->getPath(), $version->getPath()) !== false) {
return true;
}
public function hasRecord($page) {
if(!$page) {
return false;
}
return strstr($page->getPath(), $this->getPath()) !== false;
}
/**
* @param boolean $bool
*/
public function setDefaultEntity($bool) {
public function setIsDefaultEntity($bool) {
$this->defaultEntity = $bool;
return $this;
}
public function getDefaultEntity() {
/**
* @return boolean
*/
public function getIsDefaultEntity() {
return $this->defaultEntity;
}
/**
* @return string
*/
public function getKey() {
return $this->key;
}
/**
* @return string
*/
public function getLanguage() {
return $this->language;
}
/**
* @param string
*
* @return this
*/
public function setLanguage($language) {
$this->language = $language;
return $this;
}
/**
* @param string
*/
public function setVersion($version) {
$this->version = $version;
return $this;
}
/**
* @return float
*/
public function getVersion() {
return $this->version;
}
/**
* @return string
*/
public function getPath() {
return $this->path;
}
/**
* @param string $path
*
* @return this
*/
public function setPath($path) {
$this->path = $path;
return $this;
}
/**
* @param boolean
*/
public function setIsStable($stable) {
$this->stable = $stable;
return $this;
}
/**
* @return boolean
*/
public function getIsStable() {
return $this->stable;
}
/**
* Returns an integer value based on if a given version is the latest
* version. Will return -1 for if the version is older, 0 if versions are
* the same and 1 if the version is greater than.
*
* @param string $version
* @return int
*/
public function compare(DocumentationEntity $other) {
return version_compare($this->getVersion(), $other->getVersion());
}
}

View File

@ -1,104 +0,0 @@
<?php
/**
* @package docsviewer
*/
class DocumentationEntityLanguage extends ViewableData {
/**
* @var string
*/
protected $language;
/**
* @var DocumentationEntityVersion
*/
protected $entity;
/**
* @param DocumentationEntityVersion $version
* @param string $language
*/
public function __construct(DocumentationEntityVersion $version, $language) {
$this->entity = $version;
$this->language = $language;
}
/**
* @return string
*/
public function Link() {
return Controller::join_links(
$this->entity->Link(),
$this->language,
'/'
);
}
/**
* @return DocumentationEntityVersion
*/
public function getVersion() {
return $this->entity;
}
/**
* @return array
*/
public function getVersions() {
return $this->entity->getEntity()->getVersions();
}
/**
* @return
*/
public function getStableVersion() {
return $this->entity->getEntity()->getStableVersion();
}
/**
* @return string
*/
public function getLanguage() {
return $this->language;
}
/**
* @return string
*/
public function getPath() {
return Controller::join_links(
$this->entity->getPath(),
$this->language
);
}
/**
* @return string
*/
public function getBasePath() {
return $this->entity->getPath();
}
/**
* @return string
*/
public function getTitle() {
return $this->entity->getTitle();
}
/**
* @return string
*/
public function getBaseFolder() {
return $this->entity->getBaseFolder();
}
/**
* @return array
*/
public function getSupportedLanguages() {
return $this->entity->getSupportedLanguages();
}
}

View File

@ -1,149 +0,0 @@
<?php
/**
* A more specific instance of a {@link DocumentationEntity}. Each instance of
* a entity will have at least one of these objects attached to encapsulate
* linking to a particular URL.
*
* Versions are assumed to be in numeric format (e.g. '2.4'),
*
* They're also parsed through version_compare() in {@link getStableVersion()}
* which assumes a certain format:
*
* @see http://php.net/manual/en/function.version-compare.php
*
* Each {@link DocumentationEntityVersion} has a list of supported language
* instances. All documentation in the docs folder must sit under a supported
* language {@link DocumentationEntityLanguage}.
*
* @package docsviewer
*/
class DocumentationEntityVersion extends ViewableData {
/**
* @var array
*/
protected $supportedLanguages = array();
/**
* @var DocumentationEntity
*/
protected $entity;
/**
* @var mixed
*/
protected $path, $version, $stable;
/**
* @param DocumentationEntity $entity
* @param string $path
* @param float $version
* @param boolean $stable
*/
public function __construct($entity, $path, $version, $stable) {
$this->entity = $entity;
$this->path = $path;
$this->version = $version;
$this->stable = $stable;
// check what languages that this instance will support.
$langs = scandir($path);
$available = array();
if($langs) {
$possible = i18n::get_common_languages(true);
$possible['en'] = true;
foreach($langs as $key => $lang) {
if(isset($possible[$lang])) {
$this->supportedLanguages[$lang] = Injector::inst()->create(
'DocumentationEntityLanguage',
$this,
$lang
);
} else {
}
}
}
}
/**
* @return string
*/
public function Link() {
if($this->stable) {
return $this->entity->Link();
}
return Controller::join_links($this->entity->Link(), $this->version);
}
/**
* Return the languages which are available for this version of the entity.
*
* @return array
*/
public function getSupportedLanguages() {
return $this->supportedLanguages;
}
/**
* Return whether this entity has a given language.
*
* @return bool
*/
public function hasLanguageSupport($lang) {
return (in_array($lang, $this->getSupportedLanguages()));
}
/**
* @return float
*/
public function getVersion() {
return $this->version;
}
/**
* @return string
*/
public function getPath() {
return $this->path;
}
/**
* @return string
*/
public function getBaseFolder() {
return $this->entity->getFolder();
}
/**
* @return string
*/
public function getTitle() {
return $this->entity->getTitle();
}
/**
* @return DocumentationEntity
*/
public function getEntity() {
return $this->entity;
}
/**
* Returns an integer value based on if a given version is the latest
* version. Will return -1 for if the version is older, 0 if versions are
* the same and 1 if the version is greater than.
*
* @param string $version
* @return int
*/
public function compare(DocumentationEntityVersion $other) {
return version_compare($this->getVersion(), $other->getVersion());
}
}

View File

@ -23,21 +23,28 @@ class DocumentationPage extends ViewableData {
protected $summary;
/**
* @var DocumentationEntityLanguage
* @var DocumentationEntity
*/
protected $entity;
/**
* @var string
*/
protected $path, $filename;
protected $path;
/**
* @param DocumentationEntityLanguage $entity
* Filename
*
* @var string
*/
protected $filename;
/**
* @param DocumentationEntity $entity
* @param string $filename
* @param string $path
*/
public function __construct(DocumentationEntityLanguage $entity, $filename, $path) {
public function __construct(DocumentationEntity $entity, $filename, $path) {
$this->filename = $filename;
$this->path = $path;
$this->entity = $entity;
@ -56,20 +63,25 @@ class DocumentationPage extends ViewableData {
* @return string
*/
public function getBreadcrumbTitle($divider = ' - ') {
$pathParts = explode('/', $this->getRelativeLink());
$pathParts = explode('/', trim($this->getRelativeLink(), '/'));
// from the page from this
array_pop($pathParts);
// add the module to the breadcrumb trail.
array_unshift($pathParts, $this->entity->getTitle());
$pathParts[] = $this->entity->getTitle();
$titleParts = array_map(array(
'DocumentationHelper', 'clean_page_name'
), $pathParts);
return implode($divider, $titleParts + array($this->getTitle()));
array_unshift($titleParts, $this->getTitle());
return implode($divider, $titleParts);
}
/**
* @return DocumentationEntityLanguage
* @return DocumentationEntity
*/
public function getEntity() {
return $this->entity;
@ -114,7 +126,7 @@ class DocumentationPage extends ViewableData {
*/
public function getMarkdown($removeMetaData = false) {
try {
if ($md = file_get_contents($this->path)) {
if ($md = file_get_contents($this->getPath())) {
$this->populateMetaDataFromText($md, $removeMetaData);
return $md;
@ -126,6 +138,12 @@ class DocumentationPage extends ViewableData {
return false;
}
public function setMetaData($key, $value) {
$key = strtolower($key);
$this->$key = $value;
}
/**
* Parse a file and return the parsed HTML version.
@ -140,19 +158,19 @@ class DocumentationPage extends ViewableData {
$this->entity->Link()
);
}
/**
* @return string
*/
public function getRelativeLink() {
$path = str_replace($this->entity->getPath(), '', $this->path);
$path = str_replace($this->entity->getPath(), '', $this->getPath());
$url = explode('/', $path);
$url = implode('/', array_map(function($a) {
return DocumentationHelper::clean_page_url($a);
}, $url));
$url = rtrim($url, '/') . '/';
$url = trim($url, '/') . '/';
return $url;
}
@ -174,10 +192,10 @@ class DocumentationPage extends ViewableData {
* @return string
*/
public function Link() {
return Controller::join_links(
return ltrim(Controller::join_links(
$this->entity->Link(),
$this->getRelativeLink()
);
), '/');
}
/**
@ -220,4 +238,12 @@ class DocumentationPage extends ViewableData {
}
}
}
public function getVersion() {
return $this->entity->getVersion();
}
public function __toString() {
return sprintf(get_class($this) .': %s)', $this->getPath());
}
}

View File

@ -16,12 +16,11 @@ class RebuildLuceneDocsIndex extends BuildTask {
protected $description = "
Rebuilds the indexes used for the search engine in the docsviewer.";
function run($request) {
public function run($request) {
$this->rebuildIndexes();
}
function rebuildIndexes($quiet = false) {
require_once(DOCSVIEWER_PATH .'/thirdparty/markdown/markdown.php');
public function rebuildIndexes($quiet = false) {
require_once 'Zend/Search/Lucene.php';
ini_set("memory_limit", -1);
@ -60,7 +59,8 @@ class RebuildLuceneDocsIndex extends BuildTask {
}
// includes registration
$pages = DocumentationHelper::get_all_documentation_pages();
$manifest = new DocumentationManifest(true);
$pages = $manifest->getPages();
if($pages) {
$count = 0;
@ -70,40 +70,54 @@ class RebuildLuceneDocsIndex extends BuildTask {
$error = error_reporting();
error_reporting('E_ALL ^ E_NOTICE');
foreach($pages as $page) {
if(!Director::is_cli()) {
echo "<ul>";
}
foreach($pages as $url => $record) {
$count++;
$page = $manifest->getPage($url);
$doc = new Zend_Search_Lucene_Document();
$content = $page->getHTML();
$doc->addField(Zend_Search_Lucene_Field::Text('content', $content));
$doc->addField($titleField = Zend_Search_Lucene_Field::Text('Title', $page->getTitle()));
$doc->addField($breadcrumbField = Zend_Search_Lucene_Field::Text('BreadcrumbTitle', $page->getBreadcrumbTitle()));
if(!is_dir($page->getPath())) {
$doc = new Zend_Search_Lucene_Document();
$content = $page->getMarkdown();
if($content) $content = Markdown($content);
$doc->addField(Zend_Search_Lucene_Field::Keyword(
'Version', $page->getEntity()->getVersion()->getVersion()
));
$entity = ($entity = $page->getEntity()) ? $entity->getTitle() : "";
$doc->addField(Zend_Search_Lucene_Field::Text('content', $content));
$doc->addField($titleField = Zend_Search_Lucene_Field::Text('Title', $page->getTitle()));
$doc->addField($breadcrumbField = Zend_Search_Lucene_Field::Text('BreadcrumbTitle', $page->getBreadcrumbTitle()));
$doc->addField(Zend_Search_Lucene_Field::Keyword('Version', $page->getVersion()));
$doc->addField(Zend_Search_Lucene_Field::Keyword('Language', $page->getLang()));
$doc->addField(Zend_Search_Lucene_Field::Keyword('Entity', $entity));
$doc->addField(Zend_Search_Lucene_Field::Keyword('Link', $page->getLink(false)));
// custom boosts
$titleField->boost = 3;
$breadcrumbField->boost = 1.5;
$doc->addField(Zend_Search_Lucene_Field::Keyword(
'Language', $page->getEntity()->getLanguage()
));
$boost = Config::inst()->get('DocumentationSearch', 'boost_by_path');
$doc->addField(Zend_Search_Lucene_Field::Keyword(
'Entity', $entity
));
foreach($boost as $pathExpr => $boost) {
if(preg_match($pathExpr, $page->getRelativePath())) {
$doc->boost = $boost;
}
$doc->addField(Zend_Search_Lucene_Field::Keyword(
'Link', $page->Link()
));
// custom boosts
$titleField->boost = 3;
$breadcrumbField->boost = 1.5;
$boost = Config::inst()->get('DocumentationSearch', 'boost_by_path');
foreach($boost as $pathExpr => $boost) {
if(preg_match($pathExpr, $page->getRelativeLink())) {
$doc->boost = $boost;
}
$index->addDocument($doc);
}
if(!$quiet) echo "adding ". $page->getPath() ."\n";
$index->addDocument($doc);
if(!$quiet) {
if(Director::is_cli()) echo " * adding ". $page->getPath() ."\n";
else echo "<li>adding ". $page->getPath() ."</li>\n";
}
}
error_reporting($error);
@ -111,7 +125,9 @@ class RebuildLuceneDocsIndex extends BuildTask {
$index->commit();
if(!$quiet) echo "complete.";
if(!$quiet) {
echo "complete.";
}
}
}
@ -121,7 +137,7 @@ class RebuildLuceneDocsIndex extends BuildTask {
*/
class RebuildLuceneDocusIndex_Hourly extends HourlyTask {
function process() {
public function process() {
$reindex = new RebuildLuceneDocusIndex();
$reindex->rebuildIndexes(true);

View File

@ -87,17 +87,39 @@ html {
font-weight: bold;
}
#sidebar .search {
}
#sidebar .search fieldset {
border-bottom: 1px solid #eee;
padding: 15px 10px 14px;
}
#sidebar .search label,
#sidebar .search .Actions {
display: none;
}
#sidebar .search input {
width: 100%;
outline: none;
border-radius: 2px;
border: 1px solid #ddd;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.1);
padding: 9px;
}
#sidebar .search input:focus {
border-color: #1389ce;
}
#layout {
padding-bottom: 20px;
}
/*! language */
#language {
position: absolute;
top: 12px;
left: 50%;
margin-left: -480px;
width: 960px;
}
#language label {
@ -364,27 +386,29 @@ html {
}
/* Used on 404 page not found */
.warningBox { margin:9px 0 18px; }
.warningBox { margin: 0 0 18px; }
#content .warningBox p {
margin: 0;
}
.warningBoxTop {
background-color: #F9FAFA;
border: 1px solid #d3d9dc;
padding: 13px 9px 13px 66px;
background: #F9FAFA url(../../docsviewer/images/warning.png) no-repeat 18px 14px;
padding: 10px 10px 10px 70px;
background: url(../../docsviewer/images/warning.png) no-repeat 18px 14px;
}
#content .warningBoxTop h1 {
font-size: 27px; margin-bottom: 0; letter-spacing: 0;
margin-bottom: 0;
}
#content .warningBoxTop ul {
margin: 9px 0 18px;
}
#content .warningBoxTop li {
margin-bottom: 4px;
margin-bottom: 0;
}
#content .warningBoxBottom {
background-color: #0973A6;
padding: 12px 0 16px;

View File

@ -16,7 +16,7 @@
float: none !important;
height: auto !important;
left: auto !important;
line-height: 14px !important;
line-height: 20px !important;
margin: 0 !important;
outline: 0 !important;
overflow: visible !important;
@ -38,7 +38,7 @@
.syntaxhighlighter {
width: 100% !important;
margin: 20px 0 !important;
margin: 20px 0 30px 0 !important;
position: relative !important;
overflow: auto !important;
font-size: 13px !important;

View File

@ -59,7 +59,7 @@ a.application {
p {
font-size: 14px;
line-height: 20px;
margin: 0 0 10px;
margin: 0 0 25px;
}
.text-wrap {
@ -128,7 +128,7 @@ h1 {
letter-spacing: -1px;
}
h1 + p {
#table-of-contents + p {
font-size: 18px;
line-height: 20px;
}
@ -204,7 +204,7 @@ img {
/*! code */
pre {
margin: 20px 0;
margin: 20px 0 30px;
font: 13px/20px Monaco, 'Bitstream Vera Sans Mono', 'Courier New', monospace;
background-color: #f6f7f8;
border: 1px solid #e9eaed;

View File

@ -146,6 +146,58 @@
$("h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]").mouseleave(function() {
$(this).removeClass('hover');
});
$(".search input").live("keyup", function(e) {
clearTimeout($.data(this, 'timer'));
var string = $(this).val();
var self = $(this);
if (string == '') {
$(".search .autocomplete-results").hide();
} else {
var container;
if($(this).siblings('.autocomplete-results').length == 0) {
container = $("<div class='autocomplete-results'></div");
$(this).after(container);
} else {
container = $(this).siblings('.autocomplete-results').first();
}
$(this).data('timer', setTimeout(function() {
if(string !== '') {
$.getJSON(
self.parents('form').attr('action'),
{ query: string },
function(results) {
if(results) {
var list = $("<ul></ul>");
$.each(results, function(i, elem) {
list.append(
$("<li></li>")
.append(
$("<a></a>").attr('href', elem.link).text(elem.title)
).append(
elem.path
)
);
});
container.append(list);
} else {
container.hide().removeClass('loading');
}
}
);
}
return false;
}, 100));
};
});
/** ---------------------------------------------
* LANGAUGE SELECTER

View File

@ -11,10 +11,6 @@
<div class="wrapper">
<div id="header">
<h1><a href="$Link"><% _t('SILVERSTRIPEDOCUMENTATION', 'SilverStripe Documentation') %></a></h1>
<div id="language">
$LanguageForm
</div>
</div>
<div id="layout" class="clearfix">
@ -36,7 +32,7 @@
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', '$GoogleAnalyticsCode', 'auto'); // Replace with your property ID.
ga('create', '$GoogleAnalyticsCode', 'auto');
ga('send', 'pageview');
</script>
<% end_if %>

View File

@ -1,35 +1,37 @@
<div id="sidebar">
<div class="box">
$DocumentationSearchForm
<ul class="nav">
<% loop Entities %>
<% if DefaultEntity %>
<% loop Children %>
<li class="$LinkingMode $FirstLast">
<a href="$Link" class="top">$Title</a>
<% loop Menu %>
<% if DefaultEntity %>
<% loop Children %>
<li class="$LinkingMode $FirstLast">
<a href="$Link" class="top">$Title</a>
<% if LinkingMode == current %>
<% if Children %>
<ul class="$FirstLast">
<% loop Children %>
<li><a href="$Link" class="$LinkingMode">$Title</a></li>
<% end_loop %>
</ul><% end_if %>
<% end_if %>
</li>
<% end_loop %>
<% else %>
<li class="$LinkingMode $FirstLast"><a href="$Link" class="top">$Title <% if IsFolder %><span class="is-folder">&#9658;</span><% end_if %></a>
<% if LinkingMode == current %>
<% if Children %>
<ul class="$FirstLast">
<% loop Children %>
<li><a href="$Link" class="$LinkingMode">$Title</a></li>
<% end_loop %>
</ul><% end_if %>
<% if Children %>
<ul class="$FirstLast">
<% loop Children %>
<li><a href="$Link" class="$LinkingMode">$Title</a></li>
<% end_loop %>
</ul><% end_if %>
<% end_if %>
</li>
<% end_loop %>
<% else %>
<li class="$LinkingMode $FirstLast"><a href="$Link" class="top">$Title <% if IsFolder %><span class="is-folder">&#9658;</span><% end_if %></a>
<% if LinkingMode == current %>
<% if Children %>
<ul class="$FirstLast">
<% loop Children %>
<li><a href="$Link" class="$LinkingMode">$Title</a></li>
<% end_loop %>
</ul><% end_if %>
<% end_if %>
</li>
<% end_if %>
<% end_loop %>
<% end_if %>
<% end_loop %>
</ul>
</div>

View File

@ -0,0 +1,9 @@
<% if Versions %>
<div class="versions">
<ul>
<% loop Versions %>
<li><a href="$Link" class="$LinkingMode">$Title</a></li>
<% end_loop %>
</ul>
</div>
<% end_if %>

View File

@ -1,17 +1,24 @@
<div class="box">
<% if VersionWarning %>
<% include DocumentationVersion_warning %>
<% if SearchQuery %>
$SearchResults
<% else %>
<% include DocumentationVersions %>
<% if VersionWarning %>
<% include DocumentationVersion_warning %>
<% end_if %>
<h2>$Title</h2>
<% include DocumentationTableContents %>
<% loop Children %>
<ul>
<li><a href="$Link">$Title</a></li>
</ul>
<% end_loop %>
<% include DocumentationNextPrevious %>
<% end_if %>
<h2>$Title</h2>
<% include DocumentationTableContents %>
<% loop Children %>
<ul>
<li><a href="$Link">$Title</a></li>
</ul>
<% end_loop %>
<% include DocumentationNextPrevious %>
</div>

View File

@ -1,4 +1,6 @@
<div id="documentation-page" class="box">
<% include DocumentationVersions %>
<% if VersionWarning %>
<% include DocumentationVersion_warning %>
<% end_if %>

View File

@ -1,4 +1,4 @@
<div class="box">
<div id="documentation_index" class="box">
<h1>Documentation Index</h1>
<div id="page-numbers">

View File

@ -1,4 +1,4 @@
<div id="documentation-page" class="documentation-error-page">
<div id="documentation_error_page" class="box">
<div class="warningBox" id="pageNotFoundWarning">
<div class="warningBoxTop">
<h1>We're sorry&#8230;</h1>

View File

@ -1,45 +0,0 @@
<div id="documentation-page" class="box">
<p>Your search for <strong>&quot;$Query.XML&quot;</strong> found $TotalResults result<% if TotalResults != 1 %>s<% end_if %>.</p>
<% if AdvancedSearchEnabled %>
<h4><% _t('ADVANCEDSEARCH', 'Advanced Search') %></h4>
$AdvancedSearchForm
<% end_if %>
<% if Results %>
<p>Showing page $ThisPage of $TotalPages</p>
<% loop Results %>
<h2><a href="$Link"><% if BreadcrumbTitle %>$BreadcrumbTitle<% else %>$Title<% end_if %></a></h2>
<p>$Content.LimitCharacters(200)</p>
<% end_loop %>
<% if SearchPages %>
<ul class="pagination">
<% if PrevUrl = false %><% else %>
<li class="prev"><a href="$PrevUrl">Prev</a></li>
<% end_if %>
<% loop SearchPages %>
<% if IsEllipsis %>
<li class="ellipsis">...</li>
<% else %>
<% if Current %>
<li class="active"><strong>$PageNumber</strong></li>
<% else %>
<li><a href="$Link">$PageNumber</a></li>
<% end_if %>
<% end_if %>
<% end_loop %>
<% if NextUrl = false %>
<% else %>
<li class="next"><a href="$NextUrl">Next</a></li>
<% end_if %>
</ul>
<% end_if %>
<% else %>
<p>No Results</p>
<% end_if %>
</div>

View File

@ -0,0 +1,43 @@
<p>Your search for <strong>&quot;$SearchQuery.XML&quot;</strong> found $TotalResults result<% if TotalResults != 1 %>s<% end_if %>.</p>
<% if AdvancedSearchEnabled %>
<h4><% _t('ADVANCEDSEARCH', 'Advanced Search') %></h4>
$AdvancedSearchForm
<% end_if %>
<% if Results %>
<p>Showing page $ThisPage of $TotalPages</p>
<% loop Results %>
<h2><a href="$Link"><% if BreadcrumbTitle %>$BreadcrumbTitle<% else %>$Title<% end_if %></a></h2>
<p>$Content.LimitCharacters(200)</p>
<% end_loop %>
<% if SearchPages %>
<ul class="pagination">
<% if PrevUrl = false %><% else %>
<li class="prev"><a href="$PrevUrl">Prev</a></li>
<% end_if %>
<% loop SearchPages %>
<% if IsEllipsis %>
<li class="ellipsis">...</li>
<% else %>
<% if Current %>
<li class="active"><strong>$PageNumber</strong></li>
<% else %>
<li><a href="$Link">$PageNumber</a></li>
<% end_if %>
<% end_if %>
<% end_loop %>
<% if NextUrl = false %>
<% else %>
<li class="next"><a href="$NextUrl">Next</a></li>
<% end_if %>
</ul>
<% end_if %>
<% else %>
<p>No Results</p>
<% end_if %>

View File

@ -1,17 +1,70 @@
<?php
/**
* @package docsviewer
* @subpackage tests
*/
class DocumentationHelperTests extends SapphireTest {
public function testGetAllPages() {
if(!DocumentationSearch::enabled()) {
return;
}
DocumentationService::set_automatic_registration(false);
DocumentationService::register('docs-search', DOCSVIEWER_PATH . '/tests/docs-search/');
$search = DocumentationSearch::get_all_documentation_pages();
$this->assertEquals(7, $search->Count(), '5 pages. 5 pages in entire folder');
public function testCleanName() {
$this->assertEquals("File Path", DocumentationHelper::clean_page_name(
'00_file-path.md'
));
}
public function testCleanUrl() {
$this->assertEquals("some_path", DocumentationHelper::clean_page_url(
'Some Path'
));
$this->assertEquals("somefilepath", DocumentationHelper::clean_page_url(
'00_SomeFilePath.md'
));
}
public function testTrimSortNumber() {
$this->assertEquals('file', DocumentationHelper::trim_sort_number(
'0_file'
));
$this->assertEquals('2.1', DocumentationHelper::trim_sort_number(
'2.1'
));
$this->assertEquals('dev/tasks/2.1', DocumentationHelper::trim_sort_number(
'dev/tasks/2.1'
));
}
public function testTrimExtension() {
$this->assertEquals('file', DocumentationHelper::trim_extension_off(
'file.md'
));
$this->assertEquals('dev/path/file', DocumentationHelper::trim_extension_off(
'dev/path/file.md'
));
}
public function testGetExtension() {
$this->assertEquals('md', DocumentationHelper::get_extension(
'file.md'
));
$this->assertEquals('md', DocumentationHelper::get_extension(
'dev/tasks/file.md'
));
$this->assertEquals('txt', DocumentationHelper::get_extension(
'dev/tasks/file.txt'
));
$this->assertNull(DocumentationHelper::get_extension(
'doc_test/2.3'
));
$this->assertNull(DocumentationHelper::get_extension(
'dev/docs/en/doc_test/2.3/subfolder'
));
}
}

View File

@ -6,70 +6,88 @@
*/
class DocumentationManifestTests extends SapphireTest {
private $manifest, $pages;
private $manifest;
public function setUpOnce() {
parent::setUpOnce();
$this->origEnabled = DocumentationService::automatic_registration_enabled();
DocumentationService::set_automatic_registration(false);
$this->origModules = DocumentationService::get_registered_entities();
$this->origLinkBase = Config::inst()->get('DocumentationViewer', 'link_base');
Config::inst()->update('DocumentationViewer', 'link_base', 'dev/docs/');
public function setUp() {
parent::setUp();
foreach($this->origModules as $module) {
DocumentationService::unregister($module->getFolder());
}
Config::nest();
// We set 3.0 as current, and test most assertions against 2.4 - to avoid 'current' rewriting issues
DocumentationService::register("testdocs", DOCSVIEWER_PATH . "/tests/docs/", '2.3');
DocumentationService::register("testdocs", DOCSVIEWER_PATH . "/tests/docs-v2.4/", '2.4', 'Doc Test', true);
DocumentationService::register("testdocs", DOCSVIEWER_PATH . "/tests/docs-v3.0/", '3.0', 'Doc Test');
// explicitly use dev/docs. Custom paths should be tested separately
Config::inst()->update(
'DocumentationViewer', 'link_base', 'dev/docs'
);
// disable automatic module registration so modules don't interfere.
Config::inst()->update(
'DocumentationManifest', 'automatic_registration', false
);
Config::inst()->remove('DocumentationManifest', 'register_entities');
Config::inst()->update(
'DocumentationManifest', 'register_entities', array(
array(
'Path' => DOCSVIEWER_PATH . "/tests/docs/",
'Title' => 'Doc Test',
'Key' => 'testdocs',
'Version' => '2.3'
),
array(
'Path' => DOCSVIEWER_PATH . "/tests/docs-v2.4/",
'Title' => 'Doc Test',
'Version' => '2.4',
'Key' => 'testdocs',
'Stable' => true
),
array(
'Path' => DOCSVIEWER_PATH . "/tests/docs-v3.0/",
'Title' => 'Doc Test',
'Key' => 'testdocs',
'Version' => '3.0'
)
)
);
$this->manifest = new DocumentationManifest(true);
$this->pages = $this->manifest->getPages();
}
public function tearDownOnce() {
parent::tearDownOnce();
public function tearDown() {
parent::tearDown();
DocumentationService::unregister("testdocs");
DocumentationService::set_automatic_registration($this->origEnabled);
Config::inst()->update('DocumentationViewer', 'link_base', $this->origLinkBase);
Config::unnest();
}
/**
* Check that the manifest matches what we'd expect.
*/
public function testRegenerate() {
$match = array(
'dev/docs/testdocs/2.3/de/',
'dev/docs/testdocs/2.3/de/german/',
'dev/docs/testdocs/2.3/de/test/',
'dev/docs/testdocs/2.3/en/',
'dev/docs/testdocs/2.3/en/sort/',
'dev/docs/testdocs/2.3/en/subfolder/',
'dev/docs/testdocs/2.3/en/test/',
'dev/docs/testdocs/2.3/en/sort/basic/',
'dev/docs/testdocs/2.3/en/sort/some-page/',
'dev/docs/testdocs/2.3/en/sort/intermediate/',
'dev/docs/testdocs/2.3/en/sort/another-page/',
'dev/docs/testdocs/2.3/en/sort/advanced/',
'dev/docs/testdocs/2.3/en/subfolder/subpage/',
'dev/docs/testdocs/2.3/en/subfolder/subsubfolder/',
'dev/docs/testdocs/2.3/en/subfolder/subsubfolder/subsubpage/',
'dev/docs/testdocs/en/',
'dev/docs/testdocs/en/test/',
'dev/docs/testdocs/3.0/en/',
'dev/docs/testdocs/3.0/en/changelog/',
'dev/docs/testdocs/3.0/en/tutorials/',
'dev/docs/testdocs/3.0/en/empty/'
'de/testdocs/2.3/',
'de/testdocs/2.3/german/',
'de/testdocs/2.3/test/',
'en/testdocs/2.3/',
'en/testdocs/2.3/sort/',
'en/testdocs/2.3/subfolder/',
'en/testdocs/2.3/test/',
'en/testdocs/2.3/sort/basic/',
'en/testdocs/2.3/sort/some-page/',
'en/testdocs/2.3/sort/intermediate/',
'en/testdocs/2.3/sort/another-page/',
'en/testdocs/2.3/sort/advanced/',
'en/testdocs/2.3/subfolder/subpage/',
'en/testdocs/2.3/subfolder/subsubfolder/',
'en/testdocs/2.3/subfolder/subsubfolder/subsubpage/',
'en/testdocs/',
'en/testdocs/test/',
'en/testdocs/3.0/',
'en/testdocs/3.0/changelog/',
'en/testdocs/3.0/tutorials/',
'en/testdocs/3.0/empty/'
);
$this->assertEquals($match, array_keys($this->pages));
$this->assertEquals($match, array_keys($this->manifest->getPages()));
}
public function testGetNextPage() {
@ -83,4 +101,20 @@ class DocumentationManifestTests extends SapphireTest {
public function testGetPage() {
}
}
public function testGenerateBreadcrumbs() {
}
public function testGetChildrenFor() {
}
public function testGetAllVersions() {
}
public function testGetStableVersion() {
}
}

View File

@ -6,85 +6,76 @@
*/
class DocumentationPageTest extends SapphireTest {
protected $entity;
public function setUp() {
parent::setUp();
$this->entity = new DocumentationEntity('doctest');
$this->entity->setPath(DOCSVIEWER_PATH . '/tests/docs/en/');
$this->entity->setVersion('2.4');
$this->entity->setLanguage('en');
Config::nest();
// explicitly use dev/docs. Custom paths should be tested separately
Config::inst()->update(
'DocumentationViewer', 'link_base', 'dev/docs/'
);
$manifest = new DocumentationManifest(true);
}
public function tearDown() {
parent::tearDown();
Config::unnest();
}
public function testGetLink() {
$entity = new DocumentationEntity('testmodule', null, DOCSVIEWER_PATH .'/tests/docs/');
$page = new DocumentationPage();
$page->setRelativePath('test.md');
$page->setEntity($entity);
$page = new DocumentationPage(
$this->entity,
'test.md',
DOCSVIEWER_PATH . '/tests/docs/en/test.md'
);
// single layer
$this->assertStringEndsWith('testmodule/en/test', $page->Link, 'The page link should have no extension and have a language');
$this->assertEquals('dev/docs/en/doctest/2.4/test/', $page->Link(),
'The page link should have no extension and have a language'
);
$page = new DocumentationFolder(
$this->entity,
'sort',
DOCSVIEWER_PATH . '/tests/docs/en/sort/'
);
$folder = new DocumentationPage();
$folder->setRelativePath('sort');
$folder->setEntity($entity);
$this->assertEquals('dev/docs/en/doctest/2.4/sort/', $page->Link());
// folder, should have a trailing slash
$this->assertStringEndsWith('testmodule/en/sort/', $folder->Link);
$page = new DocumentationFolder(
$this->entity,
'1-basic.md',
DOCSVIEWER_PATH . '/tests/docs/en/sort/1-basic.md'
);
// second
$nested = new DocumentationPage();
$nested->setRelativePath('subfolder/subpage.md');
$nested->setEntity($entity);
$this->assertStringEndsWith('testmodule/en/subfolder/subpage', $nested->Link);
// test with version.
$entity = DocumentationService::register("versionlinks", DOCSVIEWER_PATH ."/tests/docs-v2.4/", '1');
$entity->addVersion('2', DOCSVIEWER_PATH ."/tests/docs-v3.0/");
$entity->setStableVersion('2');
$page = new DocumentationPage();
$page->setRelativePath('test.md');
$page->setEntity($entity);
$page->setVersion('1');
$this->assertStringEndsWith('versionlinks/en/1/test', $page->Link);
}
public function testGetRelativePath() {
$page = new DocumentationPage();
$page->setRelativePath('test.md');
$page->setEntity(new DocumentationEntity('mymodule', null, DOCSVIEWER_PATH . '/tests/docs/'));
$this->assertEquals('test.md', $page->getRelativePath());
$page = new DocumentationPage();
$page->setRelativePath('subfolder/subpage.md');
$page->setEntity(new DocumentationEntity('mymodule', null, DOCSVIEWER_PATH . '/tests/docs/'));
$this->assertEquals('subfolder/subpage.md', $page->getRelativePath());
}
public function testGetPath() {
$absPath = DOCSVIEWER_PATH .'/tests/docs/';
$page = new DocumentationPage();
$page->setRelativePath('test.md');
$page->setEntity(new DocumentationEntity('mymodule', null, $absPath));
$this->assertEquals($absPath . 'en/test.md', $page->getPath());
$page = new DocumentationPage();
$page->setRelativePath('subfolder/subpage.md');
$page->setEntity(new DocumentationEntity('mymodule', null, $absPath));
$this->assertEquals($absPath . 'en/subfolder/subpage.md', $page->getPath());
$this->assertEquals('dev/docs/en/doctest/2.4/sort/basic/', $page->Link());
}
public function testGetBreadcrumbTitle() {
$entity = new DocumentationEntity('testmodule', null, DOCSVIEWER_PATH . '/tests/docs/');
$page = new DocumentationPage(
$this->entity,
'test.md',
DOCSVIEWER_PATH . '/tests/docs/en/test.md'
);
$this->assertEquals("Test - Doctest", $page->getBreadcrumbTitle());
$page = new DocumentationPage();
$page->setRelativePath('test.md');
$page->setEntity($entity);
$page = new DocumentationFolder(
$this->entity,
'1-basic.md',
DOCSVIEWER_PATH . '/tests/docs/en/sort/1-basic.md'
);
$this->assertEquals("Testmodule - Test", $page->getBreadcrumbTitle());
$page = new DocumentationPage();
$page->setRelativePath('subfolder/subpage.md');
$page->setEntity(new DocumentationEntity('mymodule', null, DOCSVIEWER_PATH . '/tests/docs/'));
$this->assertEquals('Mymodule - Subfolder - Subpage', $page->getBreadcrumbTitle());
$this->assertEquals('Basic - Sort - Doctest', $page->getBreadcrumbTitle());
}
}

View File

@ -1,10 +1,74 @@
<?php
/**
* @package docsviewer
* @subpackage tests
*/
class DocumentationParserTest extends SapphireTest {
function testGenerateHtmlId() {
protected $entity, $entityAlt, $page, $subPage, $subSubPage, $filePage, $metaDataPage;
public function tearDown() {
parent::tearDown();
Config::unnest();
}
public function setUp() {
parent::setUp();
Config::nest();
// explicitly use dev/docs. Custom paths should be tested separately
Config::inst()->update(
'DocumentationViewer', 'link_base', 'dev/docs/'
);
$this->entity = new DocumentationEntity('DocumentationParserTest');
$this->entity->setPath(DOCSVIEWER_PATH . '/tests/docs/en/');
$this->entity->setVersion('2.4');
$this->entity->setLanguage('en');
$this->entityAlt = new DocumentationEntity('DocumentationParserParserTest');
$this->entityAlt->setPath(DOCSVIEWER_PATH . '/tests/docs-parser/en/');
$this->entityAlt->setVersion('2.4');
$this->entityAlt->setLanguage('en');
$this->page = new DocumentationPage(
$this->entity,
'test.md',
DOCSVIEWER_PATH . '/tests/docs/en/test.md'
);
$this->subPage = new DocumentationPage(
$this->entity,
'subpage.md',
DOCSVIEWER_PATH. '/tests/docs/en/subfolder/subpage.md'
);
$this->subSubPage = new DocumentationPage(
$this->entity,
'subsubpage.md',
DOCSVIEWER_PATH. '/tests/docs/en/subfolder/subsubfolder/subsubpage.md'
);
$this->filePage = new DocumentationPage(
$this->entityAlt,
'file-download.md',
DOCSVIEWER_PATH . '/tests/docs-parser/en/file-download.md'
);
$this->metaDataPage = new DocumentationPage(
$this->entityAlt,
'MetaDataTest.md',
DOCSVIEWER_PATH . '/tests/docs-parser/en/MetaDataTest.md'
);
$manifest = new DocumentationManifest(true);
}
public function testGenerateHtmlId() {
$this->assertEquals('title-one', DocumentationParser::generate_html_id('title one'));
$this->assertEquals('title-one', DocumentationParser::generate_html_id('Title one'));
$this->assertEquals('title-and-one', DocumentationParser::generate_html_id('Title &amp; One'));
@ -13,13 +77,11 @@ class DocumentationParserTest extends SapphireTest {
$this->assertEquals('title-one', DocumentationParser::generate_html_id('Title--one'));
}
function testRewriteCodeBlocks() {
$page = new DocumentationPage();
$page->setRelativePath('test.md');
$page->setEntity(new DocumentationEntity('mymodule', '2.4', DOCSVIEWER_PATH . '/tests/docs/'));
$page->setLang('en');
$page->setVersion('2.4');
$result = DocumentationParser::rewrite_code_blocks($page->getMarkdown());
public function testRewriteCodeBlocks() {
$result = DocumentationParser::rewrite_code_blocks(
$this->page->getMarkdown()
);
$expected = <<<HTML
<pre class="brush: php">
code block
@ -62,58 +124,60 @@ HTML;
$this->assertContains($expected, $result, 'Backtick with newlines');
}
function testImageRewrites() {
// Page on toplevel
$page = new DocumentationPage();
$page->setRelativePath('subfolder/subpage.md');
$page->setEntity(new DocumentationEntity('mymodule', '2.4', DOCSVIEWER_PATH . '/tests/docs/'));
$page->setLang('en');
$page->setVersion('2.4');
public function testImageRewrites() {
$result = DocumentationParser::rewrite_image_links($page->getMarkdown(), $page, 'mycontroller/cms/2.4/en/');
$result = DocumentationParser::rewrite_image_links(
$this->subPage->getMarkdown(),
$this->subPage
);
$expected = Controller::join_links(
Director::absoluteBaseURL(), DOCSVIEWER_DIR, '/tests/docs/en/subfolder/_images/image.png'
);
$this->assertContains(
'[relative image link](' . Director::absoluteBaseURL() .'/'. DOCSVIEWER_DIR . '/tests/docs/en/subfolder/_images/image.png)',
sprintf('[relative image link](%s)', $expected),
$result
);
$this->assertContains(
'[parent image link](' . Director::absoluteBaseURL() . '/'. DOCSVIEWER_DIR. '/tests/docs/en/_images/image.png)',
sprintf('[parent image link](%s)', Controller::join_links(
Director::absoluteBaseURL(), DOCSVIEWER_DIR, '/tests/docs/en/_images/image.png'
)),
$result
);
// $this->assertContains(
// '[absolute image link](' . Director::absoluteBaseURL() . '/'. DOCSVIEWER_DIR. '/tests/docs/en/_images/image.png)',
// $result
// );
}
function testApiLinks() {
// Page on toplevel
$page = new DocumentationPage();
$page->setRelativePath('test.md');
$page->setEntity(new DocumentationEntity('mymodule', '2.4', DOCSVIEWER_PATH .'/tests/docs/'));
$page->setLang('en');
$page->setVersion('2.4');
$result = DocumentationParser::rewrite_api_links($page->getMarkdown(), $page, 'mycontroller/cms/2.4/en/');
$expected = Controller::join_links(
Director::absoluteBaseURL(), DOCSVIEWER_DIR, '/tests/docs/en/_images/image.png'
);
$this->assertContains(
'[link: api](http://api.silverstripe.org/search/lookup/?q=DataObject&version=2.4&module=mymodule)',
$result
);
$this->assertContains( '[DataObject::$has_one](http://api.silverstripe.org/search/lookup/?q=DataObject::$has_one&version=2.4&module=mymodule)',
sprintf('[absolute image link](%s)', $expected),
$result
);
}
function testHeadlineAnchors() {
$page = new DocumentationPage();
$page->setRelativePath('test.md');
$page->setEntity(new DocumentationEntity('mymodule', '2.4', DOCSVIEWER_PATH . '/tests/docs/'));
$page->setLang('en');
$page->setVersion('2.4');
$result = DocumentationParser::rewrite_heading_anchors($page->getMarkdown(), $page);
public function testApiLinks() {
$result = DocumentationParser::rewrite_api_links(
$this->page->getMarkdown(),
$this->page
);
$this->assertContains(
'[link: api](http://api.silverstripe.org/search/lookup/?q=DataObject&version=2.4&module=documentationparsertest)',
$result
);
$this->assertContains(
'[DataObject::$has_one](http://api.silverstripe.org/search/lookup/?q=DataObject::$has_one&version=2.4&module=documentationparsertest)',
$result
);
}
public function testHeadlineAnchors() {
$result = DocumentationParser::rewrite_heading_anchors(
$this->page->getMarkdown(),
$this->page
);
/*
# Heading one {#Heading-one}
@ -143,20 +207,18 @@ HTML;
}
function testRelativeLinks() {
// Page on toplevel
$page = new DocumentationPage();
$page->setRelativePath('test.md');
$page->setEntity(new DocumentationEntity('mymodule', '2.4', DOCSVIEWER_PATH . '/tests/docs/'));
$result = DocumentationParser::rewrite_relative_links($page->getMarkdown(), $page, 'mycontroller/cms/2.4/en/');
public function testRelativeLinks() {
$result = DocumentationParser::rewrite_relative_links(
$this->page->getMarkdown(),
$this->page
);
$this->assertContains(
'[link: subfolder index](mycontroller/cms/2.4/en/subfolder/)',
'[link: subfolder index](dev/docs/en/documentationparsertest/2.4/subfolder/)',
$result
);
$this->assertContains(
'[link: subfolder page](mycontroller/cms/2.4/en/subfolder/subpage)',
'[link: subfolder page](dev/docs/en/documentationparsertest/2.4/subfolder/subpage)',
$result
);
$this->assertContains(
@ -167,93 +229,80 @@ HTML;
'[link: api](api:DataObject)',
$result
);
$this->assertContains(
'[link: relative](mycontroller/cms/2.4/a-relative-file.md)',
$result
);
// Page in subfolder
$page = new DocumentationPage();
$page->setRelativePath('subfolder/subpage.md');
$page->setEntity(new DocumentationEntity('mymodule', '2.4', DOCSVIEWER_PATH . '/tests/docs/'));
$result = DocumentationParser::rewrite_relative_links($page->getMarkdown(), $page, 'mycontroller/cms/2.4/en/');
$result = DocumentationParser::rewrite_relative_links(
$this->subPage->getMarkdown(),
$this->subPage
);
$this->assertContains(
'[link: relative](mycontroller/cms/2.4/en/subfolder/subpage.md)',
'[link: relative](dev/docs/en/documentationparsertest/2.4/subfolder/subpage.md)',
$result
);
$this->assertContains(
'[link: absolute index](mycontroller/cms/2.4/en/)',
'[link: absolute index](dev/docs/en/documentationparsertest/2.4/)',
$result
);
$this->assertContains(
'[link: absolute index with name](mycontroller/cms/2.4/en/index)',
'[link: absolute index with name](dev/docs/en/documentationparsertest/2.4/index)',
$result
);
$this->assertContains(
'[link: relative index](mycontroller/cms/2.4/en/)',
$result
);
$this->assertContains(
'[link: relative parent page](mycontroller/cms/2.4/en/test)',
$result
);
$this->assertContains(
'[link: absolute parent page](mycontroller/cms/2.4/en/test)',
'[link: relative index](dev/docs/en/documentationparsertest/2.4/)',
$result
);
// Page in nested subfolder
$page = new DocumentationPage();
$page->setRelativePath('subfolder/subsubfolder/subsubpage.md');
$page->setEntity(new DocumentationEntity('mymodule', '2.4', DOCSVIEWER_PATH . '/tests/docs/'));
$result = DocumentationParser::rewrite_relative_links($page->getMarkdown(), $page, 'mycontroller/cms/2.4/en/');
$this->assertContains(
'[link: relative parent page](dev/docs/en/documentationparsertest/2.4/test)',
$result
);
$this->assertContains(
'[link: absolute index](mycontroller/cms/2.4/en/)',
'[link: absolute parent page](dev/docs/en/documentationparsertest/2.4/test)',
$result
);
$result = DocumentationParser::rewrite_relative_links(
$this->subSubPage->getMarkdown(),
$this->subSubPage
);
$this->assertContains(
'[link: relative index](mycontroller/cms/2.4/en/subfolder/)',
'[link: absolute index](dev/docs/en/documentationparsertest/2.4/)',
$result
);
$this->assertContains(
'[link: relative parent page](mycontroller/cms/2.4/en/subfolder/subpage)',
'[link: relative index](dev/docs/en/documentationparsertest/2.4/subfolder/)',
$result
);
$this->assertContains(
'[link: relative grandparent page](mycontroller/cms/2.4/en/test)',
'[link: relative parent page](dev/docs/en/documentationparsertest/2.4/subfolder/subpage)',
$result
);
$this->assertContains(
'[link: absolute page](mycontroller/cms/2.4/en/test)',
'[link: relative grandparent page](dev/docs/en/documentationparsertest/2.4/test)',
$result
);
$this->assertContains(
'[link: absolute page](dev/docs/en/documentationparsertest/2.4/test)',
$result
);
}
function testRetrieveMetaData() {
$page = new DocumentationPage();
$page->setRelativePath('MetaDataTest.md');
$page->setEntity(new DocumentationEntity('parser', '2.4', DOCSVIEWER_PATH . '/tests/docs-parser/'));
public function testRetrieveMetaData() {
DocumentationParser::retrieve_meta_data($this->metaDataPage);
DocumentationParser::retrieve_meta_data($page);
$this->assertEquals('Dr. Foo Bar.', $page->Author);
$this->assertEquals("Foo Bar's Test page.", $page->getTitle());
$this->assertEquals("Foo Bar's Test page.", $page->Title);
$this->assertEquals('Dr. Foo Bar.', $this->metaDataPage->author);
$this->assertEquals("Foo Bar's Test page.", $this->metaDataPage->getTitle());
}
function testRewritingRelativeLinksToFiles() {
$folder = DOCSVIEWER_PATH . '/tests/docs-parser/';
$page = new DocumentationPage();
$page->setRelativePath('file-download.md');
$page->setEntity(new DocumentationEntity('parser', '2.4', $folder));
$parsed = DocumentationParser::parse($page, $folder);
public function testRewritingRelativeLinksToFiles() {
$parsed = DocumentationParser::parse($this->filePage);
$this->assertContains(
DOCSVIEWER_DIR .'/tests/docs-parser/en/_images/external_link.png',

View File

@ -1,26 +1,35 @@
<?php
/**
* @package docsviewer
* @subpackage tests
*/
class DocumentationPermalinksTest extends FunctionalTest {
function testSavingAndAccessingMapping() {
public function testSavingAndAccessingMapping() {
// basic test
DocumentationPermalinks::add(array(
'foo' => 'current/en/sapphire/subfolder/foo',
'bar' => 'current/en/cms/bar'
'foo' => 'en/framework/subfolder/foo',
'bar' => 'en/cms/bar'
));
$this->assertEquals('current/en/sapphire/subfolder/foo', DocumentationPermalinks::map('foo'));
$this->assertEquals('current/en/cms/bar', DocumentationPermalinks::map('bar'));
$this->assertEquals('en/framework/subfolder/foo',
DocumentationPermalinks::map('foo')
);
$this->assertEquals('en/cms/bar',
DocumentationPermalinks::map('bar')
);
}
/**
* Tests to make sure short codes get translated to full paths
* Tests to make sure short codes get translated to full paths.
*
*/
function testRedirectingMapping() {
// testing the viewer class but clearer here
public function testRedirectingMapping() {
DocumentationPermalinks::add(array(
'foo' => 'current/en/sapphire/subfolder/foo',
'bar' => 'current/en/cms/bar'
'foo' => 'en/framework/subfolder/foo',
'bar' => 'en/cms/bar'
));
$this->autoFollowRedirection = false;
@ -29,6 +38,6 @@ class DocumentationPermalinksTest extends FunctionalTest {
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'foo'), DataModel::inst());
$this->assertEquals('301', $response->getStatusCode());
$this->assertContains('current/en/sapphire/subfolder/foo', $response->getHeader('Location'));
$this->assertContains('en/framework/subfolder/foo', $response->getHeader('Location'));
}
}

View File

@ -7,29 +7,56 @@
class DocumentationSearchTest extends FunctionalTest {
function setUp() {
public function setUp() {
parent::setUp();
if(!DocumentationSearch::enabled()) return;
DocumentationService::set_automatic_registration(false);
DocumentationService::register('docs-search', DOCSVIEWER_PATH . '/tests/docs-search/');
Config::nest();
// explicitly use dev/docs. Custom paths should be tested separately
Config::inst()->update(
'DocumentationViewer', 'link_base', 'dev/docs'
);
// disable automatic module registration so modules don't interfere.
Config::inst()->update(
'DocumentationManifest', 'automatic_registration', false
);
Config::inst()->remove('DocumentationManifest', 'register_entities');
Config::inst()->update('DocumentationSearch', 'enabled', true);
Config::inst()->update(
'DocumentationManifest', 'register_entities', array(
array(
'Path' => DOCSVIEWER_PATH . "/tests/docs-search/",
'Title' => 'Docs Search Test', )
)
);
$this->manifest = new DocumentationManifest(true);
}
function testOpenSearchControllerAccessible() {
public function tearDown() {
parent::tearDown();
Config::unnest();
}
public function testOpenSearchControllerAccessible() {
$c = new DocumentationOpenSearchController();
$response = $c->handleRequest(new SS_HTTPRequest('GET', ''), DataModel::inst());
$this->assertEquals(404, $response->getStatusCode());
// test accessing it when the search isn't active
DocumentationSearch::enable(false);
Config::inst()->update('DocumentationSearch', 'enabled', false);
$response = $c->handleRequest(new SS_HTTPRequest('GET', 'description/'), DataModel::inst());
$this->assertEquals(404, $response->getStatusCode());
// test we get a response to the description. The meta data test will check
// that the individual fields are valid but we should check urls are there
DocumentationSearch::enable(true);
// test we get a response to the description. The meta data test will
// check that the individual fields are valid but we should check urls
// are there
Config::inst()->update('DocumentationSearch', 'enabled', true);
$response = $c->handleRequest(new SS_HTTPRequest('GET', 'description'), DataModel::inst());
$this->assertEquals(200, $response->getStatusCode());

View File

@ -1,112 +0,0 @@
<?php
/**
* @package docsviewer
* @subpackage tests
*/
class DocumentationServiceTest extends SapphireTest {
function testGetPagesFromFolder() {
$entity = DocumentationService::register('testdocs', DOCSVIEWER_PATH . '/tests/docs/');
$pages = DocumentationService::get_pages_from_folder($entity);
$this->assertContains('index.md', $pages->column('Filename'), 'The tests/docs/en folder should contain a index file');
$this->assertContains('subfolder/', $pages->column('Filename'), 'The tests/docs/en folder should contain a subfolder called subfolder');
$this->assertContains('test.md', $pages->column('Filename'), 'The tests/docs/en folder should contain a test file');
$this->assertNotContains('_images', $pages->column('Filename'), 'It should not include hidden files');
$this->assertNotContains('.svn', $pages->column('Filename'), 'It should not include hidden files');
// test the order of pages
$pages = DocumentationService::get_pages_from_folder($entity, 'sort');
$this->assertEquals(
array('Basic', 'Intermediate', 'Advanced', 'Some page', 'Another page'),
$pages->column('Title')
);
}
function testGetPagesFromFolderRecursive() {
$entity = DocumentationService::register('testdocsrecursive', DOCSVIEWER_PATH . '/tests/docs-recursive/');
$pages = DocumentationService::get_pages_from_folder($entity, null, true);
// check to see all the pages are found, we don't care about order
$this->assertEquals($pages->Count(), 9);
$pages = $pages->column('Title');
foreach(array('Index', 'SubFolder TestFile', 'SubSubFolder TestFile', 'TestFile') as $expected) {
$this->assertContains($expected, $pages);
}
}
function testFindPath() {
DocumentationService::register("DocumentationViewerTests", DOCSVIEWER_PATH . "/tests/docs/");
// file
$path = DocumentationService::find_page('DocumentationViewerTests', array('test'));
$this->assertEquals(DOCSVIEWER_PATH . "/tests/docs/en/test.md", $path);
// the home page. The path finder should go to the index.md file in the default language
$path = DocumentationService::find_page('DocumentationViewerTests', array(''));
$this->assertEquals(DOCSVIEWER_PATH . "/tests/docs/en/index.md", $path);
// second level
$path = DocumentationService::find_page('DocumentationViewerTests', array('subfolder', 'subpage'));
$this->assertEquals(DOCSVIEWER_PATH . "/tests/docs/en/subfolder/subpage.md", $path);
// subsubfolder has no index file. It should fail instead the viewer should pick up on this
// and display the listing of the folder
$path = DocumentationService::find_page('DocumentationViewerTests', array('subfolder', 'subsubfolder'));
$this->assertFalse($path);
// third level
$path = DocumentationService::find_page('DocumentationViewerTests', array('subfolder', 'subsubfolder', 'subsubpage'));
$this->assertEquals(DOCSVIEWER_PATH . "/tests/docs/en/subfolder/subsubfolder/subsubpage.md", $path);
// with trailing slash
$path = DocumentationService::find_page('DocumentationViewerTests', array('subfolder', 'subsubfolder', 'subsubpage'));
$this->assertEquals(DOCSVIEWER_PATH . "/tests/docs/en/subfolder/subsubfolder/subsubpage.md", $path);
}
function testCleanPageNames() {
$names = array(
'documentation-Page',
'documentation_Page',
'documentation.md',
'documentation.pdf',
'documentation.file.txt',
'.hidden'
);
$should = array(
'Documentation Page',
'Documentation Page',
'Documentation',
'Documentation.pdf', // do not remove an extension we don't know
'Documentation.file', // .txt we do know about
'.hidden' // don't display something without a title
);
foreach($names as $key => $value) {
$this->assertEquals(DocumentationService::clean_page_name($value), $should[$key]);
}
}
function testIsValidExtension() {
$this->assertTrue(DocumentationService::is_valid_extension('md'));
$this->assertTrue(DocumentationService::is_valid_extension('markdown'));
$this->assertTrue(DocumentationService::is_valid_extension('MD'));
$this->assertTrue(DocumentationService::is_valid_extension('MARKDOWN'));
$this->assertFalse(DocumentationService::is_valid_extension('.markd'));
$this->assertFalse(DocumentationService::is_valid_extension('.exe'));
// require an extension as internally we check for extension, not using
// one could cause issues.
$this->assertFalse(DocumentationService::is_valid_extension(''));
}
}

View File

@ -11,239 +11,191 @@
class DocumentationViewerTest extends FunctionalTest {
protected $autoFollowRedirection = false;
public function setUpOnce() {
parent::setUpOnce();
$this->origEnabled = DocumentationService::automatic_registration_enabled();
DocumentationService::set_automatic_registration(false);
$this->origModules = DocumentationService::get_registered_entities();
$this->origLinkBase = Config::inst()->get('DocumentationViewer', 'link_base');
Config::inst()->update('DocumentationViewer', 'link_base', 'dev/docs/');
protected $manifest;
foreach($this->origModules as $module) {
DocumentationService::unregister($module->getFolder());
}
// We set 3.0 as current, and test most assertions against 2.4 - to avoid 'current' rewriting issues
DocumentationService::register("DocumentationViewerTests", DOCSVIEWER_PATH . "/tests/docs/", '2.3');
DocumentationService::register("DocumentationViewerTests", DOCSVIEWER_PATH . "/tests/docs-v2.4/", '2.4', 'Doc Test', true);
DocumentationService::register("DocumentationViewerTests", DOCSVIEWER_PATH . "/tests/docs-v3.0/", '3.0', 'Doc Test');
DocumentationService::register("DocumentationViewerAltModule1", DOCSVIEWER_PATH . "/tests/docs-parser/", '1.0');
DocumentationService::register("DocumentationViewerAltModule2", DOCSVIEWER_PATH . "/tests/docs-search/", '1.0');
public function setUp() {
parent::setUp();
Config::nest();
// explicitly use dev/docs. Custom paths should be tested separately
Config::inst()->update(
'DocumentationViewer', 'link_base', 'dev/docs/'
);
// disable automatic module registration so modules don't interfere.
Config::inst()->update(
'DocumentationManifest', 'automatic_registration', false
);
Config::inst()->remove('DocumentationManifest', 'register_entities');
Config::inst()->update(
'DocumentationManifest', 'register_entities', array(
array(
'Path' => DOCSVIEWER_PATH . "/tests/docs/",
'Title' => 'Doc Test',
'Version' => '2.3'
),
array(
'Path' => DOCSVIEWER_PATH . "/tests/docs-v2.4/",
'Title' => 'Doc Test',
'Version' => '2.4',
'Stable' => true
),
array(
'Path' => DOCSVIEWER_PATH . "/tests/docs-v3.0/",
'Title' => 'Doc Test',
'Version' => '3.0'
),
array(
'Path' => DOCSVIEWER_PATH . "/tests/docs-parser/",
'Title' => 'DocumentationViewerAltModule1'
),
array(
'Path' => DOCSVIEWER_PATH . "/tests/docs-search/",
'Title' => 'DocumentationViewerAltModule2'
)
)
);
$this->manifest = new DocumentationManifest(true);
}
public function tearDownOnce() {
parent::tearDownOnce();
public function tearDown() {
parent::tearDown();
DocumentationService::unregister("DocumentationViewerTests");
DocumentationService::set_automatic_registration($this->origEnabled);
Config::inst()->update('DocumentationViewer', 'link_base', $this->origLinkBase);
Config::unnest();
}
public function testGetMenu() {
$v = new DocumentationViewer();
// check with children
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'en/doc_test/2.3/'), DataModel::inst());
$expected = array(
'dev/docs/en/doc_test/2.3/sort/' => 'Sort',
'dev/docs/en/doc_test/2.3/subfolder/' => 'Subfolder',
'dev/docs/en/doc_test/2.3/test/' => 'Test'
);
$actual = $v->getMenu()->first()->Children->map('Link', 'Title');
$this->assertEquals($expected, $actual);
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'en/doc_test/'), DataModel::inst());
$this->assertEquals('current', $v->getMenu()->first()->LinkingMode);
// 2.4 stable release has 1 child page (not including index)
$this->assertEquals(1, $v->getMenu()->first()->Children->count());
// menu should contain all the english entities
$expected = array(
'dev/docs/en/doc_test/' => 'Doc Test',
'dev/docs/en/documentationvieweraltmodule1/' => 'DocumentationViewerAltModule1',
'dev/docs/en/documentationvieweraltmodule2/' => 'DocumentationViewerAltModule2'
);
$this->assertEquals($expected, $v->getMenu()->map('Link', 'Title'));
}
/**
* This tests that all the locations will exist if we access it via the urls.
*/
function testLocationsExists() {
$response = $this->get('dev/docs/DocumentationViewerTests/en/2.3/subfolder');
public function testLocationsExists() {
$this->autoFollowRedirection = false;
$response = $this->get('dev/docs/en/');
$this->assertEquals($response->getStatusCode(), 200, 'Lists the home index');
$response = $this->get('dev/docs/');
$this->assertEquals($response->getStatusCode(), 302, 'Go to english view');
$response = $this->get('dev/docs/en/doc_test/2.3/subfolder/');
$this->assertEquals($response->getStatusCode(), 200, 'Existing base folder');
$response = $this->get('dev/docs/DocumentationViewerTests/en/2.4');
$response = $this->get('dev/docs/en/doc_test/3.0/empty.md');
$this->assertEquals(301, $response->getStatusCode(), 'Direct markdown links also work. They should redirect to /empty/');
// 2.4 is the stable release. Not in the URL
$response = $this->get('dev/docs/en/doc_test/');
$this->assertEquals($response->getStatusCode(), 200, 'Existing base folder');
$this->assertContains('english test', $response->getBody(), 'Toplevel content page');
$response = $this->get('dev/docs/DocumentationViewerTests/en/2.4/');
// accessing 2.4 is redirects to the version without the version number.
$response = $this->get('dev/docs/en/doc_test/2.4/');
$this->assertEquals($response->getStatusCode(), 404, 'Existing base folder redirects to without version');
$response = $this->get('dev/docs/en/doc_test/3.0/');
$this->assertEquals($response->getStatusCode(), 200, 'Existing base folder');
$response = $this->get('dev/docs/DocumentationViewerTests/en/2.3/nonexistant-subfolder');
$response = $this->get('dev/docs/en/doc_test/2.3/nonexistant-subfolder');
$this->assertEquals($response->getStatusCode(), 404, 'Nonexistant subfolder');
$response = $this->get('dev/docs/DocumentationViewerTests/en/2.3/nonexistant-file.txt');
$response = $this->get('dev/docs/en/doc_test/2.3/nonexistant-file.txt');
$this->assertEquals($response->getStatusCode(), 301, 'Nonexistant file');
$response = $this->get('dev/docs/en/doc_test/2.3/nonexistant-file/');
$this->assertEquals($response->getStatusCode(), 404, 'Nonexistant file');
$response = $this->get('dev/docs/DocumentationViewerTests/en/2.3/test');
$response = $this->get('dev/docs/en/doc_test/2.3/test');
$this->assertEquals($response->getStatusCode(), 200, 'Existing file');
$response = $this->get('dev/docs/DocumentationViewerTests/en/3.0/empty?foo');
$response = $this->get('dev/docs/en/doc_test/3.0/empty?foo');
$this->assertEquals(200, $response->getStatusCode(), 'Existing page');
$response = $this->get('dev/docs/en/doc_test/3.0/empty/');
$this->assertEquals($response->getStatusCode(), 200, 'Existing page');
$response = $this->get('dev/docs/DocumentationViewerTests/en/3.0/empty.md');
$this->assertEquals($response->getStatusCode(), 200, 'Existing page');
$response = $this->get('dev/docs/DocumentationViewerTests/en/3.0/empty/');
$this->assertEquals($response->getStatusCode(), 200, 'Existing page');
$response = $this->get('dev/docs/DocumentationViewerTests/en/3.0/test');
$response = $this->get('dev/docs/en/doc_test/3.0/test');
$this->assertEquals($response->getStatusCode(), 404, 'Missing page');
$response = $this->get('dev/docs/DocumentationViewerTests/en/3.0/test.md');
$response = $this->get('dev/docs/en/doc_test/3.0/test.md');
$this->assertEquals($response->getStatusCode(), 301, 'Missing page');
$response = $this->get('dev/docs/en/doc_test/3.0/test/');
$this->assertEquals($response->getStatusCode(), 404, 'Missing page');
$response = $this->get('dev/docs/DocumentationViewerTests/en/3.0/test/');
$this->assertEquals($response->getStatusCode(), 404, 'Missing page');
$response = $this->get('dev/docs/en');
$this->assertEquals($response->getStatusCode(), 404, 'Must include a module');
$response = $this->get('dev/docs/DocumentationViewerTests/dk/');;
$response = $this->get('dev/docs/dk/');
$this->assertEquals($response->getStatusCode(), 404, 'Access a language that doesn\'t exist');
}
function testRouting() {
$response = $this->get('dev/docs/DocumentationViewerTests/en/2.4');
public function testAccessingAll() {
$response = $this->get('dev/docs/en/all/');
// should response with the documentation index
$this->assertEquals(200, $response->getStatusCode());
$this->assertContains('english test', $response->getBody(), 'Toplevel content page');
$response = $this->get('dev/docs/DocumentationViewerTests/en/2.4/');
$items = $this->cssParser()->getBySelector('#documentation_index');
$this->assertNotEmpty($items);
// should also have a DE version of the page
$response = $this->get('dev/docs/de/all/');
// should response with the documentation index
$this->assertEquals(200, $response->getStatusCode());
$this->assertContains('english test', $response->getBody(), 'Toplevel content page');
$response = $this->get('dev/docs/DocumentationViewerTests/en/2.4/index.md');
$this->assertEquals(200, $response->getStatusCode());
$this->assertContains('english test', $response->getBody(), 'Toplevel content page');
}
function testGetModulePagesShort() {
$v = new DocumentationViewer();
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'DocumentationViewerTests/en/2.3/subfolder/'), DataModel::inst());
$pages = $v->getEntityPages();
$this->assertEquals(
$pages->column('Title'),
array('Sort', 'Subfolder', 'Test')
);
}
function testGetEntityPages() {
$v = new DocumentationViewer();
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'DocumentationViewerTests/en/2.3/subfolder/'), DataModel::inst());
$pages = $v->getEntityPages();
$this->assertEquals(
array('sort/', 'subfolder/', 'test.md'),
$pages->column('Filename')
);
$this->assertEquals(
array('link','current', 'link'),
$pages->column('LinkingMode')
);
foreach($pages as $page) {
$page->setVersion('2.3');
}
$links = $pages->column('Link');
$this->assertStringEndsWith('DocumentationViewerTests/en/2.3/sort/', $links[0]);
$this->assertStringEndsWith('DocumentationViewerTests/en/2.3/subfolder/', $links[1]);
$this->assertStringEndsWith('DocumentationViewerTests/en/2.3/test', $links[2]);
$pageSort = $pages->find('Title', 'Sort');
$this->assertFalse($pageSort->Children);
$pageSubfolder = $pages->find('Title', 'Subfolder');
$this->assertEquals(
array('subfolder/subpage.md', 'subfolder/subsubfolder/'),
$pageSubfolder->Children->column('Filename')
);
$children = $pageSubfolder->Children;
foreach($children as $child) {
$child->setVersion('2.3');
}
$child2Links = $children->column('Link');
$this->assertStringEndsWith('DocumentationViewerTests/en/2.3/subfolder/subpage', $child2Links[0]);
$this->assertStringEndsWith('DocumentationViewerTests/en/2.3/subfolder/subsubfolder/', $child2Links[1]);
}
function testUrlParsing() {
// Module index
$v = new DocumentationViewer();
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'DocumentationViewerTests/en/2.3/test'), DataModel::inst());
$this->assertEquals('2.3', $v->getVersion());
$this->assertEquals('en', $v->getLang());
$this->assertEquals('DocumentationViewerTests', $v->getEntity()->getTitle());
$this->assertEquals(array('test'), $v->Remaining);
// Module index without version and language. Should pick up the defaults
$v2 = new DocumentationViewer();
$response = $v2->handleRequest(new SS_HTTPRequest('GET', 'DocumentationViewerTests/en/test'), DataModel::inst());
$this->assertEquals('2.4', $v2->getVersion());
$this->assertEquals('en', $v2->getLang());
$this->assertEquals('DocumentationViewerTests', $v2->getEntity()->getTitle());
$this->assertEquals(array('test'), $v2->Remaining);
// Overall index
$v = new DocumentationViewer();
$response = $v->handleRequest(new SS_HTTPRequest('GET', ''), DataModel::inst());
$this->assertEquals('', $v->getVersion());
$this->assertEquals('en', $v->getLang());
$this->assertEquals('', $v->module);
$this->assertEquals(array(), $v->Remaining);
}
function testBreadcrumbs() {
// Module index
$v = new DocumentationViewer();
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'DocumentationViewerTests/en/2.4'), DataModel::inst());
$crumbs = $v->getBreadcrumbs();
$this->assertEquals(1, $crumbs->Count());
// Subfolder index
$v = new DocumentationViewer();
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'DocumentationViewerTests/en/2.4/subfolder/'), DataModel::inst());
$crumbs = $v->getBreadcrumbs();
$this->assertEquals(2, $crumbs->Count());
// Subfolder page
$v = new DocumentationViewer();
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'DocumentationViewerTests/en/2.4/subfolder/subpage'), DataModel::inst());
$crumbs = $v->getBreadcrumbs();
$this->assertEquals(3, $crumbs->Count());
}
function testGetVersion() {
$v = new DocumentationViewer();
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'DocumentationViewerTests/en/2.4'), DataModel::inst());
$this->assertEquals('2.4', $v->getVersion());
$items = $this->cssParser()->getBySelector('#documentation_index');
$this->assertNotEmpty($items);
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'DocumentationViewerTests/en/1'), DataModel::inst());
$this->assertEquals('1', $v->getVersion());
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'DocumentationViewerTests/en/3.0'), DataModel::inst());
$this->assertEquals('3.0', $v->getVersion());
}
function testGetEntities() {
$v = new DocumentationViewer();
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'DocumentationViewerTests/en/2.4'), DataModel::inst());
$pages = $v->getEntities();
$this->assertEquals(3, $pages->Count(), 'Registered 3 entities');
// check to see the links don't have version or pages in them
foreach($pages as $page) {
$expected = Controller::join_links('docs', $page->Title, 'en');
$this->assertStringEndsWith($expected, $page->Link);
}
}
// accessing a language that doesn't exist should throw a 404
$response = $this->get('dev/docs/fu/all/');
$this->assertEquals(404, $response->getStatusCode());
// accessing all without a language should fail
$response = $this->get('dev/docs/all/');
$this->assertEquals(404, $response->getStatusCode());
/**
* Test that the pages comes back sorted by filename
*/
function testGetEntityPagesSortedByFilename() {
$v = new DocumentationViewer();
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'DocumentationViewerTests/en/3.0/'), DataModel::inst());
$pages = $v->getEntityPages();
$links = $pages->column('Link');
$this->assertStringEndsWith('DocumentationViewerTests/en/3.0/ChangeLog', $links[0]);
$this->assertStringEndsWith('DocumentationViewerTests/en/3.0/Tutorials', $links[1]);
$this->assertStringEndsWith('DocumentationViewerTests/en/3.0/empty', $links[2]);
}
}

View File

@ -1,57 +1,83 @@
<?php
/**
* @package docsviewer
* @subpackage tests
*/
class DocumentationViewerVersionWarningTest extends SapphireTest {
protected $autoFollowRedirection = false;
public function setUpOnce() {
parent::setUpOnce();
$this->origEnabled = DocumentationService::automatic_registration_enabled();
DocumentationService::set_automatic_registration(false);
$this->origModules = DocumentationService::get_registered_entities();
$this->origLinkBase = Config::inst()->get('DocumentationViewer', 'link_base');
Config::inst()->update('DocumentationViewer', 'link_base', 'dev/docs/');
private $manifest;
foreach($this->origModules as $module) {
DocumentationService::unregister($module->getFolder());
}
// We set 3.0 as current, and test most assertions against 2.4 - to avoid 'current' rewriting issues
DocumentationService::register("DocumentationViewerTests", DOCSVIEWER_PATH . "/tests/docs/", '2.3');
DocumentationService::register("DocumentationViewerTests", DOCSVIEWER_PATH . "/tests/docs-v2.4/", '2.4', 'Doc Test', true);
DocumentationService::register("DocumentationViewerTests", DOCSVIEWER_PATH . "/tests/docs-v3.0/", '3.0', 'Doc Test');
DocumentationService::register("DocumentationViewerAltModule1", DOCSVIEWER_PATH . "/tests/docs-parser/", '1.0');
DocumentationService::register("DocumentationViewerAltModule2", DOCSVIEWER_PATH . "/tests/docs-search/", '1.0');
public function setUp() {
parent::setUp();
Config::nest();
// explicitly use dev/docs. Custom paths should be tested separately
Config::inst()->update(
'DocumentationViewer', 'link_base', 'dev/docs'
);
// disable automatic module registration so modules don't interfere.
Config::inst()->update(
'DocumentationManifest', 'automatic_registration', false
);
Config::inst()->remove('DocumentationManifest', 'register_entities');
Config::inst()->update(
'DocumentationManifest', 'register_entities', array(
array(
'Path' => DOCSVIEWER_PATH . "/tests/docs/",
'Title' => 'Doc Test',
'Key' => 'testdocs',
'Version' => '2.3'
),
array(
'Path' => DOCSVIEWER_PATH . "/tests/docs-v2.4/",
'Title' => 'Doc Test',
'Version' => '2.4',
'Key' => 'testdocs',
'Stable' => true
),
array(
'Path' => DOCSVIEWER_PATH . "/tests/docs-v3.0/",
'Title' => 'Doc Test',
'Key' => 'testdocs',
'Version' => '3.0'
)
)
);
$this->manifest = new DocumentationManifest(true);
}
public function tearDownOnce() {
parent::tearDownOnce();
public function tearDown() {
parent::tearDown();
DocumentationService::unregister("DocumentationViewerTests");
DocumentationService::set_automatic_registration($this->origEnabled);
Config::unnest();
Config::inst()->update('DocumentationViewer', 'link_base', $this->origLinkBase);
}
public function testVersionWarning() {
$v = new DocumentationViewer();
// the current version is set to 2.4, no notice should be shown on that page
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'DocumentationViewerTests/en/2.4'), DataModel::inst());
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'en/testdocs/'), DataModel::inst());
$this->assertFalse($v->VersionWarning());
// 2.3 is an older release, hitting that should return us an outdated flag
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'DocumentationViewerTests/en/2.3'), DataModel::inst());
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'en/testdocs/2.3/'), DataModel::inst());
$warn = $v->VersionWarning();
$this->assertTrue($warn->OutdatedRelease);
$this->assertNull($warn->FutureRelease);
// 3.0 is a future release
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'DocumentationViewerTests/en/3.0'), DataModel::inst());
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'en/testdocs/3.0/'), DataModel::inst());
$warn = $v->VersionWarning();
$this->assertNull($warn->OutdatedRelease);

View File

@ -9,7 +9,6 @@ test
[link: with anchor](/test#anchor)
[link: http](http://silverstripe.org)
[link: api](api:DataObject)
[link: relative](../a-relative-file.md)
[api:DataObject::$has_one]