FEATURE Added parsing support for relative links (relative to module base). Introduced DocumentationPage to encapsulate this information.

ENHANCEMENT Saving $ModuleName in viewer instead of getting it from Remaining[0]
MINOR Don't include version in breadcrumbs, doesn't make sense in this context (e.g. "2.4/en/cms", the "2.4" part is connecte to the cms module, hence an index of all versions regardless of module is not very useful)
This commit is contained in:
Ingo Schommer 2010-08-01 04:46:32 +00:00
parent df4a3ed721
commit 277bca7b11
11 changed files with 546 additions and 86 deletions

View File

@ -6,6 +6,9 @@
* rather than a specific page but if we need page options we may need to introduce
* a class for that.
*
* Each folder must have at least one language subfolder, which is automatically
* determined through {@link addVersion()} and should not be included in the $path argument.
*
* @package sapphiredocs
*/
@ -41,7 +44,7 @@ class DocumentationEntity extends ViewableData {
*
* @param String $module name of module
* @param String $version version of this module
* @param String $path path to this module
* @param String $path Absolute path to this module (excluding language folders)
*/
function __construct($module, $version = '', $path, $title = false) {
$this->addVersion($version, $path);
@ -165,7 +168,7 @@ class DocumentationEntity extends ViewableData {
}
/**
* Return the path to this documentation entity
* Return the absolute path to this documentation entity.
*
* @return String
*/
@ -174,9 +177,14 @@ class DocumentationEntity extends ViewableData {
if(!$version) $version = '';
if(!$lang) $lang = 'en';
if(!$this->hasVersion($version)) $path = array_pop($this->versions);
else $path = $this->versions[$version];
// Get version, or fall back to first available
if($this->hasVersion($version)) {
$path = $this->versions[$version];
} else {
$versions = $this->getVersions();
$path = $this->versions[$versions[0]];
}
return $path . $lang .'/';
return $path . '/' . $lang .'/';
}
}

View File

@ -0,0 +1,90 @@
<?php
/**
* A specific page within a {@link DocumentationEntity}.
* Has to represent an actual file, please use {@link DocumentationViewer}
* to generate "virtual" index views.
*
* @package sapphiredocs
*/
class DocumentationPage extends ViewableData {
/**
* @var DocumentationEntity
*/
protected $entity;
/**
* @var String
*/
protected $relativePath;
protected $lang = 'en';
protected $version;
function __construct($relativePath, $entity, $lang = null, $version = null) {
$this->entity = $entity;
$this->relativePath = $relativePath;
if($lang) $this->lang = $lang;
if($version) $this->version = $version;
if(!file_exists($this->getPath())) {
throw new InvalidArgumentException(sprintf(
'Path could not be found: "%s" (module path: %s, file path: %s)',
$this->getPath(),
$this->entity->getPath(),
$this->relativePath
));
}
parent::__construct();
}
/**
* @return DocumentationEntity
*/
function getEntity() {
return $this->entity;
}
/**
* @return String Relative path to file or folder within the entity (including file extension),
* but excluding version or language folders.
*/
function getRelativePath() {
return $this->relativePath;
}
/**
* Absolute path including version and lang folder.
*
* @return String
*/
function getPath() {
return realpath($this->entity->getPath($this->version, $this->lang) . '/' . $this->getRelativePath());
}
function getLang() {
return $this->lang;
}
function getVersion() {
return $this->version;
}
/**
* @return String
*/
function getMarkdown() {
return file_get_contents($this->getPath());
}
/**
* @param String $baselink
* @return String
*/
function getHTML($baselink = null) {
return DocumentationParser::parse($this, $baselink);
}
}

View File

@ -14,21 +14,89 @@ class DocumentationParser {
* lookup on the file system. Automatically appends the file extension to one of the markdown
* extensions as well so /install/ in a web browser will match /install.md or /INSTALL.md
*
* @param String $module path to a module
* @param Array path of urls. Should be folders, last one is a page
* Filepath: /var/www/myproject/src/cms/en/folder/subfolder/page.md
* URL: http://myhost/mywebroot/dev/docs/2.4/cms/en/folder/subfolder/page
* Webroot: http://myhost/mywebroot/
* Baselink: dev/docs/2.4/cms/en/
* Pathparts: folder/subfolder/page
*
* @param DocumentationPage $page
* @param String $baselink Link relative to webroot, up until the "root" of the module.
* Necessary to rewrite relative links
*
* @return HTMLText
*/
public static function parse($module, $path) {
public static function parse(DocumentationPage $page, $baselink = null) {
require_once('../sapphiredocs/thirdparty/markdown.php');
if($content = self::find_page($module, $path)) {
$content = Markdown(file_get_contents($content));
$md = $page->getMarkdown();
return DBField::create('HTMLText', $content);
// Pre-processing
$md = self::rewrite_relative_links($md, $page, $baselink);
$html = Markdown($md);
return DBField::create('HTMLText', $html);
}
/**
* Resolves all relative links within markdown.
*
* @param String $md Markdown content
* @param DocumentationPage $page
* @param String $baselink
* @return String Markdown
*/
static function rewrite_relative_links($md, $page, $baselink) {
$re = '/
\[
(.*?) # link title (non greedy)
\]
\(
(.*?) # link url (non greedy)
\)
/x';
preg_match_all($re, $md, $matches);
// relative path (to module base folder), without the filename
$relativePath = dirname($page->getRelativePath());
if($relativePath == '.') $relativePath = '';
if($matches) foreach($matches[0] as $i => $match) {
$title = $matches[1][$i];
$url = $matches[2][$i];
// Don't process API links
if(preg_match('/^api:/', $url)) continue;
// Don't process absolute links (based on protocol detection)
$urlParts = parse_url($url);
if($urlParts && isset($urlParts['scheme'])) continue;
// Rewrite URL (relative or absolute)
if(preg_match('/^\//', $url)) {
$relativeUrl = $baselink . $url;
} else {
$relativeUrl = $baselink . '/' . $relativePath . '/' . $url;
}
// Resolve relative paths
while(strpos($relativeUrl, '..') !== FALSE) {
$relativeUrl = preg_replace('/\w+\/\.\.\//', '', $relativeUrl);
}
// Replace any double slashes (apart from protocol)
$relativeUrl = preg_replace('/([^:])\/{2,}/', '$1/', $relativeUrl);
// Replace in original content
$md = str_replace(
$match,
sprintf('[%s](%s)', $title, $relativeUrl),
$md
);
}
return false;
return $md;
}
/**
@ -37,13 +105,13 @@ class DocumentationParser {
*
* Name may also be a path /install/foo/bar.
*
* @param String $entity path to the entity
* @param String $modulePath Absolute path to the entity
* @param Array $path path to the file in the entity
*
* @return String|false - File path
*/
private static function find_page($entity, $path) {
return self::find_page_recursive($entity, $path);
static function find_page($modulePath, $path) {
return self::find_page_recursive($modulePath, $path);
}
/**

View File

@ -38,7 +38,6 @@ class DocumentationViewer extends Controller {
'LanguageTitle' => 'Text'
);
function init() {
parent::init();
@ -61,9 +60,9 @@ class DocumentationViewer extends Controller {
*/
public function handleRequest(SS_HTTPRequest $request) {
$this->Version = $request->shift();
$this->Lang = $request->shift();
$this->Version = $request->shift();
$this->Lang = $request->shift();
$this->ModuleName = $request->shift();
$this->Remaining = $request->shift(10);
DocumentationService::load_automatic_registration();
@ -75,9 +74,11 @@ class DocumentationViewer extends Controller {
// /en/sapphire/page which is a link to the latest one
if(!is_numeric($this->Version)) {
array_unshift($this->Remaining, $this->ModuleName);
// not numeric so /en/sapphire/folder/page
if(isset($this->Lang) && $this->Lang)
array_unshift($this->Remaining, $this->Lang);
$this->ModuleName = $this->Lang;
$this->Lang = $this->Version;
$this->Version = null;
@ -108,18 +109,16 @@ class DocumentationViewer extends Controller {
// 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->Remaining) {
if($this->ModuleName || $this->Remaining) {
$paramCount = count($this->Remaining);
if($paramCount == 1) {
if($paramCount == 0) {
return parent::getViewer('folder');
}
else if($module = $this->getModule()) {
$params = $this->Remaining;
array_shift($params); // module name
$path = implode('/', array_unique($params));
if(is_dir($module->getPath() . $path)) return parent::getViewer('folder');
@ -224,11 +223,24 @@ class DocumentationViewer extends Controller {
if($modules) {
foreach($modules as $module) {
$filepath = $module->getPath() . '/index.md';
if(file_exists($filepath)) {
$page = new DocumentationPage(
$filepath,
$module,
$this->Lang,
$this->Version
);
$content = DocumentationParser::parse($page, $this->Link(array_slice($this->Remaining, -1, -1)));
} else {
$content = '';
}
// build the dataset. Load the $Content from an index.md
$output->push(new ArrayData(array(
'Title' => $module->getTitle(),
'Code' => $module,
'Content' => DocumentationParser::parse($module->getPath(), array('index'))
'Content' => $content
)));
}
}
@ -242,13 +254,34 @@ class DocumentationViewer extends Controller {
* @return false|DocumentationEntity
*/
function getModule() {
if($this->Remaining && is_array($this->Remaining)) {
return DocumentationService::is_registered_module($this->Remaining[0], $this->Version, $this->Lang);
if($this->ModuleName) {
return DocumentationService::is_registered_module($this->ModuleName, $this->Version, $this->Lang);
}
return false;
}
/**
* @return DocumentationPage
*/
function getPage() {
$module = $this->getModule();
if(!$module) return false;
$absFilepath = DocumentationParser::find_page($module->getPath(), $this->Remaining);
if($absFilepath) {
$relativeFilePath = str_replace($module->getPath(), '', $absFilepath);
return new DocumentationPage(
$relativeFilePath,
$module,
$this->Lang,
$this->Version
);
} else {
return false;
}
}
/**
* Get the related pages to this module and the children to those pages
*
@ -262,7 +295,7 @@ class DocumentationViewer extends Controller {
if($pages) {
foreach($pages as $page) {
$linkParts = array($module->getModuleFolder());
$linkParts = array();
// don't include the 'index in the url
if($page->Title != "Index") $linkParts[] = $page->Filename;
@ -272,16 +305,14 @@ class DocumentationViewer extends Controller {
$page->LinkingMode = 'link';
$page->Children = false;
if(isset($this->Remaining[1])) {
if(strtolower($this->Remaining[1]) == $page->Filename) {
if(isset($this->Remaining[0])) {
if(strtolower($this->Remaining[0]) == $page->Filename) {
$page->LinkingMode = 'current';
if(is_dir($page->Path)) {
$children = DocumentationParser::get_pages_from_folder($page->Path);
$segments = array($module->getModuleFolder(), $this->Remaining[1]);
foreach($children as $child) {
$child->Link = $this->Link(array_merge($segments, array($child->Filename)));
$child->Link = $this->Link(array($this->Remaining[0], $child->Filename));
}
$page->Children = $children;
@ -296,6 +327,7 @@ class DocumentationViewer extends Controller {
return false;
}
/**
* Return the content for the page. If its an actual documentation page then
* display the content from the page, otherwise display the contents from
@ -304,12 +336,9 @@ class DocumentationViewer extends Controller {
* @return HTMLText
*/
function getContent() {
if($module = $this->getModule()) {
// name of the module. Throw it away since we already have the module path.
$filepath = $this->Remaining;
array_shift($filepath);
return DocumentationParser::parse($module->getPath(), $filepath);
if($page = $this->getPage()) {
// Remove last portion of path (filename), we want a link to the folder base
return DocumentationParser::parse($page, $this->Link(array_slice($this->Remaining, -1, -1)));
}
return false;
@ -322,20 +351,22 @@ class DocumentationViewer extends Controller {
* @return DataObjectSet
*/
function getBreadcrumbs() {
$pages = $this->Remaining;
$pages = array_merge(array($this->ModuleName), $this->Remaining);;
$output = new DataObjectSet();
$output->push(new ArrayData(array(
'Title' => ($this->Version) ? $this->Version : _t('DocumentationViewer.DOCUMENTATION', 'Documentation'),
'Link' => $this->Link()
)));
// $output->push(new ArrayData(array(
// 'Title' => ($this->Version) ? $this->Version : _t('DocumentationViewer.DOCUMENTATION', 'Documentation'),
// 'Link' => $this->Link()
// )));
if($pages) {
$path = array();
foreach($pages as $page => $title) {
foreach($pages as $i => $title) {
if($title) {
$path[] = $title;
// Don't add module name, already present in Link()
if($i > 0) $path[] = $title;
$output->push(new ArrayData(array(
'Title' => DocumentationParser::clean_page_name($title),
@ -362,6 +393,7 @@ class DocumentationViewer extends Controller {
$version = ($this->Version) ? $this->Version . '/' : false;
$lang = ($this->Lang) ? $this->Lang .'/' : false;
$module = ($this->ModuleName) ? $this->ModuleName .'/' : false;
$action = '';
if(is_string($path)) $action = $path . '/';
@ -374,7 +406,7 @@ class DocumentationViewer extends Controller {
}
}
return $base . $loc . $version . $lang . $action;
return $base . $loc . $version . $lang . $module . $action;
}
/**

View File

@ -0,0 +1,21 @@
<?php
/**
* @package sapphiredocs
*/
class DocumentationEntityTest extends SapphireTest {
function testDocumentationEntityAccessing() {
$entity = new DocumentationEntity('docs', '1.0', '../sapphiredocs/tests/docs/', 'My Test');
$this->assertEquals($entity->getTitle(), 'My Test');
$this->assertEquals($entity->getVersions(), array('1.0'));
$this->assertEquals($entity->getLanguages(), array('en', 'de'));
$this->assertEquals($entity->getModuleFolder(), 'docs');
$this->assertTrue($entity->hasVersion('1.0'));
$this->assertFalse($entity->hasVersion('2.0'));
$this->assertTrue($entity->hasLanguage('en'));
$this->assertFalse($entity->hasLanguage('fr'));
}
}

View File

@ -0,0 +1,33 @@
<?php
class DocumentationPageTest extends SapphireTest {
function testGetRelativePath() {
$page = new DocumentationPage(
'test.md',
new DocumentationEntity('mymodule', null, BASE_PATH . '/sapphiredocs/tests/docs/')
);
$this->assertEquals('test.md', $page->getRelativePath());
$page = new DocumentationPage(
'subfolder/subpage.md',
new DocumentationEntity('mymodule', null, BASE_PATH . '/sapphiredocs/tests/docs/')
);
$this->assertEquals('subfolder/subpage.md', $page->getRelativePath());
}
function testGetPath() {
$absPath = BASE_PATH . '/sapphiredocs/tests/docs/';
$page = new DocumentationPage(
'test.md',
new DocumentationEntity('mymodule', null, $absPath)
);
$this->assertEquals($absPath . 'en/test.md', $page->getPath());
$page = new DocumentationPage(
'subfolder/subpage.md',
new DocumentationEntity('mymodule', null, $absPath)
);
$this->assertEquals($absPath . 'en/subfolder/subpage.md', $page->getPath());
}
}

View File

@ -4,6 +4,109 @@
*/
class DocumentationParserTest extends SapphireTest {
function testRelativeLinks() {
// Page on toplevel
$page = new DocumentationPage(
'test.md',
new DocumentationEntity('mymodule', null, BASE_PATH . '/sapphiredocs/tests/docs/')
);
$result = DocumentationParser::rewrite_relative_links($page->getMarkdown(), $page, 'mycontroller/cms/2.4/en/');
$this->assertContains(
'[link: subfolder index](mycontroller/cms/2.4/en/subfolder/)',
$result
);
$this->assertContains(
'[link: subfolder page](mycontroller/cms/2.4/en/subfolder/subpage)',
$result
);
$this->assertContains(
'[link: http](http://silverstripe.org)',
$result
);
$this->assertContains(
'[link: api](api:DataObject)',
$result
);
// Page in subfolder
$page = new DocumentationPage(
'subfolder/subpage.md',
new DocumentationEntity('mymodule', null, BASE_PATH . '/sapphiredocs/tests/docs/')
);
$result = DocumentationParser::rewrite_relative_links($page->getMarkdown(), $page, 'mycontroller/cms/2.4/en/');
$this->assertContains(
'[link: absolute index](mycontroller/cms/2.4/en/)',
$result
);
$this->assertContains(
'[link: absolute index with name](mycontroller/cms/2.4/en/index)',
$result
);
$this->assertContains(
'[link: relative index](mycontroller/cms/2.4/en/)',
$result
);
$this->assertContains(
'[link: relative parent page](mycontroller/cms/2.4/en/test)',
$result
);
$this->assertContains(
'[link: absolute parent page](mycontroller/cms/2.4/en/test)',
$result
);
// Page in nested subfolder
$page = new DocumentationPage(
'subfolder/subsubfolder/subsubpage.md',
new DocumentationEntity('mymodule', null, BASE_PATH . '/sapphiredocs/tests/docs/')
);
$result = DocumentationParser::rewrite_relative_links($page->getMarkdown(), $page, 'mycontroller/cms/2.4/en/');
$this->assertContains(
'[link: absolute index](mycontroller/cms/2.4/en/)',
$result
);
$this->assertContains(
'[link: relative index](mycontroller/cms/2.4/en/subfolder/)',
$result
);
$this->assertContains(
'[link: relative parent page](mycontroller/cms/2.4/en/subfolder/subpage)',
$result
);
$this->assertContains(
'[link: relative grandparent page](mycontroller/cms/2.4/en/test)',
$result
);
$this->assertContains(
'[link: absolute page](mycontroller/cms/2.4/en/test)',
$result
);
}
function testCleanPageNames() {
$names = array(
'documentation-Page',
'documentation_Page',
'documentation.md',
'documentation.pdf',
'documentation.file.txt',
'.hidden'
);
$should = array(
'Documentation Page',
'Documentation Page',
'Documentation',
'Documentation',
'Documentation',
'.hidden' // don't display something without a title
);
foreach($names as $key => $value) {
$this->assertEquals(DocumentationParser::clean_page_name($value), $should[$key]);
}
}
function testGetPagesFromFolder() {
$pages = DocumentationParser::get_pages_from_folder(BASE_PATH . '/sapphiredocs/tests/docs/en/');
$this->assertContains('index', $pages->column('Filename'), 'Index');

View File

@ -11,42 +11,131 @@ class DocumentationViewerTests extends FunctionalTest {
static $fixture_file = 'sapphiredocs/tests/DocumentTests.yml';
function testCleanPageNames() {
$names = array(
'documentation-Page',
'documentation_Page',
'documentation.md',
'documentation.pdf',
'documentation.file.txt',
'.hidden'
);
function setUpOnce() {
parent::setUpOnce();
$should = array(
'Documentation Page',
'Documentation Page',
'Documentation',
'Documentation',
'Documentation',
'.hidden' // don't display something without a title
);
foreach($names as $key => $value) {
$this->assertEquals(DocumentationParser::clean_page_name($value), $should[$key]);
$this->origEnabled = DocumentationService::automatic_registration_enabled();
DocumentationService::set_automatic_registration(false);
$this->origModules = DocumentationService::get_registered_modules();
foreach($this->origModules as $module) {
DocumentationService::unregister($module->getModuleFolder());
}
DocumentationService::register("DocumentationViewerTests", BASE_PATH . "/sapphiredocs/tests/docs/", '2.4');
}
function testDocumentationEntityAccessing() {
$entity = new DocumentationEntity('docs', '1.0', '../sapphiredocs/tests/docs/', 'My Test');
function tearDownOnce() {
parent::tearDownOnce();
$this->assertEquals($entity->getTitle(), 'My Test');
$this->assertEquals($entity->getVersions(), array('1.0'));
$this->assertEquals($entity->getLanguages(), array('en', 'de'));
$this->assertEquals($entity->getModuleFolder(), 'docs');
$this->assertTrue($entity->hasVersion('1.0'));
$this->assertFalse($entity->hasVersion('2.0'));
$this->assertTrue($entity->hasLanguage('en'));
$this->assertFalse($entity->hasLanguage('fr'));
DocumentationService::unregister("DocumentationViewerTests");
DocumentationService::set_automatic_registration($this->origEnabled);
// $this->origModules = Documentation::get_registered_modules();
// foreach($this->origModules as $name => $module) {
// DocumentationService::register($name);
// }
}
function testUrlParsing() {
// Module index
$v = new DocumentationViewer();
$response = $v->handleRequest(new SS_HTTPRequest('GET', '2.4/en/DocumentationViewerTests/test'));
$this->assertEquals('2.4', $v->Version);
$this->assertEquals('en', $v->Lang);
$this->assertEquals('DocumentationViewerTests', $v->ModuleName);
$this->assertEquals(array('test'), $v->Remaining);
// Module index without version and language
$v = new DocumentationViewer();
$response = $v->handleRequest(new SS_HTTPRequest('GET', 'en/DocumentationViewerTests/test'));
$this->assertEquals(null, $v->Version);
$this->assertEquals('en', $v->Lang);
$this->assertEquals('DocumentationViewerTests', $v->ModuleName);
$this->assertEquals(array('test'), $v->Remaining);
// Overall index
// $v = new DocumentationViewer();
// $response = $v->handleRequest(new SS_HTTPRequest('GET', ''));
// $this->assertEquals(null, $v->Version);
// $this->assertEquals(null, $v->Lang);
// $this->assertEquals(null, $v->ModuleName);
// $this->assertEquals(array(), $v->Remaining);
}
function testBreadcrumbs() {
// Module index
$v = new DocumentationViewer();
$response = $v->handleRequest(new SS_HTTPRequest('GET', '2.4/en/DocumentationViewerTests/'));
$crumbs = $v->getBreadcrumbs();
$this->assertEquals(1, $crumbs->Count());
$crumbLinks = $crumbs->column('Link');
$this->assertStringEndsWith('DocumentationViewerTests/', $crumbLinks[0]);
// Subfolder index
$v = new DocumentationViewer();
$response = $v->handleRequest(new SS_HTTPRequest('GET', '2.4/en/DocumentationViewerTests/subfolder/'));
$crumbs = $v->getBreadcrumbs();
$this->assertEquals(2, $crumbs->Count());
$crumbLinks = $crumbs->column('Link');
$this->assertStringEndsWith('DocumentationViewerTests/', $crumbLinks[0]);
$this->assertStringEndsWith('DocumentationViewerTests/subfolder/', $crumbLinks[1]);
// Subfolder page
$v = new DocumentationViewer();
$response = $v->handleRequest(new SS_HTTPRequest('GET', '2.4/en/DocumentationViewerTests/subfolder/subpage'));
$crumbs = $v->getBreadcrumbs();
$this->assertEquals(3, $crumbs->Count());
$crumbLinks = $crumbs->column('Link');
$this->assertStringEndsWith('DocumentationViewerTests/', $crumbLinks[0]);
$this->assertStringEndsWith('DocumentationViewerTests/subfolder/', $crumbLinks[1]);
$this->assertStringEndsWith('DocumentationViewerTests/subfolder/subpage/', $crumbLinks[2]);
}
function testGetModulePages() {
$v = new DocumentationViewer();
$response = $v->handleRequest(new SS_HTTPRequest('GET', '2.4/en/DocumentationViewerTests/subfolder/'));
$pages = $v->getModulePages();
$this->assertEquals(
array('index', 'subfolder', 'test'),
$pages->column('Filename')
);
$this->assertEquals(
array('link', 'current', 'link'),
$pages->column('LinkingMode')
);
$links = $pages->column('Link');
$this->assertStringEndsWith('2.4/en/DocumentationViewerTests/', $links[0]);
$this->assertStringEndsWith('2.4/en/DocumentationViewerTests/subfolder/', $links[1]);
$this->assertStringEndsWith('2.4/en/DocumentationViewerTests/test/', $links[2]);
// Children
$pagesArr = $pages->toArray();
$child1 = $pagesArr[0];
$this->assertFalse($child1->Children);
$child2 = $pagesArr[1];
$this->assertType('DataObjectSet', $child2->Children);
$this->assertEquals(
array('subpage', 'subsubfolder'),
$child2->Children->column('Filename')
);
$child2Links = $child2->Children->column('Link');
$this->assertStringEndsWith('2.4/en/DocumentationViewerTests/subfolder/subpage/', $child2Links[0]);
$this->assertStringEndsWith('2.4/en/DocumentationViewerTests/subfolder/subsubfolder/', $child2Links[1]);
}
function testRouting() {
$response = $this->get('dev/docs/2.4/en/DocumentationViewerTests/test');
$this->assertEquals(200, $response->getStatusCode());
$this->assertContains('english test', $response->getBody(), 'Toplevel content page');
}
// function testGetPage() {
// $v = new DocumentationViewer();
// $v->handleRequest(new SS_HTTPRequest('GET', '2.4/en/cms'));
// $p = $v->getPage();
// $this->assertType('DocumentationPage', $p);
// $this->assertEquals('/', $p->getRelativePath());
// $this->assertEquals('en', $p->getLang());
// $this->assertEquals('2.4', $p->getVersion());
// }
}

View File

@ -0,0 +1,5 @@
[link: absolute index](/)
[link: absolute index with name](/index)
[link: relative index](../)
[link: relative parent page](../test)
[link: absolute parent page](/test)

View File

@ -0,0 +1,5 @@
[link: absolute index](/)
[link: relative index](../)
[link: relative parent page](../subpage)
[link: relative grandparent page](../../test)
[link: absolute page](/test)

View File

@ -3,3 +3,9 @@
test
1.0
[link: subfolder index](subfolder/)
[link: subfolder page](subfolder/subpage)
[link: with anchor](/test#anchor)
[link: http](http://silverstripe.org)
[link: api](api:DataObject)