Merge "Provide command to adjust phpunit.xml for code coverage"
[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 ( $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 * Return the tables, fields, and join conditions to be selected to create
218 * a new archivedfile object.
219 * @since 1.31
220 * @return array[] With three keys:
221 * - tables: (string[]) to include in the `$table` to `IDatabase->select()`
222 * - fields: (string[]) to include in the `$vars` to `IDatabase->select()`
223 * - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
224 */
225 public static function getQueryInfo() {
226 $commentQuery = MediaWikiServices::getInstance()->getCommentStore()->getJoin( 'fa_description' );
227 $actorQuery = ActorMigration::newMigration()->getJoin( 'fa_user' );
228 return [
229 'tables' => [ 'filearchive' ] + $commentQuery['tables'] + $actorQuery['tables'],
230 'fields' => [
231 'fa_id',
232 'fa_name',
233 'fa_archive_name',
234 'fa_storage_key',
235 'fa_storage_group',
236 'fa_size',
237 'fa_bits',
238 'fa_width',
239 'fa_height',
240 'fa_metadata',
241 'fa_media_type',
242 'fa_major_mime',
243 'fa_minor_mime',
244 'fa_timestamp',
245 'fa_deleted',
246 'fa_deleted_timestamp', /* Used by LocalFileRestoreBatch */
247 'fa_sha1',
248 ] + $commentQuery['fields'] + $actorQuery['fields'],
249 'joins' => $commentQuery['joins'] + $actorQuery['joins'],
250 ];
251 }
252
253 /**
254 * Load ArchivedFile object fields from a DB row.
255 *
256 * @param stdClass $row Object database row
257 * @since 1.21
258 */
259 public function loadFromRow( $row ) {
260 $this->id = intval( $row->fa_id );
261 $this->name = $row->fa_name;
262 $this->archive_name = $row->fa_archive_name;
263 $this->group = $row->fa_storage_group;
264 $this->key = $row->fa_storage_key;
265 $this->size = $row->fa_size;
266 $this->bits = $row->fa_bits;
267 $this->width = $row->fa_width;
268 $this->height = $row->fa_height;
269 $this->metadata = $row->fa_metadata;
270 $this->mime = "$row->fa_major_mime/$row->fa_minor_mime";
271 $this->media_type = $row->fa_media_type;
272 $this->description = MediaWikiServices::getInstance()->getCommentStore()
273 // Legacy because $row may have come from self::selectFields()
274 ->getCommentLegacy( wfGetDB( DB_REPLICA ), 'fa_description', $row )->text;
275 $this->user = User::newFromAnyId( $row->fa_user, $row->fa_user_text, $row->fa_actor );
276 $this->timestamp = $row->fa_timestamp;
277 $this->deleted = $row->fa_deleted;
278 if ( isset( $row->fa_sha1 ) ) {
279 $this->sha1 = $row->fa_sha1;
280 } else {
281 // old row, populate from key
282 $this->sha1 = LocalRepo::getHashFromKey( $this->key );
283 }
284 if ( !$this->title ) {
285 $this->title = Title::makeTitleSafe( NS_FILE, $row->fa_name );
286 }
287 }
288
289 /**
290 * Return the associated title object
291 *
292 * @return Title
293 */
294 public function getTitle() {
295 if ( !$this->title ) {
296 $this->load();
297 }
298 return $this->title;
299 }
300
301 /**
302 * Return the file name
303 *
304 * @return string
305 */
306 public function getName() {
307 if ( $this->name === false ) {
308 $this->load();
309 }
310
311 return $this->name;
312 }
313
314 /**
315 * @return int
316 */
317 public function getID() {
318 $this->load();
319
320 return $this->id;
321 }
322
323 /**
324 * @return bool
325 */
326 public function exists() {
327 $this->load();
328
329 return $this->exists;
330 }
331
332 /**
333 * Return the FileStore key
334 * @return string
335 */
336 public function getKey() {
337 $this->load();
338
339 return $this->key;
340 }
341
342 /**
343 * Return the FileStore key (overriding base File class)
344 * @return string
345 */
346 public function getStorageKey() {
347 return $this->getKey();
348 }
349
350 /**
351 * Return the FileStore storage group
352 * @return string
353 */
354 public function getGroup() {
355 return $this->group;
356 }
357
358 /**
359 * Return the width of the image
360 * @return int
361 */
362 public function getWidth() {
363 $this->load();
364
365 return $this->width;
366 }
367
368 /**
369 * Return the height of the image
370 * @return int
371 */
372 public function getHeight() {
373 $this->load();
374
375 return $this->height;
376 }
377
378 /**
379 * Get handler-specific metadata
380 * @return string
381 */
382 public function getMetadata() {
383 $this->load();
384
385 return $this->metadata;
386 }
387
388 /**
389 * Return the size of the image file, in bytes
390 * @return int
391 */
392 public function getSize() {
393 $this->load();
394
395 return $this->size;
396 }
397
398 /**
399 * Return the bits of the image file, in bytes
400 * @return int
401 */
402 public function getBits() {
403 $this->load();
404
405 return $this->bits;
406 }
407
408 /**
409 * Returns the MIME type of the file.
410 * @return string
411 */
412 public function getMimeType() {
413 $this->load();
414
415 return $this->mime;
416 }
417
418 /**
419 * Get a MediaHandler instance for this file
420 * @return MediaHandler
421 */
422 function getHandler() {
423 if ( !isset( $this->handler ) ) {
424 $this->handler = MediaHandler::getHandler( $this->getMimeType() );
425 }
426
427 return $this->handler;
428 }
429
430 /**
431 * Returns the number of pages of a multipage document, or false for
432 * documents which aren't multipage documents
433 * @return bool|int
434 */
435 function pageCount() {
436 if ( !isset( $this->pageCount ) ) {
437 // @FIXME: callers expect File objects
438 if ( $this->getHandler() && $this->handler->isMultiPage( $this ) ) {
439 $this->pageCount = $this->handler->pageCount( $this );
440 } else {
441 $this->pageCount = false;
442 }
443 }
444
445 return $this->pageCount;
446 }
447
448 /**
449 * Return the type of the media in the file.
450 * Use the value returned by this function with the MEDIATYPE_xxx constants.
451 * @return string
452 */
453 public function getMediaType() {
454 $this->load();
455
456 return $this->media_type;
457 }
458
459 /**
460 * Return upload timestamp.
461 *
462 * @return string
463 */
464 public function getTimestamp() {
465 $this->load();
466
467 return wfTimestamp( TS_MW, $this->timestamp );
468 }
469
470 /**
471 * Get the SHA-1 base 36 hash of the file
472 *
473 * @return string
474 * @since 1.21
475 */
476 function getSha1() {
477 $this->load();
478
479 return $this->sha1;
480 }
481
482 /**
483 * Returns ID or name of user who uploaded the file
484 *
485 * @note Prior to MediaWiki 1.23, this method always
486 * returned the user id, and was inconsistent with
487 * the rest of the file classes.
488 * @param string $type 'text', 'id', or 'object'
489 * @return int|string|User|null
490 * @throws MWException
491 * @since 1.31 added 'object'
492 */
493 public function getUser( $type = 'text' ) {
494 $this->load();
495
496 if ( $type === 'object' ) {
497 return $this->user;
498 } elseif ( $type === 'text' ) {
499 return $this->user ? $this->user->getName() : '';
500 } elseif ( $type === 'id' ) {
501 return $this->user ? $this->user->getId() : 0;
502 }
503
504 throw new MWException( "Unknown type '$type'." );
505 }
506
507 /**
508 * Return upload description.
509 *
510 * @return string|int
511 */
512 public function getDescription() {
513 $this->load();
514 if ( $this->isDeleted( File::DELETED_COMMENT ) ) {
515 return 0;
516 } else {
517 return $this->description;
518 }
519 }
520
521 /**
522 * Return the user ID of the uploader.
523 *
524 * @return int
525 */
526 public function getRawUser() {
527 return $this->getUser( 'id' );
528 }
529
530 /**
531 * Return the user name of the uploader.
532 *
533 * @return string
534 */
535 public function getRawUserText() {
536 return $this->getUser( 'text' );
537 }
538
539 /**
540 * Return upload description.
541 *
542 * @return string
543 */
544 public function getRawDescription() {
545 $this->load();
546
547 return $this->description;
548 }
549
550 /**
551 * Returns the deletion bitfield
552 * @return int
553 */
554 public function getVisibility() {
555 $this->load();
556
557 return $this->deleted;
558 }
559
560 /**
561 * for file or revision rows
562 *
563 * @param int $field One of DELETED_* bitfield constants
564 * @return bool
565 */
566 public function isDeleted( $field ) {
567 $this->load();
568
569 return ( $this->deleted & $field ) == $field;
570 }
571
572 /**
573 * Determine if the current user is allowed to view a particular
574 * field of this FileStore image file, if it's marked as deleted.
575 * @param int $field
576 * @param null|User $user User object to check, or null to use $wgUser
577 * @return bool
578 */
579 public function userCan( $field, User $user = null ) {
580 $this->load();
581
582 $title = $this->getTitle();
583 return Revision::userCanBitfield( $this->deleted, $field, $user, $title ?: null );
584 }
585 }