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