/** * Render the header of a diff page including: * Name with url to page * Bytes added/removed * Day and time of edit * Edit Comment */ function showHeader() { $title = $this->targetTitle; if ($this->prevRev) { $bytesChanged = $this->rev->getSize() - $this->prevRev->getSize(); } else { $bytesChanged = $this->rev->getSize(); } if ($bytesChanged > 0) { $changeMsg = 'mobile-frontend-diffview-bytesadded'; $sizeClass = MobileUI::iconClass('bytesadded', 'before', 'icon-12px meta mw-mf-bytesadded'); } elseif ($bytesChanged === 0) { $changeMsg = 'mobile-frontend-diffview-bytesnochange'; $sizeClass = MobileUI::iconClass('bytesneutral', 'before', 'icon-12px meta mw-mf-bytesneutral'); } else { $changeMsg = 'mobile-frontend-diffview-bytesremoved'; $sizeClass = MobileUI::iconClass('bytesremoved', 'before', 'icon-12px meta mw-mf-bytesremoved'); $bytesChanged = abs($bytesChanged); } if ($this->rev->isMinor()) { $minor = ChangesList::flag('minor'); } else { $minor = ''; } if ($this->rev->getComment() !== '') { $comment = Linker::formatComment($this->rev->getComment(), $title); } else { $comment = $this->msg('mobile-frontend-changeslist-nocomment')->escaped(); } $ts = new MWTimestamp($this->rev->getTimestamp()); $this->getOutput()->addHtml(Html::openElement('div', array('id' => 'mw-mf-diff-info', 'class' => 'page-summary')) . Html::openElement('h2', array()) . Html::element('a', array('href' => $title->getLocalURL()), $title->getPrefixedText()) . Html::closeElement('h2') . $this->msg('mobile-frontend-diffview-comma')->rawParams(Html::element('span', array('class' => $sizeClass), $this->msg($changeMsg)->numParams($bytesChanged)->text()), Html::element('span', array('class' => 'mw-mf-diff-date meta'), $ts->getHumanTimestamp()))->text() . Html::closeElement('div') . $minor . Html::rawElement('div', array('id' => 'mw-mf-diff-comment'), $comment)); }
/** * Get the HTML link to the revision text. * Overridden by RevDelArchiveItem. * @return string */ protected function getRevisionLink() { $date = htmlspecialchars($this->list->getLanguage()->userTimeAndDate($this->revision->getTimestamp(), $this->list->getUser())); if ($this->isDeleted() && !$this->canViewContent()) { return $date; } return Linker::linkKnown($this->list->title, $date, array(), array('oldid' => $this->revision->getId(), 'unhide' => 1)); }
protected function assertRevEquals(Revision $orig, Revision $rev = null) { $this->assertNotNull($rev, 'missing revision'); $this->assertEquals($orig->getId(), $rev->getId()); $this->assertEquals($orig->getPage(), $rev->getPage()); $this->assertEquals($orig->getTimestamp(), $rev->getTimestamp()); $this->assertEquals($orig->getUser(), $rev->getUser()); $this->assertEquals($orig->getSha1(), $rev->getSha1()); }
/** * @return String: timestamp */ function getTimestamp() { if ($this->mRevision) { return $this->mRevision->getTimestamp(); } elseif ($this->mImage) { return $this->mImage->getTimestamp(); } return ''; }
/** * @param Title $pageTitle Title instance of the categorized page * @param Revision $revision Latest Revision instance of the categorized page * * @throws MWException */ public function __construct(Title $pageTitle, Revision $revision = null) { $this->pageTitle = $pageTitle; if ($revision === null) { $this->timestamp = wfTimestampNow(); } else { $this->timestamp = $revision->getTimestamp(); } $this->revision = $revision; $this->newForCategorizationCallback = ['RecentChange', 'newForCategorization']; }
/** * Update the revision's recentchanges record if fields have been hidden * @param Revision $rev * @param int $bitfield new rev_deleted bitfield value */ function updateRecentChangesEdits($rev, $bitfield) { $this->dbw->update('recentchanges', array('rc_deleted' => $bitfield, 'rc_patrolled' => 1), array('rc_this_oldid' => $rev->getId(), 'rc_timestamp' => $this->dbw->timestamp($rev->getTimestamp())), __METHOD__); }
private function extractRowInfo($row) { $revision = new Revision($row); $title = $revision->getTitle(); $vals = array(); if ($this->fld_ids) { $vals['revid'] = intval($revision->getId()); // $vals['oldid'] = intval( $row->rev_text_id ); // todo: should this be exposed? if (!is_null($revision->getParentId())) { $vals['parentid'] = intval($revision->getParentId()); } } if ($this->fld_flags && $revision->isMinor()) { $vals['minor'] = ''; } if ($this->fld_user || $this->fld_userid) { if ($revision->isDeleted(Revision::DELETED_USER)) { $vals['userhidden'] = ''; } else { if ($this->fld_user) { $vals['user'] = $revision->getUserText(); } $userid = $revision->getUser(); if (!$userid) { $vals['anon'] = ''; } if ($this->fld_userid) { $vals['userid'] = $userid; } } } if ($this->fld_timestamp) { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $revision->getTimestamp()); } if ($this->fld_size) { if (!is_null($revision->getSize())) { $vals['size'] = intval($revision->getSize()); } else { $vals['size'] = 0; } } if ($this->fld_sha1) { if ($revision->getSha1() != '') { $vals['sha1'] = wfBaseConvert($revision->getSha1(), 36, 16, 40); } else { $vals['sha1'] = ''; } } if ($this->fld_comment || $this->fld_parsedcomment) { if ($revision->isDeleted(Revision::DELETED_COMMENT)) { $vals['commenthidden'] = ''; } else { $comment = $revision->getComment(); if ($this->fld_comment) { $vals['comment'] = $comment; } if ($this->fld_parsedcomment) { $vals['parsedcomment'] = Linker::formatComment($comment, $title); } } } if ($this->fld_tags) { if ($row->ts_tags) { $tags = explode(',', $row->ts_tags); $this->getResult()->setIndexedTagName($tags, 'tag'); $vals['tags'] = $tags; } else { $vals['tags'] = array(); } } if (!is_null($this->token)) { $tokenFunctions = $this->getTokenFunctions(); foreach ($this->token as $t) { $val = call_user_func($tokenFunctions[$t], $title->getArticleID(), $title, $revision); if ($val === false) { $this->setWarning("Action '{$t}' is not allowed for the current user"); } else { $vals[$t . 'token'] = $val; } } } $text = null; global $wgParser; if ($this->fld_content || !is_null($this->difftotext)) { $text = $revision->getText(); // Expand templates after getting section content because // template-added sections don't count and Parser::preprocess() // will have less input if ($this->section !== false) { $text = $wgParser->getSection($text, $this->section, false); if ($text === false) { $this->dieUsage("There is no section {$this->section} in r" . $revision->getId(), 'nosuchsection'); } } } if ($this->fld_content && !$revision->isDeleted(Revision::DELETED_TEXT)) { if ($this->generateXML) { $wgParser->startExternalParse($title, ParserOptions::newFromContext($this->getContext()), OT_PREPROCESS); $dom = $wgParser->preprocessToDom($text); if (is_callable(array($dom, 'saveXML'))) { $xml = $dom->saveXML(); } else { $xml = $dom->__toString(); } $vals['parsetree'] = $xml; } if ($this->expandTemplates && !$this->parseContent) { $text = $wgParser->preprocess($text, $title, ParserOptions::newFromContext($this->getContext())); } if ($this->parseContent) { $text = $wgParser->parse($text, $title, ParserOptions::newFromContext($this->getContext()))->getText(); } ApiResult::setContent($vals, $text); } elseif ($this->fld_content) { $vals['texthidden'] = ''; } if (!is_null($this->diffto) || !is_null($this->difftotext)) { global $wgAPIMaxUncachedDiffs; static $n = 0; // Number of uncached diffs we've had if ($n < $wgAPIMaxUncachedDiffs) { $vals['diff'] = array(); $context = new DerivativeContext($this->getContext()); $context->setTitle($title); if (!is_null($this->difftotext)) { $engine = new DifferenceEngine($context); $engine->setText($text, $this->difftotext); } else { $engine = new DifferenceEngine($context, $revision->getID(), $this->diffto); $vals['diff']['from'] = $engine->getOldid(); $vals['diff']['to'] = $engine->getNewid(); } $difftext = $engine->getDiffBody(); ApiResult::setContent($vals['diff'], $difftext); if (!$engine->wasCacheHit()) { $n++; } } else { $vals['diff']['notcached'] = ''; } } return $vals; }
private function getExplicitCategoriesChanges(Title $title, Revision $newRev, Revision $oldRev = null) { // Inject the same timestamp for both revision parses to avoid seeing category changes // due to time-based parser functions. Inject the same page title for the parses too. // Note that REPEATABLE-READ makes template/file pages appear unchanged between parses. $parseTimestamp = $newRev->getTimestamp(); // Parse the old rev and get the categories. Do not use link tables as that // assumes these updates are perfectly FIFO and that link tables are always // up to date, neither of which are true. $oldCategories = $oldRev ? $this->getCategoriesAtRev($title, $oldRev, $parseTimestamp) : []; // Parse the new revision and get the categories $newCategories = $this->getCategoriesAtRev($title, $newRev, $parseTimestamp); $categoryInserts = array_values(array_diff($newCategories, $oldCategories)); $categoryDeletes = array_values(array_diff($oldCategories, $newCategories)); return [$categoryInserts, $categoryDeletes]; }
/** * Hook for deletion archive revision view, giving us a chance to * insert a removal tab for a deleted revision. * * @param $title * @param Revision $rev */ function hrUndeleteShowRevisionHook($title, $rev) { hrInstallArchiveTab($title, $rev->getTimestamp()); return true; }
/** * If the given revision is newer than the currently set page_latest, * update the page record. Otherwise, do nothing. * * @param Database $dbw * @param Revision $revision */ function updateIfNewerOn(&$dbw, $revision) { $fname = 'Article::updateIfNewerOn'; wfProfileIn($fname); $row = $dbw->selectRow(array('revision', 'page'), array('rev_id', 'rev_timestamp'), array('page_id' => $this->getId(), 'page_latest=rev_id'), $fname); if ($row) { if ($row->rev_timestamp >= $revision->getTimestamp()) { wfProfileOut($fname); return false; } $prev = $row->rev_id; } else { # No or missing previous revision; mark the page as new $prev = 0; } $ret = $this->updateRevisionOn($dbw, $revision, $prev); wfProfileOut($fname); return $ret; }
/** * Get a header for a specified revision. * * @param Revision $rev * @param string $complete 'complete' to get the header wrapped depending * the visibility of the revision and a link to edit the page. * * @return string HTML fragment */ protected function getRevisionHeader(Revision $rev, $complete = '') { $lang = $this->getLanguage(); $user = $this->getUser(); $revtimestamp = $rev->getTimestamp(); $timestamp = $lang->userTimeAndDate($revtimestamp, $user); $dateofrev = $lang->userDate($revtimestamp, $user); $timeofrev = $lang->userTime($revtimestamp, $user); $header = $this->msg($rev->isCurrent() ? 'currentrev-asof' : 'revisionasof', $timestamp, $dateofrev, $timeofrev)->escaped(); if ($complete !== 'complete') { return $header; } $title = $rev->getTitle(); $header = Linker::linkKnown($title, $header, array(), array('oldid' => $rev->getID())); if ($rev->userCan(Revision::DELETED_TEXT, $user)) { $editQuery = array('action' => 'edit'); if (!$rev->isCurrent()) { $editQuery['oldid'] = $rev->getID(); } $key = $title->quickUserCan('edit', $user) ? 'editold' : 'viewsourceold'; $msg = $this->msg($key)->escaped(); $editLink = $this->msg('parentheses')->rawParams(Linker::linkKnown($title, $msg, array(), $editQuery))->escaped(); $header .= ' ' . Html::rawElement('span', array('class' => 'mw-diff-edit'), $editLink); if ($rev->isDeleted(Revision::DELETED_TEXT)) { $header = Html::rawElement('span', array('class' => 'history-deleted'), $header); } } else { $header = Html::rawElement('span', array('class' => 'history-deleted'), $header); } return $header; }
/** * If patrol is possible, output a patrol UI box. This is called from the * footer section of ordinary page views. If patrol is not possible or not * desired, does nothing. * Side effect: When the patrol link is build, this method will call * OutputPage::preventClickjacking() and load mediawiki.page.patrol.ajax. * * @return bool */ public function showPatrolFooter() { global $wgUseNPPatrol, $wgUseRCPatrol, $wgEnableAPI, $wgEnableWriteAPI; $outputPage = $this->getContext()->getOutput(); $user = $this->getContext()->getUser(); $cache = wfGetMainCache(); $rc = false; if (!$this->getTitle()->quickUserCan('patrol', $user) || !($wgUseRCPatrol || $wgUseNPPatrol)) { // Patrolling is disabled or the user isn't allowed to return false; } wfProfileIn(__METHOD__); // New page patrol: Get the timestamp of the oldest revison which // the revision table holds for the given page. Then we look // whether it's within the RC lifespan and if it is, we try // to get the recentchanges row belonging to that entry // (with rc_new = 1). // Check for cached results if ($cache->get(wfMemcKey('NotPatrollablePage', $this->getTitle()->getArticleID()))) { wfProfileOut(__METHOD__); return false; } if ($this->mRevision && !RecentChange::isInRCLifespan($this->mRevision->getTimestamp(), 21600)) { // The current revision is already older than what could be in the RC table // 6h tolerance because the RC might not be cleaned out regularly wfProfileOut(__METHOD__); return false; } $dbr = wfGetDB(DB_SLAVE); $oldestRevisionTimestamp = $dbr->selectField('revision', 'MIN( rev_timestamp )', array('rev_page' => $this->getTitle()->getArticleID()), __METHOD__); if ($oldestRevisionTimestamp && RecentChange::isInRCLifespan($oldestRevisionTimestamp, 21600)) { // 6h tolerance because the RC might not be cleaned out regularly $rc = RecentChange::newFromConds(array('rc_new' => 1, 'rc_timestamp' => $oldestRevisionTimestamp, 'rc_namespace' => $this->getTitle()->getNamespace(), 'rc_cur_id' => $this->getTitle()->getArticleID(), 'rc_patrolled' => 0), __METHOD__, array('USE INDEX' => 'new_name_timestamp')); } if (!$rc) { // No RC entry around // Cache the information we gathered above in case we can't patrol // Don't cache in case we can patrol as this could change $cache->set(wfMemcKey('NotPatrollablePage', $this->getTitle()->getArticleID()), '1'); wfProfileOut(__METHOD__); return false; } if ($rc->getPerformer()->getName() == $user->getName()) { // Don't show a patrol link for own creations. If the user could // patrol them, they already would be patrolled wfProfileOut(__METHOD__); return false; } $rcid = $rc->getAttribute('rc_id'); $token = $user->getEditToken($rcid); $outputPage->preventClickjacking(); if ($wgEnableAPI && $wgEnableWriteAPI && $user->isAllowed('writeapi')) { $outputPage->addModules('mediawiki.page.patrol.ajax'); } $link = Linker::linkKnown($this->getTitle(), wfMessage('markaspatrolledtext')->escaped(), array('class' => 'mw-patrollink'), array('action' => 'markpatrolled', 'rcid' => $rcid, 'token' => $token)); $outputPage->addHTML("<div class='patrollink'>" . wfMessage('markaspatrolledlink')->rawParams($link)->escaped() . '</div>'); wfProfileOut(__METHOD__); return true; }
/** * Create a link to view this revision of the page * * @param Revision $rev * @return string */ function revLink($rev) { $date = $this->getLanguage()->userTimeAndDate($rev->getTimestamp(), $this->getUser()); $date = htmlspecialchars($date); if ($rev->userCan(Revision::DELETED_TEXT, $this->getUser())) { $link = Linker::linkKnown($this->getTitle(), $date, array('class' => 'mw-changeslist-date'), array('oldid' => $rev->getId())); } else { $link = $date; } if ($rev->isDeleted(Revision::DELETED_TEXT)) { $link = "<span class=\"history-deleted\">{$link}</span>"; } return $link; }
protected static function getDiffRevMsgAndClass(Revision $rev, FlaggedRevision $srev = null) { $tier = FlaggedRevision::getRevQuality($rev->getId()); if ($tier !== false) { $msg = $tier ? 'revreview-hist-quality' : 'revreview-hist-basic'; } else { $msg = $srev && $rev->getTimestamp() > $srev->getRevTimestamp() ? 'revreview-hist-pending' : 'revreview-hist-draft'; } $css = FlaggedRevsXML::getQualityColor($tier); return array($msg, $css); }
/** * Generates each row in the contributions list. * * Contributions which are marked "top" are currently on top of the history. * For these contributions, a [rollback] link is shown for users with sysop * privileges. The rollback link restores the most recent version that was not * written by the target user. * * @todo This would probably look a lot nicer in a table. */ function formatRow($row) { global $wgUser, $wgLang; wfProfileIn(__METHOD__); $sk = $this->getSkin(); $rev = new Revision(array('id' => $row->ar_rev_id, 'comment' => $row->ar_comment, 'user' => $row->ar_user, 'user_text' => $row->ar_user_text, 'timestamp' => $row->ar_timestamp, 'minor_edit' => $row->ar_minor_edit, 'deleted' => $row->ar_deleted)); $page = Title::makeTitle($row->ar_namespace, $row->ar_title); $undelete = SpecialPage::getTitleFor('Undelete'); $logs = SpecialPage::getTitleFor('Log'); $dellog = $sk->linkKnown($logs, $this->messages['deletionlog'], array(), array('type' => 'delete', 'page' => $page->getPrefixedText())); $reviewlink = $sk->linkKnown(SpecialPage::getTitleFor('Undelete', $page->getPrefixedDBkey()), $this->messages['undeleteviewlink']); if ($wgUser->isAllowed('deletedtext')) { $last = $sk->linkKnown($undelete, $this->messages['diff'], array(), array('target' => $page->getPrefixedText(), 'timestamp' => $rev->getTimestamp(), 'diff' => 'prev')); } else { $last = $this->messages['diff']; } $comment = $sk->revComment($rev); $date = htmlspecialchars($wgLang->timeanddate($rev->getTimestamp(), true)); if (!$wgUser->isAllowed('undelete') || !$rev->userCan(Revision::DELETED_TEXT)) { $link = $date; // unusable link } else { $link = $sk->linkKnown($undelete, $date, array(), array('target' => $page->getPrefixedText(), 'timestamp' => $rev->getTimestamp())); } // Style deleted items if ($rev->isDeleted(Revision::DELETED_TEXT)) { $link = '<span class="history-deleted">' . $link . '</span>'; } $pagelink = $sk->link($page); if ($rev->isMinor()) { $mflag = ChangesList::flag('minor'); } else { $mflag = ''; } // Revision delete link $canHide = $wgUser->isAllowed('deleterevision'); if ($canHide || $rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) { if (!$rev->userCan(Revision::DELETED_RESTRICTED)) { $del = $this->mSkin->revDeleteLinkDisabled($canHide); // revision was hidden from sysops } else { $query = array('type' => 'archive', 'target' => $page->getPrefixedDbkey(), 'ids' => $rev->getTimestamp()); $del = $this->mSkin->revDeleteLink($query, $rev->isDeleted(Revision::DELETED_RESTRICTED), $canHide) . ' '; } } else { $del = ''; } $tools = Html::rawElement('span', array('class' => 'mw-deletedcontribs-tools'), wfMsg('parentheses', $wgLang->pipeList(array($last, $dellog, $reviewlink)))); $ret = "{$del}{$link} {$tools} . . {$mflag} {$pagelink} {$comment}"; # Denote if username is redacted for this edit if ($rev->isDeleted(Revision::DELETED_USER)) { $ret .= " <strong>" . wfMsgHtml('rev-deleted-user-contribs') . "</strong>"; } $ret = Html::rawElement('li', array(), $ret) . "\n"; wfProfileOut(__METHOD__); return $ret; }
/** * @param Revision $rev * @returns string */ function historyLine($rev) { global $wgContLang; $date = $wgContLang->timeanddate($rev->getTimestamp()); return "<li>" . $this->skin->makeLinkObj($this->page, $date, 'oldid=' . $rev->getId()) . " " . $this->skin->revUserLink($rev) . " " . $this->skin->revComment($rev) . "</li>"; }
/** * Generates each row in the contributions list. * * Contributions which are marked "top" are currently on top of the history. * For these contributions, a [rollback] link is shown for users with sysop * privileges. The rollback link restores the most recent version that was not * written by the target user. * * @todo This would probably look a lot nicer in a table. */ function formatRow($row) { wfProfileIn(__METHOD__); global $wgLang, $wgUser; $sk = $this->getSkin(); $rev = new Revision(array('id' => $row->ar_rev_id, 'comment' => $row->ar_comment, 'user' => $row->ar_user, 'user_text' => $row->ar_user_text, 'timestamp' => $row->ar_timestamp, 'minor_edit' => $row->ar_minor_edit, 'rev_deleted' => $row->ar_deleted)); $page = Title::makeTitle($row->ar_namespace, $row->ar_title); $undelete = SpecialPage::getTitleFor('Undelete'); $logs = SpecialPage::getTitleFor('Log'); $dellog = $sk->makeKnownLinkObj($logs, $this->messages['deletionlog'], 'type=delete&page=' . $page->getPrefixedUrl()); $reviewlink = $sk->makeKnownLinkObj(SpecialPage::getTitleFor('Undelete', $page->getPrefixedDBkey()), $this->messages['undeletebtn']); $link = $sk->makeKnownLinkObj($undelete, htmlspecialchars($page->getPrefixedText()), 'target=' . $page->getPrefixedUrl() . '×tamp=' . $rev->getTimestamp()); $last = $sk->makeKnownLinkObj($undelete, $this->messages['diff'], "target=" . $page->getPrefixedUrl() . "×tamp=" . $rev->getTimestamp() . "&diff=prev"); $comment = $sk->revComment($rev); $d = $wgLang->timeanddate($rev->getTimestamp(), true); if ($rev->isDeleted(Revision::DELETED_TEXT)) { $d = '<span class="history-deleted">' . $d . '</span>'; } else { $link = $sk->makeKnownLinkObj($undelete, $d, 'target=' . $page->getPrefixedUrl() . '×tamp=' . $rev->getTimestamp()); } $pagelink = $sk->makeLinkObj($page); if ($rev->isMinor()) { $mflag = '<span class="minor">' . $this->messages['minoreditletter'] . '</span> '; } else { $mflag = ''; } $ret = "{$link} ({$last}) ({$dellog}) ({$reviewlink}) . . {$mflag} {$pagelink} {$comment}"; if ($rev->isDeleted(Revision::DELETED_TEXT)) { $ret .= ' ' . wfMsgHtml('deletedrev'); } $ret = "<li>{$ret}</li>\n"; wfProfileOut(__METHOD__); return $ret; }
/** * @param Revision $rev * @param string $prefix * @return string */ private function diffHeader($rev, $prefix) { $isDeleted = !($rev->getId() && $rev->getTitle()); if ($isDeleted) { /// @todo FIXME: $rev->getTitle() is null for deleted revs...? $targetPage = $this->getTitle(); $targetQuery = array('target' => $this->mTargetObj->getPrefixedText(), 'timestamp' => wfTimestamp(TS_MW, $rev->getTimestamp())); } else { /// @todo FIXME: getId() may return non-zero for deleted revs... $targetPage = $rev->getTitle(); $targetQuery = array('oldid' => $rev->getId()); } // Add show/hide deletion links if available $user = $this->getUser(); $lang = $this->getLanguage(); $rdel = Linker::getRevDeleteLink($user, $rev, $this->mTargetObj); if ($rdel) { $rdel = " {$rdel}"; } return '<div id="mw-diff-' . $prefix . 'title1"><strong>' . Linker::link($targetPage, $this->msg('revisionasof', $lang->userTimeAndDate($rev->getTimestamp(), $user), $lang->userDate($rev->getTimestamp(), $user), $lang->userTime($rev->getTimestamp(), $user))->escaped(), array(), $targetQuery) . '</strong></div>' . '<div id="mw-diff-' . $prefix . 'title2">' . Linker::revUserTools($rev) . '<br />' . '</div>' . '<div id="mw-diff-' . $prefix . 'title3">' . Linker::revComment($rev) . $rdel . '<br />' . '</div>'; }
/** * Get a revision-deletion link, or disabled link, or nothing, depending * on user permissions & the settings on the revision. * * Will use forward-compatible revision ID in the Special:RevDelete link * if possible, otherwise the timestamp-based ID which may break after * undeletion. * * @param User $user * @param Revision $rev * @param Revision $title * @return string HTML fragment */ public static function getRevDeleteLink(User $user, Revision $rev, Title $title) { $canHide = $user->isAllowed('deleterevision'); if (!$canHide && !($rev->getVisibility() && $user->isAllowed('deletedhistory'))) { return ''; } if (!$rev->userCan(Revision::DELETED_RESTRICTED, $user)) { return Linker::revDeleteLinkDisabled($canHide); // revision was hidden from sysops } else { if ($rev->getId()) { // RevDelete links using revision ID are stable across // page deletion and undeletion; use when possible. $query = array('type' => 'revision', 'target' => $title->getPrefixedDBkey(), 'ids' => $rev->getId()); } else { // Older deleted entries didn't save a revision ID. // We have to refer to these by timestamp, ick! $query = array('type' => 'archive', 'target' => $title->getPrefixedDBkey(), 'ids' => $rev->getTimestamp()); } return Linker::revDeleteLink($query, $rev->isDeleted(Revision::DELETED_RESTRICTED), $canHide); } }
/** * @param Revision $rev * @param string $prefix * @return string */ private function diffHeader($rev, $prefix) { $isDeleted = !($rev->getId() && $rev->getTitle()); if ($isDeleted) { /// @todo FIXME: $rev->getTitle() is null for deleted revs...? $targetPage = $this->getPageTitle(); $targetQuery = array('target' => $this->mTargetObj->getPrefixedText(), 'timestamp' => wfTimestamp(TS_MW, $rev->getTimestamp())); } else { /// @todo FIXME: getId() may return non-zero for deleted revs... $targetPage = $rev->getTitle(); $targetQuery = array('oldid' => $rev->getId()); } // Add show/hide deletion links if available $user = $this->getUser(); $lang = $this->getLanguage(); $rdel = Linker::getRevDeleteLink($user, $rev, $this->mTargetObj); if ($rdel) { $rdel = " {$rdel}"; } $minor = $rev->isMinor() ? ChangesList::flag('minor') : ''; $tags = wfGetDB(DB_SLAVE)->selectField('tag_summary', 'ts_tags', array('ts_rev_id' => $rev->getId()), __METHOD__); $tagSummary = ChangeTags::formatSummaryRow($tags, 'deleteddiff'); // FIXME This is reimplementing DifferenceEngine#getRevisionHeader // and partially #showDiffPage, but worse return '<div id="mw-diff-' . $prefix . 'title1"><strong>' . Linker::link($targetPage, $this->msg('revisionasof', $lang->userTimeAndDate($rev->getTimestamp(), $user), $lang->userDate($rev->getTimestamp(), $user), $lang->userTime($rev->getTimestamp(), $user))->escaped(), array(), $targetQuery) . '</strong></div>' . '<div id="mw-diff-' . $prefix . 'title2">' . Linker::revUserTools($rev) . '<br />' . '</div>' . '<div id="mw-diff-' . $prefix . 'title3">' . $minor . Linker::revComment($rev) . $rdel . '<br />' . '</div>' . '<div id="mw-diff-' . $prefix . 'title5">' . $tagSummary[0] . '<br />' . '</div>'; }
/** * Extract information from the Revision * * @param Revision $revision * @param object $row Should have a field 'ts_tags' if $this->fld_tags is set * @return array */ protected function extractRevisionInfo(Revision $revision, $row) { $title = $revision->getTitle(); $user = $this->getUser(); $vals = array(); $anyHidden = false; if ($this->fld_ids) { $vals['revid'] = intval($revision->getId()); if (!is_null($revision->getParentId())) { $vals['parentid'] = intval($revision->getParentId()); } } if ($this->fld_flags) { $vals['minor'] = $revision->isMinor(); } if ($this->fld_user || $this->fld_userid) { if ($revision->isDeleted(Revision::DELETED_USER)) { $vals['userhidden'] = true; $anyHidden = true; } if ($revision->userCan(Revision::DELETED_USER, $user)) { if ($this->fld_user) { $vals['user'] = $revision->getUserText(Revision::RAW); } $userid = $revision->getUser(Revision::RAW); if (!$userid) { $vals['anon'] = true; } if ($this->fld_userid) { $vals['userid'] = $userid; } } } if ($this->fld_timestamp) { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $revision->getTimestamp()); } if ($this->fld_size) { if (!is_null($revision->getSize())) { $vals['size'] = intval($revision->getSize()); } else { $vals['size'] = 0; } } if ($this->fld_sha1) { if ($revision->isDeleted(Revision::DELETED_TEXT)) { $vals['sha1hidden'] = true; $anyHidden = true; } if ($revision->userCan(Revision::DELETED_TEXT, $user)) { if ($revision->getSha1() != '') { $vals['sha1'] = wfBaseConvert($revision->getSha1(), 36, 16, 40); } else { $vals['sha1'] = ''; } } } if ($this->fld_contentmodel) { $vals['contentmodel'] = $revision->getContentModel(); } if ($this->fld_comment || $this->fld_parsedcomment) { if ($revision->isDeleted(Revision::DELETED_COMMENT)) { $vals['commenthidden'] = true; $anyHidden = true; } if ($revision->userCan(Revision::DELETED_COMMENT, $user)) { $comment = $revision->getComment(Revision::RAW); if ($this->fld_comment) { $vals['comment'] = $comment; } if ($this->fld_parsedcomment) { $vals['parsedcomment'] = Linker::formatComment($comment, $title); } } } if ($this->fld_tags) { if ($row->ts_tags) { $tags = explode(',', $row->ts_tags); ApiResult::setIndexedTagName($tags, 'tag'); $vals['tags'] = $tags; } else { $vals['tags'] = array(); } } $content = null; global $wgParser; if ($this->fetchContent) { $content = $revision->getContent(Revision::FOR_THIS_USER, $this->getUser()); // Expand templates after getting section content because // template-added sections don't count and Parser::preprocess() // will have less input if ($content && $this->section !== false) { $content = $content->getSection($this->section, false); if (!$content) { $this->dieUsage("There is no section {$this->section} in r" . $revision->getId(), 'nosuchsection'); } } if ($revision->isDeleted(Revision::DELETED_TEXT)) { $vals['texthidden'] = true; $anyHidden = true; } elseif (!$content) { $vals['textmissing'] = true; } } if ($this->fld_content && $content) { $text = null; if ($this->generateXML) { if ($content->getModel() === CONTENT_MODEL_WIKITEXT) { $t = $content->getNativeData(); # note: don't set $text $wgParser->startExternalParse($title, ParserOptions::newFromContext($this->getContext()), Parser::OT_PREPROCESS); $dom = $wgParser->preprocessToDom($t); if (is_callable(array($dom, 'saveXML'))) { $xml = $dom->saveXML(); } else { $xml = $dom->__toString(); } $vals['parsetree'] = $xml; } else { $vals['badcontentformatforparsetree'] = true; $this->setWarning("Conversion to XML is supported for wikitext only, " . $title->getPrefixedDBkey() . " uses content model " . $content->getModel()); } } if ($this->expandTemplates && !$this->parseContent) { #XXX: implement template expansion for all content types in ContentHandler? if ($content->getModel() === CONTENT_MODEL_WIKITEXT) { $text = $content->getNativeData(); $text = $wgParser->preprocess($text, $title, ParserOptions::newFromContext($this->getContext())); } else { $this->setWarning("Template expansion is supported for wikitext only, " . $title->getPrefixedDBkey() . " uses content model " . $content->getModel()); $vals['badcontentformat'] = true; $text = false; } } if ($this->parseContent) { $po = $content->getParserOutput($title, $revision->getId(), ParserOptions::newFromContext($this->getContext())); $text = $po->getText(); } if ($text === null) { $format = $this->contentFormat ? $this->contentFormat : $content->getDefaultFormat(); $model = $content->getModel(); if (!$content->isSupportedFormat($format)) { $name = $title->getPrefixedDBkey(); $this->setWarning("The requested format {$this->contentFormat} is not " . "supported for content model {$model} used by {$name}"); $vals['badcontentformat'] = true; $text = false; } else { $text = $content->serialize($format); // always include format and model. // Format is needed to deserialize, model is needed to interpret. $vals['contentformat'] = $format; $vals['contentmodel'] = $model; } } if ($text !== false) { ApiResult::setContentValue($vals, 'content', $text); } } if ($content && (!is_null($this->diffto) || !is_null($this->difftotext))) { static $n = 0; // Number of uncached diffs we've had if ($n < $this->getConfig()->get('APIMaxUncachedDiffs')) { $vals['diff'] = array(); $context = new DerivativeContext($this->getContext()); $context->setTitle($title); $handler = $revision->getContentHandler(); if (!is_null($this->difftotext)) { $model = $title->getContentModel(); if ($this->contentFormat && !ContentHandler::getForModelID($model)->isSupportedFormat($this->contentFormat)) { $name = $title->getPrefixedDBkey(); $this->setWarning("The requested format {$this->contentFormat} is not " . "supported for content model {$model} used by {$name}"); $vals['diff']['badcontentformat'] = true; $engine = null; } else { $difftocontent = ContentHandler::makeContent($this->difftotext, $title, $model, $this->contentFormat); $engine = $handler->createDifferenceEngine($context); $engine->setContent($content, $difftocontent); } } else { $engine = $handler->createDifferenceEngine($context, $revision->getID(), $this->diffto); $vals['diff']['from'] = $engine->getOldid(); $vals['diff']['to'] = $engine->getNewid(); } if ($engine) { $difftext = $engine->getDiffBody(); ApiResult::setContentValue($vals['diff'], 'body', $difftext); if (!$engine->wasCacheHit()) { $n++; } } } else { $vals['diff']['notcached'] = true; } } if ($anyHidden && $revision->isDeleted(Revision::DELETED_RESTRICTED)) { $vals['suppressed'] = true; } return $vals; }
/** * Automatically review an revision and add a log entry in the review log. * * This is called during edit operations after the new revision is added * and the page tables updated, but before LinksUpdate is called. * * $auto is here for revisions checked off to be reviewed. Auto-review * triggers on edit, but we don't want those to count as just automatic. * This also makes it so the user's name shows up in the page history. * * If $flags is given, then they will be the review tags. If not, the one * from the stable version will be used or minimal tags if that's not possible. * If no appropriate tags can be found, then the review will abort. */ public static function autoReviewEdit(WikiPage $article, $user, Revision $rev, array $flags = null, $auto = true) { wfProfileIn(__METHOD__); $title = $article->getTitle(); // convenience # Get current stable version ID (for logging) $oldSv = FlaggedRevision::newFromStable($title, FR_MASTER); $oldSvId = $oldSv ? $oldSv->getRevId() : 0; # Set the auto-review tags from the prior stable version. # Normally, this should already be done and given here... if (!is_array($flags)) { if ($oldSv) { # Use the last stable version if $flags not given if ($user->isAllowed('bot')) { $flags = $oldSv->getTags(); // no change for bot edits } else { # Account for perms/tags... $flags = self::getAutoReviewTags($user, $oldSv->getTags()); } } else { // new page? $flags = self::quickTags(FR_CHECKED); // use minimal level } if (!is_array($flags)) { wfProfileOut(__METHOD__); return false; // can't auto-review this revision } } # Get review property flags $propFlags = $auto ? array('auto') : array(); # Note: this needs to match the prepareContentForEdit() call WikiPage::doEditContent. # This is for consistency and also to avoid triggering a second parse otherwise. $editInfo = $article->prepareContentForEdit($rev->getContent(), null, $user, $rev->getContentFormat()); $poutput = $editInfo->output; // revision HTML output # Get the "review time" versions of templates and files. # This tries to make sure each template/file version either came from the stable # version of that template/file or was a "review time" version used in the stable # version of this page. If a pending version of a template/file is currently vandalism, # we try to avoid storing its ID as the "review time" version so it won't show up when # someone views the page. If not possible, this stores the current template/file. if (FlaggedRevs::inclusionSetting() === FR_INCLUDES_CURRENT) { $tVersions = $poutput->getTemplateIds(); $fVersions = $poutput->getFileSearchOptions(); } else { $tVersions = $oldSv ? $oldSv->getTemplateVersions() : array(); $fVersions = $oldSv ? $oldSv->getFileVersions() : array(); foreach ($poutput->getTemplateIds() as $ns => $pages) { foreach ($pages as $dbKey => $revId) { if (!isset($tVersions[$ns][$dbKey])) { $srev = FlaggedRevision::newFromStable(Title::makeTitle($ns, $dbKey)); if ($srev) { // use stable $tVersions[$ns][$dbKey] = $srev->getRevId(); } else { // use current $tVersions[$ns][$dbKey] = $revId; } } } } foreach ($poutput->getFileSearchOptions() as $dbKey => $info) { if (!isset($fVersions[$dbKey])) { $srev = FlaggedRevision::newFromStable(Title::makeTitle(NS_FILE, $dbKey)); if ($srev && $srev->getFileTimestamp()) { // use stable $fVersions[$dbKey]['time'] = $srev->getFileTimestamp(); $fVersions[$dbKey]['sha1'] = $srev->getFileSha1(); } else { // use current $fVersions[$dbKey]['time'] = $info['time']; $fVersions[$dbKey]['sha1'] = $info['sha1']; } } } } # If this is an image page, get the corresponding file version info... $fileData = array('name' => null, 'timestamp' => null, 'sha1' => null); if ($title->getNamespace() == NS_FILE) { # We must use WikiFilePage process cache on upload or get bitten by slave lag $file = $article instanceof WikiFilePage || $article instanceof ImagePage ? $article->getFile() : wfFindFile($title, array('bypassCache' => true)); // skip cache; bug 31056 if (is_object($file) && $file->exists()) { $fileData['name'] = $title->getDBkey(); $fileData['timestamp'] = $file->getTimestamp(); $fileData['sha1'] = $file->getSha1(); } } # Our review entry $flaggedRevision = new FlaggedRevision(array('rev' => $rev, 'user_id' => $user->getId(), 'timestamp' => $rev->getTimestamp(), 'quality' => FlaggedRevs::getQualityTier($flags, 0), 'tags' => FlaggedRevision::flattenRevisionTags($flags), 'img_name' => $fileData['name'], 'img_timestamp' => $fileData['timestamp'], 'img_sha1' => $fileData['sha1'], 'templateVersions' => $tVersions, 'fileVersions' => $fVersions, 'flags' => implode(',', $propFlags))); $flaggedRevision->insert(); # Update the article review log FlaggedRevsLog::updateReviewLog($title, $flags, array(), '', $rev->getId(), $oldSvId, true, $auto); # Update page and tracking tables and clear cache FlaggedRevs::stableVersionUpdates($article); wfProfileOut(__METHOD__); return true; }
/** * Show a row in history, including: * time of edit * changed bytes * name of editor * comment of edit * @param Revision $rev Revision id of the row wants to show * @param Revision|null $prev Revision id of previous Revision to display the difference */ protected function showRow(Revision $rev, $prev) { $user = $this->getUser(); $userId = $rev->getUser(Revision::FOR_THIS_USER, $user); if ($userId === 0) { $username = IP::prettifyIP($rev->getUserText(Revision::RAW)); $isAnon = true; } else { $username = $rev->getUserText(Revision::FOR_THIS_USER, $user); $isAnon = false; } // FIXME: Style differently user comment when this is the case if ($rev->userCan(Revision::DELETED_COMMENT, $user)) { $comment = $rev->getComment(Revision::FOR_THIS_USER, $user); $comment = $this->formatComment($comment, $this->title); } else { $comment = $this->msg('rev-deleted-comment')->plain(); } $ts = $rev->getTimestamp(); $this->renderListHeaderWhereNeeded($this->getLanguage()->userDate($ts, $this->getUser())); $ts = new MWTimestamp($ts); $canSeeText = $rev->userCan(Revision::DELETED_TEXT, $user); if ($canSeeText && $prev && $prev->userCan(Revision::DELETED_TEXT, $user)) { $diffLink = SpecialPage::getTitleFor('MobileDiff', $rev->getId())->getLocalUrl(); } elseif ($canSeeText && $rev->getTitle() !== null) { $diffLink = $rev->getTitle()->getLocalUrl(array('oldid' => $rev->getId())); } else { $diffLink = false; } // FIXME: Style differently user comment when this is the case if (!$rev->userCan(Revision::DELETED_USER, $user)) { $username = $this->msg('rev-deleted-user')->plain(); } // When the page is named there is no need to print it in output if ($this->title) { $title = null; } else { $title = $rev->getTitle(); } $bytes = $rev->getSize(); if ($prev) { $bytes -= $prev->getSize(); } $isMinor = $rev->isMinor(); $this->renderFeedItemHtml($ts, $diffLink, $username, $comment, $title, $isAnon, $bytes, $isMinor); }
/** * If the given revision is newer than the currently set page_latest, * update the page record. Otherwise, do nothing. * * @param Database $dbw * @param Revision $revision */ function updateIfNewerOn(&$dbw, $revision) { wfProfileIn(__METHOD__); $row = $dbw->selectRow(array('revision', 'page'), array('rev_id', 'rev_timestamp', 'page_is_redirect'), array('page_id' => $this->getId(), 'page_latest=rev_id'), __METHOD__); if ($row) { if (wfTimestamp(TS_MW, $row->rev_timestamp) >= $revision->getTimestamp()) { wfProfileOut(__METHOD__); return false; } $prev = $row->rev_id; $lastRevIsRedirect = (bool) $row->page_is_redirect; } else { # No or missing previous revision; mark the page as new $prev = 0; $lastRevIsRedirect = null; } $ret = $this->updateRevisionOn($dbw, $revision, $prev, $lastRevIsRedirect); wfProfileOut(__METHOD__); return $ret; }
/** * If the given revision is newer than the currently set page_latest, * update the page record. Otherwise, do nothing. * * @deprecated since 1.24, use updateRevisionOn instead * * @param IDatabase $dbw * @param Revision $revision * @return bool */ public function updateIfNewerOn($dbw, $revision) { $row = $dbw->selectRow(['revision', 'page'], ['rev_id', 'rev_timestamp', 'page_is_redirect'], ['page_id' => $this->getId(), 'page_latest=rev_id'], __METHOD__); if ($row) { if (wfTimestamp(TS_MW, $row->rev_timestamp) >= $revision->getTimestamp()) { return false; } $prev = $row->rev_id; $lastRevIsRedirect = (bool) $row->page_is_redirect; } else { // No or missing previous revision; mark the page as new $prev = 0; $lastRevIsRedirect = null; } $ret = $this->updateRevisionOn($dbw, $revision, $prev, $lastRevIsRedirect); return $ret; }
/** * Generate a FeedItem object from a given revision table row * Borrows Recent Changes' feed generation functions for formatting; * includes a diff to the previous revision (if any). * * @param $row Object: database row * @return FeedItem */ function feedItem($row) { $rev = new Revision($row); $rev->setTitle($this->getTitle()); $text = FeedUtils::formatDiffRow($this->getTitle(), $this->getTitle()->getPreviousRevisionID($rev->getId()), $rev->getId(), $rev->getTimestamp(), $rev->getComment()); if ($rev->getComment() == '') { global $wgContLang; $title = $this->msg('history-feed-item-nocomment', $rev->getUserText(), $wgContLang->timeanddate($rev->getTimestamp()), $wgContLang->date($rev->getTimestamp()), $wgContLang->time($rev->getTimestamp()))->inContentLanguage()->text(); } else { $title = $rev->getUserText() . $this->msg('colon-separator')->inContentLanguage()->text() . FeedItem::stripComment($rev->getComment()); } return new FeedItem($title, $text, $this->getTitle()->getFullUrl('diff=' . $rev->getId() . '&oldid=prev'), $rev->getTimestamp(), $rev->getUserText(), $this->getTitle()->getTalkPage()->getFullUrl()); }
/** * Render the contribution of the pagerevision (time, bytes added/deleted, pagename comment) * @param Revision $rev */ protected function showContributionsRow(Revision $rev) { $user = $this->getUser(); $userId = $rev->getUser(Revision::FOR_THIS_USER, $user); if ($userId === 0) { $username = IP::prettifyIP($rev->getUserText(Revision::RAW)); $isAnon = true; } else { $username = $rev->getUserText(Revision::FOR_THIS_USER, $user); $isAnon = false; } // FIXME: Style differently user comment when this is the case if ($rev->userCan(Revision::DELETED_COMMENT, $user)) { $comment = $rev->getComment(Revision::FOR_THIS_USER, $user); $comment = $this->formatComment($comment, $this->title); } else { $comment = $this->msg('rev-deleted-comment')->plain(); } $ts = $rev->getTimestamp(); $this->renderListHeaderWhereNeeded($this->getLanguage()->userDate($ts, $this->getUser())); $ts = new MWTimestamp($ts); if ($rev->userCan(Revision::DELETED_TEXT, $user)) { $diffLink = SpecialPage::getTitleFor('MobileDiff', $rev->getId())->getLocalUrl(); } else { $diffLink = false; } // FIXME: Style differently user comment when this is the case if (!$rev->userCan(Revision::DELETED_USER, $user)) { $username = $this->msg('rev-deleted-user')->plain(); } $bytes = null; if (isset($this->prevLengths[$rev->getParentId()])) { $bytes = $rev->getSize() - $this->prevLengths[$rev->getParentId()]; } $isMinor = $rev->isMinor(); $this->renderFeedItemHtml($ts, $diffLink, $username, $comment, $rev->getTitle(), $isAnon, $bytes, $isMinor); }
/** * Generates each row in the contributions list. * * Contributions which are marked "top" are currently on top of the history. * For these contributions, a [rollback] link is shown for users with sysop * privileges. The rollback link restores the most recent version that was not * written by the target user. * * @todo This would probably look a lot nicer in a table. * @param $row * @return string */ function formatRow( $row ) { wfProfileIn( __METHOD__ ); $page = Title::makeTitle( $row->ar_namespace, $row->ar_title ); $rev = new Revision( array( 'title' => $page, 'id' => $row->ar_rev_id, 'comment' => $row->ar_comment, 'user' => $row->ar_user, 'user_text' => $row->ar_user_text, 'timestamp' => $row->ar_timestamp, 'minor_edit' => $row->ar_minor_edit, 'deleted' => $row->ar_deleted, ) ); $undelete = SpecialPage::getTitleFor( 'Undelete' ); $logs = SpecialPage::getTitleFor( 'Log' ); $dellog = Linker::linkKnown( $logs, $this->messages['deletionlog'], array(), array( 'type' => 'delete', 'page' => $page->getPrefixedText() ) ); $reviewlink = Linker::linkKnown( SpecialPage::getTitleFor( 'Undelete', $page->getPrefixedDBkey() ), $this->messages['undeleteviewlink'] ); $user = $this->getUser(); if ( $user->isAllowed( 'deletedtext' ) ) { $last = Linker::linkKnown( $undelete, $this->messages['diff'], array(), array( 'target' => $page->getPrefixedText(), 'timestamp' => $rev->getTimestamp(), 'diff' => 'prev' ) ); } else { $last = $this->messages['diff']; } $comment = Linker::revComment( $rev ); $date = $this->getLanguage()->userTimeAndDate( $rev->getTimestamp(), $user ); $date = htmlspecialchars( $date ); if ( !$user->isAllowed( 'undelete' ) || !$rev->userCan( Revision::DELETED_TEXT, $user ) ) { $link = $date; // unusable link } else { $link = Linker::linkKnown( $undelete, $date, array( 'class' => 'mw-changeslist-date' ), array( 'target' => $page->getPrefixedText(), 'timestamp' => $rev->getTimestamp() ) ); } // Style deleted items if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) { $link = '<span class="history-deleted">' . $link . '</span>'; } $pagelink = Linker::link( $page, null, array( 'class' => 'mw-changeslist-title' ) ); if ( $rev->isMinor() ) { $mflag = ChangesList::flag( 'minor' ); } else { $mflag = ''; } // Revision delete link $del = Linker::getRevDeleteLink( $user, $rev, $page ); if ( $del ) { $del .= ' '; } $tools = Html::rawElement( 'span', array( 'class' => 'mw-deletedcontribs-tools' ), $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList( array( $last, $dellog, $reviewlink ) ) )->escaped() ); $separator = '<span class="mw-changeslist-separator">. .</span>'; $ret = "{$del}{$link} {$tools} {$separator} {$mflag} {$pagelink} {$comment}"; # Denote if username is redacted for this edit if ( $rev->isDeleted( Revision::DELETED_USER ) ) { $ret .= " <strong>" . $this->msg( 'rev-deleted-user-contribs' )->escaped() . "</strong>"; } $ret = Html::rawElement( 'li', array(), $ret ) . "\n"; wfProfileOut( __METHOD__ ); return $ret; }
/** * Set the latest revision */ protected function setLastEdit( Revision $revision ) { $this->mLastRevision = $revision; $this->mTimestamp = $revision->getTimestamp(); }
/** * Generate a FeedItem object from a given revision table row * Borrows Recent Changes' feed generation functions for formatting; * includes a diff to the previous revision (if any). * * @param $row * @return FeedItem */ function feedItem($row) { $rev = new Revision($row); $rev->setTitle($this->mTitle); $text = rcFormatDiffRow($this->mTitle, $this->mTitle->getPreviousRevisionID($rev->getId()), $rev->getId(), $rev->getTimestamp(), $rev->getComment()); if ($rev->getComment() == '') { global $wgContLang; $title = wfMsgForContent('history-feed-item-nocomment', $rev->getUserText(), $wgContLang->timeanddate($rev->getTimestamp())); } else { $title = $rev->getUserText() . ": " . $this->stripComment($rev->getComment()); } return new FeedItem($title, $text, $this->mTitle->getFullUrl('diff=' . $rev->getId() . '&oldid=prev'), $rev->getTimestamp(), $rev->getUserText(), $this->mTitle->getTalkPage()->getFullUrl()); }