From 43b6d4271938bac37d2b8069095cdbd76189c46e Mon Sep 17 00:00:00 2001 From: Will Rossiter Date: Sun, 7 Sep 2014 11:26:12 +1200 Subject: [PATCH] Overhaul of module to use DocumentationManifest This major update changes the behaviour of the docviewer module to use a cached manifest rather than on demand. This allows us to simplify the URL matching and store 'nice' URL configuration rather than altering handleAction(). --- README.md | 46 +- _config.php | 6 - code/DocumentationHelper.php | 68 ++ code/DocumentationManifest.php | 267 +++++++ code/DocumentationManifestFileFinder.php | 38 + code/DocumentationParser.php | 2 +- code/DocumentationService.php | 500 ++---------- .../DocumentationSearchController.php | 109 +++ code/controllers/DocumentationViewer.php | 727 +++--------------- code/forms/DocumentationSearchForm.php | 32 + code/models/DocumentationEntity.php | 246 ++---- code/models/DocumentationEntityLanguage.php | 83 ++ code/models/DocumentationEntityVersion.php | 130 ++++ code/models/DocumentationFolder.php | 19 + code/models/DocumentationPage.php | 322 +++----- code/tasks/DocumentationBuild.php | 14 + composer.json | 4 - css/layout.css | 5 + docs/en/Syntax-Highlighting.md | 7 +- docs/en/Writing-Documentation.md | 62 -- ...figuration-Options.md => configuration.md} | 76 +- docs/en/index.md | 2 +- docs/en/{ss-markdown.md => markdown.md} | 3 +- docs/en/statichtml.md | 24 + javascript/DocumentationViewer.js | 12 +- .../Includes/DocumentationBreadcrumbs.ss | 10 +- .../Includes/DocumentationNextPrevious.ss | 11 + templates/Includes/DocumentationSidebar.ss | 24 +- .../Includes/DocumentationTableContents.ss | 1 + templates/Layout/DocumentationViewer.ss | 19 - ...DocumentationViewer_DocumentationFolder.ss | 17 + .../DocumentationViewer_DocumentationPage.ss | 20 + .../Layout/DocumentationViewer_folder.ss | 15 - templates/Layout/DocumentationViewer_home.ss | 13 - .../Layout/DocumentationViewer_results.ss | 81 +- tests/DocumentationEntityTest.php | 34 - tests/DocumentationManifestTests.php | 85 ++ tests/DocumentationPageTest.php | 10 +- tests/DocumentationViewerTest.php | 13 +- tests/docs-withoutlang/fail.md | 1 + tests/docs/de/german.md | 2 + 41 files changed, 1398 insertions(+), 1762 deletions(-) create mode 100644 code/DocumentationManifest.php create mode 100644 code/DocumentationManifestFileFinder.php create mode 100644 code/controllers/DocumentationSearchController.php create mode 100644 code/forms/DocumentationSearchForm.php create mode 100644 code/models/DocumentationEntityLanguage.php create mode 100644 code/models/DocumentationEntityVersion.php create mode 100644 code/models/DocumentationFolder.php create mode 100644 code/tasks/DocumentationBuild.php delete mode 100755 docs/en/Writing-Documentation.md rename docs/en/{Configuration-Options.md => configuration.md} (59%) rename docs/en/{ss-markdown.md => markdown.md} (99%) create mode 100644 docs/en/statichtml.md create mode 100644 templates/Includes/DocumentationNextPrevious.ss create mode 100644 templates/Includes/DocumentationTableContents.ss delete mode 100755 templates/Layout/DocumentationViewer.ss create mode 100755 templates/Layout/DocumentationViewer_DocumentationFolder.ss create mode 100755 templates/Layout/DocumentationViewer_DocumentationPage.ss delete mode 100755 templates/Layout/DocumentationViewer_folder.ss delete mode 100755 templates/Layout/DocumentationViewer_home.ss delete mode 100755 tests/DocumentationEntityTest.php create mode 100644 tests/DocumentationManifestTests.php create mode 100644 tests/docs-withoutlang/fail.md create mode 100644 tests/docs/de/german.md diff --git a/README.md b/README.md index 746bba3..056a593 100755 --- a/README.md +++ b/README.md @@ -13,35 +13,39 @@ ## Summary -Reads text files from a given list of folders from your installation and -provides a web interface for viewing. +Reads markdown files from a given list of folders from your installation and +provides a web interface for viewing the documentation. Ideal for providing +documentation alongside your module or project code. -To read documentation go to yoursite.com/dev/docs/ +A variation of this module powers the main SilverStripe developer documentation +and the user help websites. For more documentation on how to use the module please read /docs/Writing-Documentation.md (or via this in /dev/docs/docsviewer/Writing-Documentation in your webbrowser) -**Note** This module assumes you are using numeric values for your versions. +## Installation -### HTML Publishing + composer require "silverstripe/docsviewer" "dev-master" -If you wish to generate a truly static version of your documentation after it -has been rendered through the website, add the [Static Publisher](https://github.com/silverstripe-labs/silverstripe-staticpublisher) -module to your documentation project and set the following configuration in your -applications config.yml: +## Usage -``` -StaticExporter: - extensions: - - DocumentationStaticPublisherExtension -``` +After installing the files via composer, rebuild the SilverStripe database.. -If you don't plan on using static publisher for anything else and you have the -cms module installed, make sure you disable the CMS from being published. + sake dev/build -Again, in your applications config.yml file +Then start by viewing the documentation at `yoursite.com/dev/docs`. -``` -StaticExporter: - disable_sitetree_export: true -``` \ No newline at end of file +Out of the box the module will display the documentation files that have been +bundled into any of your installed modules. To configure what is shown in the +documentation viewer see the detailed [documentation](docs/en/configuration.md). + +For more information about how to use the module see each of the documentation + + * [Configuration](docs/en/configuration.md) + * [Markdown Syntax](docs/en/markdown.md) + * [Syntax Highlighting](docs/en/syntax-highlighting.md) + * [Publishing Static Files](docs/en/statichtml.md) + +## License + +See LICENSE \ No newline at end of file diff --git a/_config.php b/_config.php index 13c1b24..170f6a7 100755 --- a/_config.php +++ b/_config.php @@ -15,9 +15,3 @@ if(!defined('DOCSVIEWER_DIR')) { define('DOCSVIEWER_DIR', array_pop($dir)); } - -// define filetypes to ignore -DocumentationService::set_ignored_files(array( - '.', '..', '.DS_Store', - '.svn', '.git', 'assets', 'themes', '_images', '_resources' -)); diff --git a/code/DocumentationHelper.php b/code/DocumentationHelper.php index 18f914a..7863daf 100644 --- a/code/DocumentationHelper.php +++ b/code/DocumentationHelper.php @@ -40,4 +40,72 @@ class DocumentationHelper { return $output; } + + + /** + * 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 ucwords(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); + $name = self::trim_sort_number($name); + + if(preg_match('/^[\/]?index[\/]?/', $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; + } + + + /** + * Helper function to strip the extension off and return the name without + * the extension. + * + * @param string + * + * @return string + */ + public static function trim_extension_off($name) { + if(strrpos($name,'.') !== false) { + return substr($name, 0, strrpos($name,'.')); + } + + return $name; + } } \ No newline at end of file diff --git a/code/DocumentationManifest.php b/code/DocumentationManifest.php new file mode 100644 index 0000000..6e00877 --- /dev/null +++ b/code/DocumentationManifest.php @@ -0,0 +1,267 @@ + + * array( + * 'en/someniceurl/' => array( + * 'filepath' => '/path/to/docs/en/SomeniceFile.md', + * 'title' => 'Some nice URL', + * 'summary' => 'Summary Text', + * 'basename' => 'SomeniceFile.md', + * 'type' => 'DocumentationPage' + * ) + * ) + * + * + * URL format is in the following structures: + * + * {lang}/{path} + * {lang}/{module}/{path} + * {lang}/{module}/{version}/{/path} + * + * @package framework + * @subpackage manifest + */ +class DocumentationManifest { + + const TEMPLATES_DIR = 'documentation'; + + protected $base; + protected $cache; + protected $cacheKey; + protected $inited; + protected $forceRegen; + protected $pages = array(); + + private $entity; + + /** + * Constructs a new template manifest. The manifest is not actually built + * or loaded from cache until needed. + * + * @param bool $includeTests Include tests in the manifest. + * @param bool $forceRegen Force the manifest to be regenerated. + */ + public function __construct($forceRegen = false) { + $this->cacheKey = 'manifest'; + $this->forceRegen = $forceRegen; + + $this->cache = SS_Cache::factory('DocumentationManifest', 'Core', array( + 'automatic_serialization' => true, + 'lifetime' => null + )); + } + + + /** + * + */ + protected function init() { + if (!$this->forceRegen && $data = $this->cache->load($this->cacheKey)) { + $this->pages = $data; + $this->inited = true; + } else { + $this->regenerate(); + } + } + + + /** + * Returns a map of all documentation pages. + * + * @return array + */ + public function getPages() { + if (!$this->inited) { + $this->init(); + } + + return $this->pages; + } + + /** + * Returns a particular page for the requested URL. + * + * @return DocumentationPage + */ + public function getPage($url) { + $pages = $this->getPages(); + + if(!isset($pages[$url])) { + return null; + } + + $record = $pages[$url]; + + DocumentationService::load_automatic_registration(); + + foreach(DocumentationService::get_registered_entities() as $entity) { + foreach($entity->getVersions() as $version) { + foreach($version->getSupportedLanguages() as $language) { + if(strpos($record['filepath'], $language->getPath()) !== false) { + $page = Injector::inst()->create( + $record['type'], + $language, + $record['basename'], + $record['filepath'] + ); + + return $page; + } + } + } + } + } + + /** + * Regenerates the manifest by scanning the base path. + * + * @param bool $cache + */ + public function regenerate($cache = true) { + $finder = new DocumentationManifestFileFinder(); + $finder->setOptions(array( + 'dir_callback' => array($this, 'handleFolder'), + 'file_callback' => array($this, 'handleFile') + )); + + DocumentationService::load_automatic_registration(); + foreach(DocumentationService::get_registered_entities() as $entity) { + foreach($entity->getVersions() as $version) { + + foreach($version->getSupportedLanguages() as $k => $v) { + $this->entity = $v; + $this->handleFolder('', $this->entity->getPath(), 0); + + $finder->find($this->entity->getPath()); + } + } + } + + if ($cache) { + $this->cache->save($this->pages, $this->cacheKey); + } + + $this->inited = true; + } + + /** + * + */ + public function handleFolder($basename, $path, $depth) { + $folder = Injector::inst()->create( + 'DocumentationFolder', $this->entity, $basename, $path + ); + + $this->pages[$folder->Link()] = array( + 'title' => $folder->getTitle(), + 'basename' => $basename, + 'filepath' => $path, + 'type' => 'DocumentationFolder' + ); + } + + /** + * Individual files can optionally provide a nice title and a better URL + * through the use of markdown meta data. This creates a new + * {@link DocumentationPage} instance for the file. + * + * If the markdown does not specify the title in the meta data it falls back + * to using the file name. + * + * @param string $basename + * @param string $path + * @param int $depth + */ + public function handleFile($basename, $path, $depth) { + $page = Injector::inst()->create( + 'DocumentationPage', + $this->entity, $basename, $path + ); + + $this->pages[$page->Link()] = array( + 'title' => $page->getTitle(), + 'filepath' => $path, + 'basename' => $basename, + 'type' => 'DocumentationPage', + 'summary' => $page->getSummary() + ); + } + + /** + * Generate an {@link ArrayList} of the pages to the given page. + * + * @return ArrayList + */ + public function generateBreadcrumbs($record) { + $output = new ArrayList(); + + // @todo + + return $output; + } + + /** + * Determine the next page from the given page. + * + * Relies on the fact when the manifest was built, it was generated in + * order. + * + * @param string + * + * @return ArrayData + */ + public function getNextPage($filepath) { + $grabNext = false; + + foreach($this->getPages() as $url => $page) { + if($grabNext) { + return new ArrayData(array( + 'Link' => $url, + 'Title' => $page['title'] + )); + } + + if($filepath == $page['filepath']) { + $grabNext = true; + } + } + + return null; + } + + /** + * Determine the previous page from the given page. + * + * Relies on the fact when the manifest was built, it was generated in + * order. + * + * @param string + * + * @return ArrayData + */ + public function getPreviousPage($filepath) { + $previousUrl = $previousPage = null; + + foreach($this->getPages() as $url => $page) { + if($filepath == $page['filepath']) { + if($previousUrl) { + return new ArrayData(array( + 'Link' => $previousUrl, + 'Title' => $previousPage['title'] + )); + } + } + + $previousUrl = $url; + $previousPage = $page; + } + + return null; + } +} diff --git a/code/DocumentationManifestFileFinder.php b/code/DocumentationManifestFileFinder.php new file mode 100644 index 0000000..18705d8 --- /dev/null +++ b/code/DocumentationManifestFileFinder.php @@ -0,0 +1,38 @@ + '/\.(md|markdown)$/i', + 'file_callback' => null, + 'dir_callback' => null, + 'ignore_vcs' => true + ); + + /** + * + */ + 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; + } + +} \ No newline at end of file diff --git a/code/DocumentationParser.php b/code/DocumentationParser.php index 75e08b9..cff49c5 100755 --- a/code/DocumentationParser.php +++ b/code/DocumentationParser.php @@ -348,7 +348,7 @@ class DocumentationParser { // relative path (relative to module base folder), without the filename. // For "sapphire/en/current/topics/templates", this would be "templates" - $relativePath = dirname($page->getRelativePath()); + $relativePath = dirname($page->Link()); if($relativePath == '.') $relativePath = ''; // file base link diff --git a/code/DocumentationService.php b/code/DocumentationService.php index 4b3747f..a7cc48e 100755 --- a/code/DocumentationService.php +++ b/code/DocumentationService.php @@ -5,43 +5,14 @@ * * Handles the management of the documentation services delivered by the entity. * - * Includes registering which components to document and handles the entities being - * documented. + * Includes registering which components to document and handles the entities + * being documented. * * @package docsviewer */ class DocumentationService { - - /** - * A mapping of known / popular languages to nice titles. - * - * @var Array - */ - private static $language_mapping = array( - 'en' => 'English', - 'fr' => 'Français', - 'de' => 'Deutsch' - ); - - /** - * Files to ignore from any documentation listing. - * - * @var array - */ - private static $ignored_files = array( - '.', '..', '.DS_Store', - '.svn', '.git', 'assets', 'themes', '_images' - ); - /** - * Case insenstive values to use as extensions on markdown pages. The - * recommended extension is .md. - * - * @var array - */ - public static $valid_markdown_extensions = array('md', 'txt', 'markdown'); - /** * Registered {@link DocumentationEntity} objects to include in the * documentation. @@ -77,57 +48,6 @@ class DocumentationService { */ private static $automatic_registration = true; - /** - * by default pagenumbers start high at 10.000 - * - * @var integer - */ - private static $pagenumber_start_at = 10000; - - /** - * allow the use of key/value pairs in comments - * - * @var boolean - */ - private static $meta_comments_enabled = false; - - /** - * Return the allowed extensions - * - * @return array - */ - public static function get_valid_extensions() { - return self::$valid_markdown_extensions; - } - - /** - * Check to see if a given extension is a valid extension to be rendered. - * Assumes that $ext has a leading dot as that is what $valid_extension uses. - * - * @return bool - */ - public static function is_valid_extension($ext) { - return in_array(strtolower($ext), self::get_valid_extensions()); - } - - /** - * Set the ignored files list - * - * @param array - */ - public static function set_ignored_files($files) { - self::$ignored_files = $files; - } - - /** - * Return the list of files which are ignored - * - * @return array - */ - public static function get_ignored_files() { - return self::$ignored_files; - } - /** * Set automatic registration of entities and documentation folders * @@ -138,8 +58,9 @@ class DocumentationService { self::$automatic_registration = $bool; if(!$bool) { - // remove current registed entities when disabling automatic registration - // needed to avoid caching issues when running all the tests + // remove current registed entities when disabling automatic + // registration needed to avoid caching issues when running all the + // tests self::$registered_entities = array(); } } @@ -152,43 +73,6 @@ class DocumentationService { public static function automatic_registration_enabled() { return self::$automatic_registration; } - - /** - * set the number to start default pagenumbering, allowing room for - * custom pagenumbers below. - * - * @param int $number - */ - public static function start_pagenumbers_at($number = 10000) { - if (is_int($number)) self::$pagenumber_start_at = $number; - } - - /** - * return the startlevel for default pagenumbering - * - * @return int - */ - public static function get_pagenumber_start_at() { - return self::$pagenumber_start_at; - } - - /** - * Allow the use of key/value pairs in comments? - * - * @param bool $allow - */ - public static function enable_meta_comments($allow = true) { - self::$meta_comments_enabled = (bool) $allow; - } - - /** - * can we use key/value pairs - * - * @return bool - */ - public static function meta_comments_enabled() { - return self::$meta_comments_enabled; - } /** * Return the entities which are listed for documentation. Optionally only @@ -216,38 +100,13 @@ class DocumentationService { } /** - * Check to see if a entity is registered with the documenter. + * Register a entity to be included in the documentation. To unregister a + * entity use {@link DocumentationService::unregister()}. * - * @param String $entity entity name - * @param String $version version - * @param String $lang language - * - * @return DocumentationEntity $entity the registered entity - */ - public static function is_registered_entity($entity, $version = false, $lang = false) { - $check = ($entity instanceof DocumentationEntity) ? $entity->getFolder() : (string) $entity; - - if(isset(self::$registered_entities[$check])) { - $entity = self::$registered_entities[$check]; - - if(($lang && !$entity->hasLanguage($lang)) || ($version && !$entity->hasVersion($version))) { - return false; - } - - return $entity; - } - - return false; - } - - /** - * Register a entity to be included in the documentation. To unregister a entity - * use {@link DocumentationService::unregister()}. Must include the trailing slash - * - * @param String $entity Name of entity to register - * @param String $path Path to documentation root. - * @param Float $version Version of entity. - * @param String $title Nice title to use + * @param string $entity Name of entity to register + * @param string $path Path to documentation root. + * @param float $version Version of entity. + * @param string $title Nice title to use * @param bool $latest - return is this the latest release. * * @throws InvalidArgumentException @@ -255,38 +114,40 @@ class DocumentationService { * @return DocumentationEntity */ public static function register($entity, $path, $version = '', $title = false, $latest = false) { - if(!file_exists($path)) throw new InvalidArgumentException(sprintf('Path "%s" doesn\'t exist', $path)); + if(!file_exists($path)) { + throw new InvalidArgumentException(sprintf('Path "%s" doesn\'t exist', $path)); + } // add the entity to the registered array if(!isset(self::$registered_entities[$entity])) { - // entity is completely new - $output = new DocumentationEntity($entity, $version, $path, $title); - - self::$registered_entities[$entity] = $output; + $de = new DocumentationEntity($entity, $title); + + self::$registered_entities[$entity] = $de; } else { // entity exists so add the version to it - $output = self::$registered_entities[$entity]; - $output->addVersion($version, $path); + $de = self::$registered_entities[$entity]; } - - if($latest) - $output->setStableVersion($version); - - return $output; + + // create a new version of the entity and attach it the the entity + $dve = new DocumentationEntityVersion($de, $path, $version, $latest); + $de->addVersion($dve); + + return $de; } /** * Unregister a entity from being included in the documentation. Useful * for keeping {@link DocumentationService::$automatic_registration} enabled * but disabling entities which you do not want to show. Combined with a - * {@link Director::isLive()} you can hide entities you don't want a client to see. + * {@link Director::isLive()} you can hide entities you don't want a client + * to see. * - * If no version or lang specified then the whole entity is removed. Otherwise only - * the specified version of the documentation. + * If no version or lang specified then the whole entity is removed. + * Otherwise only the specified version of the documentation. * - * @param String $entity - * @param String $version + * @param string $entity + * @param string $version * * @return bool */ @@ -296,9 +157,7 @@ class DocumentationService { if($version) { $entity->removeVersion($version); - } - else { - // only given a entity so unset the whole entity + } else { unset(self::$registered_entities[$entityName]); } @@ -315,292 +174,25 @@ class DocumentationService { * @see {@link DocumentationService::set_automatic_registration()} */ public static function load_automatic_registration() { - if(self::automatic_registration_enabled()) { - $entities = scandir(BASE_PATH); + if(!self::automatic_registration_enabled()) { + return; + } - if($entities) { - foreach($entities as $key => $entity) { - $dir = is_dir(Controller::join_links(BASE_PATH, $entity)); - $ignored = in_array($entity, self::get_ignored_files(), true); - - if($dir && !$ignored) { - // check to see if it has docs - $docs = Director::baseFolder() . '/' . Controller::join_links($entity, 'docs'); - - if(is_dir($docs)) { - self::register($entity, $docs, 'current', $entity, true); - } + $entities = scandir(BASE_PATH); + + if($entities) { + foreach($entities as $key => $entity) { + $dir = is_dir(Controller::join_links(BASE_PATH, $entity)); + + if($dir) { + // check to see if it has docs + $docs = Director::baseFolder() . '/' . Controller::join_links($entity, 'docs'); + + if(is_dir($docs)) { + self::register($entity, $docs, 'current', $entity, true); } } } } } - - /** - * Convert a language code to a 'nice' text string. Uses the - * {@link self::$language_mapping} array combined with translatable. - * - * @param String $code code - */ - public static function get_language_title($lang) { - $map = self::$language_mapping; - - if(isset($map[$lang])) { - return _t("DOCUMENTATIONSERVICE.LANG-$lang", $map[$lang]); - } - - return $lang; - } - - - /** - * Find a documentation page given a path and a file name. It ignores the - * extensions and simply compares the title. - * - * Name may also be a path /install/foo/bar. - * - * @param DocumentationEntity - * @param array exploded url string - * @param string version number - * @param string lang code - * - * @return String|false - File path - */ - static function find_page($entity, $path, $version = '', $lang = 'en') { - if($entity = self::is_registered_entity($entity, $version, $lang)) { - return self::find_page_recursive($entity->getPath($version, $lang), $path); - } - - return false; - } - - /** - * Recursive function for finding the goal of a path to a documentation - * page - * - * @return string - */ - private static function find_page_recursive($base, $goal) { - $handle = (is_dir($base)) ? opendir($base) : false; - - $name = self::trim_extension_off(strtolower(array_shift($goal))); - if(!$name || $name == '/') $name = 'index'; - - - if($handle) { - $ignored = self::get_ignored_files(); - - // ensure we end with a slash - $base = rtrim($base, '/') .'/'; - - while (false !== ($file = readdir($handle))) { - if(in_array($file, $ignored)) continue; - - $formatted = self::trim_extension_off(strtolower($file)); - - // the folder is the one that we are looking for. - if(strtolower($name) == strtolower($formatted)) { - - // if this file is a directory we could be displaying that - // or simply moving towards the goal. - if(is_dir(Controller::join_links($base, $file))) { - - $base = $base . trim($file, '/') .'/'; - - // if this is a directory check that there is any more states to get - // to in the goal. If none then what we want is the 'index.md' file - if(count($goal) > 0) { - return self::find_page_recursive($base, $goal); - } - else { - // recurse but check for an index.md file next time around - return self::find_page_recursive($base, array('index')); - } - } - else { - // goal state. End of recursion. - // tidy up the URLs with single trailing slashes - $result = $base . ltrim($file, '/'); - - if(is_dir($result)) $result = (rtrim($result, '/') . '/'); - - return $result; - } - } - } - - closedir($handle); - } - - return false; - } - - /** - * 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) { - // remove dashs and _ - $name = str_replace(array('-', '_'), ' ', $name); - - // remove extension - $name = self::trim_extension_off($name); - - // if it starts with a number strip and contains a space strip it off - if(strpos($name, ' ') !== false) { - $space = strpos($name, ' '); - $short = substr($name, 0, $space); - - if(is_numeric($short)) { - $name = substr($name, $space); - } - } - - // convert first letter - return ucfirst(trim($name)); - } - - /** - * Helper function to strip the extension off and return the name without - * the extension. If you need the extension see {@link get_extension()} - * - * @param string - * - * @return string - */ - public static function trim_extension_off($name) { - $ext = self::get_extension($name); - - if($ext) { - if(self::is_valid_extension($ext)) { - return substr($name, 0, strrpos($name,'.')); - } - } - - return $name; - } - - /** - * Returns the extension from a string. If you want to trim the extension - * off the end of the string see {@link trim_extension_off()} - * - * @param string - * - * @return string - */ - public static function get_extension($name) { - return substr(strrchr($name,'.'), 1); - } - - /** - * Return the children from a given entity sorted by Title using natural ordering. - * It is used for building the tree of the page. - * - * @param DocumentationEntity path - * @param string - an optional path within a entity - * @param bool enable several recursive calls (more than 1 level) - * @param string - version to use - * @param string - lang to use - * - * @throws Exception - * @return ArrayList - */ - public static function get_pages_from_folder($entity, $relativePath = false, $recursive = true, $version = 'trunk', $lang = 'en') { - $output = new ArrayList(); - $metaCommentsEnabled = self::meta_comments_enabled(); - $pages = array(); - - if(!$entity instanceof DocumentationEntity) - user_error("get_pages_from_folder must be passed a entity", E_USER_ERROR); - - $path = $entity->getPath($version, $lang); - - - if(self::is_registered_entity($entity)) { - self::get_pages_from_folder_recursive($path, $relativePath, $recursive, $pages); - } - else { - return user_error("$entity is not registered", E_USER_WARNING); - } - - if(count($pages) > 0) { - $pagenumber = self::get_pagenumber_start_at(); - natsort($pages); - - foreach($pages as $key => $pagePath) { - - // get file name from the path - $file = ($pos = strrpos($pagePath, '/')) ? substr($pagePath, $pos + 1) : $pagePath; - - $page = new DocumentationPage(); - $page->setTitle(self::clean_page_name($file)); - $relative = str_replace($path, '', $pagePath); - - // if no extension, put a slash on it - if(strpos($relative, '.') === false) $relative .= '/'; - - $page->setEntity($entity); - $page->setRelativePath($relative); - $page->setVersion($version); - $page->setLang($lang); - - // does this page act as a folder? - $path = $page->getPath(); - if (is_dir($path)) { $page->setIsFolder(true); } - - $page->setPagenumber($pagenumber++); - - // we need the markdown to get the comments - if ($metaCommentsEnabled) $page->getMarkdown(); - - $output->push($page); - } - } - - return ($metaCommentsEnabled)? $output->sort('pagenumber') : $output; - } - - /** - * Recursively search through a given folder - * - * @see {@link DocumentationService::get_pages_from_folder} - */ - private static function get_pages_from_folder_recursive($base, $relative, $recusive, &$pages) { - if(!is_dir($base)) throw new Exception(sprintf('%s is not a folder', $folder)); - - $folder = Controller::join_links($base, $relative); - - if(!is_dir($folder)) return false; - - $handle = opendir($folder); - - if($handle) { - $ignore = self::get_ignored_files(); - $files = array(); - - while (false !== ($file = readdir($handle))) { - if(!in_array($file, $ignore)) { - - $path = Controller::join_links($folder, $file); - $relativeFilePath = Controller::join_links($relative, $file); - - if(is_dir($path)) { - // dir - $pages[] = $relativeFilePath; - - if($recusive) self::get_pages_from_folder_recursive($base, $relativeFilePath, $recusive, $pages); - } - else if(self::is_valid_extension(self::get_extension($path))) { - // file we want - $pages[] = $relativeFilePath; - } - } - } - } - - closedir($handle); - } } diff --git a/code/controllers/DocumentationSearchController.php b/code/controllers/DocumentationSearchController.php new file mode 100644 index 0000000..6da0966 --- /dev/null +++ b/code/controllers/DocumentationSearchController.php @@ -0,0 +1,109 @@ +getEntity()) { + $entities[$entity->getFolder()] = Convert::raw2att($entity->getTitle()); + } + + return $entities; + } + + /** + * Return an array of versions that we're allowed to return + * + * @return array + */ + public function getSearchedVersions() { + $versions = array(); + + if(!empty($_REQUEST['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; + } + } + else if($version = $this->getVersion()) { + $version = Convert::raw2att($version); + $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']); + } + } + + /** + * Past straight to results, display and encode the query + */ + public function results($data, $form = false) { + $query = (isset($_REQUEST['Search'])) ? $_REQUEST['Search'] : false; + + $search = new DocumentationSearch(); + $search->setQuery($query); + $search->setVersions($this->getSearchedVersions()); + $search->setModules($this->getSearchedEntities()); + $search->setOutputController($this); + + return $search->renderResults(); + } + + /** + * Returns an search form which allows people to express more complex rules + * and options than the plain search form. + * + * @todo client side filtering of checkable option based on the module selected. + * + * @return Form + */ + public function AdvancedSearchForm() { + $entities = DocumentationService::get_registered_entities(); + + return new DocumentationAdvancedSearchForm($this); + } + + /** + * Check if the Advanced SearchForm can be displayed. It is enabled by + * default, to disable use: + * + * + * DocumentationSearch::enable_advanced_search(false); + * + * + * @return bool + */ + public function getAdvancedSearchEnabled() { + return DocumentationSearch::advanced_search_enabled(); + } + +} \ No newline at end of file diff --git a/code/controllers/DocumentationViewer.php b/code/controllers/DocumentationViewer.php index 4d9d9af..f72deba 100755 --- a/code/controllers/DocumentationViewer.php +++ b/code/controllers/DocumentationViewer.php @@ -35,16 +35,6 @@ class DocumentationViewer extends Controller { * @var string */ private static $documentation_title = 'SilverStripe Documentation'; - - /** - * @var string - */ - public $version = ""; - - /** - * @var string - */ - public $language = "en"; /** * The string name of the currently accessed {@link DocumentationEntity} @@ -52,27 +42,26 @@ class DocumentationViewer extends Controller { * * @var string */ - public $entity = ''; - - /** - * @var array - */ - public $remaining = array(); - + protected $entity = ''; + /** * @var DocumentationPage */ - public $currentLevelOnePage; + protected $record; /** - * @var String Same as the routing pattern set through Director::addRules(). + * @config + * + * @var string same as the routing pattern set through Director::addRules(). */ - protected static $link_base = 'dev/docs/'; + private static $link_base = 'dev/docs/'; /** - * @var String|array Optional permission check + * @config + * + * @var string|array Optional permission check */ - static $check_permission = 'ADMIN'; + private static $check_permission = 'ADMIN'; /** * @var array map of modules to edit links. @@ -80,6 +69,13 @@ class DocumentationViewer extends Controller { */ private static $edit_links = array(); + /** + * @var array + */ + private static $url_handlers = array( + '$Action' => 'handleAction' + ); + /** * */ @@ -162,217 +158,84 @@ class DocumentationViewer extends Controller { * Handle the url parsing for the documentation. In order to make this * user friendly this does some tricky things.. * - * The urls which should work - * / - index page - * /en/sapphire - the index page of sapphire (shows versions) - * /2.4/en/sapphire - the docs for 2.4 sapphire. - * /2.4/en/sapphire/installation/ - * * @return SS_HTTPResponse */ public function handleRequest(SS_HTTPRequest $request, DataModel $model) { - DocumentationService::load_automatic_registration(); - + $response = parent::handleRequest($request, $model); + // if we submitted a form, let that pass if(!$request->isGET() || isset($_GET['action_results'])) { - return parent::handleRequest($request, $model); - } - - $firstParam = ($request->param('Action')) ? $request->param('Action') : $request->shift(); - $secondParam = $request->shift(); - $thirdParam = $request->shift(); - - $this->Remaining = $request->shift(10); - - // if no params passed at all then it's the homepage - if(!$firstParam && !$secondParam && !$thirdParam) { - return parent::handleRequest($request, $model); + return $response; } - if($firstParam) { - // allow assets - if($firstParam == "assets") { - return parent::handleRequest($request, $model); - } - - // check for permalinks - if($link = DocumentationPermalinks::map($firstParam)) { - // the first param is a shortcode for a page so redirect the user to - // the short code. - $this->response = new SS_HTTPResponse(); - $this->redirect($link, 301); // 301 permanent redirect - - return $this->response; - - } + // look up the manifest to see find the nearest match against the + // list of the URL. If the URL exists then set that as the current + // page to match against. + if($record = $this->getManifest()->getPage($this->request->getURL())) { + $this->record = $record; - // check to see if the request is a valid entity. If it isn't, then we - // need to throw a 404. - if(!DocumentationService::is_registered_entity($firstParam)) { - return $this->throw404(); - } - - $this->entity = $firstParam; - $this->language = $secondParam; - - if(isset($thirdParam) && (is_numeric($thirdParam) || in_array($thirdParam, array('master', 'trunk')))) { - $this->version = $thirdParam; - } - else { - // current version so store one area para - array_unshift($this->Remaining, $thirdParam); - - $this->version = false; - } - } - - // 'current' version mapping - $entity = DocumentationService::is_registered_entity($this->entity, null, $this->getLang()); + $type = get_class($this->record); + $body = $this->renderWith(array( + "DocumentationViewer_{$type}", + "DocumentationViewer" + )); - if($entity) { - $current = $entity->getStableVersion(); - $version = $this->getVersion(); - - if(!$version) { - $this->version = $current; - } - - // Check if page exists, otherwise return 404 - if(!$this->locationExists()) { - return $this->throw404(); - } - - - return parent::handleRequest($request, $model); - } - - return $this->throw404(); - } - - - /** - * Helper function for throwing a 404 error from the {@link handleRequest} - * method. - * - * @return HttpResponse - */ - function throw404() { - $this->init(); - - $class = get_class($this); - - $body = $this->renderWith(array("{$class}_error", $class)); - $this->response = new SS_HTTPResponse($body, 404); - - return $this->response; - } - - /** - * Custom templates for each of the sections. - */ - function getViewer($action) { - // count the number of parameters after the language, version are taken - // into account. This automatically includes ' ' so all the counts - // are 1 more than what you would expect - - if($this->entity || $this->Remaining) { - - $paramCount = count($this->Remaining); - - if($paramCount == 0) { - return parent::getViewer('folder'); - } - else if($entity = $this->getEntity()) { - // if this is a folder return the folder listing - if($this->locationExists() == 2) { - return parent::getViewer('folder'); - } - } + return new SS_HTTPResponse($body, 200); } else { - return parent::getViewer('home'); - } + $this->init(); - return parent::getViewer($action); + $class = get_class($this); + $body = $this->renderWith(array("{$class}_error", $class)); + + return new SS_HTTPResponse($body, 404); + } } - + /** * Returns the current version. If no version is set then it is the current * set version so need to pull that from the {@link Entity}. * - * @return String + * @return string */ - function getVersion() { - if($this->version) return $this->version; - - if($entity = $this->getEntity()) { - $this->version = $entity->getStableVersion(); - - return $this->version; - } - - return false; + public function getVersion() { + return ($this->record) ? $this->record->getEntity()->getVersion() : null; } /** - * Returns the current language + * Returns the current language. * - * @return String + * @return string */ - function getLang() { - return $this->language; + public function getLanguage() { + return ($this->record) ? $this->record->getEntity()->getLanguage() : null; } + /** + * + */ + public function getManifest() { + return new DocumentationManifest((isset($_GET['flush']))); + } + /** * Return all the available languages for the {@link Entity}. * * @return array */ - function getLanguages() { - $entity = $this->getEntity(); - - if($entity) { - return $entity->getLanguages(); - } - - return array('en' => 'English'); + public function getLanguages() { + return ($this->record) ? $this->record->getEntity()->getSupportedLanguages() : null; } /** * Get all the versions loaded for the current {@link DocumentationEntity}. - * the filesystem then they are loaded under the 'Current' namespace. + * the file system then they are loaded under the 'Current' name space. * * @param String $entity name of {@link Entity} to limit it to eg sapphire * @return ArrayList */ - function getVersions($entity = false) { - if(!$entity) $entity = $this->entity; - - $entity = DocumentationService::is_registered_entity($entity); - if(!$entity) return false; - - $versions = $entity->getVersions(); - $output = new ArrayList(); - - if($versions) { - $lang = $this->getLang(); - $currentVersion = $this->getVersion(); - - foreach($versions as $key => $version) { - if(!$version) continue; - - $linkingMode = ($currentVersion == $version) ? 'current' : 'link'; - - $output->push(new ArrayData(array( - 'Title' => $version, - 'Link' => $this->Link(implode('/',$this->Remaining), $entity->getFolder(), $version), - 'LinkingMode' => $linkingMode, - 'Version' => $version // separate from title, we may want to make title nicer. - ))); - } - } - - return $output; + public function getVersions() { + return ($this->record) ? $this->record->getEntity()->getVersions() : null; } /** @@ -381,8 +244,8 @@ class DocumentationViewer extends Controller { * * @return DataObject */ - public function getEntities($version = false, $lang = false) { - $entities = DocumentationService::get_registered_entities($version, $lang); + public function getEntities() { + $entities = DocumentationService::get_registered_entities(); $output = new ArrayList(); $currentEntity = $this->getEntity(); @@ -392,12 +255,13 @@ class DocumentationViewer extends Controller { $mode = ($entity === $currentEntity) ? 'current' : 'link'; $folder = $entity->getFolder(); - $link = $this->Link(array(), $folder, false, $lang); + $link = $entity->Link(); $content = false; - if($page = $entity->getIndexPage($version, $lang)) { - $content = DBField::create_field('HTMLText', DocumentationParser::parse($page, $link)); - } + + // if($page = $entity->getIndexPage()) { + // $content = DBField::create_field('HTMLText', DocumentationParser::parse($page, $link)); + // } $output->push(new ArrayData(array( 'Title' => $entity->getTitle(), @@ -415,7 +279,7 @@ class DocumentationViewer extends Controller { /** * Get the currently accessed entity from the site. * - * @return false|DocumentationEntity + * @return DocumentationEntity */ public function getEntity() { if($this->entity) { @@ -426,179 +290,7 @@ class DocumentationViewer extends Controller { ); } - return false; - } - - /** - * Simple way to check for existence of page of folder - * without constructing too much object state. Useful for - * generating 404 pages. Returns 0 for not a page or - * folder, returns 1 for a page and 2 for folder - * - * @return int - */ - public function locationExists() { - $entity = $this->getEntity(); - - if($entity) { - - $has_dir = is_dir(Controller::join_links( - $entity->getPath($this->getVersion(), $this->getLang()), - implode('/', $this->Remaining) - )); - - if($has_dir) return 2; - - $has_page = DocumentationService::find_page( - $entity, - $this->Remaining, - $this->getVersion(), - $this->getLang() - ); - - if($has_page) return 1; - } - - return 0; - } - - /** - * @return DocumentationPage - */ - function getPage() { - $entity = $this->getEntity(); - - if(!$entity) return false; - - $version = $this->getVersion(); - $lang = $this->getLang(); - - $absFilepath = DocumentationService::find_page( - $entity, - $this->Remaining, - $version, - $lang - ); - - if($absFilepath) { - $relativeFilePath = str_replace( - $entity->getPath($version, $lang), - '', - $absFilepath - ); - - $page = new DocumentationPage(); - $page->setRelativePath($relativeFilePath); - $page->setEntity($entity); - $page->setLang($lang); - $page->setVersion($version); - - return $page; - } - - return false; - } - - /** - * Get the related pages to the current {@link DocumentationEntity} and - * the children to those pages - * - * @todo this only handles 2 levels. Could make it recursive - * - * @return false|ArrayList - */ - function getEntityPages() { - if($entity = $this->getEntity()) { - $pages = DocumentationService::get_pages_from_folder($entity, null, self::$recursive_submenu, $this->getVersion(), $this->getLang()); - - if($pages) { - foreach($pages as $page) { - if(strtolower($page->Title) == "index") { - $pages->remove($page); - - continue; - } - - $page->LinkingMode = 'link'; - $page->Children = $this->_getEntityPagesNested($page, $entity); - - if (!empty($page->Children)) { - $this->currentLevelOnePage = $page; - } - } - } - - return $pages; - } - - return false; - } - - /** - * Get all the pages under a given page. Recursive call for {@link getEntityPages()} - * - * @todo Need to rethink how to support pages which are pulling content from their children - * i.e if a folder doesn't have 2 then it will load the first file in the folder - * however it doesn't yet pass the highlighting to it. - * - * @param ArrayData CurrentPage - * @param DocumentationEntity - * @param int Depth of page in the tree - * - * @return ArrayList|false - */ - private function _getEntityPagesNested(&$page, $entity, $level = 0) { - if(isset($this->Remaining[$level])) { - // compare segment successively, e.g. with "changelogs/alpha/2.4.0-alpha", - // first comparison on $level=0 is against "changelogs", - // second comparison on $level=1 is against "changelogs/alpha", etc. - $segments = array_slice($this->Remaining, 0, $level+1); - - if(strtolower(implode('/', $segments)) == strtolower(trim($page->getRelativeLink(), '/'))) { - - // its either in this section or is the actual link - $page->LinkingMode = (isset($this->Remaining[$level + 1])) ? 'section' : 'current'; - - $relativePath = Controller::join_links( - $entity->getPath($this->getVersion(), $this->getLang()), - $page->getRelativePath() - ); - - if(is_dir($relativePath)) { - $children = DocumentationService::get_pages_from_folder( - $entity, - $page->getRelativePath(), - self::$recursive_submenu, - $this->getVersion(), - $this->getLang() - ); - - $segments = array(); - for($x = 0; $x <= $level; $x++) { - $segments[] = $this->Remaining[$x]; - } - - foreach($children as $child) { - if(strtolower($child->Title) == "index") { - $children->remove($child); - - continue; - } - - $child->LinkingMode = 'link'; - $child->Children = $this->_getEntityPagesNested($child, $entity, $level + 1); - } - - return $children; - } - } else { - if ($page->getRelativeLink() == $this->Remaining[$level]) { - $page->LinkingMode = 'current'; - } - } - } - - return false; + return null; } /** @@ -612,7 +304,9 @@ class DocumentationViewer extends Controller { $page = $this->getPage(); if($page) { - return DBField::create_field("HTMLText", $page->getHTML($this->getVersion(), $this->getLang())); + return DBField::create_field("HTMLText", $page->getHTML( + $this->getVersion(), $this->getLanguage() + )); } // If no page found then we may want to get the listing of the folder. @@ -621,13 +315,7 @@ class DocumentationViewer extends Controller { $url = $this->Remaining; if($url && $entity) { - $pages = DocumentationService::get_pages_from_folder( - $entity, - implode('/', $url), - false, - $this->getVersion(), - $this->getLang() - ); + // @todo manifest return $this->customise(array( 'Content' => false, @@ -647,96 +335,40 @@ class DocumentationViewer extends Controller { } /** - * Generate a list of breadcrumbs for the user. Based off the remaining - * params in the url + * Generate a list of breadcrumbs for the user. * * @return ArrayList */ public function getBreadcrumbs() { - if(!$this->Remaining) { - $this->Remaining = array(); + if($this->record) { + return $this->getManifest()->generateBreadcrumbs($this->record); } - - $pages = array_merge(array($this->entity), $this->Remaining); - $output = new ArrayList(); - - if($pages) { - $path = array(); - $version = $this->getVersion(); - $lang = $this->getLang(); - - foreach($pages as $i => $title) { - if($title) { - // Don't add entity name, already present in Link() - if($i > 0) $path[] = $title; - - $output->push(new ArrayData(array( - 'Title' => DocumentationService::clean_page_name($title), - 'Link' => rtrim($this->Link($path, false, $version, $lang), "/"). "/" - ))); - } - } - } - - return $output; } - + + /** + * @return DocumentationPage + */ + public function getPage() { + return $this->record; + } /** * Generate a string for the title tag in the URL. * * @return string */ public function getPageTitle() { - if($pages = $this->getBreadcrumbs()) { - $output = ""; - - foreach($pages as $page) { - $output = $page->Title .' – '. $output; - } - - return $output; - } - - return false; + return ($this->record) ? $this->record->getBreadcrumbTitle() : null; } /** - * Return the base link to this documentation location + * Return the base link to this documentation location. * - * @param string $path - subfolder path - * @param string $entity - name of entity - * @param float $version - optional version - * @param string $lang - optional lang - * - * @return String + * @return string */ - public function Link($path = false, $entity = false, $version = false, $lang = false) { - $version = ($version === null) ? $this->getVersion() : $version; - - $lang = (!$lang) ? $this->getLang() : $lang; - - $entity = (!$entity && $this->entity) ? $this->entity : $entity; - $action = ''; - - if(is_string($path)) { - $action = $path; - } - else if(is_array($path)) { - $action = implode('/', $path); - } - - // check for stable version: if so, remove version from link - // (see DocumentationEntity->getRelativeLink() ) - $objEntity = $this->getEntity(); - if ($objEntity && $objEntity->getStableVersion() == $version) $version = ''; - + public function Link() { $link = Controller::join_links( Director::absoluteBaseURL(), - self::get_link_base(), - $entity, - ($entity) ? $lang : "", // only include lang for entity - sapphire/en vs en/ - ($entity) ? $version :"", - $action + Config::inst()->get('DocumentationViewer', 'link_base') ); return $link; @@ -751,7 +383,7 @@ class DocumentationViewer extends Controller { */ public function LanguageForm() { $langs = $this->getLanguages(); - + $fields = new FieldList( $dropdown = new DropdownField( 'LangCode', @@ -764,9 +396,7 @@ class DocumentationViewer extends Controller { $actions = new FieldList( new FormAction('doLanguageForm', _t('DocumentationViewer.CHANGE', 'Change')) ); - - $dropdown->setDisabled(true); - + return new Form($this, 'LanguageForm', $fields, $actions); } @@ -780,27 +410,6 @@ class DocumentationViewer extends Controller { return $this->redirect($this->Link()); } - /** - * @param string - */ - public static function set_link_base($base) { - self::$link_base = $base; - } - - /** - * @return string - */ - public static function get_link_base() { - return self::$link_base; - } - - /** - * @see {@link Form::FormObjectLink()} - */ - public function FormObjectLink($name) { - return $name; - } - /** * Documentation Search Form. Allows filtering of the results by many entities * and multiple versions. @@ -811,140 +420,11 @@ class DocumentationViewer extends Controller { if(!DocumentationSearch::enabled()) { return false; } - - $q = ($q = $this->getSearchQuery()) ? $q->NoHTML() : ""; - - $entities = $this->getSearchedEntities(); - $versions = $this->getSearchedVersions(); - $fields = new FieldList( - new TextField('Search', _t('DocumentationViewer.SEARCH', 'Search'), $q) - ); - - if ($entities) $fields->push( - new HiddenField('Entities', '', implode(',', array_keys($entities))) - ); - - if ($versions) $fields->push( - new HiddenField('Versions', '', implode(',', $versions)) - ); - - $actions = new FieldList( - new FormAction('results', 'Search') - ); - - $form = new Form($this, 'DocumentationSearchForm', $fields, $actions); - $form->disableSecurityToken(); - $form->setFormMethod('GET'); - $form->setFormAction(self::$link_base . 'DocumentationSearchForm'); - - return $form; + return new DocumentationSearchForm($this); } - /** - * Return an array of folders and titles - * - * @return array - */ - public function getSearchedEntities() { - $entities = array(); - if(!empty($_REQUEST['Entities'])) { - if(is_array($_REQUEST['Entities'])) { - $entities = Convert::raw2att($_REQUEST['Entities']); - } - else { - $entities = explode(',', Convert::raw2att($_REQUEST['Entities'])); - $entities = array_combine($entities, $entities); - } - } - else if($entity = $this->getEntity()) { - $entities[$entity->getFolder()] = Convert::raw2att($entity->getTitle()); - } - - return $entities; - } - - /** - * Return an array of versions that we're allowed to return - * - * @return array - */ - public function getSearchedVersions() { - $versions = array(); - - if(!empty($_REQUEST['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; - } - } - else if($version = $this->getVersion()) { - $version = Convert::raw2att($version); - $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']); - } - } - - /** - * Past straight to results, display and encode the query - */ - public function results($data, $form = false) { - $query = (isset($_REQUEST['Search'])) ? $_REQUEST['Search'] : false; - - $search = new DocumentationSearch(); - $search->setQuery($query); - $search->setVersions($this->getSearchedVersions()); - $search->setModules($this->getSearchedEntities()); - $search->setOutputController($this); - - return $search->renderResults(); - } - - /** - * Returns an search form which allows people to express more complex rules - * and options than the plain search form. - * - * @todo client side filtering of checkable option based on the module selected. - * - * @return Form - */ - public function AdvancedSearchForm() { - $entities = DocumentationService::get_registered_entities(); - - return new DocumentationAdvancedSearchForm($this); - } - - /** - * Check if the Advanced SearchForm can be displayed. It is enabled by - * default, to disable use: - * - * - * DocumentationSearch::enable_advanced_search(false); - * - * - * @return bool - */ - public function getAdvancedSearchEnabled() { - return DocumentationSearch::advanced_search_enabled(); - } - /** * Check to see if the currently accessed version is out of date or * perhaps a future version rather than the stable edition @@ -1026,7 +506,7 @@ class DocumentationViewer extends Controller { if($entity && isset(self::$edit_links[$entity->title])) { // build the edit link, using the version defined $url = self::$edit_links[$entity->title]; - $version = $page->getVersion(); + $version = $this->getVersion(); if($version == "trunk" && (isset($url['options']['rewritetrunktomaster']))) { if($url['options']['rewritetrunktomaster']) { @@ -1037,10 +517,10 @@ class DocumentationViewer extends Controller { return str_replace( array('%entity%', '%lang%', '%version%', '%path%'), array( - $entity->getFolder(), - $page->getLang(), + $entity->getBaseFolder(), + $this->getLanguage(), $version, - ltrim($page->getRelativePath(), '/') + ltrim($page->getPath(), '/') ), $url['url'] @@ -1050,6 +530,27 @@ class DocumentationViewer extends Controller { return false; } + + + /** + * Returns the next page. Either retrieves the sibling of the current page + * or return the next sibling of the parent page. + * + * @return DocumentationPage + */ + public function getNextPage() { + return ($this->record) ? $this->getManifest()->getNextPage($this->record->getPath()) : null; + } + + /** + * Returns the previous page. Either returns the previous sibling or the + * parent of this page + * + * @return DocumentationPage + */ + public function getPreviousPage() { + return ($this->record) ? $this->getManifest()->getPreviousPage($this->record->getPath()) : null; + } /** * @return string diff --git a/code/forms/DocumentationSearchForm.php b/code/forms/DocumentationSearchForm.php new file mode 100644 index 0000000..7fd9372 --- /dev/null +++ b/code/forms/DocumentationSearchForm.php @@ -0,0 +1,32 @@ +getSearchQuery()) ? $q->NoHTML() : ""; + + $entities = $controller->getSearchedEntities(); + $versions = $controller->getSearchedVersions(); + + $fields = new FieldList( + new TextField('Search', _t('DocumentationViewer.SEARCH', 'Search'), $q) + ); + + if ($entities) $fields->push( + new HiddenField('Entities', '', implode(',', array_keys($entities))) + ); + + if ($versions) $fields->push( + new HiddenField('Versions', '', implode(',', $versions)) + ); + + $actions = new FieldList( + new FormAction('results', 'Search') + ); + + parent::__construct($controller, 'DocumentationSearchForm', $fields, $actions); + + $this->disableSecurityToken(); + $this->setFormMethod('GET'); + $this->setFormAction($controller->Link('DocumentationSearchForm')); + } \ No newline at end of file diff --git a/code/models/DocumentationEntity.php b/code/models/DocumentationEntity.php index 0716650..08e00da 100755 --- a/code/models/DocumentationEntity.php +++ b/code/models/DocumentationEntity.php @@ -1,24 +1,12 @@ 'Text' + 'Title' => 'Text' ); /** - * @var string $folder folder name + * @var string $title */ - private $folder; - - /** - * @var string $title nice title - */ - private $title; + protected $title; /** - * @var array $version version numbers and the paths to each + * @var string $folder */ - private $versions = array(); - + protected $folder; + /** - * @var array + * @var ArrayList $versions */ - private $stableVersion; - - /** - * @var Array $langs a list of available langauges - */ - private $langs = array(); + protected $versions; /** * Constructor. You do not need to pass the langs to this as * it will work out the languages from the filesystem * * @param string $folder folder name - * @param string $version version of this module - * @param string $path Absolute path to this module (excluding language folders) * @param string $title */ - public function __construct($folder, $version, $path, $title = false) { - $this->addVersion($version, $path); - $this->title = (!$title) ? $folder : $title; + public function __construct($folder, $title = false) { + $this->versions = new ArrayList(); $this->folder = $folder; + $this->title = (!$title) ? $folder : $title; } /** - * Return the languages which are available - * - * @return array - */ - public function getLanguages() { - return $this->langs; - } - - /** - * Return whether this entity has a given language - * - * @return bool - */ - public function hasLanguage($lang) { - return (in_array($lang, $this->langs)); - } - - /** - * Add a langauge or languages to the entity - * - * @param Array|String languages - */ - public function addLanguage($language) { - if(is_array($language)) { - $this->langs = array_unique(array_merge($this->langs, $language)); - } - else { - $this->langs[] = $language; - } - } - - /** - * Get the folder name of this module - * - * @return String - */ - public function getFolder() { - return $this->folder; - } - - /** - * Get the title of this module + * Get the title of this module. * * @return String */ @@ -128,35 +63,22 @@ class DocumentationEntity extends ViewableData { * @return array */ public function getVersions() { - return array_keys($this->versions); + return $this->versions; } /** * @return string|boo */ public function getStableVersion() { - if(!$this->hasVersions()) return false; - - if($this->stableVersion) { - return $this->stableVersion; - } else { - $sortedVersions = $this->getVersions(); - - usort($sortedVersions, create_function('$a,$b', 'return version_compare($a,$b);')); - - return array_pop($sortedVersions); - } - } - - /** - * @param String $version - */ - public function setStableVersion($version) { - if(!$this->hasVersion($version)) { - throw new InvalidArgumentException(sprintf('Version "%s" does not exist', $version)); + if(!$this->hasVersions()) { + return false; } - $this->stableVersion = $version; + $sortedVersions = $this->getVersions(); + + usort($sortedVersions, create_function('$a,$b', 'return version_compare($a,$b);')); + + return array_pop($sortedVersions); } /** @@ -179,7 +101,7 @@ class DocumentationEntity extends ViewableData { * @return bool */ public function hasVersion($version) { - return (isset($this->versions[$version])); + return $this->versions->find('Version', $version); } /** @@ -188,133 +110,65 @@ class DocumentationEntity extends ViewableData { * @return bool */ public function hasVersions() { - return (sizeof($this->versions) > 0); + return $this->versions->count() > 0; } /** * Add another version to this entity * - * @param Float $version Version number - * @param String $path path to folder + * @param DocumentationEntityVersion */ - public function addVersion($version = '', $path) { + public function addVersion($version) { + $this->versions->push($version); - $langs = scandir($path); - $available = array(); - - if($langs) { - foreach($langs as $key => $lang) { - if(!is_dir($path . $lang) || strlen($lang) > 2 || in_array($lang, DocumentationService::get_ignored_files(), true)) { - $lang = 'en'; - } - - if(!in_array($lang, $available)) { - $available[] = $lang; - } - } - } - - $this->addLanguage($available); - $this->versions[$version] = $path; + return $this; } /** * Remove a version from this entity * - * @param Float $version + * @param float $version + * */ - public function removeVersion($version = '') { - if(isset($this->versions[$version])) { - unset($this->versions[$version]); - } + public function removeVersion($version) { + $this->versions->remove('Version', $version); + + return $this; } /** - * Return the absolute path to this documentation entity on the - * filesystem + * Return the absolute path to this documentation entity. * * @return string */ - public function getPath($version = false, $lang = false) { - if(!$version) { - $version = $this->getStableVersion(); - } - - if(!$lang) { - $lang = 'en'; - } - - if($this->hasVersion($version)) { - $path = $this->versions[$version]; - } - else { - $versions = $this->getVersions(); - $path = $this->versions[$versions[0]]; - } - - return Controller::join_links($path, $lang); + public function getPath() { + return $this->path; } + /** + * @return string + */ + public function getFolder() { + return $this->folder; + } + /** * Returns the web accessible link to this Entity * * @return string */ - public function Link($version = false, $lang = false) { + public function Link() { return Controller::join_links( - Director::absoluteBaseURL(), - $this->getRelativeLink($version, $lang) + Config::inst()->get('DocumentationViewer', 'link_base'), + $this->getFolder() ); } - public function getRelativeLink($version = false, $lang = false) { - if(!$lang) { - $lang = 'en'; - } - - if($version == $this->getStableVersion()) { - $version = false; - } - - return Controller::join_links( - DocumentationViewer::get_link_base(), - $this->getFolder(), - $lang, - $version - ); - } - - /** - * Return the summary / index text for this entity. Either pulled - * from an index file or some other summary field - * - * @return DocumentationPage - */ - public function getIndexPage($version, $lang = 'en') { - $path = $this->getPath($version, $lang); - $absFilepath = Controller::join_links($path, 'index.md'); - - if(file_exists($absFilepath)) { - $relativeFilePath = str_replace($path, '', $absFilepath); - - $page = new DocumentationPage(); - $page->setRelativePath($relativeFilePath); - $page->setEntity($this); - $page->setLang($lang); - $page->setVersion($version); - - return $page; - } else { - // fall back to reading the modules README.md - } - - return false; - } - /** * @return string */ public function __toString() { return sprintf('DocumentationEntity: %s)', $this->getPath()); } + } \ No newline at end of file diff --git a/code/models/DocumentationEntityLanguage.php b/code/models/DocumentationEntityLanguage.php new file mode 100644 index 0000000..9070744 --- /dev/null +++ b/code/models/DocumentationEntityLanguage.php @@ -0,0 +1,83 @@ +entity = $version; + $this->language = $language; + } + + /** + * @return string + */ + public function Link() { + return Controller::join_links($this->entity->Link(), $this->language); + } + + + /** + * @return string + */ + public function getVersion() { + return $this->entity->getVersion(); + } + + /** + * @return string + */ + public function getLanguage() { + return $this->language; + } + + /** + * @return string + */ + public function getPath() { + return Controller::join_links($this->entity->getPath(), $this->language); + } + + /** + * @return string + */ + public function getBasePath() { + return $this->entity->getPath(); + } + + /** + * @return string + */ + public function getTitle() { + return $this->entity->getTitle(); + } + + /** + * @return string + */ + public function getBaseFolder() { + return $this->entity->getBaseFolder(); + } + + /** + * @return array + */ + public function getSupportedLanguages() { + return $this->entity->getSupportedLanguages(); + } +} \ No newline at end of file diff --git a/code/models/DocumentationEntityVersion.php b/code/models/DocumentationEntityVersion.php new file mode 100644 index 0000000..65c1024 --- /dev/null +++ b/code/models/DocumentationEntityVersion.php @@ -0,0 +1,130 @@ +entity = $entity; + $this->path = $path; + $this->version = $version; + $this->stable = $stable; + + // check what languages that this instance will support. + $langs = scandir($path); + $available = array(); + + if($langs) { + $possible = i18n::get_common_languages(true); + $possible['en'] = true; + + foreach($langs as $key => $lang) { + if(isset($possible[$lang])) { + $this->supportedLanguages[$lang] = Injector::inst()->create( + 'DocumentationEntityLanguage', + $this, + $lang + ); + } else { + + } + } + } + } + + /** + * @return string + */ + public function Link() { + if($this->stable) { + return $this->entity->Link(); + } + + return Controller::join_links($this->entity->Link(), $this->version); + } + + /** + * Return the languages which are available for this version of the entity. + * + * @return array + */ + public function getSupportedLanguages() { + return $this->supportedLanguages; + + } + + /** + * Return whether this entity has a given language. + * + * @return bool + */ + public function hasLanguageSupport($lang) { + return (in_array($lang, $this->getSupportedLanguages())); + } + + /** + * @return float + */ + public function getVersion() { + return $this->version; + } + + /** + * @return string + */ + public function getPath() { + return $this->path; + } + + /** + * @return string + */ + public function getBaseFolder() { + return $this->entity->getFolder(); + } + + /** + * @return string + */ + public function getTitle() { + return $this->entity->getTitle(); + } +} \ No newline at end of file diff --git a/code/models/DocumentationFolder.php b/code/models/DocumentationFolder.php new file mode 100644 index 0000000..679b3d8 --- /dev/null +++ b/code/models/DocumentationFolder.php @@ -0,0 +1,19 @@ +filename); + } +} \ No newline at end of file diff --git a/code/models/DocumentationPage.php b/code/models/DocumentationPage.php index c03cb3d..c3bc926 100755 --- a/code/models/DocumentationPage.php +++ b/code/models/DocumentationPage.php @@ -12,137 +12,42 @@ */ class DocumentationPage extends ViewableData { - /** - * @var DocumentationEntity - */ - protected $entity; - - /** - * Stores the relative path (from the {@link DocumentationEntity} to - * this page. The actual file name can be accessed via {@link $this->getFilename()} - * - * @var string - */ - protected $relativePath; - - /** - * @var string - */ - protected $lang = 'en'; - /** * @var string */ protected $title; - + /** * @var string */ - protected $version; - - /** - * @var boolean - */ - protected $isFolder = false; + protected $summary; /** - * @var integer + * @var DocumentationEntityLanguage */ - protected $pagenumber = 0; - - /** - * @param boolean - */ - public function setIsFolder($isFolder = false) { - $this->isFolder = $isFolder; - } + protected $entity; /** - * @return boolean + * @var string */ - public function getIsFolder($isFolder = false) { - return $this->isFolder; - } + protected $path, $filename; /** - * - * @param int $number + * @param DocumentationEntityLanguage $entity + * @param string $filename + * @param string $path */ - public function setPagenumber($number = 0) { - if (is_int($number )) $this->pagenumber = $number; - } - - /** - * @return DocumentationEntity - */ - public function getEntity() { - return $this->entity; - } - - /** - * @param DocumentationEntity - */ - public function setEntity($entity) { + public function __construct(DocumentationEntityLanguage $entity, $filename, $path) { + $this->filename = $filename; + $this->path = $path; $this->entity = $entity; } - /** - * @return string - */ - public function getRelativePath() { - return $this->relativePath; - } - - /** - * @param string - */ - public function setRelativePath($path) { - $this->relativePath = $path; - } - /** * @return string */ public function getExtension() { - return DocumentationService::get_extension($this->getRelativePath()); - } - - /** - * Absolute path including version and lang folder. - * - * @throws InvalidArgumentException - * - * @param bool $defaultFile - If this is a folder and this is set to true then getPath - * will return the path of the first file in the folder - * @return string - */ - public function getPath($defaultFile = false, $realpath = true) { - if($this->entity) { - $path = Controller::join_links( - $this->entity->getPath($this->getVersion(), $this->lang), - $this->getRelativePath() - ); - - if(!is_dir($path) && $realpath) $path = realpath($path); - else if($defaultFile) { - $file = DocumentationService::find_page($this->entity, explode('/', $this->getRelativePath())); - - if($file) $path = $file; - } - } - else { - $path = $this->getRelativePath(); - } - if(!file_exists($path)) { - throw new InvalidArgumentException(sprintf( - 'Path could not be found. Module path: %s, file path: %s', - $this->entity->getPath(), - $this->getRelativePath() - )); - } - - - return (is_dir($path)) ? rtrim($path, '/') . '/' : $path; + return DocumentationService::get_extension($this->filename); } /** @@ -151,110 +56,39 @@ class DocumentationPage extends ViewableData { * @return string */ public function getBreadcrumbTitle($divider = ' - ') { - $pathParts = explode('/', $this->getRelativePath()); + $pathParts = explode('/', $this->getRelativeLink()); // add the module to the breadcrumb trail. array_unshift($pathParts, $this->entity->getTitle()); - $titleParts = array_map(array('DocumentationService', 'clean_page_name'), $pathParts); + $titleParts = array_map(array('DocumentationHelper', 'clean_page_name'), $pathParts); return implode($divider, $titleParts + array($this->getTitle())); } /** - * Returns the public accessible link for this page. - * - * @param Boolean Absolute URL (incl. domain), or relative to webroot - * @return string + * @return DocumentationEntityLanguage */ - public function getLink($absolute = true) { - if($entity = $this->getEntity()) { - $link = $this->getRelativeLink(); - $link = rtrim(DocumentationService::trim_extension_off($link), '/'); - - // folders should have a / on them. Looks nicer - try { - if(is_dir($this->getPath())) $link .= '/'; - } - catch (Exception $e) {} - } - else { - $link = $this->getPath(true); - } - - if($absolute) { - $fullLink = Controller::join_links($entity->Link($this->getVersion(), $this->lang), $link); - } else { - $fullLink = Controller::join_links($entity->getRelativeLink($this->getVersion(), $this->lang), $link); - } - - return $fullLink; - } - - /** - * Relative to the module base, not the webroot. - * - * @return string - */ - public function getRelativeLink() { - $link = rtrim(DocumentationService::trim_extension_off($this->getRelativePath()), '/'); - - // folders should have a / on them. Looks nicer - try { - if(is_dir($this->getPath())) $link .= '/'; - } catch (Exception $e) {}; - - return $link; - } - - function getLang() { - return $this->lang; - } - - function setLang($lang) { - $this->lang = $lang; - } - - function getVersion() { - return $this->version ? $this->version : $this->entity->getStableVersion(); - } - - function setVersion($version) { - $this->version = $version; - } - - function setTitle($title) { - $this->title = $title; - } - - function getTitle() { - return $this->title; + public function getEntity() { + return $this->entity; } /** - * Set a variable from the metadata field on this class - * - * @param string key - * @param mixed value + * @return string */ - public function setMetaData($key, $value) { - $this->$key = $value; + public function getTitle() { + if($this->title) { + return $this->title; + } + + return DocumentationHelper::clean_page_name($this->filename); } /** * @return string */ - public function getFilename() { - $path = rtrim($this->relativePath, '/'); - - try { - return (is_dir($this->getPath())) ? $path . '/' : $path; - } - catch (Exception $e) { - - } - - return $path; + public function getSummary() { + return $this->summary; } /** @@ -266,20 +100,17 @@ class DocumentationPage extends ViewableData { */ public function getMarkdown($removeMetaData = false) { try { - $path = $this->getPath(true); - - if($path) { - $ext = $this->getExtension(); - - if(empty($ext) || DocumentationService::is_valid_extension($ext)) { - if ($md = file_get_contents($path)) { - if ($this->title != 'Index') $this->getMetadataFromComments($md, $removeMetaData); - } - return $md; - } + if ($md = file_get_contents($this->path)) { + if ($this->title != 'Index') { + $this->populateMetaDataFromText($md, $removeMetaData); + } + + return $md; } } - catch(InvalidArgumentException $e) {} + catch(InvalidArgumentException $e) { + + } return false; } @@ -291,23 +122,65 @@ class DocumentationPage extends ViewableData { * * @return string */ - public function getHTML($version, $lang = 'en') { - return DocumentationParser::parse($this, $this->entity->getRelativeLink($version, $lang)); + public function getHTML() { + return DocumentationParser::parse( + $this, + $this->entity->Link() + ); + } + + /** + * @return string + */ + public function getRelativeLink() { + $path = str_replace($this->entity->getPath(), '', $this->path); + $url = explode('/', $path); + + $url = implode('/', array_map(function($a) { + return DocumentationHelper::clean_page_url($a); + }, $url)); + + $url = rtrim($url, '/') . '/'; + + return $url; + } + + /** + * @return string + */ + public function getPath() { + return $this->path; + } + + /** + * Returns the URL that will be required for the user to hit to view the + * given document base name. + * + * @param string $file + * @param string $path + * + * @return string + */ + public function Link() { + return Controller::join_links( + $this->entity->Link(), + $this->getRelativeLink() + ); } /** - * get metadata from the first html block in the page, then remove the + * Return metadata from the first html block in the page, then remove the * block on request * * @param DocumentationPage $md * @param bool $remove */ - public function getMetadataFromComments(&$md, $removeMetaData = false) { - if($md && DocumentationService::meta_comments_enabled()) { - + 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]) { $metaDataFound = false; @@ -320,36 +193,19 @@ class DocumentationPage extends ViewableData { // check if a property exists for this key if (property_exists(get_class(), $key)) { - $this->setMetaData($key, $meta['value'][$index]); + $this->$key = $meta['value'][$index]; + $metaDataFound = true; } } } - // optionally remove the metadata block (only on the page that is displayed) + + // optionally remove the metadata block (only on the page that + // is displayed) if ($metaDataFound && $removeMetaData) { $md = preg_replace($extPattern, '', $md); } } } } - - /** - * Returns the next page. Either retrieves the sibling of the current page - * or return the next sibling of the parent page. - * - * @return DocumentationPage - */ - public function getNextPage() { - - } - - /** - * Returns the previous page. Either returns the previous sibling or the - * parent of this page - * - * @return DocumentationPage - */ - public function getPreviousPage() { - - } } \ No newline at end of file diff --git a/code/tasks/DocumentationBuild.php b/code/tasks/DocumentationBuild.php new file mode 100644 index 0000000..6492180 --- /dev/null +++ b/code/tasks/DocumentationBuild.php @@ -0,0 +1,14 @@ +"; + print_r($manifest->getPages()); + echo ""; + die();; + + + } +} \ No newline at end of file diff --git a/composer.json b/composer.json index 1f82217..7c3ff5d 100644 --- a/composer.json +++ b/composer.json @@ -17,10 +17,6 @@ "silverstripe/framework": "~3.1", "erusev/parsedown-extra": "0.1.0" }, - "repositories": [{ - "type": "vcs", - "url": "https://github.com/wilr/parsedown" - }], "suggest": { "silverstripe/staticpublisher": "Allows publishing documentation as HTML" } diff --git a/css/layout.css b/css/layout.css index 2b044a8..9682514 100644 --- a/css/layout.css +++ b/css/layout.css @@ -57,6 +57,11 @@ html { padding: 15px 15px 14px; } + #sidebar .nav .current { + background: #f6f7f8; + font-weight: bold; + } + #layout { padding-bottom: 20px; } diff --git a/docs/en/Syntax-Highlighting.md b/docs/en/Syntax-Highlighting.md index 58c097a..c49bc21 100644 --- a/docs/en/Syntax-Highlighting.md +++ b/docs/en/Syntax-Highlighting.md @@ -1,3 +1,5 @@ +title: Syntax Highlighting + # Syntax Highlighting The custom Markdown parser can render custom prefixes for code blocks, and @@ -9,12 +11,12 @@ To include the syntax highlighter source, add the following to your `Documentati ```php - Requirements::javascript(THIRDPARTY_DIR .'/jquery/jquery.js'); Requirements::javascript('sapphiredocs/thirdparty/syntaxhighlighter/scripts/shCore.js'); Requirements::javascript('sapphiredocs/thirdparty/syntaxhighlighter/scripts/shBrushJScript.js'); Requirements::javascript('sapphiredocs/thirdparty/syntaxhighlighter/scripts/shBrushPHP.js'); Requirements::javascript('sapphiredocs/thirdparty/syntaxhighlighter/scripts/shBrushXML.js'); + // ... any additional syntaxes you want to support Requirements::combine_files( 'syntaxhighlighter.js', @@ -33,6 +35,3 @@ Requirements::css('sapphiredocs/thirdparty/syntaxhighlighter/styles/shCore.css') Requirements::css('sapphiredocs/thirdparty/syntaxhighlighter/styles/shCoreDefault.css'); Requirements::css('sapphiredocs/thirdparty/syntaxhighlighter/styles/shThemeRDark.css'); ``` - -You can overload the `DocumentationViewer` class and add a custom route through `Director::addRule()` -if you prefer not to modify the module file. diff --git a/docs/en/Writing-Documentation.md b/docs/en/Writing-Documentation.md deleted file mode 100755 index d1507f8..0000000 --- a/docs/en/Writing-Documentation.md +++ /dev/null @@ -1,62 +0,0 @@ -# Writing Documentation - -The files have to end with the __.md__ or __.markdown__ extension. The -documentation viewer will automatically replace hyphens (-) with spaces. - - my-documentation-file.md - -Translates to: - - My documentation file - -The module also support number prefixing for specifying the order of pages in -the index pages and navigation trees. - - 03-foo.md - 1-bar.md - 4-baz.md - -Will be output as the following in the listing views. - - Bar - Foo - Baz - -## Localization - -All documentation folder should be localized. Even if you do not plan on supporting -multiple languages you need to write your documentation in a 'en' subfolder - - /module/docs/en/ - - -## Syntax - -Documentation should be written in markdown with an `.md` extension attached. -To view the syntax for page formatting check out [Daring Fireball](http://daringfireball.net/projects/markdown/syntax). - -To see how to use the documentation from examples, I recommend opening up this -file in your text editor and playing around. As these files are plain text, any -text editor will be able to open and write markdown files. - - -## Creating Hierarchy - -The document viewer supports a hierarchical folder structure so you can categorize -documentation and create topics. - -## Directory Listing - -Each folder you create should also contain a __index.md__ file which contains -an overview of the module and related links. If no index is available, the -default behaviour is to display an ordered list of links. - -## Table of Contents - -The table of contents on each module page is generated based on where and what -headers you use. - -## Images and Files - -If you want to attach images and other assets to a page you need to bundle those -in a directory called _images at the same level as your documentation. \ No newline at end of file diff --git a/docs/en/Configuration-Options.md b/docs/en/configuration.md similarity index 59% rename from docs/en/Configuration-Options.md rename to docs/en/configuration.md index 0c62420..3127c0d 100755 --- a/docs/en/Configuration-Options.md +++ b/docs/en/configuration.md @@ -2,9 +2,9 @@ ## Registering what to document -By default the documentation system will parse all the directories in your project -and include the documentation. If you want to only specify a few folders you can -disable it and register your paths manually +By default the documentation system will parse all the directories in your +project and include the documentation. If you want to only specify a few folders +you can disable it and register your paths manually :::php // turns off automatic parsing of filesystem @@ -65,13 +65,6 @@ Custom metadata can be added to the head of the MarkDown file like this: Make sure to add an empty line to separate the metadata from the content of the file. -You now need to explicitly enable the use of metadata by adding the following to -your _config.php: - - ```php - DocumentationService::enable_meta_comments(); - ``` - **Note:** SilverStripe needs to read the contents of each page to retrieve the metadata. This is expensive, so if you do not plan to use custom sorting, do not enable this feature: @@ -101,4 +94,65 @@ Basically all DocumentationPage properties can be added to the metadata comment block. Beware that the outcome isn't always predictable. Adding a title property to the block will change the menu title, but the breadcrumbs are at this time not yet supported. - \ No newline at end of file + + +The files have to end with the __.md__ or __.markdown__ extension. The +documentation viewer will automatically replace hyphens (-) with spaces. + + my-documentation-file.md + +Translates to: + + My documentation file + +The module also support number prefixing for specifying the order of pages in +the index pages and navigation trees. + + 03-foo.md + 1-bar.md + 4-baz.md + +Will be output as the following in the listing views. + + Bar + Foo + Baz + +## Localization + +All documentation folder should be localized. Even if you do not plan on supporting +multiple languages you need to write your documentation in a 'en' subfolder + + /module/docs/en/ + + +## Syntax + +Documentation should be written in markdown with an `.md` extension attached. +To view the syntax for page formatting check out [Daring Fireball](http://daringfireball.net/projects/markdown/syntax). + +To see how to use the documentation from examples, I recommend opening up this +file in your text editor and playing around. As these files are plain text, any +text editor will be able to open and write markdown files. + + +## Creating Hierarchy + +The document viewer supports a hierarchical folder structure so you can categorize +documentation and create topics. + +## Directory Listing + +Each folder you create should also contain a __index.md__ file which contains +an overview of the module and related links. If no index is available, the +default behaviour is to display an ordered list of links. + +## Table of Contents + +The table of contents on each module page is generated based on where and what +headers you use. + +## Images and Files + +If you want to attach images and other assets to a page you need to bundle those +in a directory called _images at the same level as your documentation. \ No newline at end of file diff --git a/docs/en/index.md b/docs/en/index.md index b60bc43..43c83e1 100755 --- a/docs/en/index.md +++ b/docs/en/index.md @@ -84,7 +84,7 @@ By default, the documentation is available in `dev/docs`. If you want it to live on the webroot instead of a subfolder or on another url address, add the following configuration to your _config.php file: - DocumentationViewer::set_link_base(''); + Config::inst()->update('DocumentationViewer', 'link_base', ''); Director::addRules(1, array( '$Action' => 'DocumentationViewer', diff --git a/docs/en/ss-markdown.md b/docs/en/markdown.md similarity index 99% rename from docs/en/ss-markdown.md rename to docs/en/markdown.md index 7d64d06..c9d1d08 100644 --- a/docs/en/ss-markdown.md +++ b/docs/en/markdown.md @@ -108,7 +108,8 @@ Currently this is not supported, as all HTML is generated on the fly. ### Can I contribute to the parser and rendering project? -Of course, the `docsviewer` code is BSD licensed - we're looking forward to your contributions! +Of course, the `docsviewer` code is BSD licensed - we're looking forward to your +contributions! ## Related ## diff --git a/docs/en/statichtml.md b/docs/en/statichtml.md new file mode 100644 index 0000000..5c20c59 --- /dev/null +++ b/docs/en/statichtml.md @@ -0,0 +1,24 @@ +title: Publishing Static Files + +# HTML Publishing + +If you wish to generate a truly static version of your documentation after it +has been rendered through the website, add the [Static Publisher](https://github.com/silverstripe-labs/silverstripe-staticpublisher) +module to your documentation project and set the following configuration in your +applications config.yml: + +``` +StaticExporter: + extensions: + - DocumentationStaticPublisherExtension +``` + +If you don't plan on using static publisher for anything else and you have the +cms module installed, make sure you disable the CMS from being published. + +Again, in your applications config.yml file + +``` +StaticExporter: + disable_sitetree_export: true +``` \ No newline at end of file diff --git a/javascript/DocumentationViewer.js b/javascript/DocumentationViewer.js index 91cda01..e2c34bc 100755 --- a/javascript/DocumentationViewer.js +++ b/javascript/DocumentationViewer.js @@ -77,7 +77,7 @@ * * Transform a #table-of-contents div to a nested list */ - if($("#content-column").length > 0) { + if($("#table-contents-holder").length > 0) { var toc = '
' + '

Table of contents

'; // Table of content location - var title = $('#content-column h1:first'); + var title = $('#content h1:first'); if (title.length > 0) { title.after(toc); } else { - var breadcrums = $('#content-column .doc-breadcrumbs'); + var breadcrums = $('#content .doc-breadcrumbs'); if (breadcrums.length > 0) { breadcrums.after(toc); } else { - $('#content-column').prepend(toc); + $('#content').prepend(toc); } } @@ -133,7 +133,7 @@ */ var url = window.location.href; - $("#content-column h1[id], #content-column h2[id], #content-column h3[id], #content-column h4[id], #content-column h5[id], #content-column h6[id]").each(function() { + $("#content h1[id], #content h2[id], #content h3[id], #content h4[id], #content h5[id], #content h6[id]").each(function() { var link = ''; $(this).append(' ' + link); }); diff --git a/templates/Includes/DocumentationBreadcrumbs.ss b/templates/Includes/DocumentationBreadcrumbs.ss index 3eefe41..2b5c8d3 100755 --- a/templates/Includes/DocumentationBreadcrumbs.ss +++ b/templates/Includes/DocumentationBreadcrumbs.ss @@ -1,7 +1,7 @@
-

- <% loop Breadcrumbs %> - $Title <% if Last %><% else %>›<% end_if %> - <% end_loop %> -

