/** * Returns a row from the history printout. * * @todo document some more, and maybe clean up the code (some params redundant?) * * @param $row Object: the database row corresponding to the previous line. * @param $next Mixed: the database row corresponding to the next line. * @param $notificationtimestamp * @param $latest Boolean: whether this row corresponds to the page's latest revision. * @param $firstInList Boolean: whether this row corresponds to the first displayed on this history page. * @return String: HTML output for the row */ function historyLine($row, $next, $notificationtimestamp = false, $latest = false, $firstInList = false) { global $wgUser, $wgLang; $rev = new Revision($row); $rev->setTitle($this->title); $curlink = $this->curLink($rev, $latest); $lastlink = $this->lastLink($rev, $next); $diffButtons = $this->diffButtons($rev, $firstInList); $histLinks = Html::rawElement('span', array('class' => 'mw-history-histlinks'), '(' . $curlink . $this->historyPage->message['pipe-separator'] . $lastlink . ') '); $s = $histLinks . $diffButtons; $link = $this->revLink($rev); $classes = array(); $del = ''; // Show checkboxes for each revision if ($wgUser->isAllowed('deleterevision')) { $this->preventClickjacking(); // If revision was hidden from sysops, disable the checkbox if (!$rev->userCan(Revision::DELETED_RESTRICTED)) { $del = Xml::check('deleterevisions', false, array('disabled' => 'disabled')); // Otherwise, enable the checkbox... } else { $del = Xml::check('showhiderevisions', false, array('name' => 'ids[' . $rev->getId() . ']')); } // User can only view deleted revisions... } else { if ($rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) { // If revision was hidden from sysops, disable the link if (!$rev->userCan(Revision::DELETED_RESTRICTED)) { $cdel = $this->getSkin()->revDeleteLinkDisabled(false); // Otherwise, show the link... } else { $query = array('type' => 'revision', 'target' => $this->title->getPrefixedDbkey(), 'ids' => $rev->getId()); $del .= $this->getSkin()->revDeleteLink($query, $rev->isDeleted(Revision::DELETED_RESTRICTED), false); } } } if ($del) { $s .= " {$del} "; } $s .= " {$link}"; $s .= " <span class='history-user'>" . $this->getSkin()->revUserTools($rev, true) . "</span>"; if ($rev->isMinor()) { $s .= ' ' . ChangesList::flag('minor'); } if (!is_null($size = $rev->getSize()) && !$rev->isDeleted(Revision::DELETED_TEXT)) { $s .= ' ' . $this->getSkin()->formatRevisionSize($size); } $s .= $this->getSkin()->revComment($rev, false, true); if ($notificationtimestamp && $row->rev_timestamp >= $notificationtimestamp) { $s .= ' <span class="updatedmarker">' . wfMsgHtml('updatedmarker') . '</span>'; } $tools = array(); # Rollback and undo links if (!is_null($next) && is_object($next)) { if ($latest && $this->title->userCan('rollback') && $this->title->userCan('edit')) { $this->preventClickjacking(); $tools[] = '<span class="mw-rollback-link">' . $this->getSkin()->buildRollbackLink($rev) . '</span>'; } if ($this->title->quickUserCan('edit') && !$rev->isDeleted(Revision::DELETED_TEXT) && !$next->rev_deleted & Revision::DELETED_TEXT) { # Create undo tooltip for the first (=latest) line only $undoTooltip = $latest ? array('title' => wfMsg('tooltip-undo')) : array(); $undolink = $this->getSkin()->link($this->title, wfMsgHtml('editundo'), $undoTooltip, array('action' => 'edit', 'undoafter' => $next->rev_id, 'undo' => $rev->getId()), array('known', 'noclasses')); $tools[] = "<span class=\"mw-history-undo\">{$undolink}</span>"; } } if ($tools) { $s .= ' (' . $wgLang->pipeList($tools) . ')'; } # Tags list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow($row->ts_tags, 'history'); $classes = array_merge($classes, $newClasses); $s .= " {$tagSummary}"; wfRunHooks('PageHistoryLineEnding', array($this, &$row, &$s, &$classes)); $attribs = array(); if ($classes) { $attribs['class'] = implode(' ', $classes); } return Xml::tags('li', $attribs, $s) . "\n"; }
function showDiffPage($diffOnly = false) { global $wgUser, $wgOut, $wgUseExternalEditor, $wgUseRCPatrol; wfProfileIn(__METHOD__); # Allow frames except in certain special cases $wgOut->allowClickjacking(); # If external diffs are enabled both globally and for the user, # we'll use the application/x-external-editor interface to call # an external diff tool like kompare, kdiff3, etc. if ($wgUseExternalEditor && $wgUser->getOption('externaldiff')) { global $wgCanonicalServer, $wgScript, $wgLang; $wgOut->disable(); header("Content-type: application/x-external-editor; charset=UTF-8"); $url1 = $this->mTitle->getCanonical(array('action' => 'raw', 'oldid' => $this->mOldid)); $url2 = $this->mTitle->getCanonical(array('action' => 'raw', 'oldid' => $this->mNewid)); $special = $wgLang->getNsText(NS_SPECIAL); $control = <<<CONTROL \t\t\t[Process] \t\t\tType=Diff text \t\t\tEngine=MediaWiki \t\t\tScript={$wgCanonicalServer}{$wgScript} \t\t\tSpecial namespace={$special} \t\t\t[File] \t\t\tExtension=wiki \t\t\tURL={$url1} \t\t\t[File 2] \t\t\tExtension=wiki \t\t\tURL={$url2} CONTROL; echo $control; wfProfileOut(__METHOD__); return; } $wgOut->setArticleFlag(false); if (!$this->loadRevisionData()) { // Sounds like a deleted revision... Let's see what we can do. $t = $this->mTitle->getPrefixedText(); $d = wfMsgExt('missingarticle-diff', array('escape'), $this->deletedIdMarker($this->mOldid), $this->deletedIdMarker($this->mNewid)); $wgOut->setPagetitle(wfMsg('errorpagetitle')); $wgOut->addWikiMsg('missing-article', "<nowiki>{$t}</nowiki>", "<span class='plainlinks'>{$d}</span>"); wfProfileOut(__METHOD__); return; } wfRunHooks('DiffViewHeader', array($this, $this->mOldRev, $this->mNewRev)); if ($this->mNewRev->isCurrent()) { $wgOut->setArticleFlag(true); } # mOldid is false if the difference engine is called with a "vague" query for # a diff between a version V and its previous version V' AND the version V # is the first version of that article. In that case, V' does not exist. if ($this->mOldid === false) { $this->showFirstRevision(); $this->renderNewRevision(); // should we respect $diffOnly here or not? wfProfileOut(__METHOD__); return; } $oldTitle = $this->mOldPage->getPrefixedText(); $newTitle = $this->mNewPage->getPrefixedText(); if ($oldTitle == $newTitle) { $wgOut->setPageTitle($newTitle); } else { $wgOut->setPageTitle($oldTitle . ', ' . $newTitle); } if ($this->mNewPage->equals($this->mOldPage)) { $wgOut->setSubtitle(wfMsgExt('difference', array('parseinline'))); } else { $wgOut->setSubtitle(wfMsgExt('difference-multipage', array('parseinline'))); } $wgOut->setRobotPolicy('noindex,nofollow'); if (!$this->mOldPage->userCanRead() || !$this->mNewPage->userCanRead()) { $wgOut->loginToUse(); $wgOut->output(); $wgOut->disable(); wfProfileOut(__METHOD__); return; } $sk = $wgUser->getSkin(); if (method_exists($sk, 'suppressQuickbar')) { $sk->suppressQuickbar(); } // Check if page is editable $editable = $this->mNewRev->getTitle()->userCan('edit'); if ($editable && $this->mNewRev->isCurrent() && $wgUser->isAllowed('rollback')) { $wgOut->preventClickjacking(); $rollback = '   ' . $sk->generateRollback($this->mNewRev); } else { $rollback = ''; } // Prepare a change patrol link, if applicable if ($wgUseRCPatrol && $this->mTitle->userCan('patrol')) { // If we've been given an explicit change identifier, use it; saves time if ($this->mRcidMarkPatrolled) { $rcid = $this->mRcidMarkPatrolled; $rc = RecentChange::newFromId($rcid); // Already patrolled? $rcid = is_object($rc) && !$rc->getAttribute('rc_patrolled') ? $rcid : 0; } else { // Look for an unpatrolled change corresponding to this diff $db = wfGetDB(DB_SLAVE); $change = RecentChange::newFromConds(array('rc_user_text' => $this->mNewRev->getRawUserText(), 'rc_timestamp' => $db->timestamp($this->mNewRev->getTimestamp()), 'rc_this_oldid' => $this->mNewid, 'rc_last_oldid' => $this->mOldid, 'rc_patrolled' => 0), __METHOD__); if ($change instanceof RecentChange) { $rcid = $change->mAttribs['rc_id']; $this->mRcidMarkPatrolled = $rcid; } else { // None found $rcid = 0; } } // Build the link if ($rcid) { $wgOut->preventClickjacking(); $token = $wgUser->editToken($rcid); $patrol = ' <span class="patrollink">[' . $sk->link($this->mTitle, wfMsgHtml('markaspatrolleddiff'), array(), array('action' => 'markpatrolled', 'rcid' => $rcid, 'token' => $token), array('known', 'noclasses')) . ']</span>'; } else { $patrol = ''; } } else { $patrol = ''; } # Carry over 'diffonly' param via navigation links if ($diffOnly != $wgUser->getBoolOption('diffonly')) { $query['diffonly'] = $diffOnly; } # Make "previous revision link" $query['diff'] = 'prev'; $query['oldid'] = $this->mOldid; # Cascade unhide param in links for easy deletion browsing if ($this->unhide) { $query['unhide'] = 1; } if (!$this->mOldRev->getPrevious()) { $prevlink = ' '; } else { $prevlink = $sk->link($this->mTitle, wfMsgHtml('previousdiff'), array('id' => 'differences-prevlink'), $query, array('known', 'noclasses')); } # Make "next revision link" $query['diff'] = 'next'; $query['oldid'] = $this->mNewid; # Skip next link on the top revision if ($this->mNewRev->isCurrent()) { $nextlink = ' '; } else { $nextlink = $sk->link($this->mTitle, wfMsgHtml('nextdiff'), array('id' => 'differences-nextlink'), $query, array('known', 'noclasses')); } $oldminor = ''; $newminor = ''; if ($this->mOldRev->isMinor()) { $oldminor = ChangesList::flag('minor'); } if ($this->mNewRev->isMinor()) { $newminor = ChangesList::flag('minor'); } # Handle RevisionDelete links... $ldel = $this->revisionDeleteLink($this->mOldRev); $rdel = $this->revisionDeleteLink($this->mNewRev); $oldHeader = '<div id="mw-diff-otitle1"><strong>' . $this->mOldtitle . '</strong></div>' . '<div id="mw-diff-otitle2">' . $sk->revUserTools($this->mOldRev, !$this->unhide) . '</div>' . '<div id="mw-diff-otitle3">' . $oldminor . $sk->revComment($this->mOldRev, !$diffOnly, !$this->unhide) . $ldel . '</div>' . '<div id="mw-diff-otitle4">' . $prevlink . '</div>'; $newHeader = '<div id="mw-diff-ntitle1"><strong>' . $this->mNewtitle . '</strong></div>' . '<div id="mw-diff-ntitle2">' . $sk->revUserTools($this->mNewRev, !$this->unhide) . " {$rollback}</div>" . '<div id="mw-diff-ntitle3">' . $newminor . $sk->revComment($this->mNewRev, !$diffOnly, !$this->unhide) . $rdel . '</div>' . '<div id="mw-diff-ntitle4">' . $nextlink . $patrol . '</div>'; # Check if this user can see the revisions $allowed = $this->mOldRev->userCan(Revision::DELETED_TEXT) && $this->mNewRev->userCan(Revision::DELETED_TEXT); # Check if one of the revisions is deleted/suppressed $deleted = $suppressed = false; if ($this->mOldRev->isDeleted(Revision::DELETED_TEXT)) { $deleted = true; // old revisions text is hidden if ($this->mOldRev->isDeleted(Revision::DELETED_RESTRICTED)) { $suppressed = true; } // also suppressed } if ($this->mNewRev->isDeleted(Revision::DELETED_TEXT)) { $deleted = true; // new revisions text is hidden if ($this->mNewRev->isDeleted(Revision::DELETED_RESTRICTED)) { $suppressed = true; } // also suppressed } # If the diff cannot be shown due to a deleted revision, then output # the diff header and links to unhide (if available)... if ($deleted && (!$this->unhide || !$allowed)) { $this->showDiffStyle(); $multi = $this->getMultiNotice(); $wgOut->addHTML($this->addHeader('', $oldHeader, $newHeader, $multi)); if (!$allowed) { $msg = $suppressed ? 'rev-suppressed-no-diff' : 'rev-deleted-no-diff'; # Give explanation for why revision is not visible $wgOut->wrapWikiMsg("<div id='mw-{$msg}' class='mw-warning plainlinks'>\n\$1\n</div>\n", array($msg)); } else { # Give explanation and add a link to view the diff... $link = $this->mTitle->getFullUrl(array('diff' => $this->mNewid, 'oldid' => $this->mOldid, 'unhide' => 1)); $msg = $suppressed ? 'rev-suppressed-unhide-diff' : 'rev-deleted-unhide-diff'; $wgOut->wrapWikiMsg("<div id='mw-{$msg}' class='mw-warning plainlinks'>\n\$1\n</div>\n", array($msg, $link)); } # Otherwise, output a regular diff... } else { # Add deletion notice if the user is viewing deleted content $notice = ''; if ($deleted) { $msg = $suppressed ? 'rev-suppressed-diff-view' : 'rev-deleted-diff-view'; $notice = "<div id='mw-{$msg}' class='mw-warning plainlinks'>\n" . wfMsgExt($msg, 'parseinline') . "</div>\n"; } $this->showDiff($oldHeader, $newHeader, $notice); if (!$diffOnly) { $this->renderNewRevision(); } } wfProfileOut(__METHOD__); }
/** * Returns the change size (HTML). * The lengths can be given optionally. */ public function getCharacterDifference($old = 0, $new = 0) { if ($old === 0) { $old = $this->mAttribs['rc_old_len']; } if ($new === 0) { $new = $this->mAttribs['rc_new_len']; } if ($old === null || $new === null) { return ''; } return ChangesList::showCharacterDifference($old, $new); }
/** * Build and output the actual changes list. * * @param array $rows Database rows * @param FormOptions $opts */ public function outputChangesList($rows, $opts) { $limit = $opts['limit']; $showWatcherCount = $this->getConfig()->get('RCShowWatchingUsers') && $this->getUser()->getOption('shownumberswatching'); $watcherCache = array(); $dbr = $this->getDB(); $counter = 1; $list = ChangesList::newFromContext($this->getContext()); $list->initChangesListRows($rows); $rclistOutput = $list->beginRecentChangesList(); foreach ($rows as $obj) { if ($limit == 0) { break; } $rc = RecentChange::newFromRow($obj); $rc->counter = $counter++; # Check if the page has been updated since the last visit if ($this->getConfig()->get('ShowUpdatedMarker') && !empty($obj->wl_notificationtimestamp)) { $rc->notificationtimestamp = $obj->rc_timestamp >= $obj->wl_notificationtimestamp; } else { $rc->notificationtimestamp = false; // Default } # Check the number of users watching the page $rc->numberofWatchingusers = 0; // Default if ($showWatcherCount && $obj->rc_namespace >= 0) { if (!isset($watcherCache[$obj->rc_namespace][$obj->rc_title])) { $watcherCache[$obj->rc_namespace][$obj->rc_title] = $dbr->selectField('watchlist', 'COUNT(*)', array('wl_namespace' => $obj->rc_namespace, 'wl_title' => $obj->rc_title), __METHOD__ . '-watchers'); } $rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title]; } $changeLine = $list->recentChangesLine($rc, !empty($obj->wl_user), $counter); if ($changeLine !== false) { $rclistOutput .= $changeLine; --$limit; } } $rclistOutput .= $list->endRecentChangesList(); if ($rows->numRows() === 0) { $this->getOutput()->addHtml('<div class="mw-changeslist-empty">' . $this->msg('recentchanges-noresult')->parse() . '</div>'); if (!$this->including()) { $this->getOutput()->setStatusCode(404); } } else { $this->getOutput()->addHTML($rclistOutput); } }
public function showDiffPage($diffOnly = false) { # Allow frames except in certain special cases $out = $this->getOutput(); $out->allowClickjacking(); $out->setRobotPolicy('noindex,nofollow'); if (!$this->loadRevisionData()) { $this->showMissingRevision(); return; } $user = $this->getUser(); $permErrors = $this->mNewPage->getUserPermissionsErrors('read', $user); if ($this->mOldPage) { # mOldPage might not be set, see below. $permErrors = wfMergeErrorArrays($permErrors, $this->mOldPage->getUserPermissionsErrors('read', $user)); } if (count($permErrors)) { throw new PermissionsError('read', $permErrors); } $rollback = ''; $query = array(); # Carry over 'diffonly' param via navigation links if ($diffOnly != $user->getBoolOption('diffonly')) { $query['diffonly'] = $diffOnly; } # Cascade unhide param in links for easy deletion browsing if ($this->unhide) { $query['unhide'] = 1; } # Check if one of the revisions is deleted/suppressed $deleted = $suppressed = false; $allowed = $this->mNewRev->userCan(Revision::DELETED_TEXT, $user); $revisionTools = array(); # mOldRev is false if the difference engine is called with a "vague" query for # a diff between a version V and its previous version V' AND the version V # is the first version of that article. In that case, V' does not exist. if ($this->mOldRev === false) { $out->setPageTitle($this->msg('difference-title', $this->mNewPage->getPrefixedText())); $samePage = true; $oldHeader = ''; } else { Hooks::run('DiffViewHeader', array($this, $this->mOldRev, $this->mNewRev)); if ($this->mNewPage->equals($this->mOldPage)) { $out->setPageTitle($this->msg('difference-title', $this->mNewPage->getPrefixedText())); $samePage = true; } else { $out->setPageTitle($this->msg('difference-title-multipage', $this->mOldPage->getPrefixedText(), $this->mNewPage->getPrefixedText())); $out->addSubtitle($this->msg('difference-multipage')); $samePage = false; } if ($samePage && $this->mNewPage->quickUserCan('edit', $user)) { if ($this->mNewRev->isCurrent() && $this->mNewPage->userCan('rollback', $user)) { $rollbackLink = Linker::generateRollback($this->mNewRev, $this->getContext()); if ($rollbackLink) { $out->preventClickjacking(); $rollback = '   ' . $rollbackLink; } } if (!$this->mOldRev->isDeleted(Revision::DELETED_TEXT) && !$this->mNewRev->isDeleted(Revision::DELETED_TEXT)) { $undoLink = Html::element('a', array('href' => $this->mNewPage->getLocalURL(array('action' => 'edit', 'undoafter' => $this->mOldid, 'undo' => $this->mNewid)), 'title' => Linker::titleAttrib('undo')), $this->msg('editundo')->text()); $revisionTools['mw-diff-undo'] = $undoLink; } } # Make "previous revision link" if ($samePage && $this->mOldRev->getPrevious()) { $prevlink = Linker::linkKnown($this->mOldPage, $this->msg('previousdiff')->escaped(), array('id' => 'differences-prevlink'), array('diff' => 'prev', 'oldid' => $this->mOldid) + $query); } else { $prevlink = ' '; } if ($this->mOldRev->isMinor()) { $oldminor = ChangesList::flag('minor'); } else { $oldminor = ''; } $ldel = $this->revisionDeleteLink($this->mOldRev); $oldRevisionHeader = $this->getRevisionHeader($this->mOldRev, 'complete'); $oldChangeTags = ChangeTags::formatSummaryRow($this->mOldTags, 'diff'); $oldHeader = '<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader . '</strong></div>' . '<div id="mw-diff-otitle2">' . Linker::revUserTools($this->mOldRev, !$this->unhide) . '</div>' . '<div id="mw-diff-otitle3">' . $oldminor . Linker::revComment($this->mOldRev, !$diffOnly, !$this->unhide) . $ldel . '</div>' . '<div id="mw-diff-otitle5">' . $oldChangeTags[0] . '</div>' . '<div id="mw-diff-otitle4">' . $prevlink . '</div>'; if ($this->mOldRev->isDeleted(Revision::DELETED_TEXT)) { $deleted = true; // old revisions text is hidden if ($this->mOldRev->isDeleted(Revision::DELETED_RESTRICTED)) { $suppressed = true; // also suppressed } } # Check if this user can see the revisions if (!$this->mOldRev->userCan(Revision::DELETED_TEXT, $user)) { $allowed = false; } } # Make "next revision link" # Skip next link on the top revision if ($samePage && !$this->mNewRev->isCurrent()) { $nextlink = Linker::linkKnown($this->mNewPage, $this->msg('nextdiff')->escaped(), array('id' => 'differences-nextlink'), array('diff' => 'next', 'oldid' => $this->mNewid) + $query); } else { $nextlink = ' '; } if ($this->mNewRev->isMinor()) { $newminor = ChangesList::flag('minor'); } else { $newminor = ''; } # Handle RevisionDelete links... $rdel = $this->revisionDeleteLink($this->mNewRev); # Allow extensions to define their own revision tools Hooks::run('DiffRevisionTools', array($this->mNewRev, &$revisionTools, $this->mOldRev, $user)); $formattedRevisionTools = array(); // Put each one in parentheses (poor man's button) foreach ($revisionTools as $key => $tool) { $toolClass = is_string($key) ? $key : 'mw-diff-tool'; $element = Html::rawElement('span', array('class' => $toolClass), $this->msg('parentheses')->rawParams($tool)->escaped()); $formattedRevisionTools[] = $element; } $newRevisionHeader = $this->getRevisionHeader($this->mNewRev, 'complete') . ' ' . implode(' ', $formattedRevisionTools); $newChangeTags = ChangeTags::formatSummaryRow($this->mNewTags, 'diff'); $newHeader = '<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader . '</strong></div>' . '<div id="mw-diff-ntitle2">' . Linker::revUserTools($this->mNewRev, !$this->unhide) . " {$rollback}</div>" . '<div id="mw-diff-ntitle3">' . $newminor . Linker::revComment($this->mNewRev, !$diffOnly, !$this->unhide) . $rdel . '</div>' . '<div id="mw-diff-ntitle5">' . $newChangeTags[0] . '</div>' . '<div id="mw-diff-ntitle4">' . $nextlink . $this->markPatrolledLink() . '</div>'; if ($this->mNewRev->isDeleted(Revision::DELETED_TEXT)) { $deleted = true; // new revisions text is hidden if ($this->mNewRev->isDeleted(Revision::DELETED_RESTRICTED)) { $suppressed = true; // also suppressed } } # If the diff cannot be shown due to a deleted revision, then output # the diff header and links to unhide (if available)... if ($deleted && (!$this->unhide || !$allowed)) { $this->showDiffStyle(); $multi = $this->getMultiNotice(); $out->addHTML($this->addHeader('', $oldHeader, $newHeader, $multi)); if (!$allowed) { $msg = $suppressed ? 'rev-suppressed-no-diff' : 'rev-deleted-no-diff'; # Give explanation for why revision is not visible $out->wrapWikiMsg("<div id='mw-{$msg}' class='mw-warning plainlinks'>\n\$1\n</div>\n", array($msg)); } else { # Give explanation and add a link to view the diff... $query = $this->getRequest()->appendQueryValue('unhide', '1'); $link = $this->getTitle()->getFullURL($query); $msg = $suppressed ? 'rev-suppressed-unhide-diff' : 'rev-deleted-unhide-diff'; $out->wrapWikiMsg("<div id='mw-{$msg}' class='mw-warning plainlinks'>\n\$1\n</div>\n", array($msg, $link)); } # Otherwise, output a regular diff... } else { # Add deletion notice if the user is viewing deleted content $notice = ''; if ($deleted) { $msg = $suppressed ? 'rev-suppressed-diff-view' : 'rev-deleted-diff-view'; $notice = "<div id='mw-{$msg}' class='mw-warning plainlinks'>\n" . $this->msg($msg)->parse() . "</div>\n"; } $this->showDiff($oldHeader, $newHeader, $notice); if (!$diffOnly) { $this->renderNewRevision(); } } }
/** * Returns a row from the history printout. * * @todo document some more, and maybe clean up the code (some params redundant?) * * @param stdClass $row The database row corresponding to the previous line. * @param mixed $next The database row corresponding to the next line * (chronologically previous) * @param bool|string $notificationtimestamp * @param bool $latest Whether this row corresponds to the page's latest revision. * @param bool $firstInList Whether this row corresponds to the first * displayed on this history page. * @return string HTML output for the row */ function historyLine($row, $next, $notificationtimestamp = false, $latest = false, $firstInList = false) { $rev = new Revision($row); $rev->setTitle($this->getTitle()); if (is_object($next)) { $prevRev = new Revision($next); $prevRev->setTitle($this->getTitle()); } else { $prevRev = null; } $curlink = $this->curLink($rev, $latest); $lastlink = $this->lastLink($rev, $next); $curLastlinks = $curlink . $this->historyPage->message['pipe-separator'] . $lastlink; $histLinks = Html::rawElement('span', array('class' => 'mw-history-histlinks'), $this->msg('parentheses')->rawParams($curLastlinks)->escaped()); $diffButtons = $this->diffButtons($rev, $firstInList); $s = $histLinks . $diffButtons; $link = $this->revLink($rev); $classes = array(); $del = ''; $user = $this->getUser(); // Show checkboxes for each revision if ($user->isAllowed('deleterevision')) { $this->preventClickjacking(); // If revision was hidden from sysops, disable the checkbox if (!$rev->userCan(Revision::DELETED_RESTRICTED, $user)) { $del = Xml::check('deleterevisions', false, array('disabled' => 'disabled')); // Otherwise, enable the checkbox... } else { $del = Xml::check('showhiderevisions', false, array('name' => 'ids[' . $rev->getId() . ']')); } // User can only view deleted revisions... } elseif ($rev->getVisibility() && $user->isAllowed('deletedhistory')) { // If revision was hidden from sysops, disable the link if (!$rev->userCan(Revision::DELETED_RESTRICTED, $user)) { $del = Linker::revDeleteLinkDisabled(false); // Otherwise, show the link... } else { $query = array('type' => 'revision', 'target' => $this->getTitle()->getPrefixedDBkey(), 'ids' => $rev->getId()); $del .= Linker::revDeleteLink($query, $rev->isDeleted(Revision::DELETED_RESTRICTED), false); } } if ($del) { $s .= " {$del} "; } $lang = $this->getLanguage(); $dirmark = $lang->getDirMark(); $s .= " {$link}"; $s .= $dirmark; $s .= " <span class='history-user'>" . Linker::revUserTools($rev, true) . "</span>"; $s .= $dirmark; if ($rev->isMinor()) { $s .= ' ' . ChangesList::flag('minor'); } # Sometimes rev_len isn't populated if ($rev->getSize() !== null) { # Size is always public data $prevSize = isset($this->parentLens[$row->rev_parent_id]) ? $this->parentLens[$row->rev_parent_id] : 0; $sDiff = ChangesList::showCharacterDifference($prevSize, $rev->getSize()); $fSize = Linker::formatRevisionSize($rev->getSize()); $s .= ' <span class="mw-changeslist-separator">. .</span> ' . "{$fSize} {$sDiff}"; } # Text following the character difference is added just before running hooks $s2 = Linker::revComment($rev, false, true); if ($notificationtimestamp && $row->rev_timestamp >= $notificationtimestamp) { $s2 .= ' <span class="updatedmarker">' . $this->msg('updatedmarker')->escaped() . '</span>'; $classes[] = 'mw-history-line-updated'; } $tools = array(); # Rollback and undo links if ($prevRev && $this->getTitle()->quickUserCan('edit', $user)) { if ($latest && $this->getTitle()->quickUserCan('rollback', $user)) { // Get a rollback link without the brackets $rollbackLink = Linker::generateRollback($rev, $this->getContext(), array('verify', 'noBrackets')); if ($rollbackLink) { $this->preventClickjacking(); $tools[] = $rollbackLink; } } if (!$rev->isDeleted(Revision::DELETED_TEXT) && !$prevRev->isDeleted(Revision::DELETED_TEXT)) { # Create undo tooltip for the first (=latest) line only $undoTooltip = $latest ? array('title' => $this->msg('tooltip-undo')->text()) : array(); $undolink = Linker::linkKnown($this->getTitle(), $this->msg('editundo')->escaped(), $undoTooltip, array('action' => 'edit', 'undoafter' => $prevRev->getId(), 'undo' => $rev->getId())); $tools[] = "<span class=\"mw-history-undo\">{$undolink}</span>"; } } // Allow extension to add their own links here wfRunHooks('HistoryRevisionTools', array($rev, &$tools)); if ($tools) { $s2 .= ' ' . $this->msg('parentheses')->rawParams($lang->pipeList($tools))->escaped(); } # Tags list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow($row->ts_tags, 'history'); $classes = array_merge($classes, $newClasses); if ($tagSummary !== '') { $s2 .= " {$tagSummary}"; } # Include separator between character difference and following text if ($s2 !== '') { $s .= ' <span class="mw-changeslist-separator">. .</span> ' . $s2; } wfRunHooks('PageHistoryLineEnding', array($this, &$row, &$s, &$classes)); $attribs = array(); if ($classes) { $attribs['class'] = implode(' ', $classes); } return Xml::tags('li', $attribs, $s) . "\n"; }
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"; }
/** * Constructor */ function wfSpecialRecentchanges($par, $specialPage) { global $wgUser, $wgOut, $wgRequest, $wgUseRCPatrol; global $wgRCShowWatchingUsers, $wgShowUpdatedMarker; global $wgAllowCategorizedRecentChanges; $fname = 'wfSpecialRecentchanges'; $userGroups = $wgUser->getGroups(); if (!$wgUser->isAllowed('patrol') || in_array('patrolblock', $userGroups)) { $wgOut->errorpage('nosuchspecialpage', 'nospecialpagetext'); return; } # Get query parameters $feedFormat = $wgRequest->getVal('feed'); /* Checkbox values can't be true by default, because * we cannot differentiate between unset and not set at all */ $defaults = array('days' => $wgUser->getDefaultOption('rcdays'), 'limit' => $wgUser->getDefaultOption('rclimit'), 'hideminor' => false, 'hidebots' => true, 'hideanons' => false, 'hideliu' => false, 'hidepatrolled' => false, 'hidemyself' => false, 'from' => '', 'namespace' => null, 'invert' => false, 'categories_any' => false, 'reverse' => false, 'featured' => false, 'categories_any' => false); extract($defaults); $days = $wgUser->getOption('rcdays', $defaults['days']); $days = $wgRequest->getInt('days', $days); $limit = $wgUser->getOption('rclimit', $defaults['limit']); # list( $limit, $offset ) = wfCheckLimits( 100, 'rclimit' ); $limit = $wgRequest->getInt('limit', $limit); /* order of selection: url > preferences > default */ $hideminor = $wgRequest->getBool('hideminor', $wgUser->getOption('hideminor') ? true : $defaults['hideminor']); # As a feed, use limited settings only if ($feedFormat) { global $wgFeedLimit; if ($limit > $wgFeedLimit) { $limit = $wgFeedLimit; } } else { $namespace = $wgRequest->getIntOrNull('namespace'); $invert = $wgRequest->getBool('invert', $defaults['invert']); //XXADDED $reverse = $wgRequest->getBool('reverse', $defaults['reverse']); $featured = $wgRequest->getBool('featured', $defaults['featured']); $hidebots = $wgRequest->getBool('hidebots', $defaults['hidebots']); $hideanons = $wgRequest->getBool('hideanons', $defaults['hideanons']); $hideliu = $wgRequest->getBool('hideliu', $defaults['hideliu']); $hidepatrolled = $wgRequest->getBool('hidepatrolled', $defaults['hidepatrolled']); $hidemyself = $wgRequest->getBool('hidemyself', $defaults['hidemyself']); $from = $wgRequest->getVal('from', $defaults['from']); # Get query parameters from path if ($par) { $bits = preg_split('/\\s*,\\s*/', trim($par)); foreach ($bits as $bit) { if ('hidebots' == $bit) { $hidebots = 1; } if ('bots' == $bit) { $hidebots = 0; } if ('hideminor' == $bit) { $hideminor = 1; } if ('minor' == $bit) { $hideminor = 0; } if ('hideliu' == $bit) { $hideliu = 1; } if ('hidepatrolled' == $bit) { $hidepatrolled = 1; } if ('hideanons' == $bit) { $hideanons = 1; } if ('hidemyself' == $bit) { $hidemyself = 1; } if (is_numeric($bit)) { $limit = $bit; } $m = array(); if (preg_match('/^limit=(\\d+)$/', $bit, $m)) { $limit = $m[1]; } if (preg_match('/^days=(\\d+)$/', $bit, $m)) { $days = $m[1]; } } } } if ($limit < 0 || $limit > 5000) { $limit = $defaults['limit']; } # Database connection and caching $dbr = wfGetDB(DB_SLAVE); list($recentchanges, $watchlist) = $dbr->tableNamesN('recentchanges', 'watchlist'); $cutoff_unixtime = time() - $days * 86400; $cutoff_unixtime = $cutoff_unixtime - $cutoff_unixtime % 86400; $cutoff = $dbr->timestamp($cutoff_unixtime); if (preg_match('/^[0-9]{14}$/', $from) and $from > wfTimestamp(TS_MW, $cutoff)) { $cutoff = $dbr->timestamp($from); } else { $from = $defaults['from']; } # 10 seconds server-side caching max $wgOut->setSquidMaxage(0); # Get last modified date, for client caching # Don't use this if we are using the patrol feature, patrol changes don't update the timestamp $lastmod = $dbr->selectField('recentchanges', 'MAX(rc_timestamp)', false, $fname); if ($feedFormat || !$wgUseRCPatrol) { if ($lastmod && $wgOut->checkLastModified($lastmod)) { # Client cache fresh and headers sent, nothing more to do. return; } } # It makes no sense to hide both anons and logged-in users # Where this occurs, force anons to be shown if ($hideanons && $hideliu) { $hideanons = false; } # Form WHERE fragments for all the options $hidem = $hideminor ? 'AND rc_minor = 0' : ''; $hidem .= $hidebots ? ' AND rc_bot = 0' : ''; $hidem .= $hideliu ? ' AND rc_user = 0' : ''; $hidem .= $wgUseRCPatrol && $hidepatrolled ? ' AND rc_patrolled = 0' : ''; $hidem .= $hideanons ? ' AND rc_user != 0' : ''; if ($hidemyself) { if ($wgUser->getID()) { $hidem .= ' AND rc_user != ' . $wgUser->getID(); } else { $hidem .= ' AND rc_user_text != ' . $dbr->addQuotes($wgUser->getName()); } } # Namespace filtering $hidem .= is_null($namespace) ? '' : ' AND rc_namespace' . ($invert ? '!=' : '=') . $namespace; //XXADDED $order = $reverse ? " ASC" : "DESC"; $ft = $featured ? " AND page_is_featured = 1 " : ""; // This is the big thing! $uid = $wgUser->getID(); //XXCHANGED // Perform query $forceclause = $dbr->useIndexClause("rc_timestamp"); $sql2 = "SELECT * FROM {$recentchanges} {$forceclause}" . ($uid ? "LEFT OUTER JOIN {$watchlist} ON wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace " : "") . " LEFT OUTER JOIN page ON page_title=rc_title AND page_namespace=rc_namespace " . "WHERE rc_timestamp >= '{$cutoff}' {$hidem} {$ft} " . "ORDER BY rc_timestamp {$order} "; $sql2 = $dbr->limitResult($sql2, $limit, 0); $res = $dbr->query($sql2, $fname); // Fetch results, prepare a batch link existence check query $rows = array(); $batch = new LinkBatch(); while ($row = $dbr->fetchObject($res)) { $rows[] = $row; if (!$feedFormat) { // User page and talk links $batch->add(NS_USER, $row->rc_user_text); $batch->add(NS_USER_TALK, $row->rc_user_text); } } $dbr->freeResult($res); if ($feedFormat) { rcOutputFeed($rows, $feedFormat, $limit, $hideminor, $lastmod); } else { # Web output... // Run existence checks $batch->execute(); $any = $wgRequest->getBool('categories_any', $defaults['categories_any']); // Output header if (!$specialPage->including()) { $wgOut->addWikiText('<div class="minor_text">' . wfMsgForContentNoTrans("recentchangestext") . '<br /></div>'); // Dump everything here $nondefaults = array(); wfAppendToArrayIfNotDefault('days', $days, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('limit', $limit, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hideminor', $hideminor, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hidebots', $hidebots, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hideanons', $hideanons, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hideliu', $hideliu, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hidepatrolled', $hidepatrolled, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hidemyself', $hidemyself, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('from', $from, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('namespace', $namespace, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('invert', $invert, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('categories_any', $any, $defaults, $nondefaults); // Add end of the texts $wgOut->addHTML('<div class="rcoptions">' . rcOptionsPanel($defaults, $nondefaults) . "\n"); //XXCHANGED $wgOut->addHTML(rcNamespaceForm($namespace, $invert, $reverse, $featured, $nondefaults, $any) . '</div>' . "\n"); //XXADDED global $wgLanguageCode; if ($wgUser->getID() > 0 && $wgLanguageCode == 'en') { $sk = $wgUser->getSkin(); $url = $wgRequest->getRequestURL(); if ($wgRequest->getVal('refresh', null) != null) { $url = str_replace("&refresh=1", "", $url); $url = str_replace("?refresh=1", "", $url); $wgOut->addHTML("<a href='{$url}' class='button secondary'>" . wfMsg('rc_turn_refresh_off') . "</a>"); } else { if (strpos($url, "?") !== false) { $url .= "&refresh=1"; } else { $url .= "?refresh=1"; } $wgOut->addHTML("<a href='{$url}' class='button secondary'>" . wfMsg('rc_turn_refresh_on') . "</a>"); } $wgOut->addHTML(" <a class='button secondary' href='#' onclick=\"open('/index.php?title=Special:RCBuddy&hidepatrolled=1&limit=200&featured=1', '', 'scrollbars=no,status=no,width=570,height=200,resizable=yes,titlebar=no');\">RC Buddy</a>"); } } // And now for the content $wgOut->setSyndicated(true); $list = ChangesList::newFromUser($wgUser); if ($wgAllowCategorizedRecentChanges) { $categories = trim($wgRequest->getVal('categories', "")); $categories = str_replace("|", "\n", $categories); $categories = explode("\n", $categories); rcFilterByCategories($rows, $categories, $any); } $s = $list->beginRecentChangesList(); $s .= "<div id='recentchanges'>\n"; $counter = 1; $showWatcherCount = $wgRCShowWatchingUsers && $wgUser->getOption('shownumberswatching'); $watcherCache = array(); foreach ($rows as $obj) { if ($limit == 0) { break; } if (!($hideminor && $obj->rc_minor) && !($hidepatrolled && $obj->rc_patrolled)) { $rc = RecentChange::newFromRow($obj); $rc->counter = $counter++; if ($wgShowUpdatedMarker && !empty($obj->wl_notificationtimestamp) && $obj->rc_timestamp >= $obj->wl_notificationtimestamp) { $rc->notificationtimestamp = true; } else { $rc->notificationtimestamp = false; } $rc->numberofWatchingusers = 0; // Default if ($showWatcherCount && $obj->rc_namespace >= 0) { if (!isset($watcherCache[$obj->rc_namespace][$obj->rc_title])) { $watcherCache[$obj->rc_namespace][$obj->rc_title] = $dbr->selectField('watchlist', 'COUNT(*)', array('wl_namespace' => $obj->rc_namespace, 'wl_title' => $obj->rc_title), __METHOD__ . '-watchers'); } $rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title]; } $rc->show_namespace = $namespace; $rc->invert = $invert; $rc->reverse = $reverse; $rc->featured = $featured; $s .= $list->recentChangesLine($rc, !empty($obj->wl_user)); --$limit; } } $s .= $list->endRecentChangesList(); $s .= "</div>\n"; $wgOut->addHTML($s); } }
/** * 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 roll- * back 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, $wgContLang; wfProfileIn(__METHOD__); $sk = $this->getSkin(); $rev = new Revision($row); $classes = array(); $page = Title::newFromRow($row); $page->resetArticleId($row->rev_page); // use process cache $link = $sk->link($page, htmlspecialchars($page->getPrefixedText()), array(), $page->isRedirect() ? array('redirect' => 'no') : array()); # Mark current revisions $difftext = $topmarktext = ''; if ($row->rev_id == $row->page_latest) { $topmarktext .= '<span class="mw-uctop">' . $this->messages['uctop'] . '</span>'; # Add rollback link if (!$row->page_is_new && $page->quickUserCan('rollback') && $page->quickUserCan('edit')) { $this->preventClickjacking(); $topmarktext .= ' ' . $sk->generateRollback($rev); } } # Is there a visible previous revision? if ($rev->userCan(Revision::DELETED_TEXT) && $rev->getParentId() !== 0) { $difftext = $sk->linkKnown($page, $this->messages['diff'], array(), array('diff' => 'prev', 'oldid' => $row->rev_id)); } else { $difftext = $this->messages['diff']; } $histlink = $sk->linkKnown($page, $this->messages['hist'], array(), array('action' => 'history')); $comment = $wgContLang->getDirMark() . $sk->revComment($rev, false, true); $date = $wgLang->timeanddate(wfTimestamp(TS_MW, $row->rev_timestamp), true); if ($rev->isDeleted(Revision::DELETED_TEXT)) { $d = '<span class="history-deleted">' . $date . '</span>'; } else { $d = $sk->linkKnown($page, htmlspecialchars($date), array(), array('oldid' => intval($row->rev_id))); } if ($this->target == 'newbies') { $userlink = ' . . ' . $sk->userLink($row->rev_user, $row->rev_user_text); $userlink .= ' ' . wfMsg('parentheses', $sk->userTalkLink($row->rev_user, $row->rev_user_text)) . ' '; } else { $userlink = ''; } if ($rev->getParentId() === 0) { $nflag = ChangesList::flag('newpage'); } else { $nflag = ''; } if ($rev->isMinor()) { $mflag = ChangesList::flag('minor'); } else { $mflag = ''; } // Don't show useless link to people who cannot hide revisions $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' => 'revision', 'target' => $page->getPrefixedDbkey(), 'ids' => $rev->getId()); $del = $this->mSkin->revDeleteLink($query, $rev->isDeleted(Revision::DELETED_RESTRICTED), $canHide); } $del .= ' '; } else { $del = ''; } $diffHistLinks = '(' . $difftext . $this->messages['pipe-separator'] . $histlink . ')'; $ret = "{$del}{$d} {$diffHistLinks} {$nflag}{$mflag} {$link}{$userlink} {$comment} {$topmarktext}"; # Denote if username is redacted for this edit if ($rev->isDeleted(Revision::DELETED_USER)) { $ret .= " <strong>" . wfMsgHtml('rev-deleted-user-contribs') . "</strong>"; } # Tags, if any. list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow($row->ts_tags, 'contributions'); $classes = array_merge($classes, $newClasses); $ret .= " {$tagSummary}"; // Let extensions add data wfRunHooks('ContributionsLineEnding', array(&$this, &$ret, $row)); $classes = implode(' ', $classes); $ret = "<li class=\"{$classes}\">{$ret}</li>\n"; wfProfileOut(__METHOD__); return $ret; }
/** * Renders an item in the feed * @param MWTimestamp $ts The time the edit occurred * @param string $diffLink url to the diff for the edit * @param string $username The username of the user that made the edit (absent if anonymous) * @param string $comment The edit summary * @param Title|null $title The title of the page that was edited * @param bool $isAnon Is the edit anonymous? * @param int|null $bytes Net number of bytes changed or null if not applicable * @param bool $isMinor Is the edit minor? * @return string HTML code * * @todo FIXME: use an array as an argument? */ protected function renderFeedItemHtml($ts, $diffLink = '', $username = '', $comment = '', $title = null, $isAnon = false, $bytes = 0, $isMinor = false) { $output = $this->getOutput(); $user = $this->getUser(); $lang = $this->getLanguage(); if ($isAnon) { $usernameClass = MobileUI::iconClass('anonymous', 'before', 'icon-16px mw-mf-user mw-mf-anon'); } else { $usernameClass = MobileUI::iconClass('user', 'before', 'icon-16px mw-mf-user'); } $html = Html::openElement('li', array('class' => 'page-summary')); if ($diffLink) { $html .= Html::openElement('a', array('href' => $diffLink, 'class' => 'title')); } else { $html .= Html::openElement('div', array('class' => 'title')); } if ($title) { $html .= Html::element('h3', array(), $title->getPrefixedText()); } if ($username && $this->showUsername) { $html .= Html::element('p', array('class' => $usernameClass), $username); } $html .= Html::element('p', array('class' => 'edit-summary component truncated-text multi-line two-line'), $comment); if ($isMinor) { $html .= ChangesList::flag('minor'); } $html .= Html::openElement('div', array('class' => 'list-thumb')) . Html::element('p', array('class' => 'timestamp'), $lang->userTime($ts, $user)); if ($bytes) { $formattedBytes = $lang->formatNum($bytes); if ($bytes > 0) { $formattedBytes = '+' . $formattedBytes; $bytesClass = 'mw-mf-bytesadded'; } else { $bytesClass = 'mw-mf-bytesremoved'; } $html .= Html::element('p', array('class' => $bytesClass, 'dir' => 'ltr'), $formattedBytes); } $html .= Html::closeElement('div'); if ($diffLink) { $html .= Html::closeElement('a'); } else { $html .= Html::closeElement('div'); } $html .= Html::closeElement('li'); $output->addHtml($html); }
/** * Constructor * * @param $par Parameter passed to the page */ function wfSpecialWatchlist($par) { global $wgUser, $wgOut, $wgLang, $wgRequest; global $wgRCShowWatchingUsers, $wgEnotifWatchlist, $wgShowUpdatedMarker; global $wgEnotifWatchlist; $skin = $wgUser->getSkin(); $specialTitle = SpecialPage::getTitleFor('Watchlist'); $wgOut->setRobotPolicy('noindex,nofollow'); # Anons don't get a watchlist if ($wgUser->isAnon()) { $wgOut->setPageTitle(wfMsg('watchnologin')); $llink = $skin->makeKnownLinkObj(SpecialPage::getTitleFor('Userlogin'), wfMsgHtml('loginreqlink'), 'returnto=' . $specialTitle->getPrefixedUrl()); $wgOut->addHTML(wfMsgWikiHtml('watchlistanontext', $llink)); return; } $wgOut->setPageTitle(wfMsg('watchlist')); $sub = wfMsgExt('watchlistfor', 'parseinline', $wgUser->getName()); $sub .= '<br />' . WatchlistEditor::buildTools($wgUser->getSkin()); $wgOut->setSubtitle($sub); if (($mode = WatchlistEditor::getMode($wgRequest, $par)) !== false) { $editor = new WatchlistEditor(); $editor->execute($wgUser, $wgOut, $wgRequest, $mode); return; } $uid = $wgUser->getId(); if (($wgEnotifWatchlist || $wgShowUpdatedMarker) && $wgRequest->getVal('reset') && $wgRequest->wasPosted()) { $wgUser->clearAllNotifications($uid); $wgOut->redirect($specialTitle->getFullUrl()); return; } $defaults = array('days' => floatval($wgUser->getOption('watchlistdays')), 'hideMinor' => (int) $wgUser->getBoolOption('watchlisthideminor'), 'hideBots' => (int) $wgUser->getBoolOption('watchlisthidebots'), 'hideAnons' => (int) $wgUser->getBoolOption('watchlisthideanons'), 'hideLiu' => (int) $wgUser->getBoolOption('watchlisthideliu'), 'hidePatrolled' => (int) $wgUser->getBoolOption('watchlisthidepatrolled'), 'hideOwn' => (int) $wgUser->getBoolOption('watchlisthideown'), 'namespace' => 'all', 'invert' => false); extract($defaults); # Extract variables from the request, falling back to user preferences or # other default values if these don't exist $prefs['days'] = floatval($wgUser->getOption('watchlistdays')); $prefs['hideminor'] = $wgUser->getBoolOption('watchlisthideminor'); $prefs['hidebots'] = $wgUser->getBoolOption('watchlisthidebots'); $prefs['hideanons'] = $wgUser->getBoolOption('watchlisthideanon'); $prefs['hideliu'] = $wgUser->getBoolOption('watchlisthideliu'); $prefs['hideown'] = $wgUser->getBoolOption('watchlisthideown'); $prefs['hidepatrolled'] = $wgUser->getBoolOption('watchlisthidepatrolled'); # Get query variables $days = $wgRequest->getVal('days', $prefs['days']); $hideMinor = $wgRequest->getBool('hideMinor', $prefs['hideminor']); $hideBots = $wgRequest->getBool('hideBots', $prefs['hidebots']); $hideAnons = $wgRequest->getBool('hideAnons', $prefs['hideanons']); $hideLiu = $wgRequest->getBool('hideLiu', $prefs['hideliu']); $hideOwn = $wgRequest->getBool('hideOwn', $prefs['hideown']); $hidePatrolled = $wgRequest->getBool('hidePatrolled', $prefs['hidepatrolled']); # Get namespace value, if supplied, and prepare a WHERE fragment $nameSpace = $wgRequest->getIntOrNull('namespace'); $invert = $wgRequest->getIntOrNull('invert'); if (!is_null($nameSpace)) { $nameSpace = intval($nameSpace); if ($invert && $nameSpace !== 'all') { $nameSpaceClause = "rc_namespace != {$nameSpace}"; } else { $nameSpaceClause = "rc_namespace = {$nameSpace}"; } } else { $nameSpace = ''; $nameSpaceClause = ''; } $dbr = wfGetDB(DB_SLAVE, 'watchlist'); list($page, $watchlist, $recentchanges) = $dbr->tableNamesN('page', 'watchlist', 'recentchanges'); $watchlistCount = $dbr->selectField('watchlist', 'COUNT(*)', array('wl_user' => $uid), __METHOD__); // Adjust for page X, talk:page X, which are both stored separately, // but treated together $nitems = floor($watchlistCount / 2); if (is_null($days) || !is_numeric($days)) { $big = 1000; /* The magical big */ if ($nitems > $big) { # Set default cutoff shorter $days = $defaults['days'] = 12.0 / 24.0; # 12 hours... } else { $days = $defaults['days']; # default cutoff for shortlisters } } else { $days = floatval($days); } // Dump everything here $nondefaults = array(); wfAppendToArrayIfNotDefault('days', $days, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hideMinor', (int) $hideMinor, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hideBots', (int) $hideBots, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hideAnons', (int) $hideAnons, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hideLiu', (int) $hideLiu, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hideOwn', (int) $hideOwn, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('namespace', $nameSpace, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hidePatrolled', (int) $hidePatrolled, $defaults, $nondefaults); if ($nitems == 0) { $wgOut->addWikiMsg('nowatchlist'); return; } if ($days <= 0) { $andcutoff = ''; } else { $andcutoff = "rc_timestamp > '" . $dbr->timestamp(time() - intval($days * 86400)) . "'"; } # If the watchlist is relatively short, it's simplest to zip # down its entirety and then sort the results. # If it's relatively long, it may be worth our while to zip # through the time-sorted page list checking for watched items. # Up estimate of watched items by 15% to compensate for talk pages... # Toggles $andHideOwn = $hideOwn ? "rc_user != {$uid}" : ''; $andHideBots = $hideBots ? "rc_bot = 0" : ''; $andHideMinor = $hideMinor ? "rc_minor = 0" : ''; $andHideLiu = $hideLiu ? "rc_user = 0" : ''; $andHideAnons = $hideAnons ? "rc_user != 0" : ''; $andHidePatrolled = $wgUser->useRCPatrol() && $hidePatrolled ? "rc_patrolled != 1" : ''; # Toggle watchlist content (all recent edits or just the latest) if ($wgUser->getOption('extendwatchlist')) { $andLatest = ''; $limitWatchlist = intval($wgUser->getOption('wllimit')); } else { # Top log Ids for a page are not stored $andLatest = 'rc_this_oldid=page_latest OR rc_type=' . RC_LOG; $limitWatchlist = 0; } # Show a message about slave lag, if applicable if (($lag = $dbr->getLag()) > 0) { $wgOut->showLagWarning($lag); } # Create output form $form = Xml::fieldset(wfMsg('watchlist-options'), false, array('id' => 'mw-watchlist-options')); # Show watchlist header $form .= wfMsgExt('watchlist-details', array('parseinline'), $wgLang->formatNum($nitems)); if ($wgUser->getOption('enotifwatchlistpages') && $wgEnotifWatchlist) { $form .= wfMsgExt('wlheader-enotif', 'parse') . "\n"; } if ($wgShowUpdatedMarker) { $form .= Xml::openElement('form', array('method' => 'post', 'action' => $specialTitle->getLocalUrl(), 'id' => 'mw-watchlist-resetbutton')) . wfMsgExt('wlheader-showupdated', array('parseinline')) . ' ' . Xml::submitButton(wfMsg('enotif_reset'), array('name' => 'dummy')) . Xml::hidden('reset', 'all') . Xml::closeElement('form'); } $form .= '<hr />'; $tables = array('recentchanges', 'watchlist', 'page'); $fields = array("{$recentchanges}.*"); $conds = array(); $join_conds = array('watchlist' => array('INNER JOIN', "wl_user='******' AND wl_namespace=rc_namespace AND wl_title=rc_title"), 'page' => array('LEFT JOIN', 'rc_cur_id=page_id')); $options = array('ORDER BY' => 'rc_timestamp DESC'); if ($wgShowUpdatedMarker) { $fields[] = 'wl_notificationtimestamp'; } if ($limitWatchlist) { $options['LIMIT'] = $limitWatchlist; } if ($andcutoff) { $conds[] = $andcutoff; } if ($andLatest) { $conds[] = $andLatest; } if ($andHideOwn) { $conds[] = $andHideOwn; } if ($andHideBots) { $conds[] = $andHideBots; } if ($andHideMinor) { $conds[] = $andHideMinor; } if ($andHideLiu) { $conds[] = $andHideLiu; } if ($andHideAnons) { $conds[] = $andHideAnons; } if ($andHidePatrolled) { $conds[] = $andHidePatrolled; } if ($nameSpaceClause) { $conds[] = $nameSpaceClause; } wfRunHooks('SpecialWatchlistQuery', array(&$conds, &$tables, &$join_conds, &$fields)); $res = $dbr->select($tables, $fields, $conds, __METHOD__, $options, $join_conds); $numRows = $dbr->numRows($res); /* Start bottom header */ $wlInfo = ''; if ($days >= 1) { $wlInfo = wfMsgExt('rcnote', 'parseinline', $wgLang->formatNum($numRows), $wgLang->formatNum($days), $wgLang->timeAndDate(wfTimestampNow(), true), $wgLang->date(wfTimestampNow(), true), $wgLang->time(wfTimestampNow(), true)) . '<br />'; } elseif ($days > 0) { $wlInfo = wfMsgExt('wlnote', 'parseinline', $wgLang->formatNum($numRows), $wgLang->formatNum(round($days * 24))) . '<br />'; } $cutofflinks = "\n" . wlCutoffLinks($days, 'Watchlist', $nondefaults) . "<br />\n"; # Spit out some control panel links $thisTitle = SpecialPage::getTitleFor('Watchlist'); $skin = $wgUser->getSkin(); $showLinktext = wfMsgHtml('show'); $hideLinktext = wfMsgHtml('hide'); # Hide/show minor edits $label = $hideMinor ? $showLinktext : $hideLinktext; $linkBits = wfArrayToCGI(array('hideMinor' => 1 - (int) $hideMinor), $nondefaults); $links[] = wfMsgHtml('rcshowhideminor', $skin->makeKnownLinkObj($thisTitle, $label, $linkBits)); # Hide/show bot edits $label = $hideBots ? $showLinktext : $hideLinktext; $linkBits = wfArrayToCGI(array('hideBots' => 1 - (int) $hideBots), $nondefaults); $links[] = wfMsgHtml('rcshowhidebots', $skin->makeKnownLinkObj($thisTitle, $label, $linkBits)); # Hide/show anonymous edits $label = $hideAnons ? $showLinktext : $hideLinktext; $linkBits = wfArrayToCGI(array('hideAnons' => 1 - (int) $hideAnons), $nondefaults); $links[] = wfMsgHtml('rcshowhideanons', $skin->makeKnownLinkObj($thisTitle, $label, $linkBits)); # Hide/show logged in edits $label = $hideLiu ? $showLinktext : $hideLinktext; $linkBits = wfArrayToCGI(array('hideLiu' => 1 - (int) $hideLiu), $nondefaults); $links[] = wfMsgHtml('rcshowhideliu', $skin->makeKnownLinkObj($thisTitle, $label, $linkBits)); # Hide/show own edits $label = $hideOwn ? $showLinktext : $hideLinktext; $linkBits = wfArrayToCGI(array('hideOwn' => 1 - (int) $hideOwn), $nondefaults); $links[] = wfMsgHtml('rcshowhidemine', $skin->makeKnownLinkObj($thisTitle, $label, $linkBits)); # Hide/show patrolled edits if ($wgUser->useRCPatrol()) { $label = $hidePatrolled ? $showLinktext : $hideLinktext; $linkBits = wfArrayToCGI(array('hidePatrolled' => 1 - (int) $hidePatrolled), $nondefaults); $links[] = wfMsgHtml('rcshowhidepatr', $skin->makeKnownLinkObj($thisTitle, $label, $linkBits)); } # Namespace filter and put the whole form together. $form .= $wlInfo; $form .= $cutofflinks; $form .= implode(' | ', $links); $form .= Xml::openElement('form', array('method' => 'post', 'action' => $thisTitle->getLocalUrl())); $form .= '<hr /><p>'; $form .= Xml::label(wfMsg('namespace'), 'namespace') . ' '; $form .= Xml::namespaceSelector($nameSpace, '') . ' '; $form .= Xml::checkLabel(wfMsg('invert'), 'invert', 'nsinvert', $invert) . ' '; $form .= Xml::submitButton(wfMsg('allpagessubmit')) . '</p>'; $form .= Xml::hidden('days', $days); if ($hideMinor) { $form .= Xml::hidden('hideMinor', 1); } if ($hideBots) { $form .= Xml::hidden('hideBots', 1); } if ($hideAnons) { $form .= Xml::hidden('hideAnons', 1); } if ($hideLiu) { $form .= Xml::hidden('hideLiu', 1); } if ($hideOwn) { $form .= Xml::hidden('hideOwn', 1); } $form .= Xml::closeElement('form'); $form .= Xml::closeElement('fieldset'); $wgOut->addHTML($form); # If there's nothing to show, stop here if ($numRows == 0) { $wgOut->addWikiMsg('watchnochange'); return; } /* End bottom header */ /* Do link batch query */ $linkBatch = new LinkBatch(); while ($row = $dbr->fetchObject($res)) { $userNameUnderscored = str_replace(' ', '_', $row->rc_user_text); if ($row->rc_user != 0) { $linkBatch->add(NS_USER, $userNameUnderscored); } $linkBatch->add(NS_USER_TALK, $userNameUnderscored); } $linkBatch->execute(); $dbr->dataSeek($res, 0); $list = ChangesList::newFromUser($wgUser); $s = $list->beginRecentChangesList(); $counter = 1; while ($obj = $dbr->fetchObject($res)) { # Make RC entry $rc = RecentChange::newFromRow($obj); $rc->counter = $counter++; if ($wgShowUpdatedMarker) { $updated = $obj->wl_notificationtimestamp; } else { $updated = false; } if ($wgRCShowWatchingUsers && $wgUser->getOption('shownumberswatching')) { $rc->numberofWatchingusers = $dbr->selectField('watchlist', 'COUNT(*)', array('wl_namespace' => $obj->rc_namespace, 'wl_title' => $obj->rc_title), __METHOD__); } else { $rc->numberofWatchingusers = 0; } $s .= $list->recentChangesLine($rc, $updated); } $s .= $list->endRecentChangesList(); $dbr->freeResult($res); $wgOut->addHTML($s); }
private function extractRowInfo($row) { /* Determine the title of the page that has been changed. */ $title = Title::makeTitle($row->rc_namespace, $row->rc_title); $user = $this->getUser(); /* Our output data. */ $vals = array(); $type = intval($row->rc_type); /* Determine what kind of change this was. */ switch ($type) { case RC_EDIT: $vals['type'] = 'edit'; break; case RC_NEW: $vals['type'] = 'new'; break; case RC_MOVE: $vals['type'] = 'move'; break; case RC_LOG: $vals['type'] = 'log'; break; case RC_EXTERNAL: $vals['type'] = 'external'; break; case RC_MOVE_OVER_REDIRECT: $vals['type'] = 'move over redirect'; break; default: $vals['type'] = $type; } $anyHidden = false; /* Create a new entry in the result for the title. */ if ($this->fld_title || $this->fld_ids) { // These should already have been filtered out of the query, but just in case. if ($type === RC_LOG && $row->rc_deleted & LogPage::DELETED_ACTION) { $vals['actionhidden'] = ''; $anyHidden = true; } if ($type !== RC_LOG || LogEventsList::userCanBitfield($row->rc_deleted, LogPage::DELETED_ACTION, $user)) { if ($this->fld_title) { ApiQueryBase::addTitleInfo($vals, $title); } if ($this->fld_ids) { $vals['pageid'] = intval($row->rc_cur_id); $vals['revid'] = intval($row->rc_this_oldid); $vals['old_revid'] = intval($row->rc_last_oldid); } } } /* Add user data and 'anon' flag, if user is anonymous. */ if ($this->fld_user || $this->fld_userid) { if ($row->rc_deleted & Revision::DELETED_USER) { $vals['userhidden'] = ''; $anyHidden = true; } if (Revision::userCanBitfield($row->rc_deleted, Revision::DELETED_USER, $user)) { if ($this->fld_userid) { $vals['userid'] = $row->rc_user; // for backwards compatibility $vals['user'] = $row->rc_user; } if ($this->fld_user) { $vals['user'] = $row->rc_user_text; } if (!$row->rc_user) { $vals['anon'] = ''; } } } /* Add flags, such as new, minor, bot. */ if ($this->fld_flags) { if ($row->rc_bot) { $vals['bot'] = ''; } if ($row->rc_type == RC_NEW) { $vals['new'] = ''; } if ($row->rc_minor) { $vals['minor'] = ''; } } /* Add sizes of each revision. (Only available on 1.10+) */ if ($this->fld_sizes) { $vals['oldlen'] = intval($row->rc_old_len); $vals['newlen'] = intval($row->rc_new_len); } /* Add the timestamp. */ if ($this->fld_timestamp) { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rc_timestamp); } if ($this->fld_notificationtimestamp) { $vals['notificationtimestamp'] = $row->wl_notificationtimestamp == null ? '' : wfTimestamp(TS_ISO_8601, $row->wl_notificationtimestamp); } /* Add edit summary / log summary. */ if ($this->fld_comment || $this->fld_parsedcomment) { if ($row->rc_deleted & Revision::DELETED_COMMENT) { $vals['commenthidden'] = ''; $anyHidden = true; } if (Revision::userCanBitfield($row->rc_deleted, Revision::DELETED_COMMENT, $user)) { if ($this->fld_comment && isset($row->rc_comment)) { $vals['comment'] = $row->rc_comment; } if ($this->fld_parsedcomment && isset($row->rc_comment)) { $vals['parsedcomment'] = Linker::formatComment($row->rc_comment, $title); } } } /* Add the patrolled flag */ if ($this->fld_patrol && $row->rc_patrolled == 1) { $vals['patrolled'] = ''; } if ($this->fld_patrol && ChangesList::isUnpatrolled($row, $user)) { $vals['unpatrolled'] = ''; } if ($this->fld_loginfo && $row->rc_type == RC_LOG) { if ($row->rc_deleted & LogPage::DELETED_ACTION) { $vals['actionhidden'] = ''; $anyHidden = true; } if (LogEventsList::userCanBitfield($row->rc_deleted, LogPage::DELETED_ACTION, $user)) { $vals['logid'] = intval($row->rc_logid); $vals['logtype'] = $row->rc_log_type; $vals['logaction'] = $row->rc_log_action; $logEntry = DatabaseLogEntry::newFromRow((array) $row); ApiQueryLogEvents::addLogParams($this->getResult(), $vals, $logEntry->getParameters(), $logEntry->getType(), $logEntry->getSubtype(), $logEntry->getTimestamp()); } } if ($anyHidden && $row->rc_deleted & Revision::DELETED_RESTRICTED) { $vals['suppressed'] = ''; } return $vals; }
function revisionInfo($row) { global $wgUser; $changes = ChangesList::newFromContext(RequestContext::getMain()); $out = $changes->beginRecentChangesList(); $rc = RecentChange::newFromCurRow($row); $rc->counter = 0; // ??? $out .= $changes->recentChangesLine($rc); $out .= $changes->endRecentChangesList(); return $out; }
/** * 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; }
/** * 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 roll- * back 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__); $ret = ''; $classes = array(); /* * 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. */ wfSuppressWarnings(); $rev = new Revision($row); $validRevision = $rev->getParentId() !== null; wfRestoreWarnings(); if ($validRevision) { $classes = array(); $page = Title::newFromRow($row); $link = Linker::link($page, htmlspecialchars($page->getPrefixedText()), array('class' => 'mw-contributions-title'), $page->isRedirect() ? array('redirect' => 'no') : array()); # Mark current revisions $topmarktext = ''; $user = $this->getUser(); if ($row->rev_id == $row->page_latest) { $topmarktext .= '<span class="mw-uctop">' . $this->messages['uctop'] . '</span>'; # Add rollback link if (!$row->page_is_new && $page->quickUserCan('rollback', $user) && $page->quickUserCan('edit', $user)) { $this->preventClickjacking(); $topmarktext .= ' ' . Linker::generateRollback($rev, $this->getContext()); } } # Is there a visible previous revision? if ($rev->userCan(Revision::DELETED_TEXT, $user) && $rev->getParentId() !== 0) { $difftext = Linker::linkKnown($page, $this->messages['diff'], array(), array('diff' => 'prev', 'oldid' => $row->rev_id)); } else { $difftext = $this->messages['diff']; } $histlink = Linker::linkKnown($page, $this->messages['hist'], array(), array('action' => 'history')); if ($row->rev_parent_id === null) { // For some reason rev_parent_id isn't populated for this row. // Its rumoured this is true on wikipedia for some revisions (bug 34922). // Next best thing is to have the total number of bytes. $chardiff = ' <span class="mw-changeslist-separator">. .</span> ' . Linker::formatRevisionSize($row->rev_len) . ' <span class="mw-changeslist-separator">. .</span> '; } else { $parentLen = isset($this->mParentLens[$row->rev_parent_id]) ? $this->mParentLens[$row->rev_parent_id] : 0; $chardiff = ' <span class="mw-changeslist-separator">. .</span> ' . ChangesList::showCharacterDifference($parentLen, $row->rev_len, $this->getContext()) . ' <span class="mw-changeslist-separator">. .</span> '; } $lang = $this->getLanguage(); $comment = $lang->getDirMark() . Linker::revComment($rev, false, true); $date = $lang->userTimeAndDate($row->rev_timestamp, $user); if ($rev->userCan(Revision::DELETED_TEXT, $user)) { $d = Linker::linkKnown($page, htmlspecialchars($date), array('class' => 'mw-changeslist-date'), array('oldid' => intval($row->rev_id))); } else { $d = htmlspecialchars($date); } if ($rev->isDeleted(Revision::DELETED_TEXT)) { $d = '<span class="history-deleted">' . $d . '</span>'; } # Show user names for /newbies as there may be different users. # Note that we already excluded rows with hidden user names. if ($this->contribs == 'newbie') { $userlink = ' . . ' . Linker::userLink($rev->getUser(), $rev->getUserText()); $userlink .= ' ' . $this->msg('parentheses')->rawParams(Linker::userTalkLink($rev->getUser(), $rev->getUserText()))->escaped() . ' '; } else { $userlink = ''; } if ($rev->getParentId() === 0) { $nflag = ChangesList::flag('newpage'); } else { $nflag = ''; } if ($rev->isMinor()) { $mflag = ChangesList::flag('minor'); } else { $mflag = ''; } $del = Linker::getRevDeleteLink($user, $rev, $page); if ($del !== '') { $del .= ' '; } $diffHistLinks = $this->msg('parentheses')->rawParams($difftext . $this->messages['pipe-separator'] . $histlink)->escaped(); $ret = "{$del}{$d} {$diffHistLinks}{$chardiff}{$nflag}{$mflag} {$link}{$userlink} {$comment} {$topmarktext}"; # Denote if username is redacted for this edit if ($rev->isDeleted(Revision::DELETED_USER)) { $ret .= " <strong>" . $this->msg('rev-deleted-user-contribs')->escaped() . "</strong>"; } # Tags, if any. list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow($row->ts_tags, 'contributions'); $classes = array_merge($classes, $newClasses); $ret .= " {$tagSummary}"; } // Let extensions add data wfRunHooks('ContributionsLineEnding', array($this, &$ret, $row, &$classes)); $classes = implode(' ', $classes); $ret = "<li class=\"{$classes}\">{$ret}</li>\n"; wfProfileOut(__METHOD__); return $ret; }
/** * @brief Adjusting recent changes for Wall * * @desc This method creates comment about revision deletion of a message on message wall * * @param ChangesList $list * @param RecentChange $rc * @param String $s * @param Formatter $formatter * @param string $mark * * @return true because this is a hook * * @author Andrzej 'nAndy' Lukaszewski */ public function onChangesListInsertLogEntry($list, $rc, &$s, $formatter, &$mark) { $app = F::app(); if ($rc->getAttribute('rc_type') == RC_LOG && in_array(MWNamespace::getSubject($rc->getAttribute('rc_namespace')), $app->wg->WallNS) && in_array($rc->getAttribute('rc_log_action'), $this->rcWallActionTypes)) { $actionText = ''; $wfMsgOptsBase = $this->getMessageOptions($rc); $wfMsgOpts = array($wfMsgOptsBase['articleUrl'], $wfMsgOptsBase['articleTitleTxt'], $wfMsgOptsBase['wallPageUrl'], $wfMsgOptsBase['wallPageName'], $wfMsgOptsBase['actionUser']); $msgType = $wfMsgOptsBase['isThread'] ? 'thread' : 'reply'; //created in WallHooksHelper::getMessageOptions() //and there is not needed to be passed to wfMsg() unset($wfMsgOpts['isThread'], $wfMsgOpts['isNew']); switch ($rc->getAttribute('rc_log_action')) { case 'wall_remove': $actionText = wfMsgExt($this->getMessagePrefix($rc->getAttribute('rc_namespace')) . '-removed-' . $msgType, array('parseinline'), $wfMsgOpts); break; case 'wall_restore': $actionText = wfMsgExt($this->getMessagePrefix($rc->getAttribute('rc_namespace')) . '-restored-' . $msgType, array('parseinline'), $wfMsgOpts); break; case 'wall_admindelete': $actionText = wfMsgExt($this->getMessagePrefix($rc->getAttribute('rc_namespace')) . '-deleted-' . $msgType, array('parseinline'), $wfMsgOpts); break; case 'wall_archive': $actionText = wfMsgExt($this->getMessagePrefix($rc->getAttribute('rc_namespace')) . '-closed-thread', array('parseinline'), $wfMsgOpts); break; case 'wall_reopen': $actionText = wfMsgExt($this->getMessagePrefix($rc->getAttribute('rc_namespace')) . '-reopened-thread', array('parseinline'), $wfMsgOpts); break; default: $actionText = wfMsg($this->getMessagePrefix($rc->getAttribute('rc_namespace')) . '-unrecognized-log-action', $wfMsgOpts); break; } $s = ''; $list->insertUserRelatedLinks($s, $rc); $s .= ' ' . $actionText . ' ' . $list->insertComment($rc); } return true; }
/** * Send output to the OutputPage object, only called if not used feeds * * @param array $rows Database rows * @param FormOptions $opts */ public function webOutput($rows, $opts) { global $wgRCShowWatchingUsers, $wgShowUpdatedMarker, $wgAllowCategorizedRecentChanges; $limit = $opts['limit']; if (!$this->including()) { // Output options box $this->doHeader($opts); } // And now for the content $feedQuery = $this->getFeedQuery(); if ($feedQuery !== '') { $this->getOutput()->setFeedAppendQuery($feedQuery); } else { $this->getOutput()->setFeedAppendQuery(false); } if ($wgAllowCategorizedRecentChanges) { $this->filterByCategories($rows, $opts); } $showNumsWachting = $this->getUser()->getOption('shownumberswatching'); $showWatcherCount = $wgRCShowWatchingUsers && $showNumsWachting; $watcherCache = array(); $dbr = wfGetDB(DB_SLAVE); $counter = 1; $list = ChangesList::newFromContext($this->getContext()); $s = $list->beginRecentChangesList(); foreach ($rows as $obj) { if ($limit == 0) { break; } $rc = RecentChange::newFromRow($obj); $rc->counter = $counter++; # Check if the page has been updated since the last visit if ($wgShowUpdatedMarker && !empty($obj->wl_notificationtimestamp)) { $rc->notificationtimestamp = $obj->rc_timestamp >= $obj->wl_notificationtimestamp; } else { $rc->notificationtimestamp = false; // Default } # Check the number of users watching the page $rc->numberofWatchingusers = 0; // Default if ($showWatcherCount && $obj->rc_namespace >= 0) { if (!isset($watcherCache[$obj->rc_namespace][$obj->rc_title])) { $watcherCache[$obj->rc_namespace][$obj->rc_title] = $dbr->selectField('watchlist', 'COUNT(*)', array('wl_namespace' => $obj->rc_namespace, 'wl_title' => $obj->rc_title), __METHOD__ . '-watchers'); } $rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title]; } $changeLine = $list->recentChangesLine($rc, !empty($obj->wl_user), $counter); if ($changeLine !== false) { $s .= $changeLine; --$limit; } } $s .= $list->endRecentChangesList(); $this->getOutput()->addHTML($s); }
/** * 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 roll- * back 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 object $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 = new Revision($row); $validRevision = (bool) $rev->getId(); } catch (Exception $e) { $validRevision = false; } MediaWiki\restoreWarnings(); if ($validRevision) { $classes = []; $page = Title::newFromRow($row); $link = Linker::link($page, htmlspecialchars($page->getPrefixedText()), ['class' => 'mw-contributions-title'], $page->isRedirect() ? ['redirect' => 'no'] : []); # Mark current revisions $topmarktext = ''; $user = $this->getUser(); if ($row->rev_id === $row->page_latest) { $topmarktext .= '<span class="mw-uctop">' . $this->messages['uctop'] . '</span>'; $classes[] = 'mw-contributions-current'; # Add rollback link if (!$row->page_is_new && $page->quickUserCan('rollback', $user) && $page->quickUserCan('edit', $user)) { $this->preventClickjacking(); $topmarktext .= ' ' . Linker::generateRollback($rev, $this->getContext()); } } # Is there a visible previous revision? if ($rev->userCan(Revision::DELETED_TEXT, $user) && $rev->getParentId() !== 0) { $difftext = Linker::linkKnown($page, $this->messages['diff'], [], ['diff' => 'prev', 'oldid' => $row->rev_id]); } else { $difftext = $this->messages['diff']; } $histlink = Linker::linkKnown($page, $this->messages['hist'], [], ['action' => 'history']); if ($row->rev_parent_id === null) { // For some reason rev_parent_id isn't populated for this row. // Its rumoured this is true on wikipedia for some revisions (bug 34922). // Next best thing is to have the total number of bytes. $chardiff = ' <span class="mw-changeslist-separator">. .</span> '; $chardiff .= Linker::formatRevisionSize($row->rev_len); $chardiff .= ' <span class="mw-changeslist-separator">. .</span> '; } else { $parentLen = 0; if (isset($this->mParentLens[$row->rev_parent_id])) { $parentLen = $this->mParentLens[$row->rev_parent_id]; } $chardiff = ' <span class="mw-changeslist-separator">. .</span> '; $chardiff .= ChangesList::showCharacterDifference($parentLen, $row->rev_len, $this->getContext()); $chardiff .= ' <span class="mw-changeslist-separator">. .</span> '; } $lang = $this->getLanguage(); $comment = $lang->getDirMark() . Linker::revComment($rev, false, true); $date = $lang->userTimeAndDate($row->rev_timestamp, $user); if ($rev->userCan(Revision::DELETED_TEXT, $user)) { $d = Linker::linkKnown($page, htmlspecialchars($date), ['class' => 'mw-changeslist-date'], ['oldid' => intval($row->rev_id)]); } else { $d = htmlspecialchars($date); } if ($rev->isDeleted(Revision::DELETED_TEXT)) { $d = '<span class="history-deleted">' . $d . '</span>'; } # Show user names for /newbies as there may be different users. # Note that we already excluded rows with hidden user names. if ($this->contribs == 'newbie') { $userlink = ' . . ' . $lang->getDirMark() . Linker::userLink($rev->getUser(), $rev->getUserText()); $userlink .= ' ' . $this->msg('parentheses')->rawParams(Linker::userTalkLink($rev->getUser(), $rev->getUserText()))->escaped() . ' '; } else { $userlink = ''; } $flags = []; if ($rev->getParentId() === 0) { $flags[] = ChangesList::flag('newpage'); } if ($rev->isMinor()) { $flags[] = ChangesList::flag('minor'); } $del = Linker::getRevDeleteLink($user, $rev, $page); if ($del !== '') { $del .= ' '; } $diffHistLinks = $this->msg('parentheses')->rawParams($difftext . $this->messages['pipe-separator'] . $histlink)->escaped(); # Tags, if any. list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow($row->ts_tags, 'contributions', $this->getContext()); $classes = array_merge($classes, $newClasses); Hooks::run('SpecialContributions::formatRow::flags', [$this->getContext(), $row, &$flags]); $templateParams = ['del' => $del, 'timestamp' => $d, 'diffHistLinks' => $diffHistLinks, 'charDifference' => $chardiff, 'flags' => $flags, 'articleLink' => $link, 'userlink' => $userlink, 'logText' => $comment, 'topmarktext' => $topmarktext, 'tagSummary' => $tagSummary]; # Denote if username is redacted for this edit if ($rev->isDeleted(Revision::DELETED_USER)) { $templateParams['rev-deleted-user-contribs'] = $this->msg('rev-deleted-user-contribs')->escaped(); } $templateParser = new TemplateParser(); $ret = $templateParser->processTemplate('SpecialContributionsLine', $templateParams); } // Let extensions add data Hooks::run('ContributionsLineEnding', [$this, &$ret, $row, &$classes]); // TODO: Handle exceptions in the catch block above. Do any extensions rely on // receiving empty rows? if ($classes === [] && $ret === '') { wfDebug("Dropping Special:Contribution row that could not be formatted\n"); return "<!-- Could not format Special:Contribution row. -->\n"; } // FIXME: The signature of the ContributionsLineEnding hook makes it // very awkward to move this LI wrapper into the template. return Html::rawElement('li', ['class' => $classes], $ret) . "\n"; }
function showDiffPage($diffOnly = false) { wfProfileIn(__METHOD__); # Allow frames except in certain special cases $out = $this->getOutput(); $out->allowClickjacking(); $out->setRobotPolicy('noindex,nofollow'); if (!$this->loadRevisionData()) { $this->showMissingRevision(); wfProfileOut(__METHOD__); return; } $user = $this->getUser(); $permErrors = $this->mNewPage->getUserPermissionsErrors('read', $user); if ($this->mOldPage) { # mOldPage might not be set, see below. $permErrors = wfMergeErrorArrays($permErrors, $this->mOldPage->getUserPermissionsErrors('read', $user)); } if (count($permErrors)) { wfProfileOut(__METHOD__); throw new PermissionsError('read', $permErrors); } # If external diffs are enabled both globally and for the user, # we'll use the application/x-external-editor interface to call # an external diff tool like kompare, kdiff3, etc. if (ExternalEdit::useExternalEngine($this->getContext(), 'diff')) { //TODO: come up with a good solution for non-text content here. // at least, the content format needs to be passed to the client somehow. // Currently, action=raw will just fail for non-text content. $urls = array('File' => array('Extension' => 'wiki', 'URL' => $this->mNewPage->getCanonicalURL(array('action' => 'raw', 'oldid' => $this->mOldid))), 'File2' => array('Extension' => 'wiki', 'URL' => $this->mNewPage->getCanonicalURL(array('action' => 'raw', 'oldid' => $this->mNewid)))); $externalEditor = new ExternalEdit($this->getContext(), $urls); $externalEditor->execute(); wfProfileOut(__METHOD__); return; } $rollback = ''; $undoLink = ''; $query = array(); # Carry over 'diffonly' param via navigation links if ($diffOnly != $user->getBoolOption('diffonly')) { $query['diffonly'] = $diffOnly; } # Cascade unhide param in links for easy deletion browsing if ($this->unhide) { $query['unhide'] = 1; } # Check if one of the revisions is deleted/suppressed $deleted = $suppressed = false; $allowed = $this->mNewRev->userCan(Revision::DELETED_TEXT, $user); # mOldRev is false if the difference engine is called with a "vague" query for # a diff between a version V and its previous version V' AND the version V # is the first version of that article. In that case, V' does not exist. if ($this->mOldRev === false) { $out->setPageTitle($this->msg('difference-title', $this->mNewPage->getPrefixedText())); $samePage = true; $oldHeader = ''; } else { wfRunHooks('DiffViewHeader', array($this, $this->mOldRev, $this->mNewRev)); $sk = $this->getSkin(); if (method_exists($sk, 'suppressQuickbar')) { $sk->suppressQuickbar(); } if ($this->mNewPage->equals($this->mOldPage)) { $out->setPageTitle($this->msg('difference-title', $this->mNewPage->getPrefixedText())); $samePage = true; } else { $out->setPageTitle($this->msg('difference-title-multipage', $this->mOldPage->getPrefixedText(), $this->mNewPage->getPrefixedText())); $out->addSubtitle($this->msg('difference-multipage')); $samePage = false; } if ($samePage && $this->mNewPage->quickUserCan('edit', $user)) { if ($this->mNewRev->isCurrent() && $this->mNewPage->userCan('rollback', $user)) { $out->preventClickjacking(); $rollback = '   ' . Linker::generateRollback($this->mNewRev, $this->getContext()); } if (!$this->mOldRev->isDeleted(Revision::DELETED_TEXT) && !$this->mNewRev->isDeleted(Revision::DELETED_TEXT)) { $undoLink = ' ' . $this->msg('parentheses')->rawParams(Html::element('a', array('href' => $this->mNewPage->getLocalUrl(array('action' => 'edit', 'undoafter' => $this->mOldid, 'undo' => $this->mNewid)), 'title' => Linker::titleAttrib('undo')), $this->msg('editundo')->text()))->escaped(); } } # Make "previous revision link" if ($samePage && $this->mOldRev->getPrevious()) { $prevlink = Linker::linkKnown($this->mOldPage, $this->msg('previousdiff')->escaped(), array('id' => 'differences-prevlink'), array('diff' => 'prev', 'oldid' => $this->mOldid) + $query); } else { $prevlink = ' '; } if ($this->mOldRev->isMinor()) { $oldminor = ChangesList::flag('minor'); } else { $oldminor = ''; } $ldel = $this->revisionDeleteLink($this->mOldRev); $oldRevisionHeader = $this->getRevisionHeader($this->mOldRev, 'complete'); $oldHeader = '<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader . '</strong></div>' . '<div id="mw-diff-otitle2">' . Linker::revUserTools($this->mOldRev, !$this->unhide) . '</div>' . '<div id="mw-diff-otitle3">' . $oldminor . Linker::revComment($this->mOldRev, !$diffOnly, !$this->unhide) . $ldel . '</div>' . '<div id="mw-diff-otitle4">' . $prevlink . '</div>'; if ($this->mOldRev->isDeleted(Revision::DELETED_TEXT)) { $deleted = true; // old revisions text is hidden if ($this->mOldRev->isDeleted(Revision::DELETED_RESTRICTED)) { $suppressed = true; // also suppressed } } # Check if this user can see the revisions if (!$this->mOldRev->userCan(Revision::DELETED_TEXT, $user)) { $allowed = false; } } # Make "next revision link" # Skip next link on the top revision if ($samePage && !$this->mNewRev->isCurrent()) { $nextlink = Linker::linkKnown($this->mNewPage, $this->msg('nextdiff')->escaped(), array('id' => 'differences-nextlink'), array('diff' => 'next', 'oldid' => $this->mNewid) + $query); } else { $nextlink = ' '; } if ($this->mNewRev->isMinor()) { $newminor = ChangesList::flag('minor'); } else { $newminor = ''; } # Handle RevisionDelete links... $rdel = $this->revisionDeleteLink($this->mNewRev); $newRevisionHeader = $this->getRevisionHeader($this->mNewRev, 'complete') . $undoLink; $newHeader = '<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader . '</strong></div>' . '<div id="mw-diff-ntitle2">' . Linker::revUserTools($this->mNewRev, !$this->unhide) . " {$rollback}</div>" . '<div id="mw-diff-ntitle3">' . $newminor . Linker::revComment($this->mNewRev, !$diffOnly, !$this->unhide) . $rdel . '</div>' . '<div id="mw-diff-ntitle4">' . $nextlink . $this->markPatrolledLink() . '</div>'; if ($this->mNewRev->isDeleted(Revision::DELETED_TEXT)) { $deleted = true; // new revisions text is hidden if ($this->mNewRev->isDeleted(Revision::DELETED_RESTRICTED)) { $suppressed = true; } // also suppressed } # If the diff cannot be shown due to a deleted revision, then output # the diff header and links to unhide (if available)... if ($deleted && (!$this->unhide || !$allowed)) { $this->showDiffStyle(); $multi = $this->getMultiNotice(); $out->addHTML($this->addHeader('', $oldHeader, $newHeader, $multi)); if (!$allowed) { $msg = $suppressed ? 'rev-suppressed-no-diff' : 'rev-deleted-no-diff'; # Give explanation for why revision is not visible $out->wrapWikiMsg("<div id='mw-{$msg}' class='mw-warning plainlinks'>\n\$1\n</div>\n", array($msg)); } else { # Give explanation and add a link to view the diff... $link = $this->getTitle()->getFullUrl($this->getRequest()->appendQueryValue('unhide', '1', true)); $msg = $suppressed ? 'rev-suppressed-unhide-diff' : 'rev-deleted-unhide-diff'; $out->wrapWikiMsg("<div id='mw-{$msg}' class='mw-warning plainlinks'>\n\$1\n</div>\n", array($msg, $link)); } # Otherwise, output a regular diff... } else { # Add deletion notice if the user is viewing deleted content $notice = ''; if ($deleted) { $msg = $suppressed ? 'rev-suppressed-diff-view' : 'rev-deleted-diff-view'; $notice = "<div id='mw-{$msg}' class='mw-warning plainlinks'>\n" . $this->msg($msg)->parse() . "</div>\n"; } $this->showDiff($oldHeader, $newHeader, $notice); if (!$diffOnly) { $this->renderNewRevision(); } } wfProfileOut(__METHOD__); }
/** * Returns a row from the history printout. * * @todo document some more, and maybe clean up the code (some params redundant?) * * @param $row Object: the database row corresponding to the previous line. * @param $next Mixed: the database row corresponding to the next line. (chronologically previous) * @param $notificationtimestamp * @param $latest Boolean: whether this row corresponds to the page's latest revision. * @param $firstInList Boolean: whether this row corresponds to the first displayed on this history page. * @return String: HTML output for the row */ function historyLine($row, $next, $notificationtimestamp = false, $latest = false, $firstInList = false) { $rev = new Revision($row); $rev->setTitle($this->getTitle()); if (is_object($next)) { $prevRev = new Revision($next); $prevRev->setTitle($this->getTitle()); } else { $prevRev = null; } $curlink = $this->curLink($rev, $latest); $lastlink = $this->lastLink($rev, $next); $diffButtons = $this->diffButtons($rev, $firstInList); $histLinks = Html::rawElement('span', array('class' => 'mw-history-histlinks'), '(' . $curlink . $this->historyPage->message['pipe-separator'] . $lastlink . ') '); $s = $histLinks . $diffButtons; $link = $this->revLink($rev); $classes = array(); $del = ''; $user = $this->getUser(); // Show checkboxes for each revision if ($user->isAllowed('deleterevision')) { $this->preventClickjacking(); // If revision was hidden from sysops, disable the checkbox if (!$rev->userCan(Revision::DELETED_RESTRICTED, $user)) { $del = Xml::check('deleterevisions', false, array('disabled' => 'disabled')); // Otherwise, enable the checkbox... } else { $del = Xml::check('showhiderevisions', false, array('name' => 'ids[' . $rev->getId() . ']')); } // User can only view deleted revisions... } elseif ($rev->getVisibility() && $user->isAllowed('deletedhistory')) { // If revision was hidden from sysops, disable the link if (!$rev->userCan(Revision::DELETED_RESTRICTED, $user)) { $cdel = Linker::revDeleteLinkDisabled(false); // Otherwise, show the link... } else { $query = array('type' => 'revision', 'target' => $this->getTitle()->getPrefixedDbkey(), 'ids' => $rev->getId()); $del .= Linker::revDeleteLink($query, $rev->isDeleted(Revision::DELETED_RESTRICTED), false); } } if ($del) { $s .= " {$del} "; } $lang = $this->getLanguage(); $dirmark = $lang->getDirMark(); $s .= " {$link}"; $s .= $dirmark; $s .= " <span class='history-user'>" . Linker::revUserTools($rev, true) . "</span>"; $s .= $dirmark; if ($rev->isMinor()) { $s .= ' ' . ChangesList::flag('minor'); } # Size is always public data $prevSize = $prevRev ? $prevRev->getSize() : 0; $sDiff = ChangesList::showCharacterDifference($prevSize, $rev->getSize()); $fSize = Linker::formatRevisionSize($rev->getSize()); $s .= " . . {$fSize} {$sDiff} . . "; $s .= Linker::revComment($rev, false, true); if ($notificationtimestamp && $row->rev_timestamp >= $notificationtimestamp) { $s .= ' <span class="updatedmarker">' . $this->msg('updatedmarker')->escaped() . '</span>'; } $tools = array(); # Rollback and undo links if ($prevRev && !count($this->getTitle()->getUserPermissionsErrors('edit', $this->getUser()))) { if ($latest && !count($this->getTitle()->getUserPermissionsErrors('rollback', $this->getUser()))) { $this->preventClickjacking(); $tools[] = '<span class="mw-rollback-link">' . Linker::buildRollbackLink($rev) . '</span>'; } if (!$rev->isDeleted(Revision::DELETED_TEXT) && !$prevRev->isDeleted(Revision::DELETED_TEXT)) { # Create undo tooltip for the first (=latest) line only $undoTooltip = $latest ? array('title' => $this->msg('tooltip-undo')->text()) : array(); $undolink = Linker::linkKnown($this->getTitle(), $this->msg('editundo')->escaped(), $undoTooltip, array('action' => 'edit', 'undoafter' => $prevRev->getId(), 'undo' => $rev->getId())); $tools[] = "<span class=\"mw-history-undo\">{$undolink}</span>"; } } if ($tools) { $s .= ' (' . $lang->pipeList($tools) . ')'; } # Tags list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow($row->ts_tags, 'history'); $classes = array_merge($classes, $newClasses); $s .= " {$tagSummary}"; wfRunHooks('PageHistoryLineEnding', array($this, &$row, &$s, &$classes)); $attribs = array(); if ($classes) { $attribs['class'] = implode(' ', $classes); } return Xml::tags('li', $attribs, $s) . "\n"; }
/** * Constructor */ function wfSpecialRecentchanges($par, $specialPage) { global $wgUser, $wgOut, $wgRequest, $wgUseRCPatrol; global $wgRCShowWatchingUsers, $wgShowUpdatedMarker; global $wgAllowCategorizedRecentChanges; $fname = 'wfSpecialRecentchanges'; # Get query parameters $feedFormat = $wgRequest->getVal('feed'); /* Checkbox values can't be true by default, because * we cannot differentiate between unset and not set at all */ $defaults = array('days' => $wgUser->getDefaultOption('rcdays'), 'limit' => $wgUser->getDefaultOption('rclimit'), 'hideminor' => false, 'hidebots' => true, 'hideanons' => false, 'hideliu' => false, 'hidepatrolled' => false, 'hidemyself' => false, 'from' => '', 'namespace' => null, 'invert' => false, 'categories_any' => false); extract($defaults); $days = $wgUser->getOption('rcdays', $defaults['days']); $days = $wgRequest->getInt('days', $days); $limit = $wgUser->getOption('rclimit', $defaults['limit']); # list( $limit, $offset ) = wfCheckLimits( 100, 'rclimit' ); $limit = $wgRequest->getInt('limit', $limit); /* order of selection: url > preferences > default */ $hideminor = $wgRequest->getBool('hideminor', $wgUser->getOption('hideminor') ? true : $defaults['hideminor']); # As a feed, use limited settings only if ($feedFormat) { global $wgFeedLimit; if ($limit > $wgFeedLimit) { $limit = $wgFeedLimit; } } else { $namespace = $wgRequest->getIntOrNull('namespace'); $invert = $wgRequest->getBool('invert', $defaults['invert']); $hidebots = $wgRequest->getBool('hidebots', $defaults['hidebots']); $hideanons = $wgRequest->getBool('hideanons', $defaults['hideanons']); $hideliu = $wgRequest->getBool('hideliu', $defaults['hideliu']); $hidepatrolled = $wgRequest->getBool('hidepatrolled', $defaults['hidepatrolled']); $hidemyself = $wgRequest->getBool('hidemyself', $defaults['hidemyself']); $from = $wgRequest->getVal('from', $defaults['from']); # Get query parameters from path if ($par) { $bits = preg_split('/\\s*,\\s*/', trim($par)); foreach ($bits as $bit) { if ('hidebots' == $bit) { $hidebots = 1; } if ('bots' == $bit) { $hidebots = 0; } if ('hideminor' == $bit) { $hideminor = 1; } if ('minor' == $bit) { $hideminor = 0; } if ('hideliu' == $bit) { $hideliu = 1; } if ('hidepatrolled' == $bit) { $hidepatrolled = 1; } if ('hideanons' == $bit) { $hideanons = 1; } if ('hidemyself' == $bit) { $hidemyself = 1; } if (is_numeric($bit)) { $limit = $bit; } $m = array(); if (preg_match('/^limit=(\\d+)$/', $bit, $m)) { $limit = $m[1]; } if (preg_match('/^days=(\\d+)$/', $bit, $m)) { $days = $m[1]; } } } } if ($limit < 0 || $limit > 5000) { $limit = $defaults['limit']; } # Database connection and caching $dbr = wfGetDB(DB_SLAVE); list($recentchanges, $watchlist) = $dbr->tableNamesN('recentchanges', 'watchlist'); $cutoff_unixtime = time() - $days * 86400; $cutoff_unixtime = $cutoff_unixtime - $cutoff_unixtime % 86400; $cutoff = $dbr->timestamp($cutoff_unixtime); if (preg_match('/^[0-9]{14}$/', $from) and $from > wfTimestamp(TS_MW, $cutoff)) { $cutoff = $dbr->timestamp($from); } else { $from = $defaults['from']; } # 10 seconds server-side caching max $wgOut->setSquidMaxage(10); # Get last modified date, for client caching # Don't use this if we are using the patrol feature, patrol changes don't update the timestamp $lastmod = $dbr->selectField('recentchanges', 'MAX(rc_timestamp)', false, $fname); if ($feedFormat || !$wgUseRCPatrol) { if ($lastmod && $wgOut->checkLastModified($lastmod)) { # Client cache fresh and headers sent, nothing more to do. return; } } # It makes no sense to hide both anons and logged-in users # Where this occurs, force anons to be shown if ($hideanons && $hideliu) { $hideanons = false; } # Form WHERE fragments for all the options $hidem = $hideminor ? 'AND rc_minor = 0' : ''; $hidem .= $hidebots ? ' AND rc_bot = 0' : ''; $hidem .= $hideliu ? ' AND rc_user = 0' : ''; $hidem .= $wgUseRCPatrol && $hidepatrolled ? ' AND rc_patrolled = 0' : ''; $hidem .= $hideanons ? ' AND rc_user != 0' : ''; if ($hidemyself) { if ($wgUser->getID()) { $hidem .= ' AND rc_user != ' . $wgUser->getID(); } else { $hidem .= ' AND rc_user_text != ' . $dbr->addQuotes($wgUser->getName()); } } # Namespace filtering $hidem .= is_null($namespace) ? '' : ' AND rc_namespace' . ($invert ? '!=' : '=') . $namespace; // This is the big thing! $uid = $wgUser->getID(); // Perform query $forceclause = $dbr->useIndexClause("rc_timestamp"); $sql2 = "SELECT * FROM {$recentchanges} {$forceclause}" . ($uid ? "LEFT OUTER JOIN {$watchlist} ON wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace " : "") . "WHERE rc_timestamp >= '{$cutoff}' {$hidem} " . "ORDER BY rc_timestamp DESC"; $sql2 = $dbr->limitResult($sql2, $limit, 0); $res = $dbr->query($sql2, $fname); // Fetch results, prepare a batch link existence check query $rows = array(); $batch = new LinkBatch(); while ($row = $dbr->fetchObject($res)) { $rows[] = $row; if (!$feedFormat) { // User page link $title = Title::makeTitleSafe(NS_USER, $row->rc_user_text); $batch->addObj($title); // User talk $title = Title::makeTitleSafe(NS_USER_TALK, $row->rc_user_text); $batch->addObj($title); } } $dbr->freeResult($res); if ($feedFormat) { rcOutputFeed($rows, $feedFormat, $limit, $hideminor, $lastmod); } else { # Web output... // Run existence checks $batch->execute(); $any = $wgRequest->getBool('categories_any', $defaults['categories_any']); // Output header if (!$specialPage->including()) { $wgOut->addWikiText(wfMsgForContentNoTrans("recentchangestext")); // Dump everything here $nondefaults = array(); wfAppendToArrayIfNotDefault('days', $days, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('limit', $limit, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hideminor', $hideminor, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hidebots', $hidebots, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hideanons', $hideanons, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hideliu', $hideliu, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hidepatrolled', $hidepatrolled, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hidemyself', $hidemyself, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('from', $from, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('namespace', $namespace, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('invert', $invert, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('categories_any', $any, $defaults, $nondefaults); // Add end of the texts $wgOut->addHTML('<div class="rcoptions">' . rcOptionsPanel($defaults, $nondefaults) . "\n"); $wgOut->addHTML(rcNamespaceForm($namespace, $invert, $nondefaults, $any) . '</div>' . "\n"); } // And now for the content $list = ChangesList::newFromUser($wgUser); if ($wgAllowCategorizedRecentChanges) { $categories = trim($wgRequest->getVal('categories', "")); $categories = str_replace("|", "\n", $categories); $categories = explode("\n", $categories); rcFilterByCategories($rows, $categories, $any); } $s = $list->beginRecentChangesList(); $counter = 1; foreach ($rows as $obj) { if ($limit == 0) { break; } if (!($hideminor && $obj->rc_minor) && !($hidepatrolled && $obj->rc_patrolled)) { $rc = RecentChange::newFromRow($obj); $rc->counter = $counter++; if ($wgShowUpdatedMarker && !empty($obj->wl_notificationtimestamp) && $obj->rc_timestamp >= $obj->wl_notificationtimestamp) { $rc->notificationtimestamp = true; } else { $rc->notificationtimestamp = false; } if ($wgRCShowWatchingUsers && $wgUser->getOption('shownumberswatching')) { $sql3 = "SELECT COUNT(*) AS n FROM {$watchlist} WHERE wl_title='" . $dbr->strencode($obj->rc_title) . "' AND wl_namespace={$obj->rc_namespace}"; $res3 = $dbr->query($sql3, 'wfSpecialRecentChanges'); $x = $dbr->fetchObject($res3); $rc->numberofWatchingusers = $x->n; } else { $rc->numberofWatchingusers = 0; } $s .= $list->recentChangesLine($rc, !empty($obj->wl_user)); --$limit; } } $s .= $list->endRecentChangesList(); $wgOut->addHTML($s); } }
/** * Constructor * @todo Document $par parameter. * @param $par String: FIXME */ function wfSpecialWatchlist($par) { global $wgUser, $wgOut, $wgLang, $wgMemc, $wgRequest, $wgContLang; global $wgUseWatchlistCache, $wgWLCacheTimeout, $wgDBname; global $wgRCShowWatchingUsers, $wgEnotifWatchlist, $wgShowUpdatedMarker; global $wgEnotifWatchlist; $fname = 'wfSpecialWatchlist'; $skin =& $wgUser->getSkin(); $specialTitle = Title::makeTitle(NS_SPECIAL, 'Watchlist'); $wgOut->setRobotPolicy('noindex,nofollow'); # Anons don't get a watchlist if ($wgUser->isAnon()) { $wgOut->setPageTitle(wfMsg('watchnologin')); $llink = $skin->makeKnownLinkObj(Title::makeTitle(NS_SPECIAL, 'Userlogin'), wfMsgHtml('loginreqlink'), 'returnto=' . $specialTitle->getPrefixedUrl()); $wgOut->addHtml(wfMsgWikiHtml('watchlistanontext', $llink)); return; } else { $wgOut->setPageTitle(wfMsg('watchlist')); $wgOut->setSubtitle(wfMsgWikiHtml('watchlistfor', htmlspecialchars($wgUser->getName()))); } if (wlHandleClear($wgOut, $wgRequest, $par)) { return; } $defaults = array('days' => floatval($wgUser->getOption('watchlistdays')), 'hideOwn' => (int) $wgUser->getBoolOption('watchlisthideown'), 'hideBots' => (int) $wgUser->getBoolOption('watchlisthidebots'), 'namespace' => 'all'); extract($defaults); # Extract variables from the request, falling back to user preferences or # other default values if these don't exist $prefs['days'] = floatval($wgUser->getOption('watchlistdays')); $prefs['hideown'] = $wgUser->getBoolOption('watchlisthideown'); $prefs['hidebots'] = $wgUser->getBoolOption('watchlisthidebots'); # Get query variables $days = $wgRequest->getVal('days', $prefs['days']); $hideOwn = $wgRequest->getBool('hideOwn', $prefs['hideown']); $hideBots = $wgRequest->getBool('hideBots', $prefs['hidebots']); # Get namespace value, if supplied, and prepare a WHERE fragment $nameSpace = $wgRequest->getIntOrNull('namespace'); if (!is_null($nameSpace)) { $nameSpace = intval($nameSpace); $nameSpaceClause = " AND rc_namespace = {$nameSpace}"; } else { $nameSpace = ''; $nameSpaceClause = ''; } # Watchlist editing $action = $wgRequest->getVal('action'); $remove = $wgRequest->getVal('remove'); $id = $wgRequest->getArray('id'); $uid = $wgUser->getID(); if ($wgEnotifWatchlist && $wgRequest->getVal('reset') && $wgRequest->wasPosted()) { $wgUser->clearAllNotifications($uid); } # Deleting items from watchlist if ($action == 'submit' && isset($remove) && is_array($id)) { $wgOut->addWikiText(wfMsg('removingchecked')); $wgOut->addHTML('<p>'); foreach ($id as $one) { $t = Title::newFromURL($one); if (!is_null($t)) { // WERELATE - run UnwatchArticle hooks; fix UnwatchArticle hook below to UnwatchArticleComplete $article = new Article($t); if (wfRunHooks('UnwatchArticle', array(&$wgUser, &$article))) { $wl = WatchedItem::fromUserTitle($wgUser, $t); if ($wl->removeWatch() === false) { $wgOut->addHTML("<br />\n" . wfMsg('couldntremove', htmlspecialchars($one))); } else { wfRunHooks('UnwatchArticleComplete', array(&$wgUser, &$article)); $wgOut->addHTML(' (' . htmlspecialchars($one) . ')'); } } } else { $wgOut->addHTML("<br />\n" . wfMsg('iteminvalidname', htmlspecialchars($one))); } } $wgOut->addHTML("<br />\n" . wfMsg('wldone') . "</p>\n"); } if ($wgUseWatchlistCache) { $memckey = "{$wgDBname}:watchlist:id:" . $wgUser->getId(); $cache_s = @$wgMemc->get($memckey); if ($cache_s) { $wgOut->addWikiText(wfMsg('wlsaved')); $wgOut->addHTML($cache_s); return; } } $dbr =& wfGetDB(DB_SLAVE); extract($dbr->tableNames('page', 'revision', 'watchlist', 'recentchanges')); $sql = "SELECT COUNT(*) AS n FROM {$watchlist} WHERE wl_user={$uid}"; $res = $dbr->query($sql, $fname); $s = $dbr->fetchObject($res); # Patch *** A1 *** (see A2 below) # adjust for page X, talk:page X, which are both stored separately, but treated together $nitems = floor($s->n / 2); # $nitems = $s->n; if ($nitems == 0) { $wgOut->addWikiText(wfMsg('nowatchlist')); return; } if (is_null($days) || !is_numeric($days)) { $big = 1000; /* The magical big */ // WERELATE - don't change default; pages don't change that often // if($nitems > $big) { # Set default cutoff shorter // $days = $defaults['days'] = (12.0 / 24.0); # 12 hours... // } else { $days = $defaults['days']; # default cutoff for shortlisters // } } else { $days = floatval($days); } // Dump everything here $nondefaults = array(); wfAppendToArrayIfNotDefault('days', $days, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hideOwn', (int) $hideOwn, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('hideBots', (int) $hideBots, $defaults, $nondefaults); wfAppendToArrayIfNotDefault('namespace', $nameSpace, $defaults, $nondefaults); if ($days <= 0) { $docutoff = ''; $cutoff = false; $npages = wfMsg('watchlistall1'); } else { $docutoff = "AND rev_timestamp > '" . ($cutoff = $dbr->timestamp(time() - intval($days * 86400))) . "'"; /* $sql = "SELECT COUNT(*) AS n FROM $page, $revision WHERE rev_timestamp>'$cutoff' AND page_id=rev_page"; $res = $dbr->query( $sql, $fname ); $s = $dbr->fetchObject( $res ); $npages = $s->n; */ $npages = 40000 * $days; } /* Edit watchlist form */ if ($wgRequest->getBool('edit') || $par == 'edit') { $wgOut->addWikiText(wfMsg('watchlistcontains', $wgLang->formatNum($nitems)) . "\n\n" . wfMsg('watcheditlist')); $wgOut->addHTML('<form action=\'' . $specialTitle->escapeLocalUrl('action=submit') . "' method='post'>\n"); # Patch A2 # The following was proposed by KTurner 07.11.2004 to T.Gries # $sql = "SELECT distinct (wl_namespace & ~1),wl_title FROM $watchlist WHERE wl_user=$uid"; $sql = "SELECT wl_namespace, wl_title, page_is_redirect FROM {$watchlist} LEFT JOIN {$page} ON wl_namespace = page_namespace AND wl_title = page_title WHERE wl_user={$uid}"; $res = $dbr->query($sql, $fname); # Batch existence check $linkBatch = new LinkBatch(); while ($row = $dbr->fetchObject($res)) { $linkBatch->addObj(Title::makeTitleSafe($row->wl_namespace, $row->wl_title)); } $linkBatch->execute(); if ($dbr->numRows($res) > 0) { $dbr->dataSeek($res, 0); } # Let's do the time warp again! $sk = $wgUser->getSkin(); $list = array(); while ($s = $dbr->fetchObject($res)) { $list[$s->wl_namespace][$s->wl_title] = $s->page_is_redirect; } // TODO: Display a TOC foreach ($list as $ns => $titles) { if (Namespac::isTalk($ns)) { continue; } if ($ns != NS_MAIN) { $wgOut->addHTML('<h2>' . $wgContLang->getFormattedNsText($ns) . '</h2>'); } $wgOut->addHTML('<ul>'); foreach ($titles as $title => $redir) { $titleObj = Title::makeTitle($ns, $title); if (is_null($titleObj)) { $wgOut->addHTML('<!-- bad title "' . htmlspecialchars($s->wl_title) . '" in namespace ' . $s->wl_namespace . " -->\n"); } else { global $wgContLang; $toolLinks = array(); $titleText = $titleObj->getPrefixedText(); $pageLink = $sk->makeLinkObj($titleObj); $toolLinks[] = $sk->makeLinkObj($titleObj->getTalkPage(), $wgLang->getNsText(NS_TALK)); if ($titleObj->exists()) { $toolLinks[] = $sk->makeKnownLinkObj($titleObj, wfMsgHtml('history_short'), 'action=history'); } $toolLinks = '(' . implode(' | ', $toolLinks) . ')'; $checkbox = '<input type="checkbox" name="id[]" value="' . htmlspecialchars($titleObj->getPrefixedText()) . '" /> ' . ($wgContLang->isRTL() ? '‏' : '‎'); if ($redir) { $spanopen = '<span class="watchlistredir">'; $spanclosed = '</span>'; } else { $spanopen = $spanclosed = ''; } $wgOut->addHTML("<li>{$checkbox}{$spanopen}{$pageLink}{$spanclosed} {$toolLinks}</li>\n"); } } $wgOut->addHTML('</ul>'); } $wgOut->addHTML("<input type='submit' name='remove' value=\"" . htmlspecialchars(wfMsg("removechecked")) . "\" />\n" . "</form>\n"); return; } # If the watchlist is relatively short, it's simplest to zip # down its entirety and then sort the results. # If it's relatively long, it may be worth our while to zip # through the time-sorted page list checking for watched items. # Up estimate of watched items by 15% to compensate for talk pages... # Toggles $andHideOwn = $hideOwn ? "AND (rc_user <> {$uid})" : ''; $andHideBots = $hideBots ? "AND (rc_bot = 0)" : ''; # Show watchlist header $header = ''; if ($wgUser->getOption('enotifwatchlistpages') && $wgEnotifWatchlist) { $header .= wfMsg('wlheader-enotif') . "\n"; } if ($wgEnotifWatchlist && $wgShowUpdatedMarker) { $header .= wfMsg('wlheader-showupdated') . "\n"; } # Toggle watchlist content (all recent edits or just the latest) if ($wgUser->getOption('extendwatchlist')) { $andLatest = ''; $limitWatchlist = 'LIMIT ' . intval($wgUser->getOption('wllimit')); } else { $andLatest = 'AND rc_this_oldid=page_latest'; $limitWatchlist = ''; } # TODO: Consider removing the third parameter $header .= wfMsg('watchdetails', $wgLang->formatNum($nitems), $wgLang->formatNum($npages), '', $specialTitle->getFullUrl('edit=yes')); $wgOut->addWikiText($header); if ($wgEnotifWatchlist && $wgShowUpdatedMarker) { $wgOut->addHTML('<form action="' . $specialTitle->escapeLocalUrl() . '" method="post"><input type="submit" name="dummy" value="' . htmlspecialchars(wfMsg('enotif_reset')) . '" /><input type="hidden" name="reset" value="all" /></form>' . "\n\n"); } // WERELATE - handle changed view if ($wgRequest->getBool('changed') || $par == 'changed') { $wgOut->addHTML('<hr /><h2>All pages changed since last visited</h2><ul><li><a href="' . $specialTitle->getFullUrl() . '">Show recently-changed pages</a></li></ul>'); $sql = 'select wl_namespace, wl_title from watchlist where wl_user='******' and wl_notificationtimestamp > \'0\''; $res = $dbr->query($sql, $fname); $sk = $wgUser->getSkin(); $changed = ''; $count = 0; while ($obj = $dbr->fetchObject($res)) { $t = Title::makeTitle($obj->wl_namespace, $obj->wl_title); $historylink = $sk->makeKnownLinkObj($t, 'hist', wfArrayToCGI(array('action' => 'history'))); $articlelink = $sk->makeKnownLinkObj($t); $changed .= "<li> ( {$historylink} ) . . {$articlelink}</li>\n"; $count += 1; } $dbr->freeResult($res); $wgOut->addHTML("<h3>{$count} page(s)</h3>\n<ul>" . $changed . '</ul>'); return; } // WERELATE - remove join with page table $sql = "SELECT\n\t rc_namespace AS page_namespace, rc_title AS page_title,\n\t rc_comment AS rev_comment, rc_cur_id AS page_id,\n\t rc_user AS rev_user, rc_user_text AS rev_user_text,\n\t rc_timestamp AS rev_timestamp, rc_minor AS rev_minor_edit,\n\t rc_this_oldid AS rev_id,\n\t rc_last_oldid, rc_id, rc_patrolled,\n\t rc_new AS page_is_new,wl_notificationtimestamp\n\t FROM {$watchlist},{$recentchanges}\n\t WHERE wl_user={$uid}\n\t AND wl_namespace=rc_namespace\n\t AND wl_title=rc_title\n\t AND rc_timestamp > '{$cutoff}'\n\t {$andHideOwn}\n\t {$andHideBots}\n\t {$nameSpaceClause}\n\t ORDER BY rc_timestamp DESC\n\t {$limitWatchlist}"; $res = $dbr->query($sql, $fname); // WERELATE - moved up from below so we can calculate $numRows inside the loop // $numRows = $dbr->numRows( $res ); $numRows = 0; $list = ChangesList::newFromUser($wgUser); $rcs = $list->beginRecentChangesList(); $counter = 1; // WERELATE - added array of seen pagetitles $seen = array(); while ($obj = $dbr->fetchObject($res)) { $nsTitle = "{$obj->page_namespace}:{$obj->page_title}"; if (!$andLatest || !isset($seen[$nsTitle])) { $seen[$nsTitle] = 1; $numRows++; # Make fake RC entry $rc = RecentChange::newFromCurRow($obj, $obj->rc_last_oldid); $rc->counter = $counter++; if ($wgShowUpdatedMarker) { $updated = $obj->wl_notificationtimestamp; } else { // Same visual appearance as MW 1.4 $updated = true; } if ($wgRCShowWatchingUsers && $wgUser->getOption('shownumberswatching')) { $sql3 = "SELECT COUNT(*) AS n FROM {$watchlist} WHERE wl_title='" . wfStrencode($obj->page_title) . "' AND wl_namespace='{$obj->page_namespace}'"; $res3 = $dbr->query($sql3, DB_READ, $fname); $x = $dbr->fetchObject($res3); $rc->numberofWatchingusers = $x->n; } else { $rc->numberofWatchingusers = 0; } $rcs .= $list->recentChangesLine($rc, $updated); } } $rcs .= $list->endRecentChangesList(); $dbr->freeResult($res); /* Start bottom header */ // WERELATE - add link to changed view // $wgOut->addHTML( "<hr />\n<p>" ); $wgOut->addHTML('<hr /><h2>Recently-changed pages</h2><ul><li><a href="' . $specialTitle->getFullUrl('changed=yes') . '">Show all pages changed since last visited</a></li></ul><br/><p>'); if ($days >= 1) { $wgOut->addWikiText(wfMsg('rcnote', $wgLang->formatNum($numRows), $wgLang->formatNum($days), $wgLang->timeAndDate(wfTimestampNow(), true)) . '<br />', false); } elseif ($days > 0) { $wgOut->addWikiText(wfMsg('wlnote', $wgLang->formatNum($numRows), $wgLang->formatNum(round($days * 24))) . '<br />', false); } $wgOut->addHTML("\n" . wlCutoffLinks($days, 'Watchlist', $nondefaults) . "<br />\n"); # Spit out some control panel links $thisTitle = Title::makeTitle(NS_SPECIAL, 'Watchlist'); $skin = $wgUser->getSkin(); $linkElements = array('hideOwn' => 'wlhideshowown', 'hideBots' => 'wlhideshowbots'); # Problems encountered using the fancier method $label = $hideBots ? wfMsgHtml('show') : wfMsgHtml('hide'); $linkBits = wfArrayToCGI(array('hideBots' => 1 - (int) $hideBots), $nondefaults); $link = $skin->makeKnownLinkObj($thisTitle, $label, $linkBits); $links[] = wfMsgHtml('wlhideshowbots', $link); $label = $hideOwn ? wfMsgHtml('show') : wfMsgHtml('hide'); $linkBits = wfArrayToCGI(array('hideOwn' => 1 - (int) $hideOwn), $nondefaults); $link = $skin->makeKnownLinkObj($thisTitle, $label, $linkBits); $links[] = wfMsgHtml('wlhideshowown', $link); $wgOut->addHTML(implode(' | ', $links)); # Form for namespace filtering $thisAction = $thisTitle->escapeLocalUrl(); $nsForm = "<form method=\"post\" action=\"{$thisAction}\">\n"; $nsForm .= "<label for=\"namespace\">" . wfMsgExt('namespace', array('parseinline')) . "</label> "; $nsForm .= HTMLnamespaceselector($nameSpace, '') . "\n"; $nsForm .= $hideOwn ? "<input type=\"hidden\" name=\"hideown\" value=\"1\" />\n" : ""; $nsForm .= $hideBots ? "<input type=\"hidden\" name=\"hidebots\" value=\"1\" />\n" : ""; $nsForm .= "<input type=\"hidden\" name=\"days\" value=\"" . $days . "\" />\n"; $nsForm .= "<input type=\"submit\" name=\"submit\" value=\"" . wfMsgExt('allpagessubmit', array('escape')) . "\" />\n"; $nsForm .= "</form>\n"; $wgOut->addHTML($nsForm); if ($numRows == 0) { $wgOut->addWikitext("<br />" . wfMsg('watchnochange'), false); $wgOut->addHTML("</p>\n"); return; } $wgOut->addHTML("</p>\n"); /* End bottom header */ // WERELATE - change $s to $rcs $wgOut->addHTML($rcs); if ($wgUseWatchlistCache) { $wgMemc->set($memckey, $s, $wgWLCacheTimeout); } }
/** * Returns text for the end of RC * If enhanced RC is in use, returns pretty much all the text * @return string */ public function endRecentChangesList() { return $this->recentChangesBlock() . parent::endRecentChangesList(); }
/** * Execute * @param $par Parameter passed to the page */ function execute( $par ) { global $wgRCShowWatchingUsers, $wgEnotifWatchlist, $wgShowUpdatedMarker; $user = $this->getUser(); $output = $this->getOutput(); # Anons don't get a watchlist if ( $user->isAnon() ) { $output->setPageTitle( $this->msg( 'watchnologin' ) ); $output->setRobotPolicy( 'noindex,nofollow' ); $llink = Linker::linkKnown( SpecialPage::getTitleFor( 'Userlogin' ), $this->msg( 'loginreqlink' )->escaped(), array(), array( 'returnto' => $this->getTitle()->getPrefixedText() ) ); $output->addHTML( $this->msg( 'watchlistanontext' )->rawParams( $llink )->parse() ); return; } // Check permissions $this->checkPermissions(); // Add feed links $wlToken = $user->getTokenFromOption( 'watchlisttoken' ); if ( $wlToken ) { $this->addFeedLinks( array( 'action' => 'feedwatchlist', 'allrev' => 'allrev', 'wlowner' => $user->getName(), 'wltoken' => $wlToken ) ); } $this->setHeaders(); $this->outputHeader(); $output->addSubtitle( $this->msg( 'watchlistfor2', $user->getName() )->rawParams( SpecialEditWatchlist::buildTools( null ) ) ); $request = $this->getRequest(); $mode = SpecialEditWatchlist::getMode( $request, $par ); if ( $mode !== false ) { # TODO: localise? switch ( $mode ) { case SpecialEditWatchlist::EDIT_CLEAR: $mode = 'clear'; break; case SpecialEditWatchlist::EDIT_RAW: $mode = 'raw'; break; default: $mode = null; } $title = SpecialPage::getTitleFor( 'EditWatchlist', $mode ); $output->redirect( $title->getLocalURL() ); return; } $dbr = wfGetDB( DB_SLAVE, 'watchlist' ); $nitems = $this->countItems( $dbr ); if ( $nitems == 0 ) { $output->addWikiMsg( 'nowatchlist' ); return; } // @todo use FormOptions! $defaults = array( /* float */ 'days' => floatval( $user->getOption( 'watchlistdays' ) ), /* bool */ 'hideMinor' => (int)$user->getBoolOption( 'watchlisthideminor' ), /* bool */ 'hideBots' => (int)$user->getBoolOption( 'watchlisthidebots' ), /* bool */ 'hideAnons' => (int)$user->getBoolOption( 'watchlisthideanons' ), /* bool */ 'hideLiu' => (int)$user->getBoolOption( 'watchlisthideliu' ), /* bool */ 'hidePatrolled' => (int)$user->getBoolOption( 'watchlisthidepatrolled' ), /* bool */ 'hideOwn' => (int)$user->getBoolOption( 'watchlisthideown' ), /* bool */ 'extended' => (int)$user->getBoolOption( 'extendwatchlist' ), /* ? */ 'namespace' => '', //means all /* ? */ 'invert' => false, /* bool */ 'associated' => false, ); $this->customFilters = array(); wfRunHooks( 'SpecialWatchlistFilters', array( $this, &$this->customFilters ) ); foreach ( $this->customFilters as $key => $params ) { $defaults[$key] = $params['default']; } # Extract variables from the request, falling back to user preferences or # other default values if these don't exist $values = array(); $values['days'] = floatval( $request->getVal( 'days', $defaults['days'] ) ); $values['hideMinor'] = (int)$request->getBool( 'hideMinor', $defaults['hideMinor'] ); $values['hideBots'] = (int)$request->getBool( 'hideBots', $defaults['hideBots'] ); $values['hideAnons'] = (int)$request->getBool( 'hideAnons', $defaults['hideAnons'] ); $values['hideLiu'] = (int)$request->getBool( 'hideLiu', $defaults['hideLiu'] ); $values['hideOwn'] = (int)$request->getBool( 'hideOwn', $defaults['hideOwn'] ); $values['hidePatrolled'] = (int)$request->getBool( 'hidePatrolled', $defaults['hidePatrolled'] ); $values['extended'] = (int)$request->getBool( 'extended', $defaults['extended'] ); foreach ( $this->customFilters as $key => $params ) { $values[$key] = (int)$request->getBool( $key, $defaults[$key] ); } # Get namespace value, if supplied, and prepare a WHERE fragment $nameSpace = $request->getIntOrNull( 'namespace' ); $invert = $request->getBool( 'invert' ); $associated = $request->getBool( 'associated' ); if ( !is_null( $nameSpace ) ) { $eq_op = $invert ? '!=' : '='; $bool_op = $invert ? 'AND' : 'OR'; $nameSpace = intval( $nameSpace ); // paranioa if ( !$associated ) { $nameSpaceClause = "rc_namespace $eq_op $nameSpace"; } else { $associatedNS = MWNamespace::getAssociated( $nameSpace ); $nameSpaceClause = "rc_namespace $eq_op $nameSpace " . $bool_op . " rc_namespace $eq_op $associatedNS"; } } else { $nameSpace = ''; $nameSpaceClause = ''; } $values['namespace'] = $nameSpace; $values['invert'] = $invert; $values['associated'] = $associated; // Dump everything here $nondefaults = array(); foreach ( $defaults as $name => $defValue ) { wfAppendToArrayIfNotDefault( $name, $values[$name], $defaults, $nondefaults ); } if ( ( $wgEnotifWatchlist || $wgShowUpdatedMarker ) && $request->getVal( 'reset' ) && $request->wasPosted() ) { $user->clearAllNotifications(); $output->redirect( $this->getTitle()->getFullURL( $nondefaults ) ); return; } # Possible where conditions $conds = array(); if ( $values['days'] > 0 ) { $conds[] = 'rc_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( time() - intval( $values['days'] * 86400 ) ) ); } # Toggles if ( $values['hideOwn'] ) { $conds[] = 'rc_user != ' . $user->getId(); } if ( $values['hideBots'] ) { $conds[] = 'rc_bot = 0'; } if ( $values['hideMinor'] ) { $conds[] = 'rc_minor = 0'; } if ( $values['hideLiu'] ) { $conds[] = 'rc_user = 0'; } if ( $values['hideAnons'] ) { $conds[] = 'rc_user != 0'; } if ( $user->useRCPatrol() && $values['hidePatrolled'] ) { $conds[] = 'rc_patrolled != 1'; } if ( $nameSpaceClause ) { $conds[] = $nameSpaceClause; } # Toggle watchlist content (all recent edits or just the latest) if ( $values['extended'] ) { $limitWatchlist = $user->getIntOption( 'wllimit' ); $usePage = false; } else { # Top log Ids for a page are not stored $nonRevisionTypes = array( RC_LOG ); wfRunHooks( 'SpecialWatchlistGetNonRevisionTypes', array( &$nonRevisionTypes ) ); if ( $nonRevisionTypes ) { if ( count( $nonRevisionTypes ) === 1 ) { // if only one use an equality instead of IN condition $nonRevisionTypes = reset( $nonRevisionTypes ); } $conds[] = $dbr->makeList( array( 'rc_this_oldid=page_latest', 'rc_type' => $nonRevisionTypes, ), LIST_OR ); } $limitWatchlist = 0; $usePage = true; } # Show a message about slave lag, if applicable $lag = wfGetLB()->safeGetLag( $dbr ); if ( $lag > 0 ) { $output->showLagWarning( $lag ); } # Create output $form = ''; # Show watchlist header $form .= "<p>"; $form .= $this->msg( 'watchlist-details' )->numParams( $nitems )->parse() . "\n"; if ( $wgEnotifWatchlist && $user->getOption( 'enotifwatchlistpages' ) ) { $form .= $this->msg( 'wlheader-enotif' )->parse() . "\n"; } if ( $wgShowUpdatedMarker ) { $form .= $this->msg( 'wlheader-showupdated' )->parse() . "\n"; } $form .= "</p>"; if ( $wgShowUpdatedMarker ) { $form .= Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL(), 'id' => 'mw-watchlist-resetbutton' ) ) . "\n" . Xml::submitButton( $this->msg( 'enotif_reset' )->text(), array( 'name' => 'dummy' ) ) . "\n" . Html::hidden( 'reset', 'all' ) . "\n"; foreach ( $nondefaults as $key => $value ) { $form .= Html::hidden( $key, $value ) . "\n"; } $form .= Xml::closeElement( 'form' ) . "\n"; } $form .= Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL(), 'id' => 'mw-watchlist-form' ) ); $form .= Xml::fieldset( $this->msg( 'watchlist-options' )->text(), false, array( 'id' => 'mw-watchlist-options' ) ); $tables = array( 'recentchanges', 'watchlist' ); $fields = RecentChange::selectFields(); $join_conds = array( 'watchlist' => array( 'INNER JOIN', array( 'wl_user' => $user->getId(), 'wl_namespace=rc_namespace', 'wl_title=rc_title' ), ), ); $options = array( 'ORDER BY' => 'rc_timestamp DESC' ); if ( $wgShowUpdatedMarker ) { $fields[] = 'wl_notificationtimestamp'; } if ( $limitWatchlist ) { $options['LIMIT'] = $limitWatchlist; } $rollbacker = $user->isAllowed( 'rollback' ); if ( $usePage || $rollbacker ) { $tables[] = 'page'; $join_conds['page'] = array( 'LEFT JOIN', 'rc_cur_id=page_id' ); if ( $rollbacker ) { $fields[] = 'page_latest'; } } // Log entries with DELETED_ACTION must not show up unless the user has // the necessary rights. if ( !$user->isAllowed( 'deletedhistory' ) ) { $bitmask = LogPage::DELETED_ACTION; } elseif ( !$user->isAllowed( 'suppressrevision' ) ) { $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED; } else { $bitmask = 0; } if ( $bitmask ) { $conds[] = $dbr->makeList( array( 'rc_type != ' . RC_LOG, $dbr->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask", ), LIST_OR ); } ChangeTags::modifyDisplayQuery( $tables, $fields, $conds, $join_conds, $options, '' ); wfRunHooks( 'SpecialWatchlistQuery', array( &$conds, &$tables, &$join_conds, &$fields, $values ) ); $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $join_conds ); $numRows = $res->numRows(); /* Start bottom header */ $lang = $this->getLanguage(); $wlInfo = ''; if ( $values['days'] > 0 ) { $timestamp = wfTimestampNow(); $wlInfo = $this->msg( 'wlnote' )->numParams( $numRows, round( $values['days'] * 24 ) )->params( $lang->userDate( $timestamp, $user ), $lang->userTime( $timestamp, $user ) )->parse() . "<br />\n"; } $cutofflinks = $this->cutoffLinks( $values['days'], $nondefaults ) . "<br />\n"; # Spit out some control panel links $filters = array( 'hideMinor' => 'rcshowhideminor', 'hideBots' => 'rcshowhidebots', 'hideAnons' => 'rcshowhideanons', 'hideLiu' => 'rcshowhideliu', 'hideOwn' => 'rcshowhidemine', 'hidePatrolled' => 'rcshowhidepatr' ); foreach ( $this->customFilters as $key => $params ) { $filters[$key] = $params['msg']; } // Disable some if needed if ( !$user->useNPPatrol() ) { unset( $filters['hidePatrolled'] ); } $links = array(); foreach ( $filters as $name => $msg ) { $links[] = $this->showHideLink( $nondefaults, $msg, $name, $values[$name] ); } $hiddenFields = $nondefaults; unset( $hiddenFields['namespace'] ); unset( $hiddenFields['invert'] ); unset( $hiddenFields['associated'] ); # Namespace filter and put the whole form together. $form .= $wlInfo; $form .= $cutofflinks; $form .= $lang->pipeList( $links ) . "\n"; $form .= "<hr />\n<p>"; $form .= Html::namespaceSelector( array( 'selected' => $nameSpace, 'all' => '', 'label' => $this->msg( 'namespace' )->text() ), array( 'name' => 'namespace', 'id' => 'namespace', 'class' => 'namespaceselector', ) ) . ' '; $form .= Xml::checkLabel( $this->msg( 'invert' )->text(), 'invert', 'nsinvert', $invert, array( 'title' => $this->msg( 'tooltip-invert' )->text() ) ) . ' '; $form .= Xml::checkLabel( $this->msg( 'namespace_association' )->text(), 'associated', 'associated', $associated, array( 'title' => $this->msg( 'tooltip-namespace_association' )->text() ) ) . ' '; $form .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "</p>\n"; foreach ( $hiddenFields as $key => $value ) { $form .= Html::hidden( $key, $value ) . "\n"; } $form .= Xml::closeElement( 'fieldset' ) . "\n"; $form .= Xml::closeElement( 'form' ) . "\n"; $output->addHTML( $form ); # If there's nothing to show, stop here if ( $numRows == 0 ) { $output->wrapWikiMsg( "<div class='mw-changeslist-empty'>\n$1\n</div>", 'recentchanges-noresult' ); return; } /* End bottom header */ /* Do link batch query */ $linkBatch = new LinkBatch; foreach ( $res as $row ) { $userNameUnderscored = str_replace( ' ', '_', $row->rc_user_text ); if ( $row->rc_user != 0 ) { $linkBatch->add( NS_USER, $userNameUnderscored ); } $linkBatch->add( NS_USER_TALK, $userNameUnderscored ); $linkBatch->add( $row->rc_namespace, $row->rc_title ); } $linkBatch->execute(); $dbr->dataSeek( $res, 0 ); $list = ChangesList::newFromContext( $this->getContext() ); $list->setWatchlistDivs(); $s = $list->beginRecentChangesList(); $counter = 1; foreach ( $res as $obj ) { # Make RC entry $rc = RecentChange::newFromRow( $obj ); $rc->counter = $counter++; if ( $wgShowUpdatedMarker ) { $updated = $obj->wl_notificationtimestamp; } else { $updated = false; } if ( $wgRCShowWatchingUsers && $user->getOption( 'shownumberswatching' ) ) { $rc->numberofWatchingusers = $dbr->selectField( 'watchlist', 'COUNT(*)', array( 'wl_namespace' => $obj->rc_namespace, 'wl_title' => $obj->rc_title, ), __METHOD__ ); } else { $rc->numberofWatchingusers = 0; } $changeLine = $list->recentChangesLine( $rc, $updated, $counter ); if ( $changeLine !== false ) { $s .= $changeLine; } } $s .= $list->endRecentChangesList(); $output->addHTML( $s ); }
/** * Generates amount of changes (linking to diff ) & link to history. * * @param array $block * @param array $queryParams * @param bool $allLogs * @param bool $isnew * @param bool $namehidden * @return string */ protected function getLogText($block, $queryParams, $allLogs, $isnew, $namehidden) { # Changes message static $nchanges = array(); static $sinceLastVisitMsg = array(); $n = count($block); if (!isset($nchanges[$n])) { $nchanges[$n] = $this->msg('nchanges')->numParams($n)->escaped(); } $sinceLast = 0; $unvisitedOldid = null; /** @var $rcObj RCCacheEntry */ foreach ($block as $rcObj) { // Same logic as below inside main foreach if ($rcObj->watched && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched) { $sinceLast++; $unvisitedOldid = $rcObj->mAttribs['rc_last_oldid']; } } if (!isset($sinceLastVisitMsg[$sinceLast])) { $sinceLastVisitMsg[$sinceLast] = $this->msg('enhancedrc-since-last-visit')->numParams($sinceLast)->escaped(); } $currentRevision = 0; foreach ($block as $rcObj) { if (!$currentRevision) { $currentRevision = $rcObj->mAttribs['rc_this_oldid']; } } # Total change link $links = array(); /** @var $block0 RecentChange */ $block0 = $block[0]; $last = $block[count($block) - 1]; if (!$allLogs) { if (!ChangesList::userCan($rcObj, Revision::DELETED_TEXT, $this->getUser())) { $links['total-changes'] = $nchanges[$n]; } elseif ($isnew) { $links['total-changes'] = $nchanges[$n]; } else { $links['total-changes'] = Linker::link($block0->getTitle(), $nchanges[$n], array(), $queryParams + array('diff' => $currentRevision, 'oldid' => $last->mAttribs['rc_last_oldid']), array('known', 'noclasses')); if ($sinceLast > 0 && $sinceLast < $n) { $links['total-changes-since-last'] = Linker::link($block0->getTitle(), $sinceLastVisitMsg[$sinceLast], array(), $queryParams + array('diff' => $currentRevision, 'oldid' => $unvisitedOldid), array('known', 'noclasses')); } } } # History if ($allLogs) { // don't show history link for logs } elseif ($namehidden || !$block0->getTitle()->exists()) { $links['history'] = $this->message['enhancedrc-history']; } else { $params = $queryParams; $params['action'] = 'history'; $links['history'] = Linker::linkKnown($block0->getTitle(), $this->message['enhancedrc-history'], array(), $params); } # Allow others to alter, remove or add to these links Hooks::run('EnhancedChangesList::getLogText', array($this, &$links, $block)); if (!$links) { return ''; } $logtext = implode($this->message['pipe-separator'], $links); $logtext = $this->msg('parentheses')->rawParams($logtext)->escaped(); return ' ' . $logtext; }
/** * 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; }
/** * Build and output the actual changes list. * * @param ResultWrapper $rows Database rows * @param FormOptions $opts */ public function outputChangesList($rows, $opts) { $dbr = $this->getDB(); $user = $this->getUser(); $output = $this->getOutput(); # Show a message about slave lag, if applicable $lag = wfGetLB()->safeGetLag($dbr); if ($lag > 0) { $output->showLagWarning($lag); } # If no rows to display, show message before try to render the list if ($rows->numRows() == 0) { $output->wrapWikiMsg("<div class='mw-changeslist-empty'>\n\$1\n</div>", 'recentchanges-noresult'); return; } $dbr->dataSeek($rows, 0); $list = ChangesList::newFromContext($this->getContext()); $list->setWatchlistDivs(); $list->initChangesListRows($rows); $dbr->dataSeek($rows, 0); $s = $list->beginRecentChangesList(); $counter = 1; foreach ($rows as $obj) { # Make RC entry $rc = RecentChange::newFromRow($obj); $rc->counter = $counter++; if ($this->getConfig()->get('ShowUpdatedMarker')) { $updated = $obj->wl_notificationtimestamp; } else { $updated = false; } if ($this->getConfig()->get('RCShowWatchingUsers') && $user->getOption('shownumberswatching')) { $rc->numberofWatchingusers = $dbr->selectField('watchlist', 'COUNT(*)', array('wl_namespace' => $obj->rc_namespace, 'wl_title' => $obj->rc_title), __METHOD__); } else { $rc->numberofWatchingusers = 0; } $changeLine = $list->recentChangesLine($rc, $updated, $counter); if ($changeLine !== false) { $s .= $changeLine; } } $s .= $list->endRecentChangesList(); $output->addHTML($s); }
/** * Build and output the actual changes list. * * @param ResultWrapper $rows Database rows * @param FormOptions $opts */ public function outputChangesList($rows, $opts) { $limit = $opts['limit']; $showWatcherCount = $this->getConfig()->get('RCShowWatchingUsers') && $this->getUser()->getOption('shownumberswatching'); $watcherCache = []; $dbr = $this->getDB(); $counter = 1; $list = ChangesList::newFromContext($this->getContext()); $list->initChangesListRows($rows); $userShowHiddenCats = $this->getUser()->getBoolOption('showhiddencats'); $rclistOutput = $list->beginRecentChangesList(); foreach ($rows as $obj) { if ($limit == 0) { break; } $rc = RecentChange::newFromRow($obj); # Skip CatWatch entries for hidden cats based on user preference if ($rc->getAttribute('rc_type') == RC_CATEGORIZE && !$userShowHiddenCats && $rc->getParam('hidden-cat')) { continue; } $rc->counter = $counter++; # Check if the page has been updated since the last visit if ($this->getConfig()->get('ShowUpdatedMarker') && !empty($obj->wl_notificationtimestamp)) { $rc->notificationtimestamp = $obj->rc_timestamp >= $obj->wl_notificationtimestamp; } else { $rc->notificationtimestamp = false; // Default } # Check the number of users watching the page $rc->numberofWatchingusers = 0; // Default if ($showWatcherCount && $obj->rc_namespace >= 0) { if (!isset($watcherCache[$obj->rc_namespace][$obj->rc_title])) { $watcherCache[$obj->rc_namespace][$obj->rc_title] = MediaWikiServices::getInstance()->getWatchedItemStore()->countWatchers(new TitleValue((int) $obj->rc_namespace, $obj->rc_title)); } $rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title]; } $changeLine = $list->recentChangesLine($rc, !empty($obj->wl_user), $counter); if ($changeLine !== false) { $rclistOutput .= $changeLine; --$limit; } } $rclistOutput .= $list->endRecentChangesList(); if ($rows->numRows() === 0) { $this->getOutput()->addHTML('<div class="mw-changeslist-empty">' . $this->msg('recentchanges-noresult')->parse() . '</div>'); if (!$this->including()) { $this->getOutput()->setStatusCode(404); } } else { $this->getOutput()->addHTML($rclistOutput); } }
/** * Extracts from a single sql row the data needed to describe one recent change. * * @param stdClass $row The row from which to extract the data. * @return array An array mapping strings (descriptors) to their respective string values. * @access public */ public function extractRowInfo($row) { /* Determine the title of the page that has been changed. */ $title = Title::makeTitle($row->rc_namespace, $row->rc_title); $user = $this->getUser(); /* Our output data. */ $vals = array(); $type = intval($row->rc_type); $vals['type'] = RecentChange::parseFromRCType($type); $anyHidden = false; /* Create a new entry in the result for the title. */ if ($this->fld_title || $this->fld_ids) { if ($type === RC_LOG && $row->rc_deleted & LogPage::DELETED_ACTION) { $vals['actionhidden'] = true; $anyHidden = true; } if ($type !== RC_LOG || LogEventsList::userCanBitfield($row->rc_deleted, LogPage::DELETED_ACTION, $user)) { if ($this->fld_title) { ApiQueryBase::addTitleInfo($vals, $title); } if ($this->fld_ids) { $vals['pageid'] = intval($row->rc_cur_id); $vals['revid'] = intval($row->rc_this_oldid); $vals['old_revid'] = intval($row->rc_last_oldid); } } } if ($this->fld_ids) { $vals['rcid'] = intval($row->rc_id); } /* Add user data and 'anon' flag, if user is anonymous. */ if ($this->fld_user || $this->fld_userid) { if ($row->rc_deleted & Revision::DELETED_USER) { $vals['userhidden'] = true; $anyHidden = true; } if (Revision::userCanBitfield($row->rc_deleted, Revision::DELETED_USER, $user)) { if ($this->fld_user) { $vals['user'] = $row->rc_user_text; } if ($this->fld_userid) { $vals['userid'] = $row->rc_user; } if (!$row->rc_user) { $vals['anon'] = true; } } } /* Add flags, such as new, minor, bot. */ if ($this->fld_flags) { $vals['bot'] = (bool) $row->rc_bot; $vals['new'] = $row->rc_type == RC_NEW; $vals['minor'] = (bool) $row->rc_minor; } /* Add sizes of each revision. (Only available on 1.10+) */ if ($this->fld_sizes) { $vals['oldlen'] = intval($row->rc_old_len); $vals['newlen'] = intval($row->rc_new_len); } /* Add the timestamp. */ if ($this->fld_timestamp) { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rc_timestamp); } /* Add edit summary / log summary. */ if ($this->fld_comment || $this->fld_parsedcomment) { if ($row->rc_deleted & Revision::DELETED_COMMENT) { $vals['commenthidden'] = true; $anyHidden = true; } if (Revision::userCanBitfield($row->rc_deleted, Revision::DELETED_COMMENT, $user)) { if ($this->fld_comment && isset($row->rc_comment)) { $vals['comment'] = $row->rc_comment; } if ($this->fld_parsedcomment && isset($row->rc_comment)) { $vals['parsedcomment'] = Linker::formatComment($row->rc_comment, $title); } } } if ($this->fld_redirect) { $vals['redirect'] = (bool) $row->page_is_redirect; } /* Add the patrolled flag */ if ($this->fld_patrolled) { $vals['patrolled'] = $row->rc_patrolled == 1; $vals['unpatrolled'] = ChangesList::isUnpatrolled($row, $user); } if ($this->fld_loginfo && $row->rc_type == RC_LOG) { if ($row->rc_deleted & LogPage::DELETED_ACTION) { $vals['actionhidden'] = true; $anyHidden = true; } if (LogEventsList::userCanBitfield($row->rc_deleted, LogPage::DELETED_ACTION, $user)) { $vals['logid'] = intval($row->rc_logid); $vals['logtype'] = $row->rc_log_type; $vals['logaction'] = $row->rc_log_action; $vals['logparams'] = LogFormatter::newFromRow($row)->formatParametersForApi(); } } if ($this->fld_tags) { if ($row->ts_tags) { $tags = explode(',', $row->ts_tags); ApiResult::setIndexedTagName($tags, 'tag'); $vals['tags'] = $tags; } else { $vals['tags'] = array(); } } if ($this->fld_sha1 && $row->rev_sha1 !== null) { if ($row->rev_deleted & Revision::DELETED_TEXT) { $vals['sha1hidden'] = true; $anyHidden = true; } if (Revision::userCanBitfield($row->rev_deleted, Revision::DELETED_TEXT, $user)) { if ($row->rev_sha1 !== '') { $vals['sha1'] = wfBaseConvert($row->rev_sha1, 36, 16, 40); } else { $vals['sha1'] = ''; } } } if (!is_null($this->token)) { $tokenFunctions = $this->getTokenFunctions(); foreach ($this->token as $t) { $val = call_user_func($tokenFunctions[$t], $row->rc_cur_id, $title, RecentChange::newFromRow($row)); if ($val === false) { $this->setWarning("Action '{$t}' is not allowed for the current user"); } else { $vals[$t . 'token'] = $val; } } } if ($anyHidden && $row->rc_deleted & Revision::DELETED_RESTRICTED) { $vals['suppressed'] = true; } return $vals; }
public static function contributionsLineEndingProcess(ContribsPager &$contribsPager, &$ret, $row) { wfProfileIn(__METHOD__); $rev = new Revision($row); $page = $rev->getTitle(); $page->resetArticleId($row->rev_page); $wfMsgOptsBase = self::getMessageOptions(null, $row); $isThread = $wfMsgOptsBase['isThread']; $isNew = $wfMsgOptsBase['isNew']; // Don't show useless link to people who cannot hide revisions $del = Linker::getRevDeleteLink($contribsPager->getUser(), $rev, $page); if ($del !== '') { $del .= ' '; } else { $del = ''; } // VOLDEV-40: remove html messages $ret = $del; $ret .= Linker::linkKnown($page, $contribsPager->getLanguage()->userTimeAndDate($row->rev_timestamp, $contribsPager->getUser()), [], ['oldid' => $row->rev_id]) . ' ('; if ($isNew) { $ret .= $contribsPager->msg('diff')->escaped(); } else { $ret .= Linker::linkKnown($page, $contribsPager->msg('diff')->escaped(), [], ['diff' => 'prev', 'oldid' => $row->rev_id]); } $wallMessage = new WallMessage($page); $threadId = $wallMessage->getMessagePageId(); $threadTitle = Title::newFromText($threadId, NS_USER_WALL_MESSAGE); $ret .= ' | ' . Linker::linkKnown($threadTitle, $contribsPager->msg('hist')->escaped(), [], ['action' => 'history']) . ') '; if ($isThread && $isNew) { $ret .= ChangesList::flag('newpage') . ' '; } if (MWNamespace::getSubject($row->page_namespace) === NS_WIKIA_FORUM_BOARD && empty($wfMsgOptsBase['articleTitleVal'])) { $wfMsgOptsBase['articleTitleTxt'] = $contribsPager->msg('forum-recentchanges-deleted-reply-title')->text(); } $prefix = MWNamespace::getSubject($row->page_namespace) === NS_WIKIA_FORUM_BOARD ? 'forum' : 'wall'; $ret .= $contribsPager->msg($prefix . '-contributions-line')->params($wfMsgOptsBase['articleTitle'])->rawParams(htmlspecialchars($wfMsgOptsBase['articleTitleTxt']))->params($wfMsgOptsBase['wallTitleTxt'], $wfMsgOptsBase['wallPageName'])->parse(); if (!$isNew) { $summary = $rev->getComment(); if (empty($summary)) { $msg = Linker::commentBlock($contribsPager->msg(static::getMessagePrefix($row->page_namespace) . '-edit')->inContentLanguage()->text()); } else { $msg = Linker::revComment($rev, false, true); } $ret .= ' ' . $contribsPager->getLanguage()->getDirMark() . $msg; } wfProfileOut(__METHOD__); return true; }