/**
  * Hook function which
  * - Sets the version correctly when editing a topic
  * - Redirects to the first topic in a manual if the user requested a bare manual URL
  * - Redirect to the landing page when there are no available versions
  */
 public function onArticleFromTitleQuickLookup(&$title, &$article)
 {
     global $wgScriptPath;
     if (preg_match('/&action=edit/', $_SERVER['PATH_INFO'])) {
         // Check referrer and see if we're coming from a doc page.
         // If so, we're editing it, so we should force the version
         // to be from the referrer.
         if (preg_match('/^' . str_replace("/", "\\/", $wgScriptPath) . '\\/' . PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . '\\/(\\w+)\\/((latest|[\\w\\.]*)\\/)?(\\w+)\\/?/i', $_SERVER['HTTP_REFERER'], $match)) {
             $targetProduct = $match[1];
             $targetVersion = $match[3];
             if ($targetVersion == "latest") {
                 PonyDocsProductVersion::SetSelectedVersion($targetProduct, PonyDocsProductVersion::GetLatestReleasedVersion($targetProduct)->getVersionName());
             } else {
                 PonyDocsProductVersion::SetSelectedVersion($targetProduct, $targetVersion);
             }
         }
     }
     // Match a URL like /Documentation/PRODUCT
     if (preg_match('/^' . str_replace("/", "\\/", $wgScriptPath) . '\\/' . PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . '\\/([' . PONYDOCS_PRODUCT_LEGALCHARS . ']+)$/i', $_SERVER['PATH_INFO'], $match)) {
         $targetProduct = $match[1];
         $version = PonyDocsProductVersion::GetVersions($targetProduct, TRUE);
         //check for product not found
         if (empty($version)) {
             PonyDocsExtension::redirectToLandingPage();
             return true;
         }
     }
     // Matches a URL like /Documentation/PRODUCT/VERSION/MANUAL
     // TODO: Should match PONYDOCS_PRODUCTMANUAL_LEGALCHARS instead of \w at the end
     if (preg_match('/^' . str_replace("/", "\\/", $wgScriptPath) . '\\/' . PONYDOCS_DOCUMENTATION_NAMESPACE_NAME . '\\/([' . PONYDOCS_PRODUCT_LEGALCHARS . ']+)\\/([' . PONYDOCS_PRODUCTVERSION_LEGALCHARS . ']+)\\/(\\w+)\\/?$/i', $_SERVER['PATH_INFO'], $match)) {
         $targetProduct = $match[1];
         $targetVersion = $match[2];
         $targetManual = $match[3];
         $p = PonyDocsProduct::GetProductByShortName($targetProduct);
         if (!$p instanceof PonyDocsProduct) {
             $wgHooks['BeforePageDisplay'][] = "PonyDocsExtension::handle404";
             return false;
         }
         // User wants to find first topic in a requested manual.
         // Load up versions
         PonyDocsProductVersion::LoadVersionsForProduct($targetProduct);
         // Determine version
         if ($targetVersion == '') {
             // No version specified, use the user's selected version
             $ver = PonyDocsProductVersion::GetVersionByName($targetProduct, PonyDocsProductVersion::GetSelectedVersion($targetProduct));
         } else {
             if (strtolower($targetVersion) == "latest") {
                 // User wants the latest version.
                 $ver = PonyDocsProductVersion::GetLatestReleasedVersion($targetProduct);
             } else {
                 // Okay, they want to get a version by a specific name
                 $ver = PonyDocsProductVersion::GetVersionByName($targetProduct, $targetVersion);
             }
         }
         if (!$ver) {
             if (PONYDOCS_DEBUG) {
                 error_log("DEBUG [" . __METHOD__ . ":" . __LINE__ . "] redirecting to {$wgScriptPath}/" . PONYDOCS_DOCUMENTATION_NAMESPACE_NAME);
             }
             header('Location: ' . $wgScriptPath . '/' . PONYDOCS_DOCUMENTATION_NAMESPACE_NAME);
             die;
         }
         // Okay, the version is valid, let's set the user's version.
         PonyDocsProductVersion::SetSelectedVersion($targetProduct, $ver->getVersionName());
         PonyDocsProductManual::LoadManualsForProduct($targetProduct);
         $manual = PonyDocsProductManual::GetManualByShortName($targetProduct, $targetManual);
         if (!$manual) {
             // Rewrite to Main documentation
             if (PONYDOCS_DEBUG) {
                 error_log("DEBUG [" . __METHOD__ . ":" . __LINE__ . "] redirecting to {$wgScriptPath}/" . PONYDOCS_DOCUMENTATION_NAMESPACE_NAME);
             }
             header('Location: ' . $wgScriptPath . '/' . PONYDOCS_DOCUMENTATION_NAMESPACE_NAME);
             die;
         } elseif (!$manual->isStatic()) {
             // Get the TOC out of here! heehee
             $toc = new PonyDocsTOC($manual, $ver, $p);
             list($toc, $prev, $next, $start) = $toc->loadContent();
             //Added empty check for WEB-10038
             if (empty($toc)) {
                 PonyDocsExtension::redirectToLandingPage();
                 return FALSE;
             }
             foreach ($toc as $entry) {
                 if (isset($entry['link']) && $entry['link'] != "") {
                     // We found the first article in the manual with a link.
                     // Redirect to it.
                     if (PONYDOCS_DEBUG) {
                         error_log("DEBUG [" . __METHOD__ . ":" . __LINE__ . "] redirecting to " . $entry['link']);
                     }
                     header("Location: " . $entry['link']);
                     die;
                 }
             }
             //Replace die with a warning log and redirect
             error_log("WARNING [" . __METHOD__ . ":" . __LINE__ . "] redirecting to " . PonyDocsExtension::getDefaultUrl());
             PonyDocsExtension::redirectToLandingPage();
             return FALSE;
         }
     }
     return TRUE;
 }
