MediaWiki  master
ImageHistoryList.php
Go to the documentation of this file.
1 <?php
21 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
27 
34  use ProtectedHookAccessorTrait;
35 
39  protected $title;
40 
44  protected $img;
45 
49  protected $imagePage;
50 
54  protected $current;
55 
56  protected $repo, $showThumb;
57  protected $preventClickjacking = false;
58 
62  public function __construct( $imagePage ) {
63  $context = $imagePage->getContext();
64  $this->current = $imagePage->getPage()->getFile();
65  $this->img = $imagePage->getDisplayedFile();
66  $this->title = $imagePage->getTitle();
67  $this->imagePage = $imagePage;
68  $this->showThumb = $context->getConfig()->get( MainConfigNames::ShowArchiveThumbnails ) &&
69  $this->img->canRender();
70  $this->setContext( $context );
71  }
72 
76  public function getImagePage() {
77  return $this->imagePage;
78  }
79 
83  public function getFile() {
84  return $this->img;
85  }
86 
90  public function beginImageHistoryList() {
91  // Styles for class=history-deleted
92  $this->getOutput()->addModuleStyles( 'mediawiki.interface.helpers.styles' );
93 
94  $html = '';
95  $canDelete = $this->current->isLocal() &&
96  $this->getAuthority()->isAllowedAny( 'delete', 'deletedhistory' );
97 
98  foreach ( [
99  '',
100  $canDelete ? '' : null,
101  'filehist-datetime',
102  $this->showThumb ? 'filehist-thumb' : null,
103  'filehist-dimensions',
104  'filehist-user',
105  'filehist-comment',
106  ] as $key ) {
107  if ( $key !== null ) {
108  $html .= Html::element( 'th', [], $key ? $this->msg( $key )->text() : '' );
109  }
110  }
111 
112  return Html::openElement( 'table', [ 'class' => 'wikitable filehistory' ] ) . "\n"
113  . Html::rawElement( 'tr', [], $html ) . "\n";
114  }
115 
119  public function endImageHistoryList() {
120  return Html::closeElement( 'table' ) . "\n";
121  }
122 
130  public function imageHistoryLine( $iscur, $file, $formattedComment ) {
131  $user = $this->getUser();
132  $lang = $this->getLanguage();
133  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
134  $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() );
135  // @phan-suppress-next-line PhanUndeclaredMethod
136  $img = $iscur ? $file->getName() : $file->getArchiveName();
137  $uploader = $file->getUploader( File::FOR_THIS_USER, $user );
138 
139  $local = $this->current->isLocal();
140  $row = '';
141 
142  // Deletion link
143  if ( $local && ( $this->getAuthority()->isAllowedAny( 'delete', 'deletedhistory' ) ) ) {
144  $row .= Html::openElement( 'td' );
145  # Link to hide content. Don't show useless link to people who cannot hide revisions.
146  if ( !$iscur && $this->getAuthority()->isAllowed( 'deleterevision' ) ) {
147  // If file is top revision, is missing or locked from this user, don't link
148  if ( !$file->userCan( File::DELETED_RESTRICTED, $user ) || !$file->exists() ) {
149  $row .= Html::check( 'deleterevisions', false, [ 'disabled' => 'disabled' ] );
150  } else {
151  $row .= Html::check( 'ids[' . explode( '!', $img, 2 )[0] . ']', false );
152  }
153  if ( $this->getAuthority()->isAllowed( 'delete' ) ) {
154  $row .= ' ';
155  }
156  }
157  # Link to remove from history
158  if ( $this->getAuthority()->isAllowed( 'delete' ) ) {
159  if ( $file->exists() ) {
160  $row .= $linkRenderer->makeKnownLink(
161  $this->title,
162  $this->msg( $iscur ? 'filehist-deleteall' : 'filehist-deleteone' )->text(),
163  [],
164  [ 'action' => 'delete', 'oldimage' => $iscur ? null : $img ]
165  );
166  } else {
167  // T244567: Non-existing file can not be deleted.
168  $row .= $this->msg( 'filehist-missing' )->escaped();
169  }
170 
171  }
172  $row .= Html::closeElement( 'td' );
173  }
174 
175  // Reversion link/current indicator
176  $row .= Html::openElement( 'td' );
177  if ( $iscur ) {
178  $row .= $this->msg( 'filehist-current' )->escaped();
179  } elseif ( $local && $this->getAuthority()->probablyCan( 'edit', $this->title )
180  && $this->getAuthority()->probablyCan( 'upload', $this->title )
181  ) {
182  if ( $file->isDeleted( File::DELETED_FILE ) ) {
183  $row .= $this->msg( 'filehist-revert' )->escaped();
184  } elseif ( !$file->exists() ) {
185  // T328112: Lost file, in this case there's no version to revert back to.
186  $row .= $this->msg( 'filehist-missing' )->escaped();
187  } else {
188  $row .= $linkRenderer->makeKnownLink(
189  $this->title,
190  $this->msg( 'filehist-revert' )->text(),
191  [],
192  [
193  'action' => 'revert',
194  'oldimage' => $img,
195  ]
196  );
197  }
198  }
199  $row .= Html::closeElement( 'td' );
200 
201  // Date/time and image link
202  $selected = $file->getTimestamp() === $this->img->getTimestamp();
203  $row .= Html::openElement( 'td', [
204  'class' => $selected ? 'filehistory-selected' : null,
205  'style' => 'white-space: nowrap;'
206  ] );
207  if ( !$file->userCan( File::DELETED_FILE, $user ) ) {
208  # Don't link to unviewable files
209  $row .= Html::element( 'span', [ 'class' => 'history-deleted' ],
210  $lang->userTimeAndDate( $timestamp, $user )
211  );
212  } elseif ( $file->isDeleted( File::DELETED_FILE ) ) {
213  $timeAndDate = $lang->userTimeAndDate( $timestamp, $user );
214  if ( $local ) {
215  $this->setPreventClickjacking( true );
216  # Make a link to review the image
217  $url = $linkRenderer->makeKnownLink(
218  SpecialPage::getTitleFor( 'Revisiondelete' ),
219  $timeAndDate,
220  [],
221  [
222  'target' => $this->title->getPrefixedText(),
223  'file' => $img,
224  'token' => $user->getEditToken( $img )
225  ]
226  );
227  } else {
228  $url = htmlspecialchars( $timeAndDate );
229  }
230  $row .= Html::rawElement( 'span', [ 'class' => 'history-deleted' ], $url );
231  } elseif ( !$file->exists() ) {
232  $row .= Html::element( 'span', [ 'class' => 'mw-file-missing' ],
233  $lang->userTimeAndDate( $timestamp, $user )
234  );
235  } else {
236  $url = $iscur ? $this->current->getUrl() : $this->current->getArchiveUrl( $img );
237  $row .= Html::element( 'a', [ 'href' => $url ],
238  $lang->userTimeAndDate( $timestamp, $user )
239  );
240  }
241  $row .= Html::closeElement( 'td' );
242 
243  // Thumbnail
244  if ( $this->showThumb ) {
245  $row .= Html::rawElement( 'td', [],
246  $this->getThumbForLine( $file, $iscur ) ?? $this->msg( 'filehist-nothumb' )->escaped()
247  );
248  }
249 
250  // Image dimensions + size
251  $row .= Html::openElement( 'td' );
252  $row .= htmlspecialchars( $file->getDimensionsString() );
253  $row .= $this->msg( 'word-separator' )->escaped();
254  $row .= Html::element( 'span', [ 'style' => 'white-space: nowrap;' ],
255  $this->msg( 'parentheses' )->sizeParams( $file->getSize() )->text()
256  );
257  $row .= Html::closeElement( 'td' );
258 
259  // Uploading user
260  $row .= Html::openElement( 'td' );
261  // Hide deleted usernames
262  if ( $uploader && $local ) {
263  $row .= Linker::userLink( $uploader->getId(), $uploader->getName() );
264  $row .= Html::rawElement( 'span', [ 'style' => 'white-space: nowrap;' ],
265  Linker::userToolLinks( $uploader->getId(), $uploader->getName() )
266  );
267  } elseif ( $uploader ) {
268  $row .= htmlspecialchars( $uploader->getName() );
269  } else {
270  $row .= Html::element( 'span', [ 'class' => 'history-deleted' ],
271  $this->msg( 'rev-deleted-user' )->text()
272  );
273  }
274  $row .= Html::closeElement( 'td' );
275 
276  // Don't show deleted descriptions
277  if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
278  $row .= Html::rawElement( 'td', [],
279  Html::element( 'span', [ 'class' => 'history-deleted' ],
280  $this->msg( 'rev-deleted-comment' )->text()
281  )
282  );
283  } else {
284  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
285  $row .= Html::rawElement( 'td', [ 'dir' => $contLang->getDir() ], $formattedComment );
286  }
287 
288  $rowClass = null;
289  $this->getHookRunner()->onImagePageFileHistoryLine( $this, $file, $row, $rowClass );
290 
291  return Html::rawElement( 'tr', [ 'class' => $rowClass ], $row ) . "\n";
292  }
293 
299  protected function getThumbForLine( $file, $iscur ) {
300  $user = $this->getUser();
301  if ( !$file->allowInlineDisplay() ||
302  $file->isDeleted( File::DELETED_FILE ) ||
303  !$file->userCan( File::DELETED_FILE, $user )
304  ) {
305  return null;
306  }
307 
308  $thumbnail = $file->transform(
309  [
310  'width' => '120',
311  'height' => '120',
312  'loading' => 'lazy',
313  'isFilePageThumb' => $iscur // old revisions are already versioned
314  ]
315  );
316  if ( !$thumbnail ) {
317  return null;
318  }
319 
320  $lang = $this->getLanguage();
321  $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() );
322  $alt = $this->msg(
323  'filehist-thumbtext',
324  $lang->userTimeAndDate( $timestamp, $user ),
325  $lang->userDate( $timestamp, $user ),
326  $lang->userTime( $timestamp, $user )
327  )->text();
328  return $thumbnail->toHtml( [ 'alt' => $alt, 'file-link' => true ] );
329  }
330 
335  protected function preventClickjacking( $enable = true ) {
336  $this->preventClickjacking = $enable;
337  }
338 
343  protected function setPreventClickjacking( bool $enable ) {
344  $this->preventClickjacking = $enable;
345  }
346 
350  public function getPreventClickjacking() {
352  }
353 }
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
getContext()
Gets the context this Article is executed in.
Definition: Article.php:1971
getTitle()
Get the title object of the article.
Definition: Article.php:243
getPage()
Get the WikiPage object of this instance.
Definition: Article.php:253
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
setContext(IContextSource $context)
const DELETED_COMMENT
Definition: File.php:73
const DELETED_RESTRICTED
Definition: File.php:75
const DELETED_FILE
Definition: File.php:72
const FOR_THIS_USER
Definition: File.php:89
Builds the image revision log shown on image pages.
preventClickjacking( $enable=true)
getThumbForLine( $file, $iscur)
setPreventClickjacking(bool $enable)
imageHistoryLine( $iscur, $file, $formattedComment)
__construct( $imagePage)
getDisplayedFile()
Definition: ImagePage.php:230
This class is a collection of static functions that serve two purposes:
Definition: Html.php:55
Some internal bits split of from Skin.php.
Definition: Linker.php:65
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
Represents a title within MediaWiki.
Definition: Title.php:82
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,...
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
if(!isset( $args[0])) $lang