Файловый менеджер - Редактировать - /var/www/html/mediawiki-1.43.1/includes/filerepo/file/LocalFileDeleteBatch.php
Ðазад
<?php /** * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * * @file */ use MediaWiki\FileRepo\File\FileSelectQueryBuilder; use MediaWiki\MediaWikiServices; use MediaWiki\Revision\RevisionRecord; use MediaWiki\Status\Status; use MediaWiki\User\UserIdentity; use Wikimedia\ScopedCallback; /** * Helper class for file deletion * * @internal * @ingroup FileAbstraction */ class LocalFileDeleteBatch { /** @var LocalFile */ private $file; /** @var string */ private $reason; /** @var array */ private $srcRels = []; /** @var array */ private $archiveUrls = []; /** @var array[] Items to be processed in the deletion batch */ private $deletionBatch; /** @var bool Whether to suppress all suppressable fields when deleting */ private $suppress; /** @var UserIdentity */ private $user; /** * @param File $file * @param UserIdentity $user * @param string $reason * @param bool $suppress */ public function __construct( File $file, UserIdentity $user, $reason = '', $suppress = false ) { $this->file = $file; $this->user = $user; $this->reason = $reason; $this->suppress = $suppress; } public function addCurrent() { $this->srcRels['.'] = $this->file->getRel(); } /** * @param string $oldName */ public function addOld( $oldName ) { $this->srcRels[$oldName] = $this->file->getArchiveRel( $oldName ); $this->archiveUrls[] = $this->file->getArchiveUrl( $oldName ); } /** * Add the old versions of the image to the batch * @return string[] List of archive names from old versions */ public function addOlds() { $archiveNames = []; $dbw = $this->file->repo->getPrimaryDB(); $result = $dbw->newSelectQueryBuilder() ->select( [ 'oi_archive_name' ] ) ->from( 'oldimage' ) ->where( [ 'oi_name' => $this->file->getName() ] ) ->caller( __METHOD__ )->fetchResultSet(); foreach ( $result as $row ) { $this->addOld( $row->oi_archive_name ); $archiveNames[] = $row->oi_archive_name; } return $archiveNames; } /** * @return array */ protected function getOldRels() { if ( !isset( $this->srcRels['.'] ) ) { $oldRels =& $this->srcRels; $deleteCurrent = false; } else { $oldRels = $this->srcRels; unset( $oldRels['.'] ); $deleteCurrent = true; } return [ $oldRels, $deleteCurrent ]; } /** * @param StatusValue $status To add error messages to * @return array */ protected function getHashes( StatusValue $status ): array { $hashes = []; [ $oldRels, $deleteCurrent ] = $this->getOldRels(); if ( $deleteCurrent ) { $hashes['.'] = $this->file->getSha1(); } if ( count( $oldRels ) ) { $dbw = $this->file->repo->getPrimaryDB(); $res = $dbw->newSelectQueryBuilder() ->select( [ 'oi_archive_name', 'oi_sha1' ] ) ->from( 'oldimage' ) ->where( [ 'oi_archive_name' => array_map( 'strval', array_keys( $oldRels ) ), 'oi_name' => $this->file->getName() // performance ] ) ->caller( __METHOD__ )->fetchResultSet(); foreach ( $res as $row ) { if ( $row->oi_archive_name === '' ) { // File lost, the check simulates OldLocalFile::exists $hashes[$row->oi_archive_name] = false; continue; } if ( rtrim( $row->oi_sha1, "\0" ) === '' ) { // Get the hash from the file $oldUrl = $this->file->getArchiveVirtualUrl( $row->oi_archive_name ); $props = $this->file->repo->getFileProps( $oldUrl ); if ( $props['fileExists'] ) { // Upgrade the oldimage row $dbw->newUpdateQueryBuilder() ->update( 'oldimage' ) ->set( [ 'oi_sha1' => $props['sha1'] ] ) ->where( [ 'oi_name' => $this->file->getName(), 'oi_archive_name' => $row->oi_archive_name, ] ) ->caller( __METHOD__ )->execute(); $hashes[$row->oi_archive_name] = $props['sha1']; } else { $hashes[$row->oi_archive_name] = false; } } else { $hashes[$row->oi_archive_name] = $row->oi_sha1; } } } $missing = array_diff_key( $this->srcRels, $hashes ); foreach ( $missing as $name => $rel ) { $status->error( 'filedelete-old-unregistered', $name ); } foreach ( $hashes as $name => $hash ) { if ( !$hash ) { $status->error( 'filedelete-missing', $this->srcRels[$name] ); unset( $hashes[$name] ); } } return $hashes; } protected function doDBInserts() { $now = time(); $dbw = $this->file->repo->getPrimaryDB(); $commentStore = MediaWikiServices::getInstance()->getCommentStore(); $encTimestamp = $dbw->addQuotes( $dbw->timestamp( $now ) ); $encUserId = $dbw->addQuotes( $this->user->getId() ); $encGroup = $dbw->addQuotes( 'deleted' ); $ext = $this->file->getExtension(); $dotExt = $ext === '' ? '' : ".$ext"; $encExt = $dbw->addQuotes( $dotExt ); [ $oldRels, $deleteCurrent ] = $this->getOldRels(); // Bitfields to further suppress the content if ( $this->suppress ) { $bitfield = RevisionRecord::SUPPRESSED_ALL; } else { $bitfield = 'oi_deleted'; } if ( $deleteCurrent ) { $tables = [ 'image' ]; $fields = [ 'fa_storage_group' => $encGroup, 'fa_storage_key' => $dbw->conditional( [ 'img_sha1' => '' ], $dbw->addQuotes( '' ), $dbw->buildConcat( [ "img_sha1", $encExt ] ) ), 'fa_deleted_user' => $encUserId, 'fa_deleted_timestamp' => $encTimestamp, 'fa_deleted' => $this->suppress ? $bitfield : 0, 'fa_name' => 'img_name', 'fa_archive_name' => 'NULL', 'fa_size' => 'img_size', 'fa_width' => 'img_width', 'fa_height' => 'img_height', 'fa_metadata' => 'img_metadata', 'fa_bits' => 'img_bits', 'fa_media_type' => 'img_media_type', 'fa_major_mime' => 'img_major_mime', 'fa_minor_mime' => 'img_minor_mime', 'fa_description_id' => 'img_description_id', 'fa_timestamp' => 'img_timestamp', 'fa_sha1' => 'img_sha1', 'fa_actor' => 'img_actor', ]; $joins = []; $fields += array_map( [ $dbw, 'addQuotes' ], $commentStore->insert( $dbw, 'fa_deleted_reason', $this->reason ) ); $dbw->insertSelect( 'filearchive', $tables, $fields, [ 'img_name' => $this->file->getName() ], __METHOD__, [ 'IGNORE' ], [], $joins ); } if ( count( $oldRels ) ) { $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbw ); $queryBuilder ->forUpdate() ->where( [ 'oi_name' => $this->file->getName() ] ) ->andWhere( [ 'oi_archive_name' => array_map( 'strval', array_keys( $oldRels ) ) ] ); $res = $queryBuilder->caller( __METHOD__ )->fetchResultSet(); $rowsInsert = []; if ( $res->numRows() ) { $reason = $commentStore->createComment( $dbw, $this->reason ); foreach ( $res as $row ) { $comment = $commentStore->getComment( 'oi_description', $row ); $rowsInsert[] = [ // Deletion-specific fields 'fa_storage_group' => 'deleted', 'fa_storage_key' => ( $row->oi_sha1 === '' ) ? '' : "{$row->oi_sha1}{$dotExt}", 'fa_deleted_user' => $this->user->getId(), 'fa_deleted_timestamp' => $dbw->timestamp( $now ), // Counterpart fields 'fa_deleted' => $this->suppress ? $bitfield : $row->oi_deleted, 'fa_name' => $row->oi_name, 'fa_archive_name' => $row->oi_archive_name, 'fa_size' => $row->oi_size, 'fa_width' => $row->oi_width, 'fa_height' => $row->oi_height, 'fa_metadata' => $row->oi_metadata, 'fa_bits' => $row->oi_bits, 'fa_media_type' => $row->oi_media_type, 'fa_major_mime' => $row->oi_major_mime, 'fa_minor_mime' => $row->oi_minor_mime, 'fa_actor' => $row->oi_actor, 'fa_timestamp' => $row->oi_timestamp, 'fa_sha1' => $row->oi_sha1 ] + $commentStore->insert( $dbw, 'fa_deleted_reason', $reason ) + $commentStore->insert( $dbw, 'fa_description', $comment ); } } $dbw->newInsertQueryBuilder() ->insertInto( 'filearchive' ) ->ignore() ->rows( $rowsInsert ) ->caller( __METHOD__ )->execute(); } } private function doDBDeletes() { $dbw = $this->file->repo->getPrimaryDB(); [ $oldRels, $deleteCurrent ] = $this->getOldRels(); if ( count( $oldRels ) ) { $dbw->newDeleteQueryBuilder() ->deleteFrom( 'oldimage' ) ->where( [ 'oi_name' => $this->file->getName(), 'oi_archive_name' => array_map( 'strval', array_keys( $oldRels ) ) ] ) ->caller( __METHOD__ )->execute(); } if ( $deleteCurrent ) { $dbw->newDeleteQueryBuilder() ->deleteFrom( 'image' ) ->where( [ 'img_name' => $this->file->getName() ] ) ->caller( __METHOD__ )->execute(); } } /** * Run the transaction * @return Status */ public function execute() { $repo = $this->file->getRepo(); $lockStatus = $this->file->acquireFileLock(); if ( !$lockStatus->isOK() ) { return $lockStatus; } $unlockScope = new ScopedCallback( function () { $this->file->releaseFileLock(); } ); $status = $this->file->repo->newGood(); // Prepare deletion batch $hashes = $this->getHashes( $status ); $this->deletionBatch = []; $ext = $this->file->getExtension(); $dotExt = $ext === '' ? '' : ".$ext"; foreach ( $this->srcRels as $name => $srcRel ) { // Skip files that have no hash (e.g. missing DB record, or sha1 field and file source) if ( isset( $hashes[$name] ) ) { $hash = $hashes[$name]; $key = $hash . $dotExt; $dstRel = $repo->getDeletedHashPath( $key ) . $key; $this->deletionBatch[$name] = [ $srcRel, $dstRel ]; } } if ( !$repo->hasSha1Storage() ) { // Removes non-existent file from the batch, so we don't get errors. // This also handles files in the 'deleted' zone deleted via revision deletion. $checkStatus = $this->removeNonexistentFiles( $this->deletionBatch ); if ( !$checkStatus->isGood() ) { $status->merge( $checkStatus ); return $status; } $this->deletionBatch = $checkStatus->value; // Execute the file deletion batch $status = $this->file->repo->deleteBatch( $this->deletionBatch ); if ( !$status->isGood() ) { $status->merge( $status ); } } if ( !$status->isOK() ) { // Critical file deletion error; abort return $status; } $dbw = $this->file->repo->getPrimaryDB(); $dbw->startAtomic( __METHOD__ ); // Copy the image/oldimage rows to filearchive $this->doDBInserts(); // Delete image/oldimage rows $this->doDBDeletes(); // This is typically a no-op since we are wrapped by another atomic // section in FileDeleteForm and also the implicit transaction. $dbw->endAtomic( __METHOD__ ); // Commit and return ScopedCallback::consume( $unlockScope ); return $status; } /** * Removes non-existent files from a deletion batch. * @param array[] $batch * @return Status A good status with existing files in $batch as value, or a fatal status in case of I/O errors. */ protected function removeNonexistentFiles( $batch ) { $files = []; foreach ( $batch as [ $src, /* dest */ ] ) { $files[$src] = $this->file->repo->getVirtualUrl( 'public' ) . '/' . rawurlencode( $src ); } $result = $this->file->repo->fileExistsBatch( $files ); if ( in_array( null, $result, true ) ) { return Status::newFatal( 'backend-fail-internal', $this->file->repo->getBackend()->getName() ); } $newBatch = []; foreach ( $batch as $batchItem ) { if ( $result[$batchItem[0]] ) { $newBatch[] = $batchItem; } } return Status::newGood( $newBatch ); } }
| ver. 1.1 | |
.
| PHP 8.4.18 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка