oi_name ); $file = new static( $title, $repo, null, $row->oi_archive_name ); $file->loadFromRow( $row, 'oi_' ); return $file; } /** * Create a OldLocalFile from a SHA-1 key * Do not call this except from inside a repo class. * * @stable to override * * @param string $sha1 Base-36 SHA-1 * @param LocalRepo $repo * @param string|false $timestamp MW_timestamp (optional) * * @return static|false */ public static function newFromKey( $sha1, $repo, $timestamp = false ) { $dbr = $repo->getReplicaDB(); $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbr ); $queryBuilder->where( [ 'oi_sha1' => $sha1 ] ); if ( $timestamp ) { $queryBuilder->andWhere( [ 'oi_timestamp' => $dbr->timestamp( $timestamp ) ] ); } $row = $queryBuilder->caller( __METHOD__ )->fetchRow(); if ( $row ) { return static::newFromRow( $row, $repo ); } else { return false; } } /** * Return the tables, fields, and join conditions to be selected to create * a new oldlocalfile object. * * Since 1.34, oi_user and oi_user_text have not been present in the * database, but they continue to be available in query results as * aliases. * * @since 1.31 * @stable to override * * @deprecated since 1.41 use FileSelectQueryBuilder instead * @param string[] $options * - omit-lazy: Omit fields that are lazily cached. * @return array[] With three keys: * - tables: (string[]) to include in the `$table` to `IDatabase->select()` or `SelectQueryBuilder::tables` * - fields: (string[]) to include in the `$vars` to `IDatabase->select()` or `SelectQueryBuilder::fields` * - joins: (array) to include in the `$join_conds` to `IDatabase->select()` or `SelectQueryBuilder::joinConds` * @phan-return array{tables:string[],fields:string[],joins:array} */ public static function getQueryInfo( array $options = [] ) { $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase(); $queryInfo = FileSelectQueryBuilder::newForOldFile( $dbr, $options )->getQueryInfo(); return [ 'tables' => $queryInfo['tables'], 'fields' => $queryInfo['fields'], 'joins' => $queryInfo['join_conds'], ]; } /** * @stable to call * * @param Title $title * @param LocalRepo $repo * @param string|int|null $time Timestamp or null to load by archive name * @param string|null $archiveName Archive name or null to load by timestamp */ public function __construct( $title, $repo, $time, $archiveName ) { parent::__construct( $title, $repo ); $this->requestedTime = $time; $this->archive_name = $archiveName; if ( $time === null && $archiveName === null ) { throw new LogicException( __METHOD__ . ': must specify at least one of $time or $archiveName' ); } } public function loadFromRow( $row, $prefix = 'img_' ) { $this->archive_name = $row->{"{$prefix}archive_name"}; $this->deleted = $row->{"{$prefix}deleted"}; $row = clone $row; unset( $row->{"{$prefix}archive_name"} ); unset( $row->{"{$prefix}deleted"} ); parent::loadFromRow( $row, $prefix ); } /** * @stable to override * @return bool */ protected function getCacheKey() { return false; } /** * @stable to override * @return string */ public function getArchiveName() { if ( !isset( $this->archive_name ) ) { $this->load(); } return $this->archive_name; } /** * @return bool */ public function isOld() { return true; } /** * @return bool */ public function isVisible() { return $this->exists() && !$this->isDeleted( File::DELETED_FILE ); } /** * @stable to override * @param int $flags */ protected function loadFromDB( $flags = 0 ) { $this->dataLoaded = true; $dbr = ( $flags & IDBAccessObject::READ_LATEST ) ? $this->repo->getPrimaryDB() : $this->repo->getReplicaDB(); $queryBuilder = $this->buildQueryBuilderForLoad( $dbr, [] ); $row = $queryBuilder->caller( __METHOD__ )->fetchRow(); if ( $row ) { $this->loadFromRow( $row, 'oi_' ); } else { $this->fileExists = false; } } /** * Load lazy file metadata from the DB * @stable to override */ protected function loadExtraFromDB() { $this->extraDataLoaded = true; $dbr = $this->repo->getReplicaDB(); $queryBuilder = $this->buildQueryBuilderForLoad( $dbr ); // In theory the file could have just been renamed/deleted...oh well $row = $queryBuilder->caller( __METHOD__ )->fetchRow(); if ( !$row ) { // fallback to primary DB $dbr = $this->repo->getPrimaryDB(); $queryBuilder = $this->buildQueryBuilderForLoad( $dbr ); $row = $queryBuilder->caller( __METHOD__ )->fetchRow(); } if ( $row ) { foreach ( $this->unprefixRow( $row, 'oi_' ) as $name => $value ) { $this->$name = $value; } } else { throw new RuntimeException( "Could not find data for image '{$this->archive_name}'." ); } } private function buildQueryBuilderForLoad( IReadableDatabase $dbr, $options = [ 'omit-nonlazy' ] ) { $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbr, $options ); $queryBuilder->where( [ 'oi_name' => $this->getName() ] ) ->orderBy( 'oi_timestamp', SelectQueryBuilder::SORT_DESC ); if ( $this->requestedTime === null ) { $queryBuilder->andWhere( [ 'oi_archive_name' => $this->archive_name ] ); } else { $queryBuilder->andWhere( [ 'oi_timestamp' => $dbr->timestamp( $this->requestedTime ) ] ); } return $queryBuilder; } /** * @inheritDoc * @stable to override */ protected function getCacheFields( $prefix = 'img_' ) { $fields = parent::getCacheFields( $prefix ); $fields[] = $prefix . 'archive_name'; $fields[] = $prefix . 'deleted'; return $fields; } /** * @return string * @stable to override */ public function getRel() { return $this->getArchiveRel( $this->getArchiveName() ); } /** * @return string * @stable to override */ public function getUrlRel() { return $this->getArchiveRel( rawurlencode( $this->getArchiveName() ) ); } /** * @stable to override */ public function upgradeRow() { $this->loadFromFile(); # Don't destroy file info of missing files if ( !$this->fileExists ) { wfDebug( __METHOD__ . ": file does not exist, aborting" ); return; } $dbw = $this->repo->getPrimaryDB(); [ $major, $minor ] = self::splitMime( $this->mime ); wfDebug( __METHOD__ . ': upgrading ' . $this->archive_name . " to the current schema" ); $dbw->newUpdateQueryBuilder() ->update( 'oldimage' ) ->set( [ 'oi_size' => $this->size, 'oi_width' => $this->width, 'oi_height' => $this->height, 'oi_bits' => $this->bits, 'oi_media_type' => $this->media_type, 'oi_major_mime' => $major, 'oi_minor_mime' => $minor, 'oi_metadata' => $this->getMetadataForDb( $dbw ), 'oi_sha1' => $this->sha1, ] ) ->where( [ 'oi_name' => $this->getName(), 'oi_archive_name' => $this->archive_name, ] ) ->caller( __METHOD__ )->execute(); } protected function reserializeMetadata() { // TODO: implement this and make it possible to hit it from refreshImageMetadata.php // It can be hit from action=purge but that's not very useful if the // goal is to reserialize the whole oldimage table. } /** * @param int $field One of DELETED_* bitfield constants for file or * revision rows * @return bool */ public function isDeleted( $field ) { $this->load(); return ( $this->deleted & $field ) == $field; } /** * Returns bitfield value * @return int */ public function getVisibility() { $this->load(); return (int)$this->deleted; } /** * Determine if the current user is allowed to view a particular * field of this image file, if it's marked as deleted. * * @param int $field * @param Authority $performer User object to check * @return bool */ public function userCan( $field, Authority $performer ) { $this->load(); return RevisionRecord::userCanBitfield( $this->deleted, $field, $performer ); } /** * Upload a file directly into archive. Generally for Special:Import. * * @param string $srcPath File system path of the source file * @param string $timestamp * @param string $comment * @param UserIdentity $user * @return Status */ public function uploadOld( $srcPath, $timestamp, $comment, UserIdentity $user ) { $archiveName = $this->getArchiveName(); $dstRel = $this->getArchiveRel( $archiveName ); $status = $this->publishTo( $srcPath, $dstRel ); if ( $status->isGood() && !$this->recordOldUpload( $srcPath, $archiveName, $timestamp, $comment, $user ) ) { $status->fatal( 'filenotfound', $srcPath ); } return $status; } /** * Record a file upload in the oldimage table, without adding log entries. * @stable to override * * @param string $srcPath File system path to the source file * @param string $archiveName The archive name of the file * @param string $timestamp * @param string $comment Upload comment * @param UserIdentity $user User who did this upload * @return bool */ protected function recordOldUpload( $srcPath, $archiveName, $timestamp, $comment, $user ) { $dbw = $this->repo->getPrimaryDB(); $services = MediaWikiServices::getInstance(); $mwProps = new MWFileProps( $services->getMimeAnalyzer() ); $props = $mwProps->getPropsFromPath( $srcPath, true ); if ( !$props['fileExists'] ) { return false; } $this->setProps( $props ); $dbw->startAtomic( __METHOD__ ); $commentFields = $services->getCommentStore() ->insert( $dbw, 'oi_description', $comment ); $actorId = $services->getActorNormalization() ->acquireActorId( $user, $dbw ); $dbw->newInsertQueryBuilder() ->insertInto( 'oldimage' ) ->row( [ 'oi_name' => $this->getName(), 'oi_archive_name' => $archiveName, 'oi_size' => $props['size'], 'oi_width' => intval( $props['width'] ), 'oi_height' => intval( $props['height'] ), 'oi_bits' => $props['bits'], 'oi_actor' => $actorId, 'oi_timestamp' => $dbw->timestamp( $timestamp ), 'oi_metadata' => $this->getMetadataForDb( $dbw ), 'oi_media_type' => $props['media_type'], 'oi_major_mime' => $props['major_mime'], 'oi_minor_mime' => $props['minor_mime'], 'oi_sha1' => $props['sha1'], ] + $commentFields ) ->caller( __METHOD__ )->execute(); $dbw->endAtomic( __METHOD__ ); return true; } /** * If archive name is an empty string, then file does not "exist" * * This is the case for a couple files on Wikimedia servers where * the old version is "lost". * @return bool */ public function exists() { $archiveName = $this->getArchiveName(); if ( $archiveName === '' || !is_string( $archiveName ) ) { return false; } return parent::exists(); } }