/** * Look up a special:Undelete link to the given deleted revision id, * as a workaround for being unable to load deleted diffs in currently. * * @param int $id revision ID * @return mixed URL or false */ function deletedLink($id) { global $wgUser; if ($wgUser->isAllowed('deletedhistory')) { $dbr = wfGetDB(DB_SLAVE); $row = $dbr->selectRow('archive', '*', array('ar_rev_id' => $id), __METHOD__); if ($row) { $rev = Revision::newFromArchiveRow($row); $title = Title::makeTitleSafe($row->ar_namespace, $row->ar_title); return SpecialPage::getTitleFor('Undelete')->getFullURL(array('target' => $title->getPrefixedText(), 'timestamp' => $rev->getTimestamp())); } } return false; }
protected function formatRevisionRow($row, $earliestLiveTime, $remaining) { $rev = Revision::newFromArchiveRow($row, array('title' => $this->mTargetObj)); $revTextSize = ''; $ts = wfTimestamp(TS_MW, $row->ar_timestamp); // Build checkboxen... if ($this->mAllowed) { if ($this->mInvert) { if (in_array($ts, $this->mTargetTimestamp)) { $checkBox = Xml::check("ts{$ts}"); } else { $checkBox = Xml::check("ts{$ts}", true); } } else { $checkBox = Xml::check("ts{$ts}"); } } else { $checkBox = ''; } // Build page & diff links... $user = $this->getUser(); if ($this->mCanView) { $titleObj = $this->getPageTitle(); # Last link if (!$rev->userCan(Revision::DELETED_TEXT, $this->getUser())) { $pageLink = htmlspecialchars($this->getLanguage()->userTimeAndDate($ts, $user)); $last = $this->msg('diff')->escaped(); } elseif ($remaining > 0 || $earliestLiveTime && $ts > $earliestLiveTime) { $pageLink = $this->getPageLink($rev, $titleObj, $ts); $last = Linker::linkKnown($titleObj, $this->msg('diff')->escaped(), array(), array('target' => $this->mTargetObj->getPrefixedText(), 'timestamp' => $ts, 'diff' => 'prev')); } else { $pageLink = $this->getPageLink($rev, $titleObj, $ts); $last = $this->msg('diff')->escaped(); } } else { $pageLink = htmlspecialchars($this->getLanguage()->userTimeAndDate($ts, $user)); $last = $this->msg('diff')->escaped(); } // User links $userLink = Linker::revUserTools($rev); // Minor edit $minor = $rev->isMinor() ? ChangesList::flag('minor') : ''; // Revision text size $size = $row->ar_len; if (!is_null($size)) { $revTextSize = Linker::formatRevisionSize($size); } // Edit summary $comment = Linker::revComment($rev); // Tags $attribs = array(); list($tagSummary, $classes) = ChangeTags::formatSummaryRow($row->ts_tags, 'deletedhistory'); if ($classes) { $attribs['class'] = implode(' ', $classes); } // Revision delete links $revdlink = Linker::getRevDeleteLink($user, $rev, $this->mTargetObj); $revisionRow = $this->msg('undelete-revision-row')->rawParams($checkBox, $revdlink, $last, $pageLink, $userLink, $minor, $revTextSize, $comment, $tagSummary)->escaped(); return Xml::tags('li', $attribs, $revisionRow) . "\n"; }
public function __construct($list, $row) { RevDel_Item::__construct($list, $row); $this->revision = Revision::newFromArchiveRow($row, array('page' => $this->list->title->getArticleID())); }
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); } }
/** * @param ApiPageSet $resultPageSet * @return void */ 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'); } $db = $this->getDB(); $params = $this->extractRequestParams(false); $result = $this->getResult(); // If the user wants no namespaces, they get no pages. if ($params['namespace'] === []) { if ($resultPageSet === null) { $result->addValue('query', $this->getModuleName(), []); } return; } // This module operates in two modes: // 'user': List deleted revs by a certain user // 'all': List all deleted revs in NS $mode = 'all'; if (!is_null($params['user'])) { $mode = 'user'; } if ($mode == 'user') { foreach (['from', 'to', 'prefix', 'excludeuser'] as $param) { if (!is_null($params[$param])) { $p = $this->getModulePrefix(); $this->dieUsage("The '{$p}{$param}' parameter cannot be used with '{$p}user'", 'badparams'); } } } else { foreach (['start', 'end'] as $param) { if (!is_null($params[$param])) { $p = $this->getModulePrefix(); $this->dieUsage("The '{$p}{$param}' parameter may only be used with '{$p}user'", 'badparams'); } } } // If we're generating titles only, we can use DISTINCT for a better // query. But we can't do that in 'user' mode (wrong index), and we can // only do it when sorting ASC (because MySQL apparently can't use an // index backwards for grouping even though it can for ORDER BY, WTF?) $dir = $params['dir']; $optimizeGenerateTitles = false; if ($mode === 'all' && $params['generatetitles'] && $resultPageSet !== null) { if ($dir === 'newer') { $optimizeGenerateTitles = true; } else { $p = $this->getModulePrefix(); $this->setWarning("For better performance when generating titles, set {$p}dir=newer"); } } $this->addTables('archive'); if ($resultPageSet === null) { $this->parseParameters($params); $this->addFields(Revision::selectArchiveFields()); $this->addFields(['ar_title', 'ar_namespace']); } else { $this->limit = $this->getParameter('limit') ?: 10; $this->addFields(['ar_title', 'ar_namespace']); if ($optimizeGenerateTitles) { $this->addOption('DISTINCT'); } else { $this->addFields(['ar_timestamp', 'ar_rev_id', 'ar_id']); } } if ($this->fld_tags) { $this->addTables('tag_summary'); $this->addJoinConds(['tag_summary' => ['LEFT JOIN', ['ar_rev_id=ts_rev_id']]]); $this->addFields('ts_tags'); } if (!is_null($params['tag'])) { $this->addTables('change_tag'); $this->addJoinConds(['change_tag' => ['INNER JOIN', ['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(['text' => ['LEFT JOIN', ['ar_text_id=old_id']]]); $this->addFields(['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'); } } $miser_ns = null; if ($mode == 'all') { if ($params['namespace'] !== null) { $namespaces = $params['namespace']; } else { $namespaces = MWNamespace::getValidNamespaces(); } $this->addWhereFld('ar_namespace', $namespaces); // For from/to/prefix, we have to consider the potential // transformations of the title in all specified namespaces. // Generally there will be only one transformation, but wikis with // some namespaces case-sensitive could have two. if ($params['from'] !== null || $params['to'] !== null) { $isDirNewer = $dir === 'newer'; $after = $isDirNewer ? '>=' : '<='; $before = $isDirNewer ? '<=' : '>='; $where = []; foreach ($namespaces as $ns) { $w = []; if ($params['from'] !== null) { $w[] = 'ar_title' . $after . $db->addQuotes($this->titlePartToKey($params['from'], $ns)); } if ($params['to'] !== null) { $w[] = 'ar_title' . $before . $db->addQuotes($this->titlePartToKey($params['to'], $ns)); } $w = $db->makeList($w, LIST_AND); $where[$w][] = $ns; } if (count($where) == 1) { $where = key($where); $this->addWhere($where); } else { $where2 = []; foreach ($where as $w => $ns) { $where2[] = $db->makeList([$w, 'ar_namespace' => $ns], LIST_AND); } $this->addWhere($db->makeList($where2, LIST_OR)); } } if (isset($params['prefix'])) { $where = []; foreach ($namespaces as $ns) { $w = 'ar_title' . $db->buildLike($this->titlePartToKey($params['prefix'], $ns), $db->anyString()); $where[$w][] = $ns; } if (count($where) == 1) { $where = key($where); $this->addWhere($where); } else { $where2 = []; foreach ($where as $w => $ns) { $where2[] = $db->makeList([$w, 'ar_namespace' => $ns], LIST_AND); } $this->addWhere($db->makeList($where2, LIST_OR)); } } } else { if ($this->getConfig()->get('MiserMode')) { $miser_ns = $params['namespace']; } else { $this->addWhereFld('ar_namespace', $params['namespace']); } $this->addTimestampWhereRange('ar_timestamp', $dir, $params['start'], $params['end']); } 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 ($optimizeGenerateTitles) { $this->dieContinueUsageIf(count($cont) != 2); $ns = intval($cont[0]); $this->dieContinueUsageIf(strval($ns) !== $cont[0]); $title = $db->addQuotes($cont[1]); $this->addWhere("ar_namespace {$op} {$ns} OR " . "(ar_namespace = {$ns} AND ar_title {$op}= {$title})"); } elseif ($mode == 'all') { $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', $this->limit + 1); $sort = $dir == 'newer' ? '' : ' DESC'; $orderby = []; if ($optimizeGenerateTitles) { // Targeting index name_title_timestamp if ($params['namespace'] === null || count(array_unique($params['namespace'])) > 1) { $orderby[] = "ar_namespace {$sort}"; } $orderby[] = "ar_title {$sort}"; } elseif ($mode == 'all') { // Targeting index name_title_timestamp if ($params['namespace'] === null || count(array_unique($params['namespace'])) > 1) { $orderby[] = "ar_namespace {$sort}"; } $orderby[] = "ar_title {$sort}"; $orderby[] = "ar_timestamp {$sort}"; $orderby[] = "ar_id {$sort}"; } else { // Targeting index usertext_timestamp // 'user' is always constant. $orderby[] = "ar_timestamp {$sort}"; $orderby[] = "ar_id {$sort}"; } $this->addOption('ORDER BY', $orderby); $res = $this->select(__METHOD__); $pageMap = []; // Maps ns&title to array index $count = 0; $nextIndex = 0; $generated = []; foreach ($res as $row) { if (++$count > $this->limit) { // We've had enough if ($optimizeGenerateTitles) { $this->setContinueEnumParameter('continue', "{$row->ar_namespace}|{$row->ar_title}"); } elseif ($mode == 'all') { $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; } // Miser mode namespace check if ($miser_ns !== null && !in_array($row->ar_namespace, $miser_ns)) { continue; } if ($resultPageSet !== null) { if ($params['generatetitles']) { $key = "{$row->ar_namespace}:{$row->ar_title}"; if (!isset($generated[$key])) { $generated[$key] = Title::makeTitle($row->ar_namespace, $row->ar_title); } } else { $generated[] = $row->ar_rev_id; } } else { $revision = Revision::newFromArchiveRow($row); $rev = $this->extractRevisionInfo($revision, $row); if (!isset($pageMap[$row->ar_namespace][$row->ar_title])) { $index = $nextIndex++; $pageMap[$row->ar_namespace][$row->ar_title] = $index; $title = $revision->getTitle(); $a = ['pageid' => $title->getArticleID(), 'revisions' => [$rev]]; ApiResult::setIndexedTagName($a['revisions'], 'rev'); ApiQueryBase::addTitleInfo($a, $title); $fit = $result->addValue(['query', $this->getModuleName()], $index, $a); } else { $index = $pageMap[$row->ar_namespace][$row->ar_title]; $fit = $result->addValue(['query', $this->getModuleName(), $index, 'revisions'], null, $rev); } if (!$fit) { if ($mode == 'all') { $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; } } } if ($resultPageSet !== null) { if ($params['generatetitles']) { $resultPageSet->populateFromTitles($generated); } else { $resultPageSet->populateFromRevisionIDs($generated); } } else { $result->addIndexedTagName(['query', $this->getModuleName()], 'page'); } }
/** * Generates each row in the contributions list. * * @todo This would probably look a lot nicer in a table. * @param stdClass $row * @return string */ function formatRow($row) { $ret = ''; $classes = []; /* * There may be more than just revision rows. To make sure that we'll only be processing * revisions here, let's _try_ to build a revision out of our row (without displaying * notices though) and then trying to grab data from the built object. If we succeed, * we're definitely dealing with revision data and we may proceed, if not, we'll leave it * to extensions to subscribe to the hook to parse the row. */ MediaWiki\suppressWarnings(); try { $rev = Revision::newFromArchiveRow($row); $validRevision = (bool) $rev->getId(); } catch (Exception $e) { $validRevision = false; } MediaWiki\restoreWarnings(); if ($validRevision) { $ret = $this->formatRevisionRow($row); } // Let extensions add data Hooks::run('DeletedContributionsLineEnding', [$this, &$ret, $row, &$classes]); if ($classes === [] && $ret === '') { wfDebug("Dropping Special:DeletedContribution row that could not be formatted\n"); $ret = "<!-- Could not format Special:DeletedContribution row. -->\n"; } else { $ret = Html::rawElement('li', ['class' => $classes], $ret) . "\n"; } return $ret; }
/** * @covers Revision::newFromArchiveRow */ public function testNewFromArchiveRow() { $page = $this->createPage('RevisionStorageTest_testNewFromArchiveRow', 'Lorem Ipsum', CONTENT_MODEL_WIKITEXT); $orig = $page->getRevision(); $page->doDeleteArticle('test Revision::newFromArchiveRow'); $dbr = wfGetDB(DB_SLAVE); $res = $dbr->select('archive', '*', ['ar_rev_id' => $orig->getId()]); $this->assertTrue(is_object($res), 'query failed'); $row = $res->fetchObject(); $res->free(); $rev = Revision::newFromArchiveRow($row); $this->assertRevEquals($orig, $rev); }
/** * @param $row * @return bool */ protected function upgradeLegacyArchiveRow($row) { $db = $this->getDB(DB_MASTER); $rev = Revision::newFromArchiveRow($row); $text = $rev->getRawText(); if (!is_string($text)) { # This should not happen, but sometimes does (bug 20757) $this->output("Text of revision with timestamp {$row->ar_timestamp} unavailable!\n"); return false; } else { # Archive table as no PK, but (NS,title,time) should be near unique. # Any duplicates on those should also have duplicated text anyway. $db->update('archive', array('ar_sha1' => Revision::base36Sha1($text)), array('ar_namespace' => $row->ar_namespace, 'ar_title' => $row->ar_title, 'ar_timestamp' => $row->ar_timestamp, 'ar_len' => $row->ar_len), __METHOD__); return true; } }
private function formatRevisionRow($row, $earliestLiveTime, $remaining) { $rev = Revision::newFromArchiveRow($row, array('page' => $this->mTargetObj->getArticleId())); $stxt = ''; $ts = wfTimestamp(TS_MW, $row->ar_timestamp); // Build checkboxen... if ($this->mAllowed) { if ($this->mInvert) { if (in_array($ts, $this->mTargetTimestamp)) { $checkBox = Xml::check("ts{$ts}"); } else { $checkBox = Xml::check("ts{$ts}", true); } } else { $checkBox = Xml::check("ts{$ts}"); } } else { $checkBox = ''; } $user = $this->getUser(); // Build page & diff links... if ($this->mCanView) { $titleObj = $this->getTitle(); # Last link if (!$rev->userCan(Revision::DELETED_TEXT, $this->getUser())) { $pageLink = htmlspecialchars($this->getLanguage()->userTimeAndDate($ts, $user)); $last = $this->msg('diff')->escaped(); } elseif ($remaining > 0 || $earliestLiveTime && $ts > $earliestLiveTime) { $pageLink = $this->getPageLink($rev, $titleObj, $ts); $last = Linker::linkKnown($titleObj, $this->msg('diff')->escaped(), array(), array('target' => $this->mTargetObj->getPrefixedText(), 'timestamp' => $ts, 'diff' => 'prev')); } else { $pageLink = $this->getPageLink($rev, $titleObj, $ts); $last = $this->msg('diff')->escaped(); } } else { $pageLink = htmlspecialchars($this->getLanguage()->userTimeAndDate($ts, $user)); $last = $this->msg('diff')->escaped(); } // User links $userLink = Linker::revUserTools($rev); // Revision text size $size = $row->ar_len; if (!is_null($size)) { $stxt = Linker::formatRevisionSize($size); } // Edit summary $comment = Linker::revComment($rev); // Revision delete links $revdlink = Linker::getRevDeleteLink($user, $rev, $this->mTargetObj); return "<li>{$checkBox} {$revdlink} ({$last}) {$pageLink} . . {$userLink} {$stxt} {$comment}</li>"; }
/** * @param $row * @param string $table * @param string $idCol * @param string $prefix * @return bool */ protected function upgradeRow($row, $table, $idCol, $prefix) { $db = $this->getDB(DB_MASTER); $rev = $table === 'archive' ? Revision::newFromArchiveRow($row) : new Revision($row); $content = $rev->getContent(); if (!$content) { # This should not happen, but sometimes does (bug 20757) $id = $row->{$idCol}; $this->output("Content of {$table} {$id} unavailable!\n"); return false; } # Update the row... $db->update($table, array("{$prefix}_len" => $content->getSize()), array($idCol => $row->{$idCol}), __METHOD__); return true; }
/** * Look up a special:Undelete link to the given deleted revision id, * as a workaround for being unable to load deleted diffs in currently. * * @param int $id Revision ID * * @return string|bool Link HTML or false */ public function deletedLink($id) { if ($this->getUser()->isAllowed('deletedhistory')) { $dbr = wfGetDB(DB_REPLICA); $row = $dbr->selectRow('archive', '*', ['ar_rev_id' => $id], __METHOD__); if ($row) { $rev = Revision::newFromArchiveRow($row); $title = Title::makeTitleSafe($row->ar_namespace, $row->ar_title); return SpecialPage::getTitleFor('Undelete')->getFullURL(['target' => $title->getPrefixedText(), 'timestamp' => $rev->getTimestamp()]); } } return false; }
private function formatRevisionRow($row, $earliestLiveTime, $remaining, $sk) { global $wgUser, $wgLang; $rev = Revision::newFromArchiveRow($row, array('page' => $this->mTargetObj->getArticleId())); $stxt = ''; $ts = wfTimestamp(TS_MW, $row->ar_timestamp); // Build checkboxen... if ($this->mAllowed) { if ($this->mInvert) { if (in_array($ts, $this->mTargetTimestamp)) { $checkBox = Xml::check("ts{$ts}"); } else { $checkBox = Xml::check("ts{$ts}", true); } } else { $checkBox = Xml::check("ts{$ts}"); } } else { $checkBox = ''; } // Build page & diff links... if ($this->mCanView) { $titleObj = SpecialPage::getTitleFor("Undelete"); # Last link if (!$rev->userCan(Revision::DELETED_TEXT)) { $pageLink = htmlspecialchars($wgLang->timeanddate($ts, true)); $last = wfMsgHtml('diff'); } else { if ($remaining > 0 || $earliestLiveTime && $ts > $earliestLiveTime) { $pageLink = $this->getPageLink($rev, $titleObj, $ts, $sk); $last = $sk->linkKnown($titleObj, wfMsgHtml('diff'), array(), array('target' => $this->mTargetObj->getPrefixedText(), 'timestamp' => $ts, 'diff' => 'prev')); } else { $pageLink = $this->getPageLink($rev, $titleObj, $ts, $sk); $last = wfMsgHtml('diff'); } } } else { $pageLink = htmlspecialchars($wgLang->timeanddate($ts, true)); $last = wfMsgHtml('diff'); } // User links $userLink = $sk->revUserTools($rev); // Revision text size if (!is_null($size = $row->ar_len)) { $stxt = $sk->formatRevisionSize($size); } // Edit summary $comment = $sk->revComment($rev); // Revision delete links $canHide = $wgUser->isAllowed('deleterevision'); if ($canHide || $rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) { if (!$rev->userCan(Revision::DELETED_RESTRICTED)) { $revdlink = $sk->revDeleteLinkDisabled($canHide); // revision was hidden from sysops } else { $query = array('type' => 'archive', 'target' => $this->mTargetObj->getPrefixedDBkey(), 'ids' => $ts); $revdlink = $sk->revDeleteLink($query, $rev->isDeleted(Revision::DELETED_RESTRICTED), $canHide); } } else { $revdlink = ''; } return "<li>{$checkBox} {$revdlink} ({$last}) {$pageLink} . . {$userLink} {$stxt} {$comment}</li>"; }