Completed
Pull Request — master (#48)
by Sam
01:50
created

FileUploader::setChunkSize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 3
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 2
1
<?php
2
3
namespace Mediawiki\Api\Service;
4
5
use Exception;
6
use Mediawiki\Api\MediawikiApi;
7
use Mediawiki\Api\SimpleRequest;
8
9
/**
10
 * @access private
11
 *
12
 * @author Addshore
13
 */
14
class FileUploader {
15
16
	/**
17
	 * @var MediawikiApi
18
	 */
19
	private $api;
20
21
	/** @var int */
22
	protected $chunkSize;
23
24
	/**
25
	 * @param MediawikiApi $api
26
	 */
27
	public function __construct( MediawikiApi $api ) {
28
		$this->api = $api;
29
	}
30
31
	/**
32
	 * Set the chunk size used for chunked uploading.
33
	 *
34
	 * Chunked uploading is available in MediaWiki 1.20 and above, although prior to version 1.25,
35
	 * SVGs could not be uploaded via chunked uploading.
36
	 *
37
	 * @link https://www.mediawiki.org/wiki/API:Upload#Chunked_uploading
38
	 *
39
	 * @param int $chunkSize In bytes.
40
	 */
41
	public function setChunkSize( $chunkSize ) {
42
		$this->chunkSize = $chunkSize;
43
	}
44
45
	/**
46
	 * Upload a file.
47
	 *
48
	 * @param string $targetName The name to give the file on the wiki (no 'File:' prefix required).
49
	 * @param string $location Can be local path or remote URL.
50
	 * @param string $text Initial page text for new files.
51
	 * @param string $comment Upload comment. Also used as the initial page text for new files if
52
	 * text parameter not provided.
53
	 * @param string $watchlist Unconditionally add or remove the page from your watchlist, use
54
	 * preferences or do not change watch. Possible values: 'watch', 'preferences', 'nochange'.
55
	 * @param bool $ignoreWarnings Ignore any warnings. This must be set to upload a new version of
56
	 * an existing image.
57
	 *
58
	 * @return bool
59
	 */
60
	public function upload(
61
		$targetName,
62
		$location,
63
		$text = '',
64
		$comment = '',
65
		$watchlist = 'preferences',
66
		$ignoreWarnings = false
67
	) {
68
		if ( !in_array( $watchlist, [ 'watch', 'preferences', 'nochange' ] ) ) {
69
			$watchlist = 'preferences';
70
		}
71
		$params = [
72
			'filename' => $targetName,
73
			'token' => $this->api->getToken(),
74
			'text' => $text,
75
			'comment' => $comment,
76
			'ignorewarnings' => $ignoreWarnings,
77
			'watchlist' => $watchlist,
78
		];
79
80
		if ( is_file( $location ) ) {
81
			// Normal single-request upload.
82
			$params['filesize'] = filesize( $location );
83
			$params['file'] = fopen( $location, 'r' );
84
			if ( is_int( $this->chunkSize ) && $this->chunkSize > 0 ) {
85
				// Chunked upload.
86
				$params = $this->uploadByChunks( $params );
87
			}
88
		} else {
89
			// Upload from URL.
90
			$params['url'] = $location;
91
		}
92
93
		$response = $this->api->postRequest( new SimpleRequest( 'upload', $params ) );
0 ignored issues
show
Bug introduced by
It seems like $params defined by $this->uploadByChunks($params) on line 86 can also be of type null; however, Mediawiki\Api\SimpleRequest::__construct() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
94
		return $response['upload']['result'] === 'Success';
95
	}
96
97
	/**
98
	 * Upload a file by chunks and get the parameters for the final upload call.
99
	 * @param mixed[] $params The request parameters.
100
	 * @return mixed[]
101
	 * @throws Exception
102
	 */
103
	protected function uploadByChunks( $params ) {
104
		// Get the file handle for looping, but don't keep it in the request parameters.
105
		$fileHandle = $params['file'];
106
		unset( $params['file'] );
107
		// Set extra request parameters for the 'chunk' parts of the requests.
108
		$this->api->setPostRequestEncoding( 'multipart' );
0 ignored issues
show
Bug introduced by
The method setPostRequestEncoding() does not exist on Mediawiki\Api\MediawikiApi. Did you maybe mean postRequest()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
109
		$this->api->addExtraMultipartParams( [
0 ignored issues
show
Bug introduced by
The method addExtraMultipartParams() does not seem to exist on object<Mediawiki\Api\MediawikiApi>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
110
			'chunk' => [
111
				'headers' => [
112
					'Content-Disposition' => 'form-data; name="chunk"; filename="'.$params['filename'] . '"',
113
				],
114
			],
115
		] );
116
		$chunksDone = 0;
117
		$params['offset'] = 0;
118
		while ( true ) {
119
			// 1. Make the request.
120
			$params['chunk'] = fread( $fileHandle, $this->chunkSize );
121
			$request = new SimpleRequest( 'upload', $params );
122
			$chunksDone++;
123
			$params['offset'] = $chunksDone * $this->chunkSize;
124
			$response = $this->api->postRequest( $request );
125
126
			// 2. Deal with the response.
127
			if ( !isset( $response['upload']['filekey'] ) ) {
128
				// This should never happen. Even the last response still has the filekey.
129
				throw new Exception( 'Unable to get filekey for chunked upload' );
130
			}
131
			$params['filekey'] = $response['upload']['filekey'];
132
			if ( $response['upload']['result'] === 'Continue' ) {
133
				// Amend parameters for next upload POST request.
134
				$params['offset'] = $response['upload']['offset'];
135
			} else {
136
				// The final upload POST will be done in self::upload()
137
				// to commit the upload out of the stash area.
138
				unset( $params['chunk'], $params['offset'] );
139
				return $params;
140
			}
141
		}
142
	}
143
}
144