Localisation updates from https://translatewiki.net.
[mediawiki.git] / includes / jobqueue / jobs / UploadJobTrait.php
blob4d82380e2f7f6f6f7cab5938e0ac7e7c49138978
1 <?php
2 /**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
18 * @file
19 * @defgroup JobQueue JobQueue
22 use MediaWiki\Api\ApiUpload;
23 use MediaWiki\Context\RequestContext;
24 use MediaWiki\Logger\LoggerFactory;
25 use MediaWiki\Status\Status;
26 use MediaWiki\User\User;
27 use Wikimedia\ScopedCallback;
29 /**
30 * Common functionality for async uploads
32 * @ingroup Upload
33 * @ingroup JobQueue
35 trait UploadJobTrait {
36 /** @var User|null */
37 private $user;
39 /** @var string */
40 private $cacheKey;
42 /** @var UploadBase */
43 private $upload;
45 /** @var array The job parameters */
46 public $params;
48 /**
49 * Set up the job
51 * @param string $cacheKey
52 * @return void
54 protected function initialiseUploadJob( $cacheKey ): void {
55 $this->cacheKey = $cacheKey;
56 $this->user = null;
59 /**
60 * Do not allow retries on jobs by default.
62 * @return bool
64 public function allowRetries(): bool {
65 return false;
68 /**
69 * Run the job
71 * @return bool
73 public function run(): bool {
74 $this->user = $this->getUserFromSession();
75 if ( $this->user === null ) {
76 return false;
79 try {
80 // Check the initial status of the upload
81 $startingStatus = UploadBase::getSessionStatus( $this->user, $this->cacheKey );
82 // Warn if in wrong stage, but still continue. User may be able to trigger
83 // this by retrying after failure.
84 if (
85 !$startingStatus ||
86 ( $startingStatus['result'] ?? '' ) !== 'Poll' ||
87 ( $startingStatus['stage'] ?? '' ) !== 'queued'
88 ) {
89 $logger = LoggerFactory::getInstance( 'upload' );
90 $logger->warning( "Tried to publish upload that is in stage {stage}/{result}",
91 $this->logJobParams( $startingStatus )
95 // Fetch the file if needed
96 if ( !$this->fetchFile() ) {
97 return false;
100 // Verify the upload is valid
101 if ( !$this->verifyUpload() ) {
102 return false;
105 // Actually upload the file
106 if ( !$this->performUpload() ) {
107 return false;
110 // All done
111 $this->setStatusDone();
113 // Cleanup any temporary local file
114 $this->getUpload()->cleanupTempFile();
116 } catch ( Exception $e ) {
117 $this->setStatus( 'publish', 'Failure', Status::newFatal( 'api-error-publishfailed' ) );
118 $this->setLastError( get_class( $e ) . ": " . $e->getMessage() );
119 // To prevent potential database referential integrity issues.
120 // See T34551.
121 MWExceptionHandler::rollbackPrimaryChangesAndLog( $e );
122 return false;
125 return true;
129 * Get the cache key used to store status
131 * @return string
133 public function getCacheKey() {
134 return $this->cacheKey;
138 * Get user data from the session key
140 * @return User|null
142 private function getUserFromSession() {
143 $scope = RequestContext::importScopedSession( $this->params['session'] );
144 $this->addTeardownCallback( static function () use ( &$scope ) {
145 ScopedCallback::consume( $scope ); // T126450
146 } );
148 $context = RequestContext::getMain();
149 $user = $context->getUser();
150 if ( !$user->isRegistered() ) {
151 $this->setLastError( "Could not load the author user from session." );
153 return null;
155 return $user;
159 * Set the upload status
161 * @param string $stage
162 * @param string $result
163 * @param Status|null $status
164 * @param array $additionalInfo
167 private function setStatus( $stage, $result = 'Poll', $status = null, $additionalInfo = [] ) {
168 // We're most probably not running in a job.
169 // @todo maybe throw an exception?
170 if ( $this->user === null ) {
171 return;
173 $status ??= Status::newGood();
174 $info = [ 'result' => $result, 'stage' => $stage, 'status' => $status ];
175 $info += $additionalInfo;
176 UploadBase::setSessionStatus(
177 $this->user,
178 $this->cacheKey,
179 $info
184 * Ensure we have the file available. A noop here.
186 * @return bool
188 protected function fetchFile(): bool {
189 $this->setStatus( 'fetching' );
190 // make sure the upload file is here. This is a noop in most cases.
191 $status = $this->getUpload()->fetchFile();
192 if ( !$status->isGood() ) {
193 $this->setStatus( 'fetching', 'Failure', $status );
194 $this->setLastError( "Error while fetching the image." );
195 return false;
197 $this->setStatus( 'publish' );
198 // We really don't care as this is, as mentioned, generally a noop.
199 // When that's not the case, classes will need to override this method anyways.
200 return true;
204 * Verify the upload is ok
206 * @return bool
208 private function verifyUpload(): bool {
209 // Check if the local file checks out (this is generally a no-op)
210 $verification = $this->getUpload()->verifyUpload();
211 if ( $verification['status'] !== UploadBase::OK ) {
212 $status = Status::newFatal( 'verification-error' );
213 $status->value = [ 'verification' => $verification ];
214 $this->setStatus( 'publish', 'Failure', $status );
215 $this->setLastError( "Could not verify upload." );
216 return false;
218 // Verify title permissions for this user
219 $titleVerification = $this->getUpload()->verifyTitlePermissions( $this->user );
220 if ( $titleVerification !== true ) {
221 $this->setStatus( 'publish', 'Failure', null, $titleVerification );
222 $this->setLastError( "Could not verify title permissions." );
223 return false;
226 // Verify if any upload warnings are present
227 $ignoreWarnings = $this->params['ignorewarnings'] ?? false;
228 $isReupload = $this->params['reupload'] ?? false;
229 if ( $ignoreWarnings ) {
230 // If we're ignoring warnings, we don't need to check them
231 return true;
233 $warnings = $this->getUpload()->checkWarnings( $this->user );
234 if ( $warnings ) {
235 // If the file exists and we're reuploading, ignore the warning
236 // and continue with the upload
237 if ( count( $warnings ) === 1 && isset( $warnings['exists'] ) && $isReupload ) {
238 return true;
240 // Make the array serializable
241 $serializableWarnings = UploadBase::makeWarningsSerializable( $warnings );
242 $this->setStatus( 'publish', 'Warning', null, [ 'warnings' => $serializableWarnings ] );
243 $this->setLastError( "Upload warnings present." );
244 return false;
247 return true;
251 * Upload the stashed file to a permanent location
253 * @return bool
255 private function performUpload(): bool {
256 if ( $this->user === null ) {
257 return false;
259 $status = $this->getUpload()->performUpload(
260 $this->params['comment'],
261 $this->params['text'],
262 $this->params['watch'],
263 $this->user,
264 $this->params['tags'] ?? [],
265 $this->params['watchlistexpiry'] ?? null
267 if ( !$status->isGood() ) {
268 $this->setStatus( 'publish', 'Failure', $status );
269 $this->setLastError( $status->getWikiText( false, false, 'en' ) );
270 return false;
272 return true;
276 * Set the status at the end or processing
279 private function setStatusDone() {
280 // Build the image info array while we have the local reference handy
281 $imageInfo = ApiUpload::getDummyInstance()->getUploadImageInfo( $this->getUpload() );
283 // Cache the info so the user doesn't have to wait forever to get the final info
284 $this->setStatus(
285 'publish',
286 'Success',
287 Status::newGood(),
288 [ 'filename' => $this->getUpload()->getLocalFile()->getName(), 'imageinfo' => $imageInfo ]
293 * Getter for the upload. Needs to be implemented by the job class
295 * @return UploadBase
297 abstract protected function getUpload(): UploadBase;
300 * Get the job parameters for logging. Needs to be implemented by the job class.
302 * @param Status[] $status
303 * @return array
305 abstract protected function logJobParams( $status ): array;
308 * This is actually implemented in the Job class
310 * @param mixed $error
311 * @return void
313 abstract protected function setLastError( $error );
316 * This is actually implemented in the Job class
318 * @param callable $callback
319 * @return void
321 abstract protected function addTeardownCallback( $callback );