FileUploader   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 125
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 82.14%

Importance

Changes 0
Metric Value
wmc 13
lcom 1
cbo 4
dl 0
loc 125
ccs 46
cts 56
cp 0.8214
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A setChunkSize() 0 3 1
B upload() 0 45 8
A uploadByChunks() 0 38 4
1
<?php
2
3
namespace Mediawiki\Api\Service;
4
5
use Exception;
6
use Mediawiki\Api\MultipartRequest;
7
use Mediawiki\Api\SimpleRequest;
8
9
/**
10
 * @access private
11
 *
12
 * @author Addshore
13
 */
14
class FileUploader extends Service {
15
16
	/** @var int */
17
	protected $chunkSize;
18
19
	/**
20
	 * Set the chunk size used for chunked uploading.
21
	 *
22
	 * Chunked uploading is available in MediaWiki 1.20 and above, although prior to version 1.25,
23
	 * SVGs could not be uploaded via chunked uploading.
24
	 *
25
	 * @link https://www.mediawiki.org/wiki/API:Upload#Chunked_uploading
26
	 *
27
	 * @param int $chunkSize In bytes.
28
	 */
29 1
	public function setChunkSize( $chunkSize ) {
30 1
		$this->chunkSize = $chunkSize;
31 1
	}
32
33
	/**
34
	 * Upload a file.
35
	 *
36
	 * @param string $targetName The name to give the file on the wiki (no 'File:' prefix required).
37
	 * @param string $location Can be local path or remote URL.
38
	 * @param string $text Initial page text for new files.
39
	 * @param string $comment Upload comment. Also used as the initial page text for new files if
40
	 * text parameter not provided.
41
	 * @param string $watchlist Unconditionally add or remove the page from your watchlist, use
42
	 * preferences or do not change watch. Possible values: 'watch', 'preferences', 'nochange'.
43
	 * @param bool $ignoreWarnings Ignore any warnings. This must be set to upload a new version of
44
	 * an existing image.
45
	 *
46
	 * @return bool
47
	 */
48 2
	public function upload(
49
		$targetName,
50
		$location,
51
		$text = '',
52
		$comment = '',
53
		$watchlist = 'preferences',
54
		$ignoreWarnings = false
55
	) {
56
		$params = [
57 2
			'filename' => $targetName,
58 2
			'token' => $this->api->getToken(),
59 2
		];
60
		// Watchlist behaviour.
61 2
		if ( in_array( $watchlist, [ 'watch', 'nochange' ] ) ) {
62
			$params['watchlist'] = $watchlist;
63
		}
64
		// Ignore warnings?
65 2
		if ( $ignoreWarnings ) {
66 2
			$params['ignorewarnings'] = '1';
67 2
		}
68
		// Page text.
69 2
		if ( !empty( $text ) ) {
70 2
			$params['text'] = $text;
71 2
		}
72
		// Revision comment.
73 2
		if ( !empty( $comment ) ) {
74
			$params['comment'] = $comment;
75
		}
76
77 2
		if ( is_file( $location ) ) {
78
			// Normal single-request upload.
79 2
			$params['filesize'] = filesize( $location );
80 2
			$params['file'] = fopen( $location, 'r' );
81 2
			if ( is_int( $this->chunkSize ) && $this->chunkSize > 0 ) {
82
				// Chunked upload.
83 1
				$params = $this->uploadByChunks( $params );
84 1
			}
85 2
		} else {
86
			// Upload from URL.
87
			$params['url'] = $location;
88
		}
89
90 2
		$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 83 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...
91 2
		return ( $response['upload']['result'] === 'Success' );
92
	}
93
94
	/**
95
	 * Upload a file by chunks and get the parameters for the final upload call.
96
	 * @param mixed[] $params The request parameters.
97
	 * @return mixed[]
98
	 * @throws Exception
99
	 */
100 1
	protected function uploadByChunks( $params ) {
101
		// Get the file handle for looping, but don't keep it in the request parameters.
102 1
		$fileHandle = $params['file'];
103 1
		unset( $params['file'] );
104
		// Track the chunks and offset.
105 1
		$chunksDone = 0;
106 1
		$params['offset'] = 0;
107 1
		while ( true ) {
108
			// 1. Make the request.
109
			$params['chunk'] = fread( $fileHandle, $this->chunkSize );
110 1
			$contentDisposition = 'form-data; name="chunk"; filename="' . $params['filename'] . '"';
111 1
			$request = MultipartRequest::factory()
112 1
				->setParams( $params )
113 1
				->setAction( 'upload' )
114 1
				->setMultipartParams( [
115 1
					'chunk' => [ 'headers' => [ 'Content-Disposition' => $contentDisposition ] ],
116 1
				] );
117 1
			$response = $this->api->postRequest( $request );
118 1
119
			// 2. Deal with the response.
120
			$chunksDone++;
121 1
			$params['offset'] = ( $chunksDone * $this->chunkSize );
122 1
			if ( !isset( $response['upload']['filekey'] ) ) {
123 1
				// This should never happen. Even the last response still has the filekey.
124
				throw new Exception( 'Unable to get filekey for chunked upload' );
125
			}
126
			$params['filekey'] = $response['upload']['filekey'];
127 1
			if ( $response['upload']['result'] === 'Continue' ) {
128 1
				// Amend parameters for next upload POST request.
129
				$params['offset'] = $response['upload']['offset'];
130
			} else {
131
				// The final upload POST will be done in self::upload()
132
				// to commit the upload out of the stash area.
133
				unset( $params['chunk'], $params['offset'] );
134 1
				return $params;
135 1
			}
136
		}
137
	}
138
}
139