MediaWiki REL1_34
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 ) {
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 && $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\">" .
234 Xml::submitButton(
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
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 ( !$rev->userCan( RevisionRecord::DELETED_TEXT, $user ) ) {
304 $last = $this->msg( 'last' )->escaped();
305 } elseif ( isset( $this->prevId[$row->rev_id] ) ) {
306 $last = $linkRenderer->makeKnownLink(
307 $rev->getTitle(),
308 $this->msg( 'last' )->text(),
309 [],
310 [
311 'diff' => $row->rev_id,
312 'oldid' => $this->prevId[$row->rev_id]
313 ]
314 );
315 }
316
317 $userLink = Linker::revUserTools( $rev );
318
319 $size = $row->rev_len;
320 if ( !is_null( $size ) ) {
321 $stxt = Linker::formatRevisionSize( $size );
322 }
323 $comment = Linker::revComment( $rev );
324
325 return Html::rawElement( 'li', [],
326 $this->msg( 'mergehistory-revisionrow' )
327 ->rawParams( $checkBox, $last, $pageLink, $userLink, $stxt, $comment )->escaped() );
328 }
329
342 function merge() {
343 # Get the titles directly from the IDs, in case the target page params
344 # were spoofed. The queries are done based on the IDs, so it's best to
345 # keep it consistent...
346 $targetTitle = Title::newFromID( $this->mTargetID );
347 $destTitle = Title::newFromID( $this->mDestID );
348 if ( is_null( $targetTitle ) || is_null( $destTitle ) ) {
349 return false; // validate these
350 }
351 if ( $targetTitle->getArticleID() == $destTitle->getArticleID() ) {
352 return false;
353 }
354
355 // MergeHistory object
356 $mh = new MergeHistory( $targetTitle, $destTitle, $this->mTimestamp );
357
358 // Merge!
359 $mergeStatus = $mh->merge( $this->getUser(), $this->mComment );
360 if ( !$mergeStatus->isOK() ) {
361 // Failed merge
362 $this->getOutput()->addWikiMsg( $mergeStatus->getMessage() );
363 return false;
364 }
365
367
368 $targetLink = $linkRenderer->makeLink(
369 $targetTitle,
370 null,
371 [],
372 [ 'redirect' => 'no' ]
373 );
374
375 $this->getOutput()->addWikiMsg( $this->msg( 'mergehistory-done' )
376 ->rawParams( $targetLink )
377 ->params( $destTitle->getPrefixedText() )
378 ->numParams( $mh->getMergedRevisionCount() )
379 );
380
381 return true;
382 }
383
384 protected function getGroupName() {
385 return 'pagetools';
386 }
387}
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(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:1577
static formatRevisionSize( $size)
Definition Linker.php:1602
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:1124
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Class to simplify the use of log pages.
Definition LogPage.php:33
Page revision base class.
Handles the backend logic of merging the histories of two pages.
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
$last