function getQueryInfo() { $dbr = wfGetDB(DB_SLAVE); $dMsgText = wfMsgForContent('disambiguationspage'); $linkBatch = new LinkBatch(); # If the text can be treated as a title, use it verbatim. # Otherwise, pull the titles from the links table $dp = Title::newFromText($dMsgText); if ($dp) { if ($dp->getNamespace() != NS_TEMPLATE) { # @todo FIXME: We assume the disambiguation message is a template but # the page can potentially be from another namespace :/ wfDebug("Mediawiki:disambiguationspage message does not refer to a template!\n"); } $linkBatch->addObj($dp); } else { # Get all the templates linked from the Mediawiki:Disambiguationspage $disPageObj = Title::makeTitleSafe(NS_MEDIAWIKI, 'disambiguationspage'); $res = $dbr->select(array('pagelinks', 'page'), 'pl_title', array('page_id = pl_from', 'pl_namespace' => NS_TEMPLATE, 'page_namespace' => $disPageObj->getNamespace(), 'page_title' => $disPageObj->getDBkey()), __METHOD__); foreach ($res as $row) { $linkBatch->addObj(Title::makeTitle(NS_TEMPLATE, $row->pl_title)); } } $set = $linkBatch->constructSet('tl', $dbr); if ($set === false) { # We must always return a valid SQL query, but this way # the DB will always quickly return an empty result $set = 'FALSE'; wfDebug("Mediawiki:disambiguationspage message does not link to any templates!\n"); } // @todo FIXME: What are pagelinks and p2 doing here? return array('tables' => array('templatelinks', 'p1' => 'page', 'pagelinks', 'p2' => 'page'), 'fields' => array('p1.page_namespace AS namespace', 'p1.page_title AS title', 'pl_from AS value'), 'conds' => array($set, 'p1.page_id = tl_from', 'pl_namespace = p1.page_namespace', 'pl_title = p1.page_title', 'p2.page_id = pl_from', 'p2.page_namespace' => MWNamespace::getContentNamespaces())); }
function getSQL() { $dbr =& wfGetDB(DB_SLAVE); list($page, $pagelinks, $templatelinks) = $dbr->tableNamesN('page', 'pagelinks', 'templatelinks'); $dMsgText = wfMsgForContent('disambiguationspage'); $linkBatch = new LinkBatch(); # If the text can be treated as a title, use it verbatim. # Otherwise, pull the titles from the links table $dp = Title::newFromText($dMsgText); if ($dp) { if ($dp->getNamespace() != NS_TEMPLATE) { # FIXME we assume the disambiguation message is a template but # the page can potentially be from another namespace :/ wfDebug("Mediawiki:disambiguationspage message does not refer to a template!\n"); } $linkBatch->addObj($dp); } else { # Get all the templates linked from the Mediawiki:Disambiguationspage $disPageObj = $this->getDisambiguationPageObj(); $res = $dbr->select(array('pagelinks', 'page'), 'pl_title', array('page_id = pl_from', 'pl_namespace' => NS_TEMPLATE, 'page_namespace' => $disPageObj->getNamespace(), 'page_title' => $disPageObj->getDBkey()), 'DisambiguationsPage::getSQL'); while ($row = $dbr->fetchObject($res)) { $linkBatch->addObj(Title::makeTitle(NS_TEMPLATE, $row->pl_title)); } $dbr->freeResult($res); } $set = $linkBatch->constructSet('lb.tl', $dbr); if ($set === false) { $set = 'FALSE'; # We must always return a valid sql query, but this way DB will always quicly return an empty result wfDebug("Mediawiki:disambiguationspage message does not link to any templates!\n"); } $sql = "SELECT 'Disambiguations' AS \"type\", pb.page_namespace AS namespace," . " pb.page_title AS title, la.pl_from AS value" . " FROM {$templatelinks} AS lb, {$page} AS pb, {$pagelinks} AS la, {$page} AS pa" . " WHERE {$set}" . ' AND pa.page_id = la.pl_from' . ' AND pa.page_namespace = ' . NS_MAIN . ' AND pb.page_id = lb.tl_from' . ' AND pb.page_namespace = la.pl_namespace' . ' AND pb.page_title = la.pl_title' . ' ORDER BY lb.tl_namespace, lb.tl_title'; return $sql; }
/** * Return a clause with the list of disambiguation templates. * This function was copied verbatim from specials/SpecialDisambiguations.php */ function disambiguation_templates( $dbr ) { $dMsgText = wfMsgForContent('disambiguationspage'); $linkBatch = new LinkBatch; # If the text can be treated as a title, use it verbatim. # Otherwise, pull the titles from the links table $dp = Title::newFromText($dMsgText); if( $dp ) { if($dp->getNamespace() != NS_TEMPLATE) { # FIXME we assume the disambiguation message is a template but # the page can potentially be from another namespace :/ wfDebug("Mediawiki:disambiguationspage message does not refer to a template!\n"); } $linkBatch->addObj( $dp ); } else { # Get all the templates linked from the Mediawiki:Disambiguationspage $disPageObj = Title::makeTitleSafe( NS_MEDIAWIKI, 'disambiguationspage' ); $res = $dbr->select( array('pagelinks', 'page'), 'pl_title', array('page_id = pl_from', 'pl_namespace' => NS_TEMPLATE, 'page_namespace' => $disPageObj->getNamespace(), 'page_title' => $disPageObj->getDBkey()), __METHOD__ ); foreach ( $res as $row ) { $linkBatch->addObj( Title::makeTitle( NS_TEMPLATE, $row->pl_title )); } } return $linkBatch->constructSet( 'tl', $dbr ); }
protected function reallyGetTitleMtimes(ResourceLoaderContext $context) { wfProfileIn(__METHOD__); $dbr = $this->getDB(); if (!$dbr) { // We're dealing with a subclass that doesn't have a DB wfProfileOut(__METHOD__); return array(); } $mtimes = array(); $local = array(); $byWiki = array(); $pages = $this->getPages($context); foreach ($pages as $titleText => $options) { $title = $this->createTitle($titleText, $options); if ($title instanceof GlobalTitle) { $byWiki[$title->getCityId()][] = array($title, $titleText, $options); } else { $local[] = array($title, $titleText, $options); } } if (!empty($local)) { $batch = new LinkBatch(); foreach ($local as $page) { list($title, $titleText, $options) = $page; $batch->addObj($title); } if (!$batch->isEmpty()) { $res = $dbr->select('page', array('page_namespace', 'page_title', 'page_touched'), $batch->constructSet('page', $dbr), __METHOD__); foreach ($res as $row) { $title = Title::makeTitle($row->page_namespace, $row->page_title); $mtimes[$title->getPrefixedDBkey()] = wfTimestamp(TS_UNIX, $row->page_touched); } } } foreach ($byWiki as $cityId => $pages) { // $pages[0][0] has to be GlobalTitle $dbName = $pages[0][0]->getDatabaseName(); $dbr = wfGetDB(DB_SLAVE, array(), $dbName); $pagesData = array(); foreach ($pages as $page) { list($title, $titleText, $options) = $page; /** @var $title GlobalTitle */ $pagesData[$title->getNamespace()][$title->getDBkey()] = true; } $res = $dbr->select('page', array('page_namespace', 'page_title', 'page_touched'), $dbr->makeWhereFrom2d($pagesData, 'page_namespace', 'page_title'), __METHOD__); foreach ($res as $row) { $title = GlobalTitle::newFromTextCached($row->page_title, $row->page_namespace, $cityId); $mtimes[$dbName . '::' . $title->getPrefixedDBkey()] = wfTimestamp(TS_UNIX, $row->page_touched); } } wfProfileOut(__METHOD__); return $mtimes; }
/** * Get the modification times of all titles that would be loaded for * a given context. * @param $context ResourceLoaderContext: Context object * @return array( prefixed DB key => UNIX timestamp ), nonexistent titles are dropped */ protected function getTitleMtimes(ResourceLoaderContext $context) { $dbr = $this->getDB(); if (!$dbr) { // We're dealing with a subclass that doesn't have a DB return array(); } $hash = $context->getHash(); if (isset($this->titleMtimes[$hash])) { return $this->titleMtimes[$hash]; } $this->titleMtimes[$hash] = array(); $batch = new LinkBatch(); foreach ($this->getPages($context) as $titleText => $options) { $batch->addObj(Title::newFromText($titleText)); } if (!$batch->isEmpty()) { $res = $dbr->select('page', array('page_namespace', 'page_title', 'page_touched'), $batch->constructSet('page', $dbr), __METHOD__); foreach ($res as $row) { $title = Title::makeTitle($row->page_namespace, $row->page_title); $this->titleMtimes[$hash][$title->getPrefixedDBkey()] = wfTimestamp(TS_UNIX, $row->page_touched); } } return $this->titleMtimes[$hash]; }
/** * Get information about watched status and put it in $this->watched */ private function getWatchedInfo() { global $wgUser; if ($wgUser->isAnon() || count($this->titles) == 0) { return; } $this->watched = array(); $db = $this->getDB(); $lb = new LinkBatch($this->titles); $this->resetQueryParams(); $this->addTables(array('page', 'watchlist')); $this->addFields(array('page_title', 'page_namespace')); $this->addWhere(array($lb->constructSet('page', $db), 'wl_namespace=page_namespace', 'wl_title=page_title', 'wl_user' => $wgUser->getID())); $res = $this->select(__METHOD__); while ($row = $db->fetchObject($res)) { $this->watched[$row->page_namespace][$row->page_title] = true; } }
public function execute() { global $wgUser; // Before doing anything at all, let's check permissions if (!$wgUser->isAllowed('deletedhistory')) { $this->dieUsage('You don\'t have permission to view deleted revision information', 'permissiondenied'); } $db = $this->getDB(); $params = $this->extractRequestParams(false); $prop = array_flip($params['prop']); $fld_revid = isset($prop['revid']); $fld_user = isset($prop['user']); $fld_comment = isset($prop['comment']); $fld_minor = isset($prop['minor']); $fld_len = isset($prop['len']); $fld_content = isset($prop['content']); $fld_token = isset($prop['token']); $result = $this->getResult(); $pageSet = $this->getPageSet(); $titles = $pageSet->getTitles(); $data = array(); $this->addTables('archive'); $this->addFields(array('ar_title', 'ar_namespace', 'ar_timestamp')); if ($fld_revid) { $this->addFields('ar_rev_id'); } if ($fld_user) { $this->addFields('ar_user_text'); } if ($fld_comment) { $this->addFields('ar_comment'); } if ($fld_minor) { $this->addFields('ar_minor_edit'); } if ($fld_len) { $this->addFields('ar_len'); } if ($fld_content) { $this->addTables('text'); $this->addFields(array('ar_text', 'ar_text_id', 'old_text', 'old_flags')); $this->addWhere('ar_text_id = old_id'); // This also means stricter restrictions if (!$wgUser->isAllowed('undelete')) { $this->dieUsage('You don\'t have permission to view deleted revision content', 'permissiondenied'); } } // Check limits $userMax = $fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1; $botMax = $fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2; if ($limit == 'max') { $limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax; $this->getResult()->addValue('limits', 'limit', $limit); } $this->validateLimit('limit', $params['limit'], 1, $userMax, $botMax); if ($fld_token) { // Undelete tokens are identical for all pages, so we cache one here $token = $wgUser->editToken(); } // We need a custom WHERE clause that matches all titles. if (count($titles) > 0) { $lb = new LinkBatch($titles); $where = $lb->constructSet('ar', $db); $this->addWhere($where); } $this->addOption('LIMIT', $params['limit'] + 1); $this->addWhereRange('ar_timestamp', $params['dir'], $params['start'], $params['end']); if (isset($params['namespace'])) { $this->addWhereFld('ar_namespace', $params['namespace']); } $res = $this->select(__METHOD__); $pages = array(); $count = 0; // First populate the $pages array while ($row = $db->fetchObject($res)) { if ($count++ == $params['limit']) { // We've had enough $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ar_timestamp)); break; } $rev = array(); $rev['timestamp'] = wfTimestamp(TS_ISO_8601, $row->ar_timestamp); if ($fld_revid) { $rev['revid'] = $row->ar_rev_id; } if ($fld_user) { $rev['user'] = $row->ar_user_text; } if ($fld_comment) { $rev['comment'] = $row->ar_comment; } if ($fld_minor) { if ($row->ar_minor_edit == 1) { $rev['minor'] = ''; } } if ($fld_len) { $rev['len'] = $row->ar_len; } if ($fld_content) { ApiResult::setContent($rev, Revision::getRevisionText($row)); } $t = Title::makeTitle($row->ar_namespace, $row->ar_title); if (!isset($pages[$t->getPrefixedText()])) { $pages[$t->getPrefixedText()] = array('title' => $t->getPrefixedText(), 'ns' => intval($row->ar_namespace), 'revisions' => array($rev)); if ($fld_token) { $pages[$t->getPrefixedText()]['token'] = $token; } } else { $pages[$t->getPrefixedText()]['revisions'][] = $rev; } } $db->freeResult($res); // We don't want entire pagenames as keys, so let's make this array indexed foreach ($pages as $page) { $result->setIndexedTagName($page['revisions'], 'rev'); $data[] = $page; } $result->setIndexedTagName($data, 'page'); $result->addValue('query', $this->getModuleName(), $data); }
protected function run(ApiPageSet $resultPageSet = null) { $user = $this->getUser(); // Before doing anything at all, let's check permissions if (!$user->isAllowed('deletedhistory')) { $this->dieUsage('You don\'t have permission to view deleted revision information', 'permissiondenied'); } $result = $this->getResult(); $pageSet = $this->getPageSet(); $pageMap = $pageSet->getGoodAndMissingTitlesByNamespace(); $pageCount = count($pageSet->getGoodAndMissingTitles()); $revCount = $pageSet->getRevisionCount(); if ($revCount === 0 && $pageCount === 0) { // Nothing to do return; } if ($revCount !== 0 && count($pageSet->getDeletedRevisionIDs()) === 0) { // Nothing to do, revisions were supplied but none are deleted return; } $params = $this->extractRequestParams(false); $db = $this->getDB(); if (!is_null($params['user']) && !is_null($params['excludeuser'])) { $this->dieUsage('user and excludeuser cannot be used together', 'badparams'); } $this->addTables('archive'); if ($resultPageSet === null) { $this->parseParameters($params); $this->addFields(Revision::selectArchiveFields()); $this->addFields(array('ar_title', 'ar_namespace')); } else { $this->limit = $this->getParameter('limit') ?: 10; $this->addFields(array('ar_title', 'ar_namespace', 'ar_timestamp', 'ar_rev_id', 'ar_id')); } if ($this->fld_tags) { $this->addTables('tag_summary'); $this->addJoinConds(array('tag_summary' => array('LEFT JOIN', array('ar_rev_id=ts_rev_id')))); $this->addFields('ts_tags'); } if (!is_null($params['tag'])) { $this->addTables('change_tag'); $this->addJoinConds(array('change_tag' => array('INNER JOIN', array('ar_rev_id=ct_rev_id')))); $this->addWhereFld('ct_tag', $params['tag']); } if ($this->fetchContent) { // Modern MediaWiki has the content for deleted revs in the 'text' // table using fields old_text and old_flags. But revisions deleted // pre-1.5 store the content in the 'archive' table directly using // fields ar_text and ar_flags, and no corresponding 'text' row. So // we have to LEFT JOIN and fetch all four fields. $this->addTables('text'); $this->addJoinConds(array('text' => array('LEFT JOIN', array('ar_text_id=old_id')))); $this->addFields(array('ar_text', 'ar_flags', 'old_text', 'old_flags')); // This also means stricter restrictions if (!$user->isAllowedAny('undelete', 'deletedtext')) { $this->dieUsage('You don\'t have permission to view deleted revision content', 'permissiondenied'); } } $dir = $params['dir']; if ($revCount !== 0) { $this->addWhere(array('ar_rev_id' => array_keys($pageSet->getDeletedRevisionIDs()))); } else { // We need a custom WHERE clause that matches all titles. $lb = new LinkBatch($pageSet->getGoodAndMissingTitles()); $where = $lb->constructSet('ar', $db); $this->addWhere($where); } if (!is_null($params['user'])) { $this->addWhereFld('ar_user_text', $params['user']); } elseif (!is_null($params['excludeuser'])) { $this->addWhere('ar_user_text != ' . $db->addQuotes($params['excludeuser'])); } if (!is_null($params['user']) || !is_null($params['excludeuser'])) { // Paranoia: avoid brute force searches (bug 17342) // (shouldn't be able to get here without 'deletedhistory', but // check it again just in case) if (!$user->isAllowed('deletedhistory')) { $bitmask = Revision::DELETED_USER; } elseif (!$user->isAllowedAny('suppressrevision', 'viewsuppressed')) { $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED; } else { $bitmask = 0; } if ($bitmask) { $this->addWhere($db->bitAnd('ar_deleted', $bitmask) . " != {$bitmask}"); } } if (!is_null($params['continue'])) { $cont = explode('|', $params['continue']); $op = $dir == 'newer' ? '>' : '<'; if ($revCount !== 0) { $this->dieContinueUsageIf(count($cont) != 2); $rev = intval($cont[0]); $this->dieContinueUsageIf(strval($rev) !== $cont[0]); $ar_id = (int) $cont[1]; $this->dieContinueUsageIf(strval($ar_id) !== $cont[1]); $this->addWhere("ar_rev_id {$op} {$rev} OR " . "(ar_rev_id = {$rev} AND " . "ar_id {$op}= {$ar_id})"); } else { $this->dieContinueUsageIf(count($cont) != 4); $ns = intval($cont[0]); $this->dieContinueUsageIf(strval($ns) !== $cont[0]); $title = $db->addQuotes($cont[1]); $ts = $db->addQuotes($db->timestamp($cont[2])); $ar_id = (int) $cont[3]; $this->dieContinueUsageIf(strval($ar_id) !== $cont[3]); $this->addWhere("ar_namespace {$op} {$ns} OR " . "(ar_namespace = {$ns} AND " . "(ar_title {$op} {$title} OR " . "(ar_title = {$title} AND " . "(ar_timestamp {$op} {$ts} OR " . "(ar_timestamp = {$ts} AND " . "ar_id {$op}= {$ar_id})))))"); } } $this->addOption('LIMIT', $this->limit + 1); if ($revCount !== 0) { // Sort by ar_rev_id when querying by ar_rev_id $this->addWhereRange('ar_rev_id', $dir, null, null); } else { // Sort by ns and title in the same order as timestamp for efficiency // But only when not already unique in the query if (count($pageMap) > 1) { $this->addWhereRange('ar_namespace', $dir, null, null); } $oneTitle = key(reset($pageMap)); foreach ($pageMap as $pages) { if (count($pages) > 1 || key($pages) !== $oneTitle) { $this->addWhereRange('ar_title', $dir, null, null); break; } } $this->addTimestampWhereRange('ar_timestamp', $dir, $params['start'], $params['end']); } // Include in ORDER BY for uniqueness $this->addWhereRange('ar_id', $dir, null, null); $res = $this->select(__METHOD__); $count = 0; $generated = array(); foreach ($res as $row) { if (++$count > $this->limit) { // We've had enough $this->setContinueEnumParameter('continue', $revCount ? "{$row->ar_rev_id}|{$row->ar_id}" : "{$row->ar_namespace}|{$row->ar_title}|{$row->ar_timestamp}|{$row->ar_id}"); break; } if ($resultPageSet !== null) { $generated[] = $row->ar_rev_id; } else { if (!isset($pageMap[$row->ar_namespace][$row->ar_title])) { // Was it converted? $title = Title::makeTitle($row->ar_namespace, $row->ar_title); $converted = $pageSet->getConvertedTitles(); if ($title && isset($converted[$title->getPrefixedText()])) { $title = Title::newFromText($converted[$title->getPrefixedText()]); if ($title && isset($pageMap[$title->getNamespace()][$title->getDBkey()])) { $pageMap[$row->ar_namespace][$row->ar_title] = $pageMap[$title->getNamespace()][$title->getDBkey()]; } } } if (!isset($pageMap[$row->ar_namespace][$row->ar_title])) { ApiBase::dieDebug("Found row in archive (ar_id={$row->ar_id}) that didn't " . "get processed by ApiPageSet"); } $fit = $this->addPageSubItem($pageMap[$row->ar_namespace][$row->ar_title], $this->extractRevisionInfo(Revision::newFromArchiveRow($row), $row), 'rev'); if (!$fit) { $this->setContinueEnumParameter('continue', $revCount ? "{$row->ar_rev_id}|{$row->ar_id}" : "{$row->ar_namespace}|{$row->ar_title}|{$row->ar_timestamp}|{$row->ar_id}"); break; } } } if ($resultPageSet !== null) { $resultPageSet->populateFromRevisionIDs($generated); } }
/** * Add an array of categories, with names in the keys */ public function addCategoryLinks($categories) { global $wgUser, $wgContLang; if (!is_array($categories) || count($categories) == 0) { return; } # Add the links to a LinkBatch $arr = array(NS_CATEGORY => $categories); $lb = new LinkBatch(); $lb->setArray($arr); # Fetch existence plus the hiddencat property $dbr = wfGetDB(DB_SLAVE); $pageTable = $dbr->tableName('page'); $where = $lb->constructSet('page', $dbr); $propsTable = $dbr->tableName('page_props'); $sql = "SELECT page_id, page_namespace, page_title, page_len, page_is_redirect, pp_value\n\t\t\tFROM {$pageTable} LEFT JOIN {$propsTable} ON pp_propname='hiddencat' AND pp_page=page_id WHERE {$where}"; $res = $dbr->query($sql, __METHOD__); # Add the results to the link cache $lb->addResultToCache(LinkCache::singleton(), $res); # Set all the values to 'normal'. This can be done with array_fill_keys in PHP 5.2.0+ $categories = array_combine(array_keys($categories), array_fill(0, count($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 (wfRunHooks('OutputPageMakeCategoryLinks', array(&$this, $categories, &$this->mCategoryLinks))) { $sk = $wgUser->getSkin(); foreach ($categories as $category => $type) { $title = Title::makeTitleSafe(NS_CATEGORY, $category); $text = $wgContLang->convertHtml($title->getText()); $this->mCategoryLinks[$type][] = $sk->makeLinkObj($title, $text); } } }
/** * Replace <!--LINK--> link placeholders with actual links, in the buffer * Placeholders created in Skin::makeLinkObj() * Returns an array of link CSS classes, indexed by PDBK. * $options is a bit field, RLH_FOR_UPDATE to select for update */ function replaceLinkHolders(&$text, $options = 0) { global $wgUser; global $wgContLang; $fname = 'Parser::replaceLinkHolders'; wfProfileIn($fname); $pdbks = array(); $colours = array(); $linkcolour_ids = array(); $sk = $this->mOptions->getSkin(); $linkCache =& LinkCache::singleton(); if (!empty($this->mLinkHolders['namespaces'])) { wfProfileIn($fname . '-check'); $dbr = wfGetDB(DB_SLAVE); $page = $dbr->tableName('page'); $threshold = $wgUser->getOption('stubthreshold'); # Sort by namespace asort($this->mLinkHolders['namespaces']); # Generate query $query = false; $current = null; foreach ($this->mLinkHolders['namespaces'] as $key => $ns) { # Make title object $title = $this->mLinkHolders['titles'][$key]; # Skip invalid entries. # Result will be ugly, but prevents crash. if (is_null($title)) { continue; } $pdbk = $pdbks[$key] = $title->getPrefixedDBkey(); # Check if it's a static known link, e.g. interwiki if ($title->isAlwaysKnown()) { $colours[$pdbk] = ''; } elseif (($id = $linkCache->getGoodLinkID($pdbk)) != 0) { $colours[$pdbk] = ''; $this->mOutput->addLink($title, $id); } elseif ($linkCache->isBadLink($pdbk)) { $colours[$pdbk] = 'new'; } elseif ($title->getNamespace() == NS_SPECIAL && !SpecialPage::exists($pdbk)) { $colours[$pdbk] = 'new'; } else { # Not in the link cache, add it to the query if (!isset($current)) { $current = $ns; $query = "SELECT page_id, page_namespace, page_title, page_is_redirect"; if ($threshold > 0) { $query .= ', page_len'; } $query .= " FROM {$page} WHERE (page_namespace={$ns} AND page_title IN("; } elseif ($current != $ns) { $current = $ns; $query .= ")) OR (page_namespace={$ns} AND page_title IN("; } else { $query .= ', '; } $query .= $dbr->addQuotes($this->mLinkHolders['dbkeys'][$key]); } } if ($query) { $query .= '))'; if ($options & RLH_FOR_UPDATE) { $query .= ' FOR UPDATE'; } $res = $dbr->query($query, $fname); # Fetch data and form into an associative array # non-existent = broken while ($s = $dbr->fetchObject($res)) { $title = Title::makeTitle($s->page_namespace, $s->page_title); $pdbk = $title->getPrefixedDBkey(); $linkCache->addGoodLinkObj($s->page_id, $title); $this->mOutput->addLink($title, $s->page_id); $colours[$pdbk] = $sk->getLinkColour($s, $threshold); //add id to the extension todolist $linkcolour_ids[$s->page_id] = $pdbk; } //pass an array of page_ids to an extension wfRunHooks('GetLinkColours', array($linkcolour_ids, &$colours)); } wfProfileOut($fname . '-check'); # Do a second query for different language variants of links and categories if ($wgContLang->hasVariants()) { $linkBatch = new LinkBatch(); $variantMap = array(); // maps $pdbkey_Variant => $keys (of link holders) $categoryMap = array(); // maps $category_variant => $category (dbkeys) $varCategories = array(); // category replacements oldDBkey => newDBkey $categories = $this->mOutput->getCategoryLinks(); // Add variants of links to link batch foreach ($this->mLinkHolders['namespaces'] as $key => $ns) { $title = $this->mLinkHolders['titles'][$key]; if (is_null($title)) { continue; } $pdbk = $title->getPrefixedDBkey(); $titleText = $title->getText(); // generate all variants of the link title text $allTextVariants = $wgContLang->convertLinkToAllVariants($titleText); // if link was not found (in first query), add all variants to query if (!isset($colours[$pdbk])) { foreach ($allTextVariants as $textVariant) { if ($textVariant != $titleText) { $variantTitle = Title::makeTitle($ns, $textVariant); if (is_null($variantTitle)) { continue; } $linkBatch->addObj($variantTitle); $variantMap[$variantTitle->getPrefixedDBkey()][] = $key; } } } } // process categories, check if a category exists in some variant foreach ($categories as $category) { $variants = $wgContLang->convertLinkToAllVariants($category); foreach ($variants as $variant) { if ($variant != $category) { $variantTitle = Title::newFromDBkey(Title::makeName(NS_CATEGORY, $variant)); if (is_null($variantTitle)) { continue; } $linkBatch->addObj($variantTitle); $categoryMap[$variant] = $category; } } } if (!$linkBatch->isEmpty()) { // construct query $titleClause = $linkBatch->constructSet('page', $dbr); $variantQuery = "SELECT page_id, page_namespace, page_title, page_is_redirect"; if ($threshold > 0) { $variantQuery .= ', page_len'; } $variantQuery .= " FROM {$page} WHERE {$titleClause}"; if ($options & RLH_FOR_UPDATE) { $variantQuery .= ' FOR UPDATE'; } $varRes = $dbr->query($variantQuery, $fname); // for each found variants, figure out link holders and replace while ($s = $dbr->fetchObject($varRes)) { $variantTitle = Title::makeTitle($s->page_namespace, $s->page_title); $varPdbk = $variantTitle->getPrefixedDBkey(); $vardbk = $variantTitle->getDBkey(); $holderKeys = array(); if (isset($variantMap[$varPdbk])) { $holderKeys = $variantMap[$varPdbk]; $linkCache->addGoodLinkObj($s->page_id, $variantTitle); $this->mOutput->addLink($variantTitle, $s->page_id); } // loop over link holders foreach ($holderKeys as $key) { $title = $this->mLinkHolders['titles'][$key]; if (is_null($title)) { continue; } $pdbk = $title->getPrefixedDBkey(); if (!isset($colours[$pdbk])) { // found link in some of the variants, replace the link holder data $this->mLinkHolders['titles'][$key] = $variantTitle; $this->mLinkHolders['dbkeys'][$key] = $variantTitle->getDBkey(); // set pdbk and colour $pdbks[$key] = $varPdbk; $colours[$varPdbk] = $sk->getLinkColour($s, $threshold); $linkcolour_ids[$s->page_id] = $pdbk; } wfRunHooks('GetLinkColours', array($linkcolour_ids, &$colours)); } // check if the object is a variant of a category if (isset($categoryMap[$vardbk])) { $oldkey = $categoryMap[$vardbk]; if ($oldkey != $vardbk) { $varCategories[$oldkey] = $vardbk; } } } // rebuild the categories in original order (if there are replacements) if (count($varCategories) > 0) { $newCats = array(); $originalCats = $this->mOutput->getCategories(); foreach ($originalCats as $cat => $sortkey) { // make the replacement if (array_key_exists($cat, $varCategories)) { $newCats[$varCategories[$cat]] = $sortkey; } else { $newCats[$cat] = $sortkey; } } $this->mOutput->setCategoryLinks($newCats); } } } # Construct search and replace arrays wfProfileIn($fname . '-construct'); $replacePairs = array(); foreach ($this->mLinkHolders['namespaces'] as $key => $ns) { $pdbk = $pdbks[$key]; $searchkey = "<!--LINK {$key}-->"; $title = $this->mLinkHolders['titles'][$key]; if (!isset($colours[$pdbk]) || $colours[$pdbk] == 'new') { $linkCache->addBadLinkObj($title); $colours[$pdbk] = 'new'; $this->mOutput->addLink($title, 0); $replacePairs[$searchkey] = $sk->makeBrokenLinkObj($title, $this->mLinkHolders['texts'][$key], $this->mLinkHolders['queries'][$key]); } else { $replacePairs[$searchkey] = $sk->makeColouredLinkObj($title, $colours[$pdbk], $this->mLinkHolders['texts'][$key], $this->mLinkHolders['queries'][$key]); } } $replacer = new HashtableReplacer($replacePairs, 1); wfProfileOut($fname . '-construct'); # Do the thing wfProfileIn($fname . '-replace'); $text = preg_replace_callback('/(<!--LINK .*?-->)/', $replacer->cb(), $text); wfProfileOut($fname . '-replace'); } # Now process interwiki link holders # This is quite a bit simpler than internal links if (!empty($this->mInterwikiLinkHolders['texts'])) { wfProfileIn($fname . '-interwiki'); # Make interwiki link HTML $replacePairs = array(); foreach ($this->mInterwikiLinkHolders['texts'] as $key => $link) { $title = $this->mInterwikiLinkHolders['titles'][$key]; $replacePairs[$key] = $sk->makeLinkObj($title, $link); } $replacer = new HashtableReplacer($replacePairs, 1); $text = preg_replace_callback('/<!--IWLINK (.*?)-->/', $replacer->cb(), $text); wfProfileOut($fname . '-interwiki'); } wfProfileOut($fname); return $colours; }
/** * Get the count of watchers who have visited recent edits and put it in * $this->visitingwatchers * * Based on InfoAction::pageCounts */ private function getVisitingWatcherInfo() { $config = $this->getConfig(); $user = $this->getUser(); $db = $this->getDB(); $canUnwatchedpages = $user->isAllowed('unwatchedpages'); $unwatchedPageThreshold = $this->getConfig()->get('UnwatchedPageThreshold'); if (!$canUnwatchedpages && !is_int($unwatchedPageThreshold)) { return; } $this->showZeroWatchers = $canUnwatchedpages; $titlesWithThresholds = []; if ($this->titles) { $lb = new LinkBatch($this->titles); // Fetch last edit timestamps for pages $this->resetQueryParams(); $this->addTables(['page', 'revision']); $this->addFields(['page_namespace', 'page_title', 'rev_timestamp']); $this->addWhere(['page_latest = rev_id', $lb->constructSet('page', $db)]); $this->addOption('GROUP BY', ['page_namespace', 'page_title']); $timestampRes = $this->select(__METHOD__); $age = $config->get('WatchersMaxAge'); $timestamps = []; foreach ($timestampRes as $row) { $revTimestamp = wfTimestamp(TS_UNIX, (int) $row->rev_timestamp); $timestamps[$row->page_namespace][$row->page_title] = $revTimestamp - $age; } $titlesWithThresholds = array_map(function (LinkTarget $target) use($timestamps) { return [$target, $timestamps[$target->getNamespace()][$target->getDBkey()]]; }, $this->titles); } if ($this->missing) { $titlesWithThresholds = array_merge($titlesWithThresholds, array_map(function (LinkTarget $target) { return [$target, null]; }, $this->missing)); } $store = MediaWikiServices::getInstance()->getWatchedItemStore(); $this->visitingwatchers = $store->countVisitingWatchersMultiple($titlesWithThresholds, !$canUnwatchedpages ? $unwatchedPageThreshold : null); }
/** * Get the information about the wiki pages for a given context. * @param ResourceLoaderContext $context * @return array Keyed by page name. Contains arrays with 'rev_len' and 'rev_sha1' keys */ protected function getTitleInfo(ResourceLoaderContext $context) { $dbr = $this->getDB(); if (!$dbr) { // We're dealing with a subclass that doesn't have a DB return array(); } $pages = $this->getPages($context); $key = implode('|', array_keys($pages)); if (!isset($this->titleInfo[$key])) { $this->titleInfo[$key] = array(); $batch = new LinkBatch(); foreach ($pages as $titleText => $options) { $batch->addObj(Title::newFromText($titleText)); } if (!$batch->isEmpty()) { $res = $dbr->select(array('page', 'revision'), array('page_namespace', 'page_title', 'rev_len', 'rev_sha1'), $batch->constructSet('page', $dbr), __METHOD__, array(), array('revision' => array('INNER JOIN', array('page_latest=rev_id')))); foreach ($res as $row) { // Avoid including ids or timestamps of revision/page tables so // that versions are not wasted $title = Title::makeTitle($row->page_namespace, $row->page_title); $this->titleInfo[$key][$title->getPrefixedText()] = array('rev_len' => $row->rev_len, 'rev_sha1' => $row->rev_sha1); } } } return $this->titleInfo[$key]; }
public function execute() { $user = $this->getUser(); // Before doing anything at all, let's check permissions if (!$user->isAllowed('deletedhistory')) { $this->dieUsage('You don\'t have permission to view deleted revision information', 'permissiondenied'); } $this->setWarning('list=deletedrevs has been deprecated. Please use prop=deletedrevisions or ' . 'list=alldeletedrevisions instead.'); $this->logFeatureUsage('action=query&list=deletedrevs'); $db = $this->getDB(); $params = $this->extractRequestParams(false); $prop = array_flip($params['prop']); $fld_parentid = isset($prop['parentid']); $fld_revid = isset($prop['revid']); $fld_user = isset($prop['user']); $fld_userid = isset($prop['userid']); $fld_comment = isset($prop['comment']); $fld_parsedcomment = isset($prop['parsedcomment']); $fld_minor = isset($prop['minor']); $fld_len = isset($prop['len']); $fld_sha1 = isset($prop['sha1']); $fld_content = isset($prop['content']); $fld_token = isset($prop['token']); $fld_tags = isset($prop['tags']); if (isset($prop['token'])) { $p = $this->getModulePrefix(); $this->setWarning("{$p}prop=token has been deprecated. Please use action=query&meta=tokens instead."); } // If we're in a mode that breaks the same-origin policy, no tokens can // be obtained if ($this->lacksSameOriginSecurity()) { $fld_token = false; } // If user can't undelete, no tokens if (!$user->isAllowed('undelete')) { $fld_token = false; } $result = $this->getResult(); $pageSet = $this->getPageSet(); $titles = $pageSet->getTitles(); // This module operates in three modes: // 'revs': List deleted revs for certain titles (1) // 'user': List deleted revs by a certain user (2) // 'all': List all deleted revs in NS (3) $mode = 'all'; if (count($titles) > 0) { $mode = 'revs'; } elseif (!is_null($params['user'])) { $mode = 'user'; } if ($mode == 'revs' || $mode == 'user') { // Ignore namespace and unique due to inability to know whether they were purposely set foreach (array('from', 'to', 'prefix') as $p) { if (!is_null($params[$p])) { $this->dieUsage("The '{$p}' parameter cannot be used in modes 1 or 2", 'badparams'); } } } else { foreach (array('start', 'end') as $p) { if (!is_null($params[$p])) { $this->dieUsage("The {$p} parameter cannot be used in mode 3", 'badparams'); } } } if (!is_null($params['user']) && !is_null($params['excludeuser'])) { $this->dieUsage('user and excludeuser cannot be used together', 'badparams'); } $this->addTables('archive'); $this->addFields(array('ar_title', 'ar_namespace', 'ar_timestamp', 'ar_deleted', 'ar_id')); $this->addFieldsIf('ar_parent_id', $fld_parentid); $this->addFieldsIf('ar_rev_id', $fld_revid); $this->addFieldsIf('ar_user_text', $fld_user); $this->addFieldsIf('ar_user', $fld_userid); $this->addFieldsIf('ar_comment', $fld_comment || $fld_parsedcomment); $this->addFieldsIf('ar_minor_edit', $fld_minor); $this->addFieldsIf('ar_len', $fld_len); $this->addFieldsIf('ar_sha1', $fld_sha1); if ($fld_tags) { $this->addTables('tag_summary'); $this->addJoinConds(array('tag_summary' => array('LEFT JOIN', array('ar_rev_id=ts_rev_id')))); $this->addFields('ts_tags'); } if (!is_null($params['tag'])) { $this->addTables('change_tag'); $this->addJoinConds(array('change_tag' => array('INNER JOIN', array('ar_rev_id=ct_rev_id')))); $this->addWhereFld('ct_tag', $params['tag']); } if ($fld_content) { // Modern MediaWiki has the content for deleted revs in the 'text' // table using fields old_text and old_flags. But revisions deleted // pre-1.5 store the content in the 'archive' table directly using // fields ar_text and ar_flags, and no corresponding 'text' row. So // we have to LEFT JOIN and fetch all four fields, plus ar_text_id // to be able to tell the difference. $this->addTables('text'); $this->addJoinConds(array('text' => array('LEFT JOIN', array('ar_text_id=old_id')))); $this->addFields(array('ar_text', 'ar_flags', 'ar_text_id', 'old_text', 'old_flags')); // This also means stricter restrictions if (!$user->isAllowedAny('undelete', 'deletedtext')) { $this->dieUsage('You don\'t have permission to view deleted revision content', 'permissiondenied'); } } // Check limits $userMax = $fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1; $botMax = $fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2; $limit = $params['limit']; if ($limit == 'max') { $limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax; $this->getResult()->addParsedLimit($this->getModuleName(), $limit); } $this->validateLimit('limit', $limit, 1, $userMax, $botMax); if ($fld_token) { // Undelete tokens are identical for all pages, so we cache one here $token = $user->getEditToken('', $this->getMain()->getRequest()); } $dir = $params['dir']; // We need a custom WHERE clause that matches all titles. if ($mode == 'revs') { $lb = new LinkBatch($titles); $where = $lb->constructSet('ar', $db); $this->addWhere($where); } elseif ($mode == 'all') { $this->addWhereFld('ar_namespace', $params['namespace']); $from = $params['from'] === null ? null : $this->titlePartToKey($params['from'], $params['namespace']); $to = $params['to'] === null ? null : $this->titlePartToKey($params['to'], $params['namespace']); $this->addWhereRange('ar_title', $dir, $from, $to); if (isset($params['prefix'])) { $this->addWhere('ar_title' . $db->buildLike($this->titlePartToKey($params['prefix'], $params['namespace']), $db->anyString())); } } if (!is_null($params['user'])) { $this->addWhereFld('ar_user_text', $params['user']); } elseif (!is_null($params['excludeuser'])) { $this->addWhere('ar_user_text != ' . $db->addQuotes($params['excludeuser'])); } if (!is_null($params['user']) || !is_null($params['excludeuser'])) { // Paranoia: avoid brute force searches (bug 17342) // (shouldn't be able to get here without 'deletedhistory', but // check it again just in case) if (!$user->isAllowed('deletedhistory')) { $bitmask = Revision::DELETED_USER; } elseif (!$user->isAllowedAny('suppressrevision', 'viewsuppressed')) { $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED; } else { $bitmask = 0; } if ($bitmask) { $this->addWhere($db->bitAnd('ar_deleted', $bitmask) . " != {$bitmask}"); } } if (!is_null($params['continue'])) { $cont = explode('|', $params['continue']); $op = $dir == 'newer' ? '>' : '<'; if ($mode == 'all' || $mode == 'revs') { $this->dieContinueUsageIf(count($cont) != 4); $ns = intval($cont[0]); $this->dieContinueUsageIf(strval($ns) !== $cont[0]); $title = $db->addQuotes($cont[1]); $ts = $db->addQuotes($db->timestamp($cont[2])); $ar_id = (int) $cont[3]; $this->dieContinueUsageIf(strval($ar_id) !== $cont[3]); $this->addWhere("ar_namespace {$op} {$ns} OR " . "(ar_namespace = {$ns} AND " . "(ar_title {$op} {$title} OR " . "(ar_title = {$title} AND " . "(ar_timestamp {$op} {$ts} OR " . "(ar_timestamp = {$ts} AND " . "ar_id {$op}= {$ar_id})))))"); } else { $this->dieContinueUsageIf(count($cont) != 2); $ts = $db->addQuotes($db->timestamp($cont[0])); $ar_id = (int) $cont[1]; $this->dieContinueUsageIf(strval($ar_id) !== $cont[1]); $this->addWhere("ar_timestamp {$op} {$ts} OR " . "(ar_timestamp = {$ts} AND " . "ar_id {$op}= {$ar_id})"); } } $this->addOption('LIMIT', $limit + 1); $this->addOption('USE INDEX', array('archive' => $mode == 'user' ? 'usertext_timestamp' : 'name_title_timestamp')); if ($mode == 'all') { if ($params['unique']) { // @todo Does this work on non-MySQL? $this->addOption('GROUP BY', 'ar_title'); } else { $sort = $dir == 'newer' ? '' : ' DESC'; $this->addOption('ORDER BY', array('ar_title' . $sort, 'ar_timestamp' . $sort, 'ar_id' . $sort)); } } else { if ($mode == 'revs') { // Sort by ns and title in the same order as timestamp for efficiency $this->addWhereRange('ar_namespace', $dir, null, null); $this->addWhereRange('ar_title', $dir, null, null); } $this->addTimestampWhereRange('ar_timestamp', $dir, $params['start'], $params['end']); // Include in ORDER BY for uniqueness $this->addWhereRange('ar_id', $dir, null, null); } $res = $this->select(__METHOD__); $pageMap = array(); // Maps ns&title to (fake) pageid $count = 0; $newPageID = 0; foreach ($res as $row) { if (++$count > $limit) { // We've had enough if ($mode == 'all' || $mode == 'revs') { $this->setContinueEnumParameter('continue', "{$row->ar_namespace}|{$row->ar_title}|{$row->ar_timestamp}|{$row->ar_id}"); } else { $this->setContinueEnumParameter('continue', "{$row->ar_timestamp}|{$row->ar_id}"); } break; } $rev = array(); $anyHidden = false; $rev['timestamp'] = wfTimestamp(TS_ISO_8601, $row->ar_timestamp); if ($fld_revid) { $rev['revid'] = intval($row->ar_rev_id); } if ($fld_parentid && !is_null($row->ar_parent_id)) { $rev['parentid'] = intval($row->ar_parent_id); } if ($fld_user || $fld_userid) { if ($row->ar_deleted & Revision::DELETED_USER) { $rev['userhidden'] = true; $anyHidden = true; } if (Revision::userCanBitfield($row->ar_deleted, Revision::DELETED_USER, $user)) { if ($fld_user) { $rev['user'] = $row->ar_user_text; } if ($fld_userid) { $rev['userid'] = $row->ar_user; } } } if ($fld_comment || $fld_parsedcomment) { if ($row->ar_deleted & Revision::DELETED_COMMENT) { $rev['commenthidden'] = true; $anyHidden = true; } if (Revision::userCanBitfield($row->ar_deleted, Revision::DELETED_COMMENT, $user)) { if ($fld_comment) { $rev['comment'] = $row->ar_comment; } if ($fld_parsedcomment) { $title = Title::makeTitle($row->ar_namespace, $row->ar_title); $rev['parsedcomment'] = Linker::formatComment($row->ar_comment, $title); } } } if ($fld_minor) { $rev['minor'] = $row->ar_minor_edit == 1; } if ($fld_len) { $rev['len'] = $row->ar_len; } if ($fld_sha1) { if ($row->ar_deleted & Revision::DELETED_TEXT) { $rev['sha1hidden'] = true; $anyHidden = true; } if (Revision::userCanBitfield($row->ar_deleted, Revision::DELETED_TEXT, $user)) { if ($row->ar_sha1 != '') { $rev['sha1'] = wfBaseConvert($row->ar_sha1, 36, 16, 40); } else { $rev['sha1'] = ''; } } } if ($fld_content) { if ($row->ar_deleted & Revision::DELETED_TEXT) { $rev['texthidden'] = true; $anyHidden = true; } if (Revision::userCanBitfield($row->ar_deleted, Revision::DELETED_TEXT, $user)) { if (isset($row->ar_text) && !$row->ar_text_id) { // Pre-1.5 ar_text row (if condition from Revision::newFromArchiveRow) ApiResult::setContentValue($rev, 'text', Revision::getRevisionText($row, 'ar_')); } else { ApiResult::setContentValue($rev, 'text', Revision::getRevisionText($row)); } } } if ($fld_tags) { if ($row->ts_tags) { $tags = explode(',', $row->ts_tags); ApiResult::setIndexedTagName($tags, 'tag'); $rev['tags'] = $tags; } else { $rev['tags'] = array(); } } if ($anyHidden && $row->ar_deleted & Revision::DELETED_RESTRICTED) { $rev['suppressed'] = true; } if (!isset($pageMap[$row->ar_namespace][$row->ar_title])) { $pageID = $newPageID++; $pageMap[$row->ar_namespace][$row->ar_title] = $pageID; $a['revisions'] = array($rev); ApiResult::setIndexedTagName($a['revisions'], 'rev'); $title = Title::makeTitle($row->ar_namespace, $row->ar_title); ApiQueryBase::addTitleInfo($a, $title); if ($fld_token) { $a['token'] = $token; } $fit = $result->addValue(array('query', $this->getModuleName()), $pageID, $a); } else { $pageID = $pageMap[$row->ar_namespace][$row->ar_title]; $fit = $result->addValue(array('query', $this->getModuleName(), $pageID, 'revisions'), null, $rev); } if (!$fit) { if ($mode == 'all' || $mode == 'revs') { $this->setContinueEnumParameter('continue', "{$row->ar_namespace}|{$row->ar_title}|{$row->ar_timestamp}|{$row->ar_id}"); } else { $this->setContinueEnumParameter('continue', "{$row->ar_timestamp}|{$row->ar_id}"); } break; } } $result->addIndexedTagName(array('query', $this->getModuleName()), 'page'); }
public function execute() { $user = $this->getUser(); // Before doing anything at all, let's check permissions if (!$user->isAllowed('deletedhistory')) { $this->dieUsage('You don\'t have permission to view deleted revision information', 'permissiondenied'); } $db = $this->getDB(); $params = $this->extractRequestParams(false); $prop = array_flip($params['prop']); $fld_parentid = isset($prop['parentid']); $fld_revid = isset($prop['revid']); $fld_user = isset($prop['user']); $fld_userid = isset($prop['userid']); $fld_comment = isset($prop['comment']); $fld_parsedcomment = isset($prop['parsedcomment']); $fld_minor = isset($prop['minor']); $fld_len = isset($prop['len']); $fld_sha1 = isset($prop['sha1']); $fld_content = isset($prop['content']); $fld_token = isset($prop['token']); $result = $this->getResult(); $pageSet = $this->getPageSet(); $titles = $pageSet->getTitles(); // This module operates in three modes: // 'revs': List deleted revs for certain titles (1) // 'user': List deleted revs by a certain user (2) // 'all': List all deleted revs in NS (3) $mode = 'all'; if (count($titles) > 0) { $mode = 'revs'; } elseif (!is_null($params['user'])) { $mode = 'user'; } if ($mode == 'revs' || $mode == 'user') { // Ignore namespace and unique due to inability to know whether they were purposely set foreach (array('from', 'to', 'prefix') as $p) { if (!is_null($params[$p])) { $this->dieUsage("The '{$p}' parameter cannot be used in modes 1 or 2", 'badparams'); } } } else { foreach (array('start', 'end') as $p) { if (!is_null($params[$p])) { $this->dieUsage("The {$p} parameter cannot be used in mode 3", 'badparams'); } } } if (!is_null($params['user']) && !is_null($params['excludeuser'])) { $this->dieUsage('user and excludeuser cannot be used together', 'badparams'); } $this->addTables('archive'); $this->addWhere('ar_deleted = 0'); $this->addFields(array('ar_title', 'ar_namespace', 'ar_timestamp')); $this->addFieldsIf('ar_parent_id', $fld_parentid); $this->addFieldsIf('ar_rev_id', $fld_revid); $this->addFieldsIf('ar_user_text', $fld_user); $this->addFieldsIf('ar_user', $fld_userid); $this->addFieldsIf('ar_comment', $fld_comment || $fld_parsedcomment); $this->addFieldsIf('ar_minor_edit', $fld_minor); $this->addFieldsIf('ar_len', $fld_len); $this->addFieldsIf('ar_sha1', $fld_sha1); if ($fld_content) { $this->addTables('text'); $this->addFields(array('ar_text', 'ar_text_id', 'old_text', 'old_flags')); $this->addWhere('ar_text_id = old_id'); // This also means stricter restrictions if (!$user->isAllowed('undelete')) { $this->dieUsage('You don\'t have permission to view deleted revision content', 'permissiondenied'); } } // Check limits $userMax = $fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1; $botMax = $fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2; $limit = $params['limit']; if ($limit == 'max') { $limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax; $this->getResult()->setParsedLimit($this->getModuleName(), $limit); } $this->validateLimit('limit', $limit, 1, $userMax, $botMax); if ($fld_token) { // Undelete tokens are identical for all pages, so we cache one here $token = $user->getEditToken('', $this->getMain()->getRequest()); } $dir = $params['dir']; // We need a custom WHERE clause that matches all titles. if ($mode == 'revs') { $lb = new LinkBatch($titles); $where = $lb->constructSet('ar', $db); $this->addWhere($where); } elseif ($mode == 'all') { $this->addWhereFld('ar_namespace', $params['namespace']); $from = is_null($params['from']) ? null : $this->titleToKey($params['from']); $to = is_null($params['to']) ? null : $this->titleToKey($params['to']); $this->addWhereRange('ar_title', $dir, $from, $to); if (isset($params['prefix'])) { $this->addWhere('ar_title' . $db->buildLike($this->titlePartToKey($params['prefix']), $db->anyString())); } } if (!is_null($params['user'])) { $this->addWhereFld('ar_user_text', $params['user']); } elseif (!is_null($params['excludeuser'])) { $this->addWhere('ar_user_text != ' . $db->addQuotes($params['excludeuser'])); } if (!is_null($params['continue']) && ($mode == 'all' || $mode == 'revs')) { $cont = explode('|', $params['continue']); $this->dieContinueUsageIf(count($cont) != 3); $ns = intval($cont[0]); $this->dieContinueUsageIf(strval($ns) !== $cont[0]); $title = $db->addQuotes($cont[1]); $ts = $db->addQuotes($db->timestamp($cont[2])); $op = $dir == 'newer' ? '>' : '<'; $this->addWhere("ar_namespace {$op} {$ns} OR " . "(ar_namespace = {$ns} AND " . "(ar_title {$op} {$title} OR " . "(ar_title = {$title} AND " . "ar_timestamp {$op}= {$ts})))"); } $this->addOption('LIMIT', $limit + 1); $this->addOption('USE INDEX', array('archive' => $mode == 'user' ? 'usertext_timestamp' : 'name_title_timestamp')); if ($mode == 'all') { if ($params['unique']) { $this->addOption('GROUP BY', 'ar_title'); } else { $sort = $dir == 'newer' ? '' : ' DESC'; $this->addOption('ORDER BY', array('ar_title' . $sort, 'ar_timestamp' . $sort)); } } else { if ($mode == 'revs') { // Sort by ns and title in the same order as timestamp for efficiency $this->addWhereRange('ar_namespace', $dir, null, null); $this->addWhereRange('ar_title', $dir, null, null); } $this->addTimestampWhereRange('ar_timestamp', $dir, $params['start'], $params['end']); } $res = $this->select(__METHOD__); $pageMap = array(); // Maps ns&title to (fake) pageid $count = 0; $newPageID = 0; foreach ($res as $row) { if (++$count > $limit) { // We've had enough if ($mode == 'all' || $mode == 'revs') { $this->setContinueEnumParameter('continue', intval($row->ar_namespace) . '|' . $row->ar_title . '|' . $row->ar_timestamp); } else { $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ar_timestamp)); } break; } $rev = array(); $rev['timestamp'] = wfTimestamp(TS_ISO_8601, $row->ar_timestamp); if ($fld_revid) { $rev['revid'] = intval($row->ar_rev_id); } if ($fld_parentid && !is_null($row->ar_parent_id)) { $rev['parentid'] = intval($row->ar_parent_id); } if ($fld_user) { $rev['user'] = $row->ar_user_text; } if ($fld_userid) { $rev['userid'] = $row->ar_user; } if ($fld_comment) { $rev['comment'] = $row->ar_comment; } $title = Title::makeTitle($row->ar_namespace, $row->ar_title); if ($fld_parsedcomment) { $rev['parsedcomment'] = Linker::formatComment($row->ar_comment, $title); } if ($fld_minor && $row->ar_minor_edit == 1) { $rev['minor'] = ''; } if ($fld_len) { $rev['len'] = $row->ar_len; } if ($fld_sha1) { if ($row->ar_sha1 != '') { $rev['sha1'] = wfBaseConvert($row->ar_sha1, 36, 16, 40); } else { $rev['sha1'] = ''; } } if ($fld_content) { ApiResult::setContent($rev, Revision::getRevisionText($row)); } if (!isset($pageMap[$row->ar_namespace][$row->ar_title])) { $pageID = $newPageID++; $pageMap[$row->ar_namespace][$row->ar_title] = $pageID; $a['revisions'] = array($rev); $result->setIndexedTagName($a['revisions'], 'rev'); ApiQueryBase::addTitleInfo($a, $title); if ($fld_token) { $a['token'] = $token; } $fit = $result->addValue(array('query', $this->getModuleName()), $pageID, $a); } else { $pageID = $pageMap[$row->ar_namespace][$row->ar_title]; $fit = $result->addValue(array('query', $this->getModuleName(), $pageID, 'revisions'), null, $rev); } if (!$fit) { if ($mode == 'all' || $mode == 'revs') { $this->setContinueEnumParameter('continue', intval($row->ar_namespace) . '|' . $row->ar_title . '|' . $row->ar_timestamp); } else { $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ar_timestamp)); } break; } } $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'page'); }
/** * Modify $this->internals and $colours according to language variant linking rules */ protected function doVariants(&$colours) { global $wgContLang; $linkBatch = new LinkBatch(); $variantMap = array(); // maps $pdbkey_Variant => $keys (of link holders) $output = $this->parent->getOutput(); $linkCache = LinkCache::singleton(); $sk = $this->parent->getOptions()->getSkin(); $threshold = $this->getStubThreshold(); // Add variants of links to link batch foreach ($this->internals as $ns => $entries) { foreach ($entries as $index => $entry) { $key = "{$ns}:{$index}"; $pdbk = $entry['pdbk']; $title = $entry['title']; $titleText = $title->getText(); // generate all variants of the link title text $allTextVariants = $wgContLang->convertLinkToAllVariants($titleText); // if link was not found (in first query), add all variants to query if (!isset($colours[$pdbk])) { foreach ($allTextVariants as $textVariant) { if ($textVariant != $titleText) { $variantTitle = Title::makeTitle($ns, $textVariant); if (is_null($variantTitle)) { continue; } $linkBatch->addObj($variantTitle); $variantMap[$variantTitle->getPrefixedDBkey()][] = $key; } } } } } // process categories, check if a category exists in some variant $categoryMap = array(); // maps $category_variant => $category (dbkeys) $varCategories = array(); // category replacements oldDBkey => newDBkey foreach ($output->getCategoryLinks() as $category) { $variants = $wgContLang->convertLinkToAllVariants($category); foreach ($variants as $variant) { if ($variant != $category) { $variantTitle = Title::newFromDBkey(Title::makeName(NS_CATEGORY, $variant)); if (is_null($variantTitle)) { continue; } $linkBatch->addObj($variantTitle); $categoryMap[$variant] = $category; } } } if (!$linkBatch->isEmpty()) { // construct query $dbr = wfGetDB(DB_SLAVE); $page = $dbr->tableName('page'); $titleClause = $linkBatch->constructSet('page', $dbr); $variantQuery = "SELECT page_id, page_namespace, page_title, page_is_redirect, page_len"; $variantQuery .= " FROM {$page} WHERE {$titleClause}"; $varRes = $dbr->query($variantQuery, __METHOD__); $linkcolour_ids = array(); // for each found variants, figure out link holders and replace while ($s = $dbr->fetchObject($varRes)) { $variantTitle = Title::makeTitle($s->page_namespace, $s->page_title); $varPdbk = $variantTitle->getPrefixedDBkey(); $vardbk = $variantTitle->getDBkey(); $holderKeys = array(); if (isset($variantMap[$varPdbk])) { $holderKeys = $variantMap[$varPdbk]; $linkCache->addGoodLinkObj($s->page_id, $variantTitle, $s->page_len, $s->page_is_redirect); $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])) { // found link in some of the variants, replace the link holder data $entry['title'] = $variantTitle; $entry['pdbk'] = $varPdbk; // set pdbk and colour # FIXME: convoluted data flow # The redirect status and length is passed to getLinkColour via the LinkCache # Use formal parameters instead $colours[$varPdbk] = $sk->getLinkColour($variantTitle, $threshold); $linkcolour_ids[$s->page_id] = $pdbk; } } // check if the object is a variant of a category if (isset($categoryMap[$vardbk])) { $oldkey = $categoryMap[$vardbk]; if ($oldkey != $vardbk) { $varCategories[$oldkey] = $vardbk; } } } wfRunHooks('GetLinkColours', array($linkcolour_ids, &$colours)); // rebuild the categories in original order (if there are replacements) if (count($varCategories) > 0) { $newCats = array(); $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); } } }
/** * Modify $this->internals and $colours according to language variant linking rules * @param array $colours */ protected function doVariants(&$colours) { global $wgContLang, $wgContentHandlerUseDB; $linkBatch = new LinkBatch(); $variantMap = array(); // maps $pdbkey_Variant => $keys (of link holders) $output = $this->parent->getOutput(); $linkCache = LinkCache::singleton(); $threshold = $this->parent->getOptions()->getStubThreshold(); $titlesToBeConverted = ''; $titlesAttrs = array(); // 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[] = array($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); if (is_null($variantTitle)) { continue; } // 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 = array(); // maps $category_variant => $category (dbkeys) $varCategories = array(); // 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] = array($category, $categoryTitle); } } } if (!$linkBatch->isEmpty()) { // construct query $dbr = wfGetDB(DB_SLAVE); $fields = array('page_id', 'page_namespace', 'page_title', 'page_is_redirect', 'page_len', 'page_latest'); if ($wgContentHandlerUseDB) { $fields[] = 'page_content_model'; } $varRes = $dbr->select('page', $fields, $linkBatch->constructSet('page', $dbr), __METHOD__); $linkcolour_ids = array(); // 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 = array(); 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 # @todo FIXME: Convoluted data flow # The redirect status and length is passed to getLinkColour via the LinkCache # Use formal parameters instead $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', array($linkcolour_ids, &$colours)); // rebuild the categories in original order (if there are replacements) if (count($varCategories) > 0) { $newCats = array(); $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); } } }
private function formatCategoryLinks($links) { $result = []; if (!$links) { return $result; } // Fetch hiddencat property $lb = new LinkBatch(); $lb->setArray([NS_CATEGORY => $links]); $db = $this->getDB(); $res = $db->select(['page', 'page_props'], ['page_title', 'pp_propname'], $lb->constructSet('page', $db), __METHOD__, [], ['page_props' => ['LEFT JOIN', ['pp_propname' => 'hiddencat', 'pp_page = page_id']]]); $hiddencats = []; foreach ($res as $row) { $hiddencats[$row->page_title] = isset($row->pp_propname); } $linkCache = LinkCache::singleton(); foreach ($links as $link => $sortkey) { $entry = []; $entry['sortkey'] = $sortkey; // array keys will cast numeric category names to ints, so cast back to string ApiResult::setContentValue($entry, 'category', (string) $link); if (!isset($hiddencats[$link])) { $entry['missing'] = true; // We already know the link doesn't exist in the database, so // tell LinkCache that before calling $title->isKnown(). $title = Title::makeTitle(NS_CATEGORY, $link); $linkCache->addBadLinkObj($title); if ($title->isKnown()) { $entry['known'] = true; } } elseif ($hiddencats[$link]) { $entry['hidden'] = true; } $result[] = $entry; } return $result; }
public function execute() { $user = $this->getUser(); if ($user->isAnon()) { $this->dieUsage('Anonymous users cannot use watchlist change notifications', 'notloggedin'); } if (!$user->isAllowed('editmywatchlist')) { $this->dieUsage('You don\'t have permission to edit your watchlist', 'permissiondenied'); } $params = $this->extractRequestParams(); $this->requireMaxOneParameter($params, 'timestamp', 'torevid', 'newerthanrevid'); $continuationManager = new ApiContinuationManager($this, array(), array()); $this->setContinuationManager($continuationManager); $pageSet = $this->getPageSet(); if ($params['entirewatchlist'] && $pageSet->getDataSource() !== null) { $this->dieUsage("Cannot use 'entirewatchlist' at the same time as '{$pageSet->getDataSource()}'", 'multisource'); } $dbw = wfGetDB(DB_MASTER, 'api'); $timestamp = null; if (isset($params['timestamp'])) { $timestamp = $dbw->timestamp($params['timestamp']); } if (!$params['entirewatchlist']) { $pageSet->execute(); } if (isset($params['torevid'])) { if ($params['entirewatchlist'] || $pageSet->getGoodTitleCount() > 1) { $this->dieUsage('torevid may only be used with a single page', 'multpages'); } $title = reset($pageSet->getGoodTitles()); if ($title) { $timestamp = Revision::getTimestampFromId($title, $params['torevid'], Revision::READ_LATEST); if ($timestamp) { $timestamp = $dbw->timestamp($timestamp); } else { $timestamp = null; } } } elseif (isset($params['newerthanrevid'])) { if ($params['entirewatchlist'] || $pageSet->getGoodTitleCount() > 1) { $this->dieUsage('newerthanrevid may only be used with a single page', 'multpages'); } $title = reset($pageSet->getGoodTitles()); if ($title) { $revid = $title->getNextRevisionID($params['newerthanrevid'], Title::GAID_FOR_UPDATE); if ($revid) { $timestamp = $dbw->timestamp(Revision::getTimestampFromId($title, $revid)); } else { $timestamp = null; } } } $apiResult = $this->getResult(); $result = array(); if ($params['entirewatchlist']) { // Entire watchlist mode: Just update the thing and return a success indicator $dbw->update('watchlist', array('wl_notificationtimestamp' => $timestamp), array('wl_user' => $user->getID()), __METHOD__); $result['notificationtimestamp'] = is_null($timestamp) ? '' : wfTimestamp(TS_ISO_8601, $timestamp); } else { // First, log the invalid titles foreach ($pageSet->getInvalidTitles() as $title) { $r = array(); $r['title'] = $title; $r['invalid'] = true; $result[] = $r; } foreach ($pageSet->getMissingPageIDs() as $p) { $page = array(); $page['pageid'] = $p; $page['missing'] = true; $page['notwatched'] = true; $result[] = $page; } foreach ($pageSet->getMissingRevisionIDs() as $r) { $rev = array(); $rev['revid'] = $r; $rev['missing'] = true; $rev['notwatched'] = true; $result[] = $rev; } if ($pageSet->getTitles()) { // Now process the valid titles $lb = new LinkBatch($pageSet->getTitles()); $dbw->update('watchlist', array('wl_notificationtimestamp' => $timestamp), array('wl_user' => $user->getID(), $lb->constructSet('wl', $dbw)), __METHOD__); // Query the results of our update $timestamps = array(); $res = $dbw->select('watchlist', array('wl_namespace', 'wl_title', 'wl_notificationtimestamp'), array('wl_user' => $user->getID(), $lb->constructSet('wl', $dbw)), __METHOD__); foreach ($res as $row) { $timestamps[$row->wl_namespace][$row->wl_title] = $row->wl_notificationtimestamp; } // Now, put the valid titles into the result /** @var $title Title */ foreach ($pageSet->getTitles() as $title) { $ns = $title->getNamespace(); $dbkey = $title->getDBkey(); $r = array('ns' => intval($ns), 'title' => $title->getPrefixedText()); if (!$title->exists()) { $r['missing'] = true; } if (isset($timestamps[$ns]) && array_key_exists($dbkey, $timestamps[$ns])) { $r['notificationtimestamp'] = ''; if ($timestamps[$ns][$dbkey] !== null) { $r['notificationtimestamp'] = wfTimestamp(TS_ISO_8601, $timestamps[$ns][$dbkey]); } } else { $r['notwatched'] = true; } $result[] = $r; } } ApiResult::setIndexedTagName($result, 'page'); } $apiResult->addValue(null, $this->getModuleName(), $result); $this->setContinuationManager(null); $continuationManager->setContinuationIntoResult($apiResult); }
public function execute() { global $wgUser; // Before doing anything at all, let's check permissions if (!$wgUser->isAllowed('deletedhistory')) { $this->dieUsage('You don\'t have permission to view deleted revision information', 'permissiondenied'); } $db = $this->getDB(); $params = $this->extractRequestParams(false); $prop = array_flip($params['prop']); $fld_revid = isset($prop['revid']); $fld_user = isset($prop['user']); $fld_comment = isset($prop['comment']); $fld_minor = isset($prop['minor']); $fld_len = isset($prop['len']); $fld_content = isset($prop['content']); $fld_token = isset($prop['token']); $result = $this->getResult(); $pageSet = $this->getPageSet(); $titles = $pageSet->getTitles(); $data = array(); // This module operates in three modes: // 'revs': List deleted revs for certain titles // 'user': List deleted revs by a certain user // 'all': List all deleted revs $mode = 'all'; if (count($titles) > 0) { $mode = 'revs'; } else { if (!is_null($params['user'])) { $mode = 'user'; } } if (!is_null($params['user']) && !is_null($params['excludeuser'])) { $this->dieUsage('user and excludeuser cannot be used together', 'badparams'); } $this->addTables('archive'); $this->addWhere('ar_deleted = 0'); $this->addFields(array('ar_title', 'ar_namespace', 'ar_timestamp')); if ($fld_revid) { $this->addFields('ar_rev_id'); } if ($fld_user) { $this->addFields('ar_user_text'); } if ($fld_comment) { $this->addFields('ar_comment'); } if ($fld_minor) { $this->addFields('ar_minor_edit'); } if ($fld_len) { $this->addFields('ar_len'); } if ($fld_content) { $this->addTables('text'); $this->addFields(array('ar_text', 'ar_text_id', 'old_text', 'old_flags')); $this->addWhere('ar_text_id = old_id'); // This also means stricter restrictions if (!$wgUser->isAllowed('undelete')) { $this->dieUsage('You don\'t have permission to view deleted revision content', 'permissiondenied'); } } // Check limits $userMax = $fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1; $botMax = $fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2; $limit = $params['limit']; if ($limit == 'max') { $limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax; $this->getResult()->addValue('limits', $this->getModuleName(), $limit); } $this->validateLimit('limit', $limit, 1, $userMax, $botMax); if ($fld_token) { // Undelete tokens are identical for all pages, so we cache one here $token = $wgUser->editToken(); } // We need a custom WHERE clause that matches all titles. if ($mode == 'revs') { $lb = new LinkBatch($titles); $where = $lb->constructSet('ar', $db); $this->addWhere($where); } elseif ($mode == 'all') { $this->addWhereFld('ar_namespace', $params['namespace']); if (!is_null($params['from'])) { $from = $this->getDB()->strencode($this->titleToKey($params['from'])); $this->addWhere("ar_title >= '{$from}'"); } } if (!is_null($params['user'])) { $this->addWhereFld('ar_user_text', $params['user']); } elseif (!is_null($params['excludeuser'])) { $this->addWhere('ar_user_text != ' . $this->getDB()->addQuotes($params['excludeuser'])); } if (!is_null($params['continue']) && ($mode == 'all' || $mode == 'revs')) { $cont = explode('|', $params['continue']); if (count($cont) != 3) { $this->dieUsage("Invalid continue param. You should pass the original value returned by the previous query", "badcontinue"); } $ns = intval($cont[0]); $title = $this->getDB()->strencode($this->titleToKey($cont[1])); $ts = $this->getDB()->strencode($cont[2]); $op = $params['dir'] == 'newer' ? '>' : '<'; $this->addWhere("ar_namespace {$op} {$ns} OR " . "(ar_namespace = {$ns} AND " . "(ar_title {$op} '{$title}' OR " . "(ar_title = '{$title}' AND " . "ar_timestamp = '{$ts}')))"); } $this->addOption('LIMIT', $limit + 1); $this->addOption('USE INDEX', array('archive' => $mode == 'user' ? 'usertext_timestamp' : 'name_title_timestamp')); if ($mode == 'all') { if ($params['unique']) { $this->addOption('GROUP BY', 'ar_title'); $this->addOption('ORDER BY', 'ar_title'); } else { $this->addOption('ORDER BY', 'ar_title, ar_timestamp'); } } else { if ($mode == 'revs') { // Sort by ns and title in the same order as timestamp for efficiency $this->addWhereRange('ar_namespace', $params['dir'], null, null); $this->addWhereRange('ar_title', $params['dir'], null, null); } $this->addWhereRange('ar_timestamp', $params['dir'], $params['start'], $params['end']); } $res = $this->select(__METHOD__); $pageMap = array(); // Maps ns&title to (fake) pageid $count = 0; $newPageID = 0; while ($row = $db->fetchObject($res)) { if (++$count > $limit) { // We've had enough if ($mode == 'all' || $mode == 'revs') { $this->setContinueEnumParameter('continue', intval($row->ar_namespace) . '|' . $this->keyToTitle($row->ar_title) . '|' . $row->ar_timestamp); } else { $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ar_timestamp)); } break; } $rev = array(); $rev['timestamp'] = wfTimestamp(TS_ISO_8601, $row->ar_timestamp); if ($fld_revid) { $rev['revid'] = intval($row->ar_rev_id); } if ($fld_user) { $rev['user'] = $row->ar_user_text; } if ($fld_comment) { $rev['comment'] = $row->ar_comment; } if ($fld_minor) { if ($row->ar_minor_edit == 1) { $rev['minor'] = ''; } } if ($fld_len) { $rev['len'] = $row->ar_len; } if ($fld_content) { ApiResult::setContent($rev, Revision::getRevisionText($row)); } if (!isset($pageMap[$row->ar_namespace][$row->ar_title])) { $pageID = $newPageID++; $pageMap[$row->ar_namespace][$row->ar_title] = $pageID; $t = Title::makeTitle($row->ar_namespace, $row->ar_title); $a['revisions'] = array($rev); $result->setIndexedTagName($a['revisions'], 'rev'); ApiQueryBase::addTitleInfo($a, $t); if ($fld_token) { $a['token'] = $token; } $fit = $result->addValue(array('query', $this->getModuleName()), $pageID, $a); } else { $pageID = $pageMap[$row->ar_namespace][$row->ar_title]; $fit = $result->addValue(array('query', $this->getModuleName(), $pageID, 'revisions'), null, $rev); } if (!$fit) { if ($mode == 'all' || $mode == 'revs') { $this->setContinueEnumParameter('continue', intval($row->ar_namespace) . '|' . $this->keyToTitle($row->ar_title) . '|' . $row->ar_timestamp); } else { $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ar_timestamp)); } break; } } $db->freeResult($res); $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'page'); }
/** * Modify $this->internals and $colours according to language variant linking rules */ protected function doVariants(&$colours) { global $wgContLang; $linkBatch = new LinkBatch(); $variantMap = array(); // maps $pdbkey_Variant => $keys (of link holders) $output = $this->parent->getOutput(); $linkCache = LinkCache::singleton(); $threshold = $this->parent->getOptions()->getStubThreshold(); $titlesToBeConverted = ''; $titlesAttrs = array(); // 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) { foreach ($entries as $index => $entry) { $pdbk = $entry['pdbk']; // we only deal with new links (in its first query) if (!isset($colours[$pdbk])) { $title = $entry['title']; $titleText = $title->getText(); $titlesAttrs[] = array('ns' => $ns, 'key' => "{$ns}:{$index}", 'titleText' => $titleText); // separate titles with \0 because it would never appears // in a valid title $titlesToBeConverted .= $titleText . ""; } } } // Now do the conversion and explode string to text of titles $titlesAllVariants = $wgContLang->autoConvertToAllVariants($titlesToBeConverted); $allVariantsName = array_keys($titlesAllVariants); foreach ($titlesAllVariants as &$titlesVariant) { $titlesVariant = explode("", $titlesVariant); } $l = count($titlesAttrs); // Then add variants of links to link batch for ($i = 0; $i < $l; $i++) { foreach ($allVariantsName as $variantName) { $textVariant = $titlesAllVariants[$variantName][$i]; if ($textVariant != $titlesAttrs[$i]['titleText']) { $variantTitle = Title::makeTitle($titlesAttrs[$i]['ns'], $textVariant); if (is_null($variantTitle)) { continue; } $linkBatch->addObj($variantTitle); $variantMap[$variantTitle->getPrefixedDBkey()][] = $titlesAttrs[$i]['key']; } } } // process categories, check if a category exists in some variant $categoryMap = array(); // maps $category_variant => $category (dbkeys) $varCategories = array(); // category replacements oldDBkey => newDBkey foreach ($output->getCategoryLinks() as $category) { $variants = $wgContLang->autoConvertToAllVariants($category); foreach ($variants as $variant) { if ($variant != $category) { $variantTitle = Title::newFromDBkey(Title::makeName(NS_CATEGORY, $variant)); if (is_null($variantTitle)) { continue; } $linkBatch->addObj($variantTitle); $categoryMap[$variant] = $category; } } } if (!$linkBatch->isEmpty()) { // construct query $dbr = wfGetDB(DB_SLAVE); $varRes = $dbr->select('page', array('page_id', 'page_namespace', 'page_title', 'page_is_redirect', 'page_len', 'page_latest'), $linkBatch->constructSet('page', $dbr), __METHOD__); $linkcolour_ids = array(); // 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 = array(); 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])) { // found link in some of the variants, replace the link holder data $entry['title'] = $variantTitle; $entry['pdbk'] = $varPdbk; // set pdbk and colour # @todo FIXME: Convoluted data flow # The redirect status and length is passed to getLinkColour via the LinkCache # Use formal parameters instead $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])) { $oldkey = $categoryMap[$vardbk]; if ($oldkey != $vardbk) { $varCategories[$oldkey] = $vardbk; } } } wfRunHooks('GetLinkColours', array($linkcolour_ids, &$colours)); // rebuild the categories in original order (if there are replacements) if (count($varCategories) > 0) { $newCats = array(); $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); } } }
/** * Get information about watched status and put it in $this->watched * and $this->notificationtimestamps */ private function getWatchedInfo() { $user = $this->getUser(); if ($user->isAnon() || count($this->everything) == 0) { return; } $this->watched = array(); $this->notificationtimestamps = array(); $db = $this->getDB(); $lb = new LinkBatch($this->everything); $this->resetQueryParams(); $this->addTables(array('watchlist')); $this->addFields(array('wl_title', 'wl_namespace')); $this->addFieldsIf('wl_notificationtimestamp', $this->fld_notificationtimestamp); $this->addWhere(array($lb->constructSet('wl', $db), 'wl_user' => $user->getID())); $res = $this->select(__METHOD__); foreach ($res as $row) { if ($this->fld_watched) { $this->watched[$row->wl_namespace][$row->wl_title] = true; } if ($this->fld_notificationtimestamp) { $this->notificationtimestamps[$row->wl_namespace][$row->wl_title] = $row->wl_notificationtimestamp; } } }
/** * Make a WHERE clause from a 2-d NS/dbkey array * * @param array $arr 2-d array indexed by namespace and DB key * @param string $prefix Field name prefix, without the underscore */ function makeWhereFrom2d(&$arr, $prefix) { $lb = new LinkBatch(); $lb->setArray($arr); return $lb->constructSet($prefix, $this->mDb); }
/** * @param ApiPageSet $resultPageSet */ private function run(ApiPageSet $resultPageSet = null) { $settings = self::$settings[$this->getModuleName()]; $db = $this->getDB(); $params = $this->extractRequestParams(); $prop = array_flip($params['prop']); $emptyString = $db->addQuotes(''); $pageSet = $this->getPageSet(); $titles = $pageSet->getGoodTitles() + $pageSet->getMissingTitles(); $map = $pageSet->getAllTitlesByNamespace(); // Determine our fields to query on $p = $settings['prefix']; $hasNS = !isset($settings['to_namespace']); if ($hasNS) { $bl_namespace = "{$p}_namespace"; $bl_title = "{$p}_title"; } else { $bl_namespace = $settings['to_namespace']; $bl_title = "{$p}_to"; $titles = array_filter($titles, function ($t) use($bl_namespace) { return $t->getNamespace() === $bl_namespace; }); $map = array_intersect_key($map, array($bl_namespace => true)); } $bl_from = "{$p}_from"; if (!$titles) { return; // nothing to do } // Figure out what we're sorting by, and add associated WHERE clauses. // MySQL's query planner screws up if we include a field in ORDER BY // when it's constant in WHERE, so we have to test that for each field. $sortby = array(); if ($hasNS && count($map) > 1) { $sortby[$bl_namespace] = 'ns'; } $theTitle = null; foreach ($map as $nsTitles) { reset($nsTitles); $key = key($nsTitles); if ($theTitle === null) { $theTitle = $key; } if (count($nsTitles) > 1 || $key !== $theTitle) { $sortby[$bl_title] = 'title'; break; } } $miser_ns = null; if ($params['namespace'] !== null) { if (empty($settings['from_namespace']) && $this->getConfig()->get('MiserMode')) { $miser_ns = $params['namespace']; } else { $this->addWhereFld("{$p}_from_namespace", $params['namespace']); if (!empty($settings['from_namespace']) && count($params['namespace']) > 1) { $sortby["{$p}_from_namespace"] = 'int'; } } } $sortby[$bl_from] = 'int'; // Now use the $sortby to figure out the continuation if (!is_null($params['continue'])) { $cont = explode('|', $params['continue']); $this->dieContinueUsageIf(count($cont) != count($sortby)); $where = ''; $i = count($sortby) - 1; $cont_ns = 0; $cont_title = ''; foreach (array_reverse($sortby, true) as $field => $type) { $v = $cont[$i]; switch ($type) { case 'ns': $cont_ns = (int) $v; /* fall through */ /* fall through */ case 'int': $v = (int) $v; $this->dieContinueUsageIf($v != $cont[$i]); break; case 'title': $cont_title = $v; /* fall through */ /* fall through */ default: $v = $db->addQuotes($v); break; } if ($where === '') { $where = "{$field} >= {$v}"; } else { $where = "{$field} > {$v} OR ({$field} = {$v} AND ({$where}))"; } $i--; } $this->addWhere($where); } // Populate the rest of the query $this->addTables(array($settings['linktable'], 'page')); $this->addWhere("{$bl_from} = page_id"); if ($this->getModuleName() === 'redirects') { $this->addWhere("rd_interwiki = {$emptyString} OR rd_interwiki IS NULL"); } $this->addFields(array_keys($sortby)); $this->addFields(array('bl_namespace' => $bl_namespace, 'bl_title' => $bl_title)); if (is_null($resultPageSet)) { $fld_pageid = isset($prop['pageid']); $fld_title = isset($prop['title']); $fld_redirect = isset($prop['redirect']); $this->addFieldsIf('page_id', $fld_pageid); $this->addFieldsIf(array('page_title', 'page_namespace'), $fld_title); $this->addFieldsIf('page_is_redirect', $fld_redirect); // prop=redirects $fld_fragment = isset($prop['fragment']); $this->addFieldsIf('rd_fragment', $fld_fragment); } else { $this->addFields($resultPageSet->getPageTableFields()); } $this->addFieldsIf('page_namespace', $miser_ns !== null); if ($hasNS) { $lb = new LinkBatch($titles); $this->addWhere($lb->constructSet($p, $db)); } else { $where = array(); foreach ($titles as $t) { if ($t->getNamespace() == $bl_namespace) { $where[] = "{$bl_title} = " . $db->addQuotes($t->getDBkey()); } } $this->addWhere($db->makeList($where, LIST_OR)); } if ($params['show'] !== null) { // prop=redirects only $show = array_flip($params['show']); if (isset($show['fragment']) && isset($show['!fragment']) || isset($show['redirect']) && isset($show['!redirect'])) { $this->dieUsageMsg('show'); } $this->addWhereIf("rd_fragment != {$emptyString}", isset($show['fragment'])); $this->addWhereIf("rd_fragment = {$emptyString} OR rd_fragment IS NULL", isset($show['!fragment'])); $this->addWhereIf(array('page_is_redirect' => 1), isset($show['redirect'])); $this->addWhereIf(array('page_is_redirect' => 0), isset($show['!redirect'])); } // Override any ORDER BY from above with what we calculated earlier. $this->addOption('ORDER BY', array_keys($sortby)); $this->addOption('LIMIT', $params['limit'] + 1); $res = $this->select(__METHOD__); if (is_null($resultPageSet)) { $count = 0; foreach ($res as $row) { if (++$count > $params['limit']) { // We've reached the one extra which shows that // there are additional pages to be had. Stop here... $this->setContinue($row, $sortby); break; } if ($miser_ns !== null && !in_array($row->page_namespace, $miser_ns)) { // Miser mode namespace check continue; } // Get the ID of the current page $id = $map[$row->bl_namespace][$row->bl_title]; $vals = array(); if ($fld_pageid) { $vals['pageid'] = $row->page_id; } if ($fld_title) { ApiQueryBase::addTitleInfo($vals, Title::makeTitle($row->page_namespace, $row->page_title)); } if ($fld_fragment && $row->rd_fragment !== null && $row->rd_fragment !== '') { $vals['fragment'] = $row->rd_fragment; } if ($fld_redirect && $row->page_is_redirect) { $vals['redirect'] = ''; } $fit = $this->addPageSubItem($id, $vals); if (!$fit) { $this->setContinue($row, $sortby); break; } } } else { $titles = array(); $count = 0; foreach ($res as $row) { if (++$count > $params['limit']) { // We've reached the one extra which shows that // there are additional pages to be had. Stop here... $this->setContinue($row, $sortby); break; } $titles[] = Title::makeTitle($row->page_namespace, $row->page_title); } $resultPageSet->populateFromTitles($titles); } }
private function run($resultPageSet = null) { if ($this->getPageSet()->getGoodTitleCount() == 0) { return; // nothing to do } $params = $this->extractRequestParams(); $this->addFields(array($this->prefix . '_from AS pl_from', $this->prefix . '_namespace AS pl_namespace', $this->prefix . '_title AS pl_title')); $this->addTables($this->table); $this->addWhereFld($this->prefix . '_from', array_keys($this->getPageSet()->getGoodTitles())); $this->addWhereFld($this->prefix . '_namespace', $params['namespace']); if (!is_null($params[$this->titlesParam])) { $lb = new LinkBatch(); foreach ($params[$this->titlesParam] as $t) { $title = Title::newFromText($t); if (!$title) { $this->setWarning("``{$t}'' is not a valid title"); } else { $lb->addObj($title); } } $cond = $lb->constructSet($this->prefix, $this->getDB()); if ($cond) { $this->addWhere($cond); } } if (!is_null($params['continue'])) { $cont = explode('|', $params['continue']); if (count($cont) != 3) { $this->dieUsage('Invalid continue param. You should pass the ' . 'original value returned by the previous query', '_badcontinue'); } $plfrom = intval($cont[0]); $plns = intval($cont[1]); $pltitle = $this->getDB()->strencode($this->titleToKey($cont[2])); $this->addWhere("{$this->prefix}_from > {$plfrom} OR " . "({$this->prefix}_from = {$plfrom} AND " . "({$this->prefix}_namespace > {$plns} OR " . "({$this->prefix}_namespace = {$plns} AND " . "{$this->prefix}_title >= '{$pltitle}')))"); } // Here's some MySQL craziness going on: if you use WHERE foo='bar' // and later ORDER BY foo MySQL doesn't notice the ORDER BY is pointless // but instead goes and filesorts, because the index for foo was used // already. To work around this, we drop constant fields in the WHERE // clause from the ORDER BY clause $order = array(); if (count($this->getPageSet()->getGoodTitles()) != 1) { $order[] = "{$this->prefix}_from"; } if (count($params['namespace']) != 1) { $order[] = "{$this->prefix}_namespace"; } $order[] = "{$this->prefix}_title"; $this->addOption('ORDER BY', implode(', ', $order)); $this->addOption('USE INDEX', "{$this->prefix}_from"); $this->addOption('LIMIT', $params['limit'] + 1); $res = $this->select(__METHOD__); if (is_null($resultPageSet)) { $count = 0; foreach ($res as $row) { if (++$count > $params['limit']) { // We've reached the one extra which shows that // there are additional pages to be had. Stop here... $this->setContinueEnumParameter('continue', "{$row->pl_from}|{$row->pl_namespace}|" . $this->keyToTitle($row->pl_title)); break; } $vals = array(); ApiQueryBase::addTitleInfo($vals, Title::makeTitle($row->pl_namespace, $row->pl_title)); $fit = $this->addPageSubItem($row->pl_from, $vals); if (!$fit) { $this->setContinueEnumParameter('continue', "{$row->pl_from}|{$row->pl_namespace}|" . $this->keyToTitle($row->pl_title)); break; } } } else { $titles = array(); $count = 0; foreach ($res as $row) { if (++$count > $params['limit']) { // We've reached the one extra which shows that // there are additional pages to be had. Stop here... $this->setContinueEnumParameter('continue', "{$row->pl_from}|{$row->pl_namespace}|" . $this->keyToTitle($row->pl_title)); break; } $titles[] = Title::makeTitle($row->pl_namespace, $row->pl_title); } $resultPageSet->populateFromTitles($titles); } }
private function formatCategoryLinks($links) { $result = array(); if (!$links) { return $result; } // Fetch hiddencat property $lb = new LinkBatch(); $lb->setArray(array(NS_CATEGORY => $links)); $db = $this->getDB(); $res = $db->select(array('page', 'page_props'), array('page_title', 'pp_propname'), $lb->constructSet('page', $db), __METHOD__, array(), array('page_props' => array('LEFT JOIN', array('pp_propname' => 'hiddencat', 'pp_page = page_id')))); $hiddencats = array(); foreach ($res as $row) { $hiddencats[$row->page_title] = isset($row->pp_propname); } foreach ($links as $link => $sortkey) { $entry = array(); $entry['sortkey'] = $sortkey; ApiResult::setContentValue($entry, 'category', $link); if (!isset($hiddencats[$link])) { $entry['missing'] = true; } elseif ($hiddencats[$link]) { $entry['hidden'] = true; } $result[] = $entry; } return $result; }
/** * Generates and outputs the result of this query based upon the provided parameters. */ public function execute() { /* Get the parameters of the request. */ $params = $this->extractRequestParams(); /* Build our basic query. Namely, something along the lines of: * SELECT * FROM recentchanges WHERE rc_timestamp > $start * AND rc_timestamp < $end AND rc_namespace = $namespace * AND rc_deleted = '0' */ $db = $this->getDB(); $this->addTables('recentchanges'); $this->addOption('USE INDEX', array('recentchanges' => 'rc_timestamp')); $this->addWhereRange('rc_timestamp', $params['dir'], $params['start'], $params['end']); $this->addWhereFld('rc_namespace', $params['namespace']); $this->addWhereFld('rc_deleted', 0); if ($params['titles']) { $lb = new LinkBatch(); foreach ($params['titles'] as $t) { $obj = Title::newFromText($t); $lb->addObj($obj); if ($obj->getNamespace() < 0) { // LinkBatch refuses these, but we need them anyway if (!array_key_exists($obj->getNamespace(), $lb->data)) { $lb->data[$obj->getNamespace()] = array(); } $lb->data[$obj->getNamespace()][$obj->getDBKey()] = 1; } } $where = $lb->constructSet('rc', $this->getDB()); if ($where != '') { $this->addWhere($where); } } if (!is_null($params['type'])) { $this->addWhereFld('rc_type', $this->parseRCType($params['type'])); } if (!is_null($params['show'])) { $show = array_flip($params['show']); /* Check for conflicting parameters. */ if (isset($show['minor']) && isset($show['!minor']) || isset($show['bot']) && isset($show['!bot']) || isset($show['anon']) && isset($show['!anon']) || isset($show['redirect']) && isset($show['!redirect']) || isset($show['patrolled']) && isset($show['!patrolled'])) { $this->dieUsage("Incorrect parameter - mutually exclusive values may not be supplied", 'show'); } // Check permissions global $wgUser; if ((isset($show['patrolled']) || isset($show['!patrolled'])) && !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol()) { $this->dieUsage("You need the patrol right to request the patrolled flag", 'permissiondenied'); } /* Add additional conditions to query depending upon parameters. */ $this->addWhereIf('rc_minor = 0', isset($show['!minor'])); $this->addWhereIf('rc_minor != 0', isset($show['minor'])); $this->addWhereIf('rc_bot = 0', isset($show['!bot'])); $this->addWhereIf('rc_bot != 0', isset($show['bot'])); $this->addWhereIf('rc_user = 0', isset($show['anon'])); $this->addWhereIf('rc_user != 0', isset($show['!anon'])); $this->addWhereIf('rc_patrolled = 0', isset($show['!patrolled'])); $this->addWhereIf('rc_patrolled != 0', isset($show['patrolled'])); $this->addWhereIf('page_is_redirect = 1', isset($show['redirect'])); // Don't throw log entries out the window here $this->addWhereIf('page_is_redirect = 0 OR page_is_redirect IS NULL', isset($show['!redirect'])); } /* Add the fields we're concerned with to out query. */ $this->addFields(array('rc_timestamp', 'rc_namespace', 'rc_title', 'rc_cur_id', 'rc_type', 'rc_moved_to_ns', 'rc_moved_to_title')); /* Determine what properties we need to display. */ if (!is_null($params['prop'])) { $prop = array_flip($params['prop']); /* Set up internal members based upon params. */ $this->fld_comment = isset($prop['comment']); $this->fld_user = isset($prop['user']); $this->fld_flags = isset($prop['flags']); $this->fld_timestamp = isset($prop['timestamp']); $this->fld_title = isset($prop['title']); $this->fld_ids = isset($prop['ids']); $this->fld_sizes = isset($prop['sizes']); $this->fld_redirect = isset($prop['redirect']); $this->fld_patrolled = isset($prop['patrolled']); $this->fld_loginfo = isset($prop['loginfo']); global $wgUser; if ($this->fld_patrolled && !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol()) { $this->dieUsage("You need the patrol right to request the patrolled flag", 'permissiondenied'); } /* Add fields to our query if they are specified as a needed parameter. */ $this->addFieldsIf('rc_id', $this->fld_ids); $this->addFieldsIf('rc_this_oldid', $this->fld_ids); $this->addFieldsIf('rc_last_oldid', $this->fld_ids); $this->addFieldsIf('rc_comment', $this->fld_comment); $this->addFieldsIf('rc_user', $this->fld_user); $this->addFieldsIf('rc_user_text', $this->fld_user); $this->addFieldsIf('rc_minor', $this->fld_flags); $this->addFieldsIf('rc_bot', $this->fld_flags); $this->addFieldsIf('rc_new', $this->fld_flags); $this->addFieldsIf('rc_old_len', $this->fld_sizes); $this->addFieldsIf('rc_new_len', $this->fld_sizes); $this->addFieldsIf('rc_patrolled', $this->fld_patrolled); $this->addFieldsIf('rc_logid', $this->fld_loginfo); $this->addFieldsIf('rc_log_type', $this->fld_loginfo); $this->addFieldsIf('rc_log_action', $this->fld_loginfo); $this->addFieldsIf('rc_params', $this->fld_loginfo); if ($this->fld_redirect || isset($show['redirect']) || isset($show['!redirect'])) { $this->addTables('page'); $this->addJoinConds(array('page' => array('LEFT JOIN', array('rc_namespace=page_namespace', 'rc_title=page_title')))); $this->addFields('page_is_redirect'); } } $this->token = $params['token']; $this->addOption('LIMIT', $params['limit'] + 1); $data = array(); $count = 0; /* Perform the actual query. */ $db = $this->getDB(); $res = $this->select(__METHOD__); /* Iterate through the rows, adding data extracted from them to our query result. */ while ($row = $db->fetchObject($res)) { if (++$count > $params['limit']) { // We've reached the one extra which shows that there are additional pages to be had. Stop here... $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->rc_timestamp)); break; } /* Extract the data from a single row. */ $vals = $this->extractRowInfo($row); /* Add that row's data to our final output. */ if ($vals) { $data[] = $vals; } } $db->freeResult($res); /* Format the result */ $result = $this->getResult(); $result->setIndexedTagName($data, 'rc'); $result->addValue('query', $this->getModuleName(), $data); }
public function execute() { $user = $this->getUser(); if ($user->isAnon()) { $this->dieUsage('Anonymous users cannot use watchlist change notifications', 'notloggedin'); } $params = $this->extractRequestParams(); $this->requireMaxOneParameter($params, 'timestamp', 'torevid', 'newerthanrevid'); $pageSet = new ApiPageSet($this); $args = array_merge(array($params, 'entirewatchlist'), array_keys($pageSet->getAllowedParams())); call_user_func_array(array($this, 'requireOnlyOneParameter'), $args); $dbw = $this->getDB(DB_MASTER); $timestamp = null; if (isset($params['timestamp'])) { $timestamp = $dbw->timestamp($params['timestamp']); } if (!$params['entirewatchlist']) { $pageSet->execute(); } if (isset($params['torevid'])) { if ($params['entirewatchlist'] || $pageSet->getGoodTitleCount() > 1) { $this->dieUsage('torevid may only be used with a single page', 'multpages'); } $title = reset($pageSet->getGoodTitles()); $timestamp = Revision::getTimestampFromId($title, $params['torevid']); if ($timestamp) { $timestamp = $dbw->timestamp($timestamp); } else { $timestamp = null; } } elseif (isset($params['newerthanrevid'])) { if ($params['entirewatchlist'] || $pageSet->getGoodTitleCount() > 1) { $this->dieUsage('newerthanrevid may only be used with a single page', 'multpages'); } $title = reset($pageSet->getGoodTitles()); $revid = $title->getNextRevisionID($params['newerthanrevid']); if ($revid) { $timestamp = $dbw->timestamp(Revision::getTimestampFromId($title, $revid)); } else { $timestamp = null; } } $apiResult = $this->getResult(); $result = array(); if ($params['entirewatchlist']) { // Entire watchlist mode: Just update the thing and return a success indicator $dbw->update('watchlist', array('wl_notificationtimestamp' => $timestamp), array('wl_user' => $user->getID()), __METHOD__); $result['notificationtimestamp'] = is_null($timestamp) ? '' : wfTimestamp(TS_ISO_8601, $timestamp); } else { // First, log the invalid titles foreach ($pageSet->getInvalidTitles() as $title) { $r = array(); $r['title'] = $title; $r['invalid'] = ''; $result[] = $r; } foreach ($pageSet->getMissingPageIDs() as $p) { $page = array(); $page['pageid'] = $p; $page['missing'] = ''; $page['notwatched'] = ''; $result[] = $page; } foreach ($pageSet->getMissingRevisionIDs() as $r) { $rev = array(); $rev['revid'] = $r; $rev['missing'] = ''; $rev['notwatched'] = ''; $result[] = $rev; } // Now process the valid titles $lb = new LinkBatch($pageSet->getTitles()); $dbw->update('watchlist', array('wl_notificationtimestamp' => $timestamp), array('wl_user' => $user->getID(), $lb->constructSet('wl', $dbw)), __METHOD__); // Query the results of our update $timestamps = array(); $res = $dbw->select('watchlist', array('wl_namespace', 'wl_title', 'wl_notificationtimestamp'), array('wl_user' => $user->getID(), $lb->constructSet('wl', $dbw)), __METHOD__); foreach ($res as $row) { $timestamps[$row->wl_namespace][$row->wl_title] = $row->wl_notificationtimestamp; } // Now, put the valid titles into the result foreach ($pageSet->getTitles() as $title) { $ns = $title->getNamespace(); $dbkey = $title->getDBkey(); $r = array('ns' => intval($ns), 'title' => $title->getPrefixedText()); if (!$title->exists()) { $r['missing'] = ''; } if (isset($timestamps[$ns]) && array_key_exists($dbkey, $timestamps[$ns])) { $r['notificationtimestamp'] = ''; if ($timestamps[$ns][$dbkey] !== null) { $r['notificationtimestamp'] = wfTimestamp(TS_ISO_8601, $timestamps[$ns][$dbkey]); } } else { $r['notwatched'] = ''; } $result[] = $r; } $apiResult->setIndexedTagName($result, 'page'); } $apiResult->addValue(null, $this->getModuleName(), $result); }
/** * Get the count of watchers and put it in $this->watchers */ private function getWatcherInfo() { if (count($this->everything) == 0) { return; } $user = $this->getUser(); $canUnwatchedpages = $user->isAllowed('unwatchedpages'); $unwatchedPageThreshold = $this->getConfig()->get('UnwatchedPageThreshold'); if (!$canUnwatchedpages && !is_int($unwatchedPageThreshold)) { return; } $this->watchers = array(); $this->showZeroWatchers = $canUnwatchedpages; $db = $this->getDB(); $lb = new LinkBatch($this->everything); $this->resetQueryParams(); $this->addTables(array('watchlist')); $this->addFields(array('wl_title', 'wl_namespace', 'count' => 'COUNT(*)')); $this->addWhere(array($lb->constructSet('wl', $db))); $this->addOption('GROUP BY', array('wl_namespace', 'wl_title')); if (!$canUnwatchedpages) { $this->addOption('HAVING', "COUNT(*) >= {$unwatchedPageThreshold}"); } $res = $this->select(__METHOD__); foreach ($res as $row) { $this->watchers[$row->wl_namespace][$row->wl_title] = (int) $row->count; } }
/** * 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 = array(NS_CATEGORY => $categories); $lb = new LinkBatch(); $lb->setArray($arr); # Fetch existence plus the hiddencat property $dbr = wfGetDB(DB_SLAVE); $fields = array('page_id', 'page_namespace', 'page_title', 'page_len', 'page_is_redirect', 'page_latest', 'pp_value'); if ($this->getConfig()->get('ContentHandlerUseDB')) { $fields[] = 'page_content_model'; } $res = $dbr->select(array('page', 'page_props'), $fields, $lb->constructSet('page', $dbr), __METHOD__, array(), array('page_props' => array('LEFT JOIN', array('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', array(&$this, $categories, &$this->mCategoryLinks))) { foreach ($categories as $category => $type) { $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); } } }
/** * 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); } } }