Test Failed
Push — master ( a75db7...890a1b )
by Justin
46:33 queued 42:13
created

PluginInstaller::install()   C

Complexity

Conditions 8
Paths 7

Size

Total Lines 51
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 4 Features 0
Metric Value
c 4
b 4
f 0
dl 0
loc 51
rs 6.5978
cc 8
eloc 22
nc 7
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Copyright (c) 2018 Justin Kuenzel (jukusoft.com)
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
20
/**
21
 * Project: JuKuCMS
22
 * License: Apache 2.0 license
23
 * User: Justin
24
 * Date: 08.04.2018
25
 * Time: 14:45
26
 */
27
28
class PluginInstaller {
29
30
	//plugin to install / deinstall
31
	protected $plugin = null;
32
33
	public function __construct(Plugin $plugin) {
34
		$this->plugin = $plugin;
35
	}
36
37
	/**
38
	 * check php version, php extensions and so on
39
	 *
40
	 * @return mixed true, if all required plugins are available, or an array with missing
41
	 */
42
	public function checkRequirements (bool $dontCheckPlugins = false) {
43
		//get require
44
		$require_array = $this->plugin->getRequiredPlugins();
45
46
		//get package list
47
		require(STORE_PATH . "package_list.php");
48
49
		$missing_plugins = array();
50
51
		$installed_plugins = Plugins::listInstalledPlugins();
0 ignored issues
show
Unused Code introduced by
The assignment to $installed_plugins is dead and can be removed.
Loading history...
52
53
		//iterate through all requirements
54
		foreach ($require_array as $requirement=>$version) {
55
			if ($requirement === "php") {
56
				//check php version
57
				if (!$this->checkVersion($version, phpversion())) {
58
					$missing_plugins[] = $requirement;
59
60
					continue;
61
				}
62
			} else if (PHPUtils::startsWith($requirement, "ext-")) {
63
				//check php extension
64
65
				$extension = str_replace("ext-", "", $requirement);
66
67
				//check, if php extension is loaded
68
				if (!extension_loaded($extension)) {
69
					$missing_plugins[] = $requirement;
70
71
					continue;
72
				}
73
74
				//get extension version
75
				$current_version = phpversion($extension);
76
77
				//check version
78
				if (!$this->checkVersion($version, $current_version)) {
79
					$missing_plugins[] = $requirement;
80
				}
81
			} else if (PHPUtils::startsWith($requirement, "apache-")) {
82
				//check for apache module, but no version check is supported
83
84
				$module = str_replace("apache-", "", $requirement);
85
86
				if (!function_exists('apache_get_modules')) {
87
					$missing_plugins[] = "apache";
88
89
					continue;
90
				}
91
92
				if (!in_array($module, apache_get_modules())) {
93
					$missing_plugins[] = $requirement;
94
				}
95
			} else if (PHPUtils::startsWith($requirement, "package-")) {
96
				//check if package is installed
97
				$package = str_replace("package-", "", $requirement);
98
99
				//packages doesnt supports specific version
100
101
				if (!isset($package_list[$package])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $package_list does not exist. Did you maybe mean $package?
Loading history...
102
					$missing_plugins[] = $requirement;
103
				}
104
			} else if ($requirement === "core") {
105
				//check core version
106
				if ($version === "*") {
107
					//we dont have to check version
108
				} else {
109
					//get current version
110
					$array = explode(" ", Version::current()->getVersion());
111
					$current_core_version = $array[0];
112
113
					//check version
114
					if (!$this->checkVersion($version, $current_core_version)) {
115
						$missing_plugins[] = "core";
116
					}
117
				}
118
			} else {
119
				throw new Exception("plugin requirement check isnt supported yet.");
120
121
				if (!$dontCheckPlugins) {
0 ignored issues
show
Unused Code introduced by
IfNode is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
122
					continue;
123
				}
124
125
				//check, if plugin is installed
126
				if (!self::isPluginInstalled($requirement, $installed_plugins)) {
127
					$missing_plugins[] = $requirement;
128
					continue;
129
				}
130
131
				//check plugin version
132
				$current_version = self::getPluginVersion($requirement, $installed_plugins);
133
134
				//check version
135
				if (!$this->checkVersion($version, $current_version)) {
136
					$missing_plugins[] = $requirement . ":version";
137
				}
138
			}
139
		}
140
141
		if (empty($missing_plugins)) {
142
			return true;
143
		} else {
144
			return $missing_plugins;
145
		}
146
	}
147
148
	public static function isPluginInstalled (string $plugin_name, array $installed_plugins = array()) : bool {
149
		if (empty($installed_plugins)) {
150
			$installed_plugins = Plugins::listInstalledPlugins();
151
		}
152
153
		if (!isset($installed_plugins[$plugin_name])) {
154
			//plugin is not installed
155
			return false;
156
		} else {
157
			//plugin is installed
158
			return true;
159
		}
160
	}
161
162
	public static function getPluginVersion (string $plugin_name, array $installed_plugins = array()) {
163
		if (empty($installed_plugins)) {
164
			$installed_plugins = Plugins::listInstalledPlugins();
165
		}
166
167
		if (!isset($installed_plugins[$plugin_name])) {
168
			//plugin is not installed
169
			return false;
170
		} else {
171
			//plugin is installed, return installed version
172
			$plugin = Plugin::castPlugin($installed_plugins[$plugin_name]);
173
			return $plugin->getInstalledVersion();
174
		}
175
	}
176
177
	protected function checkVersion (string $expected_version, $current_version) : bool {
178
		//remove alpha and beta labels
179
		/*$expected_version = str_replace("-alpha", "", $expected_version);
180
		$expected_version = str_replace("-beta", "", $expected_version);
181
		$current_version = str_replace("-alpha", "", $current_version);
182
		$current_version = str_replace("-beta", "", $current_version);*/
183
184
		//validate version numbers, remove suffixes "-alpha", "-beta" and such like -1~dotdeb+8.1 (PHP version 7.0.29-1~dotdeb+8.1)
185
		$array1 = explode("-", $expected_version);
186
		$expected_version = $array1[0];
187
188
		$array2 = explode("-", $current_version);
189
		$current_version = $array2[0];
190
191
		//check version
192
		if (is_numeric($expected_version)) {
193
			//a specific version is required
194
			if ($current_version !== $expected_version) {
195
				return false;
196
			} else {
197
				return true;
198
			}
199
		} else if ($expected_version === "*") {
200
			//every version is allowed
201
			return true;
202
		} else {
203
			//parse version string
204
205
			$operator_length = 0;
206
207
			for ($i = 0; $i < strlen($expected_version); $i++) {
208
				if (!is_numeric($expected_version[$i])) {
209
					$operator_length++;
210
				} else {
211
					break;
212
				}
213
			}
214
215
			//get operator and version
216
			$operator = substr($expected_version, 0, $operator_length);
217
			$version = substr($expected_version, $operator_length);
218
219
			if (!empty($operator_length)) {
220
				return version_compare($current_version, $version, $operator) === TRUE;
221
			} else {
222
				return version_compare($current_version, $expected_version) === 0;
223
			}
224
		}
225
	}
226
227
	public function install () : bool {
228
		//first, check compatibility
229
		if (!$this->checkRequirements()) {
230
			return false;
231
		}
232
233
		//check, if plugin is already installed
234
		if ($this->plugin->isInstalled()) {
235
			return false;
236
		}
237
238
		//check, if install.json is used
239
		if ($this->plugin->hasInstallJson()) {
240
			//check, if install.json exists
241
			if (!file_exists($this->plugin->getPath() . "install.json")) {
242
				throw new IllegalStateException("plugin '" . $this->plugin->getName() . "' requires a install.json, but plugin directory doesnt contains a install.json file.");
243
			}
244
245
			//get content
246
			$install_json = json_decode(file_get_contents($this->plugin->getPath() . "install.json"), true);
247
248
			$installer_plugins = self::listInstallerPlugins();
249
250
			foreach ($installer_plugins as $i_plugin) {
251
				//cast plugin
252
				$i_plugin = PluginInstaller_Plugin::cast($i_plugin);
253
254
				//execute installer plugin
255
				$i_plugin->install($this->plugin, $install_json);
256
			}
257
258
			if (isset($install_json['install_script'])) {
259
				$script_filename = $install_json['install_script'];
260
				$script_path = $this->plugin->getPath() . $script_filename;
261
262
				//execute script, if exists
263
				if (file_exists($script_path)) {
264
					require($script_path);
265
				} else {
266
					throw new IllegalStateException("a install script '" . $script_filename . "' is set for plugin '" . $this->plugin->getName() . "', but file doesnt exists (path: " . $script_path . ").");
267
				}
268
			}
269
		}
270
271
		//set plugin as installed
272
		$this->setInstalled();
273
274
		//clear cache
275
		Cache::clear();
276
277
		return true;
278
	}
279
280
	public function uninstall () : bool {
281
		//check, if install.json is used
282
		if ($this->plugin->hasInstallJson()) {
283
			//check, if install.json exists
284
			if (!file_exists($this->plugin->getPath() . "install.json")) {
285
				throw new IllegalStateException("plugin '" . $this->plugin->getName() . "' requires a install.json, but plugin directory doesnt contains a install.json file.");
286
			}
287
288
			//get content
289
			$install_json = json_decode(file_get_contents($this->plugin->getPath() . "install.json"), true);
290
291
			$installer_plugins = self::listInstallerPlugins();
292
293
			foreach ($installer_plugins as $i_plugin) {
294
				//cast plugin
295
				$i_plugin = PluginInstaller_Plugin::cast($i_plugin);
296
297
				//execute installer plugin
298
				$i_plugin->uninstall($this->plugin, $install_json);
299
			}
300
301
			if (isset($install_json['uninstall_script'])) {
302
				$script_filename = $install_json['uninstall_script'];
303
				$script_path = $this->plugin->getPath() . $script_filename;
304
305
				//execute script, if exists
306
				if (file_exists($script_path)) {
307
					require($script_path);
308
				} else {
309
					throw new IllegalStateException("a uninstall script '" . $script_filename . "' is set for plugin '" . $this->plugin->getName() . "', but file doesnt exists (path: " . $script_path . ").");
310
				}
311
			}
312
		}
313
314
		//set plugin as uninstalled
315
		$this->setUnInstalled();
316
317
		//clear cache
318
		Cache::clear();
319
320
		return true;
321
	}
322
323
	public function upgrade () {
324
		throw new Exception("UnuspportedOperationException: method PluginInstaller::upgrade() isnt implemented yet.");
325
	}
326
327
	public function setInstalled () {
328
		Database::getInstance()->execute("INSERT INTO `{praefix}plugins` (
329
			`name`, `version`, `installed`, `activated`
330
		) VALUES (
331
			:name, :version, :installed, :activated
332
		) ON DUPLICATE KEY UPDATE `installed` = '1', `version` = :version; ", array(
333
			'name' => $this->plugin->getName(),
334
			'version' => $this->plugin->getVersion(),
335
			'installed' => 1,
336
			'activated' => 0
337
		));
