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: