/** * Get the backlinks for a given table. Cached in process memory only. * @param $table String * @param $startId Integer or false * @param $endId Integer or false * @return TitleArray */ public function getLinks($table, $startId = false, $endId = false) { wfProfileIn(__METHOD__); $fromField = $this->getPrefix($table) . '_from'; if ($startId || $endId) { // Partial range, not cached wfDebug(__METHOD__ . ": from DB (uncacheable range)\n"); $conds = $this->getConditions($table); // Use the from field in the condition rather than the joined page_id, // because databases are stupid and don't necessarily propagate indexes. if ($startId) { $conds[] = "{$fromField} >= " . intval($startId); } if ($endId) { $conds[] = "{$fromField} <= " . intval($endId); } $res = $this->getDB()->select(array($table, 'page'), array('page_namespace', 'page_title', 'page_id'), $conds, __METHOD__, array('STRAIGHT_JOIN', 'ORDER BY' => $fromField)); $ta = TitleArray::newFromResult($res); wfProfileOut(__METHOD__); return $ta; } if (!isset($this->fullResultCache[$table])) { wfDebug(__METHOD__ . ": from DB\n"); $res = $this->getDB()->select(array($table, 'page'), array('page_namespace', 'page_title', 'page_id'), $this->getConditions($table), __METHOD__, array('STRAIGHT_JOIN', 'ORDER BY' => $fromField)); $this->fullResultCache[$table] = $res; } $ta = TitleArray::newFromResult($this->fullResultCache[$table]); wfProfileOut(__METHOD__); return $ta; }
/** * Fetch a TitleArray of up to $limit category members, beginning after the * category sort key $offset. * @param $limit integer * @param $offset string * @return TitleArray object for category members. */ public function getMembers($limit = false, $offset = '') { $dbr = wfGetDB(DB_SLAVE); $conds = array('cl_to' => $this->getName(), 'cl_from = page_id'); $options = array('ORDER BY' => 'cl_sortkey'); if ($limit) { $options['LIMIT'] = $limit; } if ($offset !== '') { $conds[] = 'cl_sortkey > ' . $dbr->addQuotes($offset); } return TitleArray::newFromResult($dbr->select(array('page', 'categorylinks'), array('page_id', 'page_namespace', 'page_title', 'page_len', 'page_is_redirect', 'page_latest'), $conds, __METHOD__, $options)); }
/** * Returns a list of categories this page is a member of. * Results will include hidden categories * * @return TitleArray */ public function getCategories() { $id = $this->getId(); if ($id == 0) { return TitleArray::newFromResult(new FakeResultWrapper([])); } $dbr = wfGetDB(DB_REPLICA); $res = $dbr->select('categorylinks', ['cl_to AS page_title, ' . NS_CATEGORY . ' AS page_namespace'], ['cl_from' => $id], __METHOD__); return TitleArray::newFromResult($res); }
/** * Get the categories this file is a member of on the wiki where it was uploaded. * For local files, this is the same as getCategories(). * For foreign API files (InstantCommons), this is not supported currently. * Results will include hidden categories. * * @return TitleArray|Title[] * @since 1.23 */ public function getForeignCategories() { $this->loadFile(); $title = $this->mTitle; $file = $this->mFile; if (!$file instanceof LocalFile) { wfDebug(__CLASS__ . '::' . __METHOD__ . " is not supported for this file\n"); return TitleArray::newFromResult(new FakeResultWrapper([])); } /** @var LocalRepo $repo */ $repo = $file->getRepo(); $dbr = $repo->getSlaveDB(); $res = $dbr->select(['page', 'categorylinks'], ['page_title' => 'cl_to', 'page_namespace' => NS_CATEGORY], ['page_namespace' => $title->getNamespace(), 'page_title' => $title->getDBkey()], __METHOD__, [], ['categorylinks' => ['INNER JOIN', 'page_id = cl_from']]); return TitleArray::newFromResult($res); }
/** * Get the backlinks for a given table. Cached in process memory only. * @param string $table * @param int|bool $startId * @param int|bool $endId * @param int|INF $max * @return TitleArrayFromResult */ public function getLinks($table, $startId = false, $endId = false, $max = INF) { return TitleArray::newFromResult($this->queryLinks($table, $startId, $endId, $max)); }
function doSubmit() { global $wgOut, $wgUser, $wgMaximumMovedPages, $wgLang; global $wgFixDoubleRedirects; if ($wgUser->pingLimiter('move')) { $wgOut->rateLimited(); return; } $ot = $this->oldTitle; $nt = $this->newTitle; # Delete to make way if requested if ($wgUser->isAllowed('delete') && $this->deleteAndMove) { $article = new Article($nt); # Disallow deletions of big articles $bigHistory = $article->isBigDeletion(); if ($bigHistory && !$nt->userCan('bigdelete')) { global $wgDeleteRevisionsLimit; $this->showForm(array('delete-toobig', $wgLang->formatNum($wgDeleteRevisionsLimit))); return; } // Delete an associated image if there is $file = wfLocalFile($nt); if ($file->exists()) { $file->delete(wfMsgForContent('delete_and_move_reason'), false); } // This may output an error message and exit $article->doDelete(wfMsgForContent('delete_and_move_reason')); } # don't allow moving to pages with # in if (!$nt || $nt->getFragment() != '') { $this->showForm('badtitletext'); return; } # Show a warning if the target file exists on a shared repo if ($nt->getNamespace() == NS_FILE && !($this->moveOverShared && $wgUser->isAllowed('reupload-shared')) && !RepoGroup::singleton()->getLocalRepo()->findFile($nt) && wfFindFile($nt)) { $this->showForm(array('file-exists-sharedrepo')); return; } if ($wgUser->isAllowed('suppressredirect')) { $createRedirect = $this->leaveRedirect; } else { $createRedirect = true; } # Do the actual move. $error = $ot->moveTo($nt, true, $this->reason, $createRedirect); if ($error !== true) { # @todo FIXME: Show all the errors in a list, not just the first one $this->showForm(reset($error)); return; } if ($wgFixDoubleRedirects && $this->fixRedirects) { DoubleRedirectJob::fixRedirects('move', $ot, $nt); } wfRunHooks('SpecialMovepageAfterMove', array(&$this, &$ot, &$nt)); $wgOut->setPagetitle(wfMsg('pagemovedsub')); $oldUrl = $ot->getFullUrl('redirect=no'); $newUrl = $nt->getFullUrl(); $oldText = $ot->getPrefixedText(); $newText = $nt->getPrefixedText(); $oldLink = "<span class='plainlinks'>[{$oldUrl} {$oldText}]</span>"; $newLink = "<span class='plainlinks'>[{$newUrl} {$newText}]</span>"; $msgName = $createRedirect ? 'movepage-moved-redirect' : 'movepage-moved-noredirect'; $wgOut->addWikiMsg('movepage-moved', $oldLink, $newLink, $oldText, $newText); $wgOut->addWikiMsg($msgName); # Now we move extra pages we've been asked to move: subpages and talk # pages. First, if the old page or the new page is a talk page, we # can't move any talk pages: cancel that. if ($ot->isTalkPage() || $nt->isTalkPage()) { $this->moveTalk = false; } if (!$ot->userCan('move-subpages')) { $this->moveSubpages = false; } # Next make a list of id's. This might be marginally less efficient # than a more direct method, but this is not a highly performance-cri- # tical code path and readable code is more important here. # # Note: this query works nicely on MySQL 5, but the optimizer in MySQL # 4 might get confused. If so, consider rewriting as a UNION. # # If the target namespace doesn't allow subpages, moving with subpages # would mean that you couldn't move them back in one operation, which # is bad. # @todo FIXME: A specific error message should be given in this case. // @todo FIXME: Use Title::moveSubpages() here $dbr = wfGetDB(DB_MASTER); if ($this->moveSubpages && (MWNamespace::hasSubpages($nt->getNamespace()) || $this->moveTalk && MWNamespace::hasSubpages($nt->getTalkPage()->getNamespace()))) { $conds = array('page_title' . $dbr->buildLike($ot->getDBkey() . '/', $dbr->anyString()) . ' OR page_title = ' . $dbr->addQuotes($ot->getDBkey())); $conds['page_namespace'] = array(); if (MWNamespace::hasSubpages($nt->getNamespace())) { $conds['page_namespace'][] = $ot->getNamespace(); } if ($this->moveTalk && MWNamespace::hasSubpages($nt->getTalkPage()->getNamespace())) { $conds['page_namespace'][] = $ot->getTalkPage()->getNamespace(); } } elseif ($this->moveTalk) { $conds = array('page_namespace' => $ot->getTalkPage()->getNamespace(), 'page_title' => $ot->getDBkey()); } else { # Skip the query $conds = null; } $extraPages = array(); if (!is_null($conds)) { $extraPages = TitleArray::newFromResult($dbr->select('page', array('page_id', 'page_namespace', 'page_title'), $conds, __METHOD__)); } $extraOutput = array(); $skin = $this->getSkin(); $count = 1; foreach ($extraPages as $oldSubpage) { if ($ot->equals($oldSubpage)) { # Already did this one. continue; } $newPageName = preg_replace('#^' . preg_quote($ot->getDBkey(), '#') . '#', StringUtils::escapeRegexReplacement($nt->getDBkey()), $oldSubpage->getDBkey()); if ($oldSubpage->isTalkPage()) { $newNs = $nt->getTalkPage()->getNamespace(); } else { $newNs = $nt->getSubjectPage()->getNamespace(); } # Bug 14385: we need makeTitleSafe because the new page names may # be longer than 255 characters. $newSubpage = Title::makeTitleSafe($newNs, $newPageName); if (!$newSubpage) { $oldLink = $skin->linkKnown($oldSubpage); $extraOutput[] = wfMsgHtml('movepage-page-unmoved', $oldLink, htmlspecialchars(Title::makeName($newNs, $newPageName))); continue; } # This was copy-pasted from Renameuser, bleh. if ($newSubpage->exists() && !$oldSubpage->isValidMoveTarget($newSubpage)) { $link = $skin->linkKnown($newSubpage); $extraOutput[] = wfMsgHtml('movepage-page-exists', $link); } else { $success = $oldSubpage->moveTo($newSubpage, true, $this->reason, $createRedirect); if ($success === true) { if ($this->fixRedirects) { DoubleRedirectJob::fixRedirects('move', $oldSubpage, $newSubpage); } $oldLink = $skin->linkKnown($oldSubpage, null, array(), array('redirect' => 'no')); $newLink = $skin->linkKnown($newSubpage); $extraOutput[] = wfMsgHtml('movepage-page-moved', $oldLink, $newLink); ++$count; if ($count >= $wgMaximumMovedPages) { $extraOutput[] = wfMsgExt('movepage-max-pages', array('parsemag', 'escape'), $wgLang->formatNum($wgMaximumMovedPages)); break; } } else { $oldLink = $skin->linkKnown($oldSubpage); $newLink = $skin->link($newSubpage); $extraOutput[] = wfMsgHtml('movepage-page-unmoved', $oldLink, $newLink); } } } if ($extraOutput !== array()) { $wgOut->addHTML("<ul>\n<li>" . implode("</li>\n<li>", $extraOutput) . "</li>\n</ul>"); } # Deal with watches (we don't watch subpages) if ($this->watch && $wgUser->isLoggedIn()) { $wgUser->addWatch($ot); $wgUser->addWatch($nt); } else { $wgUser->removeWatch($ot); $wgUser->removeWatch($nt); } # Re-clear the file redirect cache, which may have been polluted by # parsing in messages above. See CR r56745. # @todo FIXME: Needs a more robust solution inside FileRepo. if ($ot->getNamespace() == NS_FILE) { RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect($ot); } }
function doSubmit() { $user = $this->getUser(); if ($user->pingLimiter('move')) { throw new ThrottledError(); } $ot = $this->oldTitle; $nt = $this->newTitle; # don't allow moving to pages with # in if (!$nt || $nt->hasFragment()) { $this->showForm(array(array('badtitletext'))); return; } # Show a warning if the target file exists on a shared repo if ($nt->getNamespace() == NS_FILE && !($this->moveOverShared && $user->isAllowed('reupload-shared')) && !RepoGroup::singleton()->getLocalRepo()->findFile($nt) && wfFindFile($nt)) { $this->showForm(array(array('file-exists-sharedrepo'))); return; } # Delete to make way if requested if ($this->deleteAndMove) { $permErrors = $nt->getUserPermissionsErrors('delete', $user); if (count($permErrors)) { # Only show the first error $this->showForm($permErrors); return; } $reason = $this->msg('delete_and_move_reason', $ot)->inContentLanguage()->text(); // Delete an associated image if there is if ($nt->getNamespace() == NS_FILE) { $file = wfLocalFile($nt); $file->load(File::READ_LATEST); if ($file->exists()) { $file->delete($reason, false, $user); } } $error = ''; // passed by ref $page = WikiPage::factory($nt); $deleteStatus = $page->doDeleteArticleReal($reason, false, 0, true, $error, $user); if (!$deleteStatus->isGood()) { $this->showForm($deleteStatus->getErrorsArray()); return; } } $handler = ContentHandler::getForTitle($ot); if (!$handler->supportsRedirects()) { $createRedirect = false; } elseif ($user->isAllowed('suppressredirect')) { $createRedirect = $this->leaveRedirect; } else { $createRedirect = true; } # Do the actual move. $mp = new MovePage($ot, $nt); $valid = $mp->isValidMove(); if (!$valid->isOK()) { $this->showForm($valid->getErrorsArray()); return; } $permStatus = $mp->checkPermissions($user, $this->reason); if (!$permStatus->isOK()) { $this->showForm($permStatus->getErrorsArray()); return; } $status = $mp->move($user, $this->reason, $createRedirect); if (!$status->isOK()) { $this->showForm($status->getErrorsArray()); return; } if ($this->getConfig()->get('FixDoubleRedirects') && $this->fixRedirects) { DoubleRedirectJob::fixRedirects('move', $ot, $nt); } $out = $this->getOutput(); $out->setPageTitle($this->msg('pagemovedsub')); $oldLink = Linker::link($ot, null, array('id' => 'movepage-oldlink'), array('redirect' => 'no')); $newLink = Linker::linkKnown($nt, null, array('id' => 'movepage-newlink')); $oldText = $ot->getPrefixedText(); $newText = $nt->getPrefixedText(); if ($ot->exists()) { // NOTE: we assume that if the old title exists, it's because it was re-created as // a redirect to the new title. This is not safe, but what we did before was // even worse: we just determined whether a redirect should have been created, // and reported that it was created if it should have, without any checks. // Also note that isRedirect() is unreliable because of bug 37209. $msgName = 'movepage-moved-redirect'; } else { $msgName = 'movepage-moved-noredirect'; } $out->addHTML($this->msg('movepage-moved')->rawParams($oldLink, $newLink)->params($oldText, $newText)->parseAsBlock()); $out->addWikiMsg($msgName); Hooks::run('SpecialMovepageAfterMove', array(&$this, &$ot, &$nt)); # Now we move extra pages we've been asked to move: subpages and talk # pages. First, if the old page or the new page is a talk page, we # can't move any talk pages: cancel that. if ($ot->isTalkPage() || $nt->isTalkPage()) { $this->moveTalk = false; } if (count($ot->getUserPermissionsErrors('move-subpages', $user))) { $this->moveSubpages = false; } # Next make a list of id's. This might be marginally less efficient # than a more direct method, but this is not a highly performance-cri- # tical code path and readable code is more important here. # # Note: this query works nicely on MySQL 5, but the optimizer in MySQL # 4 might get confused. If so, consider rewriting as a UNION. # # If the target namespace doesn't allow subpages, moving with subpages # would mean that you couldn't move them back in one operation, which # is bad. # @todo FIXME: A specific error message should be given in this case. // @todo FIXME: Use Title::moveSubpages() here $dbr = wfGetDB(DB_MASTER); if ($this->moveSubpages && (MWNamespace::hasSubpages($nt->getNamespace()) || $this->moveTalk && MWNamespace::hasSubpages($nt->getTalkPage()->getNamespace()))) { $conds = array('page_title' . $dbr->buildLike($ot->getDBkey() . '/', $dbr->anyString()) . ' OR page_title = ' . $dbr->addQuotes($ot->getDBkey())); $conds['page_namespace'] = array(); if (MWNamespace::hasSubpages($nt->getNamespace())) { $conds['page_namespace'][] = $ot->getNamespace(); } if ($this->moveTalk && MWNamespace::hasSubpages($nt->getTalkPage()->getNamespace())) { $conds['page_namespace'][] = $ot->getTalkPage()->getNamespace(); } } elseif ($this->moveTalk) { $conds = array('page_namespace' => $ot->getTalkPage()->getNamespace(), 'page_title' => $ot->getDBkey()); } else { # Skip the query $conds = null; } $extraPages = array(); if (!is_null($conds)) { $extraPages = TitleArray::newFromResult($dbr->select('page', array('page_id', 'page_namespace', 'page_title'), $conds, __METHOD__)); } $extraOutput = array(); $count = 1; foreach ($extraPages as $oldSubpage) { if ($ot->equals($oldSubpage) || $nt->equals($oldSubpage)) { # Already did this one. continue; } $newPageName = preg_replace('#^' . preg_quote($ot->getDBkey(), '#') . '#', StringUtils::escapeRegexReplacement($nt->getDBkey()), $oldSubpage->getDBkey()); if ($oldSubpage->isSubpage() && ($ot->isTalkPage() xor $nt->isTalkPage())) { // Moving a subpage from a subject namespace to a talk namespace or vice-versa $newNs = $nt->getNamespace(); } elseif ($oldSubpage->isTalkPage()) { $newNs = $nt->getTalkPage()->getNamespace(); } else { $newNs = $nt->getSubjectPage()->getNamespace(); } # Bug 14385: we need makeTitleSafe because the new page names may # be longer than 255 characters. $newSubpage = Title::makeTitleSafe($newNs, $newPageName); if (!$newSubpage) { $oldLink = Linker::linkKnown($oldSubpage); $extraOutput[] = $this->msg('movepage-page-unmoved')->rawParams($oldLink)->params(Title::makeName($newNs, $newPageName))->escaped(); continue; } # This was copy-pasted from Renameuser, bleh. if ($newSubpage->exists() && !$oldSubpage->isValidMoveTarget($newSubpage)) { $link = Linker::linkKnown($newSubpage); $extraOutput[] = $this->msg('movepage-page-exists')->rawParams($link)->escaped(); } else { $success = $oldSubpage->moveTo($newSubpage, true, $this->reason, $createRedirect); if ($success === true) { if ($this->fixRedirects) { DoubleRedirectJob::fixRedirects('move', $oldSubpage, $newSubpage); } $oldLink = Linker::link($oldSubpage, null, array(), array('redirect' => 'no')); $newLink = Linker::linkKnown($newSubpage); $extraOutput[] = $this->msg('movepage-page-moved')->rawParams($oldLink, $newLink)->escaped(); ++$count; $maximumMovedPages = $this->getConfig()->get('MaximumMovedPages'); if ($count >= $maximumMovedPages) { $extraOutput[] = $this->msg('movepage-max-pages')->numParams($maximumMovedPages)->escaped(); break; } } else { $oldLink = Linker::linkKnown($oldSubpage); $newLink = Linker::link($newSubpage); $extraOutput[] = $this->msg('movepage-page-unmoved')->rawParams($oldLink, $newLink)->escaped(); } } } if ($extraOutput !== array()) { $out->addHTML("<ul>\n<li>" . implode("</li>\n<li>", $extraOutput) . "</li>\n</ul>"); } # Deal with watches (we don't watch subpages) WatchAction::doWatchOrUnwatch($this->watch, $ot, $user); WatchAction::doWatchOrUnwatch($this->watch, $nt, $user); }
public function getTranslationPages() { // Fetch the available translation pages from database $dbr = wfGetDB( DB_SLAVE ); $prefix = $this->getTitle()->getDBkey() . '/'; $likePattern = $dbr->buildLike( $prefix, $dbr->anyString() ); $res = $dbr->select( 'page', array( 'page_namespace', 'page_title' ), array( 'page_namespace' => $this->getTitle()->getNamespace(), "page_title $likePattern" ), __METHOD__ ); $titles = TitleArray::newFromResult( $res ); $filtered = array(); // Make sure we only get translation subpages while ignoring others $codes = Language::getLanguageNames( false ); $prefix = $this->getTitle()->getText(); foreach ( $titles as $title ) { list( $name, $code ) = TranslateUtils::figureMessage( $title->getText() ); if ( !isset( $codes[$code] ) || $name !== $prefix ) { continue; } $filtered[] = $title; } return $filtered; }
/** * @param array $pages Map of (page ID => (namespace, DB key)) entries */ protected function invalidateTitles(array $pages) { global $wgUpdateRowsPerQuery, $wgUseFileCache; // Get all page IDs in this query into an array $pageIds = array_keys($pages); if (!$pageIds) { return; } // The page_touched field will need to be bumped for these pages. // Only bump it to the present time if no "rootJobTimestamp" was known. // If it is known, it can be used instead, which avoids invalidating output // that was in fact generated *after* the relevant dependency change time // (e.g. template edit). This is particularily useful since refreshLinks jobs // save back parser output and usually run along side htmlCacheUpdate jobs; // their saved output would be invalidated by using the current timestamp. if (isset($this->params['rootJobTimestamp'])) { $touchTimestamp = $this->params['rootJobTimestamp']; } else { $touchTimestamp = wfTimestampNow(); } $dbw = wfGetDB(DB_MASTER); // Update page_touched (skipping pages already touched since the root job). // Check $wgUpdateRowsPerQuery for sanity; batch jobs are sized by that already. foreach (array_chunk($pageIds, $wgUpdateRowsPerQuery) as $batch) { $dbw->commit(__METHOD__, 'flush'); wfWaitForSlaves(); $dbw->update('page', array('page_touched' => $dbw->timestamp($touchTimestamp)), array('page_id' => $batch, "page_touched < " . $dbw->addQuotes($dbw->timestamp($touchTimestamp))), __METHOD__); } // Get the list of affected pages (races only mean something else did the purge) $titleArray = TitleArray::newFromResult($dbw->select('page', array('page_namespace', 'page_title'), array('page_id' => $pageIds, 'page_touched' => $dbw->timestamp($touchTimestamp)), __METHOD__)); // Update squid $u = SquidUpdate::newFromTitles($titleArray); $u->doUpdate(); // Update file cache if ($wgUseFileCache) { foreach ($titleArray as $title) { HTMLFileCache::clearFileCache($title); } } }
/** * Get a Title iterator for cascade-protected template/file use backlinks * * @return TitleArray * @since 1.25 */ public function getCascadeProtectedLinks() { $dbr = $this->getDB(); // @todo: use UNION without breaking tests that use temp tables $resSets = array(); $resSets[] = $dbr->select(array('templatelinks', 'page_restrictions', 'page'), array('page_namespace', 'page_title', 'page_id'), array('tl_namespace' => $this->title->getNamespace(), 'tl_title' => $this->title->getDBkey(), 'tl_from = pr_page', 'pr_cascade' => 1, 'page_id = tl_from'), __METHOD__, array('DISTINCT')); if ($this->title->getNamespace() == NS_FILE) { $resSets[] = $dbr->select(array('imagelinks', 'page_restrictions', 'page'), array('page_namespace', 'page_title', 'page_id'), array('il_to' => $this->title->getDBkey(), 'il_from = pr_page', 'pr_cascade' => 1, 'page_id = il_from'), __METHOD__, array('DISTINCT')); } // Combine and de-duplicate the results $mergedRes = array(); foreach ($resSets as $res) { foreach ($res as $row) { $mergedRes[$row->page_id] = $row; } } return TitleArray::newFromResult(new FakeResultWrapper(array_values($mergedRes))); }
/** * Fetch the available translation pages from database * @return Title[] */ public function getTranslationPages() { // Avoid replication lag issues $dbr = wfGetDB(DB_MASTER); $prefix = $this->getTitle()->getDBkey() . '/'; $likePattern = $dbr->buildLike($prefix, $dbr->anyString()); $res = $dbr->select('page', array('page_namespace', 'page_title'), array('page_namespace' => $this->getTitle()->getNamespace(), "page_title {$likePattern}"), __METHOD__); $titles = TitleArray::newFromResult($res); $filtered = array(); // Make sure we only get translation subpages while ignoring others $codes = Language::fetchLanguageNames(); $prefix = $this->getTitle()->getText(); /** @var Title $title */ foreach ($titles as $title) { list($name, $code) = TranslateUtils::figureMessage($title->getText()); if (!isset($codes[$code]) || $name !== $prefix) { continue; } $filtered[] = $title; } return $filtered; }
/** * @param array $pages Map of (page ID => (namespace, DB key)) entries */ protected function invalidateTitles(array $pages) { global $wgUpdateRowsPerQuery, $wgUseFileCache; // Get all page IDs in this query into an array $pageIds = array_keys($pages); if (!$pageIds) { return; } // Bump page_touched to the current timestamp. This used to use the root job timestamp // (e.g. template/file edit time), which was a bit more efficient when template edits are // rare and don't effect the same pages much. However, this way allows for better // de-duplication, which is much more useful for wikis with high edit rates. Note that // RefreshLinksJob, which is enqueued alongside HTMLCacheUpdateJob, saves the parser output // since it has to parse anyway. We assume that vast majority of the cache jobs finish // before the link jobs, so using the current timestamp instead of the root timestamp is // not expected to invalidate these cache entries too often. $touchTimestamp = wfTimestampNow(); $dbw = wfGetDB(DB_MASTER); $factory = wfGetLBFactory(); $ticket = $factory->getEmptyTransactionTicket(__METHOD__); // Update page_touched (skipping pages already touched since the root job). // Check $wgUpdateRowsPerQuery for sanity; batch jobs are sized by that already. foreach (array_chunk($pageIds, $wgUpdateRowsPerQuery) as $batch) { $factory->commitAndWaitForReplication(__METHOD__, $ticket); $dbw->update('page', ['page_touched' => $dbw->timestamp($touchTimestamp)], ['page_id' => $batch, "page_touched < " . $dbw->addQuotes($dbw->timestamp($touchTimestamp))], __METHOD__); } // Get the list of affected pages (races only mean something else did the purge) $titleArray = TitleArray::newFromResult($dbw->select('page', ['page_namespace', 'page_title'], ['page_id' => $pageIds, 'page_touched' => $dbw->timestamp($touchTimestamp)], __METHOD__)); // Update CDN $u = CdnCacheUpdate::newFromTitles($titleArray); $u->doUpdate(); // Update file cache if ($wgUseFileCache) { foreach ($titleArray as $title) { HTMLFileCache::clearFileCache($title); } } }
/** * Unless overridden by PrefixSearchBackend hook... * This is case-sensitive (First character may * be automatically capitalized by Title::secureAndSpit() * later on depending on $wgCapitalLinks) * * @param array|null $namespaces Namespaces to search in * @param string $search Term * @param int $limit Max number of items to return * @param int $offset Number of items to skip * @return Title[] Array of Title objects */ public function defaultSearchBackend($namespaces, $search, $limit, $offset) { // Backwards compatability with old code. Default to NS_MAIN if no namespaces provided. if ($namespaces === null) { $namespaces = []; } if (!$namespaces) { $namespaces[] = NS_MAIN; } // Construct suitable prefix for each namespace. They differ in cases where // some namespaces always capitalize and some don't. $prefixes = []; foreach ($namespaces as $namespace) { // For now, if special is included, ignore the other namespaces if ($namespace == NS_SPECIAL) { return $this->specialSearch($search, $limit, $offset); } $title = Title::makeTitleSafe($namespace, $search); // Why does the prefix default to empty? $prefix = $title ? $title->getDBkey() : ''; $prefixes[$prefix][] = $namespace; } $dbr = wfGetDB(DB_REPLICA); // Often there is only one prefix that applies to all requested namespaces, // but sometimes there are two if some namespaces do not always capitalize. $conds = []; foreach ($prefixes as $prefix => $namespaces) { $condition = ['page_namespace' => $namespaces, 'page_title' . $dbr->buildLike($prefix, $dbr->anyString())]; $conds[] = $dbr->makeList($condition, LIST_AND); } $table = 'page'; $fields = ['page_id', 'page_namespace', 'page_title']; $conds = $dbr->makeList($conds, LIST_OR); $options = ['LIMIT' => $limit, 'ORDER BY' => ['page_title', 'page_namespace'], 'OFFSET' => $offset]; $res = $dbr->select($table, $fields, $conds, __METHOD__, $options); return iterator_to_array(TitleArray::newFromResult($res)); }
/** * Get all subpages of this page. * * @param int $limit Maximum number of subpages to fetch; -1 for no limit * @return TitleArray|array TitleArray, or empty array if this page's namespace * doesn't allow subpages */ public function getSubpages($limit = -1) { if (!MWNamespace::hasSubpages($this->getNamespace())) { return array(); } $dbr = wfGetDB(DB_SLAVE); $conds['page_namespace'] = $this->getNamespace(); $conds[] = 'page_title ' . $dbr->buildLike($this->getDBkey() . '/', $dbr->anyString()); $options = array(); if ($limit > -1) { $options['LIMIT'] = $limit; } $this->mSubpages = TitleArray::newFromResult($dbr->select('page', array('page_id', 'page_namespace', 'page_title', 'page_is_redirect'), $conds, __METHOD__, $options)); return $this->mSubpages; }
/** * Returns all section pages, including those which are currently not active. * @return TitleArray. */ protected function getSectionPages() { if ( !isset( $this->sectionPages ) ) { $base = $this->page->getTitle()->getPrefixedDBKey(); $dbw = wfGetDB( DB_MASTER ); if ( $this->singleLanguage() ) { $like = $dbw->buildLike( "$base/", $dbw->anyString(), "/{$this->code}" ); } else { $like = $dbw->buildLike( "$base/", $dbw->anyString() ); } $fields = array( 'page_namespace', 'page_title' ); $titleCond = 'page_title ' . $like; $conds = array( 'page_namespace' => NS_TRANSLATIONS, $titleCond ); $result = $dbw->select( 'page', $fields, $conds, __METHOD__ ); $this->sectionPages = TitleArray::newFromResult( $result ); } return $this->sectionPages; }
/** * Returns a list of categories this page is a member of. * Results will include hidden categories * * @return TitleArray */ public function getCategories() { $id = $this->getId(); if ( $id == 0 ) { return TitleArray::newFromResult( new FakeResultWrapper( array() ) ); } $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( 'categorylinks', array( 'cl_to AS page_title, ' . NS_CATEGORY . ' AS page_namespace' ), // Have to do that since DatabaseBase::fieldNamesWithAlias treats numeric indexes // as not being aliases, and NS_CATEGORY is numeric array( 'cl_from' => $id ), __METHOD__ ); return TitleArray::newFromResult( $res ); }
/** * Returns a title iterator with all the subpages of the base page * for the provided title. This will include the provided title itself, * unless the provided title is a base page. * * @since 0.3 * * @param string $baseTitle * @param integer $ns * * @return TitleArray */ protected static function getBaseSubPages( $baseTitle, $ns ) { $dbr = wfGetDB( DB_SLAVE ); $titleArray = TitleArray::newFromResult( $dbr->select( 'page', array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ), array( 'page_namespace' => $ns, 'page_title' => $dbr->buildLike( $baseTitle . '/', $dbr->anyString() ) ), __METHOD__, array( 'LIMIT' => 500 ) ) ); return $titleArray; }