/** * Get the fields that have to be queried from the page table: * the ones requested through requestField() and a few basic ones * we always need * @return array Array of field names */ public function getPageTableFields() { // Ensure we get minimum required fields // DON'T change this order $pageFlds = ['page_namespace' => null, 'page_title' => null, 'page_id' => null]; if ($this->mResolveRedirects) { $pageFlds['page_is_redirect'] = null; } if ($this->getConfig()->get('ContentHandlerUseDB')) { $pageFlds['page_content_model'] = null; } if ($this->getConfig()->get('PageLanguageUseDB')) { $pageFlds['page_lang'] = null; } foreach (LinkCache::getSelectFields() as $field) { $pageFlds[$field] = null; } $pageFlds = array_merge($pageFlds, $this->mRequestedPageFields); return array_keys($pageFlds); }
/** * Add an array of categories, with names in the keys * * @param array $categories Mapping category name => sort key */ public function addCategoryLinks(array $categories) { global $wgContLang; if (!is_array($categories) || count($categories) == 0) { return; } # Add the links to a LinkBatch $arr = [NS_CATEGORY => $categories]; $lb = new LinkBatch(); $lb->setArray($arr); # Fetch existence plus the hiddencat property $dbr = wfGetDB(DB_REPLICA); $fields = array_merge(LinkCache::getSelectFields(), ['page_namespace', 'page_title', 'pp_value']); $res = $dbr->select(['page', 'page_props'], $fields, $lb->constructSet('page', $dbr), __METHOD__, [], ['page_props' => ['LEFT JOIN', ['pp_propname' => 'hiddencat', 'pp_page = page_id']]]); # Add the results to the link cache $lb->addResultToCache(LinkCache::singleton(), $res); # Set all the values to 'normal'. $categories = array_fill_keys(array_keys($categories), 'normal'); # Mark hidden categories foreach ($res as $row) { if (isset($row->pp_value)) { $categories[$row->page_title] = 'hidden'; } } # Add the remaining categories to the skin if (Hooks::run('OutputPageMakeCategoryLinks', [&$this, $categories, &$this->mCategoryLinks])) { foreach ($categories as $category => $type) { // array keys will cast numeric category names to ints, so cast back to string $category = (string) $category; $origcategory = $category; $title = Title::makeTitleSafe(NS_CATEGORY, $category); if (!$title) { continue; } $wgContLang->findVariantLink($category, $title, true); if ($category != $origcategory && array_key_exists($category, $categories)) { continue; } $text = $wgContLang->convertHtml($title->getText()); $this->mCategories[] = $title->getText(); $this->mCategoryLinks[$type][] = Linker::link($title, $text); } } }
/** * Modify $this->internals and $colours according to language variant linking rules * @param array $colours */ protected function doVariants(&$colours) { global $wgContLang; $linkBatch = new LinkBatch(); $variantMap = []; // maps $pdbkey_Variant => $keys (of link holders) $output = $this->parent->getOutput(); $linkCache = LinkCache::singleton(); $threshold = $this->parent->getOptions()->getStubThreshold(); $titlesToBeConverted = ''; $titlesAttrs = []; // Concatenate titles to a single string, thus we only need auto convert the // single string to all variants. This would improve parser's performance // significantly. foreach ($this->internals as $ns => $entries) { if ($ns == NS_SPECIAL) { continue; } foreach ($entries as $index => $entry) { $pdbk = $entry['pdbk']; // we only deal with new links (in its first query) if (!isset($colours[$pdbk]) || $colours[$pdbk] === 'new') { $titlesAttrs[] = [$index, $entry['title']]; // separate titles with \0 because it would never appears // in a valid title $titlesToBeConverted .= $entry['title']->getText() . ""; } } } // Now do the conversion and explode string to text of titles $titlesAllVariants = $wgContLang->autoConvertToAllVariants(rtrim($titlesToBeConverted, "")); $allVariantsName = array_keys($titlesAllVariants); foreach ($titlesAllVariants as &$titlesVariant) { $titlesVariant = explode("", $titlesVariant); } // Then add variants of links to link batch $parentTitle = $this->parent->getTitle(); foreach ($titlesAttrs as $i => $attrs) { /** @var Title $title */ list($index, $title) = $attrs; $ns = $title->getNamespace(); $text = $title->getText(); foreach ($allVariantsName as $variantName) { $textVariant = $titlesAllVariants[$variantName][$i]; if ($textVariant === $text) { continue; } $variantTitle = Title::makeTitle($ns, $textVariant); // Self-link checking for mixed/different variant titles. At this point, we // already know the exact title does not exist, so the link cannot be to a // variant of the current title that exists as a separate page. if ($variantTitle->equals($parentTitle) && !$title->hasFragment()) { $this->internals[$ns][$index]['selflink'] = true; continue 2; } $linkBatch->addObj($variantTitle); $variantMap[$variantTitle->getPrefixedDBkey()][] = "{$ns}:{$index}"; } } // process categories, check if a category exists in some variant $categoryMap = []; // maps $category_variant => $category (dbkeys) $varCategories = []; // category replacements oldDBkey => newDBkey foreach ($output->getCategoryLinks() as $category) { $categoryTitle = Title::makeTitleSafe(NS_CATEGORY, $category); $linkBatch->addObj($categoryTitle); $variants = $wgContLang->autoConvertToAllVariants($category); foreach ($variants as $variant) { if ($variant !== $category) { $variantTitle = Title::makeTitleSafe(NS_CATEGORY, $variant); if (is_null($variantTitle)) { continue; } $linkBatch->addObj($variantTitle); $categoryMap[$variant] = [$category, $categoryTitle]; } } } if (!$linkBatch->isEmpty()) { // construct query $dbr = wfGetDB(DB_SLAVE); $fields = array_merge(LinkCache::getSelectFields(), ['page_namespace', 'page_title']); $varRes = $dbr->select('page', $fields, $linkBatch->constructSet('page', $dbr), __METHOD__); $linkcolour_ids = []; // for each found variants, figure out link holders and replace foreach ($varRes as $s) { $variantTitle = Title::makeTitle($s->page_namespace, $s->page_title); $varPdbk = $variantTitle->getPrefixedDBkey(); $vardbk = $variantTitle->getDBkey(); $holderKeys = []; if (isset($variantMap[$varPdbk])) { $holderKeys = $variantMap[$varPdbk]; $linkCache->addGoodLinkObjFromRow($variantTitle, $s); $output->addLink($variantTitle, $s->page_id); } // loop over link holders foreach ($holderKeys as $key) { list($ns, $index) = explode(':', $key, 2); $entry =& $this->internals[$ns][$index]; $pdbk = $entry['pdbk']; if (!isset($colours[$pdbk]) || $colours[$pdbk] === 'new') { // found link in some of the variants, replace the link holder data $entry['title'] = $variantTitle; $entry['pdbk'] = $varPdbk; // set pdbk and colour $colours[$varPdbk] = Linker::getLinkColour($variantTitle, $threshold); $linkcolour_ids[$s->page_id] = $pdbk; } } // check if the object is a variant of a category if (isset($categoryMap[$vardbk])) { list($oldkey, $oldtitle) = $categoryMap[$vardbk]; if (!isset($varCategories[$oldkey]) && !$oldtitle->exists()) { $varCategories[$oldkey] = $vardbk; } } } Hooks::run('GetLinkColours', [$linkcolour_ids, &$colours]); // rebuild the categories in original order (if there are replacements) if (count($varCategories) > 0) { $newCats = []; $originalCats = $output->getCategories(); foreach ($originalCats as $cat => $sortkey) { // make the replacement if (array_key_exists($cat, $varCategories)) { $newCats[$varCategories[$cat]] = $sortkey; } else { $newCats[$cat] = $sortkey; } } $output->setCategoryLinks($newCats); } } }
/** * @param int $namespace Default NS_MAIN * @param string $prefix * @param string $from List all pages from this name (default false) */ protected function showPrefixChunk($namespace = NS_MAIN, $prefix, $from = null) { global $wgContLang; if ($from === null) { $from = $prefix; } $fromList = $this->getNamespaceKeyAndText($namespace, $from); $prefixList = $this->getNamespaceKeyAndText($namespace, $prefix); $namespaces = $wgContLang->getNamespaces(); $res = null; $n = 0; $nextRow = null; if (!$prefixList || !$fromList) { $out = $this->msg('allpagesbadtitle')->parseAsBlock(); } elseif (!array_key_exists($namespace, $namespaces)) { // Show errormessage and reset to NS_MAIN $out = $this->msg('allpages-bad-ns', $namespace)->parse(); $namespace = NS_MAIN; } else { list($namespace, $prefixKey, $prefix) = $prefixList; list(, $fromKey, ) = $fromList; # ## @todo FIXME: Should complain if $fromNs != $namespace $dbr = wfGetDB(DB_REPLICA); $conds = ['page_namespace' => $namespace, 'page_title' . $dbr->buildLike($prefixKey, $dbr->anyString()), 'page_title >= ' . $dbr->addQuotes($fromKey)]; if ($this->hideRedirects) { $conds['page_is_redirect'] = 0; } $res = $dbr->select('page', array_merge(['page_namespace', 'page_title'], LinkCache::getSelectFields()), $conds, __METHOD__, ['ORDER BY' => 'page_title', 'LIMIT' => $this->maxPerPage + 1, 'USE INDEX' => 'name_title']); // @todo FIXME: Side link to previous if ($res->numRows() > 0) { $out = Html::openElement('ul', ['class' => 'mw-prefixindex-list']); $linkCache = MediaWikiServices::getInstance()->getLinkCache(); $prefixLength = strlen($prefix); foreach ($res as $row) { if ($n >= $this->maxPerPage) { $nextRow = $row; break; } $title = Title::newFromRow($row); // Make sure it gets into LinkCache $linkCache->addGoodLinkObjFromRow($title, $row); $displayed = $title->getText(); // Try not to generate unclickable links if ($this->stripPrefix && $prefixLength !== strlen($displayed)) { $displayed = substr($displayed, $prefixLength); } $link = ($title->isRedirect() ? '<div class="allpagesredirect">' : '') . Linker::linkKnown($title, htmlspecialchars($displayed)) . ($title->isRedirect() ? '</div>' : ''); $out .= "<li>{$link}</li>\n"; $n++; } $out .= Html::closeElement('ul'); if ($res->numRows() > 2) { // Only apply CSS column styles if there's more than 2 entries. // Otherwise rendering is broken as "mw-prefixindex-body"'s CSS column count is 3. $out = Html::rawElement('div', ['class' => 'mw-prefixindex-body'], $out); } } else { $out = ''; } } $output = $this->getOutput(); if ($this->including()) { // We don't show the nav-links and the form when included into other // pages so let's just finish here. $output->addHTML($out); return; } $topOut = $this->namespacePrefixForm($namespace, $prefix); if ($res && $n == $this->maxPerPage && $nextRow) { $query = ['from' => $nextRow->page_title, 'prefix' => $prefix, 'hideredirects' => $this->hideRedirects, 'stripprefix' => $this->stripPrefix]; if ($namespace || $prefix == '') { // Keep the namespace even if it's 0 for empty prefixes. // This tells us we're not just a holdover from old links. $query['namespace'] = $namespace; } $nextLink = Linker::linkKnown($this->getPageTitle(), $this->msg('nextpage', str_replace('_', ' ', $nextRow->page_title))->escaped(), [], $query); // Link shown at the top of the page below the form $topOut .= Html::rawElement('div', ['class' => 'mw-prefixindex-nav'], $nextLink); // Link shown at the footer $out .= "\n" . Html::element('hr') . Html::rawElement('div', ['class' => 'mw-prefixindex-nav'], $nextLink); } $output->addHTML($topOut . $out); }
/** * Perform the existence test query, return a ResultWrapper with page_id fields * @return bool|ResultWrapper */ public function doQuery() { if ($this->isEmpty()) { return false; } // This is similar to LinkHolderArray::replaceInternal $dbr = wfGetDB(DB_SLAVE); $table = 'page'; $fields = array_merge(LinkCache::getSelectFields(), ['page_namespace', 'page_title']); $conds = $this->constructSet('page', $dbr); // Do query $caller = __METHOD__; if (strval($this->caller) !== '') { $caller .= " (for {$this->caller})"; } $res = $dbr->select($table, $fields, $conds, $caller); return $res; }
function doCategoryQuery() { $dbr = wfGetDB(DB_REPLICA, 'category'); $this->nextPage = ['page' => null, 'subcat' => null, 'file' => null]; $this->prevPage = ['page' => null, 'subcat' => null, 'file' => null]; $this->flip = ['page' => false, 'subcat' => false, 'file' => false]; foreach (['page', 'subcat', 'file'] as $type) { # Get the sortkeys for start/end, if applicable. Note that if # the collation in the database differs from the one # set in $wgCategoryCollation, pagination might go totally haywire. $extraConds = ['cl_type' => $type]; if (isset($this->from[$type]) && $this->from[$type] !== null) { $extraConds[] = 'cl_sortkey >= ' . $dbr->addQuotes($this->collation->getSortKey($this->from[$type])); } elseif (isset($this->until[$type]) && $this->until[$type] !== null) { $extraConds[] = 'cl_sortkey < ' . $dbr->addQuotes($this->collation->getSortKey($this->until[$type])); $this->flip[$type] = true; } $res = $dbr->select(['page', 'categorylinks', 'category'], array_merge(LinkCache::getSelectFields(), ['page_namespace', 'page_title', 'cl_sortkey', 'cat_id', 'cat_title', 'cat_subcats', 'cat_pages', 'cat_files', 'cl_sortkey_prefix', 'cl_collation']), array_merge(['cl_to' => $this->title->getDBkey()], $extraConds), __METHOD__, ['USE INDEX' => ['categorylinks' => 'cl_sortkey'], 'LIMIT' => $this->limit + 1, 'ORDER BY' => $this->flip[$type] ? 'cl_sortkey DESC' : 'cl_sortkey'], ['categorylinks' => ['INNER JOIN', 'cl_from = page_id'], 'category' => ['LEFT JOIN', ['cat_title = page_title', 'page_namespace' => NS_CATEGORY]]]); Hooks::run('CategoryViewer::doCategoryQuery', [$type, $res]); $linkCache = MediaWikiServices::getInstance()->getLinkCache(); $count = 0; foreach ($res as $row) { $title = Title::newFromRow($row); $linkCache->addGoodLinkObjFromRow($title, $row); if ($row->cl_collation === '') { // Hack to make sure that while updating from 1.16 schema // and db is inconsistent, that the sky doesn't fall. // See r83544. Could perhaps be removed in a couple decades... $humanSortkey = $row->cl_sortkey; } else { $humanSortkey = $title->getCategorySortkey($row->cl_sortkey_prefix); } if (++$count > $this->limit) { # We've reached the one extra which shows that there # are additional pages to be had. Stop here... $this->nextPage[$type] = $humanSortkey; break; } if ($count == $this->limit) { $this->prevPage[$type] = $humanSortkey; } if ($title->getNamespace() == NS_CATEGORY) { $cat = Category::newFromRow($row, $title); $this->addSubcategoryObject($cat, $humanSortkey, $row->page_len); } elseif ($title->getNamespace() == NS_FILE) { $this->addImage($title, $humanSortkey, $row->page_len, $row->page_is_redirect); } else { $this->addPage($title, $humanSortkey, $row->page_len, $row->page_is_redirect); } } } }