PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / trunk
VikAppointments Services Booking Calendar vtrunk
trunk 1.2.17 1.2.18 1.2.19
vikappointments / libraries / update / manager.php
vikappointments / libraries / update Last commit date
changelog.php 4 years ago fixer.php 1 year ago license.php 1 month ago manager.php 4 months ago
manager.php
680 lines
1 <?php
2 /**
3 * @package VikAppointments - Libraries
4 * @subpackage update
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 JLoader::import('adapter.database.helper');
15 VikAppointmentsLoader::import('update.changelog');
16 VikAppointmentsLoader::import('update.license');
17
18 /**
19 * Class used to handle the upgrade of the plugin.
20 *
21 * @since 1.0
22 */
23 class VikAppointmentsUpdateManager
24 {
25 /**
26 * Checks if the current version should be updated.
27 *
28 * @param string $version The version to check.
29 *
30 * @return boolean True if should be updated, otherwise false.
31 */
32 public static function shouldUpdate($version)
33 {
34 if (is_null($version))
35 {
36 return false;
37 }
38
39 return version_compare($version, VIKAPPOINTMENTS_SOFTWARE_VERSION, '<');
40 }
41
42 /**
43 * Executes the SQL file for the installation of the plugin.
44 *
45 * @return void
46 *
47 * @uses execSqlFile()
48 * @uses installAcl()
49 * @uses installProSettings()
50 */
51 public static function install()
52 {
53 self::execSqlFile(VIKAPPOINTMENTS_BASE . DIRECTORY_SEPARATOR . 'sql' . DIRECTORY_SEPARATOR . 'install.mysql.utf8.sql');
54
55 $dbo = JFactory::getDbo();
56
57 // extract current lang tag by splitting regional code and country code
58 $tag = preg_split("/[_-]/", JFactory::getLanguage()->getTag());
59
60 // create customf field constaining rule (phone number) and country code
61 $field = new stdClass;
62 $field->rule = VAPCustomFields::PHONE_NUMBER;
63 $field->choose = end($tag);
64
65 // update all custom fields with PHONE NUMBER rule
66 $dbo->updateObject('#__vikappointments_custfields', $field, 'rule');
67
68 $config = VAPFactory::getConfig();
69
70 // create the configuration record with the email address of the current user
71 $config->set('adminemail', JFactory::getUser()->email);
72
73 // footer must be disabled by default
74 $config->set('showfooter', false);
75
76 // auto turn off settings not supported by LITE version
77 $config->set('enablecart', 0);
78 $config->set('shoplink', '');
79 $config->set('enablerecur', 0);
80 $config->set('enablereviews', 0);
81 $config->set('enablewaitlist', 0);
82 $config->set('enablepackages', 0);
83 $config->set('conversion_track', 0);
84 $config->set('multilanguage', 0);
85
86 // generate random key for cron and calendar sync
87 $synckey = VikAppointments::generateSerialCode(12);
88
89 $config->set('synckey', $synckey);
90 $config->set('cron_secure_key', $synckey);
91
92 // get JUri object
93 $uri = JUri::getInstance();
94 // get host from URI
95 $domain = $uri->toString(array('host'));
96 // split third-level domains
97 $domain = implode(' ', explode('.', $domain));
98 // make the word uppercase
99 $domain = ucwords($domain);
100 // update agency name with domain
101 $config->set('agencyname', $domain);
102
103 // search for the default Privacy Policy page
104 $post = get_page_by_path('privacy-policy');
105
106 if ($post)
107 {
108 // get Privacy Policy URL
109 $pp_link = get_permalink($post->ID);
110
111 if ($pp_link)
112 {
113 // update GDPR link with existing Privacy Policy
114 $config->set('policylink', $pp_link);
115 }
116 }
117
118 // truncate the payment gateways table
119 $dbo->setQuery("TRUNCATE TABLE `#__vikappointments_gpayments`");
120 $dbo->execute();
121
122 // install terms and conditions custom fields
123 JModelVAP::getInstance('customf')->save([
124 'name' => __('I read and accept the terms and conditions', 'vikappointments'),
125 'type' => 'checkbox',
126 'required' => 1,
127 'poplink' => $config->get('policylink'),
128 'id' => 0,
129 'group' => 0,
130 ]);
131
132 self::installAcl();
133 self::installProSettings();
134
135 // write CSS custom file
136 $path = JPath::clean(VAPBASE . '/assets/css/vap-custom.css');
137 JFile::write($path, "/* put below your custom css code for VikAppointments */\n\n");
138
139 // import folder helper
140 JLoader::import('adapter.filesystem.folder');
141
142 // create overrides folder
143 JFolder::create(VAP_UPLOAD_DIR_PATH . DIRECTORY_SEPARATOR . 'overrides');
144
145 // create languages folder
146 JFolder::create(VAP_UPLOAD_DIR_PATH . DIRECTORY_SEPARATOR . 'languages');
147
148 // create media folders
149 JFolder::create(VAPMEDIA);
150 JFolder::create(VAPMEDIA_SMALL);
151
152 // create customers folders
153 JFolder::create(VAPCUSTOMERS_UPLOADS);
154 JFolder::create(VAPCUSTOMERS_AVATAR);
155 JFolder::create(VAPCUSTOMERS_DOCUMENTS);
156
157 // create invoice templates folders
158 JFolder::create(VAPINVOICE);
159 JFolder::create(VAPINVOICE . DIRECTORY_SEPARATOR . 'employees');
160 JFolder::create(VAPINVOICE . DIRECTORY_SEPARATOR . 'packages');
161
162 // create mail folders
163 JFolder::create(VAP_UPLOAD_DIR_PATH . DIRECTORY_SEPARATOR . 'mail' . DIRECTORY_SEPARATOR . 'tmpl');
164 JFolder::create(VAP_UPLOAD_DIR_PATH . DIRECTORY_SEPARATOR . 'mail' . DIRECTORY_SEPARATOR . 'attachments');
165
166 // create CSS folders
167 JFolder::create(VAP_UPLOAD_DIR_PATH . DIRECTORY_SEPARATOR . 'css' . DIRECTORY_SEPARATOR . 'customizer');
168 JFolder::create(VAP_UPLOAD_DIR_PATH . DIRECTORY_SEPARATOR . 'css' . DIRECTORY_SEPARATOR . 'themes');
169
170 // create extendable classes folders
171 JFolder::create(VAP_UPLOAD_DIR_PATH . DIRECTORY_SEPARATOR . 'cronjobs');
172 JFolder::create(VAP_UPLOAD_DIR_PATH . DIRECTORY_SEPARATOR . 'smsapi');
173 JFolder::create(VAP_UPLOAD_DIR_PATH . DIRECTORY_SEPARATOR . 'export');
174 }
175
176 /**
177 * Executes the SQL file for the uninstallation of the plugin.
178 *
179 * @param boolean $drop True to drop the tables of VikAppointments from the database.
180 *
181 * @return void
182 *
183 * @uses execSqlFile()
184 * @uses uninstallAcl()
185 * @uses uninstallProSettings()
186 */
187 public static function uninstall($drop = true)
188 {
189 if ($drop)
190 {
191 self::execSqlFile(VIKAPPOINTMENTS_BASE . DIRECTORY_SEPARATOR . 'sql' . DIRECTORY_SEPARATOR . 'uninstall.mysql.utf8.sql');
192 }
193
194 self::uninstallAcl();
195 self::uninstallProSettings();
196
197 // clear cached documentation
198 VikAppointmentsScreen::clearCache();
199
200 // import folder helper
201 JLoader::import('adapter.filesystem.folder');
202
203 // delete "vikappointments" folder in uploads dir
204 // and all its children recursively
205 JFolder::delete(VAP_UPLOAD_DIR_PATH);
206 }
207
208 /**
209 * Launches the process to finalise the update.
210 *
211 * @param string $version The current version.
212 *
213 * @uses getFixer()
214 * @uses installSql()
215 * @uses installAcl()
216 */
217 public static function update($version)
218 {
219 $fixer = self::getFixer($version);
220
221 // trigger before installation routine
222
223 $res = $fixer->beforeInstallation();
224
225 if ($res === false)
226 {
227 return false;
228 }
229
230 // install SQL statements
231
232 $res = self::installSql($version);
233
234 if ($res === false)
235 {
236 return false;
237 }
238
239 // install ACL
240
241 $res = self::installAcl();
242
243 if ($res === false)
244 {
245 return false;
246 }
247
248 // restore backed up files
249
250 try
251 {
252 self::restoreBackup();
253 }
254 catch (Exception $e)
255 {
256 // raise error instead of aborting the update process
257 JFactory::getApplication()->enqueueMessage("Impossible to restore backup.\n" . $e->getMessage(), 'error');
258 }
259
260 // trigger after installation routine
261
262 $res = $fixer->afterInstallation();
263
264 return ($res === false ? false : true);
265 }
266
267 /**
268 * Backups the specified source within the given destination.
269 *
270 * @param string $src The file/folder to backup.
271 * In case of folder, only the first-level files
272 * will be moved within the destination path.
273 * @param string $dest The destination folder.
274 *
275 * @return boolean True on success, false otherwise.
276 *
277 * @throws RuntimeException
278 */
279 public static function doBackup($src, $dest)
280 {
281 // import folder helper
282 JLoader::import('adapter.filesystem.folder');
283
284 // clean paths
285 $src = JPath::clean($src);
286 $dest = JPath::clean($dest);
287
288 // make sure the destination folder exists
289 if (!JFolder::exists($dest))
290 {
291 // throws exception in case of debug enabled
292 if (VIKAPPOINTMENTS_DEBUG)
293 {
294 throw new RuntimeException(sprintf('Destination folder [%s] not found.', $dest), 404);
295 }
296
297 // missing destination
298 return false;
299 }
300
301 // check if the source is a single file
302 if (is_file($src))
303 {
304 $files = (array) $src;
305 }
306 // otherwise check if the source is a folder
307 else if (JFolder::exists($src))
308 {
309 // folder path, filter ('.' means all), no recursive, return full path, exclude elements
310 $files = JFolder::files($src, '.', false, true, array('.svn', 'CVS', '.DS_Store', '__MACOSX', 'index.html'));
311 }
312 else
313 {
314 // throws exception in case of debug enabled
315 if (VIKAPPOINTMENTS_DEBUG)
316 {
317 throw new RuntimeException(sprintf('Invalid source path [%s].', $src), 400);
318 }
319
320 // nothing to backup
321 return false;
322 }
323
324 // make sure we don't have an empty array
325 if (!count($files))
326 {
327 // nothing to backup
328 return false;
329 }
330
331 /**
332 * Define an array of files to ignore.
333 *
334 * @since 1.2.1
335 */
336 static $skip = [];
337
338 if (!$skip)
339 {
340 // we need to exclude all the core e-mail templates to prevent the
341 // system from restoring outdated code
342 $skip[] = JPath::clean(VAP_UPLOAD_DIR_PATH . '/mail/tmpl/admin_email_tmpl.php');
343 $skip[] = JPath::clean(VAP_UPLOAD_DIR_PATH . '/mail/tmpl/cancellation_email_tmpl.php');
344 $skip[] = JPath::clean(VAP_UPLOAD_DIR_PATH . '/mail/tmpl/customer_email_tmpl.php');
345 $skip[] = JPath::clean(VAP_UPLOAD_DIR_PATH . '/mail/tmpl/employee_email_tmpl.php');
346 $skip[] = JPath::clean(VAP_UPLOAD_DIR_PATH . '/mail/tmpl/packages_email_tmpl.php');
347 $skip[] = JPath::clean(VAP_UPLOAD_DIR_PATH . '/mail/tmpl/waitlist_email_tmpl.php');
348
349 // we need to exclude all themes to always force new changes
350 $skip[] = JPath::clean(VAP_UPLOAD_DIR_PATH . '/css/themes/dark.css');
351 $skip[] = JPath::clean(VAP_UPLOAD_DIR_PATH . '/css/themes/light.css');
352 }
353
354 $res = true;
355
356 foreach ($files as $file)
357 {
358 /**
359 * In case the file is contained within the skip list,
360 * do not restore the backup.
361 *
362 * @since 1.2.1
363 */
364 if (in_array($file, $skip))
365 {
366 continue;
367 }
368
369 // create full destination file
370 $fileDest = rtrim($dest, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . basename($file);
371
372 // proceed only in case the destination file doesn't exist yet
373 // or the 2 files have a different bytes size
374 if (!is_file($fileDest) || filesize($file) != filesize($fileDest))
375 {
376 // copy file
377 if (!JFile::copy($file, $fileDest))
378 {
379 // throws exception in case of debug enabled
380 if (VIKAPPOINTMENTS_DEBUG)
381 {
382 throw new RuntimeException(sprintf('Impossible to copy [%s] onto [%s].', $file, $fileDest), 500);
383 }
384
385 $res = false;
386 }
387 }
388 }
389
390 return $res;
391 }
392
393 /**
394 * Restores all the files that have been backed-up using doBackup() method.
395 *
396 * @return boolean True on success, false otherwise.
397 *
398 * @throws RuntimeException
399 */
400 public static function restoreBackup()
401 {
402 $lookup = array(
403 // custom CSS
404 array(
405 // target to restore
406 VAP_UPLOAD_DIR_PATH . '/css/vap-custom.css',
407 // destination folder
408 VAPBASE . '/assets/css',
409 ),
410 // customizer CSS
411 array(
412 // target to restore
413 VAP_UPLOAD_DIR_PATH . '/css/customizer',
414 // destination folder
415 VAPBASE . '/assets/css/customizer',
416 ),
417 // theme CSS
418 array(
419 // target to restore
420 VAP_UPLOAD_DIR_PATH . '/css/themes',
421 // destination folder
422 VAPBASE . '/assets/css/themes',
423 ),
424 // mail attachments
425 array(
426 // target to restore
427 VAP_UPLOAD_DIR_PATH . '/mail/attachments',
428 // destination folder
429 VAPBASE . '/helpers/mail_attach',
430 ),
431 // mail templates
432 array(
433 // target to restore
434 VAP_UPLOAD_DIR_PATH . '/mail/tmpl',
435 // destination folder
436 VAPBASE . '/helpers/mail_tmpls',
437 ),
438 // languages
439 array(
440 // target to restore
441 VAP_UPLOAD_DIR_PATH . '/languages',
442 // destination folder
443 VIKAPPOINTMENTS_BASE . '/languages',
444 ),
445 );
446
447 /**
448 * Removed back-up of the following files:
449 * - SMS API (@see JSmsDispatcher)
450 * - Cron Jobs (@see VAPCronDispatcher::addIncludePath())
451 * - Export drivers (@see VAPOrderExportFactory)
452 *
453 * @since 1.2.1
454 */
455
456 $res = true;
457 $errors = array();
458
459 // iterate list
460 foreach ($lookup as $chunk)
461 {
462 list($src, $dest) = $chunk;
463
464 try
465 {
466 // do backup by using reversed arguments
467 $res = static::doBackup($src, $dest) && $res;
468 }
469 catch (Exception $e)
470 {
471 $res = false;
472
473 // catch any raised exceptions
474 $errors[] = $e->getMessage();
475 }
476 }
477
478 // re-throw exception in case of debug enabled
479 if ($errors && VIKAPPOINTMENTS_DEBUG)
480 {
481 throw new RuntimeException(implode("\n", $errors), 500);
482 }
483
484 return $res;
485 }
486
487 /**
488 * Get the script class to run the installation methods.
489 *
490 * @param string $version The current version.
491 *
492 * @return VikAppointmentsUpdateFixer
493 */
494 protected static function getFixer($version)
495 {
496 VikAppointmentsLoader::import('update.fixer');
497
498 return new VikAppointmentsUpdateFixer($version);
499 }
500
501 /**
502 * Provides the installation of the ACL routines.
503 *
504 * @return boolean True on success, otherwise false.
505 */
506 protected static function installAcl()
507 {
508 JLoader::import('adapter.acl.access');
509 $actions = JAccess::getActions('vikappointments');
510
511 // No actions found!
512 // Probably, the main folder is not called "vikappointments".
513 if (!$actions)
514 {
515 return false;
516 }
517
518 $roles = array(
519 get_role('administrator'),
520 );
521
522 foreach ($roles as $role)
523 {
524 if ($role)
525 {
526 foreach ($actions as $action)
527 {
528 $cap = JAccess::adjustCapability($action->name, 'com_vikappointments');
529 $role->add_cap($cap, true);
530 }
531 }
532 }
533
534 return true;
535 }
536
537 /**
538 * Sets up the options for using the Pro version.
539 *
540 * @return void
541 */
542 protected static function installProSettings()
543 {
544 VikAppointmentsChangelog::install();
545 VikAppointmentsLicense::install();
546 }
547
548 /**
549 * Sets up the options for using the Pro version.
550 *
551 * @return void
552 */
553 protected static function uninstallProSettings()
554 {
555 VikAppointmentsChangelog::uninstall();
556 VikAppointmentsLicense::uninstall();
557 }
558
559 /**
560 * Provides the uninstallation of the ACL routines.
561 *
562 * @return boolean True on success, otherwise false.
563 */
564 protected static function uninstallAcl()
565 {
566 JLoader::import('adapter.acl.access');
567 $actions = JAccess::getActions('vikappointments');
568
569 // No actions found!
570 // Probably, something went wrong while installing the plugin.
571 if (!$actions)
572 {
573 return false;
574 }
575
576 $roles = array(
577 get_role('administrator'),
578 );
579
580 foreach ($roles as $role)
581 {
582 if ($role)
583 {
584 foreach ($actions as $action)
585 {
586 $cap = JAccess::adjustCapability($action->name, 'com_vikappointments');
587 $role->remove_cap($cap);
588 }
589 }
590 }
591
592 return true;
593 }
594
595 /**
596 * Run all the proper SQL files.
597 *
598 * @param string $version The current version.
599 *
600 * @return boolean True on success, otherwise false.
601 *
602 * @uses execSqlFile()
603 */
604 protected static function installSql($version)
605 {
606 $dbo = JFactory::getDbo();
607
608 $ok = true;
609
610 $sql_base = VIKAPPOINTMENTS_BASE . DIRECTORY_SEPARATOR . 'sql' . DIRECTORY_SEPARATOR . 'update' . DIRECTORY_SEPARATOR . 'mysql' . DIRECTORY_SEPARATOR;
611 $sqlFiles = glob($sql_base . '*.sql');
612
613 /**
614 * Make sure the SQL files are properly executed per ascending version rather than alphabetically.
615 *
616 * @since 1.3
617 */
618 usort($sqlFiles, function($a, $b) {
619 return version_compare(basename($a, '.sql'), basename($b, '.sql'));
620 });
621
622 try
623 {
624 foreach ($sqlFiles as $file)
625 {
626 $sql_v = basename($file, '.sql');
627
628 if (version_compare($sql_v, $version, '>'))
629 {
630 // in case the SQL version is newer, execute the queries listed in the file
631 self::execSqlFile($file, $dbo);
632 }
633 }
634 }
635 catch (Exception $e)
636 {
637 $ok = false;
638 }
639
640 return $ok;
641 }
642
643 /**
644 * Executes all the queries contained in the specified file.
645 *
646 * @param string $file The SQL file to launch.
647 * @param JDatabase $dbo The database driver handler.
648 *
649 * @return void
650 */
651 protected static function execSqlFile($file, $dbo = null)
652 {
653 if (!is_file($file))
654 {
655 return;
656 }
657
658 if ($dbo === null)
659 {
660 $dbo = JFactory::getDbo();
661 }
662
663 $handle = fopen($file, 'r');
664
665 $bytes = '';
666 while (!feof($handle))
667 {
668 $bytes .= fread($handle, 8192);
669 }
670
671 fclose($handle);
672
673 foreach (JDatabaseHelper::splitSql($bytes) as $q)
674 {
675 $dbo->setQuery($q);
676 $dbo->execute();
677 }
678 }
679 }
680