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, [], $this->mTargetObj, $this->mDestObj
199  );
200  $haveRevisions = $revisions->getNumRows() > 0;
201 
202  $out = $this->getOutput();
203  $titleObj = $this->getPageTitle();
204  $action = $titleObj->getLocalURL( [ 'action' => 'submit' ] );
205  # Start the form here
206  $top = Xml::openElement(
207  'form',
208  [
209  'method' => 'post',
210  'action' => $action,
211  'id' => 'merge'
212  ]
213  );
214  $out->addHTML( $top );
215 
216  if ( $haveRevisions ) {
217  # Format the user-visible controls (comment field, submission button)
218  # in a nice little table
219  $table =
220  Xml::openElement( 'fieldset' ) .
221  $this->msg( 'mergehistory-merge', $this->mTargetObj->getPrefixedText(),
222  $this->mDestObj->getPrefixedText() )->parse() .
223  Xml::openElement( 'table', [ 'id' => 'mw-mergehistory-table' ] ) .
224  '<tr>
225  <td class="mw-label">' .
226  Xml::label( $this->msg( 'mergehistory-reason' )->text(), 'wpComment' ) .
227  '</td>
228  <td class="mw-input">' .
229  Xml::input( 'wpComment', 50, $this->mComment, [ 'id' => 'wpComment' ] ) .
230  "</td>
231  </tr>
232  <tr>
233  <td>\u{00A0}</td>
234  <td class=\"mw-submit\">" .
236  $this->msg( 'mergehistory-submit' )->text(),
237  [ 'name' => 'merge', 'id' => 'mw-merge-submit' ]
238  ) .
239  '</td>
240  </tr>' .
241  Xml::closeElement( 'table' ) .
242  Xml::closeElement( 'fieldset' );
243 
244  $out->addHTML( $table );
245  }
246 
247  $out->addHTML(
248  '<h2 id="mw-mergehistory">' .
249  $this->msg( 'mergehistory-list' )->escaped() . "</h2>\n"
250  );
251 
252  if ( $haveRevisions ) {
253  $out->addHTML( $revisions->getNavigationBar() );
254  $out->addHTML( '<ul>' );
255  $out->addHTML( $revisions->getBody() );
256  $out->addHTML( '</ul>' );
257  $out->addHTML( $revisions->getNavigationBar() );
258  } else {
259  $out->addWikiMsg( 'mergehistory-empty' );
260  }
261 
262  # Show relevant lines from the merge log:
263  $mergeLogPage = new LogPage( 'merge' );
264  $out->addHTML( '<h2>' . $mergeLogPage->getName()->escaped() . "</h2>\n" );
265  LogEventsList::showLogExtract( $out, 'merge', $this->mTargetObj );
266 
267  # When we submit, go by page ID to avoid some nasty but unlikely collisions.
268  # Such would happen if a page was renamed after the form loaded, but before submit
269  $misc = Html::hidden( 'targetID', $this->mTargetObj->getArticleID() );
270  $misc .= Html::hidden( 'destID', $this->mDestObj->getArticleID() );
271  $misc .= Html::hidden( 'target', $this->mTarget );
272  $misc .= Html::hidden( 'dest', $this->mDest );
273  $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() );
274  $misc .= Xml::closeElement( 'form' );
275  $out->addHTML( $misc );
276 
277  return true;
278  }
279 
280  public function formatRevisionRow( $row ) {
281  $revRecord = MediaWikiServices::getInstance()
282  ->getRevisionFactory()
283  ->newRevisionFromRow( $row );
284 
285  $linkRenderer = $this->getLinkRenderer();
286 
287  $stxt = '';
288  $last = $this->msg( 'last' )->escaped();
289 
290  $ts = wfTimestamp( TS_MW, $row->rev_timestamp );
291  $checkBox = Xml::radio( 'mergepoint', $ts, ( $this->mTimestamp === $ts ) );
292 
293  $user = $this->getUser();
294 
295  $pageLink = $linkRenderer->makeKnownLink(
296  $revRecord->getPageAsLinkTarget(),
297  $this->getLanguage()->userTimeAndDate( $ts, $user ),
298  [],
299  [ 'oldid' => $revRecord->getId() ]
300  );
301  if ( $revRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
302  $pageLink = '<span class="history-deleted">' . $pageLink . '</span>';
303  }
304 
305  # Last link
306  if ( !RevisionRecord::userCanBitfield(
307  $revRecord->getVisibility(),
308  RevisionRecord::DELETED_TEXT,
309  $user
310  ) ) {
311  $last = $this->msg( 'last' )->escaped();
312  } elseif ( isset( $this->prevId[$row->rev_id] ) ) {
313  $last = $linkRenderer->makeKnownLink(
314  $revRecord->getPageAsLinkTarget(),
315  $this->msg( 'last' )->text(),
316  [],
317  [
318  'diff' => $row->rev_id,
319  'oldid' => $this->prevId[$row->rev_id]
320  ]
321  );
322  }
323 
324  $userLink = Linker::revUserTools( $revRecord );
325 
326  $size = $row->rev_len;
327  if ( $size !== null ) {
328  $stxt = Linker::formatRevisionSize( $size );
329  }
330  $comment = Linker::revComment( $revRecord );
331 
332  return Html::rawElement( 'li', [],
333  $this->msg( 'mergehistory-revisionrow' )
334  ->rawParams( $checkBox, $last, $pageLink, $userLink, $stxt, $comment )->escaped() );
335  }
336 
349  private function merge() {
350  # Get the titles directly from the IDs, in case the target page params
351  # were spoofed. The queries are done based on the IDs, so it's best to
352  # keep it consistent...
353  $targetTitle = Title::newFromID( $this->mTargetID );
354  $destTitle = Title::newFromID( $this->mDestID );
355  if ( $targetTitle === null || $destTitle === null ) {
356  return false; // validate these
357  }
358  if ( $targetTitle->getArticleID() == $destTitle->getArticleID() ) {
359  return false;
360  }
361 
362  // MergeHistory object
363  $factory = MediaWikiServices::getInstance()->getMergeHistoryFactory();
364  $mh = $factory->newMergeHistory( $targetTitle, $destTitle, $this->mTimestamp );
365 
366  // Merge!
367  $mergeStatus = $mh->merge( $this->getUser(), $this->mComment );
368  if ( !$mergeStatus->isOK() ) {
369  // Failed merge
370  $this->getOutput()->addWikiMsg( $mergeStatus->getMessage() );
371  return false;
372  }
373 
374  $linkRenderer = $this->getLinkRenderer();
375 
376  $targetLink = $linkRenderer->makeLink(
377  $targetTitle,
378  null,
379  [],
380  [ 'redirect' => 'no' ]
381  );
382 
383  $this->getOutput()->addWikiMsg( $this->msg( 'mergehistory-done' )
384  ->rawParams( $targetLink )
385  ->params( $destTitle->getPrefixedText() )
386  ->numParams( $mh->getMergedRevisionCount() )
387  );
388 
389  return true;
390  }
391 
392  protected function getGroupName() {
393  return 'pagetools';
394  }
395 }
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:669
SpecialPage\msg
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:800
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:329
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:349
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:716
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:152
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:1811
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:328
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:902
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:836
wfScript
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
Definition: GlobalFunctions.php:2531
SpecialMergeHistory\$mDestObj
Title $mDestObj
Definition: SpecialMergeHistory.php:65
LogPage
Class to simplify the use of log pages.
Definition: LogPage.php:33
SpecialMergeHistory\formatRevisionRow
formatRevisionRow( $row)
Definition: SpecialMergeHistory.php:280
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:551
SpecialPage\getUser
getUser()
Shortcut to get the User executing this instance.
Definition: SpecialPage.php:726
LogEventsList\showLogExtract
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Definition: LogEventsList.php:639
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:39
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:706
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:1490
SpecialMergeHistory\getGroupName
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
Definition: SpecialMergeHistory.php:392
SpecialPage\getLinkRenderer
getLinkRenderer()
Definition: SpecialPage.php:912
Title
Represents a title within MediaWiki.
Definition: Title.php:42
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:341
SpecialPage\$linkRenderer
MediaWiki Linker LinkRenderer null $linkRenderer
Definition: SpecialPage.php:69
MergeHistoryPager
Definition: MergeHistoryPager.php:27
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:473
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:636
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