/** * 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 static function onArticleFromTitle_NoVersion(&$title, &$article) { global $wgArticlePath; $defaultRedirect = PonyDocsExtension::getDefaultUrl(); // If this article doesn't have a valid manual, don't display the article $articleMetadata = PonyDocsArticleFactory::getArticleMetadataFromTitle($title->__toString()); if (!PonyDocsProductManual::IsManual($articleMetadata['product'], $articleMetadata['manual'])) { $wgHooks['BeforePageDisplay'][] = "PonyDocsExtension::handle404"; return false; } $dbr = wfGetDB(DB_SLAVE); $res = $dbr->select('categorylinks', 'cl_to', array('cl_to LIKE "V:%:%"', 'cl_type = "page"', "cl_sortkey LIKE '" . $dbr->strencode(strtoupper($title->getText())) . ":%'"), __METHOD__); if (!$res->numRows()) { if (PONYDOCS_DEBUG) { error_log("DEBUG [" . __METHOD__ . ":" . __LINE__ . "] redirecting to {$defaultRedirect}"); } header("Location: " . $defaultRedirect); exit(0); } /** * First create a list of versions to which the current user has access to. */ $versionList = array_reverse(PonyDocsVersion::GetVersions(true)); $versionNameList = array(); foreach ($versionList as $pV) { $versionNameList[] = $pV->getName(); } /** * Create a list of existing versions for this topic. The list contains PonyDocsVersion instances. Only store * UNIQUE instances and valid pointers. Once done, sort them so that the LATEST version is at the front of * the list (index 0). */ $existingVersions = array(); while ($row = $dbr->fetchObject($res)) { if (preg_match('/^V:(.*)/i', $row->cl_to, $vmatch)) { $pVersion = PonyDocsVersion::GetVersionByName($vmatch[1]); if ($pVersion && !in_array($pVersion, $existingVersions)) { $existingVersions[] = $pVersion; } } } usort($existingVersions, 'PonyDocs_versionCmp'); $existingVersions = array_reverse($existingVersions); /** * Now filter out versions the user does not have access to from the top; once we find the version for this topic * to which the user has access, create our Article object and replace our title (to not redirect) and return true. */ foreach ($existingVersions as $pV) { if (in_array($pV->getName(), $versionNameList)) { /** * Look up topic name and redirect to URL. */ $res = $dbr->select(array('categorylinks'), 'cl_from', array("cl_to = 'V:" . $pV->getName() . "'", 'cl_type = "page"', "cl_sortkey LIKE '" . $dbr->strencode(strtoupper($title->getText())) . ":%'"), __METHOD__); if (!$res->numRows()) { if (PONYDOCS_DEBUG) { error_log("DEBUG [" . __METHOD__ . ":" . __LINE__ . "] redirecting to {$defaultRedirect}"); } header("Location: " . $defaultRedirect); exit(0); } $row = $dbr->fetchObject($res); $title = Title::newFromId($row->cl_from); $article = new Article($title); $article->loadContent(); if (!$article->exists()) { $article = NULL; } else { // Without this we lose SplunkComments and version switcher. // Probably we can replace with a RequestContext in the future... $wgTitle = $title; } return TRUE; } } /** * Invalid redirect -- go to Main_Page or something. */ if (PONYDOCS_DEBUG) { error_log("DEBUG [" . __METHOD__ . ":" . __LINE__ . "] redirecting to {$defaultRedirect}"); } header("Location: " . $defaultRedirect); exit(0); }
/** * This is used when an author wants to CLONE a title from outside the Documentation namespace into a * title within it. We must be passed the title of the original/source topic and then the destination * title which should be a full form PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . ':<manual>:<topicName>:<version>' * which it will then tag with the supplied version and strip out any other Category tags (since they are * invalid in the Documentation namespace unless a DEFINED version). * * This will return an AjaxResponse object which MAY contain an error in the case the version is not * valid or the topic already exists (destination). * * @FIXME: Should validate version is defined. * * @param string $topic Title of topic to clone. * @param string $destTitle Title of destination topic. * @return AjaxResponse */ function efPonyDocsAjaxCloneExternalTopic($topic, $destTitle) { $response = new AjaxResponse(); $response->setCacheDuration(false); $pieces = split(':', $destTitle); if (sizeof($pieces) < 4 || strcasecmp($pieces[0], PONYDOCS_DOCUMENTATION_NAMESPACE_NAME) != 0) { $response->addText('Destination title is not valid.'); return $response; } if (!PonyDocsManual::IsManual($pieces[1])) { $response->addText('Destination title references an invalid manual.'); return $response; } if (!PonyDocsVersion::IsVersion($pieces[3])) { $response->addText('Destination title references an undefined version.'); return $response; } $destArticle = new Article(Title::newFromText($destTitle)); if ($destArticle->exists()) { $response->addText('Destination title already exists.'); return $response; } $article = new Article(Title::newFromText($topic)); if (!$article->exists()) { $response->addText('Source article could not be found.'); return $response; } $content = $article->getContent(); //$content = preg_replace( '/\[\[ return $response; }