/** * This expects to find: * {{#topic:Text Name}} * * @param Parser $parser * @param string $param1 Full text name of topic, must be converted to wiki topic name. * @return array * * TODO: Much of this function duplicates code above in efGetTitleFromMarkup(), can we DRY? * There really shouldn't be any real code in this file, just calls to class methods... */ function efTopicParserFunction_Render(&$parser, $param1 = '') { global $wgArticlePath, $wgTitle, $action; if (PonyDocsExtension::isSpeedProcessingEnabled()) { return TRUE; } /** * We ignore this parser function if not in a TOC management page. */ if (!preg_match('/' . PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . ':(.*):(.*)TOC(.*)/i', $wgTitle->__toString(), $matches)) { return FALSE; } $manualShortName = $matches[2]; $productShortName = $matches[1]; PonyDocsWiki::getInstance($productShortName); /** * Get the earliest tagged version of this TOC page and append it to the wiki page? * Ensure the manual is valid then use PonyDocsManual::getManualByShortName(). * Next attempt to get the version tags for this page -- which may be NONE -- * and from this determine the "earliest" version to which this page applies. * * TODO: This comment is duplicated above in efGetTitleFromMarkup, can we DRY? */ if (!PonyDocsProductManual::IsManual($productShortName, $manualShortName)) { return FALSE; } $pManual = PonyDocsProductManual::GetManualByShortName($productShortName, $manualShortName); $pTopic = new PonyDocsTopic(new Article($wgTitle)); /** * @FIXME: If TOC page is NOT tagged with any versions we cannot create the pages/links to the * topics, right? */ $manVersionList = $pTopic->getProductVersions(); if (!sizeof($manVersionList)) { return $parser->insertStripItem($param1, $parser->mStripState); } $earliestVersion = PonyDocsProductVersion::findEarliest($productShortName, $manVersionList); /** * Clean up the full text name into a wiki-form. This means remove spaces, #, ?, and a few other * characters which are not valid or wanted. It's not important HOW this is done as long as it is * consistent. */ $wikiTopic = preg_replace('/([^' . str_replace(' ', '', Title::legalChars()) . '])/', '', $param1); $wikiPath = PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . ':' . $productShortName . ':' . $manualShortName . ':' . $wikiTopic; $dbr = wfGetDB(DB_SLAVE); /** * Now look in the database for any instance of this topic name PLUS :<version>. * We need to look in categorylinks for it to find a record with a cl_to (version tag) * which is equal to the set of the versions for this TOC page. * For instance, if the TOC page was for versions 1.0 and 1.1 and our topic was 'How To Foo' * we need to find any cl_sortkey which is 'HowToFoo:%' and has a cl_to equal to 1.0 or 1.1. * There should only be 0 or 1, so we ignore anything beyond 1. * If found, we use THAT cl_sortkey as the link; * if NOT found we create a new topic, the name being the compressed topic name plus the earliest TOC version * ($earliestVersion->getName()). * We then need to ACTUALLY create it in the database, tag it with all the versions the TOC mgmt page is tagged with, * and set the H1 to the text inside the parser function. * * @fixme: Can we test if $action=save here so we don't do this on every page view? */ $versionIn = array(); foreach ($manVersionList as $pV) { $versionIn[] = $productShortName . ':' . $pV->getVersionName(); } $res = $dbr->select(array('categorylinks', 'page'), 'page_title', array('cl_from = page_id', 'page_namespace = "' . NS_PONYDOCS . '"', "cl_to IN ('V:" . implode("','V:", $versionIn) . "')", 'cl_type = "page"', "cl_sortkey LIKE '" . $dbr->strencode(strtoupper($productShortName . ':' . $manualShortName . ':' . $wikiTopic)) . ":%'"), __METHOD__); $topicName = ''; if (!$res->numRows()) { /** * No match -- so this is a "new" topic. Set name. */ $topicName = PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . ':' . $productShortName . ':' . $manualShortName . ':' . $wikiTopic . ':' . $earliestVersion->getVersionName(); } else { $row = $dbr->fetchObject($res); $topicName = PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . ":{$row->page_title}"; } $output = '<a href="' . wfUrlencode(str_replace('$1', $topicName, $wgArticlePath)) . '">' . $param1 . '</a>'; return $parser->insertStripItem($output, $parser->mStripState); }
public static function onArticleFromTitle_New(&$title, &$article) { global $wgScriptPath; global $wgArticlePath, $wgTitle, $wgOut, $wgHooks; $dbr = wfGetDB(DB_SLAVE); /** * We only care about Documentation namespace for rewrites and they must contain a slash, so scan for it. * $matches[1] = product * $matches[2] = latest|version * $matches[3] = manual * $matches[4] = topic */ if (!preg_match('/^' . PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . '\\/([' . PONYDOCS_PRODUCT_LEGALCHARS . ']*)\\/(.*)\\/(.*)\\/(.*)$/i', $title->__toString(), $matches)) { return false; } $defaultRedirect = PonyDocsExtension::getDefaultUrl(); /** * At this point $matches contains: * 0= Full title. * 1= Product name * 2= Version OR 'latest' as a string. * 3= Manual name (short name). * 4= Wiki topic name. */ $productName = $matches[1]; $versionName = $matches[2]; $manualName = $matches[3]; $topicName = $matches[4]; $product = PonyDocsProduct::GetProductByShortName($productName); // If we don't have a valid product, display 404 if (!$product instanceof PonyDocsProduct) { $wgHooks['BeforePageDisplay'][] = "PonyDocsExtension::handle404"; return false; } // If this article doesn't have a valid manual, don't display the article if (!PonyDocsProductManual::IsManual($productName, $manualName)) { $wgHooks['BeforePageDisplay'][] = "PonyDocsExtension::handle404"; return false; } // If this is a static product return because that should be handled by another function if ($product->isStatic()) { return true; } $versionSelectedName = PonyDocsProductVersion::GetSelectedVersion($productName); $version = ''; PonyDocsProductVersion::LoadVersionsForProduct($productName); if (!strcasecmp('latest', $versionName)) { /** * This will be a DESCENDING mapping of version name to PonyDocsVersion object and will ONLY contain the * versions available to the current user (i.e. LoadVersions() only loads the ones permitted). */ $releasedVersions = PonyDocsProductVersion::GetReleasedVersions($productName, true); if (empty($releasedVersions)) { return false; } $versionList = array_reverse($releasedVersions); $versionNameList = array(); foreach ($versionList as $pV) { $versionNameList[] = $pV->getVersionName(); } /** * Now get a list of version names to which the current topic is mapped in DESCENDING order as well * from the 'categorylinks' table. * * DB can't do descending order here, it depends on the order defined in versions page! So we have to * do some magic sorting below. */ $res = $dbr->select('categorylinks', 'cl_to', array('cl_to LIKE "V:%:%"', 'cl_type = "page"', "cl_sortkey LIKE '" . $dbr->strencode(strtoupper("{$productName}:{$manualName}:{$topicName}")) . ":%'"), __METHOD__); if (!$res->numRows()) { /** * What happened here is we requested a topic that does not exist or is not linked to any version. * Perhaps setup a default redirect, Main_Page or something? */ if (PONYDOCS_DEBUG) { error_log("DEBUG [" . __METHOD__ . ":" . __LINE__ . "] redirecting to {$defaultRedirect}"); } header("Location: " . $defaultRedirect); exit(0); } /** * Based on our list, get the PonyDocsVersion for each version tag and store in an array. Then pass this array * to our custom sort function via usort() -- the ending result is a sorted list in $existingVersions, with the * LATEST version at the front. * * @FIXME: GetVersionByName is missing some versions? */ $existingVersions = array(); while ($row = $dbr->fetchObject($res)) { if (preg_match('/^V:(.*):(.*)/i', $row->cl_to, $vmatch)) { $pVersion = PonyDocsProductVersion::GetVersionByName($vmatch[1], $vmatch[2]); if ($pVersion && !in_array($pVersion, $existingVersions)) { $existingVersions[] = $pVersion; } } } usort($existingVersions, "PonyDocs_ProductVersionCmp"); $existingVersions = array_reverse($existingVersions); // Okay, iterate through existingVersions. If we can't see that // any of them belong to our latest released version, redirect to // our latest handler. $latestReleasedVersion = PonyDocsProductVersion::GetLatestReleasedVersion($productName)->getVersionName(); $found = false; foreach ($existingVersions as $docVersion) { if ($docVersion->getVersionName() == $latestReleasedVersion) { $found = true; break; } } if (!$found) { if (PONYDOCS_DEBUG) { error_log("DEBUG [" . __METHOD__ . ":" . __LINE__ . "] redirecting to {$wgScriptPath}/Special:PonyDocsLatestDoc?t={$title}"); } header("Location: " . $wgScriptPath . "/Special:SpecialLatestDoc?t={$title}", true, 302); exit(0); } /** * Now we need to filter out any versions which this user has no access to. The easiest way is to loop through * our resulting $existingVersions and see if each is in_array( $versionNameList ); if its NOT, continue looping. * Once we hit one, redirect. if we exhaust our list, go to the main page or something. */ foreach ($existingVersions as $pV) { if (in_array($pV->getVersionName(), $versionNameList)) { /** * Look up topic name and redirect to URL. */ $res = $dbr->select(array('categorylinks', 'page'), 'page_title', array('cl_from = page_id', 'page_namespace = "' . NS_PONYDOCS . '"', "cl_to = 'V:" . $dbr->strencode($pV->getProductName() . ':' . $pV->getVersionName()) . "'", 'cl_type = "page"', "cl_sortkey LIKE '" . $dbr->strencode(strtoupper("{$productName}:{$manualName}:{$topicName}")) . ":%'"), __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::newFromText(PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . ":{$row->page_title}"); $article = new Article($title); $article->loadContent(); PonyDocsProductVersion::SetSelectedVersion($pV->getProductName(), $pV->getVersionName()); 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); } else { /** * Ensure version specified in aliased URL is a valid version -- if it is not we just need to do our default * redirect here. */ $version = PonyDocsProductVersion::GetVersionByName($productName, $versionName); if (!$version) { if (PONYDOCS_DEBUG) { error_log("DEBUG [" . __METHOD__ . ":" . __LINE__ . "] unable to retrieve version ({$versionName}) for product ({$productName}); redirecting to {$defaultRedirect}"); } header("Location: " . $defaultRedirect); exit(0); } /** * Look up the TOPIC in the categorylinks and find the one which is tagged with the version supplied. This * is the URL to redirect to. */ $res = $dbr->select(array('categorylinks', 'page'), 'page_title', array('cl_from = page_id', 'page_namespace = "' . NS_PONYDOCS . '"', "cl_to = 'V:" . $dbr->strencode($productName . ':' . $versionSelectedName) . "'", 'cl_type = "page"', "cl_sortkey LIKE '" . $dbr->strencode(strtoupper("{$productName}:{$manualName}:{$topicName}")) . ":%'"), __METHOD__); if (!$res->numRows()) { /** * Handle invalid redirects? */ $wgHooks['BeforePageDisplay'][] = "PonyDocsExtension::handle404"; return false; } $row = $dbr->fetchObject($res); $title = Title::newFromText(PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . ":{$row->page_title}"); /// FIXME this shouldn't be necessary because selected version already comes from here PonyDocsProductVersion::SetSelectedVersion($productName, $versionSelectedName); $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; } return FALSE; }
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; } }