FRED™  3.0
FRED™: Framework for Rapid and Easy Development
File.php
Go to the documentation of this file.
1 <?php
2 
4 
5 use \Rsi\Qr;
6 
7 class File extends \Rsi\Fred\Controller\Widget{
8 
9  const TYPES = 'types'; //!< Allowed file types (mime or extension; mime can have asterisk wildcards or even be a complete
10  // regex; all lower case).
11  const FILENAME = 'filename'; //!< Fixed filename for the upload. If everything is OK, file will be overwitten directly, and
12  // can also be deleted (if not required).
13  const MULTI = 'multi'; //!< Allow multiple files to be uploaded.
14  const PREVIEW_SIZE = 'previewSize'; //!< Size (width) of preview image (false = no preview).
15  const USER_FILES = 'userFiles'; //!< Show user files.
16 
17  const EVENT_UPLOAD = 'controller:widget:file:upload';
18 
19  const TUS_VERSION = '1.0.0';
20  const TUS_EXTENSIONS = 'creation,termination,checksum';
21 
22  public $types = [];
23  public $filename = null;
24  public $multi = false;
25  public $previewSize = false;
26  public $previewTtl = null;
27  public $previewKeyPrefix = 'fred-controller-widget-file-preview-';
28  public $previewMax = 3; //!< Maximum number of preview images.
29  public $memoryMargin = 20 * 1048576; //!< Spare memory for async file upload processing.
30  public $memoryFactor = 0.5; //!< Factor memory for async file upload processing
31  public $userFiles = false;
32 
33  public $sideloadControllerName = null; //!< Controller that provides the sideloading.
34  public $sideloadPath = null; //!< Path to temporarily store sideload files.
35  public $sideloadExt = '.side'; //!< Extension for sideload files.
36  public $sideloadMode = 0666; //!< File mode for sideload files.
37  public $sideloadTokenLength = 32; //!< Length of the sideload token.
38  public $sideloadHint = null; //!< Hint to show during sideloading (translated).
39  public $sideloadInterval = 5; //!< Poll interval for sideloading check (seconds).
40  public $sideloadGarbageChance = 100; //!< Clear sideloading path once every ... times.
41  public $sideloadSkip = 5; //!< Stop scanning for new files after this number of misses.
42 
43  public $tusPath = null; //!< Path to temporarily store TUS data.
44  public $tusExt = '.tus'; //!< Extension for TUS data files.
45  public $tusTempExt = '.file'; //!< Extension for TUS partial file upload.
46  public $tusMode = 0666; //!< File mode for TUS data files.
47  public $tusTokenLength = 32; //!< Length of the TUS token.
48  public $tusTimeout = 3600; //!< Time after which an inactive upload is removed (seconds).
49  public $tusGarbageChance = 100; //!< Clear TUS path once every ... times.
50  public $tusChunkSize = null; //!< Upload chunk size for TUS upload (empty = calculate).
51  public $tusScriptSrc = '/fred/js/tus.min.js'; //!< Script to load for TUS upload (set to empty when already loaded).
52 
53  protected $_raw = null;
54  protected $_multiRaw = null;
55 
56  public function clientConfig(){
57  $config = array_merge(parent::clientConfig(),$this->get([self::TYPES]));
58  if(!$this->max) $config[self::MAX] = \Rsi\Number::shorthandBytes(ini_get('post_max_size'));
59  if($this->trailer === null) $config[self::TRAILER] = '%n (%s %u)';
60  if($this->filename){
61  $config[self::FILENAME] = \Rsi\File::basename($this->filename);
62  $config['fileExists'] = is_file($this->filename) || $this->filenames;
63  if($this->sideloadControllerName && $this->sideloadPath && !$this->_controller->component('client')->mobile){
64  $image = !$this->types;
65  foreach($this->types as $type) if($image = preg_match('/^(image|jpe?g)/i',$type)) break;
66  if($image) $config['sideloadInterval'] = $this->sideloadInterval;
67  }
68  if($this->tusPath && (($this->max > ($limit = min(
69  \Rsi\Number::shorthandBytes(ini_get('upload_max_filesize')),
70  $chunk = \Rsi::memoryLimit() * $this->memoryFactor - $this->memoryMargin
71  ))) || !$this->max)){
72  $config['asyncLimit'] = $limit;
73  $config['tusChunkSize'] = $this->tusChunkSize ?: min($chunk,\Rsi\Number::shorthandBytes(ini_get('post_max_size')));
74  $config['tusScriptSrc'] = $this->tusScriptSrc;
75  }
76 //TODO: user files werkt nu alleen met een direct lokale filename (dus niet "classic" POST - via een hidden met name ofzo)
77  if($this->userFiles && $this->_controller->component('user')->id) $config[self::USER_FILES] = true;
78  }
79  if($this->multi) $config[self::MULTI] = true;
80  return $config;
81  }
82  /**
83  * Purge uploaded filename from possible array construction.
84  * If all details are needed you can call fred->request->file(['widget','id']) (where \$id can also be multiple levels
85  * deep).
86  */
87  protected function purgeBase($value){
88  if(is_array($value)) $value = array_filter($value,function($file){
89  return !is_array($file) || (($file['error'] ?? null) != 4) || array_filter(['error' => null] + $file);
90  });
91  if($value && is_array($value)) $value = $this->multi //classic upload
92  ? array_column($this->_multiRaw = $value,'tmp_name')
93  : \Rsi\Record::get($this->_raw = $value,'tmp_name');
94  elseif(!$value && $this->filename && (is_array($value = $this->filenames) || is_file($value = $this->filename))); //earlier async upload
95  else $value = null; //no upload; clear any value
96  return parent::purgeBase($value);
97  }
98 
99  protected function uploadEvent($name){
100  return $this->_controller->component('event')->trigger(self::EVENT_UPLOAD,$this,$name);
101  }
102 
103  protected function checkMin($value,$index = null){
104  return parent::checkMin(filesize($value));
105  }
106 
107  protected function checkMax($value,$index = null){
108  return parent::checkMax(filesize($value));
109  }
110 
111  protected function checkUpload($value,$index = null){
112  return !$this->_raw || (is_uploaded_file($value) && !$this->_raw['error']); //http://php.net/manual/en/features.file-upload.errors.php
113  }
114 
115  protected function checkTypes($value,$index = null){
116  if($this->_raw) $value = $this->_raw['name'];
117  if(!$this->types || in_array(\Rsi\File::ext($value),$this->types)) return true;
118  $mime = \Rsi\File::mime($value);
119  foreach($this->types as $type)
120  if(preg_match(substr($type,0,1) == '/' ? $type : '/^' . str_replace('*','.*',$type) . '$/',$mime)) return true;
121  return false;
122  }
123 
124  public function check($value,$index = null){
125  if(is_array($value) && $this->filename){
126  foreach($value as $filename) if($error = parent::check($filename,$index)) break;
127  }
128  elseif($this->_multiRaw){
129  foreach($this->_multiRaw as $this->_raw) if($error = parent::check($this->_raw['tmp_name'],$index)) break;
130  }
131  else $error = parent::check($value,$index);
132  if(!$error && $value && !is_array($value) && $this->filename && $this->_raw){
133  if(!move_uploaded_file($value,$this->filename))
134  throw new \Exception("Can not move uploaded file from '$value' to '{$this->filename}'");
135  else $error = $this->uploadEvent($this->_raw['name']);
136  }
137  return $error;
138  }
139 
140  protected function previewFile($filename){
141  $preview = [];
142  $rotate = false;
143  try{
144  $image = new \Rsi\Wrapper\Image(file_get_contents($filename));
145  $factor = min($this->previewSize / ($width = $image->width),$this->previewSize / ($height = $image->height));
146  $image->_scale(round($factor * $width),round($factor * $height));
147  ob_start();
148  $image->png();
149  $preview[] = ob_get_clean();
150  $rotate = function_exists('image' . $image->ext($filename));
151  }
152  catch(\Exception $e){
153  $this->log->info($e);
154  if(class_exists('Imagick')){
155  $temp = \Rsi\File::tempFile('png');
156  try{
157  $image = new \Imagick($filename);
158  foreach($image as $page) if($count++ < $this->previewMax){
159  $page->setImageBackgroundColor('white');
160  $page->setImageFormat('png');
161  $page->thumbnailImage($this->previewSize,0);
162  $page->writeImage($temp);
163  $preview[] = file_get_contents($temp);
164  }
165  $image->destroy();
166  }
167  catch(\Exception $e){
168  $this->log->info($e);
169  }
170  \Rsi\File::unlink($temp);
171  }
172  else try{
173  if($preview = $this->services->preview($filename,$this->previewSize,$this->previewMax)['preview'] ?? null)
174  $preview = array_map('base64_decode',$preview);
175  }
176  catch(\Exception $e){
177  $this->log->info($e);
178  }
179  }
180  return $preview ? compact('preview','rotate') : null;
181  }
182  /**
183  * Generate a preview.
184  * @param string $filename File to generate a preview for (empty = fixed upload filename).
185  * @param int $count Will be set to the number of pages in the file.
186  * @param bool $rotate Will be set to true if the image can be rotated.
187  * @return array Preview PNG images.
188  */
189  public function preview($filename = null,&$count = null,&$rotate = null){
190  if($filename || !is_array($filenames = $this->filenames)) $filenames = [$filename ?: $this->filename];
191  $count = 0;
192  $preview = [];
193  session_write_close();
194  if($rotate = boolval($this->previewSize)) foreach($filenames as $filename) if(is_file($filename)){
195  $key = null;
196  if(
197  $this->previewTtl &&
198  ($file = $this->fred->cache->fetch($key = $this->previewKeyPrefix . $filename . '@' . $this->previewSize)) &&
199  ($file['time'] > filemtime($filename))
200  ) $key = false;
201  else $file = $this->previewFile($filename);
202  if($file){
203  $preview = array_merge($preview,$file['preview']);
204  if(!$file['rotate']) $rotate = false;
205  if($key) $this->fred->cache->save($key,$file + ['time' => time()],$this->previewTtl);
206  }
207  }
208  session_start();
209  $count = count($preview);
210  return array_slice($preview,0,$this->previewMax);
211  }
212  /**
213  * Rotate a file.
214  * @param int $angle Rotation angle (positive: degrees clockwise; -1: flip horizontal, -2: flip vertical).
215  * @param string $filename File to rotate (empty = fixed upload filename).
216  * @return bool True if the operation was successful (file will be replaced).
217  */
218  public function rotate($angle = 90,$filename = null){
219  if(!$filename) $filename = is_array($filenames = $this->filenames) ? array_shift($filenames) : $this->filename;
220  $result = !$angle;
221  if($angle && $filename && is_file($filename)) try{
222  $image = new \Rsi\Wrapper\Image($filename);
223  if($angle > 0) $image->_rotate(-$angle,$image->colorAllocate(255,255,255));
224  else switch($angle){
225  case -1: $image->flip(IMG_FLIP_HORIZONTAL); break;
226  case -2: $image->flip(IMG_FLIP_VERTICAL); break;
227  }
228  $result = $image->save($filename);
229  }
230  catch(\Exception $e){
231  $this->log->info($e);
232  }
233  return $result;
234  }
235  /**
236  * Process upload data.
237  * @param mixed $temp Temporarily file with uploaded data.
238  * @param string $name Original filename.
239  */
240  protected function processUpload($temp,$name){
241  try{
242  $size = filesize($temp);
243  if($error = $this->check($temp)) \Rsi\File::unlink($temp);
244  elseif(!\Rsi\File::rename($temp,$filename = str_replace('*',microtime(true),$this->filename)))
245  throw new \Exception("Could not rename('$temp','$filename')");
246  else $error = $this->uploadEvent($name);
247  $preview = [];
248  foreach($this->preview(null,$count,$rotate) as $image) $preview[] = base64_encode($image);
249  $this->request->result = compact('error','name','size','preview','count','rotate') + ['fileExists' => $error ? false : is_file($filename)];
250  }
251  finally{
252  \Rsi\File::unlink($temp);
253  }
254  }
255  /**
256  * Filename for a sideload file.
257  * @param string $token Sideload token.
258  * @param int $index Upload index.
259  * @return string
260  */
261  protected function sideloadFilename($token,$index = null){
262  return $this->sideloadPath . $token . ($index === null ? '' : '-' . $index) . $this->sideloadExt;
263  }
264  /**
265  * Sideload URL.
266  * @param string $token Sideload token.
267  * @return string Full URL.
268  */
269  protected function sideloadUrl($token){
270  return \Rsi\Http::host(true) . $this->_controller->component('router')->reverse($this->sideloadControllerName,null,compact('token'));
271  }
272  /**
273  * Clear old sideload tokens.
274  */
275  protected function sideloadGarbage(){
276  if($this->sideloadPath && !rand(0,$this->sideloadGarbageChance)) try{
277  foreach((new \GlobIterator($this->sideloadPath . '*' . $this->sideloadExt)) as $filename => $file)
278  if($file->getMTime() < time() - 2 * $this->sideloadInterval) \Rsi\File::unlink($filename);
279  $session = $this->_controller->session;
280  if($tokens = $session->sideloadTokens){
281  foreach($tokens as $token => $time) if(!is_file($this->sideloadFilename($token))) unset($tokens[$token]);
282  $session->sideloadTokens = $tokens;
283  }
284  }
285  catch(\Exception $e){
286  $this->log->error($e);
287  }
288  }
289 
290  protected function tusError($code,$message){
291  http_response_code($code);
292  header('Content-Type: text/plain');
293  print($message);
294  $this->fred->log->info("TUS error ($code): $message");
295  }
296 
297  protected function tusIndex($header){
298  if(($index = $_SERVER['HTTP_' . strtoupper(str_replace('-','_',$header))] ?? null) === null) $this->tusError(400,"Missing $header header");
299  elseif(($index = filter_var($index,FILTER_VALIDATE_INT,['options' => ['min_range' => 0]])) === false) $this->tusError(400,"Invalid $header header");
300  else return $index;
301  return false;
302  }
303 
304  protected function tusFilename($token){
305  return $this->tusPath . $token . $this->tusExt;
306  }
307 
308  protected function tusGarbage(){
309  if($this->tusPath && !rand(0,$this->tusGarbageChance)) try{
310  foreach((new \GlobIterator($this->tusPath . '*' . $this->tusExt)) as $filename => $file)
311  if($file->getMTime() < time() - $this->tusTimeout){
312  \Rsi\File::unlink($filename . $this->tusTempExt);
313  \Rsi\File::unlink($filename);
314  }
315  }
316  catch(\Exception $e){
317  $this->log->error($e);
318  }
319  }
320 
321  protected function actionPreview(){
322  $preview = [];
323  $count = $rotate = false;
324  if($this->_display >= self::DISPLAY_READABLE)
325  foreach($this->preview(null,$count,$rotate) as $image) $preview[] = base64_encode($image);
326  $this->request->result = compact('preview','count','rotate');
327  }
328 
329  protected function actionDownload(){
330  if($this->_display >= self::DISPLAY_READABLE){
331  if(!is_array($filenames = $this->filenames)) $filenames = [$this->filename];
332  if(($filename = $filenames[intval($this->request->index)] ?? null) && is_file($filename)){
333  \Rsi\Http::downloadHeaders($filename,null,filesize($filename));
334  readfile($filename);
335  $this->fred->halt();
336  }
337  }
338  }
339 
340  protected function actionUpload(){
341  if(
342  ($this->_display >= self::DISPLAY_WRITEABLE) &&
343  $this->filename &&
344  ($temp = \Rsi\Record::get($this->request->file('data'),'tmp_name'))
345  ) $this->processUpload($temp,$this->request->name);
346  }
347 
348  protected function actionRotate(){
349  if($this->_display >= self::DISPLAY_WRITEABLE){
350  session_write_close();
351  $this->rotate((int)$this->request->angle);
352  $preview = [];
353  foreach($this->preview(null,$count,$rotate) as $image) $preview[] = base64_encode($image);
354  clearstatcache(false,$this->filename);
355  $this->request->result = compact('preview','count','rotate') + ['size' => \Rsi\File::size($this->filename)];
356  }
357  }
358 
359  protected function actionDelete(){
360  if(($this->_display >= self::DISPLAY_WRITEABLE) && !$this->required && $this->filename){
361  if(!is_array($filenames = $this->filenames)) $filenames = [$this->filename];
362  $this->request->result = ['deleted' => $filenames ? array_sum(array_map('\Rsi\File::unlink',$filenames)) : true];
363  }
364  }
365 
366  protected function actionUserFiles(){
367  if(($this->_display >= self::DISPLAY_WRITEABLE) && $this->userFiles) $this->request->result = [
368  'files' => $this->_controller->component('user')->files->list($this->types),
369  'preview' => (bool)$this->previewSize
370  ];
371  }
372 
373  protected function actionUserFilePreview(){
374  if($data = $this->_controller->component('user')->files->fetch($this->request->name)){
375  session_write_close();
376  $temp = \Rsi\File::tempFile('tmp',$data);
377  $preview = [];
378  foreach($this->preview($temp) as $image) $preview[] = base64_encode($image);
379  unlink($temp);
380  $this->request->result = $preview;
381  }
382  }
383 
384  protected function actionUserFile(){
385  if(($this->_display >= self::DISPLAY_WRITEABLE) && $this->filename && $this->userFiles){
386  $data = $this->_controller->component('user')->files->fetch($filename = $this->request->name);
387  if(($data !== false) && ($data !== null))
388  $this->processUpload(\Rsi\File::tempFile(\Rsi\File::ext($this->filename),$data),basename($filename));
389  }
390  }
391 
392  protected function actionSideload(){
393  if(($this->_display >= self::DISPLAY_WRITEABLE) && $this->filename && $this->sideloadPath && $this->sideloadControllerName){
394  $this->sideloadGarbage();
395  while(file_exists($filename = $this->sideloadFilename($token = \Rsi\Str::random($this->sideloadTokenLength))));
396  \Rsi\File::serialize(
397  $filename,
398  [
399  'token' => $token,
400  'config' => array_merge($this->_config,[self::FILENAME => null]),
401  'time' => $time = time()
402  ],
403  $this->sideloadMode
404  );
405  $session = $this->_controller->session;
406  $tokens = $session->sideloadTokens ?: [];
407  $tokens[$token] = $time;
408  $session->sideloadTokens = $tokens;
409  $url = $this->sideloadUrl($token);
410  $this->request->result = [
411  'token' => $token,
412  'controller' => $this->sideloadControllerName,
413  'url' => $url,
414  'hint' => $this->trans->str($this->sideloadHint,compact('token','url'))
415  ];
416  }
417  }
418 
419  protected function actionSideloadImage(){
420  if($token = $this->sideloadToken){
421  header('Content-Type: image/png');
422  imagepng((new Qr(Qr::ECC_MEDIUM,null,10))->dataImage($this->sideloadUrl($token)));
423  $this->fred->halt();
424  }
425  }
426 
427  protected function actionSideloadCheck(){
428  if(($token = $this->sideloadToken) && is_file($filename = $this->sideloadFilename($token))){
429  touch($filename);
430  $index = $skip = 0;
431  while($skip++ < $this->sideloadSkip)
432  if(($data = \Rsi\File::unserialize($filename = $this->sideloadFilename($token,$index++))) !== false) try{
433  \Rsi\File::unlink($filename);
434  $this->processUpload(\Rsi\File::tempFile(\Rsi\File::ext($this->filename),$data['data']),$data['name']);
435  $skip = 0;
436  }
437  catch(\Exception $e){
438  $this->log->warning('Error while processig sideload file: ' . $e->getMessage(),__FILE__,__LINE__);
439  \Rsi\File::unlink($filename);
440  }
441  }
442  if(!$this->request->result) $this->sideloadGarbage();
443  }
444 
445  protected function actionTus(){
446  if(($this->_display < self::DISPLAY_WRITEABLE) || !$this->filename || !$this->tusPath) http_response_code(404);
447  elseif(($method = \Rsi\Http::method()) == \Rsi\Http::METHOD_OPTIONS){ //capabilities
448  header('Tus-Resumable: ' . self::TUS_VERSION);
449  header('Tus-Version: ' . self::TUS_VERSION);
450  header('Tus-Extension: ' . self::TUS_EXTENSIONS);
451  if($this->max) header('Tus-Max-Size: ' . $this->max);
452  header('Tus-Checksum-Algorithm: ' . implode(',',hash_algos()));
453  http_response_code(204);
454  }
455  elseif($this->request->server->http->tusResumable != self::TUS_VERSION){
456  header('Tus-Version: ' . self::TUS_VERSION);
457  $this->tusError(412,'Unsupported Tus-Resumable version. Server supports ' . self::TUS_VERSION);
458  }
459  elseif($method == \Rsi\Http::METHOD_POST){ //start new upload
460 //CHECK: Komen deze meldingen nog ergens in beeld? Anders vertaalbaar maken (ala sideloadHint)
461 //CHECK: bestand van 0 bytes proberen (direct klaar)
462  if(($length = $this->tusIndex('Upload-Length')) !== false){
463  if($this->max && ($length > $this->max)) $this->tusError(413,"Upload-Length exceeds maximum allowed size of {$this->max} bytes");
464 //TODO: hier ook meteen een evt ->min controleren (alhoewel dat client-side ook al gebeurd, en hier achter ook nog eens)
465  else{
466  $this->tusGarbage();
467  $meta = [];
468  foreach(array_filter(explode(',',$this->request->server->http->uploadMetadata)) as $data){
469  $data = explode(' ',$data,2);
470  $meta[$data[0]] = count($data) > 1 ? base64_decode($data[1]) : null;
471  }
472  while(file_exists($filename = $this->tusFilename($token = \Rsi\Str::random($this->tusTokenLength))));
473  \Rsi\File::serialize(
474  $filename,
475  [
476  'length' => $length,
477  'offset' => 0,
478  'meta' => $meta,
479  'complete' => false
480  ],
481  $this->tusMode
482  );
483  file_put_contents($filename . $this->tusTempExt,null);
484  header("Location: {$this->request->server->requestUri}&token=$token");
485  header('Tus-Resumable: ' . self::TUS_VERSION);
486  http_response_code(201);
487  }
488  }
489  }
490  elseif(
491  ($token = $this->request->token) &&
492  ($data = \Rsi\File::unserialize($filename = $this->tusFilename($token))) &&
493  is_file($temp = $filename . $this->tusTempExt)
494  ) switch($method){
495  case \Rsi\Http::METHOD_HEAD: //get upload info
496  header('Upload-Offset: ' . $data['offset']);
497  header('Upload-Length: ' . $data['length']);
498  header('Cache-Control: no-store');
499  header('Tus-Resumable: ' . self::TUS_VERSION);
500  $meta = [];
501  foreach($data['meta'] as $key => $value) $meta[] = $key . ($value === null ? null : ' ' . base64_encode($value));
502  if($meta) header('Upload-Metadata: ' . implode(',',$meta));
503  http_response_code(200);
504  break;
505  case \Rsi\Http::METHOD_PATCH: //add chunk to file
506  if($data['complete']) $this->tusError(400,'Upload already complete');
507  elseif(($_SERVER['CONTENT_TYPE'] ?? null) != 'application/offset+octet-stream') $this->tusError(415,'Content-Type must be application/offset+octet-stream');
508  elseif(($offset = $this->tusIndex('Upload-Offset')) !== false){
509  if($offset != $data['offset']) $this->tusError(409,"Offset mismatch: expected {$info['offset']}, got $offset");
510  elseif($offset != filesize($temp)) $this->tusError(500,'Upload file wrong size');
511  elseif(($chunk = file_get_contents('php://input')) === false) $this->tusError(500,'Failed to read request body');
512  else{
513  $error = false;
514  if($checksum = $this->request->server->http->uploadChecksum){
515  if($error = (count($checksum = explode(' ',$checksum)) != 2)) $this->tusError(400,'Invalid Upload-Checksum header');
516  elseif($error = !in_array($algo = strtolower($checksum[0]),hash_algos())) $this->tusError(400,'Checksum algorithm not supported');
517  elseif($error = !hash_equals(base64_encode(hash($algo,$chunk,true)),$checksum[1])) $this->tusError(460,'Checksum mismatch');
518  }
519  if(!$error){
520  if(($length = $data['offset'] + strlen($chunk)) > $data['length']) $tusError(416,'Resulting file size over total length');
521  else{
522  file_put_contents($temp,$chunk,FILE_APPEND);
523  if($data['complete'] = ($data['offset'] = $length) == $data['length']){
524  $this->processUpload($temp,$meta['filename'] ?? null);
525  header('Upload-Info: ' . json_encode($this->request->result));
526  }
527  \Rsi\File::serialize($filename,$data);
528  header('Upload-Offset: ' . $length);
529  header('Tus-Resumable: ' . self::TUS_VERSION);
530  http_response_code(204);
531  }
532  }
533  }
534  }
535  break;
536  case \Rsi\Http::METHOD_DELETE: //cancel upload
537  \Rsi\File::unlink($filename . $this->tusTempExt);
538  \Rsi\File::unlink($filename);
539  header('Tus-Resumable: ' . self::TUS_VERSION);
540  http_response_code(204);
541  break;
542  default:
543  http_response_code(404);
544  }
545  else http_response_code(404);
546  $this->fred->halt();
547  }
548 
549  protected function getFilenames(){
550  return $this->multi && strpos($this->filename,'*')
551  ? \Rsi\Record::sort(\Rsi\File::dir(dirname($this->filename),basename($this->filename)))
552  : false;
553  }
554 
555  protected function getSideloadToken(){
556  $session = $this->_controller->session;
557  if(
558  ($tokens = $session->sideloadTokens) &&
559  array_key_exists($token = $this->request->token,$tokens) &&
560  ($tokens[$token] > ($time = time()) - 2 * $this->sideloadInterval)
561  ){
562  $tokens[$token] = $time;
563  $session->sideloadTokens = $tokens;
564  return $token;
565  }
566  }
567 
568 }
Rsi\Fred\Controller\Widget\File\TYPES
const TYPES
Allowed file types (mime or extension; mime can have asterisk wildcards or even be a complete.
Definition: File.php:9
Rsi\Fred\Controller\Widget\File\actionUserFilePreview
actionUserFilePreview()
Definition: File.php:373
Rsi\Fred\Controller\Widget\File\$userFiles
$userFiles
Definition: File.php:31
Rsi\Fred\Controller\Widget\File\$sideloadPath
$sideloadPath
Path to temporarily store sideload files.
Definition: File.php:34
Rsi\Fred\Controller\Widget\File\actionRotate
actionRotate()
Definition: File.php:348
Rsi\Fred\Controller\Widget\File\sideloadFilename
sideloadFilename($token, $index=null)
Filename for a sideload file.
Definition: File.php:261
Rsi\Fred\Controller\Widget\File\$previewSize
$previewSize
Definition: File.php:25
Rsi\Fred\Controller\Widget\File\$tusPath
$tusPath
Path to temporarily store TUS data.
Definition: File.php:43
Rsi\Fred\Controller\Widget\File\$previewTtl
$previewTtl
Definition: File.php:26
Rsi\Fred\Controller\Widget\File\actionUserFiles
actionUserFiles()
Definition: File.php:366
Rsi
Rsi\Fred\Controller\Widget\File\actionSideloadImage
actionSideloadImage()
Definition: File.php:419
Rsi\Fred\Controller\Widget\File\$tusTokenLength
$tusTokenLength
Length of the TUS token.
Definition: File.php:47
Rsi\Fred\Controller\Widget\File\$previewMax
$previewMax
Maximum number of preview images.
Definition: File.php:28
Rsi\Fred\Controller\Widget\File\actionUpload
actionUpload()
Definition: File.php:340
Rsi\Fred\Controller\Widget\File\tusIndex
tusIndex($header)
Definition: File.php:297
Rsi\Fred\Controller\Widget\File\$sideloadInterval
$sideloadInterval
Poll interval for sideloading check (seconds).
Definition: File.php:39
Rsi\Fred\Controller\Widget\MAX
const MAX
Maximum value.
Definition: Widget.php:29
Rsi\Fred\Controller\Widget\File\EVENT_UPLOAD
const EVENT_UPLOAD
Definition: File.php:17
Rsi\Fred\Controller\Widget\File\FILENAME
const FILENAME
Fixed filename for the upload. If everything is OK, file will be overwitten directly,...
Definition: File.php:11
Rsi\Fred\Component\filemtime
filemtime($filename)
Filemtime with session cache.
Definition: Component.php:66
Rsi\Fred\Controller\Widget\File\clientConfig
clientConfig()
Public configuration.
Definition: File.php:56
Rsi\Fred\Controller\Widget\File\tusGarbage
tusGarbage()
Definition: File.php:308
Rsi\Fred\Controller\Widget\File\$previewKeyPrefix
$previewKeyPrefix
Definition: File.php:27
Rsi\Fred\Controller\Widget\File\TUS_EXTENSIONS
const TUS_EXTENSIONS
Definition: File.php:20
Rsi\Fred\Controller\Widget\File\checkTypes
checkTypes($value, $index=null)
Definition: File.php:115
Rsi\Fred\Controller\Widget\File\rotate
rotate($angle=90, $filename=null)
Rotate a file.
Definition: File.php:218
Rsi\Fred\Controller\Widget\File\actionSideloadCheck
actionSideloadCheck()
Definition: File.php:427
Rsi\Fred\Controller\Widget\File\$tusExt
$tusExt
Extension for TUS data files.
Definition: File.php:44
Rsi\Fred\Controller\Widget\File\actionSideload
actionSideload()
Definition: File.php:392
Rsi\Fred\Controller\Widget\File\actionTus
actionTus()
Definition: File.php:445
Rsi\Fred\Controller\Widget\File\uploadEvent
uploadEvent($name)
Definition: File.php:99
Rsi\Fred\Controller\Widget\File\$_raw
$_raw
Definition: File.php:53
Rsi\Fred\Controller\Widget\File\tusFilename
tusFilename($token)
Definition: File.php:304
Rsi\Fred\Controller\Widget\File\$sideloadHint
$sideloadHint
Hint to show during sideloading (translated).
Definition: File.php:38
Rsi\Fred\Controller\Widget\File\preview
preview($filename=null, &$count=null, &$rotate=null)
Generate a preview.
Definition: File.php:189
Rsi\Fred\Controller\Widget\File\check
check($value, $index=null)
Definition: File.php:124
Rsi\Fred\Controller\Widget\File\$memoryFactor
$memoryFactor
Factor memory for async file upload processing.
Definition: File.php:30
Rsi\Fred\Controller\Widget\File\purgeBase
purgeBase($value)
Purge uploaded filename from possible array construction.
Definition: File.php:87
Rsi\Fred\Controller\Widget\File\$tusTimeout
$tusTimeout
Time after which an inactive upload is removed (seconds).
Definition: File.php:48
Rsi\Fred\Controller\Widget\File\$types
$types
Definition: File.php:22
Rsi\Fred\Controller\Widget\File\$multi
$multi
Definition: File.php:24
Rsi\Fred\Controller\Widget\File\$tusChunkSize
$tusChunkSize
Upload chunk size for TUS upload (empty = calculate).
Definition: File.php:50
Rsi\Fred\Controller\Widget\File\$tusGarbageChance
$tusGarbageChance
Clear TUS path once every ... times.
Definition: File.php:49
Rsi\Fred\Controller\Widget\File\actionDownload
actionDownload()
Definition: File.php:329
Rsi\Fred\Controller\Widget\File\tusError
tusError($code, $message)
Definition: File.php:290
Rsi\Fred\Controller\Widget\File\sideloadGarbage
sideloadGarbage()
Clear old sideload tokens.
Definition: File.php:275
Rsi\Fred\Controller\Widget\File\$tusScriptSrc
$tusScriptSrc
Script to load for TUS upload (set to empty when already loaded).
Definition: File.php:51
Rsi\Fred\Controller\Widget\File
Definition: File.php:7
Rsi\Fred\Controller\Widget\File\$sideloadExt
$sideloadExt
Extension for sideload files.
Definition: File.php:35
Rsi\Fred\Controller\Widget\File\PREVIEW_SIZE
const PREVIEW_SIZE
Size (width) of preview image (false = no preview).
Definition: File.php:14
Rsi\Fred\Controller\Widget\File\checkUpload
checkUpload($value, $index=null)
Definition: File.php:111
Rsi\Fred\Controller\Widget\File\actionDelete
actionDelete()
Definition: File.php:359
Rsi\Fred\Controller\Widget\File\getFilenames
getFilenames()
Definition: File.php:549
Rsi\Fred\Controller\Widget\File\$tusTempExt
$tusTempExt
Extension for TUS partial file upload.
Definition: File.php:45
Rsi\Fred\Controller\Widget
Definition: Char.php:3
Rsi\Fred\Controller\Widget\File\$tusMode
$tusMode
File mode for TUS data files.
Definition: File.php:46
Rsi\Fred\Controller\Widget\File\actionUserFile
actionUserFile()
Definition: File.php:384
Rsi\Fred\Controller\Widget\File\USER_FILES
const USER_FILES
Show user files.
Definition: File.php:15
Rsi\Fred\Controller\Widget\File\$sideloadSkip
$sideloadSkip
Stop scanning for new files after this number of misses.
Definition: File.php:41
Rsi\Fred\Controller\Widget\File\$_multiRaw
$_multiRaw
Definition: File.php:54
Rsi\Fred\Controller\Widget\File\actionPreview
actionPreview()
Definition: File.php:321
Rsi\Fred\Controller\Widget\File\checkMax
checkMax($value, $index=null)
Definition: File.php:107
Rsi\Fred\Controller\Widget\File\getSideloadToken
getSideloadToken()
Definition: File.php:555
Rsi\Fred\Controller\Widget\File\$sideloadGarbageChance
$sideloadGarbageChance
Clear sideloading path once every ... times.
Definition: File.php:40
Rsi\Fred\Controller\Widget\File\$sideloadTokenLength
$sideloadTokenLength
Length of the sideload token.
Definition: File.php:37
Rsi\Fred\Controller\Widget\File\$sideloadMode
$sideloadMode
File mode for sideload files.
Definition: File.php:36
Rsi\Fred\Controller\Widget\TRAILER
const TRAILER
Trailer string (translated).
Definition: Widget.php:22
Rsi\Fred\Controller\Widget\File\processUpload
processUpload($temp, $name)
Process upload data.
Definition: File.php:240
Rsi\Fred\Controller\Widget\File\sideloadUrl
sideloadUrl($token)
Sideload URL.
Definition: File.php:269
Rsi\Fred\Controller\Widget\File\previewFile
previewFile($filename)
Definition: File.php:140
Rsi\Fred\Controller\Widget\File\$sideloadControllerName
$sideloadControllerName
Controller that provides the sideloading.
Definition: File.php:33
Rsi\Fred\Exception
Definition: Exception.php:5
Rsi\Fred\Controller\Widget\File\MULTI
const MULTI
Allow multiple files to be uploaded.
Definition: File.php:13
Rsi\Fred\Controller\Widget\File\$memoryMargin
$memoryMargin
Spare memory for async file upload processing.
Definition: File.php:29
Rsi\Fred\Controller\Widget\File\TUS_VERSION
const TUS_VERSION
Definition: File.php:19
Rsi\Fred\Controller\Widget\File\$filename
$filename
Definition: File.php:23
Rsi\Fred\Controller\Widget\File\checkMin
checkMin($value, $index=null)
Definition: File.php:103