+

+ <% loop Breadcrumbs %> + $Title <% if Last %><% else %>›<% end_if %> + <% end_loop %> +

\ No newline at end of file diff --git a/templates/Includes/DocumentationNextPrevious.ss b/templates/Includes/DocumentationNextPrevious.ss new file mode 100644 index 0000000..d26d88f --- /dev/null +++ b/templates/Includes/DocumentationNextPrevious.ss @@ -0,0 +1,11 @@ +<% if NextPage || PreviousPage %> +
+ <% if PreviousPage %> + + <% end_if %> + + <% if NextPage %> + + <% end_if %> +
+<% end_if %> \ No newline at end of file diff --git a/templates/Includes/DocumentationSidebar.ss b/templates/Includes/DocumentationSidebar.ss index 43a576f..efaa14f 100644 --- a/templates/Includes/DocumentationSidebar.ss +++ b/templates/Includes/DocumentationSidebar.ss @@ -1,16 +1,20 @@ diff --git a/templates/Includes/DocumentationTableContents.ss b/templates/Includes/DocumentationTableContents.ss new file mode 100644 index 0000000..b8540aa --- /dev/null +++ b/templates/Includes/DocumentationTableContents.ss @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/templates/Layout/DocumentationViewer.ss b/templates/Layout/DocumentationViewer.ss deleted file mode 100755 index 3ef0874..0000000 --- a/templates/Layout/DocumentationViewer.ss +++ /dev/null @@ -1,19 +0,0 @@ -<% if VersionWarning %> - <% include DocumentationVersion_warning %> -<% end_if %> - -
-
- <% if Breadcrumbs %> - <% include DocumentationBreadcrumbs %> - <% end_if %> - - $Content - - <% if EditLink %> - <% include DocumentationEditLink %> - <% end_if %> -
-
- -<% include DocumentationComments %> \ No newline at end of file diff --git a/templates/Layout/DocumentationViewer_DocumentationFolder.ss b/templates/Layout/DocumentationViewer_DocumentationFolder.ss new file mode 100755 index 0000000..b9a88c9 --- /dev/null +++ b/templates/Layout/DocumentationViewer_DocumentationFolder.ss @@ -0,0 +1,17 @@ +
+ <% if VersionWarning %> + <% include DocumentationVersion_warning %> + <% end_if %> + +

