MediaWiki REL1_35
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 ( $this->mTimestamp === null || !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 ) {
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\">" .
235 Xml::submitButton(
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
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
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}
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
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:1615
static formatRevisionSize( $size)
Definition Linker.php:1660
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:1152
Class to simplify the use of log pages.
Definition LogPage.php:37
MediaWikiServices is the service locator for the application scope of MediaWiki.
Page revision base class.
Special page allowing users with the appropriate permissions to merge article histories,...
merge()
Actually attempt the history move.
doesWrites()
Indicates whether this special page may perform database writes.
bool $mSubmitted
Was submitted?
execute( $par)
Default execute method Checks user permissions.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
Parent class for all special pages.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!...
getOutput()
Get the OutputPage being used for this instance.
getUser()
Shortcut to get the User executing this instance.
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getRequest()
Get the WebRequest being used for this instance.
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
getPageTitle( $subpage=false)
Get a self-referential title object.
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
MediaWiki Linker LinkRenderer null $linkRenderer
Represents a title within MediaWiki.
Definition Title.php:42