Add `actor` table and code to start using it
[lhc/web/wiklou.git] / includes / filerepo / file / LocalFile.php
index 263e45b..ec4a5fb 100644 (file)
@@ -102,12 +102,9 @@ class LocalFile extends File {
        /** @var string Upload timestamp */
        private $timestamp;
 
-       /** @var int User ID of uploader */
+       /** @var User Uploader */
        private $user;
 
-       /** @var string User name of uploader */
-       private $user_text;
-
        /** @var string Description of current revision of the file */
        private $description;
 
@@ -201,7 +198,19 @@ class LocalFile extends File {
         * @return array
         */
        static function selectFields() {
+               global $wgActorTableSchemaMigrationStage;
+
                wfDeprecated( __METHOD__, '1.31' );
+               if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+                       // If code is using this instead of self::getQueryInfo(), there's a
+                       // decent chance it's going to try to directly access
+                       // $row->img_user or $row->img_user_text and we can't give it
+                       // useful values here once those aren't being written anymore.
+                       throw new BadMethodCallException(
+                               'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+                       );
+               }
+
                return [
                        'img_name',
                        'img_size',
@@ -214,6 +223,7 @@ class LocalFile extends File {
                        'img_minor_mime',
                        'img_user',
                        'img_user_text',
+                       'img_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'img_actor' : null,
                        'img_timestamp',
                        'img_sha1',
                ] + CommentStore::getStore()->getFields( 'img_description' );
@@ -232,8 +242,9 @@ class LocalFile extends File {
         */
        public static function getQueryInfo( array $options = [] ) {
                $commentQuery = CommentStore::getStore()->getJoin( 'img_description' );
+               $actorQuery = ActorMigration::newMigration()->getJoin( 'img_user' );
                $ret = [
-                       'tables' => [ 'image' ] + $commentQuery['tables'],
+                       'tables' => [ 'image' ] + $commentQuery['tables'] + $actorQuery['tables'],
                        'fields' => [
                                'img_name',
                                'img_size',
@@ -244,12 +255,10 @@ class LocalFile extends File {
                                'img_media_type',
                                'img_major_mime',
                                'img_minor_mime',
-                               'img_user',
-                               'img_user_text',
                                'img_timestamp',
                                'img_sha1',
-                       ] + $commentQuery['fields'],
-                       'joins' => $commentQuery['joins'],
+                       ] + $commentQuery['fields'] + $actorQuery['fields'],
+                       'joins' => $commentQuery['joins'] + $actorQuery['joins'],
                ];
 
                if ( in_array( 'omit-nonlazy', $options, true ) ) {
@@ -330,6 +339,10 @@ class LocalFile extends File {
                                                $cacheVal[$field] = $this->$field;
                                        }
                                }
+                               $cacheVal['user'] = $this->user ? $this->user->getId() : 0;
+                               $cacheVal['user_text'] = $this->user ? $this->user->getName() : '';
+                               $cacheVal['actor'] = $this->user ? $this->user->getActorId() : null;
+
                                // Strip off excessive entries from the subset of fields that can become large.
                                // If the cache value gets to large it will not fit in memcached and nothing will
                                // get cached at all, causing master queries for any file access.
@@ -407,8 +420,7 @@ class LocalFile extends File {
                // and self::loadFromCache() for the caching, and self::setProps() for
                // populating the object from an array of data.
                return [ 'size', 'width', 'height', 'bits', 'media_type',
-                       'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'user',
-                       'user_text', 'description' ];
+                       'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'description' ];
        }
 
        /**
@@ -570,6 +582,13 @@ class LocalFile extends File {
                $decoded['description'] = CommentStore::getStore()
                        ->getComment( 'description', (object)$decoded )->text;
 
+               $decoded['user'] = User::newFromAnyId(
+                       isset( $decoded['user'] ) ? $decoded['user'] : null,
+                       isset( $decoded['user_text'] ) ? $decoded['user_text'] : null,
+                       isset( $decoded['actor'] ) ? $decoded['actor'] : null
+               );
+               unset( $decoded['user_text'], $decoded['actor'] );
+
                $decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
 
                $decoded['metadata'] = $this->repo->getReplicaDB()->decodeBlob( $decoded['metadata'] );
@@ -751,6 +770,14 @@ class LocalFile extends File {
                        }
                }
 
+               if ( isset( $info['user'] ) || isset( $info['user_text'] ) || isset( $info['actor'] ) ) {
+                       $this->user = User::newFromAnyId(
+                               isset( $info['user'] ) ? $info['user'] : null,
+                               isset( $info['user_text'] ) ? $info['user_text'] : null,
+                               isset( $info['actor'] ) ? $info['actor'] : null
+                       );
+               }
+
                // Fix up mime fields
                if ( isset( $info['major_mime'] ) ) {
                        $this->mime = "{$info['major_mime']}/{$info['minor_mime']}";
@@ -845,19 +872,24 @@ class LocalFile extends File {
        }
 
        /**
-        * Returns ID or name of user who uploaded the file
+        * Returns user who uploaded the file
         *
-        * @param string $type 'text' or 'id'
-        * @return int|string
+        * @param string $type 'text', 'id', or 'object'
+        * @return int|string|User
+        * @since 1.31 Added 'object'
         */
        function getUser( $type = 'text' ) {
                $this->load();
 
-               if ( $type == 'text' ) {
-                       return $this->user_text;
-               } else { // id
-                       return (int)$this->user;
+               if ( $type === 'object' ) {
+                       return $this->user;
+               } elseif ( $type === 'text' ) {
+                       return $this->user->getName();
+               } elseif ( $type === 'id' ) {
+                       return $this->user->getId();
                }
+
+               throw new MWException( "Unknown type '$type'." );
        }
 
        /**
@@ -1392,7 +1424,7 @@ class LocalFile extends File {
        function recordUpload2(
                $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null, $tags = []
        ) {
-               global $wgCommentTableSchemaMigrationStage;
+               global $wgCommentTableSchemaMigrationStage, $wgActorTableSchemaMigrationStage;
 
                if ( is_null( $user ) ) {
                        global $wgUser;
@@ -1414,6 +1446,7 @@ class LocalFile extends File {
                $props['description'] = $comment;
                $props['user'] = $user->getId();
                $props['user_text'] = $user->getName();
+               $props['actor'] = $user->getActorId( $dbw );
                $props['timestamp'] = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
                $this->setProps( $props );
 
@@ -1432,6 +1465,8 @@ class LocalFile extends File {
                $commentStore = CommentStore::getStore();
                list( $commentFields, $commentCallback ) =
                        $commentStore->insertWithTempTable( $dbw, 'img_description', $comment );
+               $actorMigration = ActorMigration::newMigration();
+               $actorFields = $actorMigration->getInsertValues( $dbw, 'img_user', $user );
                $dbw->insert( 'image',
                        [
                                'img_name' => $this->getName(),
@@ -1443,11 +1478,9 @@ class LocalFile extends File {
                                'img_major_mime' => $this->major_mime,
                                'img_minor_mime' => $this->minor_mime,
                                'img_timestamp' => $timestamp,
-                               'img_user' => $user->getId(),
-                               'img_user_text' => $user->getName(),
                                'img_metadata' => $dbw->encodeBlob( $this->metadata ),
                                'img_sha1' => $this->sha1
-                       ] + $commentFields,
+                       ] + $commentFields + $actorFields,
                        __METHOD__,
                        'IGNORE'
                );
@@ -1490,8 +1523,6 @@ class LocalFile extends File {
                                'oi_height' => 'img_height',
                                'oi_bits' => 'img_bits',
                                'oi_timestamp' => 'img_timestamp',
-                               'oi_user' => 'img_user',
-                               'oi_user_text' => 'img_user_text',
                                'oi_metadata' => 'img_metadata',
                                'oi_media_type' => 'img_media_type',
                                'oi_major_mime' => 'img_major_mime',
@@ -1534,6 +1565,37 @@ class LocalFile extends File {
                                }
                        }
 
+                       if ( $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+                               $fields['oi_user'] = 'img_user';
+                               $fields['oi_user_text'] = 'img_user_text';
+                       }
+                       if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+                               $fields['oi_actor'] = 'img_actor';
+                       }
+
+                       if ( $wgActorTableSchemaMigrationStage !== MIGRATION_OLD &&
+                               $wgActorTableSchemaMigrationStage !== MIGRATION_NEW
+                       ) {
+                               // Upgrade any rows that are still old-style. Otherwise an upgrade
+                               // might be missed if a deletion happens while the migration script
+                               // is running.
+                               $res = $dbw->select(
+                                       [ 'image' ],
+                                       [ 'img_name', 'img_user', 'img_user_text' ],
+                                       [ 'img_name' => $this->getName(), 'img_actor' => 0 ],
+                                       __METHOD__
+                               );
+                               foreach ( $res as $row ) {
+                                       $actorId = User::newFromAnyId( $row->img_user, $row->img_user_text, null )->getActorId( $dbw );
+                                       $dbw->update(
+                                               'image',
+                                               [ 'img_actor' => $actorId ],
+                                               [ 'img_name' => $row->img_name, 'img_actor' => 0 ],
+                                               __METHOD__
+                                       );
+                               }
+                       }
+
                        # (T36993) Note: $oldver can be empty here, if the previous
                        # version of the file was broken. Allow registration of the new
                        # version to continue anyway, because that's better than having
@@ -1554,11 +1616,9 @@ class LocalFile extends File {
                                        'img_major_mime' => $this->major_mime,
                                        'img_minor_mime' => $this->minor_mime,
                                        'img_timestamp' => $timestamp,
-                                       'img_user' => $user->getId(),
-                                       'img_user_text' => $user->getName(),
                                        'img_metadata' => $dbw->encodeBlob( $this->metadata ),
                                        'img_sha1' => $this->sha1
-                               ] + $commentFields,
+                               ] + $commentFields + $actorFields,
                                [ 'img_name' => $this->getName() ],
                                __METHOD__
                        );
@@ -2405,12 +2465,13 @@ class LocalFileDeleteBatch {
        }
 
        protected function doDBInserts() {
-               global $wgCommentTableSchemaMigrationStage;
+               global $wgCommentTableSchemaMigrationStage, $wgActorTableSchemaMigrationStage;
 
                $now = time();
                $dbw = $this->file->repo->getMasterDB();
 
                $commentStore = CommentStore::getStore();
+               $actorMigration = ActorMigration::newMigration();
 
                $encTimestamp = $dbw->addQuotes( $dbw->timestamp( $now ) );
                $encUserId = $dbw->addQuotes( $this->user->getId() );
@@ -2449,8 +2510,6 @@ class LocalFileDeleteBatch {
                                'fa_media_type' => 'img_media_type',
                                'fa_major_mime' => 'img_major_mime',
                                'fa_minor_mime' => 'img_minor_mime',
-                               'fa_user' => 'img_user',
-                               'fa_user_text' => 'img_user_text',
                                'fa_timestamp' => 'img_timestamp',
                                'fa_sha1' => 'img_sha1'
                        ];
@@ -2495,6 +2554,37 @@ class LocalFileDeleteBatch {
                                }
                        }
 
+                       if ( $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+                               $fields['fa_user'] = 'img_user';
+                               $fields['fa_user_text'] = 'img_user_text';
+                       }
+                       if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+                               $fields['fa_actor'] = 'img_actor';
+                       }
+
+                       if ( $wgActorTableSchemaMigrationStage !== MIGRATION_OLD &&
+                               $wgActorTableSchemaMigrationStage !== MIGRATION_NEW
+                       ) {
+                               // Upgrade any rows that are still old-style. Otherwise an upgrade
+                               // might be missed if a deletion happens while the migration script
+                               // is running.
+                               $res = $dbw->select(
+                                       [ 'image' ],
+                                       [ 'img_name', 'img_user', 'img_user_text' ],
+                                       [ 'img_name' => $this->file->getName(), 'img_actor' => 0 ],
+                                       __METHOD__
+                               );
+                               foreach ( $res as $row ) {
+                                       $actorId = User::newFromAnyId( $row->img_user, $row->img_user_text, null )->getActorId( $dbw );
+                                       $dbw->update(
+                                               'image',
+                                               [ 'img_actor' => $actorId ],
+                                               [ 'img_name' => $row->img_name, 'img_actor' => 0 ],
+                                               __METHOD__
+                                       );
+                               }
+                       }
+
                        $dbw->insertSelect( 'filearchive', $tables, $fields,
                                [ 'img_name' => $this->file->getName() ], __METHOD__, [], [], $joins );
                }
@@ -2517,6 +2607,7 @@ class LocalFileDeleteBatch {
                                $reason = $commentStore->createComment( $dbw, $this->reason );
                                foreach ( $res as $row ) {
                                        $comment = $commentStore->getComment( 'oi_description', $row );
+                                       $user = User::newFromAnyId( $row->oi_user, $row->oi_user_text, $row->oi_actor );
                                        $rowsInsert[] = [
                                                // Deletion-specific fields
                                                'fa_storage_group' => 'deleted',
@@ -2537,12 +2628,11 @@ class LocalFileDeleteBatch {
                                                'fa_media_type' => $row->oi_media_type,
                                                'fa_major_mime' => $row->oi_major_mime,
                                                'fa_minor_mime' => $row->oi_minor_mime,
-                                               'fa_user' => $row->oi_user,
-                                               'fa_user_text' => $row->oi_user_text,
                                                'fa_timestamp' => $row->oi_timestamp,
                                                'fa_sha1' => $row->oi_sha1
                                        ] + $commentStore->insert( $dbw, 'fa_deleted_reason', $reason )
-                                       + $commentStore->insert( $dbw, 'fa_description', $comment );
+                                       + $commentStore->insert( $dbw, 'fa_description', $comment )
+                                       + $actorMigration->getInsertValues( $dbw, 'fa_user', $user );
                                }
                        }
 
@@ -2741,6 +2831,7 @@ class LocalFileRestoreBatch {
                $dbw = $this->file->repo->getMasterDB();
 
                $commentStore = CommentStore::getStore();
+               $actorMigration = ActorMigration::newMigration();
 
                $status = $this->file->repo->newGood();
 
@@ -2829,11 +2920,13 @@ class LocalFileRestoreBatch {
                        }
 
                        $comment = $commentStore->getComment( 'fa_description', $row );
+                       $user = User::newFromAnyId( $row->fa_user, $row->fa_user_text, $row->fa_actor );
                        if ( $first && !$exists ) {
                                // This revision will be published as the new current version
                                $destRel = $this->file->getRel();
                                list( $commentFields, $commentCallback ) =
                                        $commentStore->insertWithTempTable( $dbw, 'img_description', $comment );
+                               $actorFields = $actorMigration->getInsertValues( $dbw, 'img_user', $user );
                                $insertCurrent = [
                                        'img_name' => $row->fa_name,
                                        'img_size' => $row->fa_size,
@@ -2844,11 +2937,9 @@ class LocalFileRestoreBatch {
                                        'img_media_type' => $props['media_type'],
                                        'img_major_mime' => $props['major_mime'],
                                        'img_minor_mime' => $props['minor_mime'],
-                                       'img_user' => $row->fa_user,
-                                       'img_user_text' => $row->fa_user_text,
                                        'img_timestamp' => $row->fa_timestamp,
                                        'img_sha1' => $sha1
-                               ] + $commentFields;
+                               ] + $commentFields + $actorFields;
 
                                // The live (current) version cannot be hidden!
                                if ( !$this->unsuppress && $row->fa_deleted ) {
@@ -2880,8 +2971,6 @@ class LocalFileRestoreBatch {
                                        'oi_width' => $row->fa_width,
                                        'oi_height' => $row->fa_height,
                                        'oi_bits' => $row->fa_bits,
-                                       'oi_user' => $row->fa_user,
-                                       'oi_user_text' => $row->fa_user_text,
                                        'oi_timestamp' => $row->fa_timestamp,
                                        'oi_metadata' => $props['metadata'],
                                        'oi_media_type' => $props['media_type'],
@@ -2889,7 +2978,8 @@ class LocalFileRestoreBatch {
                                        'oi_minor_mime' => $props['minor_mime'],
                                        'oi_deleted' => $this->unsuppress ? 0 : $row->fa_deleted,
                                        'oi_sha1' => $sha1
-                               ] + $commentStore->insert( $dbw, 'oi_description', $comment );
+                               ] + $commentStore->insert( $dbw, 'oi_description', $comment )
+                               + $actorMigration->getInsertValues( $dbw, 'oi_user', $user );
                        }
 
                        $deleteIds[] = $row->fa_id;