26 use Wikimedia\ScopedCallback;
42 private $srcRels = [];
45 private $archiveUrls = [];
48 private $deletionBatch;
70 $this->reason = $reason;
71 $this->suppress = $suppress;
75 $this->srcRels[
'.'] = $this->file->getRel();
82 $this->srcRels[$oldName] = $this->file->getArchiveRel( $oldName );
83 $this->archiveUrls[] = $this->file->getArchiveUrl( $oldName );
93 $dbw = $this->file->repo->getPrimaryDB();
94 $result = $dbw->newSelectQueryBuilder()
95 ->select( [
'oi_archive_name' ] )
97 ->where( [
'oi_name' => $this->file->getName() ] )
98 ->caller( __METHOD__ )->fetchResultSet();
100 foreach ( $result as $row ) {
101 $this->
addOld( $row->oi_archive_name );
102 $archiveNames[] = $row->oi_archive_name;
105 return $archiveNames;
112 if ( !isset( $this->srcRels[
'.'] ) ) {
113 $oldRels =& $this->srcRels;
114 $deleteCurrent =
false;
116 $oldRels = $this->srcRels;
117 unset( $oldRels[
'.'] );
118 $deleteCurrent =
true;
121 return [ $oldRels, $deleteCurrent ];
130 [ $oldRels, $deleteCurrent ] = $this->
getOldRels();
132 if ( $deleteCurrent ) {
133 $hashes[
'.'] = $this->file->getSha1();
136 if ( count( $oldRels ) ) {
137 $dbw = $this->file->repo->getPrimaryDB();
140 [
'oi_archive_name',
'oi_sha1' ],
141 [
'oi_archive_name' => array_map(
'strval', array_keys( $oldRels ) ),
142 'oi_name' => $this->file->getName() ],
146 foreach ( $res as $row ) {
147 if ( $row->oi_archive_name ===
'' ) {
149 $hashes[$row->oi_archive_name] =
false;
152 if ( rtrim( $row->oi_sha1,
"\0" ) ===
'' ) {
154 $oldUrl = $this->file->getArchiveVirtualUrl( $row->oi_archive_name );
155 $props = $this->file->repo->getFileProps( $oldUrl );
157 if ( $props[
'fileExists'] ) {
159 $dbw->newUpdateQueryBuilder()
160 ->update(
'oldimage' )
161 ->set( [
'oi_sha1' => $props[
'sha1'] ] )
163 'oi_name' => $this->file->getName(),
164 'oi_archive_name' => $row->oi_archive_name,
166 ->caller( __METHOD__ )->execute();
167 $hashes[$row->oi_archive_name] = $props[
'sha1'];
169 $hashes[$row->oi_archive_name] =
false;
172 $hashes[$row->oi_archive_name] = $row->oi_sha1;
177 $missing = array_diff_key( $this->srcRels, $hashes );
179 foreach ( $missing as $name => $rel ) {
180 $status->
error(
'filedelete-old-unregistered', $name );
183 foreach ( $hashes as $name => $hash ) {
185 $status->
error(
'filedelete-missing', $this->srcRels[$name] );
186 unset( $hashes[$name] );
195 $dbw = $this->file->repo->getPrimaryDB();
197 $commentStore = MediaWikiServices::getInstance()->getCommentStore();
199 $encTimestamp = $dbw->addQuotes( $dbw->timestamp( $now ) );
200 $encUserId = $dbw->addQuotes( $this->user->getId() );
201 $encGroup = $dbw->addQuotes(
'deleted' );
202 $ext = $this->file->getExtension();
203 $dotExt =
$ext ===
'' ?
'' :
".$ext";
204 $encExt = $dbw->addQuotes( $dotExt );
205 [ $oldRels, $deleteCurrent ] = $this->getOldRels();
208 if ( $this->suppress ) {
209 $bitfield = RevisionRecord::SUPPRESSED_ALL;
211 $bitfield =
'oi_deleted';
214 if ( $deleteCurrent ) {
215 $tables = [
'image' ];
217 'fa_storage_group' => $encGroup,
218 'fa_storage_key' => $dbw->conditional(
219 [
'img_sha1' =>
'' ],
220 $dbw->addQuotes(
'' ),
221 $dbw->buildConcat( [
"img_sha1", $encExt ] )
223 'fa_deleted_user' => $encUserId,
224 'fa_deleted_timestamp' => $encTimestamp,
225 'fa_deleted' => $this->suppress ? $bitfield : 0,
226 'fa_name' =>
'img_name',
227 'fa_archive_name' =>
'NULL',
228 'fa_size' =>
'img_size',
229 'fa_width' =>
'img_width',
230 'fa_height' =>
'img_height',
231 'fa_metadata' =>
'img_metadata',
232 'fa_bits' =>
'img_bits',
233 'fa_media_type' =>
'img_media_type',
234 'fa_major_mime' =>
'img_major_mime',
235 'fa_minor_mime' =>
'img_minor_mime',
236 'fa_description_id' =>
'img_description_id',
237 'fa_timestamp' =>
'img_timestamp',
238 'fa_sha1' =>
'img_sha1',
239 'fa_actor' =>
'img_actor',
243 $fields += array_map(
244 [ $dbw,
'addQuotes' ],
245 $commentStore->insert( $dbw,
'fa_deleted_reason', $this->reason )
248 $dbw->insertSelect(
'filearchive', $tables, $fields,
249 [
'img_name' => $this->file->getName() ], __METHOD__, [], [], $joins );
252 if ( count( $oldRels ) ) {
253 $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbw );
256 ->where( [
'oi_name' => $this->file->getName() ] )
257 ->andWhere( [
'oi_archive_name' => array_map(
'strval', array_keys( $oldRels ) ) ] );
258 $res = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
260 if ( $res->numRows() ) {
261 $reason = $commentStore->createComment( $dbw, $this->reason );
262 foreach ( $res as $row ) {
263 $comment = $commentStore->getComment(
'oi_description', $row );
266 'fa_storage_group' =>
'deleted',
267 'fa_storage_key' => ( $row->oi_sha1 ===
'' )
269 :
"{$row->oi_sha1}{$dotExt}",
270 'fa_deleted_user' => $this->user->getId(),
271 'fa_deleted_timestamp' => $dbw->timestamp( $now ),
273 'fa_deleted' => $this->suppress ? $bitfield : $row->oi_deleted,
274 'fa_name' => $row->oi_name,
275 'fa_archive_name' => $row->oi_archive_name,
276 'fa_size' => $row->oi_size,
277 'fa_width' => $row->oi_width,
278 'fa_height' => $row->oi_height,
279 'fa_metadata' => $row->oi_metadata,
280 'fa_bits' => $row->oi_bits,
281 'fa_media_type' => $row->oi_media_type,
282 'fa_major_mime' => $row->oi_major_mime,
283 'fa_minor_mime' => $row->oi_minor_mime,
284 'fa_actor' => $row->oi_actor,
285 'fa_timestamp' => $row->oi_timestamp,
286 'fa_sha1' => $row->oi_sha1
287 ] + $commentStore->insert( $dbw,
'fa_deleted_reason', $reason )
288 + $commentStore->insert( $dbw,
'fa_description', $comment );
292 $dbw->newInsertQueryBuilder()
293 ->insertInto(
'filearchive' )
294 ->rows( $rowsInsert )
295 ->caller( __METHOD__ )->execute();
299 private function doDBDeletes() {
300 $dbw = $this->file->repo->getPrimaryDB();
301 [ $oldRels, $deleteCurrent ] = $this->getOldRels();
303 if ( count( $oldRels ) ) {
304 $dbw->newDeleteQueryBuilder()
305 ->deleteFrom(
'oldimage' )
307 'oi_name' => $this->file->getName(),
308 'oi_archive_name' => array_map(
'strval', array_keys( $oldRels ) )
310 ->caller( __METHOD__ )->execute();
313 if ( $deleteCurrent ) {
314 $dbw->newDeleteQueryBuilder()
315 ->deleteFrom(
'image' )
316 ->where( [
'img_name' => $this->file->getName() ] )
317 ->caller( __METHOD__ )->execute();
326 $repo = $this->file->getRepo();
327 $lockStatus = $this->file->acquireFileLock();
328 if ( !$lockStatus->isOK() ) {
331 $unlockScope =
new ScopedCallback(
function () {
332 $this->file->releaseFileLock();
335 $status = $this->file->repo->
newGood();
337 $hashes = $this->getHashes( $status );
338 $this->deletionBatch = [];
339 $ext = $this->file->getExtension();
340 $dotExt =
$ext ===
'' ?
'' :
".$ext";
342 foreach ( $this->srcRels as $name => $srcRel ) {
344 if ( isset( $hashes[$name] ) ) {
345 $hash = $hashes[$name];
346 $key = $hash . $dotExt;
347 $dstRel = $repo->getDeletedHashPath( $key ) . $key;
348 $this->deletionBatch[$name] = [ $srcRel, $dstRel ];
352 if ( !$repo->hasSha1Storage() ) {
355 $checkStatus = $this->removeNonexistentFiles( $this->deletionBatch );
356 if ( !$checkStatus->isGood() ) {
357 $status->merge( $checkStatus );
360 $this->deletionBatch = $checkStatus->value;
363 $status = $this->file->repo->deleteBatch( $this->deletionBatch );
364 if ( !$status->isGood() ) {
365 $status->merge( $status );
369 if ( !$status->isOK() ) {
374 $dbw = $this->file->repo->getPrimaryDB();
376 $dbw->startAtomic( __METHOD__ );
379 $this->doDBInserts();
381 $this->doDBDeletes();
385 $dbw->endAtomic( __METHOD__ );
388 ScopedCallback::consume( $unlockScope );
401 foreach ( $batch as [ $src, ] ) {
402 $files[$src] = $this->file->repo->getVirtualUrl(
'public' ) .
'/' . rawurlencode( $src );
405 $result = $this->file->repo->fileExistsBatch( $files );
406 if ( in_array(
null, $result,
true ) ) {
407 return Status::newFatal(
'backend-fail-internal',
408 $this->file->repo->getBackend()->getName() );
412 foreach ( $batch as $batchItem ) {
413 if ( $result[$batchItem[0]] ) {
414 $newBatch[] = $batchItem;
418 return Status::newGood( $newBatch );
Implements some public methods and some protected utility functions which are required by multiple ch...
Helper class for file deletion.
getHashes(StatusValue $status)
addOlds()
Add the old versions of the image to the batch.
removeNonexistentFiles( $batch)
Removes non-existent files from a deletion batch.
__construct(File $file, UserIdentity $user, $reason='', $suppress=false)
execute()
Run the transaction.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
error( $message,... $parameters)
Add an error, do not set fatal flag This can be used for non-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.
if(!is_readable( $file)) $ext