summaryrefslogtreecommitdiff
path: root/bin/reevotech/vendor/addwiki/mediawiki-api/src/Service/FileUploader.php
blob: 5ada573996df61d30471f97e9e7be94d0b824d24 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
<?php

namespace Mediawiki\Api\Service;

use Exception;
use Mediawiki\Api\MultipartRequest;
use Mediawiki\Api\SimpleRequest;

/**
 * @access private
 *
 * @author Addshore
 */
class FileUploader extends Service {

	/** @var int */
	protected $chunkSize;

	/**
	 * Set the chunk size used for chunked uploading.
	 *
	 * Chunked uploading is available in MediaWiki 1.20 and above, although prior to version 1.25,
	 * SVGs could not be uploaded via chunked uploading.
	 *
	 * @link https://www.mediawiki.org/wiki/API:Upload#Chunked_uploading
	 *
	 * @param int $chunkSize In bytes.
	 */
	public function setChunkSize( $chunkSize ) {
		$this->chunkSize = $chunkSize;
	}

	/**
	 * Upload a file.
	 *
	 * @param string $targetName The name to give the file on the wiki (no 'File:' prefix required).
	 * @param string $location Can be local path or remote URL.
	 * @param string $text Initial page text for new files.
	 * @param string $comment Upload comment. Also used as the initial page text for new files if
	 * text parameter not provided.
	 * @param string $watchlist Unconditionally add or remove the page from your watchlist, use
	 * preferences or do not change watch. Possible values: 'watch', 'preferences', 'nochange'.
	 * @param bool $ignoreWarnings Ignore any warnings. This must be set to upload a new version of
	 * an existing image.
	 *
	 * @return bool
	 */
	public function upload(
		$targetName,
		$location,
		$text = '',
		$comment = '',
		$watchlist = 'preferences',
		$ignoreWarnings = false
	) {
		$params = [
			'filename' => $targetName,
			'token' => $this->api->getToken(),
		];
		// Watchlist behaviour.
		if ( in_array( $watchlist, [ 'watch', 'nochange' ] ) ) {
			$params['watchlist'] = $watchlist;
		}
		// Ignore warnings?
		if ( $ignoreWarnings ) {
			$params['ignorewarnings'] = '1';
		}
		// Page text.
		if ( !empty( $text ) ) {
			$params['text'] = $text;
		}
		// Revision comment.
		if ( !empty( $comment ) ) {
			$params['comment'] = $comment;
		}

		if ( is_file( $location ) ) {
			// Normal single-request upload.
			$params['filesize'] = filesize( $location );
			$params['file'] = fopen( $location, 'r' );
			if ( is_int( $this->chunkSize ) && $this->chunkSize > 0 ) {
				// Chunked upload.
				$params = $this->uploadByChunks( $params );
			}
		} else {
			// Upload from URL.
			$params['url'] = $location;
		}

		$response = $this->api->postRequest( new SimpleRequest( 'upload', $params ) );
		return ( $response['upload']['result'] === 'Success' );
	}

	/**
	 * Upload a file by chunks and get the parameters for the final upload call.
	 * @param mixed[] $params The request parameters.
	 * @return mixed[]
	 * @throws Exception
	 */
	protected function uploadByChunks( $params ) {
		// Get the file handle for looping, but don't keep it in the request parameters.
		$fileHandle = $params['file'];
		unset( $params['file'] );
		// Track the chunks and offset.
		$chunksDone = 0;
		$params['offset'] = 0;
		while ( true ) {

			// 1. Make the request.
			$params['chunk'] = fread( $fileHandle, $this->chunkSize );
			$contentDisposition = 'form-data; name="chunk"; filename="' . $params['filename'] . '"';
			$request = MultipartRequest::factory()
				->setParams( $params )
				->setAction( 'upload' )
				->setMultipartParams( [
					'chunk' => [ 'headers' => [ 'Content-Disposition' => $contentDisposition ] ],
				] );
			$response = $this->api->postRequest( $request );

			// 2. Deal with the response.
			$chunksDone++;
			$params['offset'] = ( $chunksDone * $this->chunkSize );
			if ( !isset( $response['upload']['filekey'] ) ) {
				// This should never happen. Even the last response still has the filekey.
				throw new Exception( 'Unable to get filekey for chunked upload' );
			}
			$params['filekey'] = $response['upload']['filekey'];
			if ( $response['upload']['result'] === 'Continue' ) {
				// Amend parameters for next upload POST request.
				$params['offset'] = $response['upload']['offset'];
			} else {
				// The final upload POST will be done in self::upload()
				// to commit the upload out of the stash area.
				unset( $params['chunk'], $params['offset'] );
				return $params;
			}
		}
	}
}