Passed
Push — master ( 7cb178...ac4679 )
by Fabio
04:35
created

TActiveFileUpload   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 394
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 141
dl 0
loc 394
ccs 0
cts 228
cp 0
rs 6.4799
c 2
b 0
f 0
wmc 54

24 Methods

Rating   Name   Duplication   Size   Complexity  
A getTempPath() 0 3 1
A getActiveControl() 0 3 1
A raisePostDataChangedEvent() 0 3 1
A setAutoPostBack() 0 3 1
A pushParamsAndGetToken() 0 14 3
A addAttributesToRender() 0 6 1
A getClientOptions() 0 14 1
A getClientClassName() 0 3 1
A setTempPath() 0 3 1
A createChildControls() 0 31 1
A popParamsByToken() 0 17 3
B onFileUpload() 0 27 9
B onPreRender() 0 24 10
A __construct() 0 4 1
A getAssetUrl() 0 4 1
A getSuccessImage() 0 4 1
A getAutoPostBack() 0 3 1
A getErrorImage() 0 4 1
A onInit() 0 12 4
A getCallbackJavascript() 0 3 1
A onUnload() 0 10 5
A getClientSide() 0 3 1
A getBusyImage() 0 4 1
A raiseCallbackEvent() 0 15 3

How to fix   Complexity   

Complex Class

Complex classes like TActiveFileUpload often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TActiveFileUpload, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * TActiveFileUpload.php
4
 *
5
 * @author Bradley Booms <[email protected]>
6
 * @author Christophe Boulain <[email protected]>
7
 * @author Gabor Berczi <[email protected]> (issue 349 remote vulnerability fix)
8
 * @author LANDWEHR Computer und Software GmbH <[email protected]>
9
 * @package Prado\Web\UI\ActiveControls
10
 */
11
12
namespace Prado\Web\UI\ActiveControls;
13
14
/**
15
 * Load TActiveControlAdapter and TFileUpload.
16
 */
17
use Exception;
18
use Prado\Exceptions\TInvalidDataValueException;
19
use Prado\Prado;
20
use Prado\TPropertyValue;
21
use Prado\Web\UI\INamingContainer;
22
use Prado\Web\UI\WebControls\TFileUpload;
23
use Prado\Web\UI\WebControls\THiddenField;
24
use Prado\Web\UI\WebControls\TImage;
25
use Prado\Web\UI\WebControls\TInlineFrame;
26
use Prado\Web\Javascripts\TJavaScript;
27
28
/**
29
 * TActiveFileUpload
30
 *
31
 * TActiveFileUpload displays a file upload field on a page. Upon postback,
32
 * the text entered into the field will be treated as the name of the file
33
 * that will be uploaded to the server. The property {@link getHasFile HasFile}
34
 * indicates whether the file upload is successful. If successful, the file
35
 * may be obtained by calling {@link saveAs} to save it at a specified place.
36
 * You can use {@link getFileName FileName}, {@link getFileType FileType},
37
 * {@link getFileSize FileSize} to get the original client-side file name,
38
 * the file mime type, and the file size information. If the upload is not
39
 * successful, {@link getErrorCode ErrorCode} contains the error code
40
 * describing the cause of failure.
41
 *
42
 * TActiveFileUpload raises {@link onFileUpload OnFileUpload} event if a file is uploaded
43
 * (whether it succeeds or not).
44
 *
45
 * TActiveFileUpload actually does a postback in a hidden IFrame, and then does a callback.
46
 * This callback then raises the {@link onFileUpload OnFileUpload} event. After the postback
47
 * a status icon is displayed; either a green checkmark if the upload is successful,
48
 * or a red x if there was an error.
49
 *
50
 * TActiveFileUpload needs either an application level cache or a security manager to work securely.
51
 *
52
 * Since Prado 4.0 the TActiveFileUpload supports HTML5 multiple file uploads by setting the
53
 * {@link setMultiple Multiple} attribute to true. See the description of the parent class
54
 * {@see TFileUpload} for further details.
55
 *
56
 * @author Bradley Booms <[email protected]>
57
 * @author Christophe Boulain <[email protected]>
58
 * @author LANDWEHR Computer und Software GmbH <[email protected]>
59
 * @package Prado\Web\UI\ActiveControls
60
 */
