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