APICHANGE: rather than passing the structure through the constructor, do so via setters which will allow us more flexibility

This commit is contained in:
Will Rossiter 2010-10-21 20:27:23 +00:00
parent ac5ce3872e
commit d2eb92233e
6 changed files with 217 additions and 102 deletions

View File

@ -1,4 +1,5 @@
<?php <?php
/** /**
* A specific page within a {@link DocumentationEntity}. * A specific page within a {@link DocumentationEntity}.
* Has to represent an actual file, please use {@link DocumentationViewer} * Has to represent an actual file, please use {@link DocumentationViewer}
@ -18,27 +19,16 @@ class DocumentationPage extends ViewableData {
*/ */
protected $relativePath; protected $relativePath;
/**
* @var String
*/
protected $lang = 'en'; protected $lang = 'en';
/**
* @var String
*/
protected $version; 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. Module path: %s, file path: %s',
$this->entity->getPath(),
$this->relativePath
));
}
parent::__construct();
}
/** /**
* @return DocumentationEntity * @return DocumentationEntity
*/ */
@ -46,6 +36,10 @@ class DocumentationPage extends ViewableData {
return $this->entity; return $this->entity;
} }
function setEntity($entity) {
$this->entity = $entity;
}
/** /**
* @return String Relative path to file or folder within the entity (including file extension), * @return String Relative path to file or folder within the entity (including file extension),
* but excluding version or language folders. * but excluding version or language folders.
@ -54,24 +48,45 @@ class DocumentationPage extends ViewableData {
return $this->relativePath; return $this->relativePath;
} }
function setRelativePath($path) {
$this->relativePath = $path;
}
/** /**
* Absolute path including version and lang folder. * Absolute path including version and lang folder.
* *
* @return String * @return String
*/ */
function getPath() { function getPath() {
$path = rtrim($this->entity->getPath($this->version, $this->lang), '/') . '/' . $this->getRelativePath(); $path = realpath(rtrim($this->entity->getPath($this->version, $this->lang), '/') . '/' . $this->getRelativePath());
return realpath($path);
if(!file_exists($path)) {
throw new InvalidArgumentException(sprintf(
'Path could not be found. Module path: %s, file path: %s',
$this->entity->getPath(),
$this->relativePath
));
}
return $path;
} }
function getLang() { function getLang() {
return $this->lang; return $this->lang;
} }
function setLang($lang) {
$this->lang = $lang;
}
function getVersion() { function getVersion() {
return $this->version; return $this->version;
} }
function setVersion($version) {
$this->version = $version;
}
/** /**
* @return String * @return String
*/ */
@ -86,5 +101,4 @@ class DocumentationPage extends ViewableData {
function getHTML($baselink = null) { function getHTML($baselink = null) {
return DocumentationParser::parse($this, $baselink); return DocumentationParser::parse($this, $baselink);
} }
} }

View File

@ -391,6 +391,7 @@ class DocumentationParser {
* @param bool Recursive search * @param bool Recursive search
* @param DataObjectSet set of pages matched so far * @param DataObjectSet set of pages matched so far
* *
* @throws Exception
* @return DataObjectSet * @return DataObjectSet
*/ */
public static function get_pages_from_folder($folder, $recursive = false, &$pages = false) { public static function get_pages_from_folder($folder, $recursive = false, &$pages = false) {
@ -398,6 +399,8 @@ class DocumentationParser {
if(!$pages) $pages = new DataObjectSet(); if(!$pages) $pages = new DataObjectSet();
if(!is_dir($folder)) throw new Exception(sprintf('%s is not a folder', $folder));
$handle = opendir($folder); $handle = opendir($folder);
if($handle) { if($handle) {

View File

@ -4,31 +4,45 @@
* @todo caching? * @todo caching?
*/ */
class DocumentationSearch extends Controller { class DocumentationSearch extends DocumentationViewer {
static $casting = array(
'Query' => 'Text'
);
static $allowed_actions = array('xml', 'search');
/**
* @var array Cached search results
*/
private $searchCache = array();
/**
* @var Int Page Length
*/
private $pageLength = 10;
/** /**
* Generates the XML tree for {@link Sphinx} XML Pipes * Generates the XML tree for {@link Sphinx} XML Pipes
* *
* @uses DomDocument * @uses DomDocument
*/ */
function sphinxxml() { function xml() {
DocumentationService::load_automatic_registration(); DocumentationService::load_automatic_registration();
// generate the head of the document
$dom = new DomDocument('1.0'); $dom = new DomDocument('1.0');
$dom->encoding = "utf-8"; $dom->encoding = "utf-8";
$dom->formatOutput = true; $dom->formatOutput = true;
$root = $dom->appendChild($dom->createElement('sphinx:docset')); $root = $dom->appendChild($dom->createElementNS('http://sphinxsearch.com', 'sphinx:docset'));
$schema = $dom->createElement('sphinx:schema'); $schema = $dom->createElement('sphinx:schema');
$field = $dom->createElement('sphinx:field'); $field = $dom->createElement('sphinx:field');
$attr = $dom->createElement('sphinx:attr'); $attr = $dom->createElement('sphinx:attr');
foreach(array('Title','Content', 'Language', 'Version', 'Module') as $field) { foreach(array('Title','Content', 'Language', 'Module', 'Path') as $field) {
$node = $dom->createElement('sphinx:field'); $node = $dom->createElement('sphinx:field');
$node->setAttribute('name', $field); $node->setAttribute('name', strtolower($field));
$schema->appendChild($node); $schema->appendChild($node);
} }
@ -42,10 +56,11 @@ class DocumentationSearch extends Controller {
foreach($pages as $doc) { foreach($pages as $doc) {
$node = $dom->createElement('sphinx:document'); $node = $dom->createElement('sphinx:document');
$node->setAttribute('ID', $doc->ID); $node->setAttribute('id', $doc->ID);
foreach($doc->getArray() as $key => $value) { foreach($doc->getArray() as $key => $value) {
if($key == 'ID') continue; $key = strtolower($key);
if($key == 'id') continue;
$tmp = $dom->createElement($key); $tmp = $dom->createElement($key);
$tmp->appendChild($dom->createTextNode($value)); $tmp->appendChild($dom->createTextNode($value));
@ -75,21 +90,60 @@ class DocumentationSearch extends Controller {
if($modules) { if($modules) {
foreach($modules as $module) { foreach($modules as $module) {
foreach($module->getLanguages() as $language) { foreach($module->getLanguages() as $language) {
$pages = DocumentationParser::get_pages_from_folder($module->getPath(false, $language)); try {
$pages = DocumentationParser::get_pages_from_folder($module->getPath(false, $language));
if($pages) { if($pages) {
foreach($pages as $page) { foreach($pages as $page) {
$output->push(new ArrayData(array( $output->push(new ArrayData(array(
'Title' => $page->Title, 'Title' => $page->Title,
'Content' => file_get_contents($page->Path), 'Content' => file_get_contents($page->Path),
'ID' => base_convert(substr(md5($page->Path), -8), 16, 10) 'Path' => $page->Path,
))); 'Language' => $language,
'ID' => base_convert(substr(md5($page->Path), -8), 16, 10)
)));
}
} }
} }
catch(Exception $e) {}
} }
} }
} }
return $output; return $output;
} }
/**
* Takes a search from the URL, performs a sphinx search and displays a search results
* template.
*
* @todo Add additional language / version filtering
*/
function search() {
$query = (isset($this->urlParams['ID'])) ? $this->urlParams['ID'] : false;
$results = false;
$keywords = "";
if($query) {
$keywords = urldecode($query);
$start = isset($_GET['start']) ? (int)$_GET['start'] : 0;
$cachekey = $query.':'.$start;
if(!isset($this->searchCache[$cachekey])) {
$this->searchCache[$cachekey] = SphinxSearch::search('DocumentationPage', $keywords, array_merge_recursive(array(
'start' => $start,
'pagesize' => $this->pageLength
)));
}
$results = $this->searchCache[$cachekey];
}
return array(
'Query' => DBField::create('Text', $keywords),
'Results' => $results
);
}
} }

View File

@ -27,6 +27,7 @@ class DocumentationViewer extends Controller {
'LanguageForm', 'LanguageForm',
'doLanguageForm', 'doLanguageForm',
'handleRequest', 'handleRequest',
'DocumentationSearchForm'
); );
static $casting = array( static $casting = array(
@ -85,7 +86,10 @@ class DocumentationViewer extends Controller {
* @return SS_HTTPResponse * @return SS_HTTPResponse
*/ */
public function handleRequest(SS_HTTPRequest $request) { public function handleRequest(SS_HTTPRequest $request) {
// Workaround for root routing, e.g. Director::addRules(10, array('$Action' => 'DocumentationViewer'))
// if we submitted a form, let that pass
if($request->httpMethod() != "GET") return parent::handleRequest($request);
$this->Version = ($request->param('Action')) ? $request->param('Action') : $request->shift(); $this->Version = ($request->param('Action')) ? $request->param('Action') : $request->shift();
$this->Lang = $request->shift(); $this->Lang = $request->shift();
$this->ModuleName = $request->shift(); $this->ModuleName = $request->shift();
@ -140,10 +144,8 @@ class DocumentationViewer extends Controller {
return $this->response; return $this->response;
} }
} }
} }
return parent::handleRequest($request); return parent::handleRequest($request);
} }
@ -271,12 +273,12 @@ class DocumentationViewer extends Controller {
$absFilepath = $module->getPath() . '/index.md'; $absFilepath = $module->getPath() . '/index.md';
$relativeFilePath = str_replace($module->getPath(), '', $absFilepath); $relativeFilePath = str_replace($module->getPath(), '', $absFilepath);
if(file_exists($absFilepath)) { if(file_exists($absFilepath)) {
$page = new DocumentationPage( $page = new DocumentationPage();
$relativeFilePath, $page->setRelativePath($relativeFilePath);
$module, $page->setEntity($module);
$this->Lang, $page->setLang($this->Lang);
$this->Version $page->setVersion($this->Version);
);
$content = DocumentationParser::parse($page, $this->Link(array_slice($this->Remaining, -1, -1))); $content = DocumentationParser::parse($page, $this->Link(array_slice($this->Remaining, -1, -1)));
} else { } else {
$content = ''; $content = '';
@ -317,12 +319,14 @@ class DocumentationViewer extends Controller {
$absFilepath = DocumentationParser::find_page($module->getPath(), $this->Remaining); $absFilepath = DocumentationParser::find_page($module->getPath(), $this->Remaining);
if($absFilepath) { if($absFilepath) {
$relativeFilePath = str_replace($module->getPath(), '', $absFilepath); $relativeFilePath = str_replace($module->getPath(), '', $absFilepath);
return new DocumentationPage(
$relativeFilePath, $page = new DocumentationPage();
$module, $page->setRelativePath($relativeFilePath);
$this->Lang, $page->setEntity($module);
$this->Version $page->setLang($this->Lang);
); $page->setVersion($this->Version);
return $page;
} else { } else {
return false; return false;
} }
@ -398,7 +402,9 @@ class DocumentationViewer extends Controller {
* @return DataObjectSet * @return DataObjectSet
*/ */
function getBreadcrumbs() { function getBreadcrumbs() {
$pages = array_merge(array($this->ModuleName), $this->Remaining);; if(!$this->Remaining) $this->Remaining = array();
$pages = array_merge(array($this->ModuleName), $this->Remaining);
$output = new DataObjectSet(); $output = new DataObjectSet();
@ -509,4 +515,39 @@ class DocumentationViewer extends Controller {
static function get_link_base() { static function get_link_base() {
return self::$link_base; return self::$link_base;
} }
/**
* @see {@link Form::FormObjectLink()}
*/
function FormObjectLink($name) {
return $name;
}
/**
* Documentation Basic Search Form
*
* Integrates with sphinx
* @return Form
*/
function DocumentationSearchForm() {
$fields = new FieldSet(
new TextField('Search')
);
$actions = new FieldSet(
new FormAction('doDocumentationSearchForm', 'Search')
);
return new Form($this, 'DocumentationSearchForm', $fields, $actions);
}
/**
* Past straight to results, display and encode the query
*/
function doDocumentationSearchForm($data, $form) {
$query = (isset($data['Search'])) ? urlencode($data['Search']) : "";
$this->redirect('DocumentationSearch/search/'. $query);
}
} }

View File

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

View File

@ -24,12 +24,12 @@ class DocumentationParserTest extends SapphireTest {
function testImageRewrites() { function testImageRewrites() {
// Page on toplevel // Page on toplevel
$page = new DocumentationPage( $page = new DocumentationPage();
'subfolder/subpage.md', $page->setRelativePath('subfolder/subpage.md');
new DocumentationEntity('mymodule', '2.4', BASE_PATH . '/sapphiredocs/tests/docs/'), $page->setEntity(new DocumentationEntity('mymodule', '2.4', BASE_PATH . '/sapphiredocs/tests/docs/'));
'en', $page->setLang('en');
'2.4' $page->setVersion('2.4');
);
$result = DocumentationParser::rewrite_image_links($page->getMarkdown(), $page, 'mycontroller/cms/2.4/en/'); $result = DocumentationParser::rewrite_image_links($page->getMarkdown(), $page, 'mycontroller/cms/2.4/en/');
$this->assertContains( $this->assertContains(
'[relative image link](' . Director::absoluteBaseURL() . '/sapphiredocs/tests/docs/en/subfolder/_images/image.png)', '[relative image link](' . Director::absoluteBaseURL() . '/sapphiredocs/tests/docs/en/subfolder/_images/image.png)',
@ -48,12 +48,13 @@ class DocumentationParserTest extends SapphireTest {
function testApiLinks() { function testApiLinks() {
// Page on toplevel // Page on toplevel
$page = new DocumentationPage( $page = new DocumentationPage();
'test.md', $page->setRelativePath('test.md');
new DocumentationEntity('mymodule', '2.4', BASE_PATH . '/sapphiredocs/tests/docs/'), $page->setEntity(new DocumentationEntity('mymodule', '2.4', BASE_PATH . '/sapphiredocs/tests/docs/'));
'en', $page->setLang('en');
'2.4' $page->setVersion('2.4');
);
$result = DocumentationParser::rewrite_api_links($page->getMarkdown(), $page, 'mycontroller/cms/2.4/en/'); $result = DocumentationParser::rewrite_api_links($page->getMarkdown(), $page, 'mycontroller/cms/2.4/en/');
$this->assertContains( $this->assertContains(
'[link: api](http://api.silverstripe.org/search/lookup/?q=DataObject&version=2.4&module=mymodule)', '[link: api](http://api.silverstripe.org/search/lookup/?q=DataObject&version=2.4&module=mymodule)',
@ -65,12 +66,11 @@ class DocumentationParserTest extends SapphireTest {
} }
function testHeadlineAnchors() { function testHeadlineAnchors() {
$page = new DocumentationPage( $page = new DocumentationPage();
'test.md', $page->setRelativePath('test.md');
new DocumentationEntity('mymodule', '2.4', BASE_PATH . '/sapphiredocs/tests/docs/'), $page->setEntity(new DocumentationEntity('mymodule', '2.4', BASE_PATH . '/sapphiredocs/tests/docs/'));
'en', $page->setLang('en');
'2.4' $page->setVersion('2.4');
);
$result = DocumentationParser::rewrite_heading_anchors($page->getMarkdown(), $page); $result = DocumentationParser::rewrite_heading_anchors($page->getMarkdown(), $page);
@ -104,11 +104,12 @@ class DocumentationParserTest extends SapphireTest {
function testRelativeLinks() { function testRelativeLinks() {
// Page on toplevel // Page on toplevel
$page = new DocumentationPage( $page = new DocumentationPage();
'test.md', $page->setRelativePath('test.md');
new DocumentationEntity('mymodule', null, BASE_PATH . '/sapphiredocs/tests/docs/') $page->setEntity(new DocumentationEntity('mymodule', '2.4', BASE_PATH . '/sapphiredocs/tests/docs/'));
);
$result = DocumentationParser::rewrite_relative_links($page->getMarkdown(), $page, 'mycontroller/cms/2.4/en/'); $result = DocumentationParser::rewrite_relative_links($page->getMarkdown(), $page, 'mycontroller/cms/2.4/en/');
$this->assertContains( $this->assertContains(
'[link: subfolder index](mycontroller/cms/2.4/en/subfolder/)', '[link: subfolder index](mycontroller/cms/2.4/en/subfolder/)',
$result $result
@ -127,11 +128,12 @@ class DocumentationParserTest extends SapphireTest {
); );
// Page in subfolder // Page in subfolder
$page = new DocumentationPage( $page = new DocumentationPage();
'subfolder/subpage.md', $page->setRelativePath('subfolder/subpage.md');
new DocumentationEntity('mymodule', null, BASE_PATH . '/sapphiredocs/tests/docs/') $page->setEntity(new DocumentationEntity('mymodule', '2.4', BASE_PATH . '/sapphiredocs/tests/docs/'));
);
$result = DocumentationParser::rewrite_relative_links($page->getMarkdown(), $page, 'mycontroller/cms/2.4/en/'); $result = DocumentationParser::rewrite_relative_links($page->getMarkdown(), $page, 'mycontroller/cms/2.4/en/');
$this->assertContains( $this->assertContains(
'[link: absolute index](mycontroller/cms/2.4/en/)', '[link: absolute index](mycontroller/cms/2.4/en/)',
$result $result
@ -154,11 +156,12 @@ class DocumentationParserTest extends SapphireTest {
); );
// Page in nested subfolder // Page in nested subfolder
$page = new DocumentationPage( $page = new DocumentationPage();
'subfolder/subsubfolder/subsubpage.md', $page->setRelativePath('subfolder/subsubfolder/subsubpage.md');
new DocumentationEntity('mymodule', null, BASE_PATH . '/sapphiredocs/tests/docs/') $page->setEntity(new DocumentationEntity('mymodule', '2.4', BASE_PATH . '/sapphiredocs/tests/docs/'));
);
$result = DocumentationParser::rewrite_relative_links($page->getMarkdown(), $page, 'mycontroller/cms/2.4/en/'); $result = DocumentationParser::rewrite_relative_links($page->getMarkdown(), $page, 'mycontroller/cms/2.4/en/');
$this->assertContains( $this->assertContains(
'[link: absolute index](mycontroller/cms/2.4/en/)', '[link: absolute index](mycontroller/cms/2.4/en/)',
$result $result