338
339
		//clear cache
340
		Plugins::clearCache();
341
	}
342
343
	public function setUnInstalled () {
344
		Database::getInstance()->execute("DELETE FROM `{praefix}plugins` WHERE `name` = :name; ", array(
345
			'name' => $this->plugin->getName()
346
		));
347
348
		//clear cache
349
		Plugins::clearCache();
350
	}
351
352
	public static function listInstallerPlugins () : array {
353
		$rows = array();
354
355
		if (Cache::contains("plugins", "installer_plugins")) {
356
			$rows = Cache::get("plugins", "installer_plugins");
357
		} else {
358
			$rows = Database::getInstance()->listRows("SELECT * FROM `{praefix}plugin_installer_plugins`; ");
359
360
			//put into cache
361
			Cache::put("plugins", "installer_plugins", $rows);
362
		}
363
364
		$plugins = array();
365
366
		foreach ($rows as $row) {
367
			$class_name = $row['class_name'];
368
			$path = $row['path'];
369
370
			//first, include path
371
			require_once(ROOT_PATH . $path);
372
373
			//create object
374
			$obj = new $class_name();
375
376
			//check, if object extends PluginInstaller_Plugin
377
			if ($obj instanceof PluginInstaller_Plugin) {
378
				//add plugin to list
379
				$plugins[] = $obj;
380
			}
381
		}
382
383
		//sort list
384
		usort($plugins, function(PluginInstaller_Plugin $a, PluginInstaller_Plugin $b) {
385
			return $a->getPriority() <=> $b->getPriority();
386
		});
387
388
		return $plugins;
389
	}
390
391
	public static function addInstallerPluginIfAbsent (string $class_name, string $path) {
392
		Database::getInstance()->execute("INSERT INTO `{praefix}plugin_installer_plugins` (
393
			`class_name`, `path`
394
		) VALUES (
395
			:class_name, :path
396
		) ON DUPLICATE KEY UPDATE `path` = :path; ", array(
397
			'class_name' => $class_name,
398
			'path' => $path
399
		));
400
401
		//clear cache
402
		Cache::clear("plugins", "installer_plugins");
403
	}
404
405
	public static function removeInstallerPlugin (string $class_name) {
406
		Database::getInstance()->execute("DELETE FROM `{praefix}plugin_installer_plugins` WHERE `class_name` = :class_name; ", array(
407
			'class_name' => $class_name
408
		));
409
410
		//clear cache
411
		Cache::clear("plugins", "installer_plugins");
412
	}
413
414
}
415
416
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...
417