$Title

+ + <% include DocumentationTableContents %> + + <% loop Children %> + + <% end_loop %> + + <% include DocumentationNextPrevious %> +
\ No newline at end of file diff --git a/templates/Layout/DocumentationViewer_DocumentationPage.ss b/templates/Layout/DocumentationViewer_DocumentationPage.ss new file mode 100755 index 0000000..bd55eb7 --- /dev/null +++ b/templates/Layout/DocumentationViewer_DocumentationPage.ss @@ -0,0 +1,20 @@ +
+ <% if VersionWarning %> + <% include DocumentationVersion_warning %> + <% end_if %> + + <% if Breadcrumbs %> + <% include DocumentationBreadcrumbs %> + <% end_if %> + + $Content + + <% include DocumentationNextPrevious %> + + <% if EditLink %> + <% include DocumentationEditLink %> + <% end_if %> + + + <% include DocumentationComments %> +
\ No newline at end of file diff --git a/templates/Layout/DocumentationViewer_folder.ss b/templates/Layout/DocumentationViewer_folder.ss deleted file mode 100755 index ba2e0e6..0000000 --- a/templates/Layout/DocumentationViewer_folder.ss +++ /dev/null @@ -1,15 +0,0 @@ -
- <% if VersionWarning %> - <% include DocumentationVersion_warning %> - <% end_if %> - - <% if Content %> - $Content - - <% if EditLink %> - <% include DocumentationEditLink %> - <% end_if %> - <% else %> -

