MediaWiki  master
ImageHistoryList.php
Go to the documentation of this file.
1 <?php
21 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
24 
31  use ProtectedHookAccessorTrait;
32 
36  protected $title;
37 
41  protected $img;
42 
46  protected $imagePage;
47 
51  protected $current;
52 
53  protected $repo, $showThumb;
54  protected $preventClickjacking = false;
55 
59  public function __construct( $imagePage ) {
61  $this->current = $imagePage->getPage()->getFile();
62  $this->img = $imagePage->getDisplayedFile();
63  $this->title = $imagePage->getTitle();
64  $this->imagePage = $imagePage;
65  $this->showThumb = $context->getConfig()->get( MainConfigNames::ShowArchiveThumbnails ) &&
66  $this->img->canRender();
67  $this->setContext( $context );
68  }
69 
73  public function getImagePage() {
74  return $this->imagePage;
75  }
76 
80  public function getFile() {
81  return $this->img;
82  }
83 
88  public function beginImageHistoryList( $navLinks = '' ) {
89  // Styles for class=history-deleted
90  $this->getOutput()->addModuleStyles( 'mediawiki.interface.helpers.styles' );
91 
92  $html = '';
93  $canDelete = $this->current->isLocal() &&
94  $this->getAuthority()->isAllowedAny( 'delete', 'deletedhistory' );
95 
96  foreach ( [
97  '',
98  $canDelete ? '' : null,
99  'filehist-datetime',
100  $this->showThumb ? 'filehist-thumb' : null,
101  'filehist-dimensions',
102  'filehist-user',
103  'filehist-comment',
104  ] as $key ) {
105  if ( $key !== null ) {
106  $html .= Html::element( 'th', [], $key ? $this->msg( $key )->text() : '' );
107  }
108  }
109 
110  return Html::element( 'h2', [ 'id' => 'filehistory' ], $this->msg( 'filehist' )->text() )
111  . "\n"
112  . Html::openElement( 'div', [ 'id' => 'mw-imagepage-section-filehistory' ] ) . "\n"
113  . $this->msg( 'filehist-help' )->parseAsBlock()
114  . $navLinks . "\n"
115  . Html::openElement( 'table', [ 'class' => 'wikitable filehistory' ] ) . "\n"
116  . Html::rawElement( 'tr', [], $html ) . "\n";
117  }
118 
123  public function endImageHistoryList( $navLinks = '' ) {
124  return Html::closeElement( 'table' ) . "\n" .
125  $navLinks . "\n" .
126  Html::closeElement( 'div' ) . "\n";
127  }
128 
136  public function imageHistoryLine( $iscur, $file, $formattedComment ) {
137  $user = $this->getUser();
138  $lang = $this->getLanguage();
139  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
140  $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() );
141  // @phan-suppress-next-line PhanUndeclaredMethod
142  $img = $iscur ? $file->getName() : $file->getArchiveName();
143  $uploader = $file->getUploader( File::FOR_THIS_USER, $user );
144 
145  $local = $this->current->isLocal();
146  $row = '';
147 
148  // Deletion link
149  if ( $local && ( $this->getAuthority()->isAllowedAny( 'delete', 'deletedhistory' ) ) ) {
150  $row .= Html::openElement( 'td' );
151  # Link to remove from history
152  if ( $this->getAuthority()->isAllowed( 'delete' ) ) {
153  $row .= $linkRenderer->makeKnownLink(
154  $this->title,
155  $this->msg( $iscur ? 'filehist-deleteall' : 'filehist-deleteone' )->text(),
156  [],
157  [ 'action' => 'delete', 'oldimage' => $iscur ? null : $img ]
158  );
159  }
160  # Link to hide content. Don't show useless link to people who cannot hide revisions.
161  $canHide = $this->getAuthority()->isAllowed( 'deleterevision' );
162  if ( $canHide || ( $this->getAuthority()->isAllowed( 'deletedhistory' )
163  && $file->getVisibility() ) ) {
164  if ( $this->getAuthority()->isAllowed( 'delete' ) ) {
165  $row .= Html::element( 'br' );
166  }
167  // If file is top revision or locked from this user, don't link
168  if ( $iscur || !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
169  $row .= Linker::revDeleteLinkDisabled( $canHide );
170  } else {
171  $row .= Linker::revDeleteLink(
172  [
173  'type' => 'oldimage',
174  'target' => $this->title->getPrefixedText(),
175  'ids' => explode( '!', $img, 2 )[0],
176  ],
177  $file->isDeleted( File::DELETED_RESTRICTED ),
178  $canHide
179  );
180  }
181  }
182  $row .= Html::closeElement( 'td' );
183  }
184 
185  // Reversion link/current indicator
186  $row .= Html::openElement( 'td' );
187  if ( $iscur ) {
188  $row .= $this->msg( 'filehist-current' )->escaped();
189  } elseif ( $local && $this->getAuthority()->probablyCan( 'edit', $this->title )
190  && $this->getAuthority()->probablyCan( 'upload', $this->title )
191  ) {
192  if ( $file->isDeleted( File::DELETED_FILE ) ) {
193  $row .= $this->msg( 'filehist-revert' )->escaped();
194  } else {
195  $row .= $linkRenderer->makeKnownLink(
196  $this->title,
197  $this->msg( 'filehist-revert' )->text(),
198  [],
199  [
200  'action' => 'revert',
201  'oldimage' => $img,
202  ]
203  );
204  }
205  }
206  $row .= Html::closeElement( 'td' );
207 
208  // Date/time and image link
209  $selected = $file->getTimestamp() === $this->img->getTimestamp();
210  $row .= Html::openElement( 'td', [
211  'class' => $selected ? 'filehistory-selected' : null,
212  'style' => 'white-space: nowrap;'
213  ] );
214  if ( !$file->userCan( File::DELETED_FILE, $user ) ) {
215  # Don't link to unviewable files
216  $row .= Html::element( 'span', [ 'class' => 'history-deleted' ],
217  $lang->userTimeAndDate( $timestamp, $user )
218  );
219  } elseif ( $file->isDeleted( File::DELETED_FILE ) ) {
220  $timeAndDate = $lang->userTimeAndDate( $timestamp, $user );
221  if ( $local ) {
222  $this->setPreventClickjacking( true );
223  # Make a link to review the image
224  $url = $linkRenderer->makeKnownLink(
225  SpecialPage::getTitleFor( 'Revisiondelete' ),
226  $timeAndDate,
227  [],
228  [
229  'target' => $this->title->getPrefixedText(),
230  'file' => $img,
231  'token' => $user->getEditToken( $img )
232  ]
233  );
234  } else {
235  $url = htmlspecialchars( $timeAndDate );
236  }
237  $row .= Html::rawElement( 'span', [ 'class' => 'history-deleted' ], $url );
238  } elseif ( !$file->exists() ) {
239  $row .= Html::element( 'span', [ 'class' => 'mw-file-missing' ],
240  $lang->userTimeAndDate( $timestamp, $user )
241  );
242  } else {
243  $url = $iscur ? $this->current->getUrl() : $this->current->getArchiveUrl( $img );
244  $row .= Html::element( 'a', [ 'href' => $url ],
245  $lang->userTimeAndDate( $timestamp, $user )
246  );
247  }
248  $row .= Html::closeElement( 'td' );
249 
250  // Thumbnail
251  if ( $this->showThumb ) {
252  $row .= Html::rawElement( 'td', [],
253  $this->getThumbForLine( $file, $iscur ) ?? $this->msg( 'filehist-nothumb' )->escaped()
254  );
255  }
256 
257  // Image dimensions + size
258  $row .= Html::openElement( 'td' );
259  $row .= htmlspecialchars( $file->getDimensionsString() );
260  $row .= $this->msg( 'word-separator' )->escaped();
261  $row .= Html::element( 'span', [ 'style' => 'white-space: nowrap;' ],
262  $this->msg( 'parentheses' )->sizeParams( $file->getSize() )->text()
263  );
264  $row .= Html::closeElement( 'td' );
265 
266  // Uploading user
267  $row .= Html::openElement( 'td' );
268  // Hide deleted usernames
269  if ( $uploader && $local ) {
270  $row .= Linker::userLink( $uploader->getId(), $uploader->getName() );
271  $row .= Html::rawElement( 'span', [ 'style' => 'white-space: nowrap;' ],
272  Linker::userToolLinks( $uploader->getId(), $uploader->getName() )
273  );
274  } elseif ( $uploader ) {
275  $row .= htmlspecialchars( $uploader->getName() );
276  } else {
277  $row .= Html::element( 'span', [ 'class' => 'history-deleted' ],
278  $this->msg( 'rev-deleted-user' )->text()
279  );
280  }
281  $row .= Html::closeElement( 'td' );
282 
283  // Don't show deleted descriptions
284  if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
285  $row .= Html::rawElement( 'td', [],
286  Html::element( 'span', [ 'class' => 'history-deleted' ],
287  $this->msg( 'rev-deleted-comment' )->text()
288  )
289  );
290  } else {
291  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
292  $row .= Html::rawElement( 'td', [ 'dir' => $contLang->getDir() ], $formattedComment );
293  }
294 
295  $rowClass = null;
296  $this->getHookRunner()->onImagePageFileHistoryLine( $this, $file, $row, $rowClass );
297 
298  return Html::rawElement( 'tr', [ 'class' => $rowClass ], $row ) . "\n";
299  }
300 
306  protected function getThumbForLine( $file, $iscur ) {
307  $user = $this->getUser();
308  if ( !$file->allowInlineDisplay() ||
309  $file->isDeleted( File::DELETED_FILE ) ||
310  !$file->userCan( File::DELETED_FILE, $user )
311  ) {
312  return null;
313  }
314 
315  $thumbnail = $file->transform(
316  [
317  'width' => '120',
318  'height' => '120',
319  'isFilePageThumb' => $iscur // old revisions are already versioned
320  ]
321  );
322  if ( !$thumbnail ) {
323  return null;
324  }
325 
326  $lang = $this->getLanguage();
327  $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() );
328  $alt = $this->msg(
329  'filehist-thumbtext',
330  $lang->userTimeAndDate( $timestamp, $user ),
331  $lang->userDate( $timestamp, $user ),
332  $lang->userTime( $timestamp, $user )
333  )->text();
334  return $thumbnail->toHtml( [ 'alt' => $alt, 'file-link' => true ] );
335  }
336 
341  protected function preventClickjacking( $enable = true ) {
342  $this->preventClickjacking = $enable;
343  }
344 
349  protected function setPreventClickjacking( bool $enable ) {
350  $this->preventClickjacking = $enable;
351  }
352 
356  public function getPreventClickjacking() {
358  }
359 }
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:1993
getTitle()
Get the title object of the article.
Definition: Article.php:223
getPage()
Get the WikiPage object of this instance.
Definition: Article.php:233
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()
IContextSource $context
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
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:236
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:214
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition: Html.php:256
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:320
Builds the image revision log shown on image pages.
endImageHistoryList( $navLinks='')
beginImageHistoryList( $navLinks='')
preventClickjacking( $enable=true)
getThumbForLine( $file, $iscur)
setPreventClickjacking(bool $enable)
imageHistoryLine( $iscur, $file, $formattedComment)
__construct( $imagePage)
getDisplayedFile()
Definition: ImagePage.php:229
static userLink( $userId, $userName, $altUserName=false)
Make user link (or user contributions for unregistered users)
Definition: Linker.php:1069
static revDeleteLinkDisabled( $delete=true)
Creates a dead (show/hide) link for deleting revisions/log entries.
Definition: Linker.php:2188
static userToolLinks( $userId, $userText, $redContribsWhenNoEdits=false, $flags=0, $edits=null, $useParentheses=true)
Generate standard user tool links (talk, contributions, block link, etc.)
Definition: Linker.php:1114
static revDeleteLink( $query=[], $restricted=false, $delete=true)
Creates a (show/hide) link for deleting revisions/log entries.
Definition: Linker.php:2164
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
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,...
getConfig()
Get the site configuration.
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