1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 * @ingroup Pager
20 */
21
22use MediaWiki\Cache\LinkBatchFactory;
23use MediaWiki\Linker\LinkRenderer;
24use Wikimedia\Rdbms\ILoadBalancer;
25
26class ProtectedPagesPager extends TablePager {
27
28	public $mConds;
29	private $type, $level, $namespace, $sizetype, $size, $indefonly, $cascadeonly, $noredirect;
30
31	/** @var LinkBatchFactory */
32	private $linkBatchFactory;
33
34	/** @var CommentStore */
35	private $commentStore;
36
37	/** @var UserCache */
38	private $userCache;
39
40	/**
41	 * @param SpecialPage $form
42	 * @param array $conds
43	 * @param string $type
44	 * @param string $level
45	 * @param int $namespace
46	 * @param string $sizetype
47	 * @param int $size
48	 * @param bool $indefonly
49	 * @param bool $cascadeonly
50	 * @param bool $noredirect
51	 * @param LinkRenderer $linkRenderer
52	 * @param LinkBatchFactory $linkBatchFactory
53	 * @param ILoadBalancer $loadBalancer
54	 * @param CommentStore $commentStore
55	 * @param UserCache $userCache
56	 */
57	public function __construct(
58		$form,
59		$conds,
60		$type,
61		$level,
62		$namespace,
63		$sizetype,
64		$size,
65		$indefonly,
66		$cascadeonly,
67		$noredirect,
68		LinkRenderer $linkRenderer,
69		LinkBatchFactory $linkBatchFactory,
70		ILoadBalancer $loadBalancer,
71		CommentStore $commentStore,
72		UserCache $userCache
73	) {
74		// Set database before parent constructor to avoid setting it there with wfGetDB
75		$this->mDb = $loadBalancer->getConnectionRef( ILoadBalancer::DB_REPLICA );
76		parent::__construct( $form->getContext(), $linkRenderer );
77		$this->mConds = $conds;
78		$this->type = $type ?: 'edit';
79		$this->level = $level;
80		$this->namespace = $namespace;
81		$this->sizetype = $sizetype;
82		$this->size = intval( $size );
83		$this->indefonly = (bool)$indefonly;
84		$this->cascadeonly = (bool)$cascadeonly;
85		$this->noredirect = (bool)$noredirect;
86		$this->linkBatchFactory = $linkBatchFactory;
87		$this->commentStore = $commentStore;
88		$this->userCache = $userCache;
89	}
90
91	public function preprocessResults( $result ) {
92		# Do a link batch query
93		$lb = $this->linkBatchFactory->newLinkBatch();
94		$userids = [];
95
96		foreach ( $result as $row ) {
97			$lb->add( $row->page_namespace, $row->page_title );
98			if ( $row->actor_user !== null ) {
99				$userids[] = $row->actor_user;
100			}
101		}
102
103		// fill LinkBatch with user page and user talk
104		if ( count( $userids ) ) {
105			$this->userCache->doQuery( $userids, [], __METHOD__ );
106			foreach ( $userids as $userid ) {
107				$name = $this->userCache->getProp( $userid, 'name' );
108				if ( $name !== false ) {
109					$lb->add( NS_USER, $name );
110					$lb->add( NS_USER_TALK, $name );
111				}
112			}
113		}
114
115		$lb->execute();
116	}
117
118	protected function getFieldNames() {
119		static $headers = null;
120
121		if ( $headers == [] ) {
122			$headers = [
123				'log_timestamp' => 'protectedpages-timestamp',
124				'pr_page' => 'protectedpages-page',
125				'pr_expiry' => 'protectedpages-expiry',
126				'actor_user' => 'protectedpages-performer',
127				'pr_params' => 'protectedpages-params',
128				'log_comment' => 'protectedpages-reason',
129			];
130			foreach ( $headers as $key => $val ) {
131				$headers[$key] = $this->msg( $val )->text();
132			}
133		}
134
135		return $headers;
136	}
137
138	/**
139	 * @param string $field
140	 * @param string|null $value
141	 * @return string HTML
142	 * @throws MWException
143	 */
144	public function formatValue( $field, $value ) {
145		/** @var stdClass $row */
146		$row = $this->mCurrentRow;
147		$linkRenderer = $this->getLinkRenderer();
148
149		switch ( $field ) {
150			case 'log_timestamp':
151				// when timestamp is null, this is a old protection row
152				if ( $value === null ) {
153					$formatted = Html::rawElement(
154						'span',
155						[ 'class' => 'mw-protectedpages-unknown' ],
156						$this->msg( 'protectedpages-unknown-timestamp' )->escaped()
157					);
158				} else {
159					$formatted = htmlspecialchars( $this->getLanguage()->userTimeAndDate(
160						$value, $this->getUser() ) );
161				}
162				break;
163
164			case 'pr_page':
165				$title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
166				if ( !$title ) {
167					$formatted = Html::element(
168						'span',
169						[ 'class' => 'mw-invalidtitle' ],
170						Linker::getInvalidTitleDescription(
171							$this->getContext(),
172							$row->page_namespace,
173							$row->page_title
174						)
175					);
176				} else {
177					$formatted = $linkRenderer->makeLink( $title );
178				}
179				if ( $row->page_len !== null ) {
180					$formatted .= $this->getLanguage()->getDirMark() .
181						' ' . Html::rawElement(
182							'span',
183							[ 'class' => 'mw-protectedpages-length' ],
184							Linker::formatRevisionSize( $row->page_len )
185						);
186				}
187				break;
188
189			case 'pr_expiry':
190				$formatted = htmlspecialchars( $this->getLanguage()->formatExpiry(
191					$value, /* User preference timezone */true, 'infinity', $this->getUser() ) );
192				$title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
193				if ( $title && $this->getAuthority()->isAllowed( 'protect' ) ) {
194					$changeProtection = $linkRenderer->makeKnownLink(
195						$title,
196						$this->msg( 'protect_change' )->text(),
197						[],
198						[ 'action' => 'unprotect' ]
199					);
200					$formatted .= ' ' . Html::rawElement(
201							'span',
202							[ 'class' => 'mw-protectedpages-actions' ],
203							$this->msg( 'parentheses' )->rawParams( $changeProtection )->escaped()
204						);
205				}
206				break;
207
208			case 'actor_user':
209				// when timestamp is null, this is a old protection row
210				if ( $row->log_timestamp === null ) {
211					$formatted = Html::rawElement(
212						'span',
213						[ 'class' => 'mw-protectedpages-unknown' ],
214						$this->msg( 'protectedpages-unknown-performer' )->escaped()
215					);
216				} else {
217					$username = $row->actor_name;
218					if ( LogEventsList::userCanBitfield(
219						$row->log_deleted,
220						LogPage::DELETED_USER,
221						$this->getUser()
222					) ) {
223						$formatted = Linker::userLink( $value, $username )
224							. Linker::userToolLinks( $value, $username );
225					} else {
226						$formatted = $this->msg( 'rev-deleted-user' )->escaped();
227					}
228					if ( LogEventsList::isDeleted( $row, LogPage::DELETED_USER ) ) {
229						$formatted = '<span class="history-deleted">' . $formatted . '</span>';
230					}
231				}
232				break;
233
234			case 'pr_params':
235				$params = [];
236				// Messages: restriction-level-sysop, restriction-level-autoconfirmed
237				$params[] = $this->msg( 'restriction-level-' . $row->pr_level )->escaped();
238				if ( $row->pr_cascade ) {
239					$params[] = $this->msg( 'protect-summary-cascade' )->escaped();
240				}
241				$formatted = $this->getLanguage()->commaList( $params );
242				break;
243
244			case 'log_comment':
245				// when timestamp is null, this is an old protection row
246				if ( $row->log_timestamp === null ) {
247					$formatted = Html::rawElement(
248						'span',
249						[ 'class' => 'mw-protectedpages-unknown' ],
250						$this->msg( 'protectedpages-unknown-reason' )->escaped()
251					);
252				} else {
253					if ( LogEventsList::userCanBitfield(
254						$row->log_deleted,
255						LogPage::DELETED_COMMENT,
256						$this->getUser()
257					) ) {
258						$value = $this->commentStore->getComment( 'log_comment', $row )->text;
259						$formatted = Linker::formatComment( $value ?? '' );
260					} else {
261						$formatted = $this->msg( 'rev-deleted-comment' )->escaped();
262					}
263					if ( LogEventsList::isDeleted( $row, LogPage::DELETED_COMMENT ) ) {
264						$formatted = '<span class="history-deleted">' . $formatted . '</span>';
265					}
266				}
267				break;
268
269			default:
270				throw new MWException( "Unknown field '$field'" );
271		}
272
273		return $formatted;
274	}
275
276	public function getQueryInfo() {
277		$dbr = $this->getDatabase();
278		$conds = $this->mConds;
279		$conds[] = 'pr_expiry > ' . $dbr->addQuotes( $dbr->timestamp() ) .
280			' OR pr_expiry IS NULL';
281		$conds[] = 'page_id=pr_page';
282		$conds[] = 'pr_type=' . $dbr->addQuotes( $this->type );
283
284		if ( $this->sizetype == 'min' ) {
285			$conds[] = 'page_len>=' . $this->size;
286		} elseif ( $this->sizetype == 'max' ) {
287			$conds[] = 'page_len<=' . $this->size;
288		}
289
290		if ( $this->indefonly ) {
291			$infinity = $dbr->addQuotes( $dbr->getInfinity() );
292			$conds[] = "pr_expiry = $infinity OR pr_expiry IS NULL";
293		}
294		if ( $this->cascadeonly ) {
295			$conds[] = 'pr_cascade = 1';
296		}
297		if ( $this->noredirect ) {
298			$conds[] = 'page_is_redirect = 0';
299		}
300
301		if ( $this->level ) {
302			$conds[] = 'pr_level=' . $dbr->addQuotes( $this->level );
303		}
304		if ( $this->namespace !== null ) {
305			$conds[] = 'page_namespace=' . $dbr->addQuotes( $this->namespace );
306		}
307
308		$commentQuery = $this->commentStore->getJoin( 'log_comment' );
309
310		return [
311			'tables' => [
312				'page', 'page_restrictions', 'log_search',
313				'logparen' => [ 'logging', 'actor' ] + $commentQuery['tables'],
314			],
315			'fields' => [
316				'pr_id',
317				'page_namespace',
318				'page_title',
319				'page_len',
320				'pr_type',
321				'pr_level',
322				'pr_expiry',
323				'pr_cascade',
324				'log_timestamp',
325				'log_deleted',
326				'actor_name',
327				'actor_user'
328			] + $commentQuery['fields'],
329			'conds' => $conds,
330			'join_conds' => [
331				'log_search' => [
332					'LEFT JOIN', [
333						'ls_field' => 'pr_id', 'ls_value = ' . $dbr->buildStringCast( 'pr_id' )
334					]
335				],
336				'logparen' => [
337					'LEFT JOIN', [
338						'ls_log_id = log_id'
339					]
340				],
341				'actor' => [
342					'JOIN', [
343						'actor_id=log_actor'
344					]
345				]
346			] + $commentQuery['joins']
347		];
348	}
349
350	protected function getTableClass() {
351		return parent::getTableClass() . ' mw-protectedpages';
352	}
353
354	public function getIndexField() {
355		return 'pr_id';
356	}
357
358	public function getDefaultSort() {
359		return 'pr_id';
360	}
361
362	protected function isFieldSortable( $field ) {
363		// no index for sorting exists
364		return false;
365	}
366}
367