Example #2
0
    /**
     * This is called upon loading the special page.  It should write output to 
     * the page with $wgOut
     */
    public function execute()
    {
        global $wgOut, $wgArticlePath, $wgScriptPath, $wgUser;
        global $wgRequest;
        global $wgDBprefix;
        $currentProduct = '';
        $currentVersion = '';
        $collapseAll = FALSE;
        $dbr = wfGetDB(DB_SLAVE);
        // Set headers and title of the page, get value of "t" from GET/POST
        $this->setHeaders();
        $title = $wgRequest->getVal('t');
        if (empty($title)) {
            $wgOut->setPagetitle("Documentation Linkage");
            $wgOut->addHTML('No topic specified.');
            return;
        }
        $wgOut->setPagetitle("Documentation Linkage For " . $title);
        // Parse "t" (the title we're looking for inbound links to)
        // Find titles for all inherited versions, etc.
        $titlePieces = explode(':', $title);
        $plainTitle = $title;
        // Create a new Title from text, such as what one would find in a link. Decodes any HTML entities in the text.
        $title = Title::newFromText($title);
        $toUrls = array();
        // Do PonyDocs-specific stuff (loop through all inherited versions)
        if ($titlePieces[0] == PONYDOCS_DOCUMENTATION_NAMESPACE_NAME) {
            // Get all the versions in the product
            $versions = PonyDocsProductVersion::LoadVersionsForProduct($titlePieces[1], true);
            if (empty($versions)) {
                error_log('WARNING [PonyDocs] [' . __CLASS__ . '] Unable to find product versions for this topic: ' . $title);
            }
            $currentProduct = $titlePieces[1];
            $currentVersion = $titlePieces[4];
            // Get the latest released version of this product
            $latestVersionObj = PonyDocsProductVersion::GetLatestReleasedVersion($titlePieces[1]);
            if (is_object($latestVersionObj)) {
                $latestVersion = $latestVersionObj->getVersionName();
            } else {
                error_log('WARNING [PonyDocs] [' . __CLASS__ . '] Unable to find latest released version of ' . $titlePieces[1]);
            }
            // Generate a title without the version so we can dynamically generate a list of titles with all inherited versions
            $titleNoVersion = $titlePieces[0] . ":" . $titlePieces[1] . ":" . $titlePieces[2] . ":" . $titlePieces[3];
            // Search the database for matching to_links for each inherited version
            if (is_array($versions)) {
                foreach ($versions as $ver) {
                    // Add this URL to array of URLs to search db for
                    $toUrls[] = PonyDocsExtension::translateTopicTitleForDocLinks($titleNoVersion, NULL, $ver);
                    // Compare this version with latest version. If they're the same, add the URL with "latest" too.
                    $thisVersion = $ver->getVersionName();
                    if ($thisVersion == $latestVersion) {
                        $titleLatestVersion = $titlePieces[0] . ':' . $titlePieces[1] . ':' . $titlePieces[2] . ':' . $titlePieces[3] . ':latest';
                        $toUrls[] = PonyDocsExtension::translateTopicTitleForDocLinks($titleLatestVersion);
                    }
                }
            } else {
                error_log('WARNING [PonyDocs] [' . __CLASS__ . '] Unable to find versions for ' . $title);
            }
        } else {
            // Do generic mediawiki stuff for non-PonyDocs namespaces
            $collapseAll = TRUE;
            $toUrls[] = PonyDocsExtension::translateTopicTitleForDocLinks($title);
        }
        // Query the database for the list of toUrls we've collated
        if (!empty($toUrls)) {
            foreach ($toUrls as &$toUrl) {
                $toUrl = $dbr->strencode($toUrl);
            }
            $inUrls = "'" . implode("','", $toUrls) . "'";
            $query = "SELECT * FROM " . $wgDBprefix . "ponydocs_doclinks WHERE to_link IN ({$inUrls})";
            $results = $dbr->query($query);
        }
        // Create array of links, sorted by product and version
        $links = array();
        // Loop through results and save into handy dandy links array
        if (!empty($results)) {
            foreach ($results as $result) {
                $fromProduct = '';
                $fromVersion = '';
                $displayUrl = '';
                if (strpos($result->from_link, PONYDOCS_DOCUMENTATION_NAMESPACE_NAME) !== false) {
                    // If this is a PonyDocs style links, with slashes,
                    // save product, version, display URL accordingly.
                    $pieces = explode('/', $result->from_link);
                    $fromProduct = $pieces[1];
                    $fromVersion = $pieces[2];
                    $displayUrl = $result->from_link;
                } else {
                    // If this is a generic mediawiki style link, with colons (or not),
                    // set product to the namespace, and remove namespace
                    // from the display URL. Leave version blank.
                    if (strpos($result->from_link, ':') !== false) {
                        $pieces = explode(':', $result->from_link);
                        $fromProduct = $pieces[0];
                        // The "product" will be the namespace
                        $displayUrl = $pieces[1];
                        // So the namespace doesn't show in every URL
                    } else {
                        // it's possible to have a link with no colons
                        $fromProduct = 'Other';
                        // No namespace, so the "product" will be the string "Other"
                        $displayUrl = $result->from_link;
                    }
                    $fromVersion = 'None';
                    // No concept of versions outside of PonyDocs
                }
                // Put all this stuff in an array that we can use to generate HTML
                $links[$fromProduct][$fromVersion][] = array('from_link' => $result->from_link, 'to_link' => $result->to_link, 'display_url' => $displayUrl);
            }
        }
        // Make HTML go!
        ob_start();
        ?>

		<div class="doclinks">
			<h2>Inbound links to <?php 
        echo $plainTitle;
        ?>
 from other topics.</h2>

			<?php 
        // If there are no links, display a message saying as much
        if (empty($links)) {
            ?>
				<p>No links to <?php 
            echo $plainTitle;
            ?>
 (and its inherited versions) from other topics.</p>
			<?php 
        } else {
            // Display all links, ordered by product then version
            foreach ($links as $fromProduct => $fromVersions) {
                // If this is a PonyDocs Product
                if (PonyDocsProduct::IsProduct($fromProduct)) {
                    // Get versions for this product, so we can display the versions in the correct order
                    PonyDocsProductVersion::LoadVersionsForProduct($fromProduct, true);
                    $fromProductVersions = PonyDocsProductVersion::GetVersions($fromProduct);
                    // If there are no valid versions for this product/user, then skip the product name header.
                    if (!count($fromProductVersions)) {
                        continue;
                    }
                    ?>
						<h2><?php 
                    echo $fromProduct;
                    ?>
</h2>
						<?php 
                    foreach ($fromProductVersions as $fromProductVersionObj) {
                        $fromProductVersionName = $fromProductVersionObj->getVersionName();
                        // If there are doclinks from this version, print them
                        if (array_key_exists($fromProductVersionName, $fromVersions)) {
                            // Expand containers of incoming links from the current Product and Version
                            // Expand containers of incoming links from other Products
                            // But don't expand any containers if this is not a PonyDocs product
                            $selected = '';
                            if (($currentProduct != $fromProduct || $currentVersion == $fromProductVersionName) && !$collapseAll) {
                                $selected = 'selected';
                            }
                            ?>
								<h3 class="doclinks-collapsible <?php 
                            print $selected;
                            ?>
">
									<?php 
                            echo $fromProduct . ' ' . $fromProductVersionName;
                            ?>
								</h3>
								<ul>
								<?php 
                            foreach ($fromVersions[$fromProductVersionName] as $linkAry) {
                                ?>
									<li>
										<a href="<?php 
                                echo str_replace('$1', $linkAry['from_link'], $wgArticlePath);
                                ?>
">
											<?php 
                                echo $linkAry['display_url'];
                                ?>
										</a>
									</li>
								<?php 
                            }
                            ?>
								</ul>
								<?php 
                        }
                    }
                } else {
                    ?>
						<h2><?php 
                    echo $fromProduct;
                    ?>
 </h2>
						<h3 class="doclinks-collapsible selected">Latest</h3>
						<?php 
                    // This is not a PonyDocs product, don't worry about sorting
                    foreach ($fromVersions as $fromVersion => $fromVersionData) {
                        ?>
							<ul>
							<?php 
                        foreach ($fromVersionData as $linkAry) {
                            ?>
								<li>
									<a href="<?php 
                            echo str_replace('$1', $linkAry['from_link'], $wgArticlePath);
                            ?>
">
										<?php 
                            echo $linkAry['display_url'];
                            ?>
									</a>
								</li>
							<?php 
                        }
                        ?>
							</ul>
							<?php 
                    }
                }
            }
        }
        ?>
		</div>

		<?php 
        $htmlContent = ob_get_contents();
        ob_end_clean();
        $wgOut->addHTML($htmlContent);
        return true;
    }
Example #3
0
 /**
  * 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);
 }
Example #4
0
 /**
  * Create a URL path (e.g. Documentation/Foo/latest/Bar/Bas) for a Topic
  * 
  * @param string $productName
  * @param string $manualName
  * @param string $topicName
  * @param string $versionName - Optional. We'll get the selected version (which defaults to 'latest') if empty
  * 
  * @return string
  * 
  * TODO: We should really be passing a topic object into this and not a string
  */
 public static function getTopicURLPath($productName, $manualName, $topicName, $versionName = NULL)
 {
     global $wgArticlePath;
     if (!isset($versionName)) {
         $versionName = PonyDocsProductVersion::GetSelectedVersion($productName);
     }
     $latestVersion = PonyDocsProductVersion::GetLatestReleasedVersion($productName);
     if ($latestVersion) {
         if ($versionName == $latestVersion->getVersionName()) {
             $versionName = 'latest';
         }
     }
     $base = str_replace('$1', PONYDOCS_DOCUMENTATION_NAMESPACE_NAME, $wgArticlePath);
     return "{$base}/{$productName}/{$versionName}/{$manualName}/{$topicName}";
 }