MediaWiki  master
SpecialMergeHistory.php
Go to the documentation of this file.
1 <?php
29 
38  protected $mAction;
39 
41  protected $mTarget;
42 
44  protected $mDest;
45 
47  protected $mTimestamp;
48 
50  protected $mTargetID;
51 
53  protected $mDestID;
54 
56  protected $mComment;
57 
59  protected $mMerge;
60 
62  protected $mSubmitted;
63 
65  protected $mTargetObj;
66 
68  protected $mDestObj;
69 
71  public $prevId;
72 
75 
78 
80  private $loadBalancer;
81 
83  private $revisionStore;
84 
91  public function __construct(
96  ) {
97  parent::__construct( 'MergeHistory', 'mergehistory' );
98  $this->mergeHistoryFactory = $mergeHistoryFactory;
99  $this->linkBatchFactory = $linkBatchFactory;
100  $this->loadBalancer = $loadBalancer;
101  $this->revisionStore = $revisionStore;
102  }
103 
104  public function doesWrites() {
105  return true;
106  }
107 
111  private function loadRequestParams() {
112  $request = $this->getRequest();
113  $this->mAction = $request->getRawVal( 'action' );
114  $this->mTarget = $request->getVal( 'target' );
115  $this->mDest = $request->getVal( 'dest' );
116  $this->mSubmitted = $request->getBool( 'submitted' );
117 
118  $this->mTargetID = intval( $request->getVal( 'targetID' ) );
119  $this->mDestID = intval( $request->getVal( 'destID' ) );
120  $this->mTimestamp = $request->getVal( 'mergepoint' );
121  if ( !preg_match( '/[0-9]{14}/', $this->mTimestamp ) ) {
122  $this->mTimestamp = '';
123  }
124  $this->mComment = $request->getText( 'wpComment' );
125 
126  $this->mMerge = $request->wasPosted()
127  && $this->getUser()->matchEditToken( $request->getVal( 'wpEditToken' ) );
128 
129  // target page
130  if ( $this->mSubmitted ) {
131  $this->mTargetObj = Title::newFromText( $this->mTarget );
132  $this->mDestObj = Title::newFromText( $this->mDest );
133  } else {
134  $this->mTargetObj = null;
135  $this->mDestObj = null;
136  }
137  }
138 
139  public function execute( $par ) {
140  $this->useTransactionalTimeLimit();
141 
142  $this->checkPermissions();
143  $this->checkReadOnly();
144 
145  $this->loadRequestParams();
146 
147  $this->setHeaders();
148  $this->outputHeader();
149 
150  if ( $this->mTargetID && $this->mDestID && $this->mAction == 'submit' && $this->mMerge ) {
151  $this->merge();
152 
153  return;
154  }
155 
156  if ( !$this->mSubmitted ) {
157  $this->showMergeForm();
158 
159  return;
160  }
161 
162  $errors = [];
163  if ( !$this->mTargetObj instanceof Title ) {
164  $errors[] = $this->msg( 'mergehistory-invalid-source' )->parseAsBlock();
165  } elseif ( !$this->mTargetObj->exists() ) {
166  $errors[] = $this->msg( 'mergehistory-no-source',
167  wfEscapeWikiText( $this->mTargetObj->getPrefixedText() )
168  )->parseAsBlock();
169  }
170 
171  if ( !$this->mDestObj instanceof Title ) {
172  $errors[] = $this->msg( 'mergehistory-invalid-destination' )->parseAsBlock();
173  } elseif ( !$this->mDestObj->exists() ) {
174  $errors[] = $this->msg( 'mergehistory-no-destination',
175  wfEscapeWikiText( $this->mDestObj->getPrefixedText() )
176  )->parseAsBlock();
177  }
178 
179  if ( $this->mTargetObj && $this->mDestObj && $this->mTargetObj->equals( $this->mDestObj ) ) {
180  $errors[] = $this->msg( 'mergehistory-same-destination' )->parseAsBlock();
181  }
182 
183  if ( count( $errors ) ) {
184  $this->showMergeForm();
185  $this->getOutput()->addHTML( implode( "\n", $errors ) );
186  } else {
187  $this->showHistory();
188  }
189  }
190 
191  private function showMergeForm() {
192  $out = $this->getOutput();
193  $out->addWikiMsg( 'mergehistory-header' );
194 
195  $out->addHTML(
196  Xml::openElement( 'form', [
197  'method' => 'get',
198  'action' => wfScript() ] ) .
199  '<fieldset>' .
200  Xml::element( 'legend', [],
201  $this->msg( 'mergehistory-box' )->text() ) .
202  Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) .
203  Html::hidden( 'submitted', '1' ) .
204  Html::hidden( 'mergepoint', $this->mTimestamp ) .
205  Xml::openElement( 'table' ) .
206  '<tr>
207  <td>' . Xml::label( $this->msg( 'mergehistory-from' )->text(), 'target' ) . '</td>
208  <td>' . Xml::input( 'target', 30, $this->mTarget, [ 'id' => 'target' ] ) . '</td>
209  </tr><tr>
210  <td>' . Xml::label( $this->msg( 'mergehistory-into' )->text(), 'dest' ) . '</td>
211  <td>' . Xml::input( 'dest', 30, $this->mDest, [ 'id' => 'dest' ] ) . '</td>
212  </tr><tr><td>' .
213  Xml::submitButton( $this->msg( 'mergehistory-go' )->text() ) .
214  '</td></tr>' .
215  Xml::closeElement( 'table' ) .
216  '</fieldset>' .
217  '</form>'
218  );
219 
220  $this->addHelpLink( 'Help:Merge history' );
221  }
222 
223  private function showHistory() {
224  $this->showMergeForm();
225 
226  # List all stored revisions
227  $revisions = new MergeHistoryPager(
228  $this,
229  [],
230  $this->mTargetObj,
231  $this->mDestObj,
232  $this->linkBatchFactory,
233  $this->loadBalancer,
234  $this->revisionStore
235  );
236  $haveRevisions = $revisions->getNumRows() > 0;
237 
238  $out = $this->getOutput();
239  $titleObj = $this->getPageTitle();
240  $action = $titleObj->getLocalURL( [ 'action' => 'submit' ] );
241  # Start the form here
242  $top = Xml::openElement(
243  'form',
244  [
245  'method' => 'post',
246  'action' => $action,
247  'id' => 'merge'
248  ]
249  );
250  $out->addHTML( $top );
251 
252  if ( $haveRevisions ) {
253  # Format the user-visible controls (comment field, submission button)
254  # in a nice little table
255  $table =
256  Xml::openElement( 'fieldset' ) .
257  $this->msg( 'mergehistory-merge', $this->mTargetObj->getPrefixedText(),
258  $this->mDestObj->getPrefixedText() )->parse() .
259  Xml::openElement( 'table', [ 'id' => 'mw-mergehistory-table' ] ) .
260  '<tr>
261  <td class="mw-label">' .
262  Xml::label( $this->msg( 'mergehistory-reason' )->text(), 'wpComment' ) .
263  '</td>
264  <td class="mw-input">' .
265  Xml::input( 'wpComment', 50, $this->mComment, [ 'id' => 'wpComment' ] ) .
266  "</td>
267  </tr>
268  <tr>
269  <td>\u{00A0}</td>
270  <td class=\"mw-submit\">" .
272  $this->msg( 'mergehistory-submit' )->text(),
273  [ 'name' => 'merge', 'id' => 'mw-merge-submit' ]
274  ) .
275  '</td>
276  </tr>' .
277  Xml::closeElement( 'table' ) .
278  Xml::closeElement( 'fieldset' );
279 
280  $out->addHTML( $table );
281  }
282 
283  $out->addHTML(
284  '<h2 id="mw-mergehistory">' .
285  $this->msg( 'mergehistory-list' )->escaped() . "</h2>\n"
286  );
287 
288  if ( $haveRevisions ) {
289  $out->addHTML( $revisions->getNavigationBar() );
290  $out->addHTML( '<ul>' );
291  $out->addHTML( $revisions->getBody() );
292  $out->addHTML( '</ul>' );
293  $out->addHTML( $revisions->getNavigationBar() );
294  } else {
295  $out->addWikiMsg( 'mergehistory-empty' );
296  }
297 
298  # Show relevant lines from the merge log:
299  $mergeLogPage = new LogPage( 'merge' );
300  $out->addHTML( '<h2>' . $mergeLogPage->getName()->escaped() . "</h2>\n" );
301  LogEventsList::showLogExtract( $out, 'merge', $this->mTargetObj );
302 
303  # When we submit, go by page ID to avoid some nasty but unlikely collisions.
304  # Such would happen if a page was renamed after the form loaded, but before submit
305  $misc = Html::hidden( 'targetID', $this->mTargetObj->getArticleID() );
306  $misc .= Html::hidden( 'destID', $this->mDestObj->getArticleID() );
307  $misc .= Html::hidden( 'target', $this->mTarget );
308  $misc .= Html::hidden( 'dest', $this->mDest );
309  $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() );
310  $misc .= Xml::closeElement( 'form' );
311  $out->addHTML( $misc );
312 
313  return true;
314  }
315 
316  public function formatRevisionRow( $row ) {
317  $revRecord = $this->revisionStore->newRevisionFromRow( $row );
318 
319  $linkRenderer = $this->getLinkRenderer();
320 
321  $stxt = '';
322  $last = $this->msg( 'last' )->escaped();
323 
324  $ts = wfTimestamp( TS_MW, $row->rev_timestamp );
325  $checkBox = Xml::radio( 'mergepoint', $ts, ( $this->mTimestamp === $ts ) );
326 
327  $user = $this->getUser();
328 
329  $pageLink = $linkRenderer->makeKnownLink(
330  $revRecord->getPageAsLinkTarget(),
331  $this->getLanguage()->userTimeAndDate( $ts, $user ),
332  [],
333  [ 'oldid' => $revRecord->getId() ]
334  );
335  if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
336  $class = Linker::getRevisionDeletedClass( $revRecord );
337  $pageLink = '<span class=" ' . $class . '">' . $pageLink . '</span>';
338  }
339 
340  # Last link
341  if ( !$revRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
342  $last = $this->msg( 'last' )->escaped();
343  } elseif ( isset( $this->prevId[$row->rev_id] ) ) {
344  $last = $linkRenderer->makeKnownLink(
345  $revRecord->getPageAsLinkTarget(),
346  $this->msg( 'last' )->text(),
347  [],
348  [
349  'diff' => $row->rev_id,
350  'oldid' => $this->prevId[$row->rev_id]
351  ]
352  );
353  }
354 
355  $userLink = Linker::revUserTools( $revRecord );
356 
357  $size = $row->rev_len;
358  if ( $size !== null ) {
359  $stxt = Linker::formatRevisionSize( $size );
360  }
361  $comment = Linker::revComment( $revRecord );
362 
363  return Html::rawElement( 'li', [],
364  $this->msg( 'mergehistory-revisionrow' )
365  ->rawParams( $checkBox, $last, $pageLink, $userLink, $stxt, $comment )->escaped() );
366  }
367 
380  private function merge() {
381  # Get the titles directly from the IDs, in case the target page params
382  # were spoofed. The queries are done based on the IDs, so it's best to
383  # keep it consistent...
384  $targetTitle = Title::newFromID( $this->mTargetID );
385  $destTitle = Title::newFromID( $this->mDestID );
386  if ( $targetTitle === null || $destTitle === null ) {
387  return false; // validate these
388  }
389  if ( $targetTitle->getArticleID() == $destTitle->getArticleID() ) {
390  return false;
391  }
392 
393  // MergeHistory object
394  $mh = $this->mergeHistoryFactory->newMergeHistory( $targetTitle, $destTitle, $this->mTimestamp );
395 
396  // Merge!
397  $mergeStatus = $mh->merge( $this->getUser(), $this->mComment );
398  if ( !$mergeStatus->isOK() ) {
399  // Failed merge
400  $this->getOutput()->addWikiMsg( $mergeStatus->getMessage() );
401  return false;
402  }
403 
404  $linkRenderer = $this->getLinkRenderer();
405 
406  $targetLink = $linkRenderer->makeLink(
407  $targetTitle,
408  null,
409  [],
410  [ 'redirect' => 'no' ]
411  );
412 
413  // In some cases the target page will be deleted
414  $append = ( $mergeStatus->getValue() === 'source-deleted' )
415  ? $this->msg( 'mergehistory-source-deleted', $targetTitle->getPrefixedText() ) : '';
416 
417  $this->getOutput()->addWikiMsg( $this->msg( 'mergehistory-done' )
418  ->rawParams( $targetLink )
419  ->params( $destTitle->getPrefixedText(), $append )
420  ->numParams( $mh->getMergedRevisionCount() )
421  );
422 
423  return true;
424  }
425 
426  protected function getGroupName() {
427  return 'pagetools';
428  }
429 }
SpecialPage\$linkRenderer
LinkRenderer null $linkRenderer
Definition: SpecialPage.php:80
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:744
SpecialPage\msg
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:912
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:383
MediaWiki\Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:47
SpecialMergeHistory\$mTarget
string $mTarget
Definition: SpecialMergeHistory.php:41
Linker\revUserTools
static revUserTools(RevisionRecord $revRecord, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
Definition: Linker.php:1319
SpecialMergeHistory\merge
merge()
Actually attempt the history move.
Definition: SpecialMergeHistory.php:380
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:790
Xml\label
static label( $label, $id, $attribs=[])
Convenience function to build an HTML form label.
Definition: Xml.php:364
MediaWiki\Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:88
SpecialMergeHistory\$mergeHistoryFactory
MergeHistoryFactory $mergeHistoryFactory
Definition: SpecialMergeHistory.php:74
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1668
SpecialMergeHistory\showMergeForm
showMergeForm()
Definition: SpecialMergeHistory.php:191
Xml\radio
static radio( $name, $value, $checked=false, $attribs=[])
Convenience function to build an HTML radio button.
Definition: Xml.php:347
SpecialPage\checkPermissions
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.
Definition: SpecialPage.php:358
SpecialMergeHistory\showHistory
showHistory()
Definition: SpecialMergeHistory.php:223
SpecialPage\useTransactionalTimeLimit
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition: SpecialPage.php:1018
Xml\openElement
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:110
SpecialMergeHistory\$mDestID
int $mDestID
Definition: SpecialMergeHistory.php:53
SpecialMergeHistory\$mTargetID
int $mTargetID
Definition: SpecialMergeHistory.php:50
SpecialPage\addHelpLink
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition: SpecialPage.php:948
Page\MergeHistoryFactory
Definition: MergeHistoryFactory.php:30
wfScript
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
Definition: GlobalFunctions.php:2284
Linker\getRevisionDeletedClass
static getRevisionDeletedClass(RevisionRecord $revisionRecord)
Returns css class of a deleted revision.
Definition: Linker.php:1299
MediaWiki\Cache\LinkBatchFactory
Definition: LinkBatchFactory.php:39
SpecialMergeHistory\$mDestObj
Title $mDestObj
Definition: SpecialMergeHistory.php:68
LogPage
Class to simplify the use of log pages.
Definition: LogPage.php:38
SpecialMergeHistory\formatRevisionRow
formatRevisionRow( $row)
Definition: SpecialMergeHistory.php:316
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:41
SpecialMergeHistory\$mComment
string $mComment
Definition: SpecialMergeHistory.php:56
SpecialPage\setHeaders
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
Definition: SpecialPage.php:618
SpecialPage\getUser
getUser()
Shortcut to get the User executing this instance.
Definition: SpecialPage.php:800
SpecialMergeHistory\__construct
__construct(MergeHistoryFactory $mergeHistoryFactory, LinkBatchFactory $linkBatchFactory, ILoadBalancer $loadBalancer, RevisionStore $revisionStore)
Definition: SpecialMergeHistory.php:91
LogEventsList\showLogExtract
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Definition: LogEventsList.php:597
SpecialMergeHistory\$mTimestamp
string $mTimestamp
Definition: SpecialMergeHistory.php:47
Html\hidden
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:831
Linker\revComment
static revComment(RevisionRecord $revRecord, $local=false, $isPublic=false, $useParentheses=true)
Wrap and format the given revision's comment block, if the current user is allowed to view it.
Definition: Linker.php:1782
SpecialPage
Parent class for all special pages.
Definition: SpecialPage.php:43
SpecialMergeHistory\loadRequestParams
loadRequestParams()
Definition: SpecialMergeHistory.php:111
SpecialMergeHistory\$linkBatchFactory
LinkBatchFactory $linkBatchFactory
Definition: SpecialMergeHistory.php:77
SpecialMergeHistory\$mSubmitted
bool $mSubmitted
Was submitted?
Definition: SpecialMergeHistory.php:62
SpecialPage\getRequest
getRequest()
Get the WebRequest being used for this instance.
Definition: SpecialPage.php:780
Linker\formatRevisionSize
static formatRevisionSize( $size)
Definition: Linker.php:1820
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1459
SpecialMergeHistory\getGroupName
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
Definition: SpecialMergeHistory.php:426
SpecialPage\getLinkRenderer
getLinkRenderer()
Definition: SpecialPage.php:1028
Title
Represents a title within MediaWiki.
Definition: Title.php:48
Xml\closeElement
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:119
SpecialMergeHistory\$mAction
string $mAction
Definition: SpecialMergeHistory.php:38
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:210
SpecialMergeHistory\$revisionStore
RevisionStore $revisionStore
Definition: SpecialMergeHistory.php:83
SpecialMergeHistory\$mMerge
bool $mMerge
Was posted?
Definition: SpecialMergeHistory.php:59
Xml\input
static input( $name, $size=false, $value=false, $attribs=[])
Convenience function to build an HTML text input field.
Definition: Xml.php:280
SpecialPage\checkReadOnly
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
Definition: SpecialPage.php:371
SpecialMergeHistory\$loadBalancer
ILoadBalancer $loadBalancer
Definition: SpecialMergeHistory.php:80
MergeHistoryPager
Definition: MergeHistoryPager.php:30
SpecialMergeHistory\$prevId
int[] $prevId
Definition: SpecialMergeHistory.php:71
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:529
SpecialMergeHistory
Special page allowing users with the appropriate permissions to merge article histories,...
Definition: SpecialMergeHistory.php:36
SpecialPage\outputHeader
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
Definition: SpecialPage.php:709
SpecialMergeHistory\$mTargetObj
Title $mTargetObj
Definition: SpecialMergeHistory.php:65
SpecialMergeHistory\doesWrites
doesWrites()
Indicates whether this special page may perform database writes.
Definition: SpecialMergeHistory.php:104
Wikimedia\Rdbms\ILoadBalancer
Database cluster connection, tracking, load balancing, and transaction manager interface.
Definition: ILoadBalancer.php:81
SpecialMergeHistory\$mDest
string $mDest
Definition: SpecialMergeHistory.php:44
Xml\submitButton
static submitButton( $value, $attribs=[])
Convenience function to build an HTML submit button When $wgUseMediaWikiUIEverywhere is true it will ...
Definition: Xml.php:465
SpecialMergeHistory\execute
execute( $par)
Default execute method Checks user permissions.
Definition: SpecialMergeHistory.php:139