Converted to PSR-2

This commit is contained in:
helpfulrobot 2015-11-21 19:25:41 +13:00
parent 2f0cbfe4f6
commit ced8128190
26 changed files with 4223 additions and 4033 deletions

View File

@ -5,86 +5,91 @@
* *
* @package docsviewer * @package docsviewer
*/ */
class DocumentationHelper { class DocumentationHelper
{
/**
* String helper for cleaning a file name to a readable version.
*
* @param string $name to convert
*
* @return string $name output
*/
public static function clean_page_name($name)
{
$name = self::trim_extension_off($name);
$name = self::trim_sort_number($name);
/** $name = str_replace(array('-', '_'), ' ', $name);
* String helper for cleaning a file name to a readable version.
*
* @param string $name to convert
*
* @return string $name output
*/
public static function clean_page_name($name) {
$name = self::trim_extension_off($name);
$name = self::trim_sort_number($name);
$name = str_replace(array('-', '_'), ' ', $name); return ucfirst(trim($name));
}
return ucfirst(trim($name)); /**
} * String helper for cleaning a file name to a URL safe version.
*
* @param string $name to convert
*
* @return string $name output
*/
public static function clean_page_url($name)
{
$name = str_replace(array(' '), '_', $name);
/** $name = self::trim_extension_off($name);
* String helper for cleaning a file name to a URL safe version. $name = self::trim_sort_number($name);
*
* @param string $name to convert
*
* @return string $name output
*/
public static function clean_page_url($name) {
$name = str_replace(array(' '), '_', $name);
$name = self::trim_extension_off($name); if (preg_match('/^[\/]?index[\/]?/', $name)) {
$name = self::trim_sort_number($name); return '';
}
if(preg_match('/^[\/]?index[\/]?/', $name)) { return strtolower($name);
return ''; }
}
return strtolower($name); /**
} * Removes leading numbers from pages (used to control sort order).
*
* @param string
*
* @return string
*/
public static function trim_sort_number($name)
{
$name = preg_replace("/^[0-9]*[_-]+/", '', $name);
/** return $name;
* Removes leading numbers from pages (used to control sort order). }
*
* @param string
*
* @return string
*/
public static function trim_sort_number($name) {
$name = preg_replace("/^[0-9]*[_-]+/", '', $name);
return $name;
}
/** /**
* Helper function to strip the extension off and return the name without * Helper function to strip the extension off and return the name without
* the extension. * the extension.
* *
* @param string * @param string
* *
* @return string * @return string
*/ */
public static function trim_extension_off($name) { public static function trim_extension_off($name)
if(strrpos($name,'.') !== false) { {
return substr($name, 0, strrpos($name,'.')); if (strrpos($name, '.') !== false) {
} return substr($name, 0, strrpos($name, '.'));
}
return $name; return $name;
} }
/** /**
* Helper function to get the extension of the filename. * Helper function to get the extension of the filename.
* *
* @param string * @param string
* *
* @return string * @return string
*/ */
public static function get_extension($name) { public static function get_extension($name)
if(preg_match('/\.[a-z]+$/', $name)) { {
return substr($name, strrpos($name,'.') + 1); if (preg_match('/\.[a-z]+$/', $name)) {
} return substr($name, strrpos($name, '.') + 1);
}
return null; return null;
} }
} }

View File

@ -27,715 +27,737 @@
* @package framework * @package framework
* @subpackage manifest * @subpackage manifest
*/ */
class DocumentationManifest { class DocumentationManifest
{
/** /**
* @config * @config
* *
* @var boolean $automatic_registration * @var boolean $automatic_registration
*/ */
private static $automatic_registration = true; private static $automatic_registration = true;
/** /**
* @config * @config
* *
* @var array $registered_entities * @var array $registered_entities
*/ */
private static $register_entities = array(); private static $register_entities = array();
protected $cache; protected $cache;
protected $cacheKey; protected $cacheKey;
protected $inited; protected $inited;
protected $forceRegen; protected $forceRegen;
/** /**
* @var array $pages * @var array $pages
*/ */
protected $pages = array(); protected $pages = array();
protected $redirects = array(); protected $redirects = array();
/** /**
* @var DocumentationEntity * @var DocumentationEntity
*/ */
private $entity; private $entity;
/** /**
* @var boolean * @var boolean
*/ */
private $automaticallyPopulated = false; private $automaticallyPopulated = false;
/** /**
* @var ArrayList * @var ArrayList
*/ */
private $registeredEntities; private $registeredEntities;
/** /**
* Constructs a new template manifest. The manifest is not actually built * Constructs a new template manifest. The manifest is not actually built
* or loaded from cache until needed. * or loaded from cache until needed.
* *
* @param bool $includeTests Include tests in the manifest. * @param bool $includeTests Include tests in the manifest.
* @param bool $forceRegen Force the manifest to be regenerated. * @param bool $forceRegen Force the manifest to be regenerated.
*/ */
public function __construct($forceRegen = false) { public function __construct($forceRegen = false)
$this->cacheKey = 'manifest'; {
$this->forceRegen = $forceRegen; $this->cacheKey = 'manifest';
$this->registeredEntities = new ArrayList(); $this->forceRegen = $forceRegen;
$this->registeredEntities = new ArrayList();
$this->cache = SS_Cache::factory('DocumentationManifest', 'Core', array(
'automatic_serialization' => true, $this->cache = SS_Cache::factory('DocumentationManifest', 'Core', array(
'lifetime' => null 'automatic_serialization' => true,
)); 'lifetime' => null
));
$this->setupEntities();
} $this->setupEntities();
}
/**
* Sets up the top level entities. /**
* * Sets up the top level entities.
* Either manually registered through the YAML syntax or automatically *
* loaded through investigating the file system for `docs` folder. * Either manually registered through the YAML syntax or automatically
*/ * loaded through investigating the file system for `docs` folder.
public function setupEntities() { */
if($this->registeredEntities->Count() > 0) { public function setupEntities()
return; {
} if ($this->registeredEntities->Count() > 0) {
return;
if(Config::inst()->get('DocumentationManifest', 'automatic_registration')) { }
$this->populateEntitiesFromInstall();
} if (Config::inst()->get('DocumentationManifest', 'automatic_registration')) {
$this->populateEntitiesFromInstall();
$registered = Config::inst()->get('DocumentationManifest', 'register_entities'); }
foreach($registered as $details) { $registered = Config::inst()->get('DocumentationManifest', 'register_entities');
// validate the details provided through the YAML configuration
$required = array('Path', 'Title'); foreach ($registered as $details) {
// validate the details provided through the YAML configuration
foreach($required as $require) { $required = array('Path', 'Title');
if(!isset($details[$require])) {
throw new Exception("$require is a required key in DocumentationManifest.register_entities"); foreach ($required as $require) {
} if (!isset($details[$require])) {
} throw new Exception("$require is a required key in DocumentationManifest.register_entities");
}
// if path is not an absolute value then assume it is relative from }
// the BASE_PATH.
$path = $this->getRealPath($details['Path']); // if path is not an absolute value then assume it is relative from
// the BASE_PATH.
$key = (isset($details['Key'])) ? $details['Key'] : $details['Title']; $path = $this->getRealPath($details['Path']);
if(!$path || !is_dir($path)) { $key = (isset($details['Key'])) ? $details['Key'] : $details['Title'];
throw new Exception($details['Path'] . ' is not a valid documentation directory');
} if (!$path || !is_dir($path)) {
throw new Exception($details['Path'] . ' is not a valid documentation directory');
$version = (isset($details['Version'])) ? $details['Version'] : ''; }
$branch = (isset($details['Branch'])) ? $details['Branch'] : ''; $version = (isset($details['Version'])) ? $details['Version'] : '';
$langs = scandir($path); $branch = (isset($details['Branch'])) ? $details['Branch'] : '';
if($langs) { $langs = scandir($path);
$possible = i18n::get_common_languages(true);
if ($langs) {
foreach($langs as $k => $lang) { $possible = i18n::get_common_languages(true);
if(isset($possible[$lang])) {
$entity = Injector::inst()->create( foreach ($langs as $k => $lang) {
'DocumentationEntity', $key if (isset($possible[$lang])) {
); $entity = Injector::inst()->create(
'DocumentationEntity', $key
$entity->setPath(Controller::join_links($path, $lang, '/')); );
$entity->setTitle($details['Title']);
$entity->setLanguage($lang); $entity->setPath(Controller::join_links($path, $lang, '/'));
$entity->setVersion($version); $entity->setTitle($details['Title']);
$entity->setBranch($branch); $entity->setLanguage($lang);
$entity->setVersion($version);
if(isset($details['Stable'])) { $entity->setBranch($branch);
$entity->setIsStable($details['Stable']);
} if (isset($details['Stable'])) {
$entity->setIsStable($details['Stable']);
if(isset($details['DefaultEntity'])) { }
$entity->setIsDefaultEntity($details['DefaultEntity']);
} if (isset($details['DefaultEntity'])) {
$entity->setIsDefaultEntity($details['DefaultEntity']);
$this->registeredEntities->push($entity); }
}
} $this->registeredEntities->push($entity);
} }
} }
} }
}
public function getRealPath($path) { }
if(substr($path, 0, 1) != '/') {
$path = Controller::join_links(BASE_PATH, $path); public function getRealPath($path)
} {
if (substr($path, 0, 1) != '/') {
return $path; $path = Controller::join_links(BASE_PATH, $path);
} }
/** return $path;
* @return ArrayList }
*/
public function getEntities() { /**
return $this->registeredEntities; * @return ArrayList
} */
public function getEntities()
/** {
* Scans the current installation and picks up all the SilverStripe modules return $this->registeredEntities;
* that contain a `docs` folder. }
*
* @return void /**
*/ * Scans the current installation and picks up all the SilverStripe modules
public function populateEntitiesFromInstall() { * that contain a `docs` folder.
if($this->automaticallyPopulated) { *
// already run * @return void
return; */
} public function populateEntitiesFromInstall()
{
foreach(scandir(BASE_PATH) as $key => $entity) { if ($this->automaticallyPopulated) {
if($key == "themes") { // already run
continue; return;
} }
$dir = Controller::join_links(BASE_PATH, $entity); foreach (scandir(BASE_PATH) as $key => $entity) {
if ($key == "themes") {
if(is_dir($dir)) { continue;
// check to see if it has docs }
$docs = Controller::join_links($dir, 'docs');
$dir = Controller::join_links(BASE_PATH, $entity);
if(is_dir($docs)) {
$entities[] = array( if (is_dir($dir)) {
'Path' => $docs, // check to see if it has docs
'Title' => DocumentationHelper::clean_page_name($entity), $docs = Controller::join_links($dir, 'docs');
'Version' => 'master',
'Branch' => 'master', if (is_dir($docs)) {
'Stable' => true $entities[] = array(
); 'Path' => $docs,
} 'Title' => DocumentationHelper::clean_page_name($entity),
} 'Version' => 'master',
} 'Branch' => 'master',
'Stable' => true
Config::inst()->update( );
'DocumentationManifest', 'register_entities', $entities }
); }
}
$this->automaticallyPopulated = true;
} Config::inst()->update(
'DocumentationManifest', 'register_entities', $entities
/** );
*
*/ $this->automaticallyPopulated = true;
protected function init() { }
if (!$this->forceRegen && $data = $this->cache->load($this->cacheKey)) {
$this->pages = $data['pages']; /**
$this->redirects = $data['redirects']; *
$this->inited = true; */
} else { protected function init()
$this->regenerate(); {
} if (!$this->forceRegen && $data = $this->cache->load($this->cacheKey)) {
} $this->pages = $data['pages'];
$this->redirects = $data['redirects'];
$this->inited = true;
/** } else {
* Returns a map of all documentation pages. $this->regenerate();
* }
* @return array }
*/
public function getPages() {
if (!$this->inited) { /**
$this->init(); * Returns a map of all documentation pages.
} *
* @return array
return $this->pages; */
} public function getPages()
{
public function getRedirects() { if (!$this->inited) {
if(!$this->inited) { $this->init();
$this->init(); }
}
return $this->pages;
return $this->redirects; }
}
public function getRedirects()
/** {
* Returns a particular page for the requested URL. if (!$this->inited) {
* $this->init();
* @return DocumentationPage }
*/
public function getPage($url) { return $this->redirects;
$pages = $this->getPages(); }
$url = $this->normalizeUrl($url);
/**
if(!isset($pages[$url])) { * Returns a particular page for the requested URL.
return null; *
} * @return DocumentationPage
*/
public function getPage($url)
$record = $pages[$url]; {
$pages = $this->getPages();
foreach($this->getEntities() as $entity) { $url = $this->normalizeUrl($url);
if(strpos($record['filepath'], $entity->getPath()) !== false) {
$page = Injector::inst()->create( if (!isset($pages[$url])) {
$record['type'], return null;
$entity, }
$record['basename'],
$record['filepath']
); $record = $pages[$url];
return $page; foreach ($this->getEntities() as $entity) {
} if (strpos($record['filepath'], $entity->getPath()) !== false) {
} $page = Injector::inst()->create(
} $record['type'],
$entity,
/** $record['basename'],
* Get any redirect for the given url $record['filepath']
* );
* @param type $url
* @return string return $page;
*/ }
public function getRedirect($url) { }
$pages = $this->getRedirects(); }
$url = $this->normalizeUrl($url);
/**
if(isset($pages[$url])) { * Get any redirect for the given url
return $pages[$url]; *
} * @param type $url
} * @return string
*/
/** public function getRedirect($url)
* Regenerates the manifest by scanning the base path. {
* $pages = $this->getRedirects();
* @param bool $cache $url = $this->normalizeUrl($url);
*/
public function regenerate($cache = true) { if (isset($pages[$url])) {
$finder = new DocumentationManifestFileFinder(); return $pages[$url];
$finder->setOptions(array( }
'dir_callback' => array($this, 'handleFolder'), }
'file_callback' => array($this, 'handleFile')
)); /**
* Regenerates the manifest by scanning the base path.
$this->redirects = array(); *
foreach($this->getEntities() as $entity) { * @param bool $cache
$this->entity = $entity; */
public function regenerate($cache = true)
$this->handleFolder('', $this->entity->getPath(), 0); {
$finder->find($this->entity->getPath()); $finder = new DocumentationManifestFileFinder();
} $finder->setOptions(array(
'dir_callback' => array($this, 'handleFolder'),
// groupds 'file_callback' => array($this, 'handleFile')
$grouped = array(); ));
foreach($this->pages as $url => $page) { $this->redirects = array();
if(!isset($grouped[$page['entitypath']])) { foreach ($this->getEntities() as $entity) {
$grouped[$page['entitypath']] = array(); $this->entity = $entity;
}
$this->handleFolder('', $this->entity->getPath(), 0);
$grouped[$page['entitypath']][$url] = $page; $finder->find($this->entity->getPath());
} }
$this->pages = array(); // groupds
$grouped = array();
foreach($grouped as $entity) {
uasort($entity, function($a, $b) { foreach ($this->pages as $url => $page) {
// ensure parent directories are first if (!isset($grouped[$page['entitypath']])) {
$a['filepath'] = str_replace('index.md', '', $a['filepath']); $grouped[$page['entitypath']] = array();
$b['filepath'] = str_replace('index.md', '', $b['filepath']); }
if(strpos($b['filepath'], $a['filepath']) === 0) { $grouped[$page['entitypath']][$url] = $page;
return -1; }
}
$this->pages = array();
if ($a['filepath'] == $b['filepath']) {
return 0; foreach ($grouped as $entity) {
} uasort($entity, function ($a, $b) {
// ensure parent directories are first
return ($a['filepath'] < $b['filepath']) ? -1 : 1; $a['filepath'] = str_replace('index.md', '', $a['filepath']);
}); $b['filepath'] = str_replace('index.md', '', $b['filepath']);
$this->pages = array_merge($this->pages, $entity); if (strpos($b['filepath'], $a['filepath']) === 0) {
} return -1;
}
if ($cache) {
$this->cache->save( if ($a['filepath'] == $b['filepath']) {
array( return 0;
'pages' => $this->pages, }
'redirects' => $this->redirects
), return ($a['filepath'] < $b['filepath']) ? -1 : 1;
$this->cacheKey });
);
} $this->pages = array_merge($this->pages, $entity);
}
$this->inited = true;
} if ($cache) {
$this->cache->save(
/** array(
* Remove the link_base from the start of a link 'pages' => $this->pages,
* 'redirects' => $this->redirects
* @param string $link ),
* @return string $this->cacheKey
*/ );
protected function stripLinkBase($link) { }
return ltrim(str_replace(
Config::inst()->get('DocumentationViewer', 'link_base'), $this->inited = true;
'', }
$link
), '/'); /**
} * Remove the link_base from the start of a link
*
/** * @param string $link
* * @return string
* @param DocumentationPage $page */
* @param string $basename protected function stripLinkBase($link)
* @param string $path {
*/ return ltrim(str_replace(
protected function addPage($page, $basename, $path) { Config::inst()->get('DocumentationViewer', 'link_base'),
$link = $this->stripLinkBase($page->Link()); '',
$link
$this->pages[$link] = array( ), '/');
'title' => $page->getTitle(), }
'basename' => $basename,
'filepath' => $path, /**
'type' => get_class($page), *
'entitypath' => $this->entity->getPath(), * @param DocumentationPage $page
'summary' => $page->getSummary() * @param string $basename
); * @param string $path
} */
protected function addPage($page, $basename, $path)
/** {
* Add a redirect $link = $this->stripLinkBase($page->Link());
*
* @param string $from $this->pages[$link] = array(
* @param string $to 'title' => $page->getTitle(),
*/ 'basename' => $basename,
protected function addRedirect($from, $to) { 'filepath' => $path,
$fromLink = $this->stripLinkBase($from); 'type' => get_class($page),
$toLink = $this->stripLinkBase($to); 'entitypath' => $this->entity->getPath(),
$this->redirects[$fromLink] = $toLink; 'summary' => $page->getSummary()
} );
}
/**
* /**
*/ * Add a redirect
public function handleFolder($basename, $path, $depth) { *
$folder = Injector::inst()->create( * @param string $from
'DocumentationFolder', $this->entity, $basename, $path * @param string $to
); */
protected function addRedirect($from, $to)
// Add main folder link {
$fullLink = $folder->Link(); $fromLink = $this->stripLinkBase($from);
$this->addPage($folder, $basename, $path); $toLink = $this->stripLinkBase($to);
$this->redirects[$fromLink] = $toLink;
// Add alternative link }
$shortLink = $folder->Link(true);
if($shortLink != $fullLink) { /**
$this->addRedirect($shortLink, $fullLink); *
} */
} public function handleFolder($basename, $path, $depth)
{
/** $folder = Injector::inst()->create(
* Individual files can optionally provide a nice title and a better URL 'DocumentationFolder', $this->entity, $basename, $path
* through the use of markdown meta data. This creates a new );
* {@link DocumentationPage} instance for the file.
* // Add main folder link
* If the markdown does not specify the title in the meta data it falls back $fullLink = $folder->Link();
* to using the file name. $this->addPage($folder, $basename, $path);
*
* @param string $basename // Add alternative link
* @param string $path $shortLink = $folder->Link(true);
* @param int $depth if ($shortLink != $fullLink) {
*/ $this->addRedirect($shortLink, $fullLink);
public function handleFile($basename, $path, $depth) { }
$page = Injector::inst()->create( }
'DocumentationPage',
$this->entity, $basename, $path /**
); * Individual files can optionally provide a nice title and a better URL
* through the use of markdown meta data. This creates a new
// populate any meta data * {@link DocumentationPage} instance for the file.
$page->getMarkdown(); *
* If the markdown does not specify the title in the meta data it falls back
// Add main link * to using the file name.
$fullLink = $page->Link(); *
$this->addPage($page, $basename, $path); * @param string $basename
* @param string $path
// If this is a stable version, add the short link * @param int $depth
$shortLink = $page->Link(true); */
if($fullLink != $shortLink) { public function handleFile($basename, $path, $depth)
$this->addRedirect($shortLink, $fullLink); {
} $page = Injector::inst()->create(
} 'DocumentationPage',
$this->entity, $basename, $path
/** );
* Generate an {@link ArrayList} of the pages to the given page.
* // populate any meta data
* @param DocumentationPage $page->getMarkdown();
* @param DocumentationEntityLanguage
* // Add main link
* @return ArrayList $fullLink = $page->Link();
*/ $this->addPage($page, $basename, $path);
public function generateBreadcrumbs($record, $base) {
$output = new ArrayList(); // If this is a stable version, add the short link
$shortLink = $page->Link(true);
$parts = explode('/', trim($record->getRelativeLink(), '/')); if ($fullLink != $shortLink) {
$this->addRedirect($shortLink, $fullLink);
// Add the base link. }
$output->push(new ArrayData(array( }
'Link' => $base->Link(),
'Title' => $base->Title /**
))); * Generate an {@link ArrayList} of the pages to the given page.
*
$progress = $base->Link(); * @param DocumentationPage
* @param DocumentationEntityLanguage
foreach($parts as $part) { *
if($part) { * @return ArrayList
$progress = Controller::join_links($progress, $part, '/'); */
public function generateBreadcrumbs($record, $base)
$output->push(new ArrayData(array( {
'Link' => $progress, $output = new ArrayList();
'Title' => DocumentationHelper::clean_page_name($part)
))); $parts = explode('/', trim($record->getRelativeLink(), '/'));
}
} // Add the base link.
$output->push(new ArrayData(array(
return $output; 'Link' => $base->Link(),
} 'Title' => $base->Title
)));
/**
* Determine the next page from the given page. $progress = $base->Link();
*
* Relies on the fact when the manifest was built, it was generated in foreach ($parts as $part) {
* order. if ($part) {
* $progress = Controller::join_links($progress, $part, '/');
* @param string $filepath
* @param string $entityBase $output->push(new ArrayData(array(
* 'Link' => $progress,
* @return ArrayData 'Title' => DocumentationHelper::clean_page_name($part)
*/ )));
public function getNextPage($filepath, $entityBase) { }
$grabNext = false; }
$fallback = null;
return $output;
foreach($this->getPages() as $url => $page) { }
if($grabNext && strpos($page['filepath'], $entityBase) !== false) {
return new ArrayData(array( /**
'Link' => $url, * Determine the next page from the given page.
'Title' => $page['title'] *
)); * Relies on the fact when the manifest was built, it was generated in
} * order.
*
if($filepath == $page['filepath']) { * @param string $filepath
$grabNext = true; * @param string $entityBase
} else if(!$fallback && strpos($page['filepath'], $filepath) !== false) { *
$fallback = new ArrayData(array( * @return ArrayData
'Link' => $url, */
'Title' => $page['title'], public function getNextPage($filepath, $entityBase)
'Fallback' => true {
)); $grabNext = false;
} $fallback = null;
}
foreach ($this->getPages() as $url => $page) {
if(!$grabNext) { if ($grabNext && strpos($page['filepath'], $entityBase) !== false) {
return $fallback; return new ArrayData(array(
} 'Link' => $url,
'Title' => $page['title']
return null; ));
} }
/** if ($filepath == $page['filepath']) {
* Determine the previous page from the given page. $grabNext = true;
* } elseif (!$fallback && strpos($page['filepath'], $filepath) !== false) {
* Relies on the fact when the manifest was built, it was generated in $fallback = new ArrayData(array(
* order. 'Link' => $url,
* 'Title' => $page['title'],
* @param string $filepath 'Fallback' => true
* @param string $entityBase ));
* }
* @return ArrayData }
*/
public function getPreviousPage($filepath, $entityPath) { if (!$grabNext) {
$previousUrl = $previousPage = null; return $fallback;
}
foreach($this->getPages() as $url => $page) {
if($filepath == $page['filepath']) { return null;
if($previousUrl) { }
return new ArrayData(array(
'Link' => $previousUrl, /**
'Title' => $previousPage['title'] * Determine the previous page from the given page.
)); *
} * Relies on the fact when the manifest was built, it was generated in
} * order.
*
if(strpos($page['filepath'], $entityPath) !== false) { * @param string $filepath
$previousUrl = $url; * @param string $entityBase
$previousPage = $page; *
} * @return ArrayData
} */
public function getPreviousPage($filepath, $entityPath)
return null; {
} $previousUrl = $previousPage = null;
/** foreach ($this->getPages() as $url => $page) {
* @param string if ($filepath == $page['filepath']) {
* if ($previousUrl) {
* @return string return new ArrayData(array(
*/ 'Link' => $previousUrl,
public function normalizeUrl($url) { 'Title' => $previousPage['title']
$url = trim($url, '/') .'/'; ));
}
// if the page is the index page then hide it from the menu }
if(strpos(strtolower($url), '/index.md/')) {
$url = substr($url, 0, strpos($url, "index.md/")); if (strpos($page['filepath'], $entityPath) !== false) {
} $previousUrl = $url;
$previousPage = $page;
return $url; }
} }
/** return null;
* Return the children of the provided record path. }
*
* Looks for any pages in the manifest which have one more slash attached. /**
* * @param string
* @param string $path *
* * @return string
* @return ArrayList */
*/ public function normalizeUrl($url)
public function getChildrenFor($entityPath, $recordPath = null) { {
if(!$recordPath) { $url = trim($url, '/') .'/';
$recordPath = $entityPath;
} // if the page is the index page then hide it from the menu
if (strpos(strtolower($url), '/index.md/')) {
$output = new ArrayList(); $url = substr($url, 0, strpos($url, "index.md/"));
$base = Config::inst()->get('DocumentationViewer', 'link_base'); }
$entityPath = $this->normalizeUrl($entityPath);
$recordPath = $this->normalizeUrl($recordPath); return $url;
$recordParts = explode(DIRECTORY_SEPARATOR, trim($recordPath,'/')); }
$currentRecordPath = end($recordParts);
$depth = substr_count($entityPath, '/'); /**
* Return the children of the provided record path.
foreach($this->getPages() as $url => $page) { *
$pagePath = $this->normalizeUrl($page['filepath']); * Looks for any pages in the manifest which have one more slash attached.
*
// check to see if this page is under the given path * @param string $path
if(strpos($pagePath, $entityPath) === false) { *
continue; * @return ArrayList
} */
public function getChildrenFor($entityPath, $recordPath = null)
// only pull it up if it's one more level depth {
if(substr_count($pagePath, DIRECTORY_SEPARATOR) == ($depth + 1)) { if (!$recordPath) {
$pagePathParts = explode(DIRECTORY_SEPARATOR, trim($pagePath,'/')); $recordPath = $entityPath;
$currentPagePath = end($pagePathParts); }
if($currentPagePath == $currentRecordPath) {
$mode = 'current'; $output = new ArrayList();
} $base = Config::inst()->get('DocumentationViewer', 'link_base');
else if(strpos($recordPath, $pagePath) !== false) { $entityPath = $this->normalizeUrl($entityPath);
$mode = 'section'; $recordPath = $this->normalizeUrl($recordPath);
} $recordParts = explode(DIRECTORY_SEPARATOR, trim($recordPath, '/'));
else { $currentRecordPath = end($recordParts);
$mode = 'link'; $depth = substr_count($entityPath, '/');
}
foreach ($this->getPages() as $url => $page) {
$children = new ArrayList(); $pagePath = $this->normalizeUrl($page['filepath']);
if($mode == 'section' || $mode == 'current') { // check to see if this page is under the given path
$children = $this->getChildrenFor($pagePath, $recordPath); if (strpos($pagePath, $entityPath) === false) {
} continue;
}
$output->push(new ArrayData(array(
'Link' => Controller::join_links($base, $url, '/'), // only pull it up if it's one more level depth
'Title' => $page['title'], if (substr_count($pagePath, DIRECTORY_SEPARATOR) == ($depth + 1)) {
'LinkingMode' => $mode, $pagePathParts = explode(DIRECTORY_SEPARATOR, trim($pagePath, '/'));
'Summary' => $page['summary'], $currentPagePath = end($pagePathParts);
'Children' => $children if ($currentPagePath == $currentRecordPath) {
))); $mode = 'current';
} } elseif (strpos($recordPath, $pagePath) !== false) {
} $mode = 'section';
} else {
return $output; $mode = 'link';
} }
/** $children = new ArrayList();
* @param DocumentationEntity
* if ($mode == 'section' || $mode == 'current') {
* @return ArrayList $children = $this->getChildrenFor($pagePath, $recordPath);
*/ }
public function getAllVersionsOfEntity(DocumentationEntity $entity) {
$all = new ArrayList(); $output->push(new ArrayData(array(
'Link' => Controller::join_links($base, $url, '/'),
foreach($this->getEntities() as $check) { 'Title' => $page['title'],
if($check->getKey() == $entity->getKey()) { 'LinkingMode' => $mode,
if($check->getLanguage() == $entity->getLanguage()) { 'Summary' => $page['summary'],
$all->push($check); 'Children' => $children
} )));
} }
} }
return $all; return $output;
} }
/** /**
* @param DocumentationEntity * @param DocumentationEntity
* *
* @return DocumentationEntity * @return ArrayList
*/ */
public function getStableVersion(DocumentationEntity $entity) { public function getAllVersionsOfEntity(DocumentationEntity $entity)
foreach($this->getEntities() as $check) { {
if($check->getKey() == $entity->getKey()) { $all = new ArrayList();
if($check->getLanguage() == $entity->getLanguage()) {
if($check->getIsStable()) { foreach ($this->getEntities() as $check) {
return $check; if ($check->getKey() == $entity->getKey()) {
} if ($check->getLanguage() == $entity->getLanguage()) {
} $all->push($check);
} }
} }
}
return $entity;
} return $all;
}
/**
* @param DocumentationEntity /**
* * @param DocumentationEntity
* @return ArrayList *
*/ * @return DocumentationEntity
public function getVersions($entity) { */
if(!$entity) { public function getStableVersion(DocumentationEntity $entity)
return null; {
} foreach ($this->getEntities() as $check) {
if ($check->getKey() == $entity->getKey()) {
$output = new ArrayList(); if ($check->getLanguage() == $entity->getLanguage()) {
if ($check->getIsStable()) {
foreach($this->getEntities() as $check) { return $check;
if($check->getKey() == $entity->getKey()) { }
if($check->getLanguage() == $entity->getLanguage()) { }
$same = ($check->getVersion() == $entity->getVersion()); }
}
$output->push(new ArrayData(array(
'Title' => $check->getVersion(), return $entity;
'Link' => $check->Link(), }
'LinkingMode' => ($same) ? 'current' : 'link',
'IsStable' => $check->getIsStable() /**
))); * @param DocumentationEntity
*
} * @return ArrayList
} */
} public function getVersions($entity)
{
return $output; if (!$entity) {
} return null;
}
/**
* Returns a sorted array of all the unique versions registered $output = new ArrayList();
*/
public function getAllVersions() { foreach ($this->getEntities() as $check) {
$versions = array(); if ($check->getKey() == $entity->getKey()) {
if ($check->getLanguage() == $entity->getLanguage()) {
foreach($this->getEntities() as $entity) { $same = ($check->getVersion() == $entity->getVersion());
if($entity->getVersion()) {
$versions[$entity->getVersion()] = $entity->getVersion(); $output->push(new ArrayData(array(
} else { 'Title' => $check->getVersion(),
$versions['0.0'] = _t('DocumentationManifest.MASTER', 'Master'); 'Link' => $check->Link(),
} 'LinkingMode' => ($same) ? 'current' : 'link',
} 'IsStable' => $check->getIsStable()
)));
asort($versions); }
}
return $versions; }
}
return $output;
}
/**
* Returns a sorted array of all the unique versions registered
*/
public function getAllVersions()
{
$versions = array();
foreach ($this->getEntities() as $entity) {
if ($entity->getVersion()) {
$versions[$entity->getVersion()] = $entity->getVersion();
} else {
$versions['0.0'] = _t('DocumentationManifest.MASTER', 'Master');
}
}
asort($versions);
return $versions;
}
} }

View File

@ -1,38 +1,38 @@
<?php <?php
class DocumentationManifestFileFinder extends SS_FileFinder { class DocumentationManifestFileFinder extends SS_FileFinder
{
/**
* @var array
*/
private static $ignored_files = array(
'.', '..', '.ds_store',
'.svn', '.git', 'assets', 'themes', '_images'
);
/** /**
* @var array * @var array
*/ */
private static $ignored_files = array( protected static $default_options = array(
'.', '..', '.ds_store', 'name_regex' => '/\.(md|markdown)$/i',
'.svn', '.git', 'assets', 'themes', '_images' 'file_callback' => null,
); 'dir_callback' => null,
'ignore_vcs' => true
);
/** /**
* @var array *
*/ */
protected static $default_options = array( public function acceptDir($basename, $pathname, $depth)
'name_regex' => '/\.(md|markdown)$/i', {
'file_callback' => null, $ignored = Config::inst()->get('DocumentationManifestFileFinder', 'ignored_files');
'dir_callback' => null,
'ignore_vcs' => true
);
/** if ($ignored) {
* if (in_array(strtolower($basename), $ignored)) {
*/ return false;
public function acceptDir($basename, $pathname, $depth) { }
$ignored = Config::inst()->get('DocumentationManifestFileFinder', 'ignored_files'); }
if($ignored) {
if(in_array(strtolower($basename), $ignored)) {
return false;
}
}
return true;
}
return true;
}
} }

View File

@ -7,201 +7,219 @@
* *
* @package docsviewer * @package docsviewer
*/ */
class DocumentationParser { class DocumentationParser
{
const CODE_BLOCK_BACKTICK = 1;
const CODE_BLOCK_COLON = 2;
const CODE_BLOCK_BACKTICK = 1; /**
const CODE_BLOCK_COLON = 2; * @var string Rewriting of api links in the format "[api:MyClass]" or "[api:MyClass::$my_property]".
*/
public static $api_link_base = 'http://api.silverstripe.org/search/lookup/?q=%s&amp;version=%s&amp;module=%s';
/** /**
* @var string Rewriting of api links in the format "[api:MyClass]" or "[api:MyClass::$my_property]". * @var array
*/ */
public static $api_link_base = 'http://api.silverstripe.org/search/lookup/?q=%s&amp;version=%s&amp;module=%s'; public static $heading_counts = array();
/** /**
* @var array * Parse a given path to the documentation for a file. Performs a case
*/ * insensitive lookup on the file system. Automatically appends the file
public static $heading_counts = array(); * extension to one of the markdown extensions as well so /install/ in a
* web browser will match /install.md or /INSTALL.md.
*
* Filepath: /var/www/myproject/src/cms/en/folder/subfolder/page.md
* URL: http://myhost/mywebroot/dev/docs/2.4/cms/en/folder/subfolder/page
* Webroot: http://myhost/mywebroot/
* Baselink: dev/docs/2.4/cms/en/
* Pathparts: folder/subfolder/page
*
* @param DocumentationPage $page
* @param String $baselink Link relative to webroot, up until the "root"
* of the module. Necessary to rewrite relative
* links
*
* @return String
*/
public static function parse(DocumentationPage $page, $baselink = null)
{
if (!$page || (!$page instanceof DocumentationPage)) {
return false;
}
/** $md = $page->getMarkdown(true);
* Parse a given path to the documentation for a file. Performs a case
* insensitive lookup on the file system. Automatically appends the file
* extension to one of the markdown extensions as well so /install/ in a
* web browser will match /install.md or /INSTALL.md.
*
* Filepath: /var/www/myproject/src/cms/en/folder/subfolder/page.md
* URL: http://myhost/mywebroot/dev/docs/2.4/cms/en/folder/subfolder/page
* Webroot: http://myhost/mywebroot/
* Baselink: dev/docs/2.4/cms/en/
* Pathparts: folder/subfolder/page
*
* @param DocumentationPage $page
* @param String $baselink Link relative to webroot, up until the "root"
* of the module. Necessary to rewrite relative
* links
*
* @return String
*/
public static function parse(DocumentationPage $page, $baselink = null) {
if(!$page || (!$page instanceof DocumentationPage)) {
return false;
}
$md = $page->getMarkdown(true); // Pre-processing
$md = self::rewrite_image_links($md, $page);
$md = self::rewrite_relative_links($md, $page, $baselink);
// Pre-processing $md = self::rewrite_api_links($md, $page);
$md = self::rewrite_image_links($md, $page); $md = self::rewrite_heading_anchors($md, $page);
$md = self::rewrite_relative_links($md, $page, $baselink);
$md = self::rewrite_api_links($md, $page); $md = self::rewrite_code_blocks($md);
$md = self::rewrite_heading_anchors($md, $page);
$md = self::rewrite_code_blocks($md); $parser = new ParsedownExtra();
$parser->setBreaksEnabled(false);
$parser = new ParsedownExtra(); $text = $parser->text($md);
$parser->setBreaksEnabled(false);
$text = $parser->text($md); return $text;
}
return $text; public static function rewrite_code_blocks($md)
} {
$started = false;
$inner = false;
$mode = false;
$end = false;
$debug = false;
public static function rewrite_code_blocks($md) { $lines = explode("\n", $md);
$started = false; $output = array();
$inner = false;
$mode = false;
$end = false;
$debug = false;
$lines = explode("\n", $md); foreach ($lines as $i => $line) {
$output = array(); if ($debug) {
var_dump('Line '. ($i + 1) . ' '. $line);
}
foreach($lines as $i => $line) { // if line just contains whitespace, continue down the page.
if($debug) var_dump('Line '. ($i + 1) . ' '. $line); // Prevents code blocks with leading tabs adding an extra line.
if (preg_match('/^\s$/', $line) && !$started) {
continue;
}
// if line just contains whitespace, continue down the page. if (!$started && preg_match('/^[\t]*:::\s*(.*)/', $line, $matches)) {
// Prevents code blocks with leading tabs adding an extra line. // first line with custom formatting
if(preg_match('/^\s$/', $line) && !$started) { if ($debug) {
continue; var_dump('Starts a new block with :::');
} }
if(!$started && preg_match('/^[\t]*:::\s*(.*)/', $line, $matches)) { $started = true;
// first line with custom formatting $mode = self::CODE_BLOCK_COLON;
if($debug) var_dump('Starts a new block with :::');
$started = true; $output[$i] = sprintf('```%s', (isset($matches[1])) ? trim($matches[1]) : "");
$mode = self::CODE_BLOCK_COLON; } elseif (!$started && preg_match('/^\t*```\s*(.*)/', $line, $matches)) {
if ($debug) {
var_dump('Starts a new block with ```');
}
$output[$i] = sprintf('```%s', (isset($matches[1])) ? trim($matches[1]) : ""); $started = true;
$mode = self::CODE_BLOCK_BACKTICK;
} else if(!$started && preg_match('/^\t*```\s*(.*)/', $line, $matches)) { $output[$i] = sprintf('```%s', (isset($matches[1])) ? trim($matches[1]) : "");
if($debug) var_dump('Starts a new block with ```'); } elseif ($started && $mode == self::CODE_BLOCK_BACKTICK) {
// inside a backtick fenced box
if (preg_match('/^\t*```\s*/', $line, $matches)) {
if ($debug) {
var_dump('End a block with ```');
}
$started = true; // end of the backtick fenced box. Unset the line that contains the backticks
$mode = self::CODE_BLOCK_BACKTICK; $end = true;
} else {
if ($debug) {
var_dump('Still in a block with ```');
}
$output[$i] = sprintf('```%s', (isset($matches[1])) ? trim($matches[1]) : ""); // still inside the line.
} else if($started && $mode == self::CODE_BLOCK_BACKTICK) { if (!$started) {
// inside a backtick fenced box $output[$i - 1] = '```';
if(preg_match('/^\t*```\s*/', $line, $matches)) { }
if($debug) var_dump('End a block with ```');
// end of the backtick fenced box. Unset the line that contains the backticks $output[$i] = $line;
$end = true; $inner = true;
} }
else { } elseif (preg_match('/^[\ ]{0,3}?[\t](.*)/', $line, $matches)) {
if($debug) var_dump('Still in a block with ```');
// still inside the line. // inner line of block, or first line of standard markdown code block
if(!$started) { // regex removes first tab (any following tabs are part of the code).
$output[$i - 1] = '```'; if (!$started) {
} if ($debug) {
var_dump('Start code block because of tab. No fence');
}
$output[$i] = $line; $output[$i - 1] = '```';
$inner = true; } else {
} if ($debug) {
} else if(preg_match('/^[\ ]{0,3}?[\t](.*)/', $line, $matches)) { var_dump('Content is still tabbed so still inner');
}
}
// inner line of block, or first line of standard markdown code block $output[$i] = $matches[1];
// regex removes first tab (any following tabs are part of the code). $inner = true;
if(!$started) { $started = true;
if($debug) var_dump('Start code block because of tab. No fence'); } elseif ($started && $inner && trim($line) === "") {
if ($debug) {
var_dump('Inner line of code block');
}
$output[$i - 1] = '```'; // still inside a colon based block, if the line is only whitespace
} else { // then continue with with it. We can continue with it for now as
if($debug) var_dump('Content is still tabbed so still inner'); // it'll be tidied up later in the $end section.
} $inner = true;
$output[$i] = $line;
} elseif ($started && $inner) {
// line contains something other than whitespace, or tabbed. E.g
// > code
// > \n
// > some message
//
// So actually want to reset $i to the line before this new line
// and include this line. The edge case where this will fail is
// new the following segment contains a code block as well as it
// will not open.
if ($debug) {
var_dump('Contains something that isnt code. So end the code.');
}
$output[$i] = $matches[1]; $end = true;
$inner = true; $output[$i] = $line;
$started = true; $i = $i - 1;
} else if($started && $inner && trim($line) === "") { } else {
if($debug) var_dump('Inner line of code block'); $output[$i] = $line;
}
// still inside a colon based block, if the line is only whitespace if ($end) {
// then continue with with it. We can continue with it for now as if ($debug) {
// it'll be tidied up later in the $end section. var_dump('End of code block');
$inner = true; }
$output[$i] = $line; $output = self::finalize_code_output($i, $output);
} else if($started && $inner) {
// line contains something other than whitespace, or tabbed. E.g
// > code
// > \n
// > some message
//
// So actually want to reset $i to the line before this new line
// and include this line. The edge case where this will fail is
// new the following segment contains a code block as well as it
// will not open.
if($debug) {
var_dump('Contains something that isnt code. So end the code.');
}
$end = true; // reset state
$output[$i] = $line; $started = $inner = $mode = $end = false;
$i = $i - 1; }
} else { }
$output[$i] = $line;
}
if($end) { if ($started) {
if($debug) var_dump('End of code block'); $output = self::finalize_code_output($i+1, $output);
$output = self::finalize_code_output($i, $output); }
// reset state return implode("\n", $output);
$started = $inner = $mode = $end = false; }
}
}
if($started) { /**
$output = self::finalize_code_output($i+1, $output); * Adds the closing code backticks. Removes trailing whitespace.
} *
* @param int
* @param array
*
* @return array
*/
private static function finalize_code_output($i, $output)
{
if (isset($output[$i]) && trim($output[$i])) {
$output[$i] .= "\n```\n";
} else {
$output[$i] = "```";
}
return implode("\n", $output); return $output;
}
} public static function rewrite_image_links($md, $page)
{
/** // Links with titles
* Adds the closing code backticks. Removes trailing whitespace. $re = '/
*
* @param int
* @param array
*
* @return array
*/
private static function finalize_code_output($i, $output) {
if(isset($output[$i]) && trim($output[$i])) {
$output[$i] .= "\n```\n";
}
else {
$output[$i] = "```";
}
return $output;
}
public static function rewrite_image_links($md, $page) {
// Links with titles
$re = '/
! !
\[ \[
(.*?) # image title (non greedy) (.*?) # image title (non greedy)
@ -210,77 +228,78 @@ class DocumentationParser {
(.*?) # image url (non greedy) (.*?) # image url (non greedy)
\) \)
/x'; /x';
preg_match_all($re, $md, $images); preg_match_all($re, $md, $images);
if($images) { if ($images) {
foreach($images[0] as $i => $match) { foreach ($images[0] as $i => $match) {
$title = $images[1][$i]; $title = $images[1][$i];
$url = $images[2][$i]; $url = $images[2][$i];
// Don't process absolute links (based on protocol detection) // Don't process absolute links (based on protocol detection)
$urlParts = parse_url($url); $urlParts = parse_url($url);
if($urlParts && isset($urlParts['scheme'])) { if ($urlParts && isset($urlParts['scheme'])) {
continue; continue;
} }
// Rewrite URL (relative or absolute) // Rewrite URL (relative or absolute)
$baselink = Director::makeRelative( $baselink = Director::makeRelative(
dirname($page->getPath()) dirname($page->getPath())
); );
// if the image starts with a slash, it's absolute // if the image starts with a slash, it's absolute
if(substr($url, 0, 1) == '/') { if (substr($url, 0, 1) == '/') {
$relativeUrl = str_replace(BASE_PATH, '', Controller::join_links( $relativeUrl = str_replace(BASE_PATH, '', Controller::join_links(
$page->getEntity()->getPath(), $page->getEntity()->getPath(),
$url $url
)); ));
} else { } else {
$relativeUrl = rtrim($baselink, '/') . '/' . ltrim($url, '/'); $relativeUrl = rtrim($baselink, '/') . '/' . ltrim($url, '/');
} }
// Resolve relative paths // Resolve relative paths
while(strpos($relativeUrl, '/..') !== FALSE) { while (strpos($relativeUrl, '/..') !== false) {
$relativeUrl = preg_replace('/\w+\/\.\.\//', '', $relativeUrl); $relativeUrl = preg_replace('/\w+\/\.\.\//', '', $relativeUrl);
} }
// Make it absolute again // Make it absolute again
$absoluteUrl = Controller::join_links( $absoluteUrl = Controller::join_links(
Director::absoluteBaseURL(), Director::absoluteBaseURL(),
$relativeUrl $relativeUrl
); );
// Replace any double slashes (apart from protocol) // Replace any double slashes (apart from protocol)
// $absoluteUrl = preg_replace('/([^:])\/{2,}/', '$1/', $absoluteUrl); // $absoluteUrl = preg_replace('/([^:])\/{2,}/', '$1/', $absoluteUrl);
// Replace in original content // Replace in original content
$md = str_replace( $md = str_replace(
$match, $match,
sprintf('![%s](%s)', $title, $absoluteUrl), sprintf('![%s](%s)', $title, $absoluteUrl),
$md $md
); );
} }
} }
return $md; return $md;
} }
/** /**
* Rewrite links with special "api:" prefix, from two possible formats: * Rewrite links with special "api:" prefix, from two possible formats:
* 1. [api:DataObject] * 1. [api:DataObject]
* 2. (My Title)(api:DataObject) * 2. (My Title)(api:DataObject)
* *
* Hack: Replaces any backticks with "<code>" blocks, * Hack: Replaces any backticks with "<code>" blocks,
* as the currently used markdown parser doesn't resolve links in backticks, * as the currently used markdown parser doesn't resolve links in backticks,
* but does resolve in "<code>" blocks. * but does resolve in "<code>" blocks.
* *
* @param String $md * @param String $md
* @param DocumentationPage $page * @param DocumentationPage $page
* @return String * @return String
*/ */
public static function rewrite_api_links($md, $page) { public static function rewrite_api_links($md, $page)
// Links with titles {
$re = '/ // Links with titles
$re = '/
`? `?
\[ \[
(.*?) # link title (non greedy) (.*?) # link title (non greedy)
@ -290,116 +309,121 @@ class DocumentationParser {
\) \)
`? `?
/x'; /x';
preg_match_all($re, $md, $linksWithTitles); preg_match_all($re, $md, $linksWithTitles);
if($linksWithTitles) { if ($linksWithTitles) {
foreach($linksWithTitles[0] as $i => $match) { foreach ($linksWithTitles[0] as $i => $match) {
$title = $linksWithTitles[1][$i]; $title = $linksWithTitles[1][$i];
$subject = $linksWithTitles[2][$i]; $subject = $linksWithTitles[2][$i];
$url = sprintf( $url = sprintf(
self::$api_link_base, self::$api_link_base,
urlencode($subject), urlencode($subject),
urlencode($page->getVersion()), urlencode($page->getVersion()),
urlencode($page->getEntity()->getKey()) urlencode($page->getEntity()->getKey())
); );
$md = str_replace( $md = str_replace(
$match, $match,
sprintf('[%s](%s)', $title, $url), sprintf('[%s](%s)', $title, $url),
$md $md
); );
} }
} }
// Bare links // Bare links
$re = '/ $re = '/
`? `?
\[ \[
api:(.*?) api:(.*?)
\] \]
`? `?
/x'; /x';
preg_match_all($re, $md, $links); preg_match_all($re, $md, $links);
if($links) { if ($links) {
foreach($links[0] as $i => $match) { foreach ($links[0] as $i => $match) {
$subject = $links[1][$i]; $subject = $links[1][$i];
$url = sprintf( $url = sprintf(
self::$api_link_base, self::$api_link_base,
$subject, $subject,
$page->getVersion(), $page->getVersion(),
$page->getEntity()->getKey() $page->getEntity()->getKey()
); );
$md = str_replace( $md = str_replace(
$match, $match,
sprintf('[%s](%s)', $subject, $url), sprintf('[%s](%s)', $subject, $url),
$md $md
); );
} }
} }
return $md; return $md;
} }
/** /**
* *
*/ */
public static function rewrite_heading_anchors($md, $page) { public static function rewrite_heading_anchors($md, $page)
$re = '/^\#+(.*)/m'; {
$md = preg_replace_callback($re, array('DocumentationParser', '_rewrite_heading_anchors_callback'), $md); $re = '/^\#+(.*)/m';
$md = preg_replace_callback($re, array('DocumentationParser', '_rewrite_heading_anchors_callback'), $md);
return $md; return $md;
} }
/** /**
* *
*/ */
public static function _rewrite_heading_anchors_callback($matches) { public static function _rewrite_heading_anchors_callback($matches)
$heading = $matches[0]; {
$headingText = $matches[1]; $heading = $matches[0];
$headingText = $matches[1];
if(preg_match('/\{\#.*\}/', $headingText)) return $heading; if (preg_match('/\{\#.*\}/', $headingText)) {
return $heading;
}
if(!isset(self::$heading_counts[$headingText])) { if (!isset(self::$heading_counts[$headingText])) {
self::$heading_counts[$headingText] = 1; self::$heading_counts[$headingText] = 1;
} } else {
else { self::$heading_counts[$headingText]++;
self::$heading_counts[$headingText]++; $headingText .= "-" . self::$heading_counts[$headingText];
$headingText .= "-" . self::$heading_counts[$headingText]; }
}
return sprintf("%s {#%s}", preg_replace('/\n/', '', $heading), self::generate_html_id($headingText)); return sprintf("%s {#%s}", preg_replace('/\n/', '', $heading), self::generate_html_id($headingText));
} }
/** /**
* Generate an html element id from a string * Generate an html element id from a string
* *
* @return String * @return String
*/ */
public static function generate_html_id($title) { public static function generate_html_id($title)
$t = $title; {
$t = str_replace('&amp;','-and-',$t); $t = $title;
$t = str_replace('&','-and-',$t); $t = str_replace('&amp;', '-and-', $t);
$t = preg_replace('/[^A-Za-z0-9]+/','-',$t); $t = str_replace('&', '-and-', $t);
$t = preg_replace('/-+/','-',$t); $t = preg_replace('/[^A-Za-z0-9]+/', '-', $t);
$t = trim($t, '-'); $t = preg_replace('/-+/', '-', $t);
$t = strtolower($t); $t = trim($t, '-');
$t = strtolower($t);
return $t; return $t;
} }
/** /**
* Resolves all relative links within markdown. * Resolves all relative links within markdown.
* *
* @param String $md Markdown content * @param String $md Markdown content
* @param DocumentationPage $page * @param DocumentationPage $page
* *
* @return String Markdown * @return String Markdown
*/ */
public static function rewrite_relative_links($md, $page) { public static function rewrite_relative_links($md, $page)
$baselink = $page->getEntity()->Link(); {
$baselink = $page->getEntity()->Link();
$re = '/ $re = '/
([^\!]?) # exclude image format ([^\!]?) # exclude image format
\[ \[
(.*?) # link title (non greedy) (.*?) # link title (non greedy)
@ -408,100 +432,104 @@ class DocumentationParser {
(.*?) # link url (non greedy) (.*?) # link url (non greedy)
\) \)
/x'; /x';
preg_match_all($re, $md, $matches); preg_match_all($re, $md, $matches);
// relative path (relative to module base folder), without the filename. // relative path (relative to module base folder), without the filename.
// For "sapphire/en/current/topics/templates", this would be "templates" // For "sapphire/en/current/topics/templates", this would be "templates"
$relativePath = dirname($page->getRelativePath()); $relativePath = dirname($page->getRelativePath());
if(strpos($page->getRelativePath(), 'index.md')) { if (strpos($page->getRelativePath(), 'index.md')) {
$relativeLink = $page->getRelativeLink(); $relativeLink = $page->getRelativeLink();
} else { } else {
$relativeLink = dirname($page->getRelativeLink()); $relativeLink = dirname($page->getRelativeLink());
} }
if($relativePath == '.') { if ($relativePath == '.') {
$relativePath = ''; $relativePath = '';
} }
if($relativeLink == ".") { if ($relativeLink == ".") {
$relativeLink = ''; $relativeLink = '';
} }
// file base link // file base link
$fileBaseLink = Director::makeRelative(dirname($page->getPath())); $fileBaseLink = Director::makeRelative(dirname($page->getPath()));
if($matches) { if ($matches) {
foreach($matches[0] as $i => $match) { foreach ($matches[0] as $i => $match) {
$title = $matches[2][$i]; $title = $matches[2][$i];
$url = $matches[3][$i]; $url = $matches[3][$i];
// Don't process API links // Don't process API links
if(preg_match('/^api:/', $url)) continue; if (preg_match('/^api:/', $url)) {
continue;
}
// Don't process absolute links (based on protocol detection) // Don't process absolute links (based on protocol detection)
$urlParts = parse_url($url); $urlParts = parse_url($url);
if($urlParts && isset($urlParts['scheme'])) continue; if ($urlParts && isset($urlParts['scheme'])) {
continue;
}
// for images we need to use the file base path // for images we need to use the file base path
if(preg_match('/_images/', $url)) { if (preg_match('/_images/', $url)) {
$relativeUrl = Controller::join_links( $relativeUrl = Controller::join_links(
Director::absoluteBaseURL(), Director::absoluteBaseURL(),
$fileBaseLink, $fileBaseLink,
$url $url
); );
} } else {
else { // Rewrite public URL
// Rewrite public URL if (preg_match('/^\//', $url)) {
if(preg_match('/^\//', $url)) { // Absolute: Only path to module base
// Absolute: Only path to module base $relativeUrl = Controller::join_links($baselink, $url, '/');
$relativeUrl = Controller::join_links($baselink, $url, '/'); } else {
} else { // Relative: Include path to module base and any folders
// Relative: Include path to module base and any folders $relativeUrl = Controller::join_links($baselink, $relativeLink, $url, '/');
$relativeUrl = Controller::join_links($baselink, $relativeLink, $url, '/'); }
} }
}
// Resolve relative paths // Resolve relative paths
while(strpos($relativeUrl, '..') !== FALSE) { while (strpos($relativeUrl, '..') !== false) {
$relativeUrl = preg_replace('/[-\w]+\/\.\.\//', '', $relativeUrl); $relativeUrl = preg_replace('/[-\w]+\/\.\.\//', '', $relativeUrl);
} }
// Replace any double slashes (apart from protocol) // Replace any double slashes (apart from protocol)
$relativeUrl = preg_replace('/([^:])\/{2,}/', '$1/', $relativeUrl); $relativeUrl = preg_replace('/([^:])\/{2,}/', '$1/', $relativeUrl);
// Replace in original content // Replace in original content
$md = str_replace( $md = str_replace(
$match, $match,
sprintf('%s[%s](%s)', $matches[1][$i], $title, $relativeUrl), sprintf('%s[%s](%s)', $matches[1][$i], $title, $relativeUrl),
$md $md
); );
} }
} }
return $md; return $md;
} }
/** /**
* Strips out the metadata for a page * Strips out the metadata for a page
* *
* @param DocumentationPage * @param DocumentationPage
*/ */
public static function retrieve_meta_data(DocumentationPage &$page) { public static function retrieve_meta_data(DocumentationPage &$page)
if($md = $page->getMarkdown()) { {
$matches = preg_match_all('/ if ($md = $page->getMarkdown()) {
$matches = preg_match_all('/
(?<key>[A-Za-z0-9_-]+): (?<key>[A-Za-z0-9_-]+):
\s* \s*
(?<value>.*) (?<value>.*)
/x', $md, $meta); /x', $md, $meta);
if($matches) { if ($matches) {
foreach($meta['key'] as $index => $key) { foreach ($meta['key'] as $index => $key) {
if(isset($meta['value'][$index])) { if (isset($meta['value'][$index])) {
$page->setMetaData($key, $meta['value'][$index]); $page->setMetaData($key, $meta['value'][$index]);
} }
} }
} }
} }
} }
} }

View File

@ -9,42 +9,43 @@
* @package docsviewer * @package docsviewer
*/ */
class DocumentationPermalinks { class DocumentationPermalinks
{
/**
* @var array
*/
private static $mapping = array();
/** /**
* @var array * Add a mapping of nice short permalinks to a full long path
*/ *
private static $mapping = array(); * <code>
* DocumentationPermalinks::add(array(
* 'debugging' => 'current/en/sapphire/topics/debugging'
* ));
* </code>
*
* Do not need to include the language or the version current as it
* will add it based off the language or version in the session
*
* @param array
*/
public static function add($map = array())
{
if (ArrayLib::is_associative($map)) {
self::$mapping = array_merge(self::$mapping, $map);
} else {
user_error("DocumentationPermalinks::add() requires an associative array", E_USER_ERROR);
}
}
/** /**
* Add a mapping of nice short permalinks to a full long path * Return the location for a given short value.
* *
* <code> * @return string|false
* DocumentationPermalinks::add(array( */
* 'debugging' => 'current/en/sapphire/topics/debugging' public static function map($url)
* )); {
* </code> return (isset(self::$mapping[$url])) ? self::$mapping[$url] : false;
* }
* Do not need to include the language or the version current as it
* will add it based off the language or version in the session
*
* @param array
*/
public static function add($map = array()) {
if(ArrayLib::is_associative($map)) {
self::$mapping = array_merge(self::$mapping, $map);
}
else {
user_error("DocumentationPermalinks::add() requires an associative array", E_USER_ERROR);
}
}
/**
* Return the location for a given short value.
*
* @return string|false
*/
public static function map($url) {
return (isset(self::$mapping[$url])) ? self::$mapping[$url] : false;
}
} }

View File

@ -1,8 +1,8 @@
<?php <?php
set_include_path( set_include_path(
dirname(dirname(__FILE__)) . '/thirdparty/'. PATH_SEPARATOR . dirname(dirname(__FILE__)) . '/thirdparty/'. PATH_SEPARATOR .
get_include_path() get_include_path()
); );
require_once 'Zend/Search/Lucene.php'; require_once 'Zend/Search/Lucene.php';
@ -31,382 +31,406 @@ require_once 'Zend/Search/Lucene.php';
* @package docsviewer * @package docsviewer
*/ */
class DocumentationSearch { class DocumentationSearch
{
/** /**
* @var bool - Is search enabled * @var bool - Is search enabled
*/ */
private static $enabled = false; private static $enabled = false;
/** /**
* @var bool - Is advanced search enabled * @var bool - Is advanced search enabled
*/ */
private static $advanced_search_enabled = true; private static $advanced_search_enabled = true;
/** /**
* @var string - OpenSearch metadata. Please use {@link DocumentationSearch::set_meta_data()} * @var string - OpenSearch metadata. Please use {@link DocumentationSearch::set_meta_data()}
*/ */
private static $meta_data = array(); private static $meta_data = array();
/** /**
* @var Array Regular expression mapped to a "boost factor" for the searched document. * @var Array Regular expression mapped to a "boost factor" for the searched document.
* Defaults to 1.0, lower to decrease relevancy. Requires reindex. * Defaults to 1.0, lower to decrease relevancy. Requires reindex.
* Uses {@link DocumentationPage->getRelativePath()} for comparison. * Uses {@link DocumentationPage->getRelativePath()} for comparison.
*/ */
private static $boost_by_path = array(); private static $boost_by_path = array();
/** /**
* @var ArrayList - Results * @var ArrayList - Results
*/ */
private $results; private $results;
/** /**
* @var int * @var int
*/ */
private $totalResults; private $totalResults;
/** /**
* @var string * @var string
*/ */
private $query; private $query;
/** /**
* @var Controller * @var Controller
*/ */
private $outputController; private $outputController;
/** /**
* Optionally filter by module and version * Optionally filter by module and version
* *
* @var array * @var array
*/ */
private $modules, $versions; private $modules, $versions;
public function setModules($modules) { public function setModules($modules)
$this->modules = $modules; {
} $this->modules = $modules;
}
public function setVersions($versions) {
$this->versions = $versions; public function setVersions($versions)
} {
$this->versions = $versions;
/** }
* Set the current search query
* /**
* @param string * Set the current search query
*/ *
public function setQuery($query) { * @param string
$this->query = $query; */
} public function setQuery($query)
{
/** $this->query = $query;
* Returns the current search query }
*
* @return string /**
*/ * Returns the current search query
public function getQuery() { *
return $this->query; * @return string
} */
public function getQuery()
/** {
* Sets the {@link DocumentationViewer} or {@link DocumentationSearch} instance which this search is rendering return $this->query;
* on based on whether it is the results display or RSS feed }
*
* @param Controller /**
*/ * Sets the {@link DocumentationViewer} or {@link DocumentationSearch} instance which this search is rendering
public function setOutputController($controller) { * on based on whether it is the results display or RSS feed
$this->outputController = $controller; *
} * @param Controller
*/
/** public function setOutputController($controller)
* Folder name for indexes (in the temp folder). {
* $this->outputController = $controller;
* @config }
* @var string
*/ /**
private static $index_location; * Folder name for indexes (in the temp folder).
*
/** * @config
* @return string * @var string
*/ */
public static function get_index_location() { private static $index_location;
$location = Config::inst()->get('DocumentationSearch', 'index_location');
/**
if(!$location) { * @return string
return Controller::join_links(TEMP_FOLDER, 'RebuildLuceneDocsIndex'); */
} public static function get_index_location()
{
return $location; $location = Config::inst()->get('DocumentationSearch', 'index_location');
}
if (!$location) {
/** return Controller::join_links(TEMP_FOLDER, 'RebuildLuceneDocsIndex');
* Perform a search query on the index }
*/
public function performSearch() { return $location;
}
try {
$index = Zend_Search_Lucene::open(self::get_index_location()); /**
* Perform a search query on the index
Zend_Search_Lucene::setResultSetLimit(100); */
public function performSearch()
$query = new Zend_Search_Lucene_Search_Query_Boolean(); {
$term = Zend_Search_Lucene_Search_QueryParser::parse($this->getQuery()); try {
$query->addSubquery($term, true); $index = Zend_Search_Lucene::open(self::get_index_location());
if($this->modules) { Zend_Search_Lucene::setResultSetLimit(100);
$moduleQuery = new Zend_Search_Lucene_Search_Query_MultiTerm();
$query = new Zend_Search_Lucene_Search_Query_Boolean();
foreach($this->modules as $module) { $term = Zend_Search_Lucene_Search_QueryParser::parse($this->getQuery());
$moduleQuery->addTerm(new Zend_Search_Lucene_Index_Term($module, 'Entity')); $query->addSubquery($term, true);
}
if ($this->modules) {
$query->addSubquery($moduleQuery, true); $moduleQuery = new Zend_Search_Lucene_Search_Query_MultiTerm();
}
foreach ($this->modules as $module) {
if($this->versions) { $moduleQuery->addTerm(new Zend_Search_Lucene_Index_Term($module, 'Entity'));
$versionQuery = new Zend_Search_Lucene_Search_Query_MultiTerm(); }
foreach($this->versions as $version) { $query->addSubquery($moduleQuery, true);
$versionQuery->addTerm(new Zend_Search_Lucene_Index_Term($version, 'Version')); }
}
if ($this->versions) {
$query->addSubquery($versionQuery, true); $versionQuery = new Zend_Search_Lucene_Search_Query_MultiTerm();
}
foreach ($this->versions as $version) {
$er = error_reporting(); $versionQuery->addTerm(new Zend_Search_Lucene_Index_Term($version, 'Version'));
error_reporting('E_ALL ^ E_NOTICE'); }
$this->results = $index->find($query);
error_reporting($er); $query->addSubquery($versionQuery, true);
$this->totalResults = $index->numDocs(); }
}
catch(Zend_Search_Lucene_Exception $e) { $er = error_reporting();
user_error($e .'. Ensure you have run the rebuld task (/dev/tasks/RebuildLuceneDocsIndex)', E_USER_ERROR); error_reporting('E_ALL ^ E_NOTICE');
} $this->results = $index->find($query);
} error_reporting($er);
$this->totalResults = $index->numDocs();
/** } catch (Zend_Search_Lucene_Exception $e) {
* @return ArrayData user_error($e .'. Ensure you have run the rebuld task (/dev/tasks/RebuildLuceneDocsIndex)', E_USER_ERROR);
*/ }
public function getSearchResults($request) { }
$pageLength = (isset($_GET['length'])) ? (int) $_GET['length'] : 10;
/**
$data = array( * @return ArrayData
'Results' => null, */
'Query' => null, public function getSearchResults($request)
'Versions' => DBField::create_field('Text', implode(', ', $this->versions)), {
'Modules' => DBField::create_field('Text', implode(', ', $this->modules)), $pageLength = (isset($_GET['length'])) ? (int) $_GET['length'] : 10;
'Title' => _t('DocumentationSearch.SEARCHRESULTS', 'Search Results'),
'TotalResults' => null, $data = array(
'TotalPages' => null, 'Results' => null,
'ThisPage' => null, 'Query' => null,
'StartResult' => null, 'Versions' => DBField::create_field('Text', implode(', ', $this->versions)),
'PageLength' => $pageLength, 'Modules' => DBField::create_field('Text', implode(', ', $this->modules)),
'EndResult' => null, 'Title' => _t('DocumentationSearch.SEARCHRESULTS', 'Search Results'),
'PrevUrl' => DBField::create_field('Text', 'false'), 'TotalResults' => null,
'NextUrl' => DBField::create_field('Text', 'false'), 'TotalPages' => null,
'SearchPages' => new ArrayList() 'ThisPage' => null,
); 'StartResult' => null,
'PageLength' => $pageLength,
$start = ($request->requestVar('start')) ? (int)$request->requestVar('start') : 0; 'EndResult' => null,
$query = ($request->requestVar('q')) ? $request->requestVar('q') : ''; 'PrevUrl' => DBField::create_field('Text', 'false'),
'NextUrl' => DBField::create_field('Text', 'false'),
$currentPage = floor( $start / $pageLength ) + 1; 'SearchPages' => new ArrayList()
);
$totalPages = ceil(count($this->results) / $pageLength );
$start = ($request->requestVar('start')) ? (int)$request->requestVar('start') : 0;
if ($totalPages == 0) { $query = ($request->requestVar('q')) ? $request->requestVar('q') : '';
$totalPages = 1;
} $currentPage = floor($start / $pageLength) + 1;
if ($currentPage > $totalPages) { $totalPages = ceil(count($this->results) / $pageLength);
$currentPage = $totalPages;
} if ($totalPages == 0) {
$totalPages = 1;
$results = new ArrayList(); }
if($this->results) { if ($currentPage > $totalPages) {
foreach($this->results as $k => $hit) { $currentPage = $totalPages;
if($k < ($currentPage-1)*$pageLength || $k >= ($currentPage*$pageLength)) { }
continue;
} $results = new ArrayList();
$doc = $hit->getDocument(); if ($this->results) {
foreach ($this->results as $k => $hit) {
$content = $hit->content; if ($k < ($currentPage-1)*$pageLength || $k >= ($currentPage*$pageLength)) {
continue;
$obj = new ArrayData(array( }
'Title' => DBField::create_field('Varchar', $doc->getFieldValue('Title')),
'BreadcrumbTitle' => DBField::create_field('HTMLText', $doc->getFieldValue('BreadcrumbTitle')), $doc = $hit->getDocument();
'Link' => DBField::create_field('Varchar',$doc->getFieldValue('Link')),
'Language' => DBField::create_field('Varchar',$doc->getFieldValue('Language')), $content = $hit->content;
'Version' => DBField::create_field('Varchar',$doc->getFieldValue('Version')),
'Entity' => DBField::create_field('Varchar', $doc->getFieldValue('Entity')), $obj = new ArrayData(array(
'Content' => DBField::create_field('HTMLText', $content), 'Title' => DBField::create_field('Varchar', $doc->getFieldValue('Title')),
'Score' => $hit->score, 'BreadcrumbTitle' => DBField::create_field('HTMLText', $doc->getFieldValue('BreadcrumbTitle')),
'Number' => $k + 1, 'Link' => DBField::create_field('Varchar', $doc->getFieldValue('Link')),
'ID' => md5($doc->getFieldValue('Link')) 'Language' => DBField::create_field('Varchar', $doc->getFieldValue('Language')),
)); 'Version' => DBField::create_field('Varchar', $doc->getFieldValue('Version')),
'Entity' => DBField::create_field('Varchar', $doc->getFieldValue('Entity')),
$results->push($obj); 'Content' => DBField::create_field('HTMLText', $content),
} 'Score' => $hit->score,
} 'Number' => $k + 1,
'ID' => md5($doc->getFieldValue('Link'))
$data['Results'] = $results; ));
$data['Query'] = DBField::create_field('Text', $query);
$data['TotalResults'] = DBField::create_field('Text', count($this->results)); $results->push($obj);
$data['TotalPages'] = DBField::create_field('Text', $totalPages); }
$data['ThisPage'] = DBField::create_field('Text', $currentPage); }
$data['StartResult'] = $start + 1;
$data['EndResult'] = $start + count($results); $data['Results'] = $results;
$data['Query'] = DBField::create_field('Text', $query);
// Pagination links $data['TotalResults'] = DBField::create_field('Text', count($this->results));
if($currentPage > 1) { $data['TotalPages'] = DBField::create_field('Text', $totalPages);
$data['PrevUrl'] = DBField::create_field('Text', $data['ThisPage'] = DBField::create_field('Text', $currentPage);
$this->buildQueryUrl(array('start' => ($currentPage - 2) * $pageLength)) $data['StartResult'] = $start + 1;
); $data['EndResult'] = $start + count($results);
}
// Pagination links
if($currentPage < $totalPages) { if ($currentPage > 1) {
$data['NextUrl'] = DBField::create_field('Text', $data['PrevUrl'] = DBField::create_field('Text',
$this->buildQueryUrl(array('start' => $currentPage * $pageLength)) $this->buildQueryUrl(array('start' => ($currentPage - 2) * $pageLength))
); );
} }
if($totalPages > 1) { if ($currentPage < $totalPages) {
// Always show a certain number of pages at the start $data['NextUrl'] = DBField::create_field('Text',
for ( $i = 1; $i <= $totalPages; $i++ ) { $this->buildQueryUrl(array('start' => $currentPage * $pageLength))
$obj = new DataObject(); );
$obj->IsEllipsis = false; }
$obj->PageNumber = $i;
$obj->Link = $this->buildQueryUrl(array( if ($totalPages > 1) {
'start' => ($i - 1) * $pageLength // Always show a certain number of pages at the start
)); for ($i = 1; $i <= $totalPages; $i++) {
$obj = new DataObject();
$obj->Current = false; $obj->IsEllipsis = false;
if ( $i == $currentPage ) $obj->Current = true; $obj->PageNumber = $i;
$data['SearchPages']->push($obj); $obj->Link = $this->buildQueryUrl(array(
} 'start' => ($i - 1) * $pageLength
} ));
return new ArrayData($data); $obj->Current = false;
} if ($i == $currentPage) {
$obj->Current = true;
/** }
* Build a nice query string for the results $data['SearchPages']->push($obj);
* }
* @return string }
*/
private function buildQueryUrl($params) { return new ArrayData($data);
$url = parse_url($_SERVER['REQUEST_URI']); }
if ( ! array_key_exists('query', $url) ) $url['query'] = '';
parse_str($url['query'], $url['query']); /**
if ( ! is_array($url['query']) ) $url['query'] = array(); * Build a nice query string for the results
// Remove 'start parameter if it exists *
if ( array_key_exists('start', $url['query']) ) unset( $url['query']['start'] ); * @return string
// Add extra parameters from argument */
$url['query'] = array_merge($url['query'], $params); private function buildQueryUrl($params)
$url['query'] = http_build_query($url['query']); {
$url = $url['path'] . ($url['query'] ? '?'.$url['query'] : ''); $url = parse_url($_SERVER['REQUEST_URI']);
if (! array_key_exists('query', $url)) {
return $url; $url['query'] = '';
} }
parse_str($url['query'], $url['query']);
/** if (! is_array($url['query'])) {
* @return int $url['query'] = array();
*/ }
public function getTotalResults() { // Remove 'start parameter if it exists
return (int) $this->totalResults; if (array_key_exists('start', $url['query'])) {
} unset($url['query']['start']);
}
/** // Add extra parameters from argument
* Optimizes the search indexes on the File System $url['query'] = array_merge($url['query'], $params);
* $url['query'] = http_build_query($url['query']);
* @return void $url = $url['path'] . ($url['query'] ? '?'.$url['query'] : '');
*/
public function optimizeIndex() { return $url;
$index = Zend_Search_Lucene::open(self::get_index_location()); }
if($index) $index->optimize(); /**
} * @return int
*/
/** public function getTotalResults()
* @return String {
*/ return (int) $this->totalResults;
public function getTitle() { }
return ($this->outputController) ? $this->outputController->Title : _t('DocumentationSearch.SEARCH', 'Search');
} /**
* Optimizes the search indexes on the File System
/** *
* OpenSearch MetaData fields. For a list of fields consult * @return void
* {@link self::get_meta_data()} */
* public function optimizeIndex()
* @param array {
*/ $index = Zend_Search_Lucene::open(self::get_index_location());
public static function set_meta_data($data) {
if(is_array($data)) { if ($index) {
foreach($data as $key => $value) { $index->optimize();
self::$meta_data[strtolower($key)] = $value; }
} }
}
else { /**
user_error("set_meta_data must be passed an array", E_USER_ERROR); * @return String
} */
} public function getTitle()
{
/** return ($this->outputController) ? $this->outputController->Title : _t('DocumentationSearch.SEARCH', 'Search');
* Returns the meta data needed by opensearch. }
*
* @return array /**
*/ * OpenSearch MetaData fields. For a list of fields consult
public static function get_meta_data() { * {@link self::get_meta_data()}
$data = self::$meta_data; *
* @param array
$defaults = array( */
'Description' => _t('DocumentationViewer.OPENSEARCHDESC', 'Search the documentation'), public static function set_meta_data($data)
'Tags' => _t('DocumentationViewer.OPENSEARCHTAGS', 'documentation'), {
'Contact' => Config::inst()->get('Email', 'admin_email'), if (is_array($data)) {
'ShortName' => _t('DocumentationViewer.OPENSEARCHNAME', 'Documentation Search'), foreach ($data as $key => $value) {
'Author' => 'SilverStripe' self::$meta_data[strtolower($key)] = $value;
); }
} else {
foreach($defaults as $key => $value) { user_error("set_meta_data must be passed an array", E_USER_ERROR);
if(isset($data[$key])) $defaults[$key] = $data[$key]; }
} }
return $defaults; /**
} * Returns the meta data needed by opensearch.
*
/** * @return array
* Renders the search results into a template. Either the search results */
* template or the Atom feed. public static function get_meta_data()
*/ {
public function renderResults() { $data = self::$meta_data;
if(!$this->results && $this->query) {
$this->performSearch(); $defaults = array(
} 'Description' => _t('DocumentationViewer.OPENSEARCHDESC', 'Search the documentation'),
'Tags' => _t('DocumentationViewer.OPENSEARCHTAGS', 'documentation'),
if(!$this->outputController) { 'Contact' => Config::inst()->get('Email', 'admin_email'),
return user_error('Call renderResults() on a DocumentationViewer instance.', E_USER_ERROR); 'ShortName' => _t('DocumentationViewer.OPENSEARCHNAME', 'Documentation Search'),
} 'Author' => 'SilverStripe'
);
$request = $this->outputController->getRequest();
$data = $this->getSearchResults($request); foreach ($defaults as $key => $value) {
$templates = array('DocumentationViewer_search'); if (isset($data[$key])) {
$defaults[$key] = $data[$key];
if($request->requestVar('format') && $request->requestVar('format') == "atom") { }
// alter the fields for the opensearch xml. }
$title = ($title = $this->getTitle()) ? ' - '. $title : "";
return $defaults;
$link = Controller::join_links( }
$this->outputController->Link(), 'DocumentationOpenSearchController/description/'
); /**
* Renders the search results into a template. Either the search results
$data->setField('Title', $data->Title . $title); * template or the Atom feed.
$data->setField('DescriptionURL', $link); */
public function renderResults()
array_unshift($templates, 'OpenSearchResults'); {
} if (!$this->results && $this->query) {
$this->performSearch();
return $this->outputController->customise($data)->renderWith($templates); }
}
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_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/'
);
$data->setField('Title', $data->Title . $title);
$data->setField('DescriptionURL', $link);
array_unshift($templates, 'OpenSearchResults');
}
return $this->outputController->customise($data)->renderWith($templates);
}
} }

View File

@ -7,40 +7,42 @@
* @package docsviewer * @package docsviewer
*/ */
class DocumentationOpenSearchController extends Controller { class DocumentationOpenSearchController extends Controller
{
private static $allowed_actions = array(
'description'
);
private static $allowed_actions = array( public function index()
'description' {
); return $this->httpError(404);
}
public function index() { public function description()
return $this->httpError(404); {
} $viewer = new DocumentationViewer();
public function description() { if (!$viewer->canView()) {
$viewer = new DocumentationViewer(); return Security::permissionFailure($this);
}
if(!$viewer->canView()) { if (!Config::inst()->get('DocumentationSearch', 'enabled')) {
return Security::permissionFailure($this); return $this->httpError('404');
} }
if(!Config::inst()->get('DocumentationSearch', 'enabled')) { $data = DocumentationSearch::get_meta_data();
return $this->httpError('404'); $link = Director::absoluteBaseUrl() .
} $data['SearchPageLink'] = Controller::join_links(
$viewer->Link(),
'results/?Search={searchTerms}&start={startIndex}&length={count}&action_results=1'
);
$data = DocumentationSearch::get_meta_data(); $data['SearchPageAtom'] = $data['SearchPageLink'] . '&format=atom';
$link = Director::absoluteBaseUrl() .
$data['SearchPageLink'] = Controller::join_links(
$viewer->Link(),
'results/?Search={searchTerms}&start={startIndex}&length={count}&action_results=1'
);
$data['SearchPageAtom'] = $data['SearchPageLink'] . '&format=atom'; return $this->customise(
new ArrayData($data)
return $this->customise( )->renderWith(array(
new ArrayData($data) 'OpenSearchDescription'
)->renderWith(array( ));
'OpenSearchDescription' }
));
}
} }

View File

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

View File

@ -1,93 +1,96 @@
<?php <?php
class DocumentationSearchExtension extends Extension { class DocumentationSearchExtension extends Extension
{
/**
* Return an array of folders and titles
*
* @return array
*/
public function getSearchedEntities()
{
$entities = array();
/** if (!empty($_REQUEST['Entities'])) {
* Return an array of folders and titles if (is_array($_REQUEST['Entities'])) {
* $entities = Convert::raw2att($_REQUEST['Entities']);
* @return array } else {
*/ $entities = explode(',', Convert::raw2att($_REQUEST['Entities']));
public function getSearchedEntities() { $entities = array_combine($entities, $entities);
$entities = array(); }
}
if(!empty($_REQUEST['Entities'])) { return $entities;
if(is_array($_REQUEST['Entities'])) { }
$entities = Convert::raw2att($_REQUEST['Entities']);
}
else {
$entities = explode(',', Convert::raw2att($_REQUEST['Entities']));
$entities = array_combine($entities, $entities);
}
}
return $entities; /**
} * Return an array of versions that we're allowed to return
*
* @return array
*/
public function getSearchedVersions()
{
$versions = array();
/** if (!empty($_REQUEST['Versions'])) {
* Return an array of versions that we're allowed to return if (is_array($_REQUEST['Versions'])) {
* $versions = Convert::raw2att($_REQUEST['Versions']);
* @return array $versions = array_combine($versions, $versions);
*/ } else {
public function getSearchedVersions() { $version = Convert::raw2att($_REQUEST['Versions']);
$versions = array(); $versions[$version] = $version;
}
}
if(!empty($_REQUEST['Versions'])) { return $versions;
if(is_array($_REQUEST['Versions'])) { }
$versions = Convert::raw2att($_REQUEST['Versions']);
$versions = array_combine($versions, $versions);
}
else {
$version = Convert::raw2att($_REQUEST['Versions']);
$versions[$version] = $version;
}
}
return $versions; /**
} * Return the current search query.
*
* @return HTMLText|null
*/
public function getSearchQuery()
{
if (isset($_REQUEST['Search'])) {
return DBField::create_field('HTMLText', $_REQUEST['Search']);
} elseif (isset($_REQUEST['q'])) {
return DBField::create_field('HTMLText', $_REQUEST['q']);
}
}
/** /**
* Return the current search query. * Past straight to results, display and encode the query.
* */
* @return HTMLText|null public function getSearchResults()
*/ {
public function getSearchQuery() { $query = $this->getSearchQuery();
if(isset($_REQUEST['Search'])) {
return DBField::create_field('HTMLText', $_REQUEST['Search']);
} else if(isset($_REQUEST['q'])) {
return DBField::create_field('HTMLText', $_REQUEST['q']);
}
}
/** $search = new DocumentationSearch();
* Past straight to results, display and encode the query. $search->setQuery($query);
*/ $search->setVersions($this->getSearchedVersions());
public function getSearchResults() { $search->setModules($this->getSearchedEntities());
$query = $this->getSearchQuery(); $search->setOutputController($this->owner);
$search = new DocumentationSearch(); return $search->renderResults();
$search->setQuery($query); }
$search->setVersions($this->getSearchedVersions());
$search->setModules($this->getSearchedEntities());
$search->setOutputController($this->owner);
return $search->renderResults(); /**
} * Returns an search form which allows people to express more complex rules
* and options than the plain search form.
/** *
* Returns an search form which allows people to express more complex rules * @return Form
* and options than the plain search form. */
* public function AdvancedSearchForm()
* @return Form {
*/ return new DocumentationAdvancedSearchForm($this->owner);
public function AdvancedSearchForm() { }
return new DocumentationAdvancedSearchForm($this->owner);
}
/**
* @return bool
*/
public function getAdvancedSearchEnabled() {
return Config::inst()->get("DocumentationSearch", 'advanced_search_enabled');
}
/**
* @return bool
*/
public function getAdvancedSearchEnabled()
{
return Config::inst()->get("DocumentationSearch", 'advanced_search_enabled');
}
} }

View File

@ -25,13 +25,14 @@
* *
* @package docsviewer * @package docsviewer
*/ */
class DocumentationStaticPublisherExtension extends Extension { class DocumentationStaticPublisherExtension extends Extension
{
public function alterExportUrls(&$urls)
{
$manifest = new DocumentationManifest(true);
public function alterExportUrls(&$urls) { foreach ($manifest->getPages() as $url => $page) {
$manifest = new DocumentationManifest(true); $urls[$url] = $url;
}
foreach($manifest->getPages() as $url => $page) { }
$urls[$url] = $url;
}
}
} }

View File

@ -7,43 +7,43 @@
* @return false|ArrayData * @return false|ArrayData
*/ */
class DocumentationViewerVersionWarning extends Extension { class DocumentationViewerVersionWarning extends Extension
{
public function VersionWarning()
{
$page = $this->owner->getPage();
public function VersionWarning() { if (!$page) {
$page = $this->owner->getPage(); return false;
}
if(!$page) { $entity = $page->getEntity();
return false;
}
$entity = $page->getEntity(); if (!$entity) {
return false;
}
if(!$entity) { $versions = $this->owner->getManifest()->getAllVersionsOfEntity($entity);
return false;
}
$versions = $this->owner->getManifest()->getAllVersionsOfEntity($entity); if ($entity->getIsStable()) {
return false;
}
if($entity->getIsStable()) { $stable = $this->owner->getManifest()->getStableVersion($entity);
return false; $compare = $entity->compare($stable);
}
$stable = $this->owner->getManifest()->getStableVersion($entity); if ($entity->getVersion() == "master" || $compare > 0) {
$compare = $entity->compare($stable); return $this->owner->customise(new ArrayData(array(
'FutureRelease' => true,
'StableVersion' => DBField::create_field('HTMLText', $stable->getVersion())
)));
} else {
return $this->owner->customise(new ArrayData(array(
'OutdatedRelease' => true,
'StableVersion' => DBField::create_field('HTMLText', $stable->getVersion())
)));
}
if($entity->getVersion() == "master" || $compare > 0) { return false;
return $this->owner->customise(new ArrayData(array( }
'FutureRelease' => true,
'StableVersion' => DBField::create_field('HTMLText', $stable->getVersion())
)));
}
else {
return $this->owner->customise(new ArrayData(array(
'OutdatedRelease' => true,
'StableVersion' => DBField::create_field('HTMLText', $stable->getVersion())
)));
}
return false;
}
} }

View File

@ -3,56 +3,57 @@
/** /**
* @package docsviewer * @package docsviewer
*/ */
class DocumentationAdvancedSearchForm extends Form { class DocumentationAdvancedSearchForm extends Form
{
public function __construct($controller)
{
$versions = $controller->getManifest()->getAllVersions();
$entities = $controller->getManifest()->getEntities();
public function __construct($controller) { $q = ($q = $controller->getSearchQuery()) ? $q->NoHTML() : "";
$versions = $controller->getManifest()->getAllVersions();
$entities = $controller->getManifest()->getEntities();
$q = ($q = $controller->getSearchQuery()) ? $q->NoHTML() : ""; // klude to take an array of objects down to a simple map
$entities = $entities->map('Key', 'Title');
// klude to take an array of objects down to a simple map // if we haven't gone any search limit then we're searching everything
$entities = $entities->map('Key', 'Title'); $searchedEntities = $controller->getSearchedEntities();
// if we haven't gone any search limit then we're searching everything if (count($searchedEntities) < 1) {
$searchedEntities = $controller->getSearchedEntities(); $searchedEntities = $entities;
}
if(count($searchedEntities) < 1) { $searchedVersions = $controller->getSearchedVersions();
$searchedEntities = $entities;
}
$searchedVersions = $controller->getSearchedVersions(); if (count($searchedVersions) < 1) {
$searchedVersions = $versions;
}
if(count($searchedVersions) < 1) { $fields = FieldList::create(
$searchedVersions = $versions; TextField::create('q', _t('DocumentationViewer.KEYWORDS', 'Keywords'), $q),
} //CheckboxSetField::create('Entities', _t('DocumentationViewer.MODULES', 'Modules'), $entities, $searchedEntities),
CheckboxSetField::create(
'Versions',
_t('DocumentationViewer.VERSIONS', 'Versions'),
$versions, $searchedVersions
)
);
$fields = FieldList::create( $actions = FieldList::create(
TextField::create('q', _t('DocumentationViewer.KEYWORDS', 'Keywords'), $q), FormAction::create('results', _t('DocumentationViewer.SEARCH', 'Search'))
//CheckboxSetField::create('Entities', _t('DocumentationViewer.MODULES', 'Modules'), $entities, $searchedEntities), );
CheckboxSetField::create(
'Versions',
_t('DocumentationViewer.VERSIONS', 'Versions'),
$versions, $searchedVersions
)
);
$actions = FieldList::create( $required = RequiredFields::create(array('Search'));
FormAction::create('results', _t('DocumentationViewer.SEARCH', 'Search'))
);
$required = RequiredFields::create(array('Search')); parent::__construct(
$controller,
'AdvancedSearchForm',
$fields,
$actions,
$required
);
parent::__construct( $this->disableSecurityToken();
$controller, $this->setFormMethod('GET');
'AdvancedSearchForm', $this->setFormAction($controller->Link('results'));
$fields, }
$actions,
$required
);
$this->disableSecurityToken();
$this->setFormMethod('GET');
$this->setFormAction($controller->Link('results'));
}
} }

View File

@ -1,37 +1,36 @@
<?php <?php
class DocumentationSearchForm extends Form { class DocumentationSearchForm extends Form
{
public function __construct($controller)
{
$fields = new FieldList(
TextField::create('q', _t('DocumentationViewer.SEARCH', 'Search'), '')
->setAttribute('placeholder', _t('DocumentationViewer.SEARCH', 'Search'))
);
public function __construct($controller) { $page = $controller->getPage();
if ($page) {
$versions = HiddenField::create(
'Versions',
_t('DocumentationViewer.VERSIONS', 'Versions'),
$page->getEntity()->getVersion()
);
$fields = new FieldList( $fields->push($versions);
TextField::create('q', _t('DocumentationViewer.SEARCH', 'Search'), '') }
->setAttribute('placeholder', _t('DocumentationViewer.SEARCH', 'Search'))
);
$page = $controller->getPage(); $actions = new FieldList(
new FormAction('results', _t('DocumentationViewer.SEARCH', 'Search'))
);
if($page){ parent::__construct($controller, 'DocumentationSearchForm', $fields, $actions);
$versions = HiddenField::create(
'Versions',
_t('DocumentationViewer.VERSIONS', 'Versions'),
$page->getEntity()->getVersion()
);
$fields->push($versions); $this->disableSecurityToken();
} $this->setFormMethod('GET');
$this->setFormAction($controller->Link('results'));
$actions = new FieldList( $this->addExtraClass('search');
new FormAction('results', _t('DocumentationViewer.SEARCH', 'Search')) }
);
parent::__construct($controller, 'DocumentationSearchForm', $fields, $actions);
$this->disableSecurityToken();
$this->setFormMethod('GET');
$this->setFormAction($controller->Link('results'));
$this->addExtraClass('search');
}
} }

View File

@ -16,287 +16,308 @@
* @subpackage models * @subpackage models
*/ */
class DocumentationEntity extends ViewableData { class DocumentationEntity extends ViewableData
{
/**
* 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
*/
protected $key;
/** /**
* The key to match entities with that is not localized. For instance, you * The human readable title of this entity. Set when the module is
* may have three entities (en, de, fr) that you want to display a nice * registered.
* title for, but matching needs to occur on a specific key. *
* * @var string $title
* @var string $key */
*/ protected $title;
protected $key;
/** /**
* The human readable title of this entity. Set when the module is * If the system is setup to only document one entity then you may only
* registered. * want to show a single entity in the URL and the sidebar. Set this when
* * you register the entity with the key `DefaultEntity` and the URL will
* @var string $title * not include any version or language information.
*/ *
protected $title; * @var boolean $default_entity
*/
protected $defaultEntity;
/** /**
* If the system is setup to only document one entity then you may only * @var mixed
* want to show a single entity in the URL and the sidebar. Set this when */
* you register the entity with the key `DefaultEntity` and the URL will protected $path;
* not include any version or language information.
*
* @var boolean $default_entity
*/
protected $defaultEntity;
/** /**
* @var mixed * @see {@link http://php.net/manual/en/function.version-compare.php}
*/ * @var float $version
protected $path; */
protected $version;
/** /**
* @see {@link http://php.net/manual/en/function.version-compare.php} * The repository branch name (allows for $version to be an alias on development branches).
* @var float $version *
*/ * @var string $branch
protected $version; */
protected $branch;
/** /**
* The repository branch name (allows for $version to be an alias on development branches). * 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 string $branch *
*/ * @var boolean $stable
protected $branch; */
protected $stable;
/** /**
* If this entity is a stable release or not. If it is not stable (i.e it * @var string
* could be a past or future release) then a warning message will be shown. */
* protected $language;
* @var boolean $stable
*/
protected $stable;
/** /**
* @var string *
*/ */
protected $language; public function __construct($key)
{
/** $this->key = DocumentationHelper::clean_page_url($key);
* }
*/
public function __construct($key) {
$this->key = DocumentationHelper::clean_page_url($key);
}
/** /**
* Get the title of this module. * Get the title of this module.
* *
* @return string * @return string
*/ */
public function getTitle() { public function getTitle()
if(!$this->title) { {
$this->title = DocumentationHelper::clean_page_name($this->key); if (!$this->title) {
} $this->title = DocumentationHelper::clean_page_name($this->key);
}
return $this->title; return $this->title;
} }
/** /**
* @param string $title * @param string $title
* @return this * @return this
*/ */
public function setTitle($title) { public function setTitle($title)
$this->title = $title; {
$this->title = $title;
return $this; return $this;
} }
/** /**
* Returns the web accessible link to this entity. * Returns the web accessible link to this entity.
* *
* Includes the version information * Includes the version information
* *
* @param boolean $short If true, will attempt to return a short version of the url * @param boolean $short If true, will attempt to return a short version of the url
* This might omit the version number if this is the default version. * This might omit the version number if this is the default version.
* @return string * @return string
*/ */
public function Link($short = false) { public function Link($short = false)
if($this->getIsDefaultEntity()) { {
$base = Controller::join_links( if ($this->getIsDefaultEntity()) {
Config::inst()->get('DocumentationViewer', 'link_base'), $base = Controller::join_links(
$this->getLanguage(), Config::inst()->get('DocumentationViewer', 'link_base'),
'/' $this->getLanguage(),
); '/'
} else { );
$base = Controller::join_links( } else {
Config::inst()->get('DocumentationViewer', 'link_base'), $base = Controller::join_links(
$this->getLanguage(), Config::inst()->get('DocumentationViewer', 'link_base'),
$this->getKey(), $this->getLanguage(),
'/' $this->getKey(),
); '/'
} );
}
$base = ltrim(str_replace('//', '/', $base), '/'); $base = ltrim(str_replace('//', '/', $base), '/');
if($short && $this->stable) { if ($short && $this->stable) {
return $base; return $base;
} }
return Controller::join_links( return Controller::join_links(
$base, $base,
$this->getVersion(), $this->getVersion(),
'/' '/'
); );
} }
/** /**
* @return string * @return string
*/ */
public function __toString() { public function __toString()
return sprintf('DocumentationEntity: %s)', $this->getPath()); {
} return sprintf('DocumentationEntity: %s)', $this->getPath());
}
/** /**
* @param DocumentationPage $page * @param DocumentationPage $page
* *
* @return boolean * @return boolean
*/ */
public function hasRecord($page) { public function hasRecord($page)
if(!$page) { {
return false; if (!$page) {
} return false;
}
return strstr($page->getPath(), $this->getPath()) !== false; return strstr($page->getPath(), $this->getPath()) !== false;
} }
/** /**
* @param boolean $bool * @param boolean $bool
*/ */
public function setIsDefaultEntity($bool) { public function setIsDefaultEntity($bool)
$this->defaultEntity = $bool; {
$this->defaultEntity = $bool;
return $this; return $this;
} }
/** /**
* @return boolean * @return boolean
*/ */
public function getIsDefaultEntity() { public function getIsDefaultEntity()
return $this->defaultEntity; {
} return $this->defaultEntity;
}
/** /**
* @return string * @return string
*/ */
public function getKey() { public function getKey()
return $this->key; {
} return $this->key;
}
/** /**
* @return string * @return string
*/ */
public function getLanguage() { public function getLanguage()
return $this->language; {
} return $this->language;
}
/** /**
* @param string * @param string
* *
* @return this * @return this
*/ */
public function setLanguage($language) { public function setLanguage($language)
$this->language = $language; {
$this->language = $language;
return $this; return $this;
} }
/** /**
* @param string * @param string
*/ */
public function setVersion($version) { public function setVersion($version)
$this->version = $version; {
$this->version = $version;
return $this; return $this;
} }
/** /**
* @return float * @return float
*/ */
public function getVersion() { public function getVersion()
return $this->version; {
} return $this->version;
}
/** /**
* @param string * @param string
*/ */
public function setBranch($branch) { public function setBranch($branch)
$this->branch = $branch; {
$this->branch = $branch;
return $this; return $this;
} }
/** /**
* @return float * @return float
*/ */
public function getBranch() { public function getBranch()
return $this->branch; {
} return $this->branch;
}
/** /**
* @return string * @return string
*/ */
public function getPath() { public function getPath()
return $this->path; {
} return $this->path;
}
/** /**
* @param string $path * @param string $path
* *
* @return this * @return this
*/ */
public function setPath($path) { public function setPath($path)
$this->path = $path; {
$this->path = $path;
return $this; return $this;
} }
/** /**
* @param boolean * @param boolean
*/ */
public function setIsStable($stable) { public function setIsStable($stable)
$this->stable = $stable; {
$this->stable = $stable;
return $this; return $this;
} }
/** /**
* @return boolean * @return boolean
*/ */
public function getIsStable() { public function getIsStable()
return $this->stable; {
} return $this->stable;
}
/** /**
* Returns an integer value based on if a given version is the latest * 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 * version. Will return -1 for if the version is older, 0 if versions are
* the same and 1 if the version is greater than. * the same and 1 if the version is greater than.
* *
* @param string $version * @param string $version
* @return int * @return int
*/ */
public function compare(DocumentationEntity $other) { public function compare(DocumentationEntity $other)
return version_compare($this->getVersion(), $other->getVersion()); {
} return version_compare($this->getVersion(), $other->getVersion());
}
/** /**
* @return array * @return array
*/ */
public function toMap() { public function toMap()
return array( {
'Key' => $this->key, return array(
'Path' => $this->getPath(), 'Key' => $this->key,
'Version' => $this->getVersion(), 'Path' => $this->getPath(),
'Branch' => $this->getBranch(), 'Version' => $this->getVersion(),
'IsStable' => $this->getIsStable(), 'Branch' => $this->getBranch(),
'Language' => $this->getLanguage() 'IsStable' => $this->getIsStable(),
); 'Language' => $this->getLanguage()
} );
}
} }

View File

@ -8,13 +8,13 @@
* @package docsviewer * @package docsviewer
* @subpackage model * @subpackage model
*/ */
class DocumentationFolder extends DocumentationPage { class DocumentationFolder extends DocumentationPage
{
/** /**
* @return string * @return string
*/ */
public function getTitle() { public function getTitle()
return $this->getTitleFromFolder(); {
} return $this->getTitleFromFolder();
}
} }

View File

@ -10,280 +10,295 @@
* @package docsviewer * @package docsviewer
* @subpackage model * @subpackage model
*/ */
class DocumentationPage extends ViewableData { class DocumentationPage extends ViewableData
{
/**
* @var string
*/
protected $title, $summary, $introduction;
/** /**
* @var string * @var DocumentationEntity
*/ */
protected $title, $summary, $introduction; protected $entity;
/** /**
* @var DocumentationEntity * @var string
*/ */
protected $entity; protected $path;
/** /**
* @var string * Filename
*/ *
protected $path; * @var string
*/
protected $filename;
/** protected $read = false;
* Filename
*
* @var string
*/
protected $filename;
protected $read = false; /**
* @param DocumentationEntity $entity
* @param string $filename
* @param string $path
*/
public function __construct(DocumentationEntity $entity, $filename, $path)
{
$this->filename = $filename;
$this->path = $path;
$this->entity = $entity;
}
/** /**
* @param DocumentationEntity $entity * @return string
* @param string $filename */
* @param string $path public function getExtension()
*/ {
public function __construct(DocumentationEntity $entity, $filename, $path) { return DocumentationHelper::get_extension($this->filename);
$this->filename = $filename; }
$this->path = $path;
$this->entity = $entity;
}
/** /**
* @return string * @param string - has to be plain text for open search compatibility.
*/ *
public function getExtension() { * @return string
return DocumentationHelper::get_extension($this->filename); */
} public function getBreadcrumbTitle($divider = ' - ')
{
$pathParts = explode('/', trim($this->getRelativePath(), '/'));
/** // from the page from this
* @param string - has to be plain text for open search compatibility. array_pop($pathParts);
*
* @return string
*/
public function getBreadcrumbTitle($divider = ' - ') {
$pathParts = explode('/', trim($this->getRelativePath(), '/'));
// from the page from this // add the module to the breadcrumb trail.
array_pop($pathParts); $pathParts[] = $this->entity->getTitle();
// add the module to the breadcrumb trail. $titleParts = array_map(array(
$pathParts[] = $this->entity->getTitle(); 'DocumentationHelper', 'clean_page_name'
), $pathParts);
$titleParts = array_map(array( $titleParts = array_filter($titleParts, function ($val) {
'DocumentationHelper', 'clean_page_name' if ($val) {
), $pathParts); return $val;
}
});
$titleParts = array_filter($titleParts, function($val) { if ($this->getTitle()) {
if($val) { array_unshift($titleParts, $this->getTitle());
return $val; }
}
});
if($this->getTitle()) { return implode($divider, $titleParts);
array_unshift($titleParts, $this->getTitle()); }
}
return implode($divider, $titleParts); /**
} * @return DocumentationEntity
*/
public function getEntity()
{
return $this->entity;
}
/** /**
* @return DocumentationEntity * @return string
*/ */
public function getEntity() { public function getTitle()
return $this->entity; {
} if ($this->title) {
return $this->title;
}
/** $page = DocumentationHelper::clean_page_name($this->filename);
* @return string
*/
public function getTitle() {
if($this->title) {
return $this->title;
}
$page = DocumentationHelper::clean_page_name($this->filename); if ($page == "Index") {
return $this->getTitleFromFolder();
}
if($page == "Index") { return $page;
return $this->getTitleFromFolder(); }
}
return $page; public function getTitleFromFolder()
} {
$folder = $this->getPath();
$entity = $this->getEntity()->getPath();
public function getTitleFromFolder() { $folder = str_replace('index.md', '', $folder);
$folder = $this->getPath();
$entity = $this->getEntity()->getPath();
$folder = str_replace('index.md', '', $folder); // if it's the root of the entity then we want to use the entity name
// otherwise we'll get 'En' for the entity folder
if ($folder == $entity) {
return $this->getEntity()->getTitle();
} else {
$path = explode(DIRECTORY_SEPARATOR, trim($folder, DIRECTORY_SEPARATOR));
$folderName = array_pop($path);
}
// if it's the root of the entity then we want to use the entity name return DocumentationHelper::clean_page_name($folderName);
// otherwise we'll get 'En' for the entity folder }
if($folder == $entity) {
return $this->getEntity()->getTitle();
} else {
$path = explode(DIRECTORY_SEPARATOR, trim($folder, DIRECTORY_SEPARATOR));
$folderName = array_pop($path);
}
return DocumentationHelper::clean_page_name($folderName); /**
} * @return string
*/
public function getSummary()
{
return $this->summary;
}
/** /**
* @return string * Return the raw markdown for a given documentation page.
*/ *
public function getSummary() { * @param boolean $removeMetaData
return $this->summary; *
} * @return string
*/
public function getMarkdown($removeMetaData = false)
{
try {
if ($md = file_get_contents($this->getPath())) {
$this->populateMetaDataFromText($md, $removeMetaData);
/** return $md;
* Return the raw markdown for a given documentation page. }
*
* @param boolean $removeMetaData
*
* @return string
*/
public function getMarkdown($removeMetaData = false) {
try {
if ($md = file_get_contents($this->getPath())) {
$this->populateMetaDataFromText($md, $removeMetaData);
return $md; $this->read = true;
} } catch (InvalidArgumentException $e) {
}
$this->read = true; return false;
} }
catch(InvalidArgumentException $e) {
} public function setMetaData($key, $value)
{
$key = strtolower($key);
return false; $this->$key = $value;
} }
public function setMetaData($key, $value) { public function getIntroduction()
$key = strtolower($key); {
if (!$this->read) {
$this->getMarkdown();
}
$this->$key = $value; return $this->introduction;
} }
public function getIntroduction() { /**
if(!$this->read) { * Parse a file and return the parsed HTML version.
$this->getMarkdown(); *
} * @param string $baselink
*
* @return string
*/
public function getHTML()
{
$html = DocumentationParser::parse(
$this,
$this->entity->Link()
);
return $this->introduction; return $html;
} }
/** /**
* Parse a file and return the parsed HTML version. * This should return the link from the entity root to the page. The link
* * value has the cleaned version of the folder names. See
* @param string $baselink * {@link getRelativePath()} for the actual file path.
* *
* @return string * @return string
*/ */
public function getHTML() { public function getRelativeLink()
$html = DocumentationParser::parse( {
$this, $path = $this->getRelativePath();
$this->entity->Link() $url = explode('/', $path);
); $url = implode('/', array_map(function ($a) {
return DocumentationHelper::clean_page_url($a);
}, $url));
return $html; $url = trim($url, '/') . '/';
}
/** return $url;
* This should return the link from the entity root to the page. The link }
* value has the cleaned version of the folder names. See
* {@link getRelativePath()} for the actual file path.
*
* @return string
*/
public function getRelativeLink() {
$path = $this->getRelativePath();
$url = explode('/', $path);
$url = implode('/', array_map(function($a) {
return DocumentationHelper::clean_page_url($a);
}, $url));
$url = trim($url, '/') . '/'; /**
* This should return the link from the entity root to the page. For the url
* polished version, see {@link getRelativeLink()}.
*
* @return string
*/
public function getRelativePath()
{
return str_replace($this->entity->getPath(), '', $this->getPath());
}
return $url; /**
} * @return string
*/
public function getPath()
{
return $this->path;
}
/** /**
* This should return the link from the entity root to the page. For the url * Returns the URL that will be required for the user to hit to view the
* polished version, see {@link getRelativeLink()}. * given document base name.
* *
* @return string * @param boolean $short If true, will attempt to return a short version of the url
*/ * This might omit the version number if this is the default version.
public function getRelativePath() { * @return string
return str_replace($this->entity->getPath(), '', $this->getPath()); */
public function Link($short = false)
{
return ltrim(Controller::join_links(
$this->entity->Link($short),
$this->getRelativeLink()
), '/');
}
} /**
* Return metadata from the first html block in the page, then remove the
* block on request
*
* @param DocumentationPage $md
* @param bool $remove
*/
public function populateMetaDataFromText(&$md, $removeMetaData = false)
{
if ($md) {
// get the text up to the first whiteline
$extPattern = "/^(.+)\n(\r)*\n/Uis";
$matches = preg_match($extPattern, $md, $block);
/** if ($matches && $block[1]) {
* @return string $metaDataFound = false;
*/
public function getPath() {
return $this->path;
}
/** // find the key/value pairs
* Returns the URL that will be required for the user to hit to view the $intPattern = '/(?<key>[A-Za-z][A-Za-z0-9_-]+)[\t]*:[\t]*(?<value>[^:\n\r\/]+)/x';
* given document base name. $matches = preg_match_all($intPattern, $block[1], $meta);
*
* @param boolean $short If true, will attempt to return a short version of the url
* This might omit the version number if this is the default version.
* @return string
*/
public function Link($short = false) {
return ltrim(Controller::join_links(
$this->entity->Link($short),
$this->getRelativeLink()
), '/');
}
/** foreach ($meta['key'] as $index => $key) {
* Return metadata from the first html block in the page, then remove the if (isset($meta['value'][$index])) {
* block on request // check if a property exists for this key
* if (property_exists(get_class(), $key)) {
* @param DocumentationPage $md $this->$key = $meta['value'][$index];
* @param bool $remove $metaDataFound = true;
*/ }
public function populateMetaDataFromText(&$md, $removeMetaData = false) { }
if($md) { }
// get the text up to the first whiteline
$extPattern = "/^(.+)\n(\r)*\n/Uis";
$matches = preg_match($extPattern, $md, $block);
if($matches && $block[1]) { // optionally remove the metadata block (only on the page that
$metaDataFound = false; // is displayed)
if ($metaDataFound && $removeMetaData) {
$md = preg_replace($extPattern, '', $md);
}
}
}
}
// find the key/value pairs public function getVersion()
$intPattern = '/(?<key>[A-Za-z][A-Za-z0-9_-]+)[\t]*:[\t]*(?<value>[^:\n\r\/]+)/x'; {
$matches = preg_match_all($intPattern, $block[1], $meta); return $this->entity->getVersion();
}
foreach($meta['key'] as $index => $key) { public function __toString()
if(isset($meta['value'][$index])) { {
// check if a property exists for this key return sprintf(get_class($this) .': %s)', $this->getPath());
if (property_exists(get_class(), $key)) { }
$this->$key = $meta['value'][$index];
$metaDataFound = true;
}
}
}
// optionally remove the metadata block (only on the page that
// is displayed)
if ($metaDataFound && $removeMetaData) {
$md = preg_replace($extPattern, '', $md);
}
}
}
}
public function getVersion() {
return $this->entity->getVersion();
}
public function __toString() {
return sprintf(get_class($this) .': %s)', $this->getPath());
}
} }

View File

@ -1,14 +1,14 @@
<?php <?php
class DocumentationBuild extends BuildTask { class DocumentationBuild extends BuildTask
{
public function run($request) { public function run($request)
$manifest = new DocumentationManifest(true); {
echo "<pre>"; $manifest = new DocumentationManifest(true);
print_r($manifest->getPages()); echo "<pre>";
echo "</pre>"; print_r($manifest->getPages());
die();; echo "</pre>";
die();
;
} }
} }

View File

@ -9,124 +9,127 @@
* @subpackage tasks * @subpackage tasks
*/ */
class RebuildLuceneDocsIndex extends BuildTask { class RebuildLuceneDocsIndex extends BuildTask
{
protected $title = "Rebuild Documentation Search Indexes";
protected $title = "Rebuild Documentation Search Indexes"; protected $description = "
protected $description = "
Rebuilds the indexes used for the search engine in the docsviewer."; Rebuilds the indexes used for the search engine in the docsviewer.";
public function run($request) { public function run($request)
$this->rebuildIndexes(); {
} $this->rebuildIndexes();
}
public function rebuildIndexes($quiet = false) { public function rebuildIndexes($quiet = false)
require_once 'Zend/Search/Lucene.php'; {
require_once 'Zend/Search/Lucene.php';
ini_set("memory_limit", -1); ini_set("memory_limit", -1);
ini_set('max_execution_time', 0); ini_set('max_execution_time', 0);
Filesystem::makeFolder(DocumentationSearch::get_index_location()); Filesystem::makeFolder(DocumentationSearch::get_index_location());
// only rebuild the index if we have to. Check for either flush or the time write.lock.file // only rebuild the index if we have to. Check for either flush or the time write.lock.file
// was last altered // was last altered
$lock = DocumentationSearch::get_index_location() .'/write.lock.file'; $lock = DocumentationSearch::get_index_location() .'/write.lock.file';
$lockFileFresh = (file_exists($lock) && filemtime($lock) > (time() - (60 * 60 * 24))); $lockFileFresh = (file_exists($lock) && filemtime($lock) > (time() - (60 * 60 * 24)));
echo "Building index in ". DocumentationSearch::get_index_location() . PHP_EOL; echo "Building index in ". DocumentationSearch::get_index_location() . PHP_EOL;
if($lockFileFresh && !isset($_REQUEST['flush'])) { if ($lockFileFresh && !isset($_REQUEST['flush'])) {
if(!$quiet) { if (!$quiet) {
echo "Index recently rebuilt. If you want to force reindex use ?flush=1"; echo "Index recently rebuilt. If you want to force reindex use ?flush=1";
} }
return true; return true;
} }
try { try {
$index = Zend_Search_Lucene::open(DocumentationSearch::get_index_location()); $index = Zend_Search_Lucene::open(DocumentationSearch::get_index_location());
$index->removeReference(); $index->removeReference();
} } catch (Zend_Search_Lucene_Exception $e) {
catch (Zend_Search_Lucene_Exception $e) { user_error($e);
user_error($e); }
}
try { try {
$index = Zend_Search_Lucene::create(DocumentationSearch::get_index_location()); $index = Zend_Search_Lucene::create(DocumentationSearch::get_index_location());
} } catch (Zend_Search_Lucene_Exception $c) {
catch(Zend_Search_Lucene_Exception $c) { user_error($c);
user_error($c); }
}
// includes registration // includes registration
$manifest = new DocumentationManifest(true); $manifest = new DocumentationManifest(true);
$pages = $manifest->getPages(); $pages = $manifest->getPages();
if($pages) { if ($pages) {
$count = 0; $count = 0;
// iconv complains about all the markdown formatting // iconv complains about all the markdown formatting
// turn off notices while we parse // turn off notices while we parse
if(!Director::is_cli()) { if (!Director::is_cli()) {
echo "<ul>"; echo "<ul>";
} }
foreach($pages as $url => $record) { foreach ($pages as $url => $record) {
$count++; $count++;
$page = $manifest->getPage($url); $page = $manifest->getPage($url);
$doc = new Zend_Search_Lucene_Document(); $doc = new Zend_Search_Lucene_Document();
$error = error_reporting(); $error = error_reporting();
error_reporting(E_ALL ^ E_NOTICE); error_reporting(E_ALL ^ E_NOTICE);
$content = $page->getHTML(); $content = $page->getHTML();
error_reporting($error); error_reporting($error);
$doc->addField(Zend_Search_Lucene_Field::Text('content', $content)); $doc->addField(Zend_Search_Lucene_Field::Text('content', $content));
$doc->addField($titleField = Zend_Search_Lucene_Field::Text('Title', $page->getTitle())); $doc->addField($titleField = Zend_Search_Lucene_Field::Text('Title', $page->getTitle()));
$doc->addField($breadcrumbField = Zend_Search_Lucene_Field::Text('BreadcrumbTitle', $page->getBreadcrumbTitle())); $doc->addField($breadcrumbField = Zend_Search_Lucene_Field::Text('BreadcrumbTitle', $page->getBreadcrumbTitle()));
$doc->addField(Zend_Search_Lucene_Field::Keyword( $doc->addField(Zend_Search_Lucene_Field::Keyword(
'Version', $page->getEntity()->getVersion() 'Version', $page->getEntity()->getVersion()
)); ));
$doc->addField(Zend_Search_Lucene_Field::Keyword( $doc->addField(Zend_Search_Lucene_Field::Keyword(
'Language', $page->getEntity()->getLanguage() 'Language', $page->getEntity()->getLanguage()
)); ));
$doc->addField(Zend_Search_Lucene_Field::Keyword( $doc->addField(Zend_Search_Lucene_Field::Keyword(
'Entity', $page->getEntity() 'Entity', $page->getEntity()
)); ));
$doc->addField(Zend_Search_Lucene_Field::Keyword( $doc->addField(Zend_Search_Lucene_Field::Keyword(
'Link', $page->Link() 'Link', $page->Link()
)); ));
// custom boosts // custom boosts
$titleField->boost = 3; $titleField->boost = 3;
$breadcrumbField->boost = 1.5; $breadcrumbField->boost = 1.5;
$boost = Config::inst()->get('DocumentationSearch', 'boost_by_path'); $boost = Config::inst()->get('DocumentationSearch', 'boost_by_path');
foreach($boost as $pathExpr => $boost) { foreach ($boost as $pathExpr => $boost) {
if(preg_match($pathExpr, $page->getRelativePath())) { if (preg_match($pathExpr, $page->getRelativePath())) {
$doc->boost = $boost; $doc->boost = $boost;
} }
} }
error_reporting(E_ALL ^ E_NOTICE); error_reporting(E_ALL ^ E_NOTICE);
$index->addDocument($doc); $index->addDocument($doc);
if(!$quiet) { if (!$quiet) {
if(Director::is_cli()) echo " * adding ". $page->getPath() ."\n"; if (Director::is_cli()) {
else echo "<li>adding ". $page->getPath() ."</li>\n"; echo " * adding ". $page->getPath() ."\n";
} } else {
} echo "<li>adding ". $page->getPath() ."</li>\n";
} }
}
}
}
$index->commit(); $index->commit();
if(!$quiet) { if (!$quiet) {
echo "complete."; echo "complete.";
} }
} }
} }

View File

@ -4,67 +4,72 @@
* @package docsviewer * @package docsviewer
* @subpackage tests * @subpackage tests
*/ */
class DocumentationHelperTests extends SapphireTest { class DocumentationHelperTests extends SapphireTest
{
public function testCleanName()
{
$this->assertEquals("File path", DocumentationHelper::clean_page_name(
'00_file-path.md'
));
}
public function testCleanName() { public function testCleanUrl()
$this->assertEquals("File path", DocumentationHelper::clean_page_name( {
'00_file-path.md' $this->assertEquals("some_path", DocumentationHelper::clean_page_url(
)); 'Some Path'
} ));
public function testCleanUrl() { $this->assertEquals("somefilepath", DocumentationHelper::clean_page_url(
$this->assertEquals("some_path", DocumentationHelper::clean_page_url( '00_SomeFilePath.md'
'Some Path' ));
)); }
$this->assertEquals("somefilepath", DocumentationHelper::clean_page_url( public function testTrimSortNumber()
'00_SomeFilePath.md' {
)); $this->assertEquals('file', DocumentationHelper::trim_sort_number(
} '0_file'
));
public function testTrimSortNumber() { $this->assertEquals('2.1', DocumentationHelper::trim_sort_number(
$this->assertEquals('file', DocumentationHelper::trim_sort_number( '2.1'
'0_file' ));
));
$this->assertEquals('2.1', DocumentationHelper::trim_sort_number( $this->assertEquals('dev/tasks/2.1', DocumentationHelper::trim_sort_number(
'2.1' 'dev/tasks/2.1'
)); ));
}
$this->assertEquals('dev/tasks/2.1', DocumentationHelper::trim_sort_number( public function testTrimExtension()
'dev/tasks/2.1' {
)); $this->assertEquals('file', DocumentationHelper::trim_extension_off(
} 'file.md'
));
public function testTrimExtension() { $this->assertEquals('dev/path/file', DocumentationHelper::trim_extension_off(
$this->assertEquals('file', DocumentationHelper::trim_extension_off( 'dev/path/file.md'
'file.md' ));
)); }
$this->assertEquals('dev/path/file', DocumentationHelper::trim_extension_off( public function testGetExtension()
'dev/path/file.md' {
)); $this->assertEquals('md', DocumentationHelper::get_extension(
} 'file.md'
));
public function testGetExtension() { $this->assertEquals('md', DocumentationHelper::get_extension(
$this->assertEquals('md', DocumentationHelper::get_extension( 'dev/tasks/file.md'
'file.md' ));
));
$this->assertEquals('md', DocumentationHelper::get_extension( $this->assertEquals('txt', DocumentationHelper::get_extension(
'dev/tasks/file.md' 'dev/tasks/file.txt'
)); ));
$this->assertEquals('txt', DocumentationHelper::get_extension( $this->assertNull(DocumentationHelper::get_extension(
'dev/tasks/file.txt' 'doc_test/2.3'
)); ));
$this->assertNull(DocumentationHelper::get_extension( $this->assertNull(DocumentationHelper::get_extension(
'doc_test/2.3' 'dev/docs/en/doc_test/2.3/subfolder'
)); ));
}
$this->assertNull(DocumentationHelper::get_extension(
'dev/docs/en/doc_test/2.3/subfolder'
));
}
} }

View File

@ -4,208 +4,219 @@
* @package docsviewer * @package docsviewer
* @subpackage tests * @subpackage tests
*/ */
class DocumentationManifestTests extends SapphireTest { class DocumentationManifestTests extends SapphireTest
{
private $manifest;
private $manifest; public function setUp()
{
parent::setUp();
public function setUp() { Config::nest();
parent::setUp();
Config::nest(); // explicitly use dev/docs. Custom paths should be tested separately
Config::inst()->update(
'DocumentationViewer', 'link_base', 'dev/docs'
);
// explicitly use dev/docs. Custom paths should be tested separately // disable automatic module registration so modules don't interfere.
Config::inst()->update( Config::inst()->update(
'DocumentationViewer', 'link_base', 'dev/docs' 'DocumentationManifest', 'automatic_registration', false
); );
// disable automatic module registration so modules don't interfere. Config::inst()->remove('DocumentationManifest', 'register_entities');
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'
),
array(
'Path' => DOCSVIEWER_PATH . "/tests/docs-manifest/",
'Title' => 'Manifest',
'Key' => 'manifest'
)
)
);
Config::inst()->update( $this->manifest = new DocumentationManifest(true);
'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'
),
array(
'Path' => DOCSVIEWER_PATH . "/tests/docs-manifest/",
'Title' => 'Manifest',
'Key' => 'manifest'
)
)
);
$this->manifest = new DocumentationManifest(true); public function tearDown()
} {
parent::tearDown();
public function tearDown() { Config::unnest();
parent::tearDown(); }
Config::unnest();
}
/** /**
* Check that the manifest matches what we'd expect. * Check that the manifest matches what we'd expect.
*/ */
public function testRegenerate() { public function testRegenerate()
$match = array( {
'de/testdocs/2.3/', $match = array(
'de/testdocs/2.3/german/', 'de/testdocs/2.3/',
'de/testdocs/2.3/test/', 'de/testdocs/2.3/german/',
'en/testdocs/2.3/', 'de/testdocs/2.3/test/',
'en/testdocs/2.3/sort/', 'en/testdocs/2.3/',
'en/testdocs/2.3/sort/basic/', 'en/testdocs/2.3/sort/',
'en/testdocs/2.3/sort/intermediate/', 'en/testdocs/2.3/sort/basic/',
'en/testdocs/2.3/sort/advanced/', 'en/testdocs/2.3/sort/intermediate/',
'en/testdocs/2.3/sort/some-page/', 'en/testdocs/2.3/sort/advanced/',
'en/testdocs/2.3/sort/another-page/', 'en/testdocs/2.3/sort/some-page/',
'en/testdocs/2.3/subfolder/', 'en/testdocs/2.3/sort/another-page/',
'en/testdocs/2.3/subfolder/subpage/', 'en/testdocs/2.3/subfolder/',
'en/testdocs/2.3/subfolder/subsubfolder/', 'en/testdocs/2.3/subfolder/subpage/',
'en/testdocs/2.3/subfolder/subsubfolder/subsubpage/', 'en/testdocs/2.3/subfolder/subsubfolder/',
'en/testdocs/2.3/test/', 'en/testdocs/2.3/subfolder/subsubfolder/subsubpage/',
'en/testdocs/2.4/', 'en/testdocs/2.3/test/',
'en/testdocs/2.4/test/', 'en/testdocs/2.4/',
'en/testdocs/3.0/', 'en/testdocs/2.4/test/',
'en/testdocs/3.0/changelog/', 'en/testdocs/3.0/',
'en/testdocs/3.0/tutorials/', 'en/testdocs/3.0/changelog/',
'en/testdocs/3.0/empty/', 'en/testdocs/3.0/tutorials/',
'en/manifest/', 'en/testdocs/3.0/empty/',
'en/manifest/guide/', 'en/manifest/',
'en/manifest/guide/test/', 'en/manifest/guide/',
'en/manifest/second-guide/', 'en/manifest/guide/test/',
'en/manifest/second-guide/afile/' 'en/manifest/second-guide/',
); 'en/manifest/second-guide/afile/'
);
$this->assertEquals($match, array_keys($this->manifest->getPages())); $this->assertEquals($match, array_keys($this->manifest->getPages()));
} }
public function testGetNextPage() { public function testGetNextPage()
// get next page at the end of one subfolder goes back up to the top {
// most directory // get next page at the end of one subfolder goes back up to the top
$this->assertStringEndsWith('2.3/test/', $this->manifest->getNextPage( // most directory
DOCSVIEWER_PATH . '/tests/docs/en/subfolder/subsubfolder/subsubpage.md', $this->assertStringEndsWith('2.3/test/', $this->manifest->getNextPage(
DOCSVIEWER_PATH . '/tests/docs/en/' DOCSVIEWER_PATH . '/tests/docs/en/subfolder/subsubfolder/subsubpage.md',
)->Link); DOCSVIEWER_PATH . '/tests/docs/en/'
)->Link);
// after sorting, 2 is shown. // after sorting, 2 is shown.
$this->assertContains('/intermediate/', $this->manifest->getNextPage( $this->assertContains('/intermediate/', $this->manifest->getNextPage(
DOCSVIEWER_PATH . '/tests/docs/en/sort/01-basic.md', DOCSVIEWER_PATH . '/tests/docs/en/sort/01-basic.md',
DOCSVIEWER_PATH . '/tests/docs/en/' DOCSVIEWER_PATH . '/tests/docs/en/'
)->Link); )->Link);
// next gets the following URL // next gets the following URL
$this->assertContains('/test/', $this->manifest->getNextPage( $this->assertContains('/test/', $this->manifest->getNextPage(
DOCSVIEWER_PATH . '/tests/docs-v2.4/en/index.md', DOCSVIEWER_PATH . '/tests/docs-v2.4/en/index.md',
DOCSVIEWER_PATH . '/tests/docs-v2.4/en/' DOCSVIEWER_PATH . '/tests/docs-v2.4/en/'
)->Link); )->Link);
// last folder in a entity does not leak // last folder in a entity does not leak
$this->assertNull($this->manifest->getNextPage( $this->assertNull($this->manifest->getNextPage(
DOCSVIEWER_PATH . '/tests/docs/en/test.md', DOCSVIEWER_PATH . '/tests/docs/en/test.md',
DOCSVIEWER_PATH . '/tests/docs/en/' DOCSVIEWER_PATH . '/tests/docs/en/'
)); ));
} }
public function testGetPreviousPage() { public function testGetPreviousPage()
// goes right into subfolders {
$this->assertContains('subfolder/subsubfolder/subsubpage', $this->manifest->getPreviousPage( // goes right into subfolders
DOCSVIEWER_PATH . '/tests/docs/en/test.md', $this->assertContains('subfolder/subsubfolder/subsubpage', $this->manifest->getPreviousPage(
DOCSVIEWER_PATH . '/tests/docs/en/' DOCSVIEWER_PATH . '/tests/docs/en/test.md',
)->Link); DOCSVIEWER_PATH . '/tests/docs/en/'
)->Link);
// does not leak between entities // does not leak between entities
$this->assertNull($this->manifest->getPreviousPage( $this->assertNull($this->manifest->getPreviousPage(
DOCSVIEWER_PATH . '/tests/docs/en/index.md', DOCSVIEWER_PATH . '/tests/docs/en/index.md',
DOCSVIEWER_PATH . '/tests/docs/en/' DOCSVIEWER_PATH . '/tests/docs/en/'
)); ));
// does not leak between entities // does not leak between entities
$this->assertNull($this->manifest->getPreviousPage( $this->assertNull($this->manifest->getPreviousPage(
DOCSVIEWER_PATH . ' /tests/docs/en/index.md', DOCSVIEWER_PATH . ' /tests/docs/en/index.md',
DOCSVIEWER_PATH . '/tests/docs/en/' DOCSVIEWER_PATH . '/tests/docs/en/'
)); ));
} }
public function testGetPage() { public function testGetPage()
$this->markTestIncomplete(); {
} $this->markTestIncomplete();
}
public function testGenerateBreadcrumbs() { public function testGenerateBreadcrumbs()
$this->markTestIncomplete(); {
} $this->markTestIncomplete();
}
public function testGetChildrenFor() { public function testGetChildrenFor()
$expected = array( {
array('Title' => 'Test', 'LinkingMode' => 'link') $expected = array(
); array('Title' => 'Test', 'LinkingMode' => 'link')
);
$this->assertDOSContains($expected, $this->manifest->getChildrenFor( $this->assertDOSContains($expected, $this->manifest->getChildrenFor(
DOCSVIEWER_PATH . "/tests/docs/en/" DOCSVIEWER_PATH . "/tests/docs/en/"
)); ));
$expected = array( $expected = array(
array('Title' => 'ChangeLog', 'LinkingMode' => 'current'), array('Title' => 'ChangeLog', 'LinkingMode' => 'current'),
array('Title' => 'Tutorials'), array('Title' => 'Tutorials'),
array('Title' => 'Empty') array('Title' => 'Empty')
); );
$this->assertDOSContains($expected, $this->manifest->getChildrenFor( $this->assertDOSContains($expected, $this->manifest->getChildrenFor(
DOCSVIEWER_PATH . '/tests/docs-v3.0/en/', DOCSVIEWER_PATH . '/tests/docs-v3.0/en/',
DOCSVIEWER_PATH . '/tests/docs-v3.0/en/ChangeLog.md' DOCSVIEWER_PATH . '/tests/docs-v3.0/en/ChangeLog.md'
)); ));
} }
public function testGetAllVersions() { public function testGetAllVersions()
$expected = array( {
'2.3' => '2.3', $expected = array(
'2.4' => '2.4', '2.3' => '2.3',
'3.0' => '3.0', '2.4' => '2.4',
'0.0' => 'Master' '3.0' => '3.0',
); '0.0' => 'Master'
);
$this->assertEquals($expected, $this->manifest->getAllVersions()); $this->assertEquals($expected, $this->manifest->getAllVersions());
} }
public function testGetAllEntityVersions() { public function testGetAllEntityVersions()
$expected = array( {
'Version' => '2.3', $expected = array(
'Version' => '2.4', 'Version' => '2.3',
'Version' => '3.0' 'Version' => '2.4',
); 'Version' => '3.0'
);
$entity = $this->manifest->getEntities()->find('Language', 'en'); $entity = $this->manifest->getEntities()->find('Language', 'en');
$this->assertEquals(3, $this->manifest->getAllVersionsOfEntity($entity)->count()); $this->assertEquals(3, $this->manifest->getAllVersionsOfEntity($entity)->count());
$entity = $this->manifest->getEntities()->find('Language', 'de'); $entity = $this->manifest->getEntities()->find('Language', 'de');
$this->assertEquals(1, $this->manifest->getAllVersionsOfEntity($entity)->count()); $this->assertEquals(1, $this->manifest->getAllVersionsOfEntity($entity)->count());
} }
public function testGetStableVersion() { public function testGetStableVersion()
$this->markTestIncomplete(); {
} $this->markTestIncomplete();
}
} }

View File

@ -4,87 +4,90 @@
* @package docsviewer * @package docsviewer
* @subpackage tests * @subpackage tests
*/ */
class DocumentationPageTest extends SapphireTest { class DocumentationPageTest extends SapphireTest
{
protected $entity;
protected $entity; public function setUp()
{
parent::setUp();
public function setUp() { $this->entity = new DocumentationEntity('doctest');
parent::setUp(); $this->entity->setPath(DOCSVIEWER_PATH . '/tests/docs/en/');
$this->entity->setVersion('2.4');
$this->entity->setLanguage('en');
$this->entity = new DocumentationEntity('doctest'); Config::nest();
$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/'
);
// explicitly use dev/docs. Custom paths should be tested separately $manifest = new DocumentationManifest(true);
Config::inst()->update( }
'DocumentationViewer', 'link_base', 'dev/docs/'
);
$manifest = new DocumentationManifest(true); public function tearDown()
} {
parent::tearDown();
public function tearDown() { Config::unnest();
parent::tearDown(); }
Config::unnest(); public function testGetLink()
} {
$page = new DocumentationPage(
$this->entity,
'test.md',
DOCSVIEWER_PATH . '/tests/docs/en/test.md'
);
public function testGetLink() { // single layer
$page = new DocumentationPage( $this->assertEquals('dev/docs/en/doctest/2.4/test/', $page->Link(),
$this->entity, 'The page link should have no extension and have a language'
'test.md', );
DOCSVIEWER_PATH . '/tests/docs/en/test.md'
);
// single layer $page = new DocumentationFolder(
$this->assertEquals('dev/docs/en/doctest/2.4/test/', $page->Link(), $this->entity,
'The page link should have no extension and have a language' 'sort',
); DOCSVIEWER_PATH . '/tests/docs/en/sort/'
);
$page = new DocumentationFolder( $this->assertEquals('dev/docs/en/doctest/2.4/sort/', $page->Link());
$this->entity,
'sort',
DOCSVIEWER_PATH . '/tests/docs/en/sort/'
);
$this->assertEquals('dev/docs/en/doctest/2.4/sort/', $page->Link()); $page = new DocumentationFolder(
$this->entity,
'1-basic.md',
DOCSVIEWER_PATH . '/tests/docs/en/sort/1-basic.md'
);
$page = new DocumentationFolder( $this->assertEquals('dev/docs/en/doctest/2.4/sort/basic/', $page->Link());
$this->entity, }
'1-basic.md',
DOCSVIEWER_PATH . '/tests/docs/en/sort/1-basic.md'
);
$this->assertEquals('dev/docs/en/doctest/2.4/sort/basic/', $page->Link()); public function testGetBreadcrumbTitle()
} {
$page = new DocumentationPage(
$this->entity,
'test.md',
DOCSVIEWER_PATH . '/tests/docs/en/test.md'
);
public function testGetBreadcrumbTitle() { $this->assertEquals("Test - Doctest", $page->getBreadcrumbTitle());
$page = new DocumentationPage(
$this->entity,
'test.md',
DOCSVIEWER_PATH . '/tests/docs/en/test.md'
);
$this->assertEquals("Test - Doctest", $page->getBreadcrumbTitle()); $page = new DocumentationFolder(
$this->entity,
'1-basic.md',
DOCSVIEWER_PATH . '/tests/docs/en/sort/1-basic.md'
);
$page = new DocumentationFolder( $this->assertEquals('Basic - Sort - Doctest', $page->getBreadcrumbTitle());
$this->entity,
'1-basic.md',
DOCSVIEWER_PATH . '/tests/docs/en/sort/1-basic.md'
);
$this->assertEquals('Basic - Sort - Doctest', $page->getBreadcrumbTitle()); $page = new DocumentationFolder(
$this->entity,
'',
DOCSVIEWER_PATH . '/tests/docs/en/sort/'
);
$page = new DocumentationFolder( $this->assertEquals('Sort - Doctest', $page->getBreadcrumbTitle());
$this->entity, }
'',
DOCSVIEWER_PATH . '/tests/docs/en/sort/'
);
$this->assertEquals('Sort - Doctest', $page->getBreadcrumbTitle());
}
} }

View File

@ -4,87 +4,90 @@
* @package docsviewer * @package docsviewer
* @subpackage tests * @subpackage tests
*/ */
class DocumentationParserTest extends SapphireTest { class DocumentationParserTest extends SapphireTest
{
protected $entity, $entityAlt, $page, $subPage, $subSubPage, $filePage, $metaDataPage, $indexPage;
protected $entity, $entityAlt, $page, $subPage, $subSubPage, $filePage, $metaDataPage, $indexPage; public function tearDown()
{
parent::tearDown();
public function tearDown() { Config::unnest();
parent::tearDown(); }
Config::unnest(); public function setUp()
} {
parent::setUp();
public function setUp() { Config::nest();
parent::setUp();
Config::nest(); // explicitly use dev/docs. Custom paths should be tested separately
Config::inst()->update(
'DocumentationViewer', 'link_base', 'dev/docs/'
);
// explicitly use dev/docs. Custom paths should be tested separately $this->entity = new DocumentationEntity('DocumentationParserTest');
Config::inst()->update( $this->entity->setPath(DOCSVIEWER_PATH . '/tests/docs/en/');
'DocumentationViewer', 'link_base', 'dev/docs/' $this->entity->setVersion('2.4');
); $this->entity->setLanguage('en');
$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 = new DocumentationEntity('DocumentationParserParserTest');
$this->entityAlt->setPath(DOCSVIEWER_PATH . '/tests/docs-parser/en/'); $this->entityAlt->setPath(DOCSVIEWER_PATH . '/tests/docs-parser/en/');
$this->entityAlt->setVersion('2.4'); $this->entityAlt->setVersion('2.4');
$this->entityAlt->setLanguage('en'); $this->entityAlt->setLanguage('en');
$this->page = new DocumentationPage( $this->page = new DocumentationPage(
$this->entity, $this->entity,
'test.md', 'test.md',
DOCSVIEWER_PATH . '/tests/docs/en/test.md' DOCSVIEWER_PATH . '/tests/docs/en/test.md'
); );
$this->subPage = new DocumentationPage( $this->subPage = new DocumentationPage(
$this->entity, $this->entity,
'subpage.md', 'subpage.md',
DOCSVIEWER_PATH. '/tests/docs/en/subfolder/subpage.md' DOCSVIEWER_PATH. '/tests/docs/en/subfolder/subpage.md'
); );
$this->subSubPage = new DocumentationPage( $this->subSubPage = new DocumentationPage(
$this->entity, $this->entity,
'subsubpage.md', 'subsubpage.md',
DOCSVIEWER_PATH. '/tests/docs/en/subfolder/subsubfolder/subsubpage.md' DOCSVIEWER_PATH. '/tests/docs/en/subfolder/subsubfolder/subsubpage.md'
); );
$this->filePage = new DocumentationPage( $this->filePage = new DocumentationPage(
$this->entityAlt, $this->entityAlt,
'file-download.md', 'file-download.md',
DOCSVIEWER_PATH . '/tests/docs-parser/en/file-download.md' DOCSVIEWER_PATH . '/tests/docs-parser/en/file-download.md'
); );
$this->metaDataPage = new DocumentationPage( $this->metaDataPage = new DocumentationPage(
$this->entityAlt, $this->entityAlt,
'MetaDataTest.md', 'MetaDataTest.md',
DOCSVIEWER_PATH . '/tests/docs-parser/en/MetaDataTest.md' DOCSVIEWER_PATH . '/tests/docs-parser/en/MetaDataTest.md'
); );
$this->indexPage = new DocumentationPage( $this->indexPage = new DocumentationPage(
$this->entity, $this->entity,
'index.md', 'index.md',
DOCSVIEWER_PATH. '/tests/docs/en/index.md' DOCSVIEWER_PATH. '/tests/docs/en/index.md'
); );
$manifest = new DocumentationManifest(true); $manifest = new DocumentationManifest(true);
} }
public function testRewriteCodeBlocks() { public function testRewriteCodeBlocks()
$codePage = new DocumentationPage( {
$this->entityAlt, $codePage = new DocumentationPage(
'CodeSnippets.md', $this->entityAlt,
DOCSVIEWER_PATH . '/tests/docs-parser/en/CodeSnippets.md' 'CodeSnippets.md',
); DOCSVIEWER_PATH . '/tests/docs-parser/en/CodeSnippets.md'
);
$result = DocumentationParser::rewrite_code_blocks( $result = DocumentationParser::rewrite_code_blocks(
$codePage->getMarkdown() $codePage->getMarkdown()
); );
$expected = <<<HTML $expected = <<<HTML
#### <% control Foo %> #### <% control Foo %>
``` ```
code block code block
@ -112,13 +115,13 @@ baz: qux
``` ```
HTML; HTML;
$this->assertEquals($expected, $result, 'Code blocks support line breaks'); $this->assertEquals($expected, $result, 'Code blocks support line breaks');
$result = DocumentationParser::rewrite_code_blocks( $result = DocumentationParser::rewrite_code_blocks(
$this->page->getMarkdown() $this->page->getMarkdown()
); );
$expected = <<<HTML $expected = <<<HTML
```php ```php
code block code block
with multiple with multiple
@ -130,24 +133,24 @@ lines
Normal text after code block Normal text after code block
HTML; HTML;
$this->assertContains($expected, $result, 'Custom code blocks with ::: prefix'); $this->assertContains($expected, $result, 'Custom code blocks with ::: prefix');
$expected = <<<HTML $expected = <<<HTML
``` ```
code block code block
without formatting prefix without formatting prefix
``` ```
HTML; HTML;
$this->assertContains($expected, $result, 'Traditional markdown code blocks'); $this->assertContains($expected, $result, 'Traditional markdown code blocks');
$expected = <<<HTML $expected = <<<HTML
``` ```
Fenced code block Fenced code block
``` ```
HTML; HTML;
$this->assertContains($expected, $result, 'Backtick code blocks'); $this->assertContains($expected, $result, 'Backtick code blocks');
$expected = <<<HTML $expected = <<<HTML
```php ```php
Fenced box with Fenced box with
@ -158,227 +161,232 @@ between
content content
``` ```
HTML; HTML;
$this->assertContains($expected, $result, 'Backtick with newlines'); $this->assertContains($expected, $result, 'Backtick with newlines');
} }
public function testRelativeLinks() { public function testRelativeLinks()
// index.md {
$result = DocumentationParser::rewrite_relative_links( // index.md
$this->indexPage->getMarkdown(), $result = DocumentationParser::rewrite_relative_links(
$this->indexPage $this->indexPage->getMarkdown(),
); $this->indexPage
);
$this->assertContains( $this->assertContains(
'[link: subfolder index](dev/docs/en/documentationparsertest/2.4/subfolder/)', '[link: subfolder index](dev/docs/en/documentationparsertest/2.4/subfolder/)',
$result $result
); );
// test.md // test.md
$result = DocumentationParser::rewrite_relative_links( $result = DocumentationParser::rewrite_relative_links(
$this->page->getMarkdown(), $this->page->getMarkdown(),
$this->page $this->page
); );
$this->assertContains( $this->assertContains(
'[link: subfolder index](dev/docs/en/documentationparsertest/2.4/subfolder/)', '[link: subfolder index](dev/docs/en/documentationparsertest/2.4/subfolder/)',
$result $result
); );
$this->assertContains( $this->assertContains(
'[link: subfolder page](dev/docs/en/documentationparsertest/2.4/subfolder/subpage/)', '[link: subfolder page](dev/docs/en/documentationparsertest/2.4/subfolder/subpage/)',
$result $result
); );
$this->assertContains( $this->assertContains(
'[link: http](http://silverstripe.org)', '[link: http](http://silverstripe.org)',
$result $result
); );
$this->assertContains( $this->assertContains(
'[link: api](api:DataObject)', '[link: api](api:DataObject)',
$result $result
); );
$result = DocumentationParser::rewrite_relative_links( $result = DocumentationParser::rewrite_relative_links(
$this->subPage->getMarkdown(), $this->subPage->getMarkdown(),
$this->subPage $this->subPage
); );
# @todo this should redirect to /subpage/ # @todo this should redirect to /subpage/
$this->assertContains( $this->assertContains(
'[link: relative](dev/docs/en/documentationparsertest/2.4/subfolder/subpage.md/)', '[link: relative](dev/docs/en/documentationparsertest/2.4/subfolder/subpage.md/)',
$result $result
); );
$this->assertContains( $this->assertContains(
'[link: absolute index](dev/docs/en/documentationparsertest/2.4/)', '[link: absolute index](dev/docs/en/documentationparsertest/2.4/)',
$result $result
); );
# @todo this should redirect to / # @todo this should redirect to /
$this->assertContains( $this->assertContains(
'[link: absolute index with name](dev/docs/en/documentationparsertest/2.4/index/)', '[link: absolute index with name](dev/docs/en/documentationparsertest/2.4/index/)',
$result $result
); );
$this->assertContains( $this->assertContains(
'[link: relative index](dev/docs/en/documentationparsertest/2.4/)', '[link: relative index](dev/docs/en/documentationparsertest/2.4/)',
$result $result
); );
$this->assertContains( $this->assertContains(
'[link: relative parent page](dev/docs/en/documentationparsertest/2.4/test/)', '[link: relative parent page](dev/docs/en/documentationparsertest/2.4/test/)',
$result $result
); );
$this->assertContains( $this->assertContains(
'[link: absolute parent page](dev/docs/en/documentationparsertest/2.4/test/)', '[link: absolute parent page](dev/docs/en/documentationparsertest/2.4/test/)',
$result $result
); );
$result = DocumentationParser::rewrite_relative_links( $result = DocumentationParser::rewrite_relative_links(
$this->subSubPage->getMarkdown(), $this->subSubPage->getMarkdown(),
$this->subSubPage $this->subSubPage
); );
$this->assertContains( $this->assertContains(
'[link: absolute index](dev/docs/en/documentationparsertest/2.4/)', '[link: absolute index](dev/docs/en/documentationparsertest/2.4/)',
$result $result
); );
$this->assertContains( $this->assertContains(
'[link: relative index](dev/docs/en/documentationparsertest/2.4/subfolder/)', '[link: relative index](dev/docs/en/documentationparsertest/2.4/subfolder/)',
$result $result
); );
$this->assertContains( $this->assertContains(
'[link: relative parent page](dev/docs/en/documentationparsertest/2.4/subfolder/subpage/)', '[link: relative parent page](dev/docs/en/documentationparsertest/2.4/subfolder/subpage/)',
$result $result
); );
$this->assertContains( $this->assertContains(
'[link: relative grandparent page](dev/docs/en/documentationparsertest/2.4/test/)', '[link: relative grandparent page](dev/docs/en/documentationparsertest/2.4/test/)',
$result $result
); );
$this->assertContains( $this->assertContains(
'[link: absolute page](dev/docs/en/documentationparsertest/2.4/test/)', '[link: absolute page](dev/docs/en/documentationparsertest/2.4/test/)',
$result $result
); );
} }
public function testGenerateHtmlId() { 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-one', DocumentationParser::generate_html_id('title one'));
$this->assertEquals('title-and-one', DocumentationParser::generate_html_id('Title &amp; One')); $this->assertEquals('title-one', DocumentationParser::generate_html_id('Title one'));
$this->assertEquals('title-and-one', DocumentationParser::generate_html_id('Title & One')); $this->assertEquals('title-and-one', DocumentationParser::generate_html_id('Title &amp; One'));
$this->assertEquals('title-one', DocumentationParser::generate_html_id(' Title one ')); $this->assertEquals('title-and-one', DocumentationParser::generate_html_id('Title & One'));
$this->assertEquals('title-one', DocumentationParser::generate_html_id('Title--one')); $this->assertEquals('title-one', DocumentationParser::generate_html_id(' Title one '));
} $this->assertEquals('title-one', DocumentationParser::generate_html_id('Title--one'));
}
public function testImageRewrites() { public function testImageRewrites()
{
$result = DocumentationParser::rewrite_image_links(
$this->subPage->getMarkdown(),
$this->subPage
);
$result = DocumentationParser::rewrite_image_links( $expected = Controller::join_links(
$this->subPage->getMarkdown(), Director::absoluteBaseURL(), DOCSVIEWER_DIR, '/tests/docs/en/subfolder/_images/image.png'
$this->subPage );
);
$expected = Controller::join_links( $this->assertContains(
Director::absoluteBaseURL(), DOCSVIEWER_DIR, '/tests/docs/en/subfolder/_images/image.png' sprintf('[relative image link](%s)', $expected),
); $result
);
$this->assertContains( $this->assertContains(
sprintf('[relative image link](%s)', $expected), sprintf('[parent image link](%s)', Controller::join_links(
$result Director::absoluteBaseURL(), DOCSVIEWER_DIR, '/tests/docs/en/_images/image.png'
); )),
$result
);
$this->assertContains( $expected = Controller::join_links(
sprintf('[parent image link](%s)', Controller::join_links( Director::absoluteBaseURL(), DOCSVIEWER_DIR, '/tests/docs/en/_images/image.png'
Director::absoluteBaseURL(), DOCSVIEWER_DIR, '/tests/docs/en/_images/image.png' );
)),
$result
);
$expected = Controller::join_links( $this->assertContains(
Director::absoluteBaseURL(), DOCSVIEWER_DIR, '/tests/docs/en/_images/image.png' sprintf('[absolute image link](%s)', $expected),
); $result
);
}
$this->assertContains( public function testApiLinks()
sprintf('[absolute image link](%s)', $expected), {
$result $result = DocumentationParser::rewrite_api_links(
); $this->page->getMarkdown(),
} $this->page
);
public function testApiLinks() { $this->assertContains(
$result = DocumentationParser::rewrite_api_links( '[link: api](http://api.silverstripe.org/search/lookup/?q=DataObject&amp;version=2.4&amp;module=documentationparsertest)',
$this->page->getMarkdown(), $result
$this->page );
); $this->assertContains(
'[DataObject::$has_one](http://api.silverstripe.org/search/lookup/?q=DataObject::$has_one&amp;version=2.4&amp;module=documentationparsertest)',
$result
);
}
$this->assertContains( public function testHeadlineAnchors()
'[link: api](http://api.silverstripe.org/search/lookup/?q=DataObject&amp;version=2.4&amp;module=documentationparsertest)', {
$result $result = DocumentationParser::rewrite_heading_anchors(
); $this->page->getMarkdown(),
$this->assertContains( $this->page
'[DataObject::$has_one](http://api.silverstripe.org/search/lookup/?q=DataObject::$has_one&amp;version=2.4&amp;module=documentationparsertest)', );
$result
);
}
public function testHeadlineAnchors() { /*
$result = DocumentationParser::rewrite_heading_anchors( # Heading one {#Heading-one}
$this->page->getMarkdown(),
$this->page
);
/* # Heading with custom anchor {#custom-anchor} {#Heading-with-custom-anchor-custom-anchor}
# Heading one {#Heading-one}
# Heading with custom anchor {#custom-anchor} {#Heading-with-custom-anchor-custom-anchor} ## Heading two {#Heading-two}
## Heading two {#Heading-two} ### Heading three {#Heading-three}
### Heading three {#Heading-three} ## Heading duplicate {#Heading-duplicate}
## Heading duplicate {#Heading-duplicate} ## Heading duplicate {#Heading-duplicate-2}
## Heading duplicate {#Heading-duplicate-2} ## Heading duplicate {#Heading-duplicate-3}
## Heading duplicate {#Heading-duplicate-3} */
*/ $this->assertContains('# Heading one {#heading-one}', $result);
$this->assertContains('# Heading with custom anchor {#custom-anchor}', $result);
$this->assertContains('# Heading one {#heading-one}', $result); $this->assertNotContains('# Heading with custom anchor {#custom-anchor} {#heading', $result);
$this->assertContains('# Heading with custom anchor {#custom-anchor}', $result); $this->assertContains('# Heading two {#heading-two}', $result);
$this->assertNotContains('# Heading with custom anchor {#custom-anchor} {#heading', $result); $this->assertContains('# Heading three {#heading-three}', $result);
$this->assertContains('# Heading two {#heading-two}', $result); $this->assertContains('## Heading duplicate {#heading-duplicate}', $result);
$this->assertContains('# Heading three {#heading-three}', $result); $this->assertContains('## Heading duplicate {#heading-duplicate-2}', $result);
$this->assertContains('## Heading duplicate {#heading-duplicate}', $result); $this->assertContains('## Heading duplicate {#heading-duplicate-3}', $result);
$this->assertContains('## Heading duplicate {#heading-duplicate-2}', $result); }
$this->assertContains('## Heading duplicate {#heading-duplicate-3}', $result);
}
public function testRetrieveMetaData() { public function testRetrieveMetaData()
DocumentationParser::retrieve_meta_data($this->metaDataPage); {
DocumentationParser::retrieve_meta_data($this->metaDataPage);
$this->assertEquals('Dr. Foo Bar.', $this->metaDataPage->author); $this->assertEquals('Dr. Foo Bar.', $this->metaDataPage->author);
$this->assertEquals("Foo Bar's Test page.", $this->metaDataPage->getTitle()); $this->assertEquals("Foo Bar's Test page.", $this->metaDataPage->getTitle());
} }
public function testRewritingRelativeLinksToFiles() { public function testRewritingRelativeLinksToFiles()
$parsed = DocumentationParser::parse($this->filePage); {
$parsed = DocumentationParser::parse($this->filePage);
$this->assertContains( $this->assertContains(
DOCSVIEWER_DIR .'/tests/docs-parser/en/_images/external_link.png', DOCSVIEWER_DIR .'/tests/docs-parser/en/_images/external_link.png',
$parsed $parsed
); );
$this->assertContains( $this->assertContains(
DOCSVIEWER_DIR .'/tests/docs-parser/en/_images/test.tar.gz', DOCSVIEWER_DIR .'/tests/docs-parser/en/_images/test.tar.gz',
$parsed $parsed
); );
} }
} }

View File

@ -4,40 +4,42 @@
* @package docsviewer * @package docsviewer
* @subpackage tests * @subpackage tests
*/ */
class DocumentationPermalinksTest extends FunctionalTest { class DocumentationPermalinksTest extends FunctionalTest
{
public function testSavingAndAccessingMapping()
{
// basic test
DocumentationPermalinks::add(array(
'foo' => 'en/framework/subfolder/foo',
'bar' => 'en/cms/bar'
));
public function testSavingAndAccessingMapping() { $this->assertEquals('en/framework/subfolder/foo',
// basic test DocumentationPermalinks::map('foo')
DocumentationPermalinks::add(array( );
'foo' => 'en/framework/subfolder/foo',
'bar' => 'en/cms/bar'
));
$this->assertEquals('en/framework/subfolder/foo', $this->assertEquals('en/cms/bar',
DocumentationPermalinks::map('foo') DocumentationPermalinks::map('bar')
); );
}
$this->assertEquals('en/cms/bar', /**
DocumentationPermalinks::map('bar') * Tests to make sure short codes get translated to full paths.
); *
} */
public function testRedirectingMapping()
{
DocumentationPermalinks::add(array(
'foo' => 'en/framework/subfolder/foo',
'bar' => 'en/cms/bar'
));
/** $this->autoFollowRedirection = false;
* Tests to make sure short codes get translated to full paths.
*
*/
public function testRedirectingMapping() {
DocumentationPermalinks::add(array(
'foo' => 'en/framework/subfolder/foo',
'bar' => 'en/cms/bar'
));
$this->autoFollowRedirection = false; $v = new DocumentationViewer();
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'foo'), DataModel::inst());
$v = new DocumentationViewer(); $this->assertEquals('301', $response->getStatusCode());
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'foo'), DataModel::inst()); $this->assertContains('en/framework/subfolder/foo', $response->getHeader('Location'));
}
$this->assertEquals('301', $response->getStatusCode());
$this->assertContains('en/framework/subfolder/foo', $response->getHeader('Location'));
}
} }

View File

@ -5,62 +5,65 @@
* @subpackage tests * @subpackage tests
*/ */
class DocumentationSearchTest extends FunctionalTest { class DocumentationSearchTest extends FunctionalTest
{
public function setUp()
{
parent::setUp();
public function setUp() { Config::nest();
parent::setUp();
Config::nest(); // explicitly use dev/docs. Custom paths should be tested separately
Config::inst()->update(
'DocumentationViewer', 'link_base', 'dev/docs'
);
// explicitly use dev/docs. Custom paths should be tested separately // disable automatic module registration so modules don't interfere.
Config::inst()->update( Config::inst()->update(
'DocumentationViewer', 'link_base', 'dev/docs' 'DocumentationManifest', 'automatic_registration', false
); );
// disable automatic module registration so modules don't interfere. Config::inst()->remove('DocumentationManifest', 'register_entities');
Config::inst()->update( Config::inst()->update('DocumentationSearch', 'enabled', true);
'DocumentationManifest', 'automatic_registration', false Config::inst()->update(
); 'DocumentationManifest', 'register_entities', array(
array(
'Path' => DOCSVIEWER_PATH . "/tests/docs-search/",
'Title' => 'Docs Search Test', )
)
);
Config::inst()->remove('DocumentationManifest', 'register_entities'); $this->manifest = new DocumentationManifest(true);
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); public function tearDown()
} {
parent::tearDown();
public function tearDown() { Config::unnest();
parent::tearDown(); }
Config::unnest(); public function testOpenSearchControllerAccessible()
} {
$c = new DocumentationOpenSearchController();
$response = $c->handleRequest(new SS_HTTPRequest('GET', ''), DataModel::inst());
$this->assertEquals(404, $response->getStatusCode());
public function testOpenSearchControllerAccessible() { Config::inst()->update('DocumentationSearch', 'enabled', false);
$c = new DocumentationOpenSearchController();
$response = $c->handleRequest(new SS_HTTPRequest('GET', ''), DataModel::inst());
$this->assertEquals(404, $response->getStatusCode());
Config::inst()->update('DocumentationSearch', 'enabled', false); $response = $c->handleRequest(new SS_HTTPRequest('GET', 'description/'), DataModel::inst());
$this->assertEquals(404, $response->getStatusCode());
$response = $c->handleRequest(new SS_HTTPRequest('GET', 'description/'), DataModel::inst()); // test we get a response to the description. The meta data test will
$this->assertEquals(404, $response->getStatusCode()); // check that the individual fields are valid but we should check urls
// are there
// test we get a response to the description. The meta data test will Config::inst()->update('DocumentationSearch', 'enabled', true);
// 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());
$response = $c->handleRequest(new SS_HTTPRequest('GET', 'description'), DataModel::inst()); $desc = new SimpleXMLElement($response->getBody());
$this->assertEquals(200, $response->getStatusCode()); $this->assertEquals(2, count($desc->Url));
}
$desc = new SimpleXMLElement($response->getBody());
$this->assertEquals(2, count($desc->Url));
}
} }

View File

@ -8,208 +8,213 @@
* @subpackage tests * @subpackage tests
*/ */
class DocumentationViewerTest extends FunctionalTest { class DocumentationViewerTest extends FunctionalTest
{
protected $autoFollowRedirection = false;
protected $autoFollowRedirection = false; protected $manifest;
protected $manifest; public function setUp()
{
parent::setUp();
public function setUp() { Config::nest();
parent::setUp();
Config::nest(); // explicitly use dev/docs. Custom paths should be tested separately
Config::inst()->update(
'DocumentationViewer', 'link_base', 'dev/docs/'
);
// explicitly use dev/docs. Custom paths should be tested separately // disable automatic module registration so modules don't interfere.
Config::inst()->update( Config::inst()->update(
'DocumentationViewer', 'link_base', 'dev/docs/' 'DocumentationManifest', 'automatic_registration', false
); );
// disable automatic module registration so modules don't interfere. Config::inst()->remove('DocumentationManifest', 'register_entities');
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'
)
)
);
Config::inst()->update( Config::inst()->update('SSViewer', 'theme_enabled', false);
'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'
)
)
);
Config::inst()->update('SSViewer', 'theme_enabled', false); $this->manifest = new DocumentationManifest(true);
}
$this->manifest = new DocumentationManifest(true); public function tearDown()
} {
parent::tearDown();
public function tearDown() { Config::unnest();
parent::tearDown(); }
Config::unnest(); /**
} * This tests that all the locations will exist if we access it via the urls.
*/
public function testLocationsExists()
{
$this->autoFollowRedirection = false;
/** $response = $this->get('dev/docs/en/doc_test/2.3/subfolder/');
* This tests that all the locations will exist if we access it via the urls. $this->assertEquals($response->getStatusCode(), 200, 'Existing base folder');
*/
public function testLocationsExists() {
$this->autoFollowRedirection = false;
$response = $this->get('dev/docs/en/doc_test/2.3/subfolder/'); $response = $this->get('dev/docs/en/doc_test/2.3/subfolder/subsubfolder/');
$this->assertEquals($response->getStatusCode(), 200, 'Existing base folder'); $this->assertEquals($response->getStatusCode(), 200, 'Existing base folder');
$response = $this->get('dev/docs/en/doc_test/2.3/subfolder/subsubfolder/'); $response = $this->get('dev/docs/en/doc_test/2.3/subfolder/subsubfolder/subsubpage/');
$this->assertEquals($response->getStatusCode(), 200, 'Existing base folder'); $this->assertEquals($response->getStatusCode(), 200, 'Existing base folder');
$response = $this->get('dev/docs/en/doc_test/2.3/subfolder/subsubfolder/subsubpage/'); $response = $this->get('dev/docs/en/');
$this->assertEquals($response->getStatusCode(), 200, 'Existing base folder'); $this->assertEquals($response->getStatusCode(), 200, 'Lists the home index');
$response = $this->get('dev/docs/en/'); $response = $this->get('dev/docs/');
$this->assertEquals($response->getStatusCode(), 200, 'Lists the home index'); $this->assertEquals($response->getStatusCode(), 302, 'Go to english view');
$response = $this->get('dev/docs/');
$this->assertEquals($response->getStatusCode(), 302, 'Go to english view');
$response = $this->get('dev/docs/en/doc_test/3.0/empty.md'); $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/'); $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 // 2.4 is the stable release. Not in the URL
$response = $this->get('dev/docs/en/doc_test/2.4'); $response = $this->get('dev/docs/en/doc_test/2.4');
$this->assertEquals($response->getStatusCode(), 200, 'Existing base folder'); $this->assertEquals($response->getStatusCode(), 200, 'Existing base folder');
$this->assertContains('english test', $response->getBody(), 'Toplevel content page'); $this->assertContains('english test', $response->getBody(), 'Toplevel content page');
// accessing base redirects to the version with the version number. // accessing base redirects to the version with the version number.
$response = $this->get('dev/docs/en/doc_test/'); $response = $this->get('dev/docs/en/doc_test/');
$this->assertEquals($response->getStatusCode(), 301, 'Existing base folder redirects to with version'); $this->assertEquals($response->getStatusCode(), 301, 'Existing base folder redirects to with version');
$response = $this->get('dev/docs/en/doc_test/3.0/'); $response = $this->get('dev/docs/en/doc_test/3.0/');
$this->assertEquals($response->getStatusCode(), 200, 'Existing base folder'); $this->assertEquals($response->getStatusCode(), 200, 'Existing base folder');
$response = $this->get('dev/docs/en/doc_test/2.3/nonexistant-subfolder'); $response = $this->get('dev/docs/en/doc_test/2.3/nonexistant-subfolder');
$this->assertEquals($response->getStatusCode(), 404, 'Nonexistant subfolder'); $this->assertEquals($response->getStatusCode(), 404, 'Nonexistant subfolder');
$response = $this->get('dev/docs/en/doc_test/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'); $this->assertEquals($response->getStatusCode(), 301, 'Nonexistant file');
$response = $this->get('dev/docs/en/doc_test/2.3/nonexistant-file/'); $response = $this->get('dev/docs/en/doc_test/2.3/nonexistant-file/');
$this->assertEquals($response->getStatusCode(), 404, 'Nonexistant file'); $this->assertEquals($response->getStatusCode(), 404, 'Nonexistant file');
$response = $this->get('dev/docs/en/doc_test/2.3/test'); $response = $this->get('dev/docs/en/doc_test/2.3/test');
$this->assertEquals($response->getStatusCode(), 200, 'Existing file'); $this->assertEquals($response->getStatusCode(), 200, 'Existing file');
$response = $this->get('dev/docs/en/doc_test/3.0/empty?foo'); $response = $this->get('dev/docs/en/doc_test/3.0/empty?foo');
$this->assertEquals(200, $response->getStatusCode(), 'Existing page'); $this->assertEquals(200, $response->getStatusCode(), 'Existing page');
$response = $this->get('dev/docs/en/doc_test/3.0/empty/'); $response = $this->get('dev/docs/en/doc_test/3.0/empty/');
$this->assertEquals($response->getStatusCode(), 200, 'Existing page'); $this->assertEquals($response->getStatusCode(), 200, 'Existing page');
$response = $this->get('dev/docs/en/doc_test/3.0/test'); $response = $this->get('dev/docs/en/doc_test/3.0/test');
$this->assertEquals($response->getStatusCode(), 404, 'Missing page'); $this->assertEquals($response->getStatusCode(), 404, 'Missing page');
$response = $this->get('dev/docs/en/doc_test/3.0/test.md'); $response = $this->get('dev/docs/en/doc_test/3.0/test.md');
$this->assertEquals($response->getStatusCode(), 301, 'Missing page'); $this->assertEquals($response->getStatusCode(), 301, 'Missing page');
$response = $this->get('dev/docs/en/doc_test/3.0/test/'); $response = $this->get('dev/docs/en/doc_test/3.0/test/');
$this->assertEquals($response->getStatusCode(), 404, 'Missing page'); $this->assertEquals($response->getStatusCode(), 404, 'Missing page');
$response = $this->get('dev/docs/dk/'); $response = $this->get('dev/docs/dk/');
$this->assertEquals($response->getStatusCode(), 404, 'Access a language that doesn\'t exist'); $this->assertEquals($response->getStatusCode(), 404, 'Access a language that doesn\'t exist');
} }
public function testGetMenu() { public function testGetMenu()
$v = new DocumentationViewer(); {
// check with children $v = new DocumentationViewer();
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'en/doc_test/2.3/'), DataModel::inst()); // check with children
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'en/doc_test/2.3/'), DataModel::inst());
$expected = array( $expected = array(
'dev/docs/en/doc_test/2.3/sort/' => 'Sort', '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/subfolder/' => 'Subfolder',
'dev/docs/en/doc_test/2.3/test/' => 'Test' 'dev/docs/en/doc_test/2.3/test/' => 'Test'
); );
$actual = $v->getMenu()->first()->Children->map('Link', 'Title'); $actual = $v->getMenu()->first()->Children->map('Link', 'Title');
$this->assertEquals($expected, $actual); $this->assertEquals($expected, $actual);
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'en/doc_test/2.4/'), DataModel::inst()); $response = $v->handleRequest(new SS_HTTPRequest('GET', 'en/doc_test/2.4/'), DataModel::inst());
$this->assertEquals('current', $v->getMenu()->first()->LinkingMode); $this->assertEquals('current', $v->getMenu()->first()->LinkingMode);
// 2.4 stable release has 1 child page (not including index) // 2.4 stable release has 1 child page (not including index)
$this->assertEquals(1, $v->getMenu()->first()->Children->count()); $this->assertEquals(1, $v->getMenu()->first()->Children->count());
// menu should contain all the english entities // menu should contain all the english entities
$expected = array( $expected = array(
'dev/docs/en/doc_test/2.4/' => 'Doc Test', 'dev/docs/en/doc_test/2.4/' => 'Doc Test',
'dev/docs/en/documentationvieweraltmodule1/' => 'DocumentationViewerAltModule1', 'dev/docs/en/documentationvieweraltmodule1/' => 'DocumentationViewerAltModule1',
'dev/docs/en/documentationvieweraltmodule2/' => 'DocumentationViewerAltModule2' 'dev/docs/en/documentationvieweraltmodule2/' => 'DocumentationViewerAltModule2'
); );
$this->assertEquals($expected, $v->getMenu()->map('Link', 'Title')); $this->assertEquals($expected, $v->getMenu()->map('Link', 'Title'));
} }
public function testGetLanguage() { public function testGetLanguage()
$v = new DocumentationViewer(); {
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'en/doc_test/2.3/'), DataModel::inst()); $v = new DocumentationViewer();
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'en/doc_test/2.3/'), DataModel::inst());
$this->assertEquals('en', $v->getLanguage()); $this->assertEquals('en', $v->getLanguage());
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'en/doc_test/2.3/subfolder/subsubfolder/subsubpage/'), DataModel::inst()); $response = $v->handleRequest(new SS_HTTPRequest('GET', 'en/doc_test/2.3/subfolder/subsubfolder/subsubpage/'), DataModel::inst());
$this->assertEquals('en', $v->getLanguage()); $this->assertEquals('en', $v->getLanguage());
} }
public function testAccessingAll() { public function testAccessingAll()
$response = $this->get('dev/docs/en/all/'); {
$response = $this->get('dev/docs/en/all/');
// should response with the documentation index // should response with the documentation index
$this->assertEquals(200, $response->getStatusCode()); $this->assertEquals(200, $response->getStatusCode());
$items = $this->cssParser()->getBySelector('#documentation_index'); $items = $this->cssParser()->getBySelector('#documentation_index');
$this->assertNotEmpty($items); $this->assertNotEmpty($items);
// should also have a DE version of the page // should also have a DE version of the page
$response = $this->get('dev/docs/de/all/'); $response = $this->get('dev/docs/de/all/');
// should response with the documentation index // should response with the documentation index
$this->assertEquals(200, $response->getStatusCode()); $this->assertEquals(200, $response->getStatusCode());
$items = $this->cssParser()->getBySelector('#documentation_index'); $items = $this->cssParser()->getBySelector('#documentation_index');
$this->assertNotEmpty($items); $this->assertNotEmpty($items);
// accessing a language that doesn't exist should throw a 404 // accessing a language that doesn't exist should throw a 404
$response = $this->get('dev/docs/fu/all/'); $response = $this->get('dev/docs/fu/all/');
$this->assertEquals(404, $response->getStatusCode()); $this->assertEquals(404, $response->getStatusCode());
// accessing all without a language should fail // accessing all without a language should fail
$response = $this->get('dev/docs/all/'); $response = $this->get('dev/docs/all/');
$this->assertEquals(404, $response->getStatusCode()); $this->assertEquals(404, $response->getStatusCode());
}
}
} }

