102 $repo = $this->file->getRepo();
103 if ( !$this->all && !$this->ids ) {
105 return $repo->newGood();
108 $status = $this->file->acquireFileLock();
109 if ( !$status->isOK() ) {
113 $dbw = $this->file->repo->getPrimaryDB();
115 $ownTrx = !$dbw->trxLevel();
116 $funcName = __METHOD__;
117 $dbw->startAtomic( __METHOD__ );
119 $unlockScope =
new ScopedCallback(
function () use ( $dbw, $funcName ) {
120 $dbw->endAtomic( $funcName );
121 $this->file->releaseFileLock();
126 $status = $this->file->repo->newGood();
128 $queryBuilder = $dbw->newSelectQueryBuilder()
131 ->where( [
'img_name' => $this->file->getName() ] );
136 $queryBuilder->lockInShareMode();
138 $exists = (bool)$queryBuilder->caller( __METHOD__ )->fetchField();
143 $arQueryBuilder->where( [
'fa_name' => $this->file->getName() ] )
144 ->orderBy(
'fa_timestamp', SelectQueryBuilder::SORT_DESC );
147 $arQueryBuilder->andWhere( [
'fa_id' => $this->ids ] );
150 $result = $arQueryBuilder->caller( __METHOD__ )->fetchResultSet();
155 $insertCurrent =
false;
156 $insertFileRevisions = [];
161 foreach ( $result as $row ) {
162 $idsPresent[] = $row->fa_id;
164 if ( $row->fa_name != $this->file->getName() ) {
165 $status->error(
'undelete-filename-mismatch',
$wgLang->timeanddate( $row->fa_timestamp ) );
166 $status->failCount++;
170 if ( $row->fa_storage_key ==
'' ) {
172 $status->error(
'undelete-bad-store-key',
$wgLang->timeanddate( $row->fa_timestamp ) );
173 $status->failCount++;
177 $deletedRel = $repo->getDeletedHashPath( $row->fa_storage_key ) .
178 $row->fa_storage_key;
179 $deletedUrl = $repo->getVirtualUrl() .
'/deleted/' . $deletedRel;
181 if ( isset( $row->fa_sha1 ) ) {
182 $sha1 = $row->fa_sha1;
189 if ( strlen( $sha1 ) == 32 && $sha1[0] ==
'0' ) {
190 $sha1 = substr( $sha1, 1 );
193 if ( $row->fa_major_mime ===
null || $row->fa_major_mime ==
'unknown'
194 || $row->fa_minor_mime ===
null || $row->fa_minor_mime ==
'unknown'
195 || $row->fa_media_type ===
null || $row->fa_media_type ==
'UNKNOWN'
196 || $row->fa_metadata ===
null
200 $this->file->loadFromFile( $deletedUrl );
201 $mime = $this->file->getMimeType();
204 'minor_mime' => $minorMime,
205 'major_mime' => $majorMime,
206 'media_type' => $this->file->getMediaType(),
207 'metadata' => $this->file->getMetadataForDb( $dbw )
211 'minor_mime' => $row->fa_minor_mime,
212 'major_mime' => $row->fa_major_mime,
213 'media_type' => $row->fa_media_type,
214 'metadata' => $row->fa_metadata
217 $this->file->setProps( [
218 'media_type' => $mediaInfo[
'media_type'],
219 'major_mime' => $mediaInfo[
'major_mime'],
220 'minor_mime' => $mediaInfo[
'minor_mime'],
222 $comment = $commentStore->getComment(
'fa_description', $row );
224 $commentFieldsNew = $commentStore->insert( $dbw,
'fr_description', $comment );
226 'fr_size' => $row->fa_size,
227 'fr_width' => $row->fa_width,
228 'fr_height' => $row->fa_height,
229 'fr_metadata' => $mediaInfo[
'metadata'],
230 'fr_bits' => $row->fa_bits,
231 'fr_actor' => $row->fa_actor,
232 'fr_timestamp' => $row->fa_timestamp,
234 ] + $commentFieldsNew;
236 if ( $first && !$exists ) {
238 $destRel = $this->file->getRel();
239 $commentFields = $commentStore->insert( $dbw,
'img_description', $comment );
241 'img_name' => $row->fa_name,
242 'img_size' => $row->fa_size,
243 'img_width' => $row->fa_width,
244 'img_height' => $row->fa_height,
245 'img_metadata' => $mediaInfo[
'metadata'],
246 'img_bits' => $row->fa_bits,
247 'img_media_type' => $mediaInfo[
'media_type'],
248 'img_major_mime' => $mediaInfo[
'major_mime'],
249 'img_minor_mime' => $mediaInfo[
'minor_mime'],
250 'img_actor' => $row->fa_actor,
251 'img_timestamp' => $row->fa_timestamp,
256 if ( !$this->unsuppress && $row->fa_deleted ) {
257 $status->fatal(
'undeleterevdel' );
260 $fileRevisionRow[
'fr_archive_name'] =
'';
261 $fileRevisionRow[
'fr_deleted'] = 0;
263 $archiveName = $row->fa_archive_name;
265 if ( $archiveName ===
null ) {
269 $timestamp = (int)
wfTimestamp( TS_UNIX, $row->fa_deleted_timestamp );
272 $archiveName =
wfTimestamp( TS_MW, $timestamp ) .
'!' . $row->fa_name;
274 }
while ( isset( $archiveNames[$archiveName] ) );
277 $archiveNames[$archiveName] =
true;
278 $destRel = $this->file->getArchiveRel( $archiveName );
280 'oi_name' => $row->fa_name,
281 'oi_archive_name' => $archiveName,
282 'oi_size' => $row->fa_size,
283 'oi_width' => $row->fa_width,
284 'oi_height' => $row->fa_height,
285 'oi_bits' => $row->fa_bits,
286 'oi_actor' => $row->fa_actor,
287 'oi_timestamp' => $row->fa_timestamp,
288 'oi_metadata' => $mediaInfo[
'metadata'],
289 'oi_media_type' => $mediaInfo[
'media_type'],
290 'oi_major_mime' => $mediaInfo[
'major_mime'],
291 'oi_minor_mime' => $mediaInfo[
'minor_mime'],
292 'oi_deleted' => $this->unsuppress ? 0 : $row->fa_deleted,
294 ] + $commentStore->insert( $dbw,
'oi_description', $comment );
296 $fileRevisionRow[
'fr_archive_name'] = $archiveName;
297 $fileRevisionRow[
'fr_deleted'] = $this->unsuppress ? 0 : $row->fa_deleted;
299 $insertFileRevisions[] = $fileRevisionRow;
301 $deleteIds[] = $row->fa_id;
305 $status->successCount++;
307 $storeBatch[] = [ $deletedUrl,
'public', $destRel ];
308 $this->cleanupBatch[] = $row->fa_storage_key;
317 $missingIds = array_diff( $this->ids, $idsPresent );
319 foreach ( $missingIds as $id ) {
320 $status->error(
'undelete-missing-filearchive', $id );
323 if ( !$repo->hasSha1Storage() ) {
326 if ( !$checkStatus->isGood() ) {
327 $status->merge( $checkStatus );
330 $storeBatch = $checkStatus->value;
335 $status->merge( $storeStatus );
337 if ( !$status->isGood() ) {
341 $status->setOK(
false );
355 if ( $insertCurrent ) {
356 $dbw->newInsertQueryBuilder()
357 ->insertInto(
'image' )
358 ->row( $insertCurrent )
359 ->caller( __METHOD__ )->execute();
361 $dbw->newUpdateQueryBuilder()
363 ->set( [
'file_deleted' => 0 ] )
364 ->where( [
'file_id' => $this->file->acquireFileIdFromName() ] )
365 ->caller( __METHOD__ )->execute();
369 if ( $insertBatch ) {
370 $dbw->newInsertQueryBuilder()
371 ->insertInto(
'oldimage' )
372 ->rows( $insertBatch )
373 ->caller( __METHOD__ )->execute();
377 $dbw->newDeleteQueryBuilder()
378 ->deleteFrom(
'filearchive' )
379 ->where( [
'fa_id' => $deleteIds ] )
380 ->caller( __METHOD__ )->execute();
385 $insertFileRevisions = array_reverse( $insertFileRevisions );
387 foreach ( $insertFileRevisions as &$row ) {
388 $row[
'fr_file'] = $this->file->getFileIdFromName();
390 $dbw->newInsertQueryBuilder()
391 ->insertInto(
'filerevision' )
392 ->rows( $insertFileRevisions )
393 ->caller( __METHOD__ )->execute();
394 $latestId = $dbw->newSelectQueryBuilder()
396 ->from(
'filerevision' )
397 ->where( [
'fr_file' => $this->file->getFileIdFromName() ] )
398 ->orderBy(
'fr_timestamp',
'DESC' )
400 $dbw->newUpdateQueryBuilder()
402 ->set( [
'file_latest' => $latestId ] )
403 ->where( [
'file_id' => $this->file->getFileIdFromName() ] )
404 ->caller( __METHOD__ )->execute();
409 if ( $status->successCount > 0 || !$storeBatch || $repo->hasSha1Storage() ) {
411 wfDebug( __METHOD__ .
" restored {$status->successCount} items, creating a new current" );
413 DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [
'images' => 1 ] ) );
415 $this->file->purgeEverything();
417 wfDebug( __METHOD__ .
" restored {$status->successCount} as archived versions" );
418 $this->file->purgeDescription();
422 ScopedCallback::consume( $unlockScope );