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