Merge "Use {{int:}} on MediaWiki:Blockedtext and MediaWiki:Autoblockedtext"
[lhc/web/wiklou.git] / includes / filerepo / file / ArchivedFile.php
1 <?php
2 /**
3 * Deleted file in the 'filearchive' table.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup FileAbstraction
22 */
23
24 use MediaWiki\MediaWikiServices;
25
26 /**
27 * Class representing a row of the 'filearchive' table
28 *
29 * @ingroup FileAbstraction
30 */
31 class ArchivedFile {
32 /** @var int Filearchive row ID */
33 private $id;
34
35 /** @var string File name */
36 private $name;
37
38 /** @var string FileStore storage group */
39 private $group;
40
41 /** @var string FileStore SHA-1 key */
42 private $key;
43
44 /** @var int File size in bytes */
45 private $size;
46
47 /** @var int Size in bytes */
48 private $bits;
49
50 /** @var int Width */
51 private $width;
52
53 /** @var int Height */
54 private $height;
55
56 /** @var string Metadata string */
57 private $metadata;
58
59 /** @var string MIME type */
60 private $mime;
61
62 /** @var string Media type */
63 private $media_type;
64
65 /** @var string Upload description */
66 private $description;
67
68 /** @var User|null Uploader */
69 private $user;
70
71 /** @var string Time of upload */
72 private $timestamp;
73
74 /** @var bool Whether or not all this has been loaded from the database (loadFromXxx) */
75 private $dataLoaded;
76
77 /** @var int Bitfield akin to rev_deleted */
78 private $deleted;
79
80 /** @var string SHA-1 hash of file content */
81 private $sha1;
82
83 /** @var int|false Number of pages of a multipage document, or false for
84 * documents which aren't multipage documents
85 */
86 private $pageCount;
87
88 /** @var string Original base filename */
89 private $archive_name;
90
91 /** @var MediaHandler */
92 protected $handler;
93
94 /** @var Title */
95 protected $title; # image title
96
97 /**
98 * @throws MWException
99 * @param Title $title
100 * @param int $id
101 * @param string $key
102 * @param string $sha1
103 */
104 function __construct( $title, $id = 0, $key = '', $sha1 = '' ) {
105 $this->id = -1;
106 $this->title = false;
107 $this->name = false;
108 $this->group = 'deleted'; // needed for direct use of constructor
109 $this->key = '';
110 $this->size = 0;
111 $this->bits = 0;
112 $this->width = 0;
113 $this->height = 0;
114 $this->metadata = '';
115 $this->mime = "unknown/unknown";
116 $this->media_type = '';
117 $this->description = '';
118 $this->user = null;
119 $this->timestamp = null;
120 $this->deleted = 0;
121 $this->dataLoaded = false;
122 $this->exists = false;
123 $this->sha1 = '';
124
125 if ( $title instanceof Title ) {
126 $this->title = File::normalizeTitle( $title, 'exception' );
127 $this->name = $title->getDBkey();
128 }
129
130 if ( $id ) {
131 $this->id = $id;
132 }
133
134 if ( $key ) {
135 $this->key = $key;
136 }
137
138 if ( $sha1 ) {
139 $this->sha1 = $sha1;
140 }
141
142 if ( !$id && !$key && !( $title instanceof Title ) && !$sha1 ) {
143 throw new MWException( "No specifications provided to ArchivedFile constructor." );
144 }
145 }
146
147 /**
148 * Loads a file object from the filearchive table
149 * @throws MWException
150 * @return bool|null True on success or null
151 */
152 public function load() {
153 if ( $this->dataLoaded ) {
154 return true;
155 }
156 $conds = [];
157
158 if ( $this->id > 0 ) {
159 $conds['fa_id'] = $this->id;
160 }
161 if ( $this->key ) {
162 $conds['fa_storage_group'] = $this->group;
163 $conds['fa_storage_key'] = $this->key;
164 }
165 if ( $this->title ) {
166 $conds['fa_name'] = $this->title->getDBkey();
167 }
168 if ( $this->sha1 ) {
169 $conds['fa_sha1'] = $this->sha1;
170 }
171
172 if ( !count( $conds ) ) {
173 throw new MWException( "No specific information for retrieving archived file" );
174 }
175
176 if ( !$this->title || $this->title->getNamespace() == NS_FILE ) {
177 $this->dataLoaded = true; // set it here, to have also true on miss
178 $dbr = wfGetDB( DB_REPLICA );
179 $fileQuery = self::getQueryInfo();
180 $row = $dbr->selectRow(
181 $fileQuery['tables'],
182 $fileQuery['fields'],
183 $conds,
184 __METHOD__,
185 [ 'ORDER BY' => 'fa_timestamp DESC' ],
186 $fileQuery['joins']
187 );
188 if ( !$row ) {
189 // this revision does not exist?
190 return null;
191 }
192
193 // initialize fields for filestore image object
194 $this->loadFromRow( $row );
195 } else {
196 throw new MWException( 'This title does not correspond to an image page.' );
197 }
198 $this->exists = true;
199
200 return true;
201 }
202
203 /**
204 * Loads a file object from the filearchive table
205 *
206 * @param stdClass $row
207 * @return ArchivedFile
208 */
209 public static function newFromRow( $row ) {
210 $file = new ArchivedFile( Title::makeTitle( NS_FILE, $row->fa_name ) );
211 $file->loadFromRow( $row );
212
213 return $file;
214 }
215
216 /**
217 * Fields in the filearchive table
218 * @deprecated since 1.31, use self::getQueryInfo() instead.
219 * @return string[]
220 */
221 static function selectFields() {
222 global $wgActorTableSchemaMigrationStage;
223
224 if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
225 // If code is using this instead of self::getQueryInfo(), there's a
226 // decent chance it's going to try to directly access
227 // $row->fa_user or $row->fa_user_text and we can't give it
228 // useful values here once those aren't being written anymore.
229 throw new BadMethodCallException(
230 'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
231 );
232 }
233
234 wfDeprecated( __METHOD__, '1.31' );
235 return [
236 'fa_id',
237 'fa_name',
238 'fa_archive_name',
239 'fa_storage_key',
240 'fa_storage_group',
241 'fa_size',
242 'fa_bits',
243 'fa_width',
244 'fa_height',
245 'fa_metadata',
246 'fa_media_type',
247 'fa_major_mime',
248 'fa_minor_mime',
249 'fa_user',
250 'fa_user_text',
251 'fa_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'fa_actor' : null,
252 'fa_timestamp',
253 'fa_deleted',
254 'fa_deleted_timestamp', /* Used by LocalFileRestoreBatch */
255 'fa_sha1',
256 ] + MediaWikiServices::getInstance()->getCommentStore()->getFields( 'fa_description' );
257 }
258
259 /**
260 * Return the tables, fields, and join conditions to be selected to create
261 * a new archivedfile object.
262 * @since 1.31
263 * @return array[] With three keys:
264 * - tables: (string[]) to include in the `$table` to `IDatabase->select()`
265 * - fields: (string[]) to include in the `$vars` to `IDatabase->select()`
266 * - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
267 */
268 public static function getQueryInfo() {
269 $commentQuery = MediaWikiServices::getInstance()->getCommentStore()->getJoin( 'fa_description' );
270 $actorQuery = ActorMigration::newMigration()->getJoin( 'fa_user' );
271 return [
272 'tables' => [ 'filearchive' ] + $commentQuery['tables'] + $actorQuery['tables'],
273 'fields' => [
274 'fa_id',
275 'fa_name',
276 'fa_archive_name',
277 'fa_storage_key',
278 'fa_storage_group',
279 'fa_size',
280 'fa_bits',
281 'fa_width',
282 'fa_height',
283 'fa_metadata',
284 'fa_media_type',
285 'fa_major_mime',
286 'fa_minor_mime',
287 'fa_timestamp',
288 'fa_deleted',
289 'fa_deleted_timestamp', /* Used by LocalFileRestoreBatch */
290 'fa_sha1',
291 ] + $commentQuery['fields'] + $actorQuery['fields'],
292 'joins' => $commentQuery['joins'] + $actorQuery['joins'],
293 ];
294 }
295
296 /**
297 * Load ArchivedFile object fields from a DB row.
298 *
299 * @param stdClass $row Object database row
300 * @since 1.21
301 */
302 public function loadFromRow( $row ) {
303 $this->id = intval( $row->fa_id );
304 $this->name = $row->fa_name;
305 $this->archive_name = $row->fa_archive_name;
306 $this->group = $row->fa_storage_group;
307 $this->key = $row->fa_storage_key;
308 $this->size = $row->fa_size;
309 $this->bits = $row->fa_bits;
310 $this->width = $row->fa_width;
311 $this->height = $row->fa_height;
312 $this->metadata = $row->fa_metadata;
313 $this->mime = "$row->fa_major_mime/$row->fa_minor_mime";
314 $this->media_type = $row->fa_media_type;
315 $this->description = MediaWikiServices::getInstance()->getCommentStore()
316 // Legacy because $row may have come from self::selectFields()
317 ->getCommentLegacy( wfGetDB( DB_REPLICA ), 'fa_description', $row )->text;
318 $this->user = User::newFromAnyId( $row->fa_user, $row->fa_user_text, $row->fa_actor );
319 $this->timestamp = $row->fa_timestamp;
320 $this->deleted = $row->fa_deleted;
321 if ( isset( $row->fa_sha1 ) ) {
322 $this->sha1 = $row->fa_sha1;
323 } else {
324 // old row, populate from key
325 $this->sha1 = LocalRepo::getHashFromKey( $this->key );
326 }
327 if ( !$this->title ) {
328 $this->title = Title::makeTitleSafe( NS_FILE, $row->fa_name );
329 }
330 }
331
332 /**
333 * Return the associated title object
334 *
335 * @return Title
336 */
337 public function getTitle() {
338 if ( !$this->title ) {
339 $this->load();
340 }
341 return $this->title;
342 }
343
344 /**
345 * Return the file name
346 *
347 * @return string
348 */
349 public function getName() {
350 if ( $this->name === false ) {
351 $this->load();
352 }
353
354 return $this->name;
355 }
356
357 /**
358 * @return int
359 */
360 public function getID() {
361 $this->load();
362
363 return $this->id;
364 }
365
366 /**
367 * @return bool
368 */
369 public function exists() {
370 $this->load();
371
372 return $this->exists;
373 }
374
375 /**
376 * Return the FileStore key
377 * @return string
378 */
379 public function getKey() {
380 $this->load();
381
382 return $this->key;
383 }
384
385 /**
386 * Return the FileStore key (overriding base File class)
387 * @return string
388 */
389 public function getStorageKey() {
390 return $this->getKey();
391 }
392
393 /**
394 * Return the FileStore storage group
395 * @return string
396 */
397 public function getGroup() {
398 return $this->group;
399 }
400
401 /**
402 * Return the width of the image
403 * @return int
404 */
405 public function getWidth() {
406 $this->load();
407
408 return $this->width;
409 }
410
411 /**
412 * Return the height of the image
413 * @return int
414 */
415 public function getHeight() {
416 $this->load();
417
418 return $this->height;
419 }
420
421 /**
422 * Get handler-specific metadata
423 * @return string
424 */
425 public function getMetadata() {
426 $this->load();
427
428 return $this->metadata;
429 }
430
431 /**
432 * Return the size of the image file, in bytes
433 * @return int
434 */
435 public function getSize() {
436 $this->load();
437
438 return $this->size;
439 }
440
441 /**
442 * Return the bits of the image file, in bytes
443 * @return int
444 */
445 public function getBits() {
446 $this->load();
447
448 return $this->bits;
449 }
450
451 /**
452 * Returns the MIME type of the file.
453 * @return string
454 */
455 public function getMimeType() {
456 $this->load();
457
458 return $this->mime;
459 }
460
461 /**
462 * Get a MediaHandler instance for this file
463 * @return MediaHandler
464 */
465 function getHandler() {
466 if ( !isset( $this->handler ) ) {
467 $this->handler = MediaHandler::getHandler( $this->getMimeType() );
468 }
469
470 return $this->handler;
471 }
472
473 /**
474 * Returns the number of pages of a multipage document, or false for
475 * documents which aren't multipage documents
476 * @return bool|int
477 */
478 function pageCount() {
479 if ( !isset( $this->pageCount ) ) {
480 // @FIXME: callers expect File objects
481 if ( $this->getHandler() && $this->handler->isMultiPage( $this ) ) {
482 $this->pageCount = $this->handler->pageCount( $this );
483 } else {
484 $this->pageCount = false;
485 }
486 }
487
488 return $this->pageCount;
489 }
490
491 /**
492 * Return the type of the media in the file.
493 * Use the value returned by this function with the MEDIATYPE_xxx constants.
494 * @return string
495 */
496 public function getMediaType() {
497 $this->load();
498
499 return $this->media_type;
500 }
501
502 /**
503 * Return upload timestamp.
504 *
505 * @return string
506 */
507 public function getTimestamp() {
508 $this->load();
509
510 return wfTimestamp( TS_MW, $this->timestamp );
511 }
512
513 /**
514 * Get the SHA-1 base 36 hash of the file
515 *
516 * @return string
517 * @since 1.21
518 */
519 function getSha1() {
520 $this->load();
521
522 return $this->sha1;
523 }
524
525 /**
526 * Returns ID or name of user who uploaded the file
527 *
528 * @note Prior to MediaWiki 1.23, this method always
529 * returned the user id, and was inconsistent with
530 * the rest of the file classes.
531 * @param string $type 'text', 'id', or 'object'
532 * @return int|string|User|null
533 * @throws MWException
534 * @since 1.31 added 'object'
535 */
536 public function getUser( $type = 'text' ) {
537 $this->load();
538
539 if ( $type === 'object' ) {
540 return $this->user;
541 } elseif ( $type === 'text' ) {
542 return $this->user ? $this->user->getName() : '';
543 } elseif ( $type === 'id' ) {
544 return $this->user ? $this->user->getId() : 0;
545 }
546
547 throw new MWException( "Unknown type '$type'." );
548 }
549
550 /**
551 * Return upload description.
552 *
553 * @return string|int
554 */
555 public function getDescription() {
556 $this->load();
557 if ( $this->isDeleted( File::DELETED_COMMENT ) ) {
558 return 0;
559 } else {
560 return $this->description;
561 }
562 }
563
564 /**
565 * Return the user ID of the uploader.
566 *
567 * @return int
568 */
569 public function getRawUser() {
570 return $this->getUser( 'id' );
571 }
572
573 /**
574 * Return the user name of the uploader.
575 *
576 * @return string
577 */
578 public function getRawUserText() {
579 return $this->getUser( 'text' );
580 }
581
582 /**
583 * Return upload description.
584 *
585 * @return string
586 */
587 public function getRawDescription() {
588 $this->load();
589
590 return $this->description;
591 }
592
593 /**
594 * Returns the deletion bitfield
595 * @return int
596 */
597 public function getVisibility() {
598 $this->load();
599
600 return $this->deleted;
601 }
602
603 /**
604 * for file or revision rows
605 *
606 * @param int $field One of DELETED_* bitfield constants
607 * @return bool
608 */
609 public function isDeleted( $field ) {
610 $this->load();
611
612 return ( $this->deleted & $field ) == $field;
613 }
614
615 /**
616 * Determine if the current user is allowed to view a particular
617 * field of this FileStore image file, if it's marked as deleted.
618 * @param int $field
619 * @param null|User $user User object to check, or null to use $wgUser
620 * @return bool
621 */
622 public function userCan( $field, User $user = null ) {
623 $this->load();
624
625 $title = $this->getTitle();
626 return Revision::userCanBitfield( $this->deleted, $field, $user, $title ?: null );
627 }
628 }