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