$Title

- <% end_if %> -
\ No newline at end of file diff --git a/templates/Layout/DocumentationViewer_home.ss b/templates/Layout/DocumentationViewer_home.ss deleted file mode 100755 index 18138d1..0000000 --- a/templates/Layout/DocumentationViewer_home.ss +++ /dev/null @@ -1,13 +0,0 @@ -
-

<% _t('DOCUMENTEDMODULES', 'Documented Modules') %>

- - <% if Entities %> - <% loop Entities %> -
-

$Title

-
- <% end_loop %> - <% else %> -

<% _t('NOMODULEDOCUMENTATION', 'No modules with documentation installed could be found.') %>

- <% end_if %> -
\ No newline at end of file diff --git a/templates/Layout/DocumentationViewer_results.ss b/templates/Layout/DocumentationViewer_results.ss index 95bf869..864ec80 100755 --- a/templates/Layout/DocumentationViewer_results.ss +++ b/templates/Layout/DocumentationViewer_results.ss @@ -1,48 +1,45 @@ -
-
-

Your search for "$Query.XML" found $TotalResults result<% if TotalResults != 1 %>s<% end_if %>.

- <% if AdvancedSearchEnabled %> +
+

Your search for "$Query.XML" found $TotalResults result<% if TotalResults != 1 %>s<% end_if %>.

