89 $repo = $this->file->getRepo();
90 if ( !$this->all && !$this->ids ) {
92 return $repo->newGood();
95 $status = $this->file->acquireFileLock();
96 if ( !$status->isOK() ) {
100 $dbw = $this->file->repo->getPrimaryDB();
102 $ownTrx = !$dbw->trxLevel();
103 $funcName = __METHOD__;
104 $dbw->startAtomic( __METHOD__ );
106 $unlockScope =
new ScopedCallback(
function () use ( $dbw, $funcName ) {
107 $dbw->endAtomic( $funcName );
108 $this->file->releaseFileLock();
113 $status = $this->file->repo->newGood();
115 $queryBuilder = $dbw->newSelectQueryBuilder()
118 ->where( [
'img_name' => $this->file->getName() ] );
123 $queryBuilder->lockInShareMode();
125 $exists = (bool)$queryBuilder->caller( __METHOD__ )->fetchField();
130 $arQueryBuilder->where( [
'fa_name' => $this->file->getName() ] )
131 ->orderBy(
'fa_timestamp', SelectQueryBuilder::SORT_DESC );
134 $arQueryBuilder->andWhere( [
'fa_id' => $this->ids ] );
137 $result = $arQueryBuilder->caller( __METHOD__ )->fetchResultSet();
142 $insertCurrent =
false;
143 $insertFileRevisions = [];
148 foreach ( $result as $row ) {
149 $idsPresent[] = $row->fa_id;
151 if ( $row->fa_name != $this->file->getName() ) {
152 $status->error(
'undelete-filename-mismatch',
$wgLang->timeanddate( $row->fa_timestamp ) );
153 $status->failCount++;
157 if ( $row->fa_storage_key ==
'' ) {
159 $status->error(
'undelete-bad-store-key',
$wgLang->timeanddate( $row->fa_timestamp ) );
160 $status->failCount++;
164 $deletedRel = $repo->getDeletedHashPath( $row->fa_storage_key ) .
165 $row->fa_storage_key;
166 $deletedUrl = $repo->getVirtualUrl() .
'/deleted/' . $deletedRel;
168 if ( isset( $row->fa_sha1 ) ) {
169 $sha1 = $row->fa_sha1;
176 if ( strlen( $sha1 ) == 32 && $sha1[0] ==
'0' ) {
177 $sha1 = substr( $sha1, 1 );
180 if ( $row->fa_major_mime ===
null || $row->fa_major_mime ==
'unknown'
181 || $row->fa_minor_mime ===
null || $row->fa_minor_mime ==
'unknown'
182 || $row->fa_media_type ===
null || $row->fa_media_type ==
'UNKNOWN'
183 || $row->fa_metadata ===
null
187 $this->file->loadFromFile( $deletedUrl );
188 $mime = $this->file->getMimeType();
191 'minor_mime' => $minorMime,
192 'major_mime' => $majorMime,
193 'media_type' => $this->file->getMediaType(),
194 'metadata' => $this->file->getMetadataForDb( $dbw )
198 'minor_mime' => $row->fa_minor_mime,
199 'major_mime' => $row->fa_major_mime,
200 'media_type' => $row->fa_media_type,
201 'metadata' => $row->fa_metadata
204 $this->file->setProps( [
205 'media_type' => $mediaInfo[
'media_type'],
206 'major_mime' => $mediaInfo[
'major_mime'],
207 'minor_mime' => $mediaInfo[
'minor_mime'],
209 $comment = $commentStore->getComment(
'fa_description', $row );
211 $commentFieldsNew = $commentStore->insert( $dbw,
'fr_description', $comment );
213 'fr_size' => $row->fa_size,
214 'fr_width' => $row->fa_width,
215 'fr_height' => $row->fa_height,
216 'fr_metadata' => $mediaInfo[
'metadata'],
217 'fr_bits' => $row->fa_bits,
218 'fr_actor' => $row->fa_actor,
219 'fr_timestamp' => $row->fa_timestamp,
221 ] + $commentFieldsNew;
223 if ( $first && !$exists ) {
225 $destRel = $this->file->getRel();
226 $commentFields = $commentStore->insert( $dbw,
'img_description', $comment );
228 'img_name' => $row->fa_name,
229 'img_size' => $row->fa_size,
230 'img_width' => $row->fa_width,
231 'img_height' => $row->fa_height,
232 'img_metadata' => $mediaInfo[
'metadata'],
233 'img_bits' => $row->fa_bits,
234 'img_media_type' => $mediaInfo[
'media_type'],
235 'img_major_mime' => $mediaInfo[
'major_mime'],
236 'img_minor_mime' => $mediaInfo[
'minor_mime'],
237 'img_actor' => $row->fa_actor,
238 'img_timestamp' => $row->fa_timestamp,
243 if ( !$this->unsuppress && $row->fa_deleted ) {
244 $status->fatal(
'undeleterevdel' );
247 $fileRevisionRow[
'fr_archive_name'] =
'';
248 $fileRevisionRow[
'fr_deleted'] = 0;
250 $archiveName = $row->fa_archive_name;
252 if ( $archiveName ===
null ) {
256 $timestamp = (int)
wfTimestamp( TS::UNIX, $row->fa_deleted_timestamp );
259 $archiveName =
wfTimestamp( TS::MW, $timestamp ) .
'!' . $row->fa_name;
261 }
while ( isset( $archiveNames[$archiveName] ) );
264 $archiveNames[$archiveName] =
true;
265 $destRel = $this->file->getArchiveRel( $archiveName );
267 'oi_name' => $row->fa_name,
268 'oi_archive_name' => $archiveName,
269 'oi_size' => $row->fa_size,
270 'oi_width' => $row->fa_width,
271 'oi_height' => $row->fa_height,
272 'oi_bits' => $row->fa_bits,
273 'oi_actor' => $row->fa_actor,
274 'oi_timestamp' => $row->fa_timestamp,
275 'oi_metadata' => $mediaInfo[
'metadata'],
276 'oi_media_type' => $mediaInfo[
'media_type'],
277 'oi_major_mime' => $mediaInfo[
'major_mime'],
278 'oi_minor_mime' => $mediaInfo[
'minor_mime'],
279 'oi_deleted' => $this->unsuppress ? 0 : $row->fa_deleted,
281 ] + $commentStore->insert( $dbw,
'oi_description', $comment );
283 $fileRevisionRow[
'fr_archive_name'] = $archiveName;
284 $fileRevisionRow[
'fr_deleted'] = $this->unsuppress ? 0 : $row->fa_deleted;
286 $insertFileRevisions[] = $fileRevisionRow;
288 $deleteIds[] = $row->fa_id;
292 $status->successCount++;
294 $storeBatch[] = [ $deletedUrl,
'public', $destRel ];
295 $this->cleanupBatch[] = $row->fa_storage_key;
304 $missingIds = array_diff( $this->ids, $idsPresent );
306 foreach ( $missingIds as $id ) {
307 $status->error(
'undelete-missing-filearchive', $id );
310 if ( !$repo->hasSha1Storage() ) {
313 if ( !$checkStatus->isGood() ) {
314 $status->merge( $checkStatus );
317 $storeBatch = $checkStatus->value;
322 $status->merge( $storeStatus );
324 if ( !$status->isGood() ) {
328 $status->setOK(
false );
342 if ( $insertCurrent ) {
343 $dbw->newInsertQueryBuilder()
344 ->insertInto(
'image' )
345 ->row( $insertCurrent )
346 ->caller( __METHOD__ )->execute();
348 $dbw->newUpdateQueryBuilder()
350 ->set( [
'file_deleted' => 0 ] )
351 ->where( [
'file_id' => $this->file->acquireFileIdFromName() ] )
352 ->caller( __METHOD__ )->execute();
356 if ( $insertBatch ) {
357 $dbw->newInsertQueryBuilder()
358 ->insertInto(
'oldimage' )
359 ->rows( $insertBatch )
360 ->caller( __METHOD__ )->execute();
364 $dbw->newDeleteQueryBuilder()
365 ->deleteFrom(
'filearchive' )
366 ->where( [
'fa_id' => $deleteIds ] )
367 ->caller( __METHOD__ )->execute();
372 $insertFileRevisions = array_reverse( $insertFileRevisions );
374 foreach ( $insertFileRevisions as &$row ) {
375 $row[
'fr_file'] = $this->file->getFileIdFromName();
377 $dbw->newInsertQueryBuilder()
378 ->insertInto(
'filerevision' )
379 ->rows( $insertFileRevisions )
380 ->caller( __METHOD__ )->execute();
381 $latestId = $dbw->newSelectQueryBuilder()
383 ->from(
'filerevision' )
384 ->where( [
'fr_file' => $this->file->getFileIdFromName() ] )
385 ->orderBy(
'fr_timestamp',
'DESC' )
386 ->caller( __METHOD__ )->fetchField();
387 $dbw->newUpdateQueryBuilder()
389 ->set( [
'file_latest' => $latestId ] )
390 ->where( [
'file_id' => $this->file->getFileIdFromName() ] )
391 ->caller( __METHOD__ )->execute();
396 if ( $status->successCount > 0 || !$storeBatch || $repo->hasSha1Storage() ) {
398 wfDebug( __METHOD__ .
" restored {$status->successCount} items, creating a new current" );
400 DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [
'images' => 1 ] ) );
402 $this->file->purgeEverything();
404 wfDebug( __METHOD__ .
" restored {$status->successCount} as archived versions" );
405 $this->file->purgeDescription();
409 ScopedCallback::consume( $unlockScope );