Merge "Handle missing namespace prefix in XML dumps more gracefully"
[lhc/web/wiklou.git] / includes / RevisionList.php
1 <?php
2 /**
3 * Holders of revision list for a single page
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22
23 use MediaWiki\MediaWikiServices;
24 use Wikimedia\Rdbms\ResultWrapper;
25
26 /**
27 * List for revision table items for a single page
28 */
29 abstract class RevisionListBase extends ContextSource implements Iterator {
30 /** @var Title */
31 public $title;
32
33 /** @var array */
34 protected $ids;
35
36 /** @var ResultWrapper|bool */
37 protected $res;
38
39 /** @var bool|object */
40 protected $current;
41
42 /**
43 * Construct a revision list for a given title
44 * @param IContextSource $context
45 * @param Title $title
46 */
47 function __construct( IContextSource $context, Title $title ) {
48 $this->setContext( $context );
49 $this->title = $title;
50 }
51
52 /**
53 * Select items only where the ID is any of the specified values
54 * @param array $ids
55 */
56 function filterByIds( array $ids ) {
57 $this->ids = $ids;
58 }
59
60 /**
61 * Get the internal type name of this list. Equal to the table name.
62 * Override this function.
63 * @return null
64 */
65 public function getType() {
66 return null;
67 }
68
69 /**
70 * Initialise the current iteration pointer
71 */
72 protected function initCurrent() {
73 $row = $this->res->current();
74 if ( $row ) {
75 $this->current = $this->newItem( $row );
76 } else {
77 $this->current = false;
78 }
79 }
80
81 /**
82 * Start iteration. This must be called before current() or next().
83 * @return Revision First list item
84 */
85 public function reset() {
86 if ( !$this->res ) {
87 $this->res = $this->doQuery( wfGetDB( DB_REPLICA ) );
88 } else {
89 $this->res->rewind();
90 }
91 $this->initCurrent();
92 return $this->current;
93 }
94
95 public function rewind() {
96 $this->reset();
97 }
98
99 /**
100 * Get the current list item, or false if we are at the end
101 * @return Revision
102 */
103 public function current() {
104 return $this->current;
105 }
106
107 /**
108 * Move the iteration pointer to the next list item, and return it.
109 * @return Revision
110 */
111 public function next() {
112 $this->res->next();
113 $this->initCurrent();
114 return $this->current;
115 }
116
117 public function key() {
118 return $this->res ? $this->res->key(): 0;
119 }
120
121 public function valid() {
122 return $this->res ? $this->res->valid() : false;
123 }
124
125 /**
126 * Get the number of items in the list.
127 * @return int
128 */
129 public function length() {
130 if ( !$this->res ) {
131 return 0;
132 } else {
133 return $this->res->numRows();
134 }
135 }
136
137 /**
138 * Do the DB query to iterate through the objects.
139 * @param IDatabase $db DB object to use for the query
140 */
141 abstract public function doQuery( $db );
142
143 /**
144 * Create an item object from a DB result row
145 * @param object $row
146 */
147 abstract public function newItem( $row );
148 }
149
150 /**
151 * Abstract base class for revision items
152 */
153 abstract class RevisionItemBase {
154 /** @var RevisionListBase The parent */
155 protected $list;
156
157 /** The database result row */
158 protected $row;
159
160 /**
161 * @param RevisionListBase $list
162 * @param object $row DB result row
163 */
164 public function __construct( $list, $row ) {
165 $this->list = $list;
166 $this->row = $row;
167 }
168
169 /**
170 * Get the DB field name associated with the ID list.
171 * Override this function.
172 * @return null
173 */
174 public function getIdField() {
175 return null;
176 }
177
178 /**
179 * Get the DB field name storing timestamps.
180 * Override this function.
181 * @return bool
182 */
183 public function getTimestampField() {
184 return false;
185 }
186
187 /**
188 * Get the DB field name storing user ids.
189 * Override this function.
190 * @return bool
191 */
192 public function getAuthorIdField() {
193 return false;
194 }
195
196 /**
197 * Get the DB field name storing user names.
198 * Override this function.
199 * @return bool
200 */
201 public function getAuthorNameField() {
202 return false;
203 }
204
205 /**
206 * Get the ID, as it would appear in the ids URL parameter
207 * @return int
208 */
209 public function getId() {
210 $field = $this->getIdField();
211 return $this->row->$field;
212 }
213
214 /**
215 * Get the date, formatted in user's language
216 * @return string
217 */
218 public function formatDate() {
219 return $this->list->getLanguage()->userDate( $this->getTimestamp(),
220 $this->list->getUser() );
221 }
222
223 /**
224 * Get the time, formatted in user's language
225 * @return string
226 */
227 public function formatTime() {
228 return $this->list->getLanguage()->userTime( $this->getTimestamp(),
229 $this->list->getUser() );
230 }
231
232 /**
233 * Get the timestamp in MW 14-char form
234 * @return mixed
235 */
236 public function getTimestamp() {
237 $field = $this->getTimestampField();
238 return wfTimestamp( TS_MW, $this->row->$field );
239 }
240
241 /**
242 * Get the author user ID
243 * @return int
244 */
245 public function getAuthorId() {
246 $field = $this->getAuthorIdField();
247 return intval( $this->row->$field );
248 }
249
250 /**
251 * Get the author user name
252 * @return string
253 */
254 public function getAuthorName() {
255 $field = $this->getAuthorNameField();
256 return strval( $this->row->$field );
257 }
258
259 /**
260 * Returns true if the current user can view the item
261 */
262 abstract public function canView();
263
264 /**
265 * Returns true if the current user can view the item text/file
266 */
267 abstract public function canViewContent();
268
269 /**
270 * Get the HTML of the list item. Should be include "<li></li>" tags.
271 * This is used to show the list in HTML form, by the special page.
272 */
273 abstract public function getHTML();
274
275 /**
276 * Returns an instance of LinkRenderer
277 * @return \MediaWiki\Linker\LinkRenderer
278 */
279 protected function getLinkRenderer() {
280 return MediaWikiServices::getInstance()->getLinkRenderer();
281 }
282 }
283
284 class RevisionList extends RevisionListBase {
285 public function getType() {
286 return 'revision';
287 }
288
289 /**
290 * @param IDatabase $db
291 * @return mixed
292 */
293 public function doQuery( $db ) {
294 $conds = [ 'rev_page' => $this->title->getArticleID() ];
295 if ( $this->ids !== null ) {
296 $conds['rev_id'] = array_map( 'intval', $this->ids );
297 }
298 return $db->select(
299 [ 'revision', 'page', 'user' ],
300 array_merge( Revision::selectFields(), Revision::selectUserFields() ),
301 $conds,
302 __METHOD__,
303 [ 'ORDER BY' => 'rev_id DESC' ],
304 [
305 'page' => Revision::pageJoinCond(),
306 'user' => Revision::userJoinCond() ]
307 );
308 }
309
310 public function newItem( $row ) {
311 return new RevisionItem( $this, $row );
312 }
313 }
314
315 /**
316 * Item class for a live revision table row
317 */
318 class RevisionItem extends RevisionItemBase {
319 /** @var Revision */
320 protected $revision;
321
322 /** @var RequestContext */
323 protected $context;
324
325 public function __construct( $list, $row ) {
326 parent::__construct( $list, $row );
327 $this->revision = new Revision( $row );
328 $this->context = $list->getContext();
329 }
330
331 public function getIdField() {
332 return 'rev_id';
333 }
334
335 public function getTimestampField() {
336 return 'rev_timestamp';
337 }
338
339 public function getAuthorIdField() {
340 return 'rev_user';
341 }
342
343 public function getAuthorNameField() {
344 return 'rev_user_text';
345 }
346
347 public function canView() {
348 return $this->revision->userCan( Revision::DELETED_RESTRICTED, $this->context->getUser() );
349 }
350
351 public function canViewContent() {
352 return $this->revision->userCan( Revision::DELETED_TEXT, $this->context->getUser() );
353 }
354
355 public function isDeleted() {
356 return $this->revision->isDeleted( Revision::DELETED_TEXT );
357 }
358
359 /**
360 * Get the HTML link to the revision text.
361 * @todo Essentially a copy of RevDelRevisionItem::getRevisionLink. That class
362 * should inherit from this one, and implement an appropriate interface instead
363 * of extending RevDelItem
364 * @return string
365 */
366 protected function getRevisionLink() {
367 $date = $this->list->getLanguage()->userTimeAndDate(
368 $this->revision->getTimestamp(), $this->list->getUser() );
369
370 if ( $this->isDeleted() && !$this->canViewContent() ) {
371 return htmlspecialchars( $date );
372 }
373 $linkRenderer = $this->getLinkRenderer();
374 return $linkRenderer->makeKnownLink(
375 $this->list->title,
376 $date,
377 [],
378 [
379 'oldid' => $this->revision->getId(),
380 'unhide' => 1
381 ]
382 );
383 }
384
385 /**
386 * Get the HTML link to the diff.
387 * @todo Essentially a copy of RevDelRevisionItem::getDiffLink. That class
388 * should inherit from this one, and implement an appropriate interface instead
389 * of extending RevDelItem
390 * @return string
391 */
392 protected function getDiffLink() {
393 if ( $this->isDeleted() && !$this->canViewContent() ) {
394 return $this->context->msg( 'diff' )->escaped();
395 } else {
396 $linkRenderer = $this->getLinkRenderer();
397 return $linkRenderer->makeKnownLink(
398 $this->list->title,
399 $this->list->msg( 'diff' )->text(),
400 [],
401 [
402 'diff' => $this->revision->getId(),
403 'oldid' => 'prev',
404 'unhide' => 1
405 ]
406 );
407 }
408 }
409
410 /**
411 * @todo Essentially a copy of RevDelRevisionItem::getHTML. That class
412 * should inherit from this one, and implement an appropriate interface instead
413 * of extending RevDelItem
414 * @return string
415 */
416 public function getHTML() {
417 $difflink = $this->context->msg( 'parentheses' )
418 ->rawParams( $this->getDiffLink() )->escaped();
419 $revlink = $this->getRevisionLink();
420 $userlink = Linker::revUserLink( $this->revision );
421 $comment = Linker::revComment( $this->revision );
422 if ( $this->isDeleted() ) {
423 $revlink = "<span class=\"history-deleted\">$revlink</span>";
424 }
425 return "<li>$difflink $revlink $userlink $comment</li>";
426 }
427 }