+ + <% if AdvancedSearchEnabled %>

<% _t('ADVANCEDSEARCH', 'Advanced Search') %>

- $AdvancedSearchForm + $AdvancedSearchForm <% end_if %> - <% if Results %> -

Showing page $ThisPage of $TotalPages

- - <% loop Results %> -

<% if BreadcrumbTitle %>$BreadcrumbTitle<% else %>$Title<% end_if %>

-

$Content.LimitCharacters(200)

- <% end_loop %> - - <% if SearchPages %> -
    - <% if PrevUrl = false %><% else %> - - <% end_if %> - - <% loop SearchPages %> - <% if IsEllipsis %> -
  • ...
  • - <% else %> - <% if Current %> -
  • $PageNumber
  • - <% else %> -
  • $PageNumber
  • - <% end_if %> - <% end_if %> - <% end_loop %> - - <% if NextUrl = false %> + <% if Results %> +

    Showing page $ThisPage of $TotalPages

    + + <% loop Results %> +

    <% if BreadcrumbTitle %>$BreadcrumbTitle<% else %>$Title<% end_if %>

    +

    $Content.LimitCharacters(200)

    + <% end_loop %> + + <% if SearchPages %> +
      + <% if PrevUrl = false %><% else %> + + <% end_if %> + + <% loop SearchPages %> + <% if IsEllipsis %> +
    • ...
    • <% else %> - - <% end_if %> -
    - <% end_if %> - - <% else %> -

    No Results

    + <% if Current %> +
  • $PageNumber
  • + <% else %> +
  • $PageNumber
  • + <% end_if %> + <% end_if %> + <% end_loop %> + + <% if NextUrl = false %> + <% else %> + + <% end_if %> +
