1: <?php
  2: 
  3:   4:   5: 
  6: class Quform_Uploader
  7: {
  8:       9:  10: 
 11:     protected static $fileKeys = array('error', 'name', 'size', 'tmp_name', 'type');
 12: 
 13:      14:  15: 
 16:     protected $session;
 17: 
 18:      19:  20: 
 21:     protected $repository;
 22: 
 23:      24:  25: 
 26:     protected $formFactory;
 27: 
 28:      29:  30:  31:  32: 
 33:     public function __construct(Quform_Session $session, Quform_Repository $repository,
 34:                                 Quform_Form_Factory $formFactory)
 35:     {
 36:         $this->session = $session;
 37:         $this->repository = $repository;
 38:         $this->formFactory = $formFactory;
 39:     }
 40: 
 41:      42:  43: 
 44:     public function upload()
 45:     {
 46:         if ( ! Quform::isPostRequest() || Quform::get($_POST, 'quform_ajax_uploading') != '1') {
 47:             return;
 48:         }
 49: 
 50:         $this->validateUploadRequest();
 51:         $this->handleUploadRequest();
 52:     }
 53: 
 54:      55:  56: 
 57:     protected function handleUploadRequest()
 58:     {
 59:         $config = $this->repository->getConfig((int) $_POST['quform_form_id']);
 60: 
 61:         if ( ! is_array($config)) {
 62:             wp_send_json(array(
 63:                 'type' => 'error',
 64:                 'message' => __('Could not find the form config', 'quform')
 65:             ));
 66:         }
 67: 
 68:         $config['uniqueId'] = $_POST['quform_form_uid'];
 69: 
 70:         $form = $this->formFactory->create($config);
 71: 
 72:         if ( ! ($form instanceof Quform_Form) || $form->config('trashed')) {
 73:             wp_send_json(array(
 74:                 'type' => 'error',
 75:                 'message' => __('Could not find the form', 'quform')
 76:             ));
 77:         }
 78: 
 79:         if ( ! $form->isActive()) {
 80:             wp_send_json(array(
 81:                 'type' => 'error',
 82:                 'message' => __('This form is not currently active', 'quform')
 83:             ));
 84:         }
 85: 
 86:         $element = $form->getElementById((int) $_POST['quform_element_id']);
 87: 
 88:         if ( ! ($element instanceof Quform_Element_File)) {
 89:             wp_send_json(array(
 90:                 'type' => 'error',
 91:                 'message' => __('Could not find the element', 'quform')
 92:             ));
 93:         }
 94: 
 95:         if ( ! isset($_FILES[$element->getName()])) {
 96:             wp_send_json(array(
 97:                 'type' => 'error',
 98:                 'message' => __('File data not found', 'quform')
 99:             ));
100:         }
101: 
102:         $uploadsTmpDir = $this->getUploadsTempDir();
103: 
104:         if ( ! is_dir($uploadsTmpDir)) {
105:             wp_mkdir_p($uploadsTmpDir);
106:         }
107: 
108:         if ( ! wp_is_writable($uploadsTmpDir)) {
109:             wp_send_json(array(
110:                 'type' => 'error',
111:                 'message' => __('Temporary uploads directory is not writable', 'quform')
112:             ));
113:         }
114: 
115:         
116:         $validator = $element->getFileUploadValidator();
117:         $validator->setConfig('minimumNumberOfFiles', 0);
118: 
119:         if ($element->isValid()) {
120:             $sessionKey = $form->getSessionKey() . '.uploads.' . $element->getName();
121: 
122:             
123:             $uniqueId = $this->generateUploadUid();
124: 
125:             
126:             $filename = tempnam($uploadsTmpDir, 'quform');
127:             move_uploaded_file($_FILES[$element->getName()]['tmp_name'][0], $filename);
128:             $_FILES[$element->getName()]['tmp_name'][0] = $filename;
129: 
130:             $files = $this->session->has($sessionKey) ? $this->session->get($sessionKey) : array();
131: 
132:             foreach (self::$fileKeys as $key) {
133:                 $files[$key][] = $_FILES[$element->getName()][$key][0];
134:             }
135: 
136:             $files['quform_upload_uid'][] = $uniqueId;
137:             $files['timestamp'][] = time();
138: 
139:             $this->session->set($sessionKey, $files);
140: 
141:             wp_send_json(array(
142:                 'type' => 'success',
143:                 'uid' => $uniqueId
144:             ));
145:         } else {
146:             wp_send_json(array(
147:                 'type' => 'error',
148:                 'message' => $element->getError()
149:             ));
150:         }
151:     }
152: 
153:     154: 155: 156: 157: 
158:     protected function getUploadsTempDir()
159:     {
160:         return Quform::getTempDir('/quform/uploads');
161:     }
162: 
163:     164: 165: 
166:     protected function validateUploadRequest()
167:     {
168:         if ( ! isset($_POST['quform_form_id'], $_POST['quform_form_uid'], $_POST['quform_element_id']) ||
169:              ! is_numeric($_POST['quform_form_id']) ||
170:              ! Quform_Form::isValidUniqueId($_POST['quform_form_uid']) ||
171:              ! is_numeric($_POST['quform_element_id'])
172:         ) {
173:             wp_send_json(array(
174:                 'type' => 'error',
175:                 'message' => __('Bad request', 'quform')
176:             ));
177:         }
178:     }
179: 
180:     181: 182: 183: 184: 
185:     public function process(Quform_Form $form)
186:     {
187:         foreach ($form->getRecursiveIterator() as $element) {
188:             if ( ! ($element instanceof Quform_Element_File)) {
189:                 continue;
190:             }
191: 
192:             $elementName = $element->getName();
193: 
194:             if ( ! array_key_exists($elementName, $_FILES) || ! is_array($_FILES[$elementName])) {
195:                 continue;
196:             }
197: 
198:             $files = $_FILES[$elementName];
199: 
200:             if (is_array($files['error'])) {
201:                 foreach ($files['error'] as $key => $error) {
202:                     if ($error == UPLOAD_ERR_OK) {
203:                         
204:                         $file = array();
205: 
206:                         foreach (self::$fileKeys as $k) {
207:                             $file[$k] = $files[$k][$key];
208:                         }
209: 
210:                         
211:                         $file['quform_upload_uid'] = isset($files['quform_upload_uid'][$key]) && $this->isValidUploadUid($files['quform_upload_uid'][$key]) ? $files['quform_upload_uid'][$key] : $this->generateUploadUid();
212:                         $file['timestamp'] = isset($files['timestamp'][$key]) ? $files['timestamp'][$key] : time();
213: 
214:                         $this->processUploadedFile($file, $element, $form);
215:                     }
216:                 }
217:             }
218:         }
219:     }
220: 
221:     222: 223: 224: 225: 226: 227: 
228:     protected function processUploadedFile(array $file, Quform_Element_File $element, Quform_Form $form)
229:     {
230:         $pathInfo = pathinfo($file['name']);
231:         $extension = isset($pathInfo['extension']) ? $pathInfo['extension'] : '';
232: 
233:         $filename = Quform::isNonEmptyString($extension) ? str_replace(".$extension", '', $pathInfo['basename']) : $pathInfo['basename'];
234:         $filename = sanitize_file_name($filename);
235:         $filename = apply_filters('quform_filename_' . $element->getName(), $filename, $element, $form); 
236:         $filename = apply_filters('quform_upload_filename_' . $element->getIdentifier(), $filename, $file, $element, $form);
237: 
238:         if (Quform::isNonEmptyString($extension)) {
239:             $filename = Quform::isNonEmptyString($filename) ? "$filename.$extension" : "upload.$extension";
240:         } else {
241:             $filename = Quform::isNonEmptyString($filename) ? $filename : 'upload';
242:         }
243: 
244:         $file['name'] = $filename;
245:         $file['path'] = $file['tmp_name'];
246: 
247:         unset($file['error'], $file['tmp_name']);
248: 
249:         if ($element->config('saveToServer')) {
250:             $result = $this->saveUploadedFile($file, $element, $form);
251: 
252:             if (is_array($result)) {
253:                 $file = $result;
254: 
255:                 if ($element->config('addToMediaLibrary')) {
256:                     $this->addToMediaLibrary($file, $element, $form);
257:                 }
258:             }
259:         } else {
260:             
261:             $tmpPath = trailingslashit(dirname($file['path']));
262: 
263:             
264:             if (file_exists($tmpPath . $file['name'])) {
265:                 $count = 1;
266:                 $newFilenamePath = $tmpPath . $file['name'];
267: 
268:                 while (file_exists($newFilenamePath)) {
269:                     $newFilename = $count++ . '_' . $file['name'];
270:                     $newFilenamePath = $tmpPath . $newFilename;
271:                 }
272: 
273:                 $file['name'] = $newFilename;
274:             }
275: 
276:             
277:             if (rename($file['path'], $tmpPath . $file['name']) !== false) {
278:                 chmod($tmpPath . $file['name'], 0644);
279: 
280:                 $file['path'] = $tmpPath . $file['name'];
281:             }
282:         }
283: 
284:         $element->addFile($file);
285:     }
286: 
287:     288: 289: 290: 291: 292: 293: 
294:     protected function addToMediaLibrary(array $file, Quform_Element_File $element, Quform_Form $form)
295:     {
296:         require_once ABSPATH . 'wp-admin/includes/image.php';
297:         require_once ABSPATH . 'wp-admin/includes/media.php';
298: 
299:         $type = wp_check_filetype($file['name']);
300: 
301:         $attachment = array(
302:             'post_title' => $file['name'],
303:             'post_content' => '',
304:             'post_mime_type' => $type['type'],
305:             'guid' => $file['url']
306:         );
307: 
308:         $attachment = apply_filters('quform_uploader_attachment', $attachment, $file, $element, $form);
309:         $attachment = apply_filters('quform_uploader_attachment_' . $element->getIdentifier(), $attachment, $file, $element, $form);
310: 
311:         $attachId = wp_insert_attachment($attachment, $file['path']);
312:         wp_update_attachment_metadata($attachId, wp_generate_attachment_metadata($attachId, $file['path']));
313:     }
314: 
315:     316: 317: 318: 319: 320: 321: 322: 323: 324: 
325:     protected function saveUploadedFile(array $file, Quform_Element_File $element, Quform_Form $form)
326:     {
327:         if (($wpUploadsDir = Quform::getUploadsDir()) == false) {
328:             
329:             return false;
330:         }
331: 
332:         
333:         $path = $element->config('savePath') == '' ? 'quform/{form_id}-{upload_security_token}/{year}/{month}/' : $element->config('savePath');
334: 
335:         
336:         $path = str_replace(
337:             array(
338:                 '{form_id}',
339:                 '{year}',
340:                 '{month}',
341:                 '{day}',
342:                 '{upload_security_token}'
343:             ),
344:             array(
345:                 $form->getId(),
346:                 Quform::date('Y'),
347:                 Quform::date('m'),
348:                 Quform::date('d'),
349:                 md5($form->getId() . $form->config('createdAt'))
350:             ),
351:             $path
352:         );
353: 
354:         
355:         $path = apply_filters('quform_upload_path', $path, $element, $form);
356:         $path = apply_filters('quform_upload_path_' . $form->getId(), $path, $element, $form);
357: 
358:         
359:         $absolutePath = rtrim($wpUploadsDir, '/') . '/' . ltrim($path, '/');
360: 
361:         
362:         $absolutePath = apply_filters('quform_upload_absolute_path', $absolutePath, $element, $form);
363:         $absolutePath = apply_filters('quform_upload_absolute_path_' . $form->getId(), $absolutePath, $element, $form);
364: 
365:         
366:         $path = trailingslashit($path);
367:         $absolutePath = trailingslashit($absolutePath);
368: 
369:         
370:         if (!is_dir($absolutePath)) {
371:             wp_mkdir_p($absolutePath);
372:         }
373: 
374:         
375:         if (file_exists($absolutePath . $file['name'])) {
376:             $count = 1;
377:             $newFilenamePath = $absolutePath . $file['name'];
378: 
379:             while (file_exists($newFilenamePath)) {
380:                 $newFilename = $count++ . '_' . $file['name'];
381:                 $newFilenamePath = $absolutePath . $newFilename;
382:             }
383: 
384:             $file['name'] = $newFilename;
385:         }
386: 
387:         
388:         if (rename($file['path'], $absolutePath . $file['name']) !== false) {
389:             chmod($absolutePath . $file['name'], 0644);
390: 
391:             $file['path'] = $absolutePath . $file['name'];
392:             $file['url'] = Quform::getUploadsUrl($path . $file['name']);
393: 
394:             return $file;
395:         } else {
396:             return false;
397:         }
398:     }
399: 
400:     401: 402: 403: 404: 
405:     public function mergeSessionFiles(Quform_Form $form)
406:     {
407:         $uploads = $this->session->get(sprintf('%s.uploads', $form->getSessionKey()));
408: 
409:         
410:         $removedUploadUids = isset($_POST['quform_removed_upload_uids']) && Quform::isNonEmptyString($_POST['quform_removed_upload_uids']) ? explode(',', $_POST['quform_removed_upload_uids']) : array();
411: 
412:         if (is_array($uploads)) {
413:             foreach ($uploads as $elementName => $uploadInfo) {
414:                 if (is_array($uploadInfo['quform_upload_uid'])) {
415:                     
416:                     foreach ($uploadInfo['quform_upload_uid'] as $key => $id) {
417:                         if (in_array($id, $removedUploadUids)) {
418:                             foreach (self::$fileKeys as $fileKey) {
419:                                 unset($uploads[$elementName][$fileKey][$key]);
420:                             }
421:                             unset($uploads[$elementName]['quform_upload_uid'][$key]);
422:                         }
423:                     }
424: 
425:                     
426:                     if ( ! count($uploads[$elementName]['quform_upload_uid'])) {
427:                         unset($uploads[$elementName]);
428:                     }
429:                 } else {
430:                     
431:                     if (in_array($uploadInfo['quform_upload_uid'], $removedUploadUids)) {
432:                         unset($uploads[$elementName]);
433:                     }
434:                 }
435:             }
436: 
437:             
438:             $_FILES = array_merge($_FILES, $uploads);
439:         }
440: 
441:         $files = $this->session->get(sprintf('%s.files', $form->getSessionKey()));
442: 
443:         if (is_array($files)) {
444:             foreach ($files as $elementName => $value) {
445:                 $element = $form->getElementByName($elementName);
446: 
447:                 foreach ($value as $key => $file) {
448:                     if (in_array($file['quform_upload_uid'], $removedUploadUids)) {
449:                         unset($value[$key]);
450:                     }
451:                 }
452: 
453:                 $value = array_values($value); 
454: 
455:                 if ($element instanceof Quform_Element_File) {
456:                     $element->setValue($value);
457:                 }
458:             }
459:         }
460:     }
461: 
462:     463: 464: 465: 466: 
467:     public function saveFileUploadValuesIntoSession(Quform_Form $form)
468:     {
469:         foreach ($form->getRecursiveIterator() as $element) {
470:             if ( ! $element instanceof Quform_Element_File) {
471:                 continue;
472:             }
473: 
474:             if ( ! $element->isEmpty()) {
475:                 $this->session->set($form->getSessionKey() . '.files.' . $element->getName(), $element->getValue());
476:             }
477:         }
478:     }
479: 
480:     481: 482: 483: 484: 
485:     public function saveUploadedFilesIntoSession(Quform_Form $form)
486:     {
487:         foreach ($form->getRecursiveIterator() as $element) {
488:             if ( ! $element instanceof Quform_Element_File) {
489:                 continue;
490:             }
491: 
492:             $elementName = $element->getName();
493: 
494:             if ( ! array_key_exists($elementName, $_FILES) || ! is_array($_FILES[$elementName])) {
495:                 continue;
496:             }
497: 
498:             $uploadsTmpDir = $this->getUploadsTempDir();
499: 
500:             if ( ! is_dir($uploadsTmpDir)) {
501:                 wp_mkdir_p($uploadsTmpDir);
502:             }
503: 
504:             if ( ! wp_is_writable($uploadsTmpDir)) {
505:                 continue;
506:             }
507: 
508:             if ($element->isValid()) {
509:                 $sessionKey = $form->getSessionKey() . '.uploads.' . $elementName;
510:                 $files = array();
511: 
512:                 foreach ($_FILES[$elementName]['error'] as $key => $error) {
513:                     if ($error == UPLOAD_ERR_OK) {
514:                         if (is_uploaded_file($_FILES[$elementName]['tmp_name'][$key])) {
515:                             $filename = tempnam($uploadsTmpDir, 'quform');
516:                             move_uploaded_file($_FILES[$elementName]['tmp_name'][$key], $filename);
517:                             $_FILES[$elementName]['tmp_name'][$key] = $filename;
518:                         }
519: 
520:                         foreach (self::$fileKeys as $fileKey) {
521:                             $files[$fileKey][] = $_FILES[$elementName][$fileKey][$key];
522:                             $file[$fileKey] = $_FILES[$elementName][$fileKey][$key];
523:                         }
524: 
525:                         $files['quform_upload_uid'][] = isset($_FILES[$elementName]['quform_upload_uid'][$key]) && $this->isValidUploadUid($_FILES[$elementName]['quform_upload_uid'][$key]) ? $_FILES[$elementName]['quform_upload_uid'][$key] : $this->generateUploadUid();
526:                         $files['timestamp'][] = time();
527: 
528:                         $element->addFile($file);
529:                     }
530:                 }
531: 
532:                 if (count($files)) {
533:                     $this->session->set($sessionKey, $files);
534:                 } else {
535:                     $this->session->forget($sessionKey);
536:                 }
537:             }
538:         }
539:     }
540: 
541:     542: 543: 544: 545: 546: 
547:     protected function isValidUploadUid($uid)
548:     {
549:         return is_string($uid) && preg_match('/^[a-zA-Z0-9]{40}$/', $uid);
550:     }
551: 
552:     553: 554: 555: 556: 
557:     protected function generateUploadUid()
558:     {
559:         return Quform::randomString(40);
560:     }
561: 
562:     563: 564: 565: 
566:     public function cleanup()
567:     {
568:         $uploadsTmpDir = $this->getUploadsTempDir();
569: 
570:         if (is_dir($uploadsTmpDir) && $handle = opendir($uploadsTmpDir)) {
571:             clearstatcache();
572:             $keepUntil = time() - 21600; 
573:             while (false !== ($file = readdir($handle))) {
574:                 $filePath = $uploadsTmpDir . '/' . $file;
575:                 $mtime = filemtime($filePath);
576:                 if ($file != '.' && $file != '..' && $mtime < $keepUntil) {
577:                     @unlink($filePath);
578:                 }
579:             }
580: 
581:             closedir($handle);
582:         }
583:     }
584: 
585:     586: 587: 
588:     protected function scheduleCleanup()
589:     {
590:         if ( ! wp_next_scheduled('quform_upload_cleanup')) {
591:             wp_schedule_event(time() + (12 * HOUR_IN_SECONDS), 'twicedaily', 'quform_upload_cleanup');
592:         }
593:     }
594: 
595:     596: 597: 
598:     protected function unscheduleCleanup()
599:     {
600:         if ($timestamp = wp_next_scheduled('quform_upload_cleanup')) {
601:             wp_unschedule_event($timestamp, 'quform_upload_cleanup');
602:         }
603:     }
604: 
605:     606: 607: 608: 609: 
610:     public function activate()
611:     {
612:         $this->scheduleCleanup();
613: 
614:         $uploadsTmpDir = $this->getUploadsTempDir();
615: 
616:         if ( ! is_dir($uploadsTmpDir)) {
617:             wp_mkdir_p($uploadsTmpDir);
618:         }
619:     }
620: 
621:     622: 623: 624: 625: 
626:     public function deactivate()
627:     {
628:         $this->unscheduleCleanup();
629:         $this->cleanup();
630:     }
631: 
632:     633: 634: 635: 636: 
637:     public function uninstall()
638:     {
639:         $this->unscheduleCleanup();
640:         $this->cleanup();
641:     }
642: }
643: