PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / 1.2.21
VikAppointments Services Booking Calendar v1.2.21
1.2.21 1.2.20 trunk 1.2.17 1.2.18 1.2.19
vikappointments / site / helpers / libraries / backup / import / director.php
vikappointments / site / helpers / libraries / backup / import Last commit date
rule 5 days ago director.php 5 days ago rule.php 5 days ago
director.php
373 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.backup.import.rule');
15
16 /**
17 * Wraps the instructions used to restore a backup.
18 *
19 * @since 1.7.1
20 */
21 class VAPBackupImportDirector
22 {
23 /**
24 * The path of the backup (folder).
25 *
26 * @var string
27 */
28 private $path;
29
30 /**
31 * The minimum requireed version to use while restoring a backup.
32 *
33 * @var string
34 */
35 private $version;
36
37 /**
38 * Class constructor.
39 *
40 * @param string $path The archive path.
41 */
42 public function __construct($path)
43 {
44 $this->path = rtrim($path, DIRECTORY_SEPARATOR);
45 }
46
47 /**
48 * Sets the version of the manifest.
49 *
50 * @param string $version
51 *
52 * @return self This object to support chaining.
53 */
54 public function setVersion($version)
55 {
56 $this->version = $version;
57
58 return $this;
59 }
60
61 /**
62 * Executes the restore process.
63 *
64 * @return void
65 *
66 * @throws Exception
67 */
68 public function process()
69 {
70 // obtain manifest object
71 $manifest = $this->parseManifest();
72
73 // make sure the backup version is compatible with the current one
74 if (!$this->validateVersion($manifest))
75 {
76 // the backup version is higher than the current one
77 throw new Exception('The backup version is not compatible with the current one.', 500);
78 }
79
80 $dispatcher = VAPFactory::getEventDispatcher();
81
82 /**
83 * Trigger event to allow third party plugins to extend the backup import.
84 * This hook triggers before processing the import of an existing backup.
85 *
86 * It is possible to throw an exception to prevent the import process.
87 *
88 * @param object $manifest The backup manifest.
89 * @param string $path The path of the backup archive (uncompressed).
90 *
91 * @return void
92 *
93 * @since 1.7.1
94 *
95 * @throws Exception
96 */
97 $dispatcher->trigger('onBeforeImportBackupVikAppointments', [$manifest, $this->path]);
98
99 // execute the uninstallation rules
100 $this->uninstall($manifest);
101
102 // execute the installation rules
103 $this->install($manifest);
104
105 /**
106 * Trigger event to allow third party plugins to extend the backup import.
107 * This hook triggers after processing the import of an existing backup.
108 *
109 * It is possible to throw an exception to prevent the import process.
110 *
111 * @param object $manifest The backup manifest.
112 * @param string $path The path of the backup archive (uncompressed).
113 *
114 * @return void
115 *
116 * @since 1.7.1
117 *
118 * @throws Exception
119 */
120 $dispatcher->trigger('onAfterImportBackupVikAppointments', [$manifest, $this->path]);
121 }
122
123 /**
124 * Helper method used to parse the manifest file contained
125 * within the backup archive.
126 *
127 * @return object
128 *
129 * @throws Exception
130 */
131 protected function parseManifest()
132 {
133 // create file
134 $file = $this->path . DIRECTORY_SEPARATOR . 'manifest.json';
135
136 // make sure the manifest exists
137 if (!JFile::exists($file))
138 {
139 // file not found, check whether the package was manually compressed
140 // on a desktop, where we should face a folder-in-folder behavior
141 $folders = JFolder::folders($this->path);
142
143 if ($folders)
144 {
145 // move the root within the first available folder
146 $this->path .= DIRECTORY_SEPARATOR . $folders[0];
147
148 // refresh manifest file path
149 $file = $this->path . DIRECTORY_SEPARATOR . 'manifest.json';
150 }
151 }
152
153 // make sure the manifest exists
154 if (!JFile::exists($file))
155 {
156 // manifest not found
157 throw new Exception('The backup does not include a manifest file', 404);
158 }
159
160 $manifest = '';
161
162 // open file pointer
163 $fp = fopen($file, 'r');
164
165 while (!feof($fp))
166 {
167 // read buffer
168 $manifest .= fread($fp, 8192);
169 }
170
171 // close file pointer
172 fclose($fp);
173
174 // decode the manifest
175 $manifest = json_decode($manifest);
176
177 if (!is_object($manifest))
178 {
179 throw new Exception('The backup manifest is not valid', 500);
180 }
181
182 return $manifest;
183 }
184
185 /**
186 * Checks whether the version of the backup is compatible with the current
187 * version of the software.
188 *
189 * @param object $manifest The backup manifest.
190 *
191 * @return boolean True if compatible, false otherwise.
192 */
193 protected function validateVersion($manifest)
194 {
195 if (empty($manifest->application) || $manifest->application != 'Vik Appointments')
196 {
197 // application is missing
198 return false;
199 }
200
201 $application = $manifest->application;
202
203 // get the identifier of the current platform
204 $platform = VersionListener::getPlatform();
205
206 // check whether the manifest specifies a custom version for the current platform
207 if (isset($manifest->platforms->{$platform}))
208 {
209 // append platform to program name
210 $application .= ' ' . $platform;
211
212 // override manifest with specific instructions for the current platform
213 $manifest = $manifest->platforms->{$platform};
214 }
215
216 if (empty($manifest->version))
217 {
218 // version not found
219 return false;
220 }
221
222 // check whether the version signature has been specified by the backup
223 if (isset($manifest->signature))
224 {
225 // validate version integrity
226 $signature = md5($application . ' ' . $manifest->version);
227
228 if ($signature !== $manifest->signature)
229 {
230 // the signature doesn't matche
231 return false;
232 }
233 }
234
235 // first of all, make sure the backup version is equals or higher than the
236 // minimum required version
237 if (version_compare($manifest->version, $this->version, '<'))
238 {
239 // the manifest version is lower than the minimum required version
240 return false;
241 }
242
243 // then check whether the current version is equals of higher than the backup version
244 return version_compare(VIKAPPOINTMENTS_SOFTWARE_VERSION, $manifest->version, '>=');
245 }
246
247 /**
248 * Executes the uninstallation queries.
249 *
250 * @param object $manifest The backup manifest.
251 *
252 * @return void
253 */
254 protected function uninstall($manifest)
255 {
256 // look for uninstall queries
257 if (!isset($manifest->uninstall))
258 {
259 // nothing to execute
260 return;
261 }
262
263 $dbo = JFactory::getDbo();
264
265 // iterate queries to clean any existing records
266 foreach ((array) $manifest->uninstall as $q)
267 {
268 // launch query
269 $dbo->setQuery($q);
270 $dbo->execute();
271 }
272 }
273
274 /**
275 * Executes the installation rules.
276 *
277 * @param object $manifest The backup manifest.
278 *
279 * @return void
280 */
281 protected function install($manifest)
282 {
283 // look for installers
284 if (!isset($manifest->installers))
285 {
286 // nothing to install...
287 return;
288 }
289
290 $dispatcher = VAPFactory::getEventDispatcher();
291
292 // iterate installers
293 foreach ((array) $manifest->installers as $install)
294 {
295 if (empty($install->role))
296 {
297 // install role not found, cannot proceed
298 throw new Exception('Missing import backup role', 500);
299 }
300
301 // extract import data from rule
302 $data = isset($install->data) ? $install->data : null;
303
304 /**
305 * Trigger event to allow third party plugins to implement at runtime new import backup rules.
306 *
307 * It is possible to throw an exception to abort the import process.
308 *
309 * @param string $role The identifier of the import rule.
310 * @param mixed $options The instructions of the backup import rule.
311 *
312 * @return boolean True in case the rule has been dispatched, false (or null) to let the
313 * system uses one of the pre-installed rules.
314 *
315 * @since 1.7.1
316 *
317 * @throws Exception
318 */
319 $executed = $dispatcher->is('onExecuteImportBackupRuleVikAppointments', [$install->role, $data]);
320
321 // check whether the rule has been already dispatched by a plugin
322 if (!$executed)
323 {
324 // dispatch one of the system rules
325 $this->loadRule($install->role)->execute($data);
326 }
327 }
328 }
329
330 /**
331 * Creates a new import rule.
332 *
333 * @param string $rule The identifier of the rule to create.
334 *
335 * @return VAPBackupImportRule
336 *
337 * @throws Exception
338 */
339 public function loadRule($rule)
340 {
341 // attempt to load the import rule
342 if (!VAPLoader::import('libraries.backup.import.rule.' . $rule))
343 {
344 // rule not found
345 throw new Exception(sprintf('Cannot load [%s] import rule', $rule), 404);
346 }
347
348 // build class name
349 $classname = preg_replace("/_/", ' ', $rule);
350 $classname = preg_replace("/\s+/", '', ucwords($classname));
351 $classname = 'VAPBackupImportRule' . $classname;
352
353 // make sure the rule class exists
354 if (!class_exists($classname))
355 {
356 // class not found
357 throw new Exception(sprintf('Cannot find [%s] import rule class', $classname), 404);
358 }
359
360 // create new rule
361 $rule = new $classname($this->path);
362
363 // make sure we have a valid instance
364 if (!$rule instanceof VAPBackupImportRule)
365 {
366 throw new Exception(sprintf('The import rule [%s] is not a valid instance', $classname), 500);
367 }
368
369 // create the rule
370 return $rule;
371 }
372 }
373