<% end_if %> -
- - + + <% else %> +

No Results

+ <% end_if %>
\ No newline at end of file diff --git a/tests/DocumentationEntityTest.php b/tests/DocumentationEntityTest.php deleted file mode 100755 index 20d921a..0000000 --- a/tests/DocumentationEntityTest.php +++ /dev/null @@ -1,34 +0,0 @@ -assertEquals($entity->getTitle(), 'My Test'); - $this->assertEquals($entity->getVersions(), array('1.0')); - $this->assertEquals($entity->getLanguages(), array('en', 'de')); - $this->assertEquals($entity->getFolder(), 'docs'); - - $this->assertTrue($entity->hasVersion('1.0')); - $this->assertFalse($entity->hasVersion('2.0')); - - $this->assertTrue($entity->hasLanguage('en')); - $this->assertFalse($entity->hasLanguage('fr')); - } - - function testgetStableVersion() { - $entity = new DocumentationEntity('docs', '1.0', DOCSVIEWER_PATH. '/tests/docs/', 'My Test'); - $entity->addVersion('1.1', DOCSVIEWER_PATH. '/tests/docs-v2.4/'); - $entity->addVersion('0.0', DOCSVIEWER_PATH. '/tests/docs-v3.0/'); - $this->assertEquals('1.1', $entity->getStableVersion(), 'Automatic version sorting'); - - $entity = new DocumentationEntity('docs', '1.0', DOCSVIEWER_PATH. '/tests/docs/', 'My Test'); - $entity->addVersion('1.1.', DOCSVIEWER_PATH .'/tests/docs-v2.4/'); - $entity->setStableVersion('1.0'); - $this->assertEquals('1.0', $entity->getStableVersion(), 'Manual setting'); - } -} \ No newline at end of file diff --git a/tests/DocumentationManifestTests.php b/tests/DocumentationManifestTests.php new file mode 100644 index 0000000..275c7cb --- /dev/null +++ b/tests/DocumentationManifestTests.php @@ -0,0 +1,85 @@ +origEnabled = DocumentationService::automatic_registration_enabled(); + DocumentationService::set_automatic_registration(false); + $this->origModules = DocumentationService::get_registered_entities(); + + $this->origLinkBase = Config::inst()->get('DocumentationViewer', 'link_base'); + Config::inst()->update('DocumentationViewer', 'link_base', 'dev/docs/'); + + foreach($this->origModules as $module) { + DocumentationService::unregister($module->getFolder()); + } + + // We set 3.0 as current, and test most assertions against 2.4 - to avoid 'current' rewriting issues + DocumentationService::register("testdocs", DOCSVIEWER_PATH . "/tests/docs/", '2.3'); + DocumentationService::register("testdocs", DOCSVIEWER_PATH . "/tests/docs-v2.4/", '2.4', 'Doc Test', true); + DocumentationService::register("testdocs", DOCSVIEWER_PATH . "/tests/docs-v3.0/", '3.0', 'Doc Test'); + + + $this->manifest = new DocumentationManifest(true); + $this->pages = $this->manifest->getPages(); + } + + public function tearDownOnce() { + parent::tearDownOnce(); + + DocumentationService::unregister("testdocs"); + DocumentationService::set_automatic_registration($this->origEnabled); + + Config::inst()->update('DocumentationViewer', 'link_base', $this->origLinkBase); + } + + /** + * Check that the manifest matches what we'd expect. + */ + public function testRegenerate() { + $match = array( + 'dev/docs/testdocs/2.3/de/', + 'dev/docs/testdocs/2.3/de/german/', + 'dev/docs/testdocs/2.3/de/test/', + 'dev/docs/testdocs/2.3/en/', + 'dev/docs/testdocs/2.3/en/sort/', + 'dev/docs/testdocs/2.3/en/subfolder/', + 'dev/docs/testdocs/2.3/en/test/', + 'dev/docs/testdocs/2.3/en/sort/basic/', + 'dev/docs/testdocs/2.3/en/sort/some-page/', + 'dev/docs/testdocs/2.3/en/sort/intermediate/', + 'dev/docs/testdocs/2.3/en/sort/another-page/', + 'dev/docs/testdocs/2.3/en/sort/advanced/', + 'dev/docs/testdocs/2.3/en/subfolder/subpage/', + 'dev/docs/testdocs/2.3/en/subfolder/subsubfolder/', + 'dev/docs/testdocs/2.3/en/subfolder/subsubfolder/subsubpage/', + 'dev/docs/testdocs/en/', + 'dev/docs/testdocs/en/test/', + 'dev/docs/testdocs/3.0/en/', + 'dev/docs/testdocs/3.0/en/changelog/', + 'dev/docs/testdocs/3.0/en/tutorials/', + 'dev/docs/testdocs/3.0/en/empty/' + ); + + $this->assertEquals($match, array_keys($this->pages)); + } + + public function testGetNextPage() { + + } + + public function testGetPreviousPage() { + + } + + public function testGetPage() { + + } +} \ No newline at end of file diff --git a/tests/DocumentationPageTest.php b/tests/DocumentationPageTest.php index cf4d02f..57f42b4 100755 --- a/tests/DocumentationPageTest.php +++ b/tests/DocumentationPageTest.php @@ -4,10 +4,9 @@ * @package docsviewer * @subpackage tests */ - class DocumentationPageTest extends SapphireTest { - function testGetLink() { + public function testGetLink() { $entity = new DocumentationEntity('testmodule', null, DOCSVIEWER_PATH .'/tests/docs/'); $page = new DocumentationPage(); @@ -43,8 +42,7 @@ class DocumentationPageTest extends SapphireTest { $this->assertStringEndsWith('versionlinks/en/1/test', $page->Link); } - - function testGetRelativePath() { + public function testGetRelativePath() { $page = new DocumentationPage(); $page->setRelativePath('test.md'); $page->setEntity(new DocumentationEntity('mymodule', null, DOCSVIEWER_PATH . '/tests/docs/')); @@ -58,7 +56,7 @@ class DocumentationPageTest extends SapphireTest { $this->assertEquals('subfolder/subpage.md', $page->getRelativePath()); } - function testGetPath() { + public function testGetPath() { $absPath = DOCSVIEWER_PATH .'/tests/docs/'; $page = new DocumentationPage(); $page->setRelativePath('test.md'); @@ -73,7 +71,7 @@ class DocumentationPageTest extends SapphireTest { $this->assertEquals($absPath . 'en/subfolder/subpage.md', $page->getPath()); } - function testGetBreadcrumbTitle() { + public function testGetBreadcrumbTitle() { $entity = new DocumentationEntity('testmodule', null, DOCSVIEWER_PATH . '/tests/docs/'); $page = new DocumentationPage(); diff --git a/tests/DocumentationViewerTest.php b/tests/DocumentationViewerTest.php index 3f854e4..fe58ec5 100755 --- a/tests/DocumentationViewerTest.php +++ b/tests/DocumentationViewerTest.php @@ -12,14 +12,16 @@ class DocumentationViewerTest extends FunctionalTest { protected $autoFollowRedirection = false; - function setUpOnce() { + public function setUpOnce() { parent::setUpOnce(); $this->origEnabled = DocumentationService::automatic_registration_enabled(); DocumentationService::set_automatic_registration(false); $this->origModules = DocumentationService::get_registered_entities(); - $this->origLinkBase = DocumentationViewer::get_link_base(); - DocumentationViewer::set_link_base('dev/docs/'); + + $this->origLinkBase = Config::inst()->get('DocumentationViewer', 'link_base'); + Config::inst()->update('DocumentationViewer', 'link_base', 'dev/docs/'); + foreach($this->origModules as $module) { DocumentationService::unregister($module->getFolder()); } @@ -33,12 +35,13 @@ class DocumentationViewerTest extends FunctionalTest { DocumentationService::register("DocumentationViewerAltModule2", DOCSVIEWER_PATH . "/tests/docs-search/", '1.0'); } - function tearDownOnce() { + public function tearDownOnce() { parent::tearDownOnce(); DocumentationService::unregister("DocumentationViewerTests"); DocumentationService::set_automatic_registration($this->origEnabled); - DocumentationViewer::set_link_base($this->origLinkBase); + + Config::inst()->update('DocumentationViewer', 'link_base', $this->origLinkBase); } /** diff --git a/tests/docs-withoutlang/fail.md b/tests/docs-withoutlang/fail.md new file mode 100644 index 0000000..e53d228 --- /dev/null +++ b/tests/docs-withoutlang/fail.md @@ -0,0 +1 @@ +# Failure \ No newline at end of file diff --git a/tests/docs/de/german.md b/tests/docs/de/german.md new file mode 100644 index 0000000..4d3319a --- /dev/null +++ b/tests/docs/de/german.md @@ -0,0 +1,2 @@ +# German +