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