MediaWiki  master
ImageHistoryPseudoPager.php
Go to the documentation of this file.
1 <?php
25 use Wikimedia\Timestamp\TimestampException;
26 
28  protected $preventClickjacking = false;
29 
33  protected $mImg;
34 
38  protected $mTitle;
39 
44  public $mImagePage;
45 
50  public $mHist;
51 
56  public $mRange;
57 
59  private $linkBatchFactory;
60 
65  public function __construct( $imagePage, LinkBatchFactory $linkBatchFactory = null ) {
66  parent::__construct( $imagePage->getContext() );
67  $this->mImagePage = $imagePage;
68  $this->mTitle = $imagePage->getTitle()->createFragmentTarget( 'filehistory' );
69  $this->mImg = null;
70  $this->mHist = [];
71  $this->mRange = [ 0, 0 ]; // display range
72 
73  // Only display 10 revisions at once by default, otherwise the list is overwhelming
74  $this->mLimitsShown = array_merge( [ 10 ], $this->mLimitsShown );
75  $this->mDefaultLimit = 10;
76  [ $this->mLimit, /* $offset */ ] =
77  $this->mRequest->getLimitOffsetForUser(
78  $this->getUser(),
79  $this->mDefaultLimit,
80  ''
81  );
82  $this->linkBatchFactory = $linkBatchFactory ?? MediaWikiServices::getInstance()->getLinkBatchFactory();
83  }
84 
88  public function getTitle() {
89  return $this->mTitle;
90  }
91 
92  public function getQueryInfo() {
93  return [];
94  }
95 
99  public function getIndexField() {
100  return '';
101  }
102 
107  public function formatRow( $row ) {
108  return '';
109  }
110 
114  public function getBody() {
115  $s = '';
116  $this->doQuery();
117  if ( count( $this->mHist ) ) {
118  if ( $this->mImg->isLocal() ) {
119  // Do a batch existence check for user pages and talkpages.
120  $linkBatch = $this->linkBatchFactory->newLinkBatch();
121  for ( $i = $this->mRange[0]; $i <= $this->mRange[1]; $i++ ) {
122  $file = $this->mHist[$i];
123  $uploader = $file->getUploader( File::FOR_THIS_USER, $this->getAuthority() );
124  if ( $uploader ) {
125  $linkBatch->add( NS_USER, $uploader->getName() );
126  $linkBatch->add( NS_USER_TALK, $uploader->getName() );
127  }
128  }
129  $linkBatch->execute();
130  }
131 
132  // Batch-format comments
133  $comments = [];
134  for ( $i = $this->mRange[0]; $i <= $this->mRange[1]; $i++ ) {
135  $file = $this->mHist[$i];
136  $comments[$i] = $file->getDescription(
138  $this->getAuthority()
139  ) ?: '';
140  }
141  $formattedComments = MediaWikiServices::getInstance()
142  ->getCommentFormatter()
143  ->formatStrings( $comments, $this->getTitle() );
144 
145  $list = new ImageHistoryList( $this->mImagePage );
146  # Generate prev/next links
147  $navLink = $this->getNavigationBar();
148 
149  $s = Html::element( 'h2', [ 'id' => 'filehistory' ], $this->msg( 'filehist' )->text() ) . "\n"
150  . Html::openElement( 'div', [ 'id' => 'mw-imagepage-section-filehistory' ] ) . "\n"
151  . $this->msg( 'filehist-help' )->parseAsBlock()
152  . $navLink . "\n";
153 
154  $sList = $list->beginImageHistoryList();
155  $onlyCurrentFile = true;
156  // Skip rows there just for paging links
157  for ( $i = $this->mRange[0]; $i <= $this->mRange[1]; $i++ ) {
158  $file = $this->mHist[$i];
159  $sList .= $list->imageHistoryLine( !$file->isOld(), $file, $formattedComments[$i] );
160  $onlyCurrentFile = !$file->isOld();
161  }
162  $sList .= $list->endImageHistoryList();
163  if ( $onlyCurrentFile || !$this->mImg->isLocal() ) {
164  // It is not possible to revision-delete the current file or foreign files,
165  // if there is only the current file or the file is not local, show no buttons
166  $s .= $sList;
167  } else {
168  $s .= $this->wrapWithActionButtons( $sList );
169  }
170  $s .= $navLink . "\n" . Html::closeElement( 'div' ) . "\n";
171 
172  if ( $list->getPreventClickjacking() ) {
173  $this->setPreventClickjacking( true );
174  }
175  }
176  return $s;
177  }
178 
179  public function doQuery() {
180  if ( $this->mQueryDone ) {
181  return;
182  }
183  $this->mImg = $this->mImagePage->getPage()->getFile(); // ensure loading
184  if ( !$this->mImg->exists() ) {
185  return;
186  }
187  // Make sure the date (probably from user input) is valid; if not, drop it.
188  if ( $this->mOffset !== null ) {
189  try {
190  $this->mDb->timestamp( $this->mOffset );
191  } catch ( TimestampException $e ) {
192  $this->mOffset = null;
193  }
194  }
195  $queryLimit = $this->mLimit + 1; // limit plus extra row
196  if ( $this->mIsBackwards ) {
197  // Fetch the file history
198  $this->mHist = $this->mImg->getHistory( $queryLimit, null, $this->mOffset, false );
199  // The current rev may not meet the offset/limit
200  $numRows = count( $this->mHist );
201  if ( $numRows <= $this->mLimit && $this->mImg->getTimestamp() > $this->mOffset ) {
202  $this->mHist = array_merge( [ $this->mImg ], $this->mHist );
203  }
204  } else {
205  // The current rev may not meet the offset
206  if ( !$this->mOffset || $this->mImg->getTimestamp() < $this->mOffset ) {
207  $this->mHist[] = $this->mImg;
208  }
209  // Old image versions (fetch extra row for nav links)
210  $oiLimit = count( $this->mHist ) ? $this->mLimit : $this->mLimit + 1;
211  // Fetch the file history
212  $this->mHist = array_merge( $this->mHist,
213  $this->mImg->getHistory( $oiLimit, $this->mOffset, null, false ) );
214  }
215  $numRows = count( $this->mHist ); // Total number of query results
216  if ( $numRows ) {
217  # Index value of top item in the list
218  $firstIndex = $this->mIsBackwards ?
219  [ $this->mHist[$numRows - 1]->getTimestamp() ] : [ $this->mHist[0]->getTimestamp() ];
220  # Discard the extra result row if there is one
221  if ( $numRows > $this->mLimit && $numRows > 1 ) {
222  if ( $this->mIsBackwards ) {
223  # Index value of item past the index
224  $this->mPastTheEndIndex = [ $this->mHist[0]->getTimestamp() ];
225  # Index value of bottom item in the list
226  $lastIndex = [ $this->mHist[1]->getTimestamp() ];
227  # Display range
228  $this->mRange = [ 1, $numRows - 1 ];
229  } else {
230  # Index value of item past the index
231  $this->mPastTheEndIndex = [ $this->mHist[$numRows - 1]->getTimestamp() ];
232  # Index value of bottom item in the list
233  $lastIndex = [ $this->mHist[$numRows - 2]->getTimestamp() ];
234  # Display range
235  $this->mRange = [ 0, $numRows - 2 ];
236  }
237  } else {
238  # Setting indexes to an empty array means that they will be
239  # omitted if they would otherwise appear in URLs. It just so
240  # happens that this is the right thing to do in the standard
241  # UI, in all the relevant cases.
242  $this->mPastTheEndIndex = [];
243  # Index value of bottom item in the list
244  $lastIndex = $this->mIsBackwards ?
245  [ $this->mHist[0]->getTimestamp() ] : [ $this->mHist[$numRows - 1]->getTimestamp() ];
246  # Display range
247  $this->mRange = [ 0, $numRows - 1 ];
248  }
249  } else {
250  $firstIndex = [];
251  $lastIndex = [];
252  $this->mPastTheEndIndex = [];
253  }
254  if ( $this->mIsBackwards ) {
255  $this->mIsFirst = ( $numRows < $queryLimit );
256  $this->mIsLast = ( $this->mOffset == '' );
257  $this->mLastShown = $firstIndex;
258  $this->mFirstShown = $lastIndex;
259  } else {
260  $this->mIsFirst = ( $this->mOffset == '' );
261  $this->mIsLast = ( $numRows < $queryLimit );
262  $this->mLastShown = $lastIndex;
263  $this->mFirstShown = $firstIndex;
264  }
265  $this->mQueryDone = true;
266  }
267 
274  private function wrapWithActionButtons( $formcontents ) {
275  if ( !$this->getAuthority()->isAllowed( 'deleterevision' ) ) {
276  return $formcontents;
277  }
278 
279  # Show button to hide log entries
280  $s = Html::openElement(
281  'form',
282  [ 'action' => wfScript(), 'id' => 'mw-filehistory-deleterevision-submit' ]
283  ) . "\n";
284  $s .= Html::hidden( 'target', $this->getTitle()->getPrefixedDBkey() ) . "\n";
285  $s .= Html::hidden( 'action', 'historysubmit' ) . "\n";
286  $s .= Html::hidden( 'type', 'oldimage' ) . "\n";
287  $this->setPreventClickjacking( true );
288 
289  $buttons = Html::element(
290  'button',
291  [
292  'type' => 'submit',
293  'name' => 'revisiondelete',
294  'value' => '1',
295  'class' => "deleterevision-filehistory-submit mw-filehistory-deleterevision-button mw-ui-button"
296  ],
297  $this->msg( 'showhideselectedfileversions' )->text()
298  ) . "\n";
299 
300  $s .= $buttons . $formcontents . $buttons;
301  $s .= Html::closeElement( 'form' );
302 
303  return $s;
304  }
305 
310  protected function preventClickjacking( $enable = true ) {
311  $this->preventClickjacking = $enable;
312  }
313 
318  protected function setPreventClickjacking( bool $enable ) {
319  $this->preventClickjacking = $enable;
320  }
321 
325  public function getPreventClickjacking() {
327  }
328 
329 }
const NS_USER
Definition: Defines.php:66
const NS_USER_TALK
Definition: Defines.php:67
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
const FOR_THIS_USER
Definition: File.php:89
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.
int $mLimit
The maximum number of entries to show.
Definition: IndexPager.php:96
This class is a collection of static functions that serve two purposes:
Definition: Html.php:55
Service locator for MediaWiki core services.
Represents a title within MediaWiki.
Definition: Title.php:82
IndexPager with a formatted navigation bar.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42