/** * Parse the content of the TOC management page. * * It should be loaded and stored and this sort of breaks the design * in the way that it returns the template ready array of data which PonyDocsWiki is really supposed to be returning, * but I do not see the point or use of an intermediate format other than to bloat the code. * * It returns an array of arrays, which can be stored as a single array or separated using the list() = loadContnet() syntax. * * toc: This is the actual TOC as a list of arrays, * each array having a set of keys available to specify the TOC level, text, href, etc. * prev: Assoc array containing the 'previous' link data (text, href), or empty if there isn't one. * next: Assoc array containing the 'next' link data or empty if there isn't one. * start: Assoc array containing the data for the FIRST topic in the TOC. * * These can be captured in a variable when calling and individually accessed or captured using the list() construct * i.e.: list( $toc, $prev, $next, $start ) = $toc->loadContent(). * * @FIXME: Store results internally and then have a $reload flag as param. * $content = $toc- * * @return array */ public function loadContent() { global $wgArticlePath; global $wgTitle; global $wgScriptPath; global $wgPonyDocs; global $title; /** * From this we have the page ID of the TOC page to use -- fetch it then parse it so we can produce an output TOC array. * * This array will contain one array per item with the following keys: * - 'level': 0= Arbitary Section Name, 1= Actual topic link. * - 'link': Link (wiki path) to item; may be unset for section headers (or set to first section H1)? * - 'text': Text to show in sidebar TOC. * - 'current': 1 if this is the currently selected topic, 0 otherwise. * * We also have to store the index of the current section in our loop. * * The reason for this is so that we can remove any sections which have no defined/valid topics listed. * * This will also assist in our prev/next links which are stored in special indices. */ // Our title is our url. // We should check to see if latest is our version. // If so, we want to FORCE the URL to include /latest/ as the version instead of the version that the user is currently in $tempParts = explode("/", $title); $latest = FALSE; if (isset($tempParts[1]) && !strcmp($tempParts[1], "latest")) { $latest = TRUE; } $selectedProduct = $this->pProduct->getShortName(); $selectedVersion = $this->pInitialVersion->getVersionName(); $selectedManual = $this->pManual->getShortName(); // Okay, let's determine if the VERSION that the user is in is latest, if so, we should set latest to true. if (PonyDocsProductVersion::GetLatestReleasedVersion($selectedProduct) != NULL) { if ($selectedVersion == PonyDocsProductVersion::GetLatestReleasedVersion($selectedProduct)->getVersionName()) { $latest = TRUE; } } $cache = PonyDocsCache::getInstance(); $key = "TOCCACHE-" . $selectedProduct . "-" . $selectedManual . "-" . $selectedVersion; $toc = $cache->get($key); // Cache did not exist, let's load our content is build up our cache entry. if ($toc === NULL && is_object($this->pTOCArticle) && is_a($this->pTOCArticle, 'Article')) { // The current index of the element in $toc we will work on $idx = 0; $section = -1; $content = $this->pTOCArticle->getContent(); $lines = explode("\n", $content); foreach ($lines as $line) { /** * Indicates an arbitrary section header if it does not begin with a bullet point. * This is level 0 in our TOC and is not a link of any type (?). */ if (!isset($line[0]) || $line[0] != '*') { /** * See if we are CLOSING a section (i.e. $section != -1). If so, check 'subs' and ensure its >0, * otherwise we need to remove the section from the list. */ if ($section != -1 && !$toc[$section]['subs']) { unset($toc[$section]); } if (isset($line[0]) && ctype_alnum($line[0])) { $toc[$idx] = array('level' => 0, 'subs' => 0, 'link' => '', 'text' => $line, 'current' => FALSE); $section = $idx; } /** * This is a bullet point and thus an actual topic which can be linked to in MediaWiki. * {{#topic:H1 Of Topic Page}} */ } else { if (-1 == $section) { continue; } $topicRegex = '/' . PonyDocsTopic::getTopicRegex() . '/i'; if (!preg_match($topicRegex, $line, $matches)) { continue; } $baseTopic = $matches[1]; $title_suffix = preg_replace('/([^' . str_replace(' ', '', Title::legalChars()) . '])/', '', $baseTopic); $title = PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . ":{$selectedProduct}:{$selectedManual}:{$title_suffix}"; $newTitle = PonyDocsTopic::GetTopicNameFromBaseAndVersion($title, $selectedProduct); /** * Hide topics which have no content (i.e. have not been created yet) from the user viewing. * * Authors must go to the TOC page in order to view and edit these. * * The only way to do this (the cleanest/quickest) is to create a Title object then see if its article ID is 0 * * @tbd: Fix so that the section name is hidden if no topics are visible? */ $t = Title::newFromText($newTitle); if (!$t || !$t->getArticleID()) { continue; } /** * Obtain H1 content from the article -- WE NEED TO CACHE THIS! */ $h1 = PonyDocsTopic::FindH1ForTitle($newTitle); if ($h1 === FALSE) { $h1 = $newTitle; } $href = str_replace('$1', PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . "/{$selectedProduct}/{$selectedVersion}/{$selectedManual}/{$title_suffix}", $wgArticlePath); $toc[$idx] = array('level' => 1, 'page_id' => $t->getArticleID(), 'link' => $href, 'toctitle' => $baseTopic, 'text' => $h1, 'section' => $toc[$section]['text'], 'title' => $newTitle, 'class' => 'toclevel-1'); $toc[$section]['subs']++; } $idx++; } if (!$toc[$section]['subs']) { unset($toc[$section]); } // Okay, let's store in our cache. $cache->put($key, $toc, TOC_CACHE_TTL, TOC_CACHE_TTL / 4); } if ($toc) { $currentIndex = -1; $start = array(); // Go through and determine start, prev, next and current elements. foreach ($toc as $idx => &$entry) { // Not using $entry. Only interested in $idx. // This allows us to process tocs with removed key indexes. if ($toc[$idx]['level'] == 1) { if (empty($start)) { $start = $toc[$idx]; } // Determine current $toc[$idx]['current'] = strcmp($wgTitle->getPrefixedText(), $toc[$idx]['title']) ? FALSE : TRUE; if ($toc[$idx]['current']) { $currentIndex = $idx; } // Now rewrite link with latest, if we are in latest if ($latest) { $safeVersion = preg_quote($selectedVersion, '#'); // Lets be specific and replace the version and not some other part of the URI that might match... $toc[$idx]['link'] = preg_replace('#^/' . PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . '/([' . PONYDOCS_PRODUCT_LEGALCHARS . ']+)/' . "{$safeVersion}#", '/' . PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . '/$1/latest', $toc[$idx]['link'], 1); } } } /** * Figure out previous and next links. * * Previous should point to previous topic regardless of section, so our best bet is to skip any 'level=0'. * * Next works the same way. */ $prev = $next = $idx = -1; if ($currentIndex >= 0) { $idx = $currentIndex; while ($idx >= 0) { --$idx; if (isset($toc[$idx]) && $toc[$idx]['level'] == 1) { $prev = $idx; break; } } $idx = $currentIndex; // Array is sparse, so sizeof() truncates the end. Use max key instead. while ($idx <= max(array_keys($toc))) { ++$idx; if (isset($toc[$idx]) && $toc[$idx]['level'] == 1) { $next = $idx; break; } } if ($prev != -1) { $prev = array('link' => $toc[$prev]['link'], 'text' => $toc[$prev]['text']); } if ($next != -1) { $next = array('link' => $toc[$next]['link'], 'text' => $toc[$next]['text']); } } /** * You should typically capture this by doing: * list( $toc, $prev, $next, $start ) = $ponydocstoc->loadContent(); * * @FIXME: Previous and next links change based on the page you are on, so we cannot CACHE those! * * $obj = new stdClass(); * $obj->toc = $toc; * $obj->prev = $prev; * $obj->next = $next; * $obj->start = $start; * $cache->addKey($tocKey, $obj); */ // Last but not least, get the manual description if there is one. if (is_object($this->pTOCArticle) && preg_match('/{{#manualDescription:([^}]*)}}/', $this->pTOCArticle->getContent(), $matches)) { $this->mManualDescription = $matches[1]; } // $this->pTOCArticle is empty, we're probably creating a new TOC } else { $toc = array(); $prev = array(); $next = array(); $start = array(); } return array($toc, $prev, $next, $start); }
public function prepareDocumentation() { global $wgOut, $wgParser, $wgScriptPath, $wgTitle, $wgUser; /** * We need a lot of stuff from our PonyDocs extension! */ $ponydocs = PonyDocsWiki::getInstance($this->data['selectedProduct']); $this->data['manuals'] = $ponydocs->getManualsForProduct($this->data['selectedProduct']); /** * Adjust content actions as needed, such as add 'view all' link. */ $this->contentActions(); $this->navURLS(); /** * Possible topic syntax we must handle: * * Documentation:<topic> *Which may include a version tag at the end, we don't care about this. * Documentation:<productShortName>:<manualShortName>:<topic>:<version> * Documentation:<productShortName>:<manualShortName> */ /** * Based on the name; i.e. 'Documentation:Product:Manual:Topic' we need to parse it out and store the manual name and * the topic name as parameters. We store manual in 'manualname' and topic in 'topicname'. Special handling * needs to be done for versions and TOC? * * 0=NS (Documentation) * 1=Product (Short name) * 2=Manual (Short name) * 3=Topic * 4=Version */ $pManual = null; $pieces = explode(':', $wgTitle->__toString()); $helpClass = ''; /** * This isn't a specific topic+version -- handle appropriately. */ if (sizeof($pieces) < 4) { if (!strcmp(PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . ':' . $this->data['selectedProduct'] . PONYDOCS_PRODUCTVERSION_SUFFIX, $wgTitle->__toString())) { $this->data['titletext'] = 'Versions Management - ' . $this->data['selectedProduct']; $wgOut->addHTML('<br><span class="' . $helpClass . '"><i>* Use {{#version:name|status}} to define a new version,' . ' where status is released, unreleased, or preview.' . ' Valid chars in version name are A-Z, 0-9, period, comma, and dash.</i></span>'); $wgOut->addHTML('<br><span class="' . $helpClass . '"><i>* Use {{#versiongroup:name|message}} to set a banner' . ' message that will appear on every topic in every version following the versiongroup.</i></span>'); } elseif (!strcmp(PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . ':' . $this->data['selectedProduct'] . PONYDOCS_PRODUCTMANUAL_SUFFIX, $wgTitle->__toString())) { $this->data['titletext'] = 'Manuals Management - ' . $this->data['selectedProduct']; $wgOut->addHTML('<br><span class="' . $helpClass . '"><i>' . '* Use {{#manual:manualShortName|displayName|categories}} to define a new manual.'); $wgOut->addHTML('<br><span class="' . $helpClass . '"><i>' . '* Prepend manual short name with ' . PONYDOCS_PRODUCT_STATIC_PREFIX . ' to define a static manual.' . '</i></span>'); $wgOut->addHTML('<br><span class="' . $helpClass . '">' . '<i>* If you omit display name, the short name will be used in links.</i></span>'); $wgOut->addHTML('<br><span class="' . $helpClass . '">' . '<i>* Categories is a comma-separated list of categories</i></span>'); } elseif (!strcmp(PONYDOCS_DOCUMENTATION_PRODUCTS_TITLE, $wgTitle->__toString())) { $this->data['titletext'] = 'Products Management'; $wgOut->addHTML('<br><span class="' . $helpClass . '"><i>' . '* Use {{#product:productShortName|displayName|description|parent|categories}} to define a new product.' . '</i></span>'); $wgOut->addHTML('<br><span class="' . $helpClass . '"><i>' . '* Prepend product short name with ' . PONYDOCS_PRODUCT_STATIC_PREFIX . ' to define a static product.' . '</i></span>'); $wgOut->addHTML('<br><span class="' . $helpClass . '"><i>' . '* displayName, description, parent, and categories can be left empty.</i></span>'); $wgOut->addHTML('<br><span class="' . $helpClass . '">' . '<i>* If you leave displayName empty, productShortName will be used in links.</i></span>'); $wgOut->addHTML('<br><span class="' . $helpClass . '">' . '<i>* Categories is a comma-separated list of categories.</i></span>'); $wgOut->addHTML('<br><span class="' . $helpClass . '">' . '<i>* Each product here <b>MUST</b> also be listed in $ponyDocsProductsList,' . ' usually configured in LocalSettings.php.</i></span>'); } elseif (preg_match('/(.*)TOC(.*)/', $pieces[2], $matches)) { $this->data['titletext'] = $matches[1] . ' Table of Contents Page'; $wgOut->addHTML('<br><span class="' . $helpClass . '"><i>' . '* Optionally start this page with {{#manualDescription:Manual Description.}}' . ' followed by two line-breaks to set a manual description for the Manual this TOC belongs to.' . '</i></span>'); $wgOut->addHTML('<br><span class="' . $helpClass . '"><i>' . '* Topics are grouped into sections by section headers.' . ' Any line without markup is considered a section header.' . ' A section header is required before the the first topic tag.</i></span>'); $wgOut->addHTML('<br><span class="' . $helpClass . '"><i>' . '* Topic tags must be part of an unordered list.' . ' Use {{#topic:Display Name}} after a * (list item markup) to create topics.</i></span>'); } elseif (sizeof($pieces) >= 2 && PonyDocsProductManual::IsManual($pieces[1], $pieces[2])) { $pManual = PonyDocsProductManual::GetManualByShortName($pieces[1], $pieces[2]); if ($pManual) { $this->data['manualname'] = $pManual->getLongName(); } else { $this->data['manualname'] = $pieces[2]; } $this->data['topicname'] = $pieces[3]; $this->data['titletext'] = $pieces[2]; } else { $this->data['topicname'] = $pieces[2]; } } else { $pManual = PonyDocsProductManual::GetManualByShortName($pieces[1], $pieces[2]); if ($pManual) { $this->data['manualname'] = $pManual->getLongName(); } else { $this->data['manualname'] = $pieces[2]; } $this->data['topicname'] = $pieces[3]; $h1 = PonyDocsTopic::FindH1ForTitle($wgTitle->__toString()); if ($h1 !== FALSE) { $this->data['titletext'] = $h1; } } /** * Get current topic, passing it our global Article object. * From this, generate our TOC based on the current topic selected. * This generates our left sidebar TOC plus our prev/next/start navigation links. * This should ONLY be done if we actually are WITHIN a manual, so special pages like TOC, etc. should not do this! */ if ($pManual) { $p = PonyDocsProduct::GetProductByShortName($this->data['selectedProduct']); $v = PonyDocsProductVersion::GetVersionByName($this->data['selectedProduct'], $this->data['selectedVersion']); $toc = new PonyDocsTOC($pManual, $v, $p); list($this->data['manualtoc'], $this->data['tocprev'], $this->data['tocnext'], $this->data['tocstart']) = $toc->loadContent(); $this->data['toctitle'] = $toc->getTOCPageTitle(); } /** * Create a PonyDocsTopic from our article. From this we populate: * * topicversions: List of version names topic is tagged with. * inlinetoc: Inline TOC shown above article body. * catcode: Special category code. * cattext: Category description. * basetopicname: Base topic name (w/o :<version> at end). * basetopiclink: Link to special TopicList page to view all same topics. */ $context = $this->skin->getContext(); $article = Article::newFromTitle($context->getTitle(), $context); $topic = new PonyDocsTopic($article); if (preg_match('/^' . PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . ':(.*):(.*):(.*):(.*)/', $wgTitle->__toString()) || preg_match('/^' . PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . ':.*:.*TOC.*/', $wgTitle->__toString())) { $this->data['topicversions'] = PonyDocsWiki::getVersionsForTopic($topic); $this->data['inlinetoc'] = $topic->getSubContents(); $this->data['versionclasses'] = $topic->getVersionClasses(); $this->data['versionGroupMessage'] = $this->data['pVersion']->getVersionGroupMessage(); /** * Sort of a hack -- we only use this right now when loading a TOC page which is new/does not exist. * When this happens a hook (AlternateEdit) adds an inline script to define this JS function, * which populates the edit box with the proper Category tag based on the currently selected version. */ $this->data['body_onload'] = 'ponyDocsOnLoad();'; switch ($this->data['catcode']) { case 0: $this->data['cattext'] = 'Applies to latest version which is currently unreleased.'; break; case 1: $this->data['cattext'] = 'Applies to latest version.'; break; case 2: $this->data['cattext'] = 'Applies to released version(s) but not the latest.'; break; case 3: $this->data['cattext'] = 'Applies to latest preview version.'; break; case 4: $this->data['cattext'] = 'Applies to one or more preview version(s) only.'; break; case 5: $this->data['cattext'] = 'Applies to one or more unreleased version(s) only.'; break; case -2: /** Means its not a a title name which should be checked. */ break; default: $this->data['cattext'] = 'Does not apply to any version of PonyDocs.'; break; } } $this->data['basetopicname'] = $topic->getBaseTopicName(); if (strlen($this->data['basetopicname'])) { $this->data['basetopiclink'] = '<a href="' . $wgScriptPath . '/index.php?title=Special:TopicList&topic=' . $this->data['basetopicname'] . '">View All</a>'; } $temp = PonyDocsTopic::FindH1ForTitle(PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . ':' . $topic->getTitle()->getText()); if ($temp !== false) { // We got an H1! $this->data['pagetitle'] = $temp; } }