/** * 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); }
/** * This is an ArticleSaveComplete hook that creates topics which don't exist yet when saving a TOC. * * @param WikiPage $article * @param User $user * @param string $text * @param string $summary * @param boolean $minor * @param boolean $watch * @param $sectionanchor * @param integer $flags * * @deprecated Replace with PageContentSaveComplete hook */ public static function onArticleSave_CheckTOC(&$article, &$user, $text, $summary, $minor, $watch, $sectionanchor, &$flags) { // Dangerous. Only set the flag if you know that you should be skipping this processing. // Currently used for branch/inherit. if (PonyDocsExtension::isSpeedProcessingEnabled()) { return TRUE; } $title = $article->getTitle(); $realArticle = Article::newFromWikiPage($article, RequestContext::getMain()); $matches = array(); if (preg_match('/' . PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . ':([' . PONYDOCS_PRODUCT_LEGALCHARS . ']*):([' . PONYDOCS_PRODUCTMANUAL_LEGALCHARS . ']*)TOC([' . PONYDOCS_PRODUCTVERSION_LEGALCHARS . ']*)/i', $title->__toString(), $match)) { $dbr = wfGetDB(DB_MASTER); /** * Get all topics */ $topicRegex = '/' . PonyDocsTopic::getTopicRegex() . '/'; preg_match_all($topicRegex, $text, $matches, PREG_SET_ORDER); /** * Create any topics which do not already exist in the saved TOC. */ $pProduct = PonyDocsProduct::GetProductByShortName($match[1]); $pManual = PonyDocsProductManual::GetManualByShortName($pProduct->getShortName(), $match[2]); $pManualTopic = new PonyDocsTopic($realArticle); $manVersionList = $pManualTopic->getProductVersions(); if (!sizeof($manVersionList)) { return TRUE; } // Clear all TOC cache entries for each version. if ($pManual) { foreach ($manVersionList as $version) { PonyDocsTOC::clearTOCCache($pManual, $version, $pProduct); PonyDocsProductVersion::clearNAVCache($version); } } $earliestVersion = PonyDocsProductVersion::findEarliest($pProduct->getShortName(), $manVersionList); foreach ($matches as $m) { $wikiTopic = preg_replace('/([^' . str_replace(' ', '', Title::legalChars()) . '])/', '', $m[1]); $wikiPath = PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . ':' . $match[1] . ':' . $match[2] . ':' . $wikiTopic; $versionIn = array(); foreach ($manVersionList as $pV) { $versionIn[] = $pProduct->getShortName() . ':' . $pV->getVersionName(); } $res = $dbr->select(array('categorylinks'), 'cl_from', array("cl_to IN ('V:" . implode("','V:", $versionIn) . "')", 'cl_type = "page"', "cl_sortkey LIKE '" . $dbr->strencode(strtoupper("{$match[1]}:{$match[2]}:{$wikiTopic}")) . ":%'"), __METHOD__); $topicName = ''; if (!$res->numRows()) { /** * No match -- so this is a "new" topic. Set name and create. */ $topicName = PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . ':' . $match[1] . ':' . $match[2] . ':' . $wikiTopic . ':' . $earliestVersion->getVersionName(); $topicArticle = new Article(Title::newFromText($topicName)); if (!$topicArticle->exists()) { $content = "= " . $m[1] . "=\n\n"; foreach ($manVersionList as $pVersion) { $content .= '[[Category:V:' . $pProduct->getShortName() . ':' . $pVersion->getVersionName() . ']]'; } $topicArticle->doEdit($content, 'Auto-creation of topic ' . $topicName . ' via TOC ' . $title->__toString(), EDIT_NEW); if (PONYDOCS_DEBUG) { error_log("DEBUG [" . __METHOD__ . ":" . __LINE__ . "] Auto-created {$topicName} from TOC " . $title->__toString()); } } } } } return TRUE; }
/** * Do a bulk add operation. Take a collection of topics and add them to the TOC if it doesn't already exist. * * @param $manual PonyDocsManual The manual the TOC belongs to. * @param $version PonyDocsVersion The version the TOC belongs to. * @param $collection array A multidimensional array of topics. First keyed with section name, then titles. * @returns boolean */ static function addCollectionToTOC($product, $manual, $version, $collection) { global $wgTitle; $title = self::TOCExists($product, $manual, $version); if ($title == FALSE) { throw new Exception("TOC does not exist for " . $manual->getShortName() . " with version " . $version->getVersionName()); } $title = Title::newFromText($title); $wgTitle = $title; $article = new Article($title); if (!$article->exists()) { throw new Exception("TOC does not exist for " . $manual->getShortName() . " with version " . $version->getVersionName()); } // Okay, let's search for the content. $content = $article->getContent(); foreach ($collection as $sectionName => $topics) { // $evalSectionName is the cleaned up section name to look for. $evalSectionName = preg_quote(trim(str_replace('?', "", strtolower($sectionName)))); foreach ($topics as $topic) { if ($topic == NULL) { continue; } // $topic is the trimmed original version of the topic. $topic = trim($topic); // $evalTopic is the clened up topic name to look for $evalTopic = preg_quote(str_replace('?', '', strtolower($topic))); $content = explode("\n", $content); $found = FALSE; $inSection = FALSE; $newContent = ''; foreach ($content as $line) { $evalLine = trim(str_replace('?', '', strtolower($line))); $topicRegex = PonyDocsTopic::getTopicRegex($evalTopic); if (preg_match("/^" . $evalSectionName . "\$/", $evalLine)) { $inSection = TRUE; $newContent .= $line . "\n"; continue; } elseif (preg_match("/\\*\\s*{$topicRegex}/", $evalLine)) { if ($inSection) { $found = TRUE; } $newContent .= $line . "\n"; continue; } elseif (preg_match("/^\\s?\$/", $evalLine)) { if ($inSection && !$found) { $newContent .= "* {{#topic:" . $topic . "}}\n\n"; $found = TRUE; continue; } $inSection = FALSE; } $newContent .= $line . "\n"; } if (!$found) { // Then the section didn't event exist, we should add to TOC and add the item. // We need to add it before the Category evalLine. $text = $sectionName . "\n" . "* {{#topic:" . $topic . "}}\n\n[[Category"; $newContent = preg_replace("/\\[\\[Category/", $text, $newContent); } $inSection = FALSE; // Reset loop data $content = $newContent; } } // Okay, do the edit $article->doEdit($content, "Updated TOC in bulk branch operation.", EDIT_UPDATE); return TRUE; }