61
class TActiveFileUpload extends TFileUpload implements IActiveControl, ICallbackEventHandler, INamingContainer
62
{
63
	const SCRIPT_PATH = 'activefileupload';
64
65
	/**
66
	 * @var THiddenField a flag to tell which component is doing the callback.
67
	 */
68
	private $_flag;
69
	/**
70
	 * @var TImage that spins to show that the file is being uploaded.
71
	 */
72
	private $_busy;
73
	/**
74
	 * @var TImage that shows a green check mark for completed upload.
75
	 */
76
	private $_success;
77
	/**
78
	 * @var TImage that shows a red X for incomplete upload.
79
	 */
80
	private $_error;
81
	/**
82
	 * @var TInlineFrame used to submit the data in an "asynchronous" fashion.
83
	 */
84
	private $_target;
85
	/**
86
	 * @var class name used to instantiate items for uploaded files: {@link TFileUploadItem}
0 ignored issues
show
Bug introduced by
The type Prado\Web\UI\ActiveControls\class was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
87
	 */
88
	protected static $fileUploadItemClass = '\Prado\Web\UI\ActiveControls\TActiveFileUploadItem';
89
90
91
	/**
92
	 * Creates a new callback control, sets the adapter to
93
	 * TActiveControlAdapter. If you override this class, be sure to set the
94
	 * adapter appropriately by, for example, by calling this constructor.
95
	 */
96
	public function __construct()
97
	{
98
		parent::__construct();
99
		$this->setAdapter(new TActiveControlAdapter($this));
100
	}
101
102
103
	/**
104
	 * @param string $file asset file in the self::SCRIPT_PATH directory.
105
	 * @return string asset file url.
106
	 */
107
	protected function getAssetUrl($file = '')
108
	{
109
		$base = $this->getPage()->getClientScript()->getPradoScriptAssetUrl();
110
		return $base . '/' . self::SCRIPT_PATH . '/' . $file;
111
	}
112
113
114
	/**
115
	 * This method is invoked when a file is uploaded.
116
	 * If you override this method, be sure to call the parent implementation to ensure
117
	 * the invocation of the attached event handlers.
118
	 * @param TEventParameter $param event parameter to be passed to the event handlers
0 ignored issues
show
Bug introduced by
The type Prado\Web\UI\ActiveControls\TEventParameter was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
119
	 */
120
	public function onFileUpload($param)
121
	{
122
		if ($this->_flag->getValue() && $this->getPage()->getIsPostBack() && $param == $this->_target->getUniqueID()) {
0 ignored issues
show
introduced by
The condition $param == $this->_target->getUniqueID() is always false.
Loading history...
123
			$params = new TActiveFileUploadCallbackParams;
124
			// save the files so that they will persist past the end of this return.
125
			foreach ($this->getFiles() as $file) {
126
				$localName = str_replace('\\', '/', tempnam(Prado::getPathOfNamespace($this->getTempPath()), ''));
127
				$file->saveAs($localName);
128
				$file->setLocalName($localName);
129
				$params->files[] = $file->toArray();
130
			}
131
132
			// return some javascript to display a completion status.
133
			echo "<script>
134
          	 Options = new Object();
135
          	 Options.clientID = '{$this->getClientID()}';
136
          	 Options.targetID = '{$this->_target->getUniqueID()}';
137
          	 Options.errorCode = '" . (int) !$this->getHasAllFiles() . "';
138
          	 Options.callbackToken = '{$this->pushParamsAndGetToken($params)}';
139
          	 Options.fileName = '" . TJavaScript::jsonEncode($this->getMultiple() ? array_column($params->files, 'fileName') : $this->getFileName(), JSON_HEX_APOS) . "';
140
             Options.fileSize = '" . TJavaScript::jsonEncode($this->getMultiple() ? array_column($params->files, 'fileSize') : $this->getFileSize(), JSON_HEX_APOS) . "';
141
             Options.fileType = '" . TJavaScript::jsonEncode($this->getMultiple() ? array_column($params->files, 'fileType') : $this->getFileType(), JSON_HEX_APOS) . "';
142
             Options.errorCode = '" . TJavaScript::jsonEncode($this->getMultiple() ? array_column($params->files, 'errorCode') : $this->getErrorCode(), JSON_HEX_APOS) . "';
143
          	 parent.Prado.WebUI.TActiveFileUpload.onFileUpload(Options);
144
           </script>";
145
146
			exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
147
		}
148
	}
149
150
	/**
151
	 * @return string the path where the uploaded file will be stored temporarily, in namespace format
152
	 * default "Application.runtime.*"
153
	 */
154
	public function getTempPath()
155
	{
156
		return $this->getViewState('TempPath', 'Application.runtime.*');
157
	}
158
159
	/**
160
	 * @param string $value the path where the uploaded file will be stored temporarily in namespace format
161
	 * default "Application.runtime.*"
162
	 */
163
	public function setTempPath($value)
164
	{
165
		$this->setViewState('TempPath', $value, 'Application.runtime.*');
166
	}
167
168
	/**
169
	 * @return bool a value indicating whether an automatic callback to the server will occur whenever the user modifies the text in the TTextBox control and then tabs out of the component. Defaults to true.
170
	 * Note: When set to false, you will need to trigger the callback yourself.
171
	 */
172
	public function getAutoPostBack()
173
	{
174
		return $this->getViewState('AutoPostBack', true);
175
	}
176
177
	/**
178
	 * @param bool $value a value indicating whether an automatic callback to the server will occur whenever the user modifies the text in the TTextBox control and then tabs out of the component. Defaults to true.
179
	 * Note: When set to false, you will need to trigger the callback yourself.
180
	 */
181
	public function setAutoPostBack($value)
182
	{
183
		$this->setViewState('AutoPostBack', TPropertyValue::ensureBoolean($value), true);
184
	}
185
186
	/**
187
	 * @return string A chuck of javascript that will need to be called if {{@link getAutoPostBack AutoPostBack} is set to false}
188
	 */
189
	public function getCallbackJavascript()
190
	{
191
		return "Prado.WebUI.TActiveFileUpload.fileChanged(\"{$this->getClientID()}\")";
192
	}
193
194
	/**
195
	 * @param mixed $sender
196
	 * @throws TInvalidDataValueException if the {@link getTempPath TempPath} is not writable.
197
	 */
198
	public function onInit($sender)
199
	{
200
		parent::onInit($sender);
201
202
		if (!Prado::getApplication()->getCache()) {
203
			if (!Prado::getApplication()->getSecurityManager()) {
204
				throw new Exception('TActiveFileUpload needs either an application level cache or a security manager to work securely');
205
			}
206
		}
207
208
		if (!is_writable(Prado::getPathOfNamespace($this->getTempPath()))) {
209
			throw new TInvalidDataValueException("activefileupload_temppath_invalid", $this->getTempPath());
210
		}
211
	}
212
213
	/**
214
	 * Raises <b>OnFileUpload</b> event.
215
	 *
216
	 * This method is required by {@link ICallbackEventHandler} interface.
217
	 * This method is mainly used by framework and control developers.
218
	 * @param TCallbackEventParameter $param the event parameter
219
	 */
220
	public function raiseCallbackEvent($param)
221
	{
222
		$cp = $param->getCallbackParameter();
223
		if ($key = $cp->targetID == $this->_target->getUniqueID()) {
224
			$params = $this->popParamsByToken($cp->callbackToken);
225
			foreach ($params->files as $index => $file) {
226
				$_FILES[$key]['name'][$index] = stripslashes($file['fileName']);
227
				$_FILES[$key]['size'][$index] = (int) ($file['fileSize']);
228
				$_FILES[$key]['type'][$index] = $file['fileType'];
229
				$_FILES[$key]['error'][$index] = (int) ($file['errorCode']);
230
				$_FILES[$key]['tmp_name'][$index] = $file['localName'];
231
			}
232
			$this->loadPostData($key, null);
0 ignored issues
show
Bug introduced by
$key of type true is incompatible with the type string expected by parameter $key of Prado\Web\UI\WebControls...eUpload::loadPostData(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

232
			$this->loadPostData(/** @scrutinizer ignore-type */ $key, null);
Loading history...
233
234
			$this->raiseEvent('OnFileUpload', $this, $param);
235
		}
236
	}
237
238
	/**
239
	 * Raises postdata changed event.
240
	 * This method calls {@link onFileUpload} method
241
	 * This method is primarily used by framework developers.
242
	 */
243
	public function raisePostDataChangedEvent()
244
	{
245
		$this->onFileUpload($this->getPage()->getRequest()->itemAt('TActiveFileUpload_TargetId'));
246
	}
247
248
	protected function pushParamsAndGetToken(TActiveFileUploadCallbackParams $params)
249
	{
250
		if ($cache = Prado::getApplication()->getCache()) {
251
			// this is the most secure method, file info can't be forged from client side, no matter what
252
			$token = md5('TActiveFileUpload::Params::' . $this->ClientID . '::' . rand(1000 * 1000, 9999 * 1000));
0 ignored issues
show
Bug Best Practice introduced by
The property ClientID does not exist on Prado\Web\UI\ActiveControls\TActiveFileUpload. Since you implemented __get, consider adding a @property annotation.
Loading history...
253
			$cache->set($token, serialize($params), 5 * 60); // expire in 5 minutes - the callback should arrive back in seconds, actually
254
		} elseif ($mgr = Prado::getApplication()->getSecurityManager()) {
255
			// this is a less secure method, file info can be still forged from client side, but only if attacker knows the secret application key
256
			$token = urlencode(base64_encode($mgr->encrypt(serialize($params))));
257
		} else {
258
			throw new Exception('TActiveFileUpload needs either an application level cache or a security manager to work securely');
259
		}
260
261
		return $token;
262
	}
263
264
	protected function popParamsByToken($token)
265
	{
266
		if ($cache = Prado::getApplication()->getCache()) {
267
			$v = $cache->get($token);
268
			assert($v != '');
269
			$cache->delete($token); // remove it from cache so it can't be used again and won't take up space either
270
			$params = unserialize($v);
271
		} elseif ($mgr = Prado::getApplication()->getSecurityManager()) {
272
			$v = $mgr->decrypt(base64_decode(urldecode($token)));
273
			$params = unserialize($v);
274
		} else {
275
			throw new Exception('TActiveFileUpload needs either an application level cache or a security manager to work securely');
276
		}
277
278
		assert($params instanceof TActiveFileUploadCallbackParams);
279
280
		return $params;
281
	}
282
283
	/**
284
	 * Publish the javascript
285
	 * @param mixed $param
286
	 */
287
	public function onPreRender($param)
288
	{
289
		parent::onPreRender($param);
290
291
		if (!$this->getPage()->getIsPostBack() && isset($_GET['TActiveFileUpload_InputId']) && isset($_GET['TActiveFileUpload_TargetId']) && $_GET['TActiveFileUpload_InputId'] == $this->getClientID()) {
292
			$params = new TActiveFileUploadCallbackParams;
293
			foreach ($this->getFiles() as $file) {
294
				$localName = str_replace('\\', '/', tempnam(Prado::getPathOfNamespace($this->getTempPath()), ''));
295
				$file->setLocalName($localName);
296
				// tricky workaround to intercept "uploaded file too big" error: real uploads happens in onFileUpload instead
297
				$file->setErrorCode(UPLOAD_ERR_FORM_SIZE);
298
				$params->files[] = $file->toArray();
299
			}
300
301
			echo "<script>
302
          	 Options = new Object();
303
          	 Options.clientID = '{$_GET['TActiveFileUpload_InputId']}';
304
          	 Options.targetID = '{$_GET['TActiveFileUpload_TargetId']}';
305
			       Options.errorCode = '" . (int) !$this->getHasAllFiles() . "';
306
          	 Options.callbackToken = '{$this->pushParamsAndGetToken($params)}';
307
          	 Options.fileName = '" . TJavaScript::jsonEncode($this->getMultiple() ? array_column($params->files, 'fileName') : $this->getFileName(), JSON_HEX_APOS) . "';
308
             Options.fileSize = '" . TJavaScript::jsonEncode($this->getMultiple() ? array_column($params->files, 'fileSize') : $this->getFileSize(), JSON_HEX_APOS) . "';
309
             Options.fileType = '" . TJavaScript::jsonEncode($this->getMultiple() ? array_column($params->files, 'fileType') : $this->getFileType(), JSON_HEX_APOS) . "';
310
             Options.errorCode = '" . TJavaScript::jsonEncode($this->getMultiple() ? array_column($params->files, 'errorCode') : $this->getErrorCode(), JSON_HEX_APOS) . "';
311
           	 parent.Prado.WebUI.TActiveFileUpload.onFileUpload(Options);
312
           </script>";
313
		}
314
	}
315
316
	public function createChildControls()
317
	{
318
		$this->getPage()->getClientScript()->registerPradoScript('activefileupload');
319
320
		$this->_flag = new THiddenField;
321
		$this->_flag->setID('Flag');
322
		$this->getControls()->add($this->_flag);
323
324
		$this->_busy = new TImage;
325
		$this->_busy->setID('Busy');
326
		$this->_busy->setImageUrl($this->getAssetUrl('ActiveFileUploadIndicator.gif'));
327
		$this->_busy->setStyle("display:none");
328
		$this->getControls()->add($this->_busy);
329
330
		$this->_success = new TImage;
331
		$this->_success->setID('Success');
332
		$this->_success->setImageUrl($this->getAssetUrl('ActiveFileUploadComplete.png'));
333
		$this->_success->setStyle("display:none");
334
		$this->getControls()->add($this->_success);
335
336
		$this->_error = new TImage;
337
		$this->_error->setID('Error');
338
		$this->_error->setImageUrl($this->getAssetUrl('ActiveFileUploadError.png'));
339
		$this->_error->setStyle("display:none");
340
		$this->getControls()->add($this->_error);
341
342
		$this->_target = new TInlineFrame;
343
		$this->_target->setID('Target');
344
		$this->_target->setFrameUrl('about:blank');
345
		$this->_target->setStyle("width:0px; height:0px; border:none");
346
		$this->getControls()->add($this->_target);
347
	}
348
349
	/**
350
	 * Removes localfile on ending of the callback.
351
	 * @param mixed $param
352
	 */
353
	public function onUnload($param)
354
	{
355
		if ($this->getPage()->getIsCallback()) {
356
			foreach ($this->getFiles() as $file) {
357
				if ($file->getHasFile() && file_exists($file->getLocalName())) {
358
					unlink($file->getLocalName());
359
				}
360
			}
361
		}
362
		parent::onUnload($param);
363
	}
364
365
	/**
366
	 * @return TBaseActiveCallbackControl standard callback control options.
367
	 */
368
	public function getActiveControl()
369
	{
370
		return $this->getAdapter()->getBaseActiveControl();
0 ignored issues
show
Bug introduced by
The method getBaseActiveControl() does not exist on Prado\Web\UI\TControlAdapter. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

370
		return $this->getAdapter()->/** @scrutinizer ignore-call */ getBaseActiveControl();
Loading history...
371
	}
372
373
	/**
374
	 * @return TCallbackClientSide client side request options.
375
	 */
376
	public function getClientSide()
377
	{
378
		return $this->getAdapter()->getBaseActiveControl()->getClientSide();
379
	}
380
381
	/**
382
	 * Adds ID attribute, and renders the javascript for active component.
383
	 * @param THtmlWriter $writer the writer used for the rendering purpose
0 ignored issues
show
Bug introduced by
The type Prado\Web\UI\ActiveControls\THtmlWriter was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
384
	 */
385
	public function addAttributesToRender($writer)
386
	{
387
		parent::addAttributesToRender($writer);
388
		$writer->addAttribute('id', $this->getClientID());
389
390
		$this->getActiveControl()->registerCallbackClientScript($this->getClientClassName(), $this->getClientOptions());
391
	}
392
393
	/**
394
	 * @return string corresponding javascript class name for this control.
395
	 */
396
	protected function getClientClassName()
397
	{
398
		return 'Prado.WebUI.TActiveFileUpload';
399
	}
400
401
	/**
402
	 * Gets the client side options for this control.
403
	 * @return array (	inputID => input client ID,
404
	 * 					flagID => flag client ID,
405
	 * 					targetName => target unique ID,
406
	 * 					formID => form client ID,
407
	 * 					indicatorID => upload indicator client ID,
408
	 * 					completeID => complete client ID,
409
	 * 					errorID => error client ID)
410
	 */
411
	protected function getClientOptions()
412
	{
413
		$options['ID'] = $this->getClientID();
0 ignored issues
show
Comprehensibility Best Practice introduced by
$options was never initialized. Although not strictly required by PHP, it is generally a good practice to add $options = array(); before regardless.
Loading history...
414
		$options['EventTarget'] = $this->getUniqueID();
415
416
		$options['inputID'] = $this->getClientID();
417
		$options['flagID'] = $this->_flag->getClientID();
418
		$options['targetID'] = $this->_target->getUniqueID();
419
		$options['formID'] = $this->getPage()->getForm()->getClientID();
420
		$options['indicatorID'] = $this->_busy->getClientID();
421
		$options['completeID'] = $this->_success->getClientID();
422
		$options['errorID'] = $this->_error->getClientID();
423
		$options['autoPostBack'] = $this->getAutoPostBack();
424
		return $options;
425
	}
426
427
	/**
428
	 * @return TImage the image displayed when an upload
429
	 * 		completes successfully.
430
	 */
431
	public function getSuccessImage()
432
	{
433
		$this->ensureChildControls();
434
		return $this->_success;
435
	}
436
437
	/**
438
	 * @return TImage the image displayed when an upload
439
	 * 		does not complete successfully.
440
	 */
441
	public function getErrorImage()
442
	{
443
		$this->ensureChildControls();
444
		return $this->_error;
445
	}
446
447
	/**
448
	 * @return TImage the image displayed when an upload
449
	 * 		is in progress.
450
	 */
451
	public function getBusyImage()
452
	{
453
		$this->ensureChildControls();
454
		return $this->_busy;
455
	}
456
}
457