MediaWiki master
ArchivedFile.php
Go to the documentation of this file.
1<?php
31
39
40 // Audience options for ::getDescription() and ::getUploader()
41 public const FOR_PUBLIC = 1;
42 public const FOR_THIS_USER = 2;
43 public const RAW = 3;
44
46 private const MDS_EMPTY = 'empty';
47
49 private const MDS_LEGACY = 'legacy';
50
52 private const MDS_PHP = 'php';
53
55 private const MDS_JSON = 'json';
56
58 private $id;
59
61 private $name;
62
64 private $group;
65
67 private $key;
68
70 private $size;
71
73 private $bits;
74
76 private $width;
77
79 private $height;
80
82 protected $metadataArray = [];
83
85 protected $extraDataLoaded = false;
86
94
96 protected $metadataBlobs = [];
97
104 protected $unloadedMetadataBlobs = [];
105
107 private $mime;
108
110 private $media_type;
111
113 private $description;
114
116 private $user;
117
119 private $timestamp;
120
122 private $dataLoaded;
123
125 private $deleted;
126
128 private $sha1;
129
133 private $pageCount;
134
136 private $archive_name;
137
139 protected $handler;
140
142 protected $title; # image title
143
145 protected $exists;
146
148 private $repo;
149
151 private $metadataStorageHelper;
152
160 public function __construct( $title, $id = 0, $key = '', $sha1 = '' ) {
161 $this->id = -1;
162 $this->title = null;
163 $this->name = false;
164 $this->group = 'deleted'; // needed for direct use of constructor
165 $this->key = '';
166 $this->size = 0;
167 $this->bits = 0;
168 $this->width = 0;
169 $this->height = 0;
170 $this->mime = "unknown/unknown";
171 $this->media_type = '';
172 $this->description = '';
173 $this->user = null;
174 $this->timestamp = null;
175 $this->deleted = 0;
176 $this->dataLoaded = false;
177 $this->exists = false;
178 $this->sha1 = '';
179
180 if ( $title instanceof Title ) {
181 $this->title = File::normalizeTitle( $title, 'exception' );
182 $this->name = $title->getDBkey();
183 }
184
185 if ( $id ) {
186 $this->id = $id;
187 }
188
189 if ( $key ) {
190 $this->key = $key;
191 }
192
193 if ( $sha1 ) {
194 $this->sha1 = $sha1;
195 }
196
197 if ( !$id && !$key && !( $title instanceof Title ) && !$sha1 ) {
198 throw new BadMethodCallException( "No specifications provided to ArchivedFile constructor." );
199 }
200
201 $this->repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
202 $this->metadataStorageHelper = new MetadataStorageHelper( $this->repo );
203 }
204
210 public function load() {
211 if ( $this->dataLoaded ) {
212 return true;
213 }
214 $conds = [];
215
216 if ( $this->id > 0 ) {
217 $conds['fa_id'] = $this->id;
218 }
219 if ( $this->key ) {
220 $conds['fa_storage_group'] = $this->group;
221 $conds['fa_storage_key'] = $this->key;
222 }
223 if ( $this->title ) {
224 $conds['fa_name'] = $this->title->getDBkey();
225 }
226 if ( $this->sha1 ) {
227 $conds['fa_sha1'] = $this->sha1;
228 }
229
230 if ( $conds === [] ) {
231 throw new RuntimeException( "No specific information for retrieving archived file" );
232 }
233
234 if ( !$this->title || $this->title->getNamespace() === NS_FILE ) {
235 $this->dataLoaded = true; // set it here, to have also true on miss
236 $dbr = $this->repo->getReplicaDB();
237 $queryBuilder = FileSelectQueryBuilder::newForArchivedFile( $dbr );
238 $row = $queryBuilder->where( $conds )
239 ->orderBy( 'fa_timestamp', SelectQueryBuilder::SORT_DESC )
240 ->caller( __METHOD__ )->fetchRow();
241 if ( !$row ) {
242 // this revision does not exist?
243 return null;
244 }
245
246 // initialize fields for filestore image object
247 $this->loadFromRow( $row );
248 } else {
249 throw new UnexpectedValueException( 'This title does not correspond to an image page.' );
250 }
251
252 return true;
253 }
254
262 public static function newFromRow( $row ) {
263 $file = new ArchivedFile( Title::makeTitle( NS_FILE, $row->fa_name ) );
264 $file->loadFromRow( $row );
265
266 return $file;
267 }
268
286 public static function getQueryInfo() {
287 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
288 $queryInfo = ( FileSelectQueryBuilder::newForArchivedFile( $dbr ) )->getQueryInfo();
289 return [
290 'tables' => $queryInfo['tables'],
291 'fields' => $queryInfo['fields'],
292 'joins' => $queryInfo['join_conds'],
293 ];
294 }
295
303 public function loadFromRow( $row ) {
304 $this->id = intval( $row->fa_id );
305 $this->name = $row->fa_name;
306 $this->archive_name = $row->fa_archive_name;
307 $this->group = $row->fa_storage_group;
308 $this->key = $row->fa_storage_key;
309 $this->size = $row->fa_size;
310 $this->bits = $row->fa_bits;
311 $this->width = $row->fa_width;
312 $this->height = $row->fa_height;
314 $this->repo->getReplicaDB(), $row->fa_metadata );
315 $this->mime = "$row->fa_major_mime/$row->fa_minor_mime";
316 $this->media_type = $row->fa_media_type;
317 $services = MediaWikiServices::getInstance();
318 $this->description = $services->getCommentStore()
319 // Legacy because $row may have come from self::selectFields()
320 ->getCommentLegacy( $this->repo->getReplicaDB(), 'fa_description', $row )->text;
321 $this->user = $services->getUserFactory()
322 ->newFromAnyId( $row->fa_user, $row->fa_user_text, $row->fa_actor );
323 $this->timestamp = $row->fa_timestamp;
324 $this->deleted = $row->fa_deleted;
325 if ( isset( $row->fa_sha1 ) ) {
326 $this->sha1 = $row->fa_sha1;
327 } else {
328 // old row, populate from key
329 $this->sha1 = LocalRepo::getHashFromKey( $this->key );
330 }
331 if ( !$this->title ) {
332 $this->title = Title::makeTitleSafe( NS_FILE, $row->fa_name );
333 }
334 $this->exists = $row->fa_archive_name !== '';
335 }
336
342 public function getTitle() {
343 if ( !$this->title ) {
344 $this->load();
345 }
346 return $this->title;
347 }
348
354 public function getName() {
355 if ( $this->name === false ) {
356 $this->load();
357 }
358
359 return $this->name;
360 }
361
365 public function getID() {
366 $this->load();
367
368 return $this->id;
369 }
370
374 public function exists() {
375 $this->load();
376
377 return $this->exists;
378 }
379
384 public function getKey() {
385 $this->load();
386
387 return $this->key;
388 }
389
394 public function getStorageKey() {
395 return $this->getKey();
396 }
397
402 public function getGroup() {
403 return $this->group;
404 }
405
410 public function getWidth() {
411 $this->load();
412
413 return $this->width;
414 }
415
420 public function getHeight() {
421 $this->load();
422
423 return $this->height;
424 }
425
432 public function getMetadata() {
433 $data = $this->getMetadataArray();
434 if ( !$data ) {
435 return '';
436 } elseif ( array_keys( $data ) === [ '_error' ] ) {
437 // Legacy error encoding
438 return $data['_error'];
439 } else {
440 return serialize( $this->getMetadataArray() );
441 }
442 }
443
450 public function getMetadataArray(): array {
451 $this->load();
452 if ( $this->unloadedMetadataBlobs ) {
453 return $this->getMetadataItems(
454 array_unique( array_merge(
455 array_keys( $this->metadataArray ),
456 array_keys( $this->unloadedMetadataBlobs )
457 ) )
458 );
459 }
460 return $this->metadataArray;
461 }
462
463 public function getMetadataItems( array $itemNames ): array {
464 $this->load();
465 $result = [];
466 $addresses = [];
467 foreach ( $itemNames as $itemName ) {
468 if ( array_key_exists( $itemName, $this->metadataArray ) ) {
469 $result[$itemName] = $this->metadataArray[$itemName];
470 } elseif ( isset( $this->unloadedMetadataBlobs[$itemName] ) ) {
471 $addresses[$itemName] = $this->unloadedMetadataBlobs[$itemName];
472 }
473 }
474
475 if ( $addresses ) {
476 $resultFromBlob = $this->metadataStorageHelper->getMetadataFromBlobStore( $addresses );
477 foreach ( $addresses as $itemName => $address ) {
478 unset( $this->unloadedMetadataBlobs[$itemName] );
479 $value = $resultFromBlob[$itemName] ?? null;
480 if ( $value !== null ) {
481 $result[$itemName] = $value;
482 $this->metadataArray[$itemName] = $value;
483 }
484 }
485 }
486 return $result;
487 }
488
500 public function getMetadataForDb( IDatabase $db ) {
501 $this->load();
502 if ( !$this->metadataArray && !$this->metadataBlobs ) {
503 $s = '';
504 } elseif ( $this->repo->isJsonMetadataEnabled() ) {
505 $s = $this->getJsonMetadata();
506 } else {
507 $s = serialize( $this->getMetadataArray() );
508 }
509 if ( !is_string( $s ) ) {
510 throw new RuntimeException( 'Could not serialize image metadata value for DB' );
511 }
512 return $db->encodeBlob( $s );
513 }
514
521 private function getJsonMetadata() {
522 // Directly store data that is not already in BlobStore
523 $envelope = [
524 'data' => array_diff_key( $this->metadataArray, $this->metadataBlobs )
525 ];
526
527 // Also store the blob addresses
528 if ( $this->metadataBlobs ) {
529 $envelope['blobs'] = $this->metadataBlobs;
530 }
531
532 [ $s, $blobAddresses ] = $this->metadataStorageHelper->getJsonMetadata( $this, $envelope );
533
534 // Repeated calls to this function should not keep inserting more blobs
535 $this->metadataBlobs += $blobAddresses;
536
537 return $s;
538 }
539
548 protected function loadMetadataFromDbFieldValue( IReadableDatabase $db, $metadataBlob ) {
549 $this->loadMetadataFromString( $db->decodeBlob( $metadataBlob ) );
550 }
551
559 protected function loadMetadataFromString( $metadataString ) {
560 $this->extraDataLoaded = true;
561 $this->metadataArray = [];
562 $this->metadataBlobs = [];
563 $this->unloadedMetadataBlobs = [];
564 $metadataString = (string)$metadataString;
565 if ( $metadataString === '' ) {
566 $this->metadataSerializationFormat = self::MDS_EMPTY;
567 return;
568 }
569 if ( $metadataString[0] === '{' ) {
570 $envelope = $this->metadataStorageHelper->jsonDecode( $metadataString );
571 if ( !$envelope ) {
572 // Legacy error encoding
573 $this->metadataArray = [ '_error' => $metadataString ];
574 $this->metadataSerializationFormat = self::MDS_LEGACY;
575 } else {
576 $this->metadataSerializationFormat = self::MDS_JSON;
577 if ( isset( $envelope['data'] ) ) {
578 $this->metadataArray = $envelope['data'];
579 }
580 if ( isset( $envelope['blobs'] ) ) {
581 $this->metadataBlobs = $this->unloadedMetadataBlobs = $envelope['blobs'];
582 }
583 }
584 } else {
585 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
586 $data = @unserialize( $metadataString );
587 if ( !is_array( $data ) ) {
588 // Legacy error encoding
589 $data = [ '_error' => $metadataString ];
590 $this->metadataSerializationFormat = self::MDS_LEGACY;
591 } else {
592 $this->metadataSerializationFormat = self::MDS_PHP;
593 }
594 $this->metadataArray = $data;
595 }
596 }
597
602 public function getSize() {
603 $this->load();
604
605 return $this->size;
606 }
607
612 public function getBits() {
613 $this->load();
614
615 return $this->bits;
616 }
617
622 public function getMimeType() {
623 $this->load();
624
625 return $this->mime;
626 }
627
632 private function getHandler() {
633 if ( !isset( $this->handler ) ) {
634 $this->handler = MediaHandler::getHandler( $this->getMimeType() );
635 }
636
637 return $this->handler;
638 }
639
646 public function pageCount() {
647 if ( !isset( $this->pageCount ) ) {
648 // @FIXME: callers expect File objects
649 // @phan-suppress-next-line PhanTypeMismatchArgument
650 if ( $this->getHandler() && $this->handler->isMultiPage( $this ) ) {
651 // @phan-suppress-next-line PhanTypeMismatchArgument
652 $this->pageCount = $this->handler->pageCount( $this );
653 } else {
654 $this->pageCount = false;
655 }
656 }
657
658 return $this->pageCount;
659 }
660
666 public function getMediaType() {
667 $this->load();
668
669 return $this->media_type;
670 }
671
677 public function getTimestamp() {
678 $this->load();
679
680 return wfTimestamp( TS_MW, $this->timestamp );
681 }
682
689 public function getSha1() {
690 $this->load();
691
692 return $this->sha1;
693 }
694
706 public function getUploader( int $audience = self::FOR_PUBLIC, Authority $performer = null ): ?UserIdentity {
707 $this->load();
708 if ( $audience === self::FOR_PUBLIC && $this->isDeleted( File::DELETED_USER ) ) {
709 return null;
710 } elseif ( $audience === self::FOR_THIS_USER && !$this->userCan( File::DELETED_USER, $performer ) ) {
711 return null;
712 } else {
713 return $this->user;
714 }
715 }
716
729 public function getDescription( int $audience = self::FOR_PUBLIC, Authority $performer = null ): string {
730 $this->load();
731 if ( $audience === self::FOR_PUBLIC && $this->isDeleted( File::DELETED_COMMENT ) ) {
732 return '';
733 } elseif ( $audience === self::FOR_THIS_USER && !$this->userCan( File::DELETED_COMMENT, $performer ) ) {
734 return '';
735 } else {
736 return $this->description;
737 }
738 }
739
744 public function getVisibility() {
745 $this->load();
746
747 return $this->deleted;
748 }
749
756 public function isDeleted( $field ) {
757 $this->load();
758
759 return ( $this->deleted & $field ) == $field;
760 }
761
769 public function userCan( $field, Authority $performer ) {
770 $this->load();
771 $title = $this->getTitle();
772
773 return RevisionRecord::userCanBitfield(
774 $this->deleted,
775 $field,
776 $performer,
777 $title ?: null
778 );
779 }
780}
const NS_FILE
Definition Defines.php:70
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Deleted file in the 'filearchive' table.
getTimestamp()
Return upload timestamp.
getSize()
Return the size of the image file, in bytes.
MediaHandler $handler
isDeleted( $field)
for file or revision rows
getHeight()
Return the height of the image.
string[] $metadataBlobs
Map of metadata item name to blob address.
loadMetadataFromDbFieldValue(IReadableDatabase $db, $metadataBlob)
Unserialize a metadata blob which came from the database and store it in $this.
getBits()
Return the bits of the image file, in bytes.
array $metadataArray
Unserialized metadata.
const FOR_THIS_USER
getMetadataForDb(IDatabase $db)
Serialize the metadata array for insertion into img_metadata, oi_metadata or fa_metadata.
string[] $unloadedMetadataBlobs
Map of metadata item name to blob address for items that exist but have not yet been loaded into $thi...
getSha1()
Get the SHA-1 base 36 hash of the file.
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new archivedfile object.
loadFromRow( $row)
Load ArchivedFile object fields from a DB row.
string null $metadataSerializationFormat
One of the MDS_* constants, giving the format of the metadata as stored in the DB,...
getMetadataItems(array $itemNames)
__construct( $title, $id=0, $key='', $sha1='')
getUploader(int $audience=self::FOR_PUBLIC, Authority $performer=null)
load()
Loads a file object from the filearchive table.
pageCount()
Returns the number of pages of a multipage document, or false for documents which aren't multipage do...
getGroup()
Return the FileStore storage group.
static newFromRow( $row)
Loads a file object from the filearchive table.
getTitle()
Return the associated title object.
Title null $title
getWidth()
Return the width of the image.
getStorageKey()
Return the FileStore key (overriding base File class)
getDescription(int $audience=self::FOR_PUBLIC, Authority $performer=null)
Return upload description.
getMimeType()
Returns the MIME type of the file.
getKey()
Return the FileStore key.
userCan( $field, Authority $performer)
Determine if the current user is allowed to view a particular field of this FileStore image file,...
getMetadataArray()
Get unserialized handler-specific metadata.
loadMetadataFromString( $metadataString)
Unserialize a metadata string which came from some non-DB source, or is the return value of IDatabase...
getName()
Return the file name.
getMediaType()
Return the type of the media in the file.
getVisibility()
Returns the deletion bitfield.
getMetadata()
Get handler-specific metadata as a serialized string.
bool $extraDataLoaded
Whether or not lazy-loaded data has been loaded from the database.
Local repository that stores files in the local filesystem and registers them in the wiki's own datab...
Definition LocalRepo.php:49
Base media handler class.
Service locator for MediaWiki core services.
Page revision base class.
Represents a title within MediaWiki.
Definition Title.php:78
getDBkey()
Get the main part with underscores.
Definition Title.php:1035
Helper for storage of metadata.
Build SELECT queries with a fluent interface.
This interface represents the authority associated with the current execution context,...
Definition Authority.php:37
Interface for objects representing user identity.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:36
A database connection without write operations.
encodeBlob( $b)
Some DBMSs have a special format for inserting into blob fields, they don't allow simple quoted strin...
decodeBlob( $b)
Some DBMSs return a special placeholder object representing blob fields in result objects.