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