manager.php
310 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 | /** |
| 15 | * Backups director class. |
| 16 | * |
| 17 | * @since 1.7.1 |
| 18 | */ |
| 19 | class VAPBackupManager |
| 20 | { |
| 21 | /** |
| 22 | * Indicates the minimum required version while creating a |
| 23 | * new backup on Joomla. This value should be changed everytime |
| 24 | * something in the database structure is altered. |
| 25 | * |
| 26 | * @var string |
| 27 | */ |
| 28 | const MINIMUM_REQUIRED_VERSION_JOOMLA = '1.7.4'; |
| 29 | |
| 30 | /** |
| 31 | * Indicates the minimum required version while creating a |
| 32 | * new backup on WordPress. This value should be changed everytime |
| 33 | * something in the database structure is altered. |
| 34 | * |
| 35 | * @var string |
| 36 | */ |
| 37 | const MINIMUM_REQUIRED_VERSION_WORDPRESS = '1.2.11'; |
| 38 | |
| 39 | /** |
| 40 | * An associative array containing the supported export types, where the |
| 41 | * key is equals to the type ID and the value is the type instance. |
| 42 | * |
| 43 | * @var array |
| 44 | */ |
| 45 | protected static $exportTypes = null; |
| 46 | |
| 47 | /** |
| 48 | * Creates a new backup. |
| 49 | * |
| 50 | * @param string $type The type of backup to execute. |
| 51 | * @param array $options A configuration array. |
| 52 | * - folder string The path in which the archive should be saved. |
| 53 | * if not specified, the system temporary path will be used. |
| 54 | * - filename string An optional filename to use for the archive. If not specified |
| 55 | * the filename will be equals to the current time. |
| 56 | * - prefix string An optional prefix to prepend to the filename. |
| 57 | * |
| 58 | * @return string The path of the backup (a ZIP archive). |
| 59 | * |
| 60 | * @throws Exception |
| 61 | */ |
| 62 | public static function create($type, array $options = []) |
| 63 | { |
| 64 | // ignore the maximum execution time |
| 65 | set_time_limit(0); |
| 66 | |
| 67 | $dispatcher = VAPFactory::getEventDispatcher(); |
| 68 | |
| 69 | if (empty($options['folder'])) |
| 70 | { |
| 71 | // before starting the export, make sure the temporary folder is supported |
| 72 | $options['folder'] = JFactory::getApplication()->get('tmp_path'); |
| 73 | |
| 74 | if (!$options['folder'] || !JFolder::exists($options['folder'])) |
| 75 | { |
| 76 | throw new Exception('The temporary folder seems to be not set', 500); |
| 77 | } |
| 78 | |
| 79 | // remove trailing directory separator |
| 80 | $options['folder'] = preg_replace("/[\/\\\\]$/", '', $options['folder']); |
| 81 | } |
| 82 | |
| 83 | if (empty($options['filename'])) |
| 84 | { |
| 85 | // use the current date and time as file name |
| 86 | $options['filename'] = 'backup_' . $type . '_' . JFactory::getDate()->format('Y-m-d H-i-s'); |
| 87 | } |
| 88 | |
| 89 | if (!empty($options['prefix'])) |
| 90 | { |
| 91 | // include a prefix before the file name |
| 92 | $options['filename'] = $options['prefix'] . $options['filename']; |
| 93 | } |
| 94 | |
| 95 | // build archive path |
| 96 | $path = $options['folder'] . DIRECTORY_SEPARATOR . $options['filename']; |
| 97 | |
| 98 | // create backup export director |
| 99 | VAPLoader::import('libraries.backup.export.director'); |
| 100 | $director = new VAPBackupExportDirector($path); |
| 101 | |
| 102 | // set the manifest version equals to the minimum required one |
| 103 | $director->setVersion(static::MINIMUM_REQUIRED_VERSION_JOOMLA, 'joomla'); |
| 104 | $director->setVersion(static::MINIMUM_REQUIRED_VERSION_WORDPRESS, 'wordpress'); |
| 105 | |
| 106 | // fetch all the supported export types |
| 107 | $exportTypes = static::getExportTypes(); |
| 108 | |
| 109 | // check whether the requested support type exists |
| 110 | if (!isset($exportTypes[$type])) |
| 111 | { |
| 112 | // type not found |
| 113 | throw new Exception(sprintf('Cannot import [%s] export type', $type), 404); |
| 114 | } |
| 115 | |
| 116 | // get export type instance |
| 117 | $handler = $exportTypes[$type]; |
| 118 | |
| 119 | $error = null; |
| 120 | |
| 121 | try |
| 122 | { |
| 123 | // build the installers manifest |
| 124 | $handler->build($director); |
| 125 | |
| 126 | /** |
| 127 | * Trigger event to allow third party plugins to extend the backup feature. |
| 128 | * This hook is useful to include third-party tables and files into the |
| 129 | * backup archive. |
| 130 | * |
| 131 | * It is possible to attach a database table into the backup by using: |
| 132 | * $director->attachRule('sqlfile', '#__extensions'); |
| 133 | * |
| 134 | * @param string $type The type of backup to execute. |
| 135 | * @param VAPBackupExportDirector $director The instance used to manage the backup. |
| 136 | * @param array $options An array of options. |
| 137 | * |
| 138 | * @return void |
| 139 | * |
| 140 | * @since 1.7.1 |
| 141 | */ |
| 142 | $dispatcher->trigger('onBuildBackupVikAppointments', [$type, $director, $options]); |
| 143 | |
| 144 | // compress the archive and obtain the full path |
| 145 | $archivePath = $director->compress(); |
| 146 | } |
| 147 | catch (Exception $e) |
| 148 | { |
| 149 | // catch any error |
| 150 | $error = $e; |
| 151 | } |
| 152 | |
| 153 | // always delete archive folder |
| 154 | JFolder::delete($path); |
| 155 | |
| 156 | if ($error) |
| 157 | { |
| 158 | // in case of error, propagate it only after cleaning the dump |
| 159 | throw $error; |
| 160 | } |
| 161 | |
| 162 | return $archivePath; |
| 163 | } |
| 164 | |
| 165 | /** |
| 166 | * Restores an existing backup. |
| 167 | * |
| 168 | * @param string $path The path of the backup to restore. |
| 169 | * |
| 170 | * @return void |
| 171 | * |
| 172 | * @throws Exception |
| 173 | */ |
| 174 | public static function restore($path) |
| 175 | { |
| 176 | // ignore the maximum execution time |
| 177 | set_time_limit(0); |
| 178 | |
| 179 | // make sure the archive exists |
| 180 | if (!JFile::exists($path)) |
| 181 | { |
| 182 | // unable to find the specified archive |
| 183 | throw new Exception(sprintf('Backup [%s] not found', $path), 404); |
| 184 | } |
| 185 | |
| 186 | // create a unique extraction folder |
| 187 | $extractdir = dirname($path) . DIRECTORY_SEPARATOR . uniqid(); |
| 188 | |
| 189 | // extract the given backup |
| 190 | VAPLoader::import('libraries.archive.factory'); |
| 191 | $status = VAPArchiveFactory::extract($path, $extractdir); |
| 192 | |
| 193 | if (!$status) |
| 194 | { |
| 195 | // cannot extract the archive |
| 196 | throw new Exception(sprintf('Unable to extract [%s] into [%s]', $path, $extractdir), 500); |
| 197 | } |
| 198 | |
| 199 | // create backup import director |
| 200 | VAPLoader::import('libraries.backup.import.director'); |
| 201 | $director = new VAPBackupImportDirector($extractdir); |
| 202 | |
| 203 | // set the manifest version equals to the minimum required one, according to the CMS in use |
| 204 | if (VersionListener::isJoomla()) |
| 205 | { |
| 206 | $director->setVersion(static::MINIMUM_REQUIRED_VERSION_JOOMLA); |
| 207 | } |
| 208 | else |
| 209 | { |
| 210 | $director->setVersion(static::MINIMUM_REQUIRED_VERSION_WORDPRESS); |
| 211 | } |
| 212 | |
| 213 | $error = null; |
| 214 | |
| 215 | try |
| 216 | { |
| 217 | // process the backup |
| 218 | $director->process(); |
| 219 | } |
| 220 | catch (Exception $e) |
| 221 | { |
| 222 | $error = $e; |
| 223 | } |
| 224 | |
| 225 | // always delete extracted folder |
| 226 | JFolder::delete($extractdir); |
| 227 | |
| 228 | if ($error) |
| 229 | { |
| 230 | // in case of error, propagate it only after cleaning the dump |
| 231 | throw $error; |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | /** |
| 236 | * Returns a list of supported export types. |
| 237 | * |
| 238 | * @return array |
| 239 | */ |
| 240 | public static function getExportTypes() |
| 241 | { |
| 242 | if (!is_null(static::$exportTypes)) |
| 243 | { |
| 244 | // export types already fetched |
| 245 | return static::$exportTypes; |
| 246 | } |
| 247 | |
| 248 | // register default include paths |
| 249 | $includePaths = [ |
| 250 | dirname(__FILE__) . DIRECTORY_SEPARATOR . 'export' . DIRECTORY_SEPARATOR . 'type', |
| 251 | ]; |
| 252 | |
| 253 | /** |
| 254 | * Trigger event to allow third party plugins to register additional include paths, |
| 255 | * from which the system can load other backup handlers. |
| 256 | * |
| 257 | * @return mixed An array of include paths or a string. |
| 258 | * |
| 259 | * @since 1.7.1 |
| 260 | */ |
| 261 | $paths = VAPFactory::getEventDispatcher()->trigger('onLoadBackupExportTypesVikAppointments'); |
| 262 | |
| 263 | // merge returned paths with the existing ones |
| 264 | foreach ($paths as $path) |
| 265 | { |
| 266 | if (is_string($path)) |
| 267 | { |
| 268 | $includePaths[] = $path; |
| 269 | } |
| 270 | else if (is_array($path)) |
| 271 | { |
| 272 | $includePaths = array_merge($includePaths, $path); |
| 273 | } |
| 274 | } |
| 275 | |
| 276 | static::$exportTypes = []; |
| 277 | |
| 278 | // iterate include paths to fetch all the supported export types |
| 279 | foreach ($includePaths as $path) |
| 280 | { |
| 281 | // get all PHP files inside the folder |
| 282 | $files = JFolder::files($path, '\.php$', $recurse = false, $fullpath = true); |
| 283 | |
| 284 | // iterate all PHP files |
| 285 | foreach ($files as $file) |
| 286 | { |
| 287 | // get file name without extension |
| 288 | $type = basename($file, '.php'); |
| 289 | |
| 290 | // load the file |
| 291 | require_once $file; |
| 292 | |
| 293 | // build class name |
| 294 | $classname = 'VAPBackupExportType' . ucfirst($type); |
| 295 | |
| 296 | // check whether the class exists |
| 297 | if (!class_exists($classname)) |
| 298 | { |
| 299 | throw new Exception(sprintf('Cannot find [%s] export type class', $classname), 404); |
| 300 | } |
| 301 | |
| 302 | // instantiate and register export type handler |
| 303 | static::$exportTypes[$type] = new $classname(); |
| 304 | } |
| 305 | } |
| 306 | |
| 307 | return static::$exportTypes; |
| 308 | } |
| 309 | } |
| 310 |