View File

@ -4,83 +4,85 @@
* @package docsviewer * @package docsviewer
* @subpackage tests * @subpackage tests
*/ */
class DocumentationViewerVersionWarningTest extends SapphireTest { class DocumentationViewerVersionWarningTest extends SapphireTest
{
protected $autoFollowRedirection = false;
protected $autoFollowRedirection = false; private $manifest;
private $manifest; public function setUp()
{
parent::setUp();
public function setUp() { Config::nest();
parent::setUp();
Config::nest(); // explicitly use dev/docs. Custom paths should be tested separately
Config::inst()->update(
'DocumentationViewer', 'link_base', 'dev/docs'
);
// explicitly use dev/docs. Custom paths should be tested separately // disable automatic module registration so modules don't interfere.
Config::inst()->update( Config::inst()->update(
'DocumentationViewer', 'link_base', 'dev/docs' 'DocumentationManifest', 'automatic_registration', false
); );
// disable automatic module registration so modules don't interfere. Config::inst()->remove('DocumentationManifest', 'register_entities');
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'
)
)
);
Config::inst()->update( $this->manifest = new DocumentationManifest(true);
'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 tearDown()
} {
parent::tearDown();
public function tearDown() { Config::unnest();
parent::tearDown(); }
Config::unnest(); 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', 'en/testdocs/'), DataModel::inst());
public function testVersionWarning() { $this->assertFalse($v->VersionWarning());
$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', 'en/testdocs/'), DataModel::inst());
$this->assertFalse($v->VersionWarning());
// 2.3 is an older release, hitting that should return us an outdated flag // 2.3 is an older release, hitting that should return us an outdated flag
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'en/testdocs/2.3/'), DataModel::inst()); $response = $v->handleRequest(new SS_HTTPRequest('GET', 'en/testdocs/2.3/'), DataModel::inst());
$warn = $v->VersionWarning(); $warn = $v->VersionWarning();
$this->assertTrue($warn->OutdatedRelease); $this->assertTrue($warn->OutdatedRelease);
$this->assertNull($warn->FutureRelease); $this->assertNull($warn->FutureRelease);
// 3.0 is a future release // 3.0 is a future release
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'en/testdocs/3.0/'), DataModel::inst()); $response = $v->handleRequest(new SS_HTTPRequest('GET', 'en/testdocs/3.0/'), DataModel::inst());
$warn = $v->VersionWarning(); $warn = $v->VersionWarning();
$this->assertNull($warn->OutdatedRelease); $this->assertNull($warn->OutdatedRelease);
$this->assertTrue($warn->FutureRelease); $this->assertTrue($warn->FutureRelease);
} }
} }