22 use Wikimedia\ScopedCallback;
35 private $cleanupBatch;
52 $this->cleanupBatch = [];
54 $this->unsuppress = $unsuppress;
61 public function addId( $fa_id ) {
62 $this->ids[] = $fa_id;
70 $this->ids = array_merge( $this->ids, $ids );
92 $repo = $this->file->getRepo();
93 if ( !$this->all && !$this->ids ) {
95 return $repo->newGood();
98 $status = $this->file->acquireFileLock();
99 if ( !$status->isOK() ) {
103 $dbw = $this->file->repo->getPrimaryDB();
105 $ownTrx = !$dbw->trxLevel();
106 $funcName = __METHOD__;
107 $dbw->startAtomic( __METHOD__ );
109 $unlockScope =
new ScopedCallback(
function () use ( $dbw, $funcName ) {
110 $dbw->endAtomic( $funcName );
111 $this->file->releaseFileLock();
114 $commentStore = MediaWikiServices::getInstance()->getCommentStore();
116 $status = $this->file->repo->newGood();
118 $exists = (bool)$dbw->selectField(
'image',
'1',
119 [
'img_name' => $this->file->getName() ],
124 $ownTrx ? [] : [
'LOCK IN SHARE MODE' ]
129 $conditions = [
'fa_name' => $this->file->getName() ];
132 $conditions[
'fa_id'] = $this->ids;
136 $result = $dbw->select(
137 $arFileQuery[
'tables'],
138 $arFileQuery[
'fields'],
141 [
'ORDER BY' =>
'fa_timestamp DESC' ],
142 $arFileQuery[
'joins']
148 $insertCurrent =
false;
153 foreach ( $result as $row ) {
154 $idsPresent[] = $row->fa_id;
156 if ( $row->fa_name != $this->file->getName() ) {
157 $status->error(
'undelete-filename-mismatch',
$wgLang->timeanddate( $row->fa_timestamp ) );
158 $status->failCount++;
162 if ( $row->fa_storage_key ==
'' ) {
164 $status->error(
'undelete-bad-store-key',
$wgLang->timeanddate( $row->fa_timestamp ) );
165 $status->failCount++;
169 $deletedRel = $repo->getDeletedHashPath( $row->fa_storage_key ) .
170 $row->fa_storage_key;
171 $deletedUrl = $repo->getVirtualUrl() .
'/deleted/' . $deletedRel;
173 if ( isset( $row->fa_sha1 ) ) {
174 $sha1 = $row->fa_sha1;
181 if ( strlen( $sha1 ) == 32 && $sha1[0] ==
'0' ) {
182 $sha1 = substr( $sha1, 1 );
185 if ( $row->fa_major_mime ===
null || $row->fa_major_mime ==
'unknown'
186 || $row->fa_minor_mime ===
null || $row->fa_minor_mime ==
'unknown'
187 || $row->fa_media_type ===
null || $row->fa_media_type ==
'UNKNOWN'
188 || $row->fa_metadata ===
null
192 $this->file->loadFromFile( $deletedUrl );
193 $mime = $this->file->getMimeType();
196 'minor_mime' => $minorMime,
197 'major_mime' => $majorMime,
198 'media_type' => $this->file->getMediaType(),
199 'metadata' => $this->file->getMetadataForDb( $dbw )
203 'minor_mime' => $row->fa_minor_mime,
204 'major_mime' => $row->fa_major_mime,
205 'media_type' => $row->fa_media_type,
206 'metadata' => $row->fa_metadata
210 $comment = $commentStore->getComment(
'fa_description', $row );
211 if ( $first && !$exists ) {
213 $destRel = $this->file->getRel();
214 $commentFields = $commentStore->insert( $dbw,
'img_description', $comment );
216 'img_name' => $row->fa_name,
217 'img_size' => $row->fa_size,
218 'img_width' => $row->fa_width,
219 'img_height' => $row->fa_height,
220 'img_metadata' => $mediaInfo[
'metadata'],
221 'img_bits' => $row->fa_bits,
222 'img_media_type' => $mediaInfo[
'media_type'],
223 'img_major_mime' => $mediaInfo[
'major_mime'],
224 'img_minor_mime' => $mediaInfo[
'minor_mime'],
225 'img_actor' => $row->fa_actor,
226 'img_timestamp' => $row->fa_timestamp,
231 if ( !$this->unsuppress && $row->fa_deleted ) {
232 $status->fatal(
'undeleterevdel' );
236 $archiveName = $row->fa_archive_name;
238 if ( $archiveName ==
'' ) {
242 $timestamp = (int)
wfTimestamp( TS_UNIX, $row->fa_deleted_timestamp );
245 $archiveName =
wfTimestamp( TS_MW, $timestamp ) .
'!' . $row->fa_name;
247 }
while ( isset( $archiveNames[$archiveName] ) );
250 $archiveNames[$archiveName] =
true;
251 $destRel = $this->file->getArchiveRel( $archiveName );
253 'oi_name' => $row->fa_name,
254 'oi_archive_name' => $archiveName,
255 'oi_size' => $row->fa_size,
256 'oi_width' => $row->fa_width,
257 'oi_height' => $row->fa_height,
258 'oi_bits' => $row->fa_bits,
259 'oi_actor' => $row->fa_actor,
260 'oi_timestamp' => $row->fa_timestamp,
261 'oi_metadata' => $mediaInfo[
'metadata'],
262 'oi_media_type' => $mediaInfo[
'media_type'],
263 'oi_major_mime' => $mediaInfo[
'major_mime'],
264 'oi_minor_mime' => $mediaInfo[
'minor_mime'],
265 'oi_deleted' => $this->unsuppress ? 0 : $row->fa_deleted,
267 ] + $commentStore->insert( $dbw,
'oi_description', $comment );
270 $deleteIds[] = $row->fa_id;
274 $status->successCount++;
276 $storeBatch[] = [ $deletedUrl,
'public', $destRel ];
277 $this->cleanupBatch[] = $row->fa_storage_key;
286 $missingIds = array_diff( $this->ids, $idsPresent );
288 foreach ( $missingIds as $id ) {
289 $status->error(
'undelete-missing-filearchive', $id );
292 if ( !$repo->hasSha1Storage() ) {
295 if ( !$checkStatus->isGood() ) {
296 $status->merge( $checkStatus );
299 $storeBatch = $checkStatus->value;
304 $status->merge( $storeStatus );
306 if ( !$status->isGood() ) {
310 $status->setOK(
false );
321 if ( $insertCurrent ) {
322 $dbw->insert(
'image', $insertCurrent, __METHOD__ );
325 if ( $insertBatch ) {
326 $dbw->insert(
'oldimage', $insertBatch, __METHOD__ );
330 $dbw->delete(
'filearchive',
331 [
'fa_id' => $deleteIds ],
336 if ( $status->successCount > 0 || !$storeBatch || $repo->hasSha1Storage() ) {
338 wfDebug( __METHOD__ .
" restored {$status->successCount} items, creating a new current" );
342 $this->file->purgeEverything();
344 wfDebug( __METHOD__ .
" restored {$status->successCount} as archived versions" );
345 $this->file->purgeDescription();
349 ScopedCallback::consume( $unlockScope );
360 $files = $filteredTriplets = [];
361 foreach ( $triplets as $file ) {
365 $result = $this->file->repo->fileExistsBatch( $files );
366 if ( in_array(
null, $result,
true ) ) {
368 $this->file->repo->getBackend()->getName() );
371 foreach ( $triplets as
$file ) {
372 if ( $result[
$file[0]] ) {
373 $filteredTriplets[] =
$file;
386 $files = $newBatch = [];
387 $repo = $this->file->repo;
389 foreach ( $batch as $file ) {
390 $files[
$file] = $repo->getVirtualUrl(
'deleted' ) .
'/' .
391 rawurlencode( $repo->getDeletedHashPath(
$file ) .
$file );
394 $result = $repo->fileExistsBatch( $files );
396 foreach ( $batch as
$file ) {
397 if ( $result[
$file] ) {
411 if ( !$this->cleanupBatch ) {
412 return $this->file->repo->newGood();
417 $status = $this->file->repo->cleanupDeletedBatch( $this->cleanupBatch );
432 foreach ( $storeStatus->success as $i =>
$success ) {
437 $cleanupBatch[] = [ $storeBatch[$i][1], $storeBatch[$i][2] ];
440 $this->file->repo->cleanupBatch( $cleanupBatch );
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) $wgLang
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new archivedfile object.
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the pending update queue for execution at the appropriate time.
static splitMime(?string $mime)
Split an internet media type into its two components; if not a two-part name, set the minor type to '...
Helper class for file undeletion.
execute()
Run the transaction, except the cleanup batch.
addIds( $ids)
Add a whole lot of files by ID.
addAll()
Add all revisions of the file.
removeNonexistentFromCleanup( $batch)
Removes non-existent files from a cleanup batch.
addId( $fa_id)
Add a file by ID.
cleanup()
Delete unused files in the deleted zone.
removeNonexistentFiles( $triplets)
Removes non-existent files from a store batch.
cleanupFailedBatch( $storeStatus, $storeBatch)
Cleanup a failed batch.
__construct(LocalFile $file, $unsuppress=false)
Local file in the wiki's own database.
static getHashFromKey( $key)
Gets the SHA1 hash from a storage key.
static factory(array $deltas)
static newFatal( $message,... $parameters)
Factory function for fatal errors.
static newGood( $value=null)
Factory function for good results.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.