PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / trunk
VikAppointments Services Booking Calendar vtrunk
trunk 1.2.17 1.2.18 1.2.19
vikappointments / admin / models / media.php
vikappointments / admin / models Last commit date
apiban.php 4 years ago apilog.php 2 years ago apiplugin.php 4 years ago apiuser.php 2 years ago apiuseroptions.php 2 years ago backup.php 4 months ago caldays.php 1 month ago calendar.php 1 month ago city.php 4 years ago closure.php 1 month ago configapp.php 4 years ago configcldays.php 4 years ago configcron.php 4 years ago configemp.php 4 years ago configsmsapi.php 4 years ago configuration.php 4 months ago conversion.php 4 years ago country.php 2 years ago coupon.php 2 years ago couponemployee.php 4 years ago coupongroup.php 2 years ago couponservice.php 4 years ago cronjob.php 2 years ago cronjoblog.php 4 years ago customer.php 4 months ago customf.php 2 years ago customfservice.php 4 years ago customizer.php 4 years ago empgroup.php 2 years ago employee.php 2 years ago empsettings.php 4 years ago file.php 4 years ago findreservation.php 1 month ago group.php 2 years ago import.php 4 years ago index.html 4 years ago invoice.php 1 month ago langcustomf.php 4 years ago langempgroup.php 4 years ago langemployee.php 4 years ago langgroup.php 4 years ago langmedia.php 4 years ago langoption.php 2 years ago langoptiongroup.php 4 years ago langoptionvar.php 4 years ago langpackage.php 4 years ago langpackgroup.php 4 years ago langpayment.php 4 years ago langservice.php 4 years ago langstatuscode.php 4 years ago langsubscr.php 4 years ago langtax.php 2 years ago langtaxrule.php 4 years ago location.php 2 years ago mailtext.php 2 years ago makerecurrence.php 1 month ago media.php 2 years ago multiorder.php 1 month ago option.php 1 year ago optiongroup.php 2 years ago optionvar.php 1 year ago orderstatus.php 2 years ago package.php 2 years ago packageservice.php 4 years ago packgroup.php 2 years ago packorder.php 2 years ago packorderitem.php 1 month ago payment.php 2 years ago rate.php 2 years ago reportsemp.php 4 months ago reportsser.php 4 months ago reservation.php 1 month ago resoptassoc.php 2 years ago restriction.php 2 years ago review.php 3 years ago serempassoc.php 1 year ago seroptassoc.php 4 years ago serrateassoc.php 4 years ago serrestrassoc.php 4 years ago service.php 2 years ago state.php 2 years ago statswidget.php 2 years ago statuscode.php 2 years ago subscription.php 1 month ago subscrorder.php 1 month ago tag.php 4 years ago tax.php 2 years ago taxrule.php 2 years ago updateprogram.php 4 years ago usernote.php 2 years ago waitinglist.php 1 month ago webhook.php 1 year ago worktime.php 1 month ago
media.php
1083 lines
1 <?php
2 /**
3 * @package VikAppointments
4 * @subpackage core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2021 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 VAPLoader::import('libraries.mvc.model');
15
16 /**
17 * VikAppointments media model.
18 *
19 * @since 1.7
20 */
21 class VikAppointmentsModelMedia extends JModelVAP
22 {
23 /**
24 * An array containing all the loaded images.
25 *
26 * @var array
27 * @since 1.7.2
28 */
29 protected static $imagesCache = [];
30
31 /**
32 * Basic item loading implementation.
33 *
34 * @param mixed $pk An optional primary key value to load the row by, or an array of fields to match.
35 * If not set the instance property value is used.
36 *
37 * @return mixed The record object on success, null otherwise.
38 *
39 * @since 1.7.2
40 */
41 public function getItem($pk, $new = false)
42 {
43 $id = $pk;
44
45 if (isset(static::$imagesCache[$id]))
46 {
47 // return cached data
48 return static::$imagesCache[$id];
49 }
50
51 if (is_string($pk))
52 {
53 // use reverse lookup
54 $pk = ['image' => $pk];
55 }
56
57 // fetch image details
58 static::$imagesCache[$id] = parent::getItem($pk, $new);
59
60 return static::$imagesCache[$id];
61 }
62
63 /**
64 * Entirely rewrite save method because the media files
65 * do not use database tables.
66 *
67 * @param mixed $data Either an array or an object of data to save.
68 *
69 * @return mixed The ID of the record on success, false otherwise.
70 */
71 public function save($data)
72 {
73 $dispatcher = VAPFactory::getEventDispatcher();
74
75 try
76 {
77 /**
78 * Trigger event to allow the plugins to bind the object that
79 * is going to be saved.
80 *
81 * @param mixed &$data The array/object to bind.
82 * @param JModelVAP $model The model instance.
83 *
84 * @return boolean False to abort saving.
85 *
86 * @throws Exception It is possible to throw an exception to abort
87 * the saving process and return a readable message.
88 *
89 * @since 1.7.2
90 */
91 if ($dispatcher->false('onBeforeSaveMediaFile', array(&$data, $this)))
92 {
93 return false;
94 }
95 }
96 catch (Exception $e)
97 {
98 // register the error thrown by the plugin and abort
99 $this->setError($e);
100
101 return false;
102 }
103
104 // attempt to bind the source to the instance
105 if (!$this->bind($data))
106 {
107 return false;
108 }
109
110 // run any sanity checks on the instance and verify that it is ready for storage
111 if (!$this->check())
112 {
113 return false;
114 }
115
116 // attempt to store the media file
117 if (!$this->store())
118 {
119 return false;
120 }
121
122 /**
123 * Trigger event to allow the plugins to make something after
124 * saving a media file.
125 *
126 * @param array $args The saved record.
127 * @param JModelVAP $model The model instance.
128 *
129 * @return void
130 *
131 * @since 1.7.2
132 */
133 $dispatcher->trigger('onAfterSaveMediaFile', array($this->getData(), $this));
134
135 return $this->id;
136 }
137
138 /**
139 * Method to bind an associative array or object to the Table instance. This
140 * method only binds properties that are publicly accessible and optionally
141 * takes an array of properties to ignore when binding.
142 *
143 * @param array|object $src An associative array or object to bind.
144 *
145 * @return boolean True on success.
146 */
147 protected function bind($src)
148 {
149 $src = (array) $src;
150
151 // register media file name
152 if (!empty($src['id']))
153 {
154 $this->id = $src['id'];
155 }
156 else
157 {
158 $this->id = null;
159 }
160
161 $this->path = null;
162
163 // register uploaded file, if any
164 if (!empty($src['file']))
165 {
166 $this->file = $src['file'];
167
168 // look for a custom path, if any
169 if (!empty($src['path']))
170 {
171 $this->path = $src['path'];
172
173 // make sure the path is an existing dir
174 if (!is_dir($this->path))
175 {
176 // nope, maybe we have a base64 string
177 $this->path = base64_decode($this->path);
178
179 // try again with decoded string
180 $this->path = is_dir($this->path) ? $this->path : null;
181 }
182 }
183 }
184 else
185 {
186 $this->file = null;
187 }
188
189 // register new name
190 if (!empty($src['name']))
191 {
192 // use specified name
193 $this->name = $src['name'];
194
195 // in case the name doesn't contain the extension type, retrieve it if possible
196 if ($this->id && !preg_match("/\.(png|jpe?g|gif|bmp)$/", $this->name))
197 {
198 // get file properties
199 $prop = AppointmentsHelper::getFileProperties(VAPMEDIA_SMALL . DIRECTORY_SEPARATOR . $this->id);
200 // append extension
201 $this->name .= $prop['file_ext'];
202 }
203 }
204 else if ($this->file && !empty($this->file['name']))
205 {
206 // use name of uploaded file
207 $this->name = $this->file['name'];
208 }
209 else if ($this->id)
210 {
211 // use same file name
212 $this->name = $this->id;
213 }
214 else
215 {
216 // no specified name
217 $this->name = '';
218 }
219
220 // register action
221 $this->action = isset($src['action']) ? (int) $src['action'] : 0;
222
223 // register file properties
224 $this->properties = array(
225 'oriwres' => @$src['oriwres'],
226 'orihres' => @$src['orihres'],
227 'smallwres' => @$src['smallwres'],
228 'smallhres' => @$src['smallhres'],
229 'isresize' => @$src['isresize'],
230 );
231
232 // register media attributes
233 $this->media = [];
234
235 if (isset($src['alt']))
236 {
237 $this->media['alt'] = $src['alt'];
238 }
239
240 if (isset($src['title']))
241 {
242 $this->media['title'] = $src['title'];
243 }
244
245 if (isset($src['caption']))
246 {
247 $this->media['caption'] = $src['caption'];
248 }
249
250 return true;
251 }
252
253 /**
254 * Method to perform sanity checks to ensure they are safe to store.
255 *
256 * @return boolean True if the instance is sane and able to be stored.
257 */
258 public function check()
259 {
260 // make sure a file was specified when replacing
261 if ((int) $this->action > 0 && !$this->file)
262 {
263 // register error message
264 $this->setError(JText::sprintf('VAP_INVALID_REQ_FIELD', JText::translate('VAPCONFIGFILETYPEERROR')));
265
266 // file not selected
267 return false;
268 }
269
270 return true;
271 }
272
273 /**
274 * Method to upload/update a media in the server filesystem.
275 *
276 * @return boolean True on success.
277 */
278 public function store()
279 {
280 // save media properties first of all
281 VikAppointments::storeMediaProperties($this->properties);
282
283 // DO NOT INVOKE PARENT
284
285 // update existing
286 if ($this->id)
287 {
288 // overwrite only in case the name of the uploaded file
289 // is the same of the existing one
290 $overwrite = $this->id == $this->name;
291
292 // replace original image
293 if ($this->action == 1)
294 {
295 // upload original image
296 $resp = VikAppointments::uploadFile($this->file, VAPMEDIA . DIRECTORY_SEPARATOR, 'jpeg,jpg,png,gif,bmp', $overwrite);
297
298 if (!$resp->status)
299 {
300 // unable to upload the image, abort
301 if ($resp->errno == 1)
302 {
303 $this->setError(JText::sprintf('VAPCONFIGFILETYPEERRORWHO', $resp->mimeType));
304 }
305 else
306 {
307 $this->setError(JText::translate('VAPCONFIGUPLOADERROR'));
308 }
309
310 return false;
311 }
312
313 // rename media file to original one
314 rename(
315 VAPMEDIA . DIRECTORY_SEPARATOR . $resp->name,
316 VAPMEDIA . DIRECTORY_SEPARATOR . $this->id
317 );
318 }
319 // replace thumbnail image
320 else if ($this->action == 2)
321 {
322 // upload thumbnail image
323 $resp = VikAppointments::uploadFile($this->file, VAPMEDIA_SMALL . DIRECTORY_SEPARATOR, 'jpeg,jpg,png,gif,bmp', $overwrite);
324
325 if (!$resp->status)
326 {
327 // unable to upload the image, abort
328 if ($resp->errno == 1)
329 {
330 $this->setError(JText::sprintf('VAPCONFIGFILETYPEERRORWHO', $resp->mimeType));
331 }
332 else
333 {
334 $this->setError(JText::translate('VAPCONFIGUPLOADERROR'));
335 }
336
337 return false;
338 }
339
340 // rename media file to original one
341 rename(
342 VAPMEDIA_SMALL . DIRECTORY_SEPARATOR . $resp->name,
343 VAPMEDIA_SMALL . DIRECTORY_SEPARATOR . $this->id
344 );
345 }
346 // replace both original and thumbnail images
347 else if ($this->action == 3)
348 {
349 $resp = VikAppointments::uploadMedia($this->file, $settings = null, $overwrite);
350
351 if (!$resp->status)
352 {
353 // unable to upload the image, abort
354 if ($resp->errno == 1)
355 {
356 $this->setError(JText::sprintf('VAPCONFIGFILETYPEERRORWHO', $resp->mimeType));
357 }
358 else
359 {
360 $this->setError(JText::translate('VAPCONFIGUPLOADERROR'));
361 }
362
363 return false;
364 }
365
366 // rename original media file to previous name
367 rename(
368 VAPMEDIA . DIRECTORY_SEPARATOR . $resp->name,
369 VAPMEDIA . DIRECTORY_SEPARATOR . $this->id
370 );
371
372 // rename thumbnail media file to previous name
373 rename(
374 VAPMEDIA_SMALL . DIRECTORY_SEPARATOR . $resp->name,
375 VAPMEDIA_SMALL . DIRECTORY_SEPARATOR . $this->id
376 );
377 }
378
379 // finally, rename the file to the specified name if different
380 if ($this->id != $this->name)
381 {
382 // make sure the new destination is empty
383 if (is_file(VAPMEDIA . DIRECTORY_SEPARATOR . $this->name))
384 {
385 // specified path is already occupied by a different file, raise error
386 $this->setError(JText::sprintf('VAPMEDIARENERR', $this->name));
387
388 return false;
389 }
390
391 // rename media file and update all the records that were using it
392 if ($this->rename($this->id, $this->name))
393 {
394 // update primary key
395 $this->id = $this->name;
396 }
397 }
398 }
399 // upload new
400 else if ($this->file)
401 {
402 if ($this->path)
403 {
404 if (!$this->isPathAllowed($this->path))
405 {
406 // path not allowed for uploads
407 $this->setError(sprintf('Path [%s] does not support uploads', $this->path));
408 return false;
409 }
410
411 // get accepted files filter
412 $filter = $this->getFileAllowedRegex();
413
414 // upload the image into the given path
415 $resp = VikAppointments::uploadFile($this->file, $this->path, $filter);
416 }
417 else
418 {
419 // upload original image and create thumbnail (do not overwrite existing)
420 $resp = VikAppointments::uploadMedia($this->file);
421 }
422
423 if (!$resp->status)
424 {
425 // unable to upload the image, abort
426 if ($resp->errno == 1)
427 {
428 $this->setError(JText::sprintf('VAPCONFIGFILETYPEERRORWHO', $resp->mimeType));
429 }
430 else
431 {
432 $this->setError(JText::translate('VAPCONFIGUPLOADERROR'));
433 }
434
435 return false;
436 }
437
438 // inject name of uploaded file within the table as primary key
439 $this->id = $resp->name;
440 // register file path
441 $this->file = $resp->path;
442 }
443
444 // check if we should proceed with the update of the media attributes
445 if (isset($this->media))
446 {
447 // save attributes only in case there is at least a non-empty value or if
448 // we need to update an existing media (just to unset the previous data)
449 if (array_filter($this->media) || $this->getItem($this->id))
450 {
451 // inject media name for a reverse lookup
452 $this->media['image'] = $this->id;
453
454 // save media attributes
455 $this->getTable('media')->save($this->media);
456 }
457 }
458
459 return true;
460 }
461
462 /**
463 * Returns the table properties, useful to retrieve the information
464 * that have been registered while saving a record.
465 *
466 * @return array
467 */
468 public function getData()
469 {
470 // return all public class properties
471 return $this->getProperties();
472 }
473
474 /**
475 * Method to delete one or more records.
476 *
477 * @param mixed $ids Either the record ID or a list of records.
478 * @param mixed $path An optional path from which the file should be
479 * deleted. If not specified, the default media
480 * folders will be used.
481 *
482 * @return boolean True on success.
483 */
484 public function delete($ids = null, $path = null)
485 {
486 if (!$ids)
487 {
488 return false;
489 }
490
491 $ids = (array) $ids;
492
493 $dispatcher = VAPFactory::getEventDispatcher();
494
495 try
496 {
497 /**
498 * Trigger event to allow the plugins to make something before
499 * deleting one or more media files.
500 *
501 * @param mixed $ids Either the record ID or a list of records.
502 * @param mixed $path An optional path from which the file should be deleted.
503 * @param JModelVAP $model The model instance.
504 *
505 * @return boolean False to abort delete.
506 *
507 * @throws Exception It is possible to throw an exception to abort
508 * the delete process and return a readable message.
509 *
510 * @since 1.7
511 */
512 if ($dispatcher->false('onBeforeDeleteMediaFile', array($ids, $path, $this)))
513 {
514 return false;
515 }
516 }
517 catch (Exception $e)
518 {
519 // register the error thrown by the plugin and abort
520 $this->setError($e);
521
522 return false;
523 }
524
525 $res = false;
526
527 // check if the given path is a directory
528 if ($path && !is_dir($path))
529 {
530 // try to decode from base 64
531 $path = base64_decode($path);
532
533 if (!is_dir($path))
534 {
535 // invalid path
536 $path = null;
537 }
538 }
539
540 $items_to_delete = [];
541
542 foreach ($ids as $id)
543 {
544 // make sure we are deleting an image and it is not protected
545 if ($this->isFileAllowed($id))
546 {
547 // check if we should delete the specified file inclusive of path
548 if (is_file($id))
549 {
550 // make sure the path of the file is safe
551 if ($this->isPathAllowed($id))
552 {
553 $res = unlink($id) || $res;
554 }
555 }
556 // check if we should delete the file from the given path
557 else if ($path || is_file($id))
558 {
559 // make sure the file exists and the path in which we are working is safe
560 if ($this->isPathAllowed($path) && is_file($path . DIRECTORY_SEPARATOR . $id))
561 {
562 $res = unlink($path . DIRECTORY_SEPARATOR . $id) || $res;
563 }
564 }
565 else
566 {
567 // try to delete original file, if exists
568 if (is_file(VAPMEDIA . DIRECTORY_SEPARATOR . $id))
569 {
570 $res = unlink(VAPMEDIA . DIRECTORY_SEPARATOR . $id) || $res;
571 }
572
573 // try to delete thumbnail file, if exists
574 if (is_file(VAPMEDIA_SMALL . DIRECTORY_SEPARATOR . $id))
575 {
576 $res = unlink(VAPMEDIA_SMALL . DIRECTORY_SEPARATOR . $id) || $res;
577 }
578
579 // check whether the deleted media owns some attributes
580 if ($item = $this->getItem($id))
581 {
582 $items_to_delete[] = $item->id;
583 }
584 }
585 }
586 }
587
588 if ($res)
589 {
590 // trigger a separated event for each ID in the list
591 foreach ($ids as $id)
592 {
593 /**
594 * Trigger event to allow the plugins to make something after
595 * deleting one or more media files.
596 *
597 * @param string $id The path of the deleted media file.
598 * @param mixed $path An optional path from which the file has been deleted.
599 * @param JModelVAP $model The model instance.
600 *
601 * @return void
602 *
603 * @since 1.7.2
604 */
605 $dispatcher->trigger('onAfterDeleteMediaFile', array($id, $path, $this));
606 }
607
608 // try to delete the media record
609 $this->getTable()->delete($items_to_delete);
610
611 $dbo = JFactory::getDbo();
612
613 // load any assigned translation
614 $q = $dbo->getQuery(true)
615 ->select($dbo->qn('id'))
616 ->from($dbo->qn('#__vikappointments_lang_media'))
617 ->where($dbo->qn('image') . ' IN (' . implode(',', array_map([$dbo, 'q'], $ids)) . ')' );
618
619 $dbo->setQuery($q);
620
621 if ($lang_ids = $dbo->loadColumn())
622 {
623 // get translation model
624 $model = JModelVAP::getInstance('langmedia');
625 // delete assigned translations
626 $model->delete($lang_ids);
627 }
628 }
629
630 return $res;
631 }
632
633 /**
634 * Renames the specified media file with the new one.
635 *
636 * @param string $prev The previous file name.
637 * @param string $new The new file name.
638 *
639 * @return boolean True on success, false otherwise.
640 */
641 protected function rename($prev, $new)
642 {
643 $dispatcher = VAPFactory::getEventDispatcher();
644
645 try
646 {
647 /**
648 * Trigger event to allow the plugins to make something before
649 * renaming one or more media files.
650 *
651 * @param string &$new The new file name.
652 * @param string $prev The previous file name.
653 * @param JModelVAP $model The model instance.
654 *
655 * @return boolean False to abort rename.
656 *
657 * @throws Exception It is possible to throw an exception to abort
658 * the rename process and return a readable message.
659 *
660 * @since 1.7.2
661 */
662 if ($dispatcher->false('onBeforeRenameMediaFile', array(&$new, $prev, $this)))
663 {
664 return false;
665 }
666 }
667 catch (Exception $e)
668 {
669 // register the error thrown by the plugin and abort
670 $this->setError($e);
671
672 return false;
673 }
674
675 // rename original media file and its thumbnail
676 $renamed = rename(VAPMEDIA . DIRECTORY_SEPARATOR . $prev, VAPMEDIA . DIRECTORY_SEPARATOR . $new)
677 && rename(VAPMEDIA_SMALL . DIRECTORY_SEPARATOR . $prev, VAPMEDIA_SMALL . DIRECTORY_SEPARATOR . $new);
678
679 if (!$renamed)
680 {
681 // something went wrong while renaming the files
682 return false;
683 }
684
685 $dbo = JFactory::getDbo();
686
687 // check whether the renamed file owns a media record
688 $item = $this->getItem($prev);
689
690 if ($item)
691 {
692 // rename image stored within the media record too
693 $this->getTable()->save([
694 'id' => $item->id,
695 'image' => $new,
696 ]);
697
698 // update all the related translations too
699 $q = $dbo->getQuery(true)
700 ->update($dbo->qn('#__vikappointments_lang_media'))
701 ->set($dbo->qn('image') . ' = ' . $dbo->q($new))
702 ->where($dbo->qn('image') . ' = ' . $dbo->q($prev));
703
704 $dbo->setQuery($q);
705 $dbo->execute();
706 }
707
708 // relationship between database tables and columns
709 // containing media names
710 $lookup = array(
711 'service' => 'image',
712 'employee' => 'image',
713 'option' => 'image',
714 );
715
716 // mass update all the specified database tables
717 foreach ($lookup as $table => $column)
718 {
719 $q = $dbo->getQuery(true)
720 ->update($dbo->qn('#__vikappointments_' . $table))
721 ->set($dbo->qn($column) . ' = ' . $dbo->q($new))
722 ->where($dbo->qn($column) . ' = ' . $dbo->q($prev));
723
724 $dbo->setQuery($q);
725 $dbo->execute();
726 }
727
728 /**
729 * @todo fetch all database tables that might store encoded images
730 */
731
732 // try to rename the company logo stored within the configuration
733 $config = VAPFactory::getConfig();
734 $logo = $config->get('companylogo');
735
736 if ($logo == $prev)
737 {
738 $config->set('companylogo', $new);
739 }
740
741 /**
742 * Trigger event to allow the plugins to make something after
743 * renaming one or more media files.
744 *
745 * @param string $new The new file name.
746 * @param string $prev The previous file name.
747 * @param JModelVAP $model The model instance.
748 *
749 * @return void
750 *
751 * @since 1.7.2
752 */
753 $dispatcher->trigger('onAfterRenameMediaFile', array($new, $prev, $this));
754
755 return true;
756 }
757
758 /**
759 * Checks whether the specified file is allowed.
760 * This only checks the extension file type.
761 *
762 * @param string $file The file name/path.
763 *
764 * @return boolean True if allowed, false otherwise.
765 */
766 public function isFileAllowed($file)
767 {
768 /**
769 * Trigger event to allow the plugins to extend the validation of
770 * a specific file, in order to support the upload of file types
771 * that are not supported by default.
772 *
773 * @param string $file The file path/name to check.
774 * @param JModel $model The current media model instance.
775 *
776 * @return boolean True to always allow the file upload.
777 *
778 * @since 1.7
779 */
780 if (VAPFactory::getEventDispatcher()->true('onCheckFileAllowed', array($file, $this)))
781 {
782 // validated by a plugin
783 return true;
784 }
785
786 // validate file type
787 return preg_match($this->getFileAllowedRegex(), $file);
788 }
789
790 /**
791 * Checks whether the specified path is allowed.
792 * Ensures that we are handling a safe directory.
793 *
794 * @param string $file The file path.
795 *
796 * @return boolean True if allowed, false otherwise.
797 */
798 public function isPathAllowed($file)
799 {
800 /**
801 * Trigger event to allow the plugins to extend the validation of
802 * a specific path, in order to support the upload on folders that
803 * are not supported by default.
804 *
805 * @param string $file The file path to check.
806 * @param JModel $model The current media model instance.
807 *
808 * @return boolean True to always allow the file upload.
809 *
810 * @since 1.7
811 */
812 if (VAPFactory::getEventDispatcher()->true('onCheckPathAllowed', array($file, $this)))
813 {
814 // validated by a plugin
815 return true;
816 }
817
818 // validate path against the list of default folders
819 foreach ($this->allowedPaths as $path)
820 {
821 // check whether the given file contains the current path
822 if ($path && strpos($file, $path) !== false)
823 {
824 return true;
825 }
826 }
827
828 return false;
829 }
830
831 /**
832 * Returns the regex used to check whether a file type if accepted or not.
833 *
834 * @param mixed $path An optional group to take only the files that
835 * belong to the specified category (eg. image or video).
836 * Leave empty to take all the groups.
837 *
838 * @return string
839 */
840 public function getFileAllowedRegex($sub = null)
841 {
842 $sub = (array) $sub;
843
844 $regex = array();
845
846 foreach ($this->allowedFiles as $group => $types)
847 {
848 // check whether we should include this group
849 if (!$sub || in_array($group, $sub))
850 {
851 // merge types into regex list
852 $regex = array_merge($regex, $types);
853 }
854 }
855
856 // create regex to check whether the specified file is allowed
857 $regex = implode('|', $regex);
858 // escape reserved chars that may be used by mime-types
859 $regex = preg_replace("/[\/]/", '\\\\$0', $regex);
860
861 if ($regex)
862 {
863 // returned given regex
864 return "/(^|[.\/])($regex)$/i";
865 }
866
867 // accept everything
868 return "/./";
869 }
870
871 /**
872 * Returns an identifier of the specified media type.
873 * In example, in case of a .zip file, it will belong
874 * to the "archive" category.
875 *
876 * @param string $file The file path/name.
877 *
878 * @return mixed The media type on success, false otherwise.
879 */
880 public function detectMediaType($file)
881 {
882 // iterate all supported groups
883 foreach ($this->allowedFiles as $type => $list)
884 {
885 // check whether the file is supported by this group
886 if (preg_match($this->getFileAllowedRegex($type), $file))
887 {
888 // found, return matching type
889 return $type;
890 }
891 }
892
893 /**
894 * Trigger event to allow external plugins to register their own media types
895 * or to extend the existing ones.
896 *
897 * @param string $file The file path/name to check.
898 * @param JModel $model The current media model instance.
899 *
900 * @return string A custom detected type.
901 *
902 * @since 1.7
903 */
904 $type = VAPFactory::getEventDispatcher()->triggerOnce('onDetectMediaType', array($file, $this));
905
906 if ($type)
907 {
908 // use type fetched by a plugin
909 return $type;
910 }
911
912 // unable to detect media type
913 return false;
914 }
915
916 /**
917 * Renders the media file according to its type.
918 *
919 * @param mixed $file Either a file path or an array of file properties.
920 *
921 * @return string The resulting HTML.
922 */
923 public function renderMedia($file)
924 {
925 // check if we have a file path
926 if (is_string($file))
927 {
928 // get file properties
929 $file = AppointmentsHelper::getFileProperties($file);
930
931 if (!$file)
932 {
933 // file not found
934 return '';
935 }
936 }
937
938 // detect media type from file name
939 $mediaType = $this->detectMediaType($file['name']);
940
941 if (!$mediaType)
942 {
943 // media type not found, use "binary" by default
944 $mediaType = 'binary';
945 }
946
947 // attempt to render the field
948 $html = JLayoutHelper::render('mediamanager.types.' . $mediaType, $file);
949
950 if (!$html)
951 {
952 // not a standard media file, which might be supported by
953 // a third party plugin, so dispatch an event
954
955 /**
956 * Trigger event to allow external plugins to implement a layout for
957 * their own media types.
958 *
959 * @param array $file An array of file properties.
960 * @param JModel $model The current media model instance.
961 *
962 * @return string The resulting HTML.
963 *
964 * @since 1.7
965 */
966 $html = VAPFactory::getEventDispatcher()->triggerOnce('onRenderMedia', array($file, $this));
967 }
968
969 return $html;
970 }
971
972 /**
973 * Method to get the model name
974 *
975 * The model name. By default parsed using the classname or it can be set
976 * by passing a $config['name'] in the class constructor
977 *
978 * @return string The name of the model
979 *
980 * @since 1.7.2
981 */
982 public function getName()
983 {
984 /**
985 * Override this method in order to prevent Joomla from using
986 * the name property to fetch the model name, which might be
987 * already occupied by the image name.
988 */
989
990 return 'media';
991 }
992
993 /**
994 * Defines a list of allowed folders.
995 *
996 * @var array
997 */
998 public $allowedPaths = array(
999 VAPMEDIA,
1000 VAPMEDIA_SMALL,
1001 VAPCUSTOMERS_UPLOADS,
1002 VAPCUSTOMERS_AVATAR,
1003 VAPCUSTOMERS_DOCUMENTS,
1004 VAPINVOICE,
1005 VAPMAIL_ATTACHMENTS,
1006 );
1007
1008 /**
1009 * Defines a list of accepted file types.
1010 * The files extensions are regex compliant.
1011 *
1012 * @var array
1013 */
1014 public $allowedFiles = array(
1015 'image' => array(
1016 'a?png',
1017 'bmp',
1018 'gif',
1019 'ico',
1020 'jpe?g',
1021 'svg',
1022 ),
1023 'video' => array(
1024 'mp4',
1025 'mov',
1026 'ogm',
1027 'webm',
1028 '3gp',
1029 'asf',
1030 'avi',
1031 'divx',
1032 'flv',
1033 'mkv',
1034 'mpe?g',
1035 'wmv',
1036 'xvid',
1037 ),
1038 'audio' => array(
1039 'aac',
1040 'm4a',
1041 'mp3',
1042 'opus',
1043 '(x-)?wave?',
1044 'ac3',
1045 'aiff',
1046 'flac',
1047 'midi?',
1048 'wma',
1049 'ogg',
1050 ),
1051 'archive' => array(
1052 'zip',
1053 'tar',
1054 'rar',
1055 'gz',
1056 'bzip2',
1057 ),
1058 'document' => array(
1059 'pdf',
1060 'docx?',
1061 'rtf',
1062 'odt',
1063 'pages',
1064 ),
1065 'spreadsheet' => array(
1066 'xlsx?',
1067 'csv',
1068 'ods',
1069 'numbers',
1070 ),
1071 'presentation' => array(
1072 'ppsx?',
1073 'odp',
1074 'key',
1075 ),
1076 'text' => array(
1077 'txt',
1078 'md',
1079 'markdown',
1080 ),
1081 );
1082 }
1083