1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* FileUpload.php - This class implements file upload with Ajax. |
5
|
|
|
* |
6
|
|
|
* @package jaxon-core |
|
|
|
|
7
|
|
|
* @author Thierry Feuzeu <[email protected]> |
8
|
|
|
* @copyright 2017 Thierry Feuzeu <[email protected]> |
9
|
|
|
* @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License |
10
|
|
|
* @link https://github.com/jaxon-php/jaxon-core |
11
|
|
|
*/ |
|
|
|
|
12
|
|
|
|
13
|
|
|
namespace Jaxon\Request\Plugin; |
14
|
|
|
|
15
|
|
|
use Jaxon\Jaxon; |
16
|
|
|
use Jaxon\Plugin\Request as RequestPlugin; |
17
|
|
|
use Jaxon\Request\Support\UploadedFile; |
18
|
|
|
use Jaxon\Request\Support\FileUpload as Support; |
19
|
|
|
use Jaxon\Response\UploadResponse; |
20
|
|
|
use Exception; |
21
|
|
|
use Closure; |
22
|
|
|
|
23
|
|
|
class FileUpload extends RequestPlugin |
|
|
|
|
24
|
|
|
{ |
25
|
|
|
use \Jaxon\Features\Config; |
26
|
|
|
use \Jaxon\Features\Validator; |
27
|
|
|
use \Jaxon\Features\Translator; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* The uploaded files copied in the user dir |
31
|
|
|
* |
32
|
|
|
* @var array |
33
|
|
|
*/ |
34
|
|
|
protected $aUserFiles = []; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* The name of file containing upload data |
38
|
|
|
* |
39
|
|
|
* @var string |
40
|
|
|
*/ |
41
|
|
|
protected $sTempFile = ''; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* The subdir where uploaded files are stored |
45
|
|
|
* |
46
|
|
|
* @var string |
47
|
|
|
*/ |
48
|
|
|
protected $sUploadSubdir = ''; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Is the current request an HTTP upload |
52
|
|
|
* |
53
|
|
|
* @var boolean |
54
|
|
|
*/ |
55
|
|
|
protected $bRequestIsHttpUpload = false; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* HTTP file upload support |
59
|
|
|
* |
60
|
|
|
* @var Support |
61
|
|
|
*/ |
62
|
|
|
protected $xSupport = null; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* The constructor |
66
|
|
|
* |
67
|
|
|
* @param Support $xSupport HTTP file upload support |
|
|
|
|
68
|
|
|
*/ |
69
|
|
|
public function __construct(Support $xSupport) |
70
|
|
|
{ |
71
|
|
|
$this->xSupport = $xSupport; |
|
|
|
|
72
|
|
|
$this->sUploadSubdir = uniqid() . DIRECTORY_SEPARATOR; |
73
|
|
|
|
74
|
|
|
if(array_key_exists('jxnupl', $_POST)) |
75
|
|
|
{ |
76
|
|
|
$this->sTempFile = $_POST['jxnupl']; |
77
|
|
|
} |
78
|
|
|
elseif(array_key_exists('jxnupl', $_GET)) |
79
|
|
|
{ |
80
|
|
|
$this->sTempFile = $_GET['jxnupl']; |
81
|
|
|
} |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Set the uploaded file name sanitizer |
86
|
|
|
* |
87
|
|
|
* @param Closure $cSanitizer The closure |
|
|
|
|
88
|
|
|
* |
89
|
|
|
* @return void |
90
|
|
|
*/ |
91
|
|
|
public function sanitizer(Closure $cSanitizer) |
92
|
|
|
{ |
93
|
|
|
$this->xSupport->setNameSanitizer($cSanitizer); |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Get the uploaded files |
98
|
|
|
* |
99
|
|
|
* @return array |
100
|
|
|
*/ |
101
|
|
|
public function files() |
102
|
|
|
{ |
103
|
|
|
return $this->aUserFiles; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Make sure the upload dir exists and is writable |
108
|
|
|
* |
109
|
|
|
* @param string $sUploadDir The filename |
|
|
|
|
110
|
|
|
* @param string $sUploadSubDir The filename |
|
|
|
|
111
|
|
|
* |
112
|
|
|
* @return string |
113
|
|
|
*/ |
114
|
|
|
private function _makeUploadDir($sUploadDir, $sUploadSubDir) |
115
|
|
|
{ |
116
|
|
|
$sUploadDir = rtrim(trim($sUploadDir), '/\\') . DIRECTORY_SEPARATOR; |
|
|
|
|
117
|
|
|
// Verify that the upload dir exists and is writable |
118
|
|
|
if(!is_writable($sUploadDir)) |
119
|
|
|
{ |
120
|
|
|
throw new \Jaxon\Exception\Error($this->trans('errors.upload.access')); |
121
|
|
|
} |
122
|
|
|
$sUploadDir .= $sUploadSubDir; |
123
|
|
|
if(!file_exists($sUploadDir) && !@mkdir($sUploadDir)) |
124
|
|
|
{ |
125
|
|
|
throw new \Jaxon\Exception\Error($this->trans('errors.upload.access')); |
126
|
|
|
} |
127
|
|
|
return $sUploadDir; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Get the path to the upload dir |
132
|
|
|
* |
133
|
|
|
* @param string $sFieldId The filename |
|
|
|
|
134
|
|
|
* |
135
|
|
|
* @return string |
136
|
|
|
*/ |
137
|
|
|
protected function getUploadDir($sFieldId) |
138
|
|
|
{ |
139
|
|
|
// Default upload dir |
140
|
|
|
$sDefaultUploadDir = $this->getOption('upload.default.dir'); |
141
|
|
|
$sUploadDir = $this->getOption('upload.files.' . $sFieldId . '.dir', $sDefaultUploadDir); |
|
|
|
|
142
|
|
|
|
143
|
|
|
return $this->_makeUploadDir($sUploadDir, $this->sUploadSubdir); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Get the path to the upload temp dir |
148
|
|
|
* |
149
|
|
|
* @return string |
150
|
|
|
*/ |
151
|
|
|
protected function getUploadTempDir() |
152
|
|
|
{ |
153
|
|
|
// Default upload dir |
154
|
|
|
$sUploadDir = $this->getOption('upload.default.dir'); |
155
|
|
|
|
156
|
|
|
return $this->_makeUploadDir($sUploadDir, 'tmp' . DIRECTORY_SEPARATOR); |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* Get the path to the upload temp file |
161
|
|
|
* |
162
|
|
|
* @return string |
163
|
|
|
*/ |
164
|
|
|
protected function getUploadTempFile() |
165
|
|
|
{ |
166
|
|
|
$sUploadDir = $this->getOption('upload.default.dir'); |
|
|
|
|
167
|
|
|
$sUploadDir = rtrim(trim($sUploadDir), '/\\') . DIRECTORY_SEPARATOR; |
|
|
|
|
168
|
|
|
$sUploadDir .= 'tmp' . DIRECTORY_SEPARATOR; |
|
|
|
|
169
|
|
|
$sUploadTempFile = $sUploadDir . $this->sTempFile . '.json'; |
170
|
|
|
if(!is_readable($sUploadTempFile)) |
171
|
|
|
{ |
172
|
|
|
throw new \Jaxon\Exception\Error($this->trans('errors.upload.access')); |
173
|
|
|
} |
174
|
|
|
return $sUploadTempFile; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* Read uploaded files info from HTTP request data |
179
|
|
|
* |
180
|
|
|
* @return void |
181
|
|
|
*/ |
182
|
|
|
protected function readFromHttpData() |
183
|
|
|
{ |
184
|
|
|
// Check validity of the uploaded files |
185
|
|
|
$aTempFiles = $this->xSupport->getUploadedFiles(); |
186
|
|
|
|
187
|
|
|
// Copy the uploaded files from the temp dir to the user dir |
188
|
|
|
foreach($aTempFiles as $sVarName => $aFiles) |
189
|
|
|
{ |
190
|
|
|
$this->aUserFiles[$sVarName] = []; |
191
|
|
|
// Get the path to the upload dir |
192
|
|
|
$sUploadDir = $this->getUploadDir($sVarName); |
193
|
|
|
|
194
|
|
|
foreach($aFiles as $aFile) |
195
|
|
|
{ |
196
|
|
|
// Set the user file data |
197
|
|
|
$xUploadedFile = UploadedFile::fromHttpData($sUploadDir, $aFile); |
198
|
|
|
// All's right, move the file to the user dir. |
199
|
|
|
move_uploaded_file($aFile["tmp_name"], $xUploadedFile->path()); |
200
|
|
|
$this->aUserFiles[$sVarName][] = $xUploadedFile; |
201
|
|
|
} |
202
|
|
|
} |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* Save uploaded files info to a temp file |
207
|
|
|
* |
208
|
|
|
* @return void |
209
|
|
|
*/ |
210
|
|
|
protected function saveToTempFile() |
211
|
|
|
{ |
212
|
|
|
// Convert uploaded file to an array |
213
|
|
|
$aFiles = []; |
214
|
|
|
foreach($this->aUserFiles as $sVarName => $aUserFiles) |
215
|
|
|
{ |
216
|
|
|
$aFiles[$sVarName] = []; |
217
|
|
|
foreach($aUserFiles as $aUserFile) |
218
|
|
|
{ |
219
|
|
|
$aFiles[$sVarName][] = $aUserFile->toTempData(); |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
// Save upload data in a temp file |
223
|
|
|
$sUploadDir = $this->getUploadTempDir(); |
|
|
|
|
224
|
|
|
$this->sTempFile = uniqid(); |
225
|
|
|
file_put_contents($sUploadDir . $this->sTempFile . '.json', json_encode($aFiles)); |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* Read uploaded files info from a temp file |
230
|
|
|
* |
231
|
|
|
* @return void |
232
|
|
|
*/ |
233
|
|
|
protected function readFromTempFile() |
234
|
|
|
{ |
235
|
|
|
// Upload temp file |
236
|
|
|
$sUploadTempFile = $this->getUploadTempFile(); |
237
|
|
|
$aFiles = json_decode(file_get_contents($sUploadTempFile), true); |
|
|
|
|
238
|
|
|
foreach($aFiles as $sVarName => $aUserFiles) |
239
|
|
|
{ |
240
|
|
|
$this->aUserFiles[$sVarName] = []; |
241
|
|
|
foreach($aUserFiles as $aUserFile) |
242
|
|
|
{ |
243
|
|
|
$this->aUserFiles[$sVarName][] = UploadedFile::fromTempData($aUserFile); |
244
|
|
|
} |
245
|
|
|
} |
246
|
|
|
unlink($sUploadTempFile); |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* Return the name of this plugin |
251
|
|
|
* |
252
|
|
|
* @return string |
253
|
|
|
*/ |
254
|
|
|
public function getName() |
255
|
|
|
{ |
256
|
|
|
return Jaxon::FILE_UPLOAD; |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* Generate a hash for the registered browser events |
261
|
|
|
* |
262
|
|
|
* @return string |
263
|
|
|
*/ |
264
|
|
|
public function generateHash() |
265
|
|
|
{ |
266
|
|
|
return ''; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* Generate client side javascript code for the registered browser events |
271
|
|
|
* |
272
|
|
|
* @return string |
273
|
|
|
*/ |
274
|
|
|
public function getScript() |
275
|
|
|
{ |
276
|
|
|
return ''; |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
/** |
280
|
|
|
* Inform this plugin that other plugin can process the current request |
281
|
|
|
* |
282
|
|
|
* @return void |
283
|
|
|
*/ |
284
|
|
|
public function noRequestPluginFound() |
285
|
|
|
{ |
286
|
|
|
if(count($_FILES) > 0) |
287
|
|
|
{ |
288
|
|
|
$this->bRequestIsHttpUpload = true; |
289
|
|
|
} |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* Check if this plugin can process the incoming Jaxon request |
294
|
|
|
* |
295
|
|
|
* @return boolean |
296
|
|
|
*/ |
297
|
|
|
public function canProcessRequest() |
298
|
|
|
{ |
299
|
|
|
return (count($_FILES) > 0 || ($this->sTempFile)); |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* Process the uploaded files into the HTTP request |
304
|
|
|
* |
305
|
|
|
* @return boolean |
306
|
|
|
*/ |
307
|
|
|
public function processRequest() |
308
|
|
|
{ |
309
|
|
|
if(!$this->canProcessRequest()) |
310
|
|
|
{ |
311
|
|
|
return false; |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
if(count($_FILES) > 0) |
315
|
|
|
{ |
316
|
|
|
// Ajax or Http request with upload |
317
|
|
|
$this->readFromHttpData(); |
318
|
|
|
|
319
|
|
|
if($this->bRequestIsHttpUpload) |
320
|
|
|
{ |
321
|
|
|
// Process an HTTP upload request |
322
|
|
|
// This requires to set the response to be returned. |
323
|
|
|
$xResponse = new UploadResponse(); |
324
|
|
|
try |
325
|
|
|
{ |
326
|
|
|
$this->saveToTempFile(); |
327
|
|
|
$xResponse->setUploadedFile($this->sTempFile); |
328
|
|
|
} |
329
|
|
|
catch(Exception $e) |
330
|
|
|
{ |
331
|
|
|
$xResponse->setErrorMessage($e->getMessage()); |
332
|
|
|
} |
333
|
|
|
jaxon()->di()->getResponseManager()->append($xResponse); |
334
|
|
|
} |
335
|
|
|
} |
336
|
|
|
elseif(($this->sTempFile)) |
337
|
|
|
{ |
338
|
|
|
// Ajax request following and HTTP upload |
339
|
|
|
$this->readFromTempFile(); |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
return true; |
343
|
|
|
} |
344
|
|
|
} |
345
|
|
|
|