1: <?php
2: /**
3: * This file is part of the PHPLucidFrame library.
4: * Helper for ajax-like file upload with instant preview if the preview placeholder is provided
5: *
6: * @package PHPLucidFrame\File
7: * @since PHPLucidFrame v 1.3.0
8: * @copyright Copyright (c), PHPLucidFrame.
9: * @link http://phplucidframe.com
10: * @license http://www.opensource.org/licenses/mit-license.php MIT License
11: *
12: * This source file is subject to the MIT license that is bundled
13: * with this source code in the file LICENSE
14: */
15:
16: namespace LucidFrame\Core;
17:
18: /**
19: * Helper for ajax-like file upload with instant preview if the preview placeholder is provided
20: */
21: class AsynFileUploader
22: {
23: /** @var string The input name or the POST name */
24: private $name;
25: /** @var string The HTML id of the file browsing button */
26: private $id;
27: /** @var string The input label name that shown to the user */
28: private $label;
29: /** @var string The button caption */
30: private $caption;
31: /** @var string The uploaded file name */
32: private $value;
33: /** @var array The array of hidden values to be posted to the callbacks */
34: private $hidden;
35: /** @var string The directory path where the file to be uploaded permanently */
36: private $uploadDir;
37: /** @var array The allowed file extensions; defaults to jpg, jpeg, png, gif */
38: private $extensions;
39: /** @var int The maximum file size allowed to upload in MB */
40: private $maxSize;
41: /** @var array Image dimensions */
42: private $dimensions;
43: /** @var string URL that handles the file uploading process */
44: private $uploadHandler;
45: /** @var array Array of HTML ID of the buttons to be disabled while uploading */
46: private $buttons;
47: /** @var boolean Enable ajax file delete or not */
48: private $isDeletable;
49: /** @var boolean The uploaded file name is displayed or not */
50: private $fileNameIsDisplayed;
51: /** @var bool Flag to save the uploaded file with original file name */
52: private $uploadAsOriginalFileName = false;
53: /** @var string The hook name that handles file upload process interacting the database layer */
54: private $onUpload;
55: /** @var string The hook name that handles file deletion process interacting the database layer */
56: private $onDelete;
57:
58: /**
59: * Constructor
60: *
61: * @param string/array anonymous The input file name or The array of property/value pairs
62: */
63: public function __construct()
64: {
65: $this->name = 'file';
66: $this->id = '';
67: $this->label = _t('File');
68: $this->caption = _t('Choose File');
69: $this->value = array();
70: $this->hidden = array();
71: $this->maxSize = 10;
72: $this->extensions = array();
73: $this->uploadDir = FILE . 'tmp' . _DS_;
74: $this->buttons = array();
75: $this->dimensions = '';
76: $this->uploadHandler = WEB_ROOT . 'lib/asyn-file-uploader.php';
77: $this->isDeletable = true;
78: $this->fileNameIsDisplayed = true;
79: $this->onUpload = '';
80: $this->onDelete = '';
81:
82: if (func_num_args()) {
83: $arg = func_get_arg(0);
84: if (is_string($arg)) {
85: $this->name = $arg;
86: } elseif (is_array($arg)) {
87: foreach ($arg as $key => $value) {
88: if (isset($this->{$key})) {
89: $this->{$key} = $value;
90: }
91: }
92: }
93: }
94: }
95: /**
96: * Setter for the property `name`
97: * @param string $name The unique name for the file input element
98: */
99: public function setName($name)
100: {
101: $this->name = $name;
102: }
103: /**
104: * Setter for the property `id`
105: * @param string $id The unique HTML id for the file browsing button
106: */
107: public function setId($id)
108: {
109: $this->id = $id;
110: }
111: /**
112: * Setter for the property `label`
113: * @param string $label The caption name for the file input to use in validation error
114: */
115: public function setLabel($label)
116: {
117: $this->label = $label;
118: }
119: /**
120: * Setter for the property `caption`
121: * @param string $caption The caption for image uploaded
122: */
123: public function setCaption($caption)
124: {
125: $this->caption = $caption;
126: }
127: /**
128: * Setter for the property `value`
129: * @param array $value The file name saved in the database
130: * @param int $id The ID related to the file name saved in the database
131: * @return void
132: */
133: public function setValue($value, $id = 0)
134: {
135: $this->value = array(
136: $id => $value
137: );
138: }
139: /**
140: * Getter for the property `value`
141: */
142: public function getValue()
143: {
144: return is_array($this->value) ? current($this->value) : $this->value;
145: }
146: /**
147: * Getter for the id saved in db related to the value
148: */
149: public function getValueId()
150: {
151: if (is_array($this->value)) {
152: return current(array_keys($this->value));
153: }
154: return 0;
155: }
156: /**
157: * Setter for the property `hidden`
158: * @param string $key The key - id, dimensions, fileName or uniqueId
159: * @param mixed $value The value for the key
160: * @return void
161: */
162: public function setHidden($key, $value = '')
163: {
164: if (!in_array($key, array('id', 'dimensions', 'fileName', 'uniqueId'))) {
165: # skip for reserved keys
166: $this->hidden[$key] = $value;
167: }
168: }
169: /**
170: * Setter for the property `uploadDir`
171: * @param string $dir The directory where the file will be uploaded. Default to /files/tmp/
172: */
173: public function setUploadDir($dir)
174: {
175: $this->uploadDir = $dir;
176: }
177: /**
178: * Setter for the property `maxSize`
179: * @param int $size The maximum file size allowed in MB
180: */
181: public function setMaxSize($size)
182: {
183: $this->maxSize = $size;
184: }
185: /**
186: * Setter for the property `extensions`
187: * @param array $extensions The array of extensions such as `array('jpg', 'png', 'gif')`
188: */
189: public function setExtensions($extensions)
190: {
191: $this->extensions = $extensions;
192: }
193: /**
194: * Setter for the property `dimensions`
195: * @param array $dimensions The array of extensions such as `array('600x400', '300x200')`
196: */
197: public function setDimensions($dimensions)
198: {
199: $this->dimensions = $dimensions;
200: }
201: /**
202: * Setter for the property `buttons`
203: * @param string $arg1[,$arg2,$arg3,...] The HTML element ID for each button
204: */
205: public function setButtons()
206: {
207: $this->buttons = func_get_args();
208: }
209: /**
210: * Setter for the property `isDeletable`
211: * @param boolean $value If the delete button is provided or not
212: */
213: public function isDeletable($value)
214: {
215: $this->isDeletable = $value;
216: }
217: /**
218: * Setter for the property `fileNameIsDisplayed`
219: * @param boolean $value If the uploaded file name is displayed next to the button or not
220: */
221: public function isFileNameDisplayed($value)
222: {
223: $this->fileNameIsDisplayed = $value;
224: }
225:
226: /**
227: * Setter for the property `uploadAsOriginalFileName`
228: * @param boolean $value Use original file name on upload or not
229: */
230: public function setUploadAsOriginalFileName($value)
231: {
232: $this->uploadAsOriginalFileName = $value;
233: }
234: /**
235: * Setter for the `onUpload` hook
236: * @param string $callable The callback PHP function name
237: */
238: public function setOnUpload($callable)
239: {
240: $this->onUpload = $callable;
241: }
242: /**
243: * Setter for the `onDelete` hook
244: * @param string $callable The callback PHP function name
245: */
246: public function setOnDelete($callable)
247: {
248: $this->onDelete = $callable;
249: }
250: /**
251: * Setter for the property `uploadHandler`
252: * @param string $url The URL where file upload will be handled
253: */
254: public function setUploadHandler($url)
255: {
256: $this->uploadHandler = $url;
257: }
258: /**
259: * Display file input HTML
260: * @param array $attributes The HTML attribute option for the button
261: *
262: * array(
263: * 'class' => '',
264: * 'id' => '',
265: * 'title' => ''
266: * )
267: *
268: */
269: public function html($attributes = array())
270: {
271: $name = $this->name;
272: $maxSize = $this->maxSize * 1024 * 1024; # convert to bytes
273:
274: # HTML attribute preparation for the file browser button
275: $attrHTML = array();
276: $htmlIdForButton = false;
277: $htmlClassForButton = false;
278: foreach ($attributes as $attrName => $attrVal) {
279: $attrName = strtolower($attrName);
280: if ($attrName === 'class' && $attrVal) {
281: $htmlClassForButton = true;
282: $attrVal = 'asynfileuploader-button '.$attrVal;
283: }
284: if ($attrName === 'id' && $attrVal) {
285: $this->id = $attrVal;
286: $htmlIdForButton = true;
287: }
288: $attrHTML[] = $attrName.'="'.$attrVal.'"';
289: }
290: if ($htmlIdForButton === false) {
291: $this->id = 'asynfileuploader-button-'.$name;
292: $attrHTML[] = 'id="'.$this->id.'"';
293: }
294: if ($htmlClassForButton === false) {
295: $attrHTML[] = 'class="asynfileuploader-button button"';
296: }
297: $buttonAttrHTML = implode(' ', $attrHTML);
298:
299: $args = array();
300: $args[] = 'name=' . $name;
301: $args[] = 'id=' . $this->id;
302: $args[] = 'label=' . $this->label;
303: $args[] = 'dir=' . base64_encode($this->uploadDir);
304: $args[] = 'buttons=' . implode(',', $this->buttons);
305: $args[] = 'onUploadHook=' . $this->onUpload;
306: $args[] = 'exts=' . implode(',', $this->extensions);
307: $args[] = 'maxSize=' . $maxSize;
308: $args[] = 'uploadAsOriginalFileName=' . (int) $this->uploadAsOriginalFileName;
309: if ($this->dimensions) {
310: $args[] = 'dimensions=' . implode(',', $this->dimensions);
311: }
312: $args[] = 'lc_namespace=' . LC_NAMESPACE;
313: $handlerURL = $this->uploadHandler.'?'.implode('&', $args);
314:
315: # If setValue(), the file information is pre-loaded
316: $id = '';
317: $value = '';
318: $currentFile = '';
319: $currentFileURL = '';
320: $extension = '';
321: $uniqueId = '';
322: $dimensions = array();
323: $webUploadDir = str_replace('\\', '/', str_replace(ROOT, WEB_ROOT, $this->uploadDir));
324:
325: if ($this->value && file_exists($this->uploadDir . $value)) {
326: $value = $this->getValue();
327: $id = $this->getValueId();
328: $currentFile = basename($this->uploadDir . $value);
329: $currentFileURL = $webUploadDir . $value;
330: $extension = pathinfo($this->uploadDir . $value, PATHINFO_EXTENSION);
331: if (is_array($this->dimensions) && count($this->dimensions)) {
332: $dimensions = $this->dimensions;
333: }
334: }
335:
336: # If the generic form POST, the file information from POST is pre-loaded
337: # by overwriting `$this->value`
338: if (count($_POST) && isset($_POST[$name]) && $_POST[$name] &&
339: isset($_POST[$name.'-fileName']) && $_POST[$name.'-fileName']) {
340: $post = _post();
341: $value = $post[$name];
342: $id = isset($post[$name.'-id']) ? $post[$name.'-id'] : '';
343:
344: if (file_exists($this->uploadDir . $value)) {
345: $currentFile = $value;
346: $currentFileURL = $webUploadDir . $value;
347: $extension = pathinfo($this->uploadDir . $value, PATHINFO_EXTENSION);
348: $uniqueId = $post[$name.'-uniqueId'];
349: }
350:
351: if (isset($post[$name.'-dimensions']) && is_array($post[$name.'-dimensions']) && count($post[$name.'-dimensions'])) {
352: $dimensions = $post[$name.'-dimensions'];
353: }
354: }
355:
356: $preview = $currentFile ? true : false;
357: ?>
358: <div class="asynfileuploader" id="asynfileuploader-<?php echo $name; ?>">
359: <div id="asynfileuploader-value-<?php echo $name; ?>">
360: <input type="hidden" name="<?php echo $name; ?>" value="<?php if ($value) echo $value; ?>" />
361: <input type="hidden" name="<?php echo $name; ?>-id" value="<?php if ($id) echo $id; ?>" />
362: <?php foreach ($dimensions as $d) { ?>
363: <input type="hidden" name="<?php echo $name; ?>-dimensions[]" value="<?php echo $d; ?>" />
364: <?php } ?>
365: </div>
366: <div id="asynfileuploader-hiddens-<?php echo $name; ?>">
367: <?php foreach ($this->hidden as $hiddenName => $hiddenValue) { ?>
368: <input type="hidden" name="<?php echo $name; ?>-<?php echo $hiddenName; ?>" value="<?php echo $hiddenValue; ?>" />
369: <?php } ?>
370: </div>
371: <input type="hidden" name="<?php echo $name; ?>-dir" value="<?php echo base64_encode($this->uploadDir); ?>" />
372: <input type="hidden" name="<?php echo $name; ?>-fileName" id="asynfileuploader-fileName-<?php echo $name; ?>" value="<?php echo $currentFile; ?>" />
373: <input type="hidden" name="<?php echo $name; ?>-uniqueId" id="asynfileuploader-uniqueId-<?php echo $name; ?>" value="<?php echo $uniqueId; ?>" />
374: <div id="asynfileuploader-progress-<?php echo $name; ?>" class="asynfileuploader-progress">
375: <div></div>
376: </div>
377: <div <?php echo $buttonAttrHTML; ?>>
378: <span><?php echo $this->caption; ?></span>
379: <iframe id="asynfileuploader-frame-<?php echo $name; ?>" src="<?php echo $handlerURL; ?>" frameborder="0" scrolling="no" style="overflow:hidden;"></iframe>
380: </div>
381: <div class="asynfileuploader-file-info">
382: <?php if ($this->fileNameIsDisplayed) { ?>
383: <span id="asynfileuploader-name-<?php echo $name; ?>">
384: <?php if ($currentFile) { ?>
385: <a href="<?php echo $currentFileURL; ?>" target="_blank" rel="nofollow"><?php echo $currentFile ?></a>
386: <?php } ?>
387: </span>
388: <?php } ?>
389: <span id="asynfileuploader-delete-<?php echo $name; ?>" class="asynfileuploader-delete" <?php if (!$currentFile) echo 'style="display:none"'; ?>>
390: <?php if ($this->isDeletable) { ?>
391: <a href="javascript:" rel="<?php echo $this->onDelete; ?>" title="Delete">
392: <span>Delete</span>
393: </a>
394: <?php } ?>
395: </span>
396: </div>
397: <span class="asynfileuploader-error" id="asynfileuploader-error-<?php echo $name; ?>"></span>
398: <script type="text/javascript">
399: LC.AsynFileUploader.init('<?php echo $name; ?>');
400: <?php
401: if ($preview) {
402: $json = array(
403: 'name' => $name,
404: 'value' => $value,
405: 'fileName' => $currentFile,
406: 'url' => $currentFileURL,
407: 'extension' => $extension,
408: 'caption' => $this->label
409: );
410: echo 'LC.AsynFileUploader.preview(' . json_encode($json) . ');';
411: }
412: ?>
413: </script>
414: </div>
415: <?php
416: }
417: /**
418: * Get the upload directory name from REQUEST
419: * @param string $name The file element name
420: * @return mixed
421: */
422: public static function getDirFromRequest($name)
423: {
424: return isset($_REQUEST[$name.'-dir']) ? _sanitize(base64_decode($_REQUEST[$name.'-dir'])) : '';
425: }
426: }
427: