Details
I'm working on a custom application extension which integrates a custom Remarkup rule (PhabricatorObjectRemarkupRule).
As this token has some parameters, I wanted to document these in the Remarkup syntax help page (located at /book/phorge/article/remarkup).
My idea was to implement a new interface:
<?php interface RemarkupSyntaxDocumentationProvider { public function getDocumentation(); }
which can then be used like this:
<?php final class DummyApplication extends PhabricatorApplication { public function getName() { return pht('Dummy'); } public function getRemarkupRules() { return array( new DummyRemarkup(), ); } public function getRoutes() { return []; } } final class DummyObject { private $id; public function __construct($id) { $this->id = $id; } public function getID() { return $this->id; } public function getPHID() { return 'PHID-DUMM-0000000000000' . $this->id; } } final class DummyRemarkup extends PhabricatorObjectRemarkupRule implements RemarkupSyntaxDocumentationProvider { protected function getObjectNamePrefix() { return "DUMMY"; } protected function loadHandles(array $objects) { $phids = mpull($objects, 'getPHID'); $result = array(); foreach ($objects as $id => $object) { $handle = new PhabricatorObjectHandle(); $handle->setPHID($object->getPHID()); $result[$id] = $handle; } return $result; } protected function loadObjects(array $ids) { return [ new DummyObject(0), new DummyObject(1), new DummyObject(2), new DummyObject(3), new DummyObject(4), new DummyObject(5) ]; } public function getDocumentation() { return '= Dummy remarkup explanation = Here you find some help about the DUMMY token... '; } protected function renderObjectEmbed( $dummyObject, PhabricatorObjectHandle $handle, $options) { return phutil_safe_html('<strong>dummy ' . $dummyObject->getId() . '</strong>'); } }
The getDocumentation function returns the help text in Remarkup format that should be appended at the bottom of the Remarkup syntax help page.
The table of content of this page will also be updated.
Currently, the Remarkup syntax help page points to the https://we.phorge.it page, even if you have the diviner pages locally installed.
This means of course that you cannot change the content of this page.
I added some extra code in PhabricatorEnv which checks if the diviner pages are locally installed. If yes, then the local pages will be used instead of the ones from https://we.phorge.it. Because this check requires a database connection and thus a $viewer, I had to add a static $viewer property to PhabricatorEnv (as all its functions are static functions). I'm not sure if this is completely kosher.
These are my changes:
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9287085d03..8bf1369821 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5878,6 +5878,7 @@ phutil_register_library_map(array( 'QueryFormattingTestCase' => 'infrastructure/storage/__tests__/QueryFormattingTestCase.php', 'QueryFuture' => 'infrastructure/storage/future/QueryFuture.php', 'RemarkupProcessConduitAPIMethod' => 'applications/remarkup/conduit/RemarkupProcessConduitAPIMethod.php', + 'RemarkupSyntaxDocumentationProvider' => 'applications/diviner/interface/RemarkupSyntaxDocumentationProvider.php', 'RemarkupValue' => 'applications/remarkup/RemarkupValue.php', 'RepositoryConduitAPIMethod' => 'applications/repository/conduit/RepositoryConduitAPIMethod.php', 'RepositoryQueryConduitAPIMethod' => 'applications/repository/conduit/RepositoryQueryConduitAPIMethod.php', diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php index 3198bb9fd4..70511dbdbc 100644 --- a/src/aphront/configuration/AphrontApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontApplicationConfiguration.php @@ -270,6 +270,9 @@ final class AphrontApplicationConfiguration try { $response = $controller->willBeginExecution(); + // store viewer in static variable, so it can be used in PhabricatorEnv's static functions + PhabricatorEnv::setViewer($request->getViewer()); + if ($request->getUser() && $request->getUser()->getPHID()) { $access_log->setData( array( diff --git a/src/applications/diviner/controller/DivinerAtomController.php b/src/applications/diviner/controller/DivinerAtomController.php index cf29389da9..5bc320a2dd 100644 --- a/src/applications/diviner/controller/DivinerAtomController.php +++ b/src/applications/diviner/controller/DivinerAtomController.php @@ -106,8 +106,24 @@ final class DivinerAtomController extends DivinerController { } $engine->process(); + $extraContent = ''; + $extraTOC = []; + + // if Remarkup Syntax Help page is shown, search for RemarkupSyntaxDocumentationProviders in extensions + if ($book_name == 'phorge' && $atom_type == 'article' && $atom_name == 'remarkup') { + $remarkupSyntaxDocumentationProviders = id(new PhutilClassMapQuery()) + ->setAncestorClass('RemarkupSyntaxDocumentationProvider') + ->execute(); + + // add custom Remarkup help + foreach ($remarkupSyntaxDocumentationProviders as $remarkupSyntaxDocumentationProvider) { + $extraContent .= $remarkupSyntaxDocumentationProvider->getDocumentation(). "\r\n\r\n"; + } + } + if ($atom) { - $content = $this->renderDocumentationText($symbol, $engine); + // convert Remarkup to HTML and parse Table of Content items out + $content = $this->renderDocumentationText($symbol, $engine, $viewer, $extraContent, $extraTOC); $document->appendChild($content); } @@ -117,6 +133,9 @@ final class DivinerAtomController extends DivinerController { PhutilRemarkupHeaderBlockRule::KEY_HEADER_TOC, array()); + // merge default and custom table of contents + $toc = array_merge($toc, $extraTOC); + if (!$atom) { $document->appendChild( id(new PHUIInfoView()) @@ -617,11 +636,44 @@ final class DivinerAtomController extends DivinerController { private function renderDocumentationText( DivinerLiveSymbol $symbol, - PhabricatorMarkupEngine $engine) { + PhabricatorMarkupEngine $engine, + $viewer, + $extraContent, + array &$extraTOCItems) { $field = 'default'; $content = $engine->getOutput($symbol, $field); + if ($extraContent != '') { + $remarkupEngine = PhabricatorMarkupEngine::newMarkupEngine(array()) + ->setConfig('header.generate-toc', true) + ->setConfig('viewer', $viewer) + ->setMode(PhutilRemarkupEngine::MODE_HTML_MAIL); + + try { + $extraContent = $remarkupEngine->markupText($extraContent); + + $dom = new DOMDocument(); + @$dom->loadHTML($extraContent); + $xpath = new DOMXPath($dom); + $headers = $xpath->query('//h1 | //h2 | //h3 | //h4 | //h5 | //h6'); + + foreach ($headers as $header) { + $key = $header->getElementsByTagName('a')->item(0)->getAttribute('name'); + $headerDepth = (int)(substr($header->tagName, 1,1)); + $textContent = trim($header->nodeValue); + + $extraTOCItems[$key] = array( + $headerDepth, + new PhutilSafeHTML($textContent) + ); + } + } catch (Exception $ex) { + } + } + + $content->appendHTML($extraContent); + if (strlen(trim($symbol->getMarkupText($field)))) { $content = phutil_tag( 'div', diff --git a/src/infrastructure/env/PhabricatorEnv.php b/src/infrastructure/env/PhabricatorEnv.php index a471874e43..18a52ac756 100644 --- a/src/infrastructure/env/PhabricatorEnv.php +++ b/src/infrastructure/env/PhabricatorEnv.php @@ -58,6 +58,7 @@ final class PhabricatorEnv extends Phobject { private static $localeCode; private static $readOnly; private static $readOnlyReason; + private static $viewer; const READONLY_CONFIG = 'config'; const READONLY_UNREACHABLE = 'unreachable'; @@ -494,9 +495,26 @@ final class PhabricatorEnv extends Phobject { 'jump' => true, ); - $uri = new PhutilURI( - 'https://we.phorge.it/diviner/find/', - $params); + $useRemoteDocumentation = true; + if (PhabricatorEnv::$viewer) { + $books = id(new DivinerBookQuery()) + ->setViewer(PhabricatorEnv::$viewer) + ->execute(); + + if (!!$books) { + $useRemoteDocumentation = false; + } + } + + if ($useRemoteDocumentation) { + $uri = new PhutilURI( + 'https://we.phorge.it/diviner/find/', + $params); + } else { + $uri = new PhutilURI( + '/diviner/find/', + $params); + } return phutil_string_cast($uri); } @@ -986,5 +1004,8 @@ final class PhabricatorEnv extends Phobject { return $root.'/support/empty/'; } - + public static function setViewer($viewer) + { + PhabricatorEnv::$viewer = $viewer; + } }
Do you think this is something that can be integrated in Phorge?
Or is this something that will hardly be used.