MediaWiki master
ImageHistoryPseudoPager.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Page;
8
15use stdClass;
16use Wikimedia\Timestamp\TimestampException;
17
20 protected $preventClickjacking = false;
21
25 protected $mImg;
26
30 protected $mTitle;
31
37
42 public $mHist;
43
48 public $mRange;
49
51 private $linkBatchFactory;
52
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
80 public function getTitle() {
81 return $this->mTitle;
82 }
83
85 public function getQueryInfo() {
86 return [];
87 }
88
92 public function getIndexField() {
93 return '';
94 }
95
100 public function formatRow( $row ) {
101 return '';
102 }
103
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(
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
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
302 protected function preventClickjacking( $enable = true ) {
303 $this->preventClickjacking = $enable;
304 }
305
310 protected function setPreventClickjacking( bool $enable ) {
311 $this->preventClickjacking = $enable;
312 }
313
317 public function getPreventClickjacking() {
319 }
320
321}
322
324class_alias( ImageHistoryPseudoPager::class, 'ImageHistoryPseudoPager' );
const NS_USER
Definition Defines.php:53
const NS_USER_TALK
Definition Defines.php:54
wfScript( $script='index')
Get the URL path to a MediaWiki entry point.
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition File.php:79
This class is a collection of static functions that serve two purposes:
Definition Html.php:43
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Builds the image revision log shown on image pages.
__construct( $imagePage, ?LinkBatchFactory $linkBatchFactory=null)
doQuery()
Do the query, using information from the object context.
getQueryInfo()
Provides all parameters needed for the main paged query.It returns an associative array with the foll...
Rendering of file description pages.
Definition ImagePage.php:34
Factory for LinkBatch objects to batch query page metadata.
int $mLimit
The maximum number of entries to show.
IndexPager with a formatted navigation bar.
Parent class for all special pages.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Represents a title within MediaWiki.
Definition Title.php:69
element(SerializerNode $parent, SerializerNode $node, $contents)