<?php /** * @package VikBooking - Libraries * @subpackage update * @author E4J s.r.l. * @copyright Copyright (C) 2018 E4J s.r.l. All Rights Reserved. * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL * @link https://vikwp.com */ // No direct access defined('ABSPATH') or die('No script kiddies please!'); JLoader::import('adapter.database.helper'); VikBookingLoader::import('update.changelog'); VikBookingLoader::import('update.license'); /** * Class used to handle the upgrade of the plugin. * * @since 1.0 */ class VikBookingUpdateManager { /** * For bc with PHP < 5.6, the private static var is now * returned by this private method, because class properties * do not support concatenation, and so we can't build the array. * * @return array the list of template files used as Options to temporary store modifications. * * @since 1.0.9 */ private static function getTemplateFiles() { return array( VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'email_tmpl.php', VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'error_form.php', VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'invoices' . DIRECTORY_SEPARATOR . 'invoice_tmpl.php', VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'invoices' . DIRECTORY_SEPARATOR . 'custom_invoice_tmpl.php', VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'checkins' . DIRECTORY_SEPARATOR . 'checkin_tmpl.php', VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'vikbooking_custom.css', VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'vikbooking_backendcustom.css' ); } /** * Checks if the current version should be updated. * * @param string $version The version to check. * * @return boolean True if should be updated, otherwise false. */ public static function shouldUpdate($version) { if (is_null($version)) { return false; } return version_compare($version, VIKBOOKING_SOFTWARE_VERSION, '<'); } /** * Executes the SQL file for the installation of the plugin. * * @return void * * @uses execSqlFile() * @uses installAcl() * @uses installProSettings() * @uses installUploadBackup() */ public static function install() { self::execSqlFile(VIKBOOKING_BASE . DIRECTORY_SEPARATOR . 'sql' . DIRECTORY_SEPARATOR . 'install.mysql.utf8.sql'); $dbo = JFactory::getDbo(); // create the configuration record with the email address of the current user $q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('adminemail', " . $dbo->q(JFactory::getUser()->email) . ");"; $dbo->setQuery($q); $dbo->execute(); // footer must be disabled by default $q = "UPDATE `#__vikbooking_config` SET `setting`='0' WHERE `param`='showfooter';"; $dbo->setQuery($q); $dbo->execute(); // closing main text must not mention the name of the software $q = "UPDATE `#__vikbooking_texts` SET `setting`='' WHERE `param`='closingmain';"; $dbo->setQuery($q); $dbo->execute(); // truncate the payment gateways table $dbo->setQuery("TRUNCATE TABLE `#__vikbooking_gpayments`"); $dbo->execute(); self::installAcl(); self::installProSettings(); self::installUploadBackup(); } /** * Executes the SQL file for the uninstallation of the plugin. * * @param boolean $drop True to drop the tables of VikBooking from the database. * * @return void * * @uses execSqlFile() * @uses uninstallAcl() * @uses uninstallProSettings() * @uses uninstallTemplateContents() * @uses uninstallUploadBackup() */ public static function uninstall($drop = true) { if ($drop) { self::execSqlFile(VIKBOOKING_BASE . DIRECTORY_SEPARATOR . 'sql' . DIRECTORY_SEPARATOR . 'uninstall.mysql.utf8.sql'); } self::uninstallAcl(); self::uninstallProSettings(); self::uninstallTemplateContents(); self::uninstallUploadBackup(); } /** * Launches the process to finalise the update. * * @param string $version The current version. * * @uses getFixer() * @uses installSql() * @uses installAcl() * @uses restoreTemplateFiles() * @uses restoreUploadBackup() */ public static function update($version) { $fixer = self::getFixer($version); // trigger before installation routine $res = $fixer->beforeInstallation(); if ($res === false) { return false; } // install SQL statements $res = self::installSql($version); if ($res === false) { return false; } // install ACL $res = self::installAcl(); if ($res === false) { return false; } // move template files self::restoreTemplateFiles(); // restore files uploaded self::restoreUploadBackup(); // trigger after installation routine $res = $fixer->afterInstallation(); return ($res === false ? false : true); } /** * Reads from the Options if the content of a template file * was previously modified. In this case, it restores the * modifications onto the template file which is always * overwritten during updates and upgrades. * * @return void * * @since 1.0.3 */ public static function restoreTemplateFiles() { jimport('joomla.filesystem.file'); foreach (self::getTemplateFiles() as $file) { // the file base name as used in the Options $base_file = basename($file); $base_file = substr($base_file, 0, strrpos($base_file, '.')); // get override value from Options $tmp_val = get_option('vikbooking_tmp_'.$base_file, null); if (!is_null($tmp_val)) { JFile::write($file, $tmp_val); } } } /** * Updates an Option with the content of the modified * template file for later use after updates/upgrades. * * @param string $path the template file full path or just its name * @param string $content the raw content to put in the Option * * @return boolean * * @since 1.0.3 */ public static function storeTemplateContent($path, $content) { $base_path = basename($path); foreach (self::getTemplateFiles() as $file) { // template file names are unique no matter of the directory $base_file = basename($file); if ($base_path == $base_file) { // template file found $base_file = substr($base_file, 0, strrpos($base_file, '.')); // update the Option and return update_option('vikbooking_tmp_'.$base_file, $content); return true; } } return false; } /** * Removes some Options that were used to hold * modifications made to the template files. * * @return void */ public static function uninstallTemplateContents() { foreach (self::getTemplateFiles() as $file) { // the file base name as used in the Options $base_file = basename($file); $base_file = substr($base_file, 0, strrpos($base_file, '.')); // remove entry from Options delete_option('vikbooking_tmp_'.$base_file); } } /** * Returns a pair of key-values for each directory used * for handling the backup of the uploaded files. * All paths are missing the trailing directory separator. * Gets the backup upload dir paths by default, or the * real plugin dir paths if $real_path is true. * Used during the installation, uninstallation and backup (post-update). * * @param boolean [$real_paths] If True, the real paths used by the plugin will be returned. * The path of the backup folders will be returned otherwise. * * @return array An array of strings with the path of each dir */ protected static function getUploadBackupDirs($real_paths = false) { if ($real_paths) { // return the real upload dir paths used by the plugin return array( // main upload path for the images of the rooms, options, characteristics and such... 'front' => VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'uploads', // upload dir path for the customers check-in documents 'checkins' => VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'checkins' . DIRECTORY_SEPARATOR . 'generated', // upload dir path for the invoices in PDF format 'invoices' => VBO_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'invoices' . DIRECTORY_SEPARATOR . 'generated', // upload dir path for the scans of the ID/Documents of the customers 'idscans' => VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'idscans', // dir path for the PMS reports data 'pmsdata' => VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'pmsdata', // dir path to extend the reports 'report' => VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'report', // dir path to extend the SMS APIs 'smsapi' => VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'smsapi', // dir path to custom language files 'languages' => VIKBOOKING_BASE . DIRECTORY_SEPARATOR . 'languages', // upload dir path for the admin section, for custom logos and such... (we keep the smaller admin path at the end of the array) 'admin' => VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'resources', ); } // get the array information of the upload dir $upload_dir = wp_upload_dir(); if (!is_array($upload_dir) || empty($upload_dir['basedir'])) { return false; } // keep the keys of this array, as they are used as a map with other methods return array( // base directory that will contain the sub-directories divided by category 'base' => $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'vikbooking', // main upload path for the images of the rooms, options, characteristics and such... 'front' => $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'vikbooking' . DIRECTORY_SEPARATOR . 'front', // upload dir path for the the customers check-in documents 'checkins' => $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'vikbooking' . DIRECTORY_SEPARATOR . 'checkins', // upload dir path for the invoices in PDF format 'invoices' => $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'vikbooking' . DIRECTORY_SEPARATOR . 'invoices', // upload dir path for the scans of the ID/Documents of the customers 'idscans' => $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'vikbooking' . DIRECTORY_SEPARATOR . 'idscans', // dir path for the PMS reports data 'pmsdata' => $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'vikbooking' . DIRECTORY_SEPARATOR . 'pmsdata', // dir path to extend the reports 'report' => $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'vikbooking' . DIRECTORY_SEPARATOR . 'report', // dir path to extend the SMS APIs 'smsapi' => $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'vikbooking' . DIRECTORY_SEPARATOR . 'smsapi', // dir path to custom language files 'languages' => $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'vikbooking' . DIRECTORY_SEPARATOR . 'languages', // upload dir path for the admin section, for custom logos and such... 'admin' => $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'vikbooking' . DIRECTORY_SEPARATOR . 'admin', // views overrides dir path: this dir should NOT be backed up, so this key should ONLY be in this array 'overrides' => $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'vikbooking' . DIRECTORY_SEPARATOR . 'overrides', // media dir path: this dir should NOT be backed up, so this key should ONLY be in this array 'media' => VBO_MEDIA_PATH, // customer docs dir path: this dir should NOT be backed up, so this key should ONLY be in this array 'customerdocs' => VBO_CUSTOMERS_PATH, ); } /** * Returns a list of keys for the upload back up directories * only containing the keys for those dirs where the extendable * PHP Classes are located by default in the plugin. * Used during the installation, uninstallation and backup (post-update). * * @return array An array of strings with the keys of the paths of each dir supported */ protected static function getExtendableDirsKeys() { // return the real upload dir paths used by the plugin return array( // dir path to extend the reports 'report', // dir path to extend the SMS APIs 'smsapi', ); } /** * Creates a directory called 'vikbooking' onto the defined upload * directory for the website, for later use during the upload of files. * All dirs are created by reading the instructions from getUploadBackupDirs(). * By default in /wp-content/uploads/vikbooking * * @return boolean True on success. * * @uses getUploadBackupDirs() * * @since 1.0.4 * @since 1.5.0 the visibility of this method has been changed to public for the Fixer class. */ public static function installUploadBackup() { // import the Folder and File classes JLoader::import('adapter.filesystem.folder'); JLoader::import('adapter.filesystem.file'); // create all necessary folders (if they don't exist) $result = true; foreach (self::getUploadBackupDirs() as $type => $path) { $result = $result && JFolder::create($path); if ($result) { // create an empty HTML file to secure the directory from browsing JFile::write($path . DIRECTORY_SEPARATOR . 'index.html', '<html></html>'); } } return $result; } /** * Removes the directory 'vikbooking' used for the file upload. * The dirs to be removed are read from the array in getUploadBackupDirs(). * This should happen only upon uninstallation of the Plugin. * * @return boolean True on success. * * @uses getUploadBackupDirs() * * @since 1.0.4 */ protected static function uninstallUploadBackup() { // import the Folder class JLoader::import('adapter.filesystem.folder'); // delete all folders no longer needed (if they exist) $result = true; foreach (self::getUploadBackupDirs() as $type => $path) { $result = JFolder::delete($path) && $result; } return $result; } /** * Restores all the uploaded files from the directory 'vikbooking' * and its sub-dirs, onto the original dirs of the Plugin. * * @return boolean True on success. * * @uses getUploadBackupDirs() * * @since 1.0.4 */ protected static function restoreUploadBackup() { // get the list of the backup directories $backup_dirs = self::getUploadBackupDirs(); // get the list of the real upload directories $real_dirs = self::getUploadBackupDirs(true); // import the Folder and File classes JLoader::import('adapter.filesystem.folder'); JLoader::import('adapter.filesystem.file'); $result = true; foreach ($backup_dirs as $type => $path) { // scan all files in this backup dir $files = JFolder::files($path); if (!isset($real_dirs[$type]) || !is_array($files) || !count($files)) { continue; } // restore all backed-up files onto its real dir foreach ($files as $file) { $fname = basename($file); // skip placeholder HTML file if ($fname == 'index.html') { continue; } // restore original file $result = JFile::copy($path . DIRECTORY_SEPARATOR . $file, $real_dirs[$type] . DIRECTORY_SEPARATOR . $fname) && $result; } } return $result; } /** * Trigger called whenever a file has been uploaded. * Checks if the destination path is recognized, and * copies the uploaded/created file onto its backup folder. * This is to have a mirror-file to be restored upon update. * * @param string $dest the path to the just uploaded file. * * @return boolean True on success. * * @uses getUploadBackupDirs() * @uses installUploadBackup() * * @since 1.0.4 */ public static function triggerUploadBackup($dest) { // seek the key of the uploaded dir $dir_key = false; foreach (self::getUploadBackupDirs(true) as $key => $path) { if (strpos($dest, $path) !== false) { $dir_key = $key; break; } } if (!$dir_key) { // could not recognize upload dir path from destination file return false; } // always make sure the upload backup dirs are set for bc with those that installed a previous version of the plugin if (!self::installUploadBackup()) { // cannot proceed because backup folders are not set return false; } // import the File class JLoader::import('adapter.filesystem.file'); // get the backup upload dir path for this type of file $backup_dirs = self::getUploadBackupDirs(); if (!isset($backup_dirs[$dir_key])) { // the arrays returned by getUploadBackupDirs do not match (should NEVER happen) return false; } // copy the file onto its backup dir return JFile::copy($dest, $backup_dirs[$dir_key] . DIRECTORY_SEPARATOR . basename($dest)); } /** * This method should be called after importing a full backup * copy from another Vik Booking installation. It recursively * parses all the upload backup directories to mirror. This * way, installing a future update will be safe. * * @return boolean true on success, false otherwise. * * @since 1.5.0 */ public static function triggerUploadFullMirroring() { // make sure all needed directories are available if (!self::installUploadBackup()) { // cannot proceed because backup folders are not set return false; } // get all the backup upload directory paths $backup_dirs = self::getUploadBackupDirs(); // copy result $result = false; // loop through all the real upload directories used by the plugin foreach (self::getUploadBackupDirs(true) as $key => $path) { if (!isset($backup_dirs[$key])) { continue; } // copy all files onto their backup directory foreach (JFolder::files($path) as $fname) { $result = JFile::copy($path . DIRECTORY_SEPARATOR . $fname, $backup_dirs[$key] . DIRECTORY_SEPARATOR . $fname) || $result; } } return $result; } /** * Trigger called to back up all the files inside the path with the key * passed. Useful to back up custom PHP files manually uploaded to extend * the default Reports, Cron Jobs, SMS APIs to save a clone copy of these * files onto a directory that will be later used for restoring the files. * This method should be called every time a back-end View with a list of * specific extendable classes is visited, to always update the back ups. * * @param string $key The key of the extendable class. * @param string $filter An optional regex to apply on the file name to skip. * * @return boolean True on success. * * @uses getUploadBackupDirs() * @uses installUploadBackup() * * @since 1.0.14 */ public static function triggerExtendableClassesBackup($key, $filter = '') { // get the list of the original directories $orig_dirs = self::getUploadBackupDirs(true); // get the list of the backup directories $backup_dirs = self::getUploadBackupDirs(); // make sure the key exists in the dirs arrays if (empty($key) || !isset($orig_dirs[$key]) || !isset($backup_dirs[$key])) { return false; } // always make sure the backup dirs are set for bc with those that installed a previous version of the plugin if (!self::installUploadBackup()) { // cannot proceed because backup folders are not set return false; } // import the Folder and File classes JLoader::import('adapter.filesystem.folder'); JLoader::import('adapter.filesystem.file'); // scan all files in the original dir $files = JFolder::files($orig_dirs[$key]); if (!is_array($files) || !count($files)) { return false; } $result = true; // back-up all files of this dir onto its real dir foreach ($files as $file) { $fname = basename($file); // skip placeholder HTML file if ($fname == 'index.html') { continue; } if (!empty($filter) && !preg_match($filter, $file)) { // we skip this file as the filter matched its name continue; } // clone original file $result = JFile::copy($orig_dirs[$key] . DIRECTORY_SEPARATOR . $file, $backup_dirs[$key] . DIRECTORY_SEPARATOR . $fname) && $result; } return $result; } /** * Get the script class to run the installation methods. * * @param string $version The current version. * * @return VikBookingUpdateFixer */ protected static function getFixer($version) { VikBookingLoader::import('update.fixer'); return new VikBookingUpdateFixer($version); } /** * Provides the installation of the ACL routines. * * @return boolean True on success, otherwise false. */ protected static function installAcl() { JLoader::import('adapter.acl.access'); $actions = JAccess::getActions('vikbooking'); // No actions found! // Probably, the main folder is not called "vikbooking". if (!$actions) { return false; } $roles = array( get_role('administrator'), ); foreach ($roles as $role) { if ($role) { foreach ($actions as $action) { $cap = JAccess::adjustCapability($action->name, 'com_vikbooking'); $role->add_cap($cap, true); } } } return true; } /** * Sets up the options for using the Pro version. * * @return void */ protected static function installProSettings() { VikBookingChangelog::install(); VikBookingLicense::install(); } /** * Sets up the options for using the Pro version. * * @return void */ protected static function uninstallProSettings() { VikBookingChangelog::uninstall(); VikBookingLicense::uninstall(); } /** * Provides the uninstallation of the ACL routines. * * @return boolean True on success, otherwise false. */ protected static function uninstallAcl() { JLoader::import('adapter.acl.access'); $actions = JAccess::getActions('vikbooking'); // No actions found! // Probably, something went wrong while installing the plugin. if (!$actions) { return false; } $roles = array( get_role('administrator'), ); foreach ($roles as $role) { if ($role) { foreach ($actions as $action) { $cap = JAccess::adjustCapability($action->name, 'com_vikbooking'); $role->remove_cap($cap); } } } return true; } /** * Run all the proper SQL files. * * @param string $version The current version. * * @return boolean True on success, otherwise false. * * @uses execSqlFile() */ protected static function installSql($version) { $dbo = JFactory::getDbo(); $ok = true; $sql_base = VIKBOOKING_BASE . DIRECTORY_SEPARATOR . 'sql' . DIRECTORY_SEPARATOR . 'update' . DIRECTORY_SEPARATOR . 'mysql' . DIRECTORY_SEPARATOR; try { foreach (glob($sql_base . '*.sql') as $file) { $name = basename($file); $sql_v = substr($name, 0, strrpos($name, '.')); if (version_compare($sql_v, $version, '>')) { // in case the SQL version is newer, execute the queries listed in the file self::execSqlFile($file, $dbo); } } } catch (Exception $e) { $ok = false; } return $ok; } /** * Executes all the queries contained in the specified file. * * @param string $file The SQL file to launch. * @param JDatabase $dbo The database driver handler. * * @return void */ protected static function execSqlFile($file, $dbo = null) { if (!is_file($file)) { return; } if ($dbo === null) { $dbo = JFactory::getDbo(); } $handle = fopen($file, 'r'); $bytes = ''; while (!feof($handle)) { $bytes .= fread($handle, 8192); } fclose($handle); foreach (JDatabaseHelper::splitSql($bytes) as $q) { $dbo->setQuery($q); $dbo->execute(); } } }