Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 136 |
|
0.00% |
0 / 11 |
CRAP | |
0.00% |
0 / 1 |
| ImageHistoryPseudoPager | |
0.00% |
0 / 135 |
|
0.00% |
0 / 11 |
1560 | |
0.00% |
0 / 1 |
| __construct | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
2 | |||
| getTitle | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getQueryInfo | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getIndexField | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| formatRow | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getBody | |
0.00% |
0 / 42 |
|
0.00% |
0 / 1 |
132 | |||
| doQuery | |
0.00% |
0 / 49 |
|
0.00% |
0 / 1 |
342 | |||
| wrapWithActionButtons | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
6 | |||
| preventClickjacking | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| setPreventClickjacking | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getPreventClickjacking | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * @license GPL-2.0-or-later |
| 4 | * @file |
| 5 | */ |
| 6 | |
| 7 | namespace MediaWiki\Page; |
| 8 | |
| 9 | use MediaWiki\FileRepo\File\File; |
| 10 | use MediaWiki\Html\Html; |
| 11 | use MediaWiki\MediaWikiServices; |
| 12 | use MediaWiki\Pager\ReverseChronologicalPager; |
| 13 | use MediaWiki\SpecialPage\SpecialPage; |
| 14 | use MediaWiki\Title\Title; |
| 15 | use stdClass; |
| 16 | use Wikimedia\Timestamp\TimestampException; |
| 17 | |
| 18 | class ImageHistoryPseudoPager extends ReverseChronologicalPager { |
| 19 | /** @var bool */ |
| 20 | protected $preventClickjacking = false; |
| 21 | |
| 22 | /** |
| 23 | * @var File|null |
| 24 | */ |
| 25 | protected $mImg; |
| 26 | |
| 27 | /** |
| 28 | * @var Title |
| 29 | */ |
| 30 | protected $mTitle; |
| 31 | |
| 32 | /** |
| 33 | * @since 1.14 |
| 34 | * @var ImagePage |
| 35 | */ |
| 36 | public $mImagePage; |
| 37 | |
| 38 | /** |
| 39 | * @since 1.14 |
| 40 | * @var File[] |
| 41 | */ |
| 42 | public $mHist; |
| 43 | |
| 44 | /** |
| 45 | * @since 1.14 |
| 46 | * @var int[] |
| 47 | */ |
| 48 | public $mRange; |
| 49 | |
| 50 | /** @var LinkBatchFactory */ |
| 51 | private $linkBatchFactory; |
| 52 | |
| 53 | /** |
| 54 | * @param ImagePage $imagePage |
| 55 | * @param LinkBatchFactory|null $linkBatchFactory |
| 56 | */ |
| 57 | public function __construct( $imagePage, ?LinkBatchFactory $linkBatchFactory = null ) { |
| 58 | parent::__construct( $imagePage->getContext() ); |
| 59 | $this->mImagePage = $imagePage; |
| 60 | $this->mTitle = $imagePage->getTitle()->createFragmentTarget( 'filehistory' ); |
| 61 | $this->mImg = null; |
| 62 | $this->mHist = []; |
| 63 | $this->mRange = [ 0, 0 ]; // display range |
| 64 | |
| 65 | // Only display 10 revisions at once by default, otherwise the list is overwhelming |
| 66 | array_unshift( $this->mLimitsShown, 10 ); |
| 67 | $this->mDefaultLimit = 10; |
| 68 | [ $this->mLimit, /* $offset */ ] = |
| 69 | $this->mRequest->getLimitOffsetForUser( |
| 70 | $this->getUser(), |
| 71 | $this->mDefaultLimit, |
| 72 | '' |
| 73 | ); |
| 74 | $this->linkBatchFactory = $linkBatchFactory ?? MediaWikiServices::getInstance()->getLinkBatchFactory(); |
| 75 | } |
| 76 | |
| 77 | /** |
| 78 | * @return Title |
| 79 | */ |
| 80 | public function getTitle() { |
| 81 | return $this->mTitle; |
| 82 | } |
| 83 | |
| 84 | /** @inheritDoc */ |
| 85 | public function getQueryInfo() { |
| 86 | return []; |
| 87 | } |
| 88 | |
| 89 | /** |
| 90 | * @return string |
| 91 | */ |
| 92 | public function getIndexField() { |
| 93 | return ''; |
| 94 | } |
| 95 | |
| 96 | /** |
| 97 | * @param stdClass $row |
| 98 | * @return string |
| 99 | */ |
| 100 | public function formatRow( $row ) { |
| 101 | return ''; |
| 102 | } |
| 103 | |
| 104 | /** |
| 105 | * @return string |
| 106 | */ |
| 107 | public function getBody() { |
| 108 | $s = ''; |
| 109 | $this->doQuery(); |
| 110 | if ( count( $this->mHist ) ) { |
| 111 | if ( $this->mImg->isLocal() ) { |
| 112 | // Do a batch existence check for user pages and talkpages. |
| 113 | $linkBatch = $this->linkBatchFactory->newLinkBatch()->setCaller( __METHOD__ ); |
| 114 | for ( $i = $this->mRange[0]; $i <= $this->mRange[1]; $i++ ) { |
| 115 | $file = $this->mHist[$i]; |
| 116 | $uploader = $file->getUploader( File::FOR_THIS_USER, $this->getAuthority() ); |
| 117 | if ( $uploader ) { |
| 118 | $linkBatch->add( NS_USER, $uploader->getName() ); |
| 119 | $linkBatch->add( NS_USER_TALK, $uploader->getName() ); |
| 120 | } |
| 121 | } |
| 122 | $linkBatch->execute(); |
| 123 | } |
| 124 | |
| 125 | // Batch-format comments |
| 126 | $comments = []; |
| 127 | for ( $i = $this->mRange[0]; $i <= $this->mRange[1]; $i++ ) { |
| 128 | $file = $this->mHist[$i]; |
| 129 | $comments[$i] = $file->getDescription( |
| 130 | File::FOR_THIS_USER, |
| 131 | $this->getAuthority() |
| 132 | ) ?: ''; |
| 133 | } |
| 134 | $formattedComments = MediaWikiServices::getInstance() |
| 135 | ->getCommentFormatter() |
| 136 | ->formatStrings( $comments, $this->getTitle() ); |
| 137 | |
| 138 | $list = new ImageHistoryList( $this->mImagePage ); |
| 139 | # Generate prev/next links |
| 140 | $navLink = $this->getNavigationBar(); |
| 141 | |
| 142 | $s = Html::element( 'h2', [ 'id' => 'filehistory' ], $this->msg( 'filehist' )->text() ) . "\n" |
| 143 | . Html::openElement( 'div', [ 'id' => 'mw-imagepage-section-filehistory' ] ) . "\n" |
| 144 | . $this->msg( 'filehist-help' )->parseAsBlock() |
| 145 | . $navLink . "\n"; |
| 146 | |
| 147 | $sList = $list->beginImageHistoryList(); |
| 148 | $onlyCurrentFile = true; |
| 149 | // Skip rows there just for paging links |
| 150 | for ( $i = $this->mRange[0]; $i <= $this->mRange[1]; $i++ ) { |
| 151 | $file = $this->mHist[$i]; |
| 152 | $sList .= $list->imageHistoryLine( !$file->isOld(), $file, $formattedComments[$i] ); |
| 153 | $onlyCurrentFile = !$file->isOld(); |
| 154 | } |
| 155 | $sList .= $list->endImageHistoryList(); |
| 156 | if ( $onlyCurrentFile || !$this->mImg->isLocal() ) { |
| 157 | // It is not possible to revision-delete the current file or foreign files, |
| 158 | // if there is only the current file or the file is not local, show no buttons |
| 159 | $s .= $sList; |
| 160 | } else { |
| 161 | $s .= $this->wrapWithActionButtons( $sList ); |
| 162 | } |
| 163 | $s .= $navLink . "\n" . Html::closeElement( 'div' ) . "\n"; |
| 164 | |
| 165 | if ( $list->getPreventClickjacking() ) { |
| 166 | $this->setPreventClickjacking( true ); |
| 167 | } |
| 168 | } |
| 169 | return $s; |
| 170 | } |
| 171 | |
| 172 | public function doQuery() { |
| 173 | if ( $this->mQueryDone ) { |
| 174 | return; |
| 175 | } |
| 176 | $this->mImg = $this->mImagePage->getPage()->getFile(); // ensure loading |
| 177 | if ( !$this->mImg->exists() ) { |
| 178 | return; |
| 179 | } |
| 180 | // Make sure the date (probably from user input) is valid; if not, drop it. |
| 181 | if ( $this->mOffset !== null ) { |
| 182 | try { |
| 183 | $this->mDb->timestamp( $this->mOffset ); |
| 184 | } catch ( TimestampException ) { |
| 185 | $this->mOffset = null; |
| 186 | } |
| 187 | } |
| 188 | $queryLimit = $this->mLimit + 1; // limit plus extra row |
| 189 | if ( $this->mIsBackwards ) { |
| 190 | // Fetch the file history |
| 191 | $this->mHist = $this->mImg->getHistory( $queryLimit, null, $this->mOffset, false ); |
| 192 | // The current rev may not meet the offset/limit |
| 193 | $numRows = count( $this->mHist ); |
| 194 | if ( $numRows <= $this->mLimit && $this->mImg->getTimestamp() > $this->mOffset ) { |
| 195 | array_unshift( $this->mHist, $this->mImg ); |
| 196 | } |
| 197 | } else { |
| 198 | // The current rev may not meet the offset |
| 199 | if ( !$this->mOffset || $this->mImg->getTimestamp() < $this->mOffset ) { |
| 200 | $this->mHist[] = $this->mImg; |
| 201 | } |
| 202 | // Old image versions (fetch extra row for nav links) |
| 203 | $oiLimit = count( $this->mHist ) ? $this->mLimit : $this->mLimit + 1; |
| 204 | // Fetch the file history |
| 205 | $this->mHist = array_merge( $this->mHist, |
| 206 | $this->mImg->getHistory( $oiLimit, $this->mOffset, null, false ) ); |
| 207 | } |
| 208 | $numRows = count( $this->mHist ); // Total number of query results |
| 209 | if ( $numRows ) { |
| 210 | # Index value of top item in the list |
| 211 | $firstIndex = $this->mIsBackwards ? |
| 212 | [ $this->mHist[$numRows - 1]->getTimestamp() ] : [ $this->mHist[0]->getTimestamp() ]; |
| 213 | # Discard the extra result row if there is one |
| 214 | if ( $numRows > $this->mLimit && $numRows > 1 ) { |
| 215 | if ( $this->mIsBackwards ) { |
| 216 | # Index value of item past the index |
| 217 | $this->mPastTheEndIndex = [ $this->mHist[0]->getTimestamp() ]; |
| 218 | # Index value of bottom item in the list |
| 219 | $lastIndex = [ $this->mHist[1]->getTimestamp() ]; |
| 220 | # Display range |
| 221 | $this->mRange = [ 1, $numRows - 1 ]; |
| 222 | } else { |
| 223 | # Index value of item past the index |
| 224 | $this->mPastTheEndIndex = [ $this->mHist[$numRows - 1]->getTimestamp() ]; |
| 225 | # Index value of bottom item in the list |
| 226 | $lastIndex = [ $this->mHist[$numRows - 2]->getTimestamp() ]; |
| 227 | # Display range |
| 228 | $this->mRange = [ 0, $numRows - 2 ]; |
| 229 | } |
| 230 | } else { |
| 231 | # Setting indexes to an empty array means that they will be |
| 232 | # omitted if they would otherwise appear in URLs. It just so |
| 233 | # happens that this is the right thing to do in the standard |
| 234 | # UI, in all the relevant cases. |
| 235 | $this->mPastTheEndIndex = []; |
| 236 | # Index value of bottom item in the list |
| 237 | $lastIndex = $this->mIsBackwards ? |
| 238 | [ $this->mHist[0]->getTimestamp() ] : [ $this->mHist[$numRows - 1]->getTimestamp() ]; |
| 239 | # Display range |
| 240 | $this->mRange = [ 0, $numRows - 1 ]; |
| 241 | } |
| 242 | } else { |
| 243 | $firstIndex = []; |
| 244 | $lastIndex = []; |
| 245 | $this->mPastTheEndIndex = []; |
| 246 | } |
| 247 | if ( $this->mIsBackwards ) { |
| 248 | $this->mIsFirst = ( $numRows < $queryLimit ); |
| 249 | $this->mIsLast = ( $this->mOffset == '' ); |
| 250 | $this->mLastShown = $firstIndex; |
| 251 | $this->mFirstShown = $lastIndex; |
| 252 | } else { |
| 253 | $this->mIsFirst = ( $this->mOffset == '' ); |
| 254 | $this->mIsLast = ( $numRows < $queryLimit ); |
| 255 | $this->mLastShown = $lastIndex; |
| 256 | $this->mFirstShown = $firstIndex; |
| 257 | } |
| 258 | $this->mQueryDone = true; |
| 259 | } |
| 260 | |
| 261 | /** |
| 262 | * Wrap the content with action buttons at begin and end if the user |
| 263 | * is allow to use the action buttons. |
| 264 | * @param string $formcontents |
| 265 | * @return string |
| 266 | */ |
| 267 | private function wrapWithActionButtons( $formcontents ) { |
| 268 | if ( !$this->getAuthority()->isAllowed( 'deleterevision' ) ) { |
| 269 | return $formcontents; |
| 270 | } |
| 271 | |
| 272 | # Show button to hide log entries |
| 273 | $s = Html::openElement( |
| 274 | 'form', |
| 275 | [ 'action' => wfScript(), 'id' => 'mw-filehistory-deleterevision-submit' ] |
| 276 | ) . "\n"; |
| 277 | $s .= Html::hidden( 'target', $this->getTitle()->getPrefixedDBkey() ) . "\n"; |
| 278 | $s .= Html::hidden( 'type', 'oldimage' ) . "\n"; |
| 279 | $this->setPreventClickjacking( true ); |
| 280 | |
| 281 | $buttons = Html::element( |
| 282 | 'button', |
| 283 | [ |
| 284 | 'type' => 'submit', |
| 285 | 'name' => 'title', |
| 286 | 'value' => SpecialPage::getTitleFor( 'Revisiondelete' )->getPrefixedDBkey(), |
| 287 | 'class' => "deleterevision-filehistory-submit mw-filehistory-deleterevision-button mw-ui-button" |
| 288 | ], |
| 289 | $this->msg( 'showhideselectedfileversions' )->text() |
| 290 | ) . "\n"; |
| 291 | |
| 292 | $s .= $buttons . $formcontents . $buttons; |
| 293 | $s .= Html::closeElement( 'form' ); |
| 294 | |
| 295 | return $s; |
| 296 | } |
| 297 | |
| 298 | /** |
| 299 | * @param bool $enable |
| 300 | * @deprecated since 1.38, use ::setPreventClickjacking() |
| 301 | */ |
| 302 | protected function preventClickjacking( $enable = true ) { |
| 303 | $this->preventClickjacking = $enable; |
| 304 | } |
| 305 | |
| 306 | /** |
| 307 | * @param bool $enable |
| 308 | * @since 1.38 |
| 309 | */ |
| 310 | protected function setPreventClickjacking( bool $enable ) { |
| 311 | $this->preventClickjacking = $enable; |
| 312 | } |
| 313 | |
| 314 | /** |
| 315 | * @return bool |
| 316 | */ |
| 317 | public function getPreventClickjacking() { |
| 318 | return $this->preventClickjacking; |
| 319 | } |
| 320 | |
| 321 | } |
| 322 | |
| 323 | /** @deprecated class alias since 1.44 */ |
| 324 | class_alias( ImageHistoryPseudoPager::class, 'ImageHistoryPseudoPager' ); |