Completed
Push — master ( 5b91fe...d7aa39 )
by Nazar
07:05
created

Packages_manipulation::update_extract()   C

Complexity

Conditions 8
Paths 9

Size

Total Lines 44
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 8

Importance

Changes 0
Metric Value
cc 8
eloc 27
nc 9
nop 4
dl 0
loc 44
rs 5.3846
c 0
b 0
f 0
ccs 26
cts 26
cp 1
crap 8
1
<?php
2
/**
3
 * @package    CleverStyle Framework
4
 * @subpackage System module
5
 * @category   modules
6
 * @author     Nazar Mokrynskyi <[email protected]>
7
 * @copyright  Copyright (c) 2015-2017, Nazar Mokrynskyi
8
 * @license    MIT License, see license.txt
9
 */
10
namespace cs\modules\System;
11
use
12
	cs\Config,
13
	cs\Core,
14
	cs\DB;
15
16
/**
17
 * Utility functions, necessary during packages manipulation (installation/uninstallation, enabling/disabling)
18
 */
19
class Packages_manipulation {
20
	/**
21
	 * Generic extraction of files from phar distributive for CleverStyle Framework (components installation)
22
	 *
23
	 * @param string $target_directory
24
	 * @param string $source_phar Will be removed after extraction
25
	 *
26
	 * @return bool
27
	 */
28 3
	public static function install_extract ($target_directory, $source_phar) {
29
		/** @noinspection MkdirRaceConditionInspection */
30 3
		if (!mkdir($target_directory, 0770)) {
31 3
			return false;
32
		}
33 3
		$tmp_dir   = "phar://$source_phar";
34 3
		$fs        = file_get_json("$tmp_dir/fs.json");
35 3
		$extracted = static::generic_extract($fs, $tmp_dir, $target_directory);
36 3
		if ($extracted) {
37 3
			unlink($source_phar);
38 3
			file_put_json("$target_directory/fs.json", array_keys($fs));
39 3
			return true;
40
		}
41
		return false;
42
	}
43
	/**
44
	 * @param array  $fs
45
	 * @param string $tmp_dir
46
	 * @param string $target_directory
47
	 *
48
	 * @return bool
49
	 */
50 3
	protected static function generic_extract ($fs, $tmp_dir, $target_directory) {
51 3
		$extracted = array_filter(
52 3
			array_map(
53 3
				function ($index, $file) use ($tmp_dir, $target_directory) {
54
					if (
55 3
						!@mkdir(dirname("$target_directory/$file"), 0770, true) &&
56 3
						!is_dir(dirname("$target_directory/$file"))
57
					) {
58
						return false;
59
					}
60 3
					return copy("$tmp_dir/fs/$index", "$target_directory/$file");
61 3
				},
62 3
				$fs,
63 3
				array_keys($fs)
64
			)
65
		);
66 3
		return count($extracted) === count($fs);
67
	}
68
	/**
69
	 * Generic extraction of files from phar distributive for CleverStyle Framework (system and components update)
70
	 *
71
	 * @param string      $target_directory
72
	 * @param string      $source_phar             Will be removed after extraction
73
	 * @param null|string $fs_location_directory   Defaults to `$target_directory`
74
	 * @param null|string $meta_location_directory Defaults to `$target_directory`
75
	 *
76
	 * @return bool
77
	 */
78 3
	public static function update_extract ($target_directory, $source_phar, $fs_location_directory = null, $meta_location_directory = null) {
79 3
		$fs_location_directory   = $fs_location_directory ?: $target_directory;
80 3
		$meta_location_directory = $meta_location_directory ?: $target_directory;
81
		/**
82
		 * Backup some necessary information about current version
83
		 */
84 3
		copy("$fs_location_directory/fs.json", "$fs_location_directory/fs_backup.json");
85 3
		copy("$meta_location_directory/meta.json", "$meta_location_directory/meta_backup.json");
86
		/**
87
		 * Extracting new versions of files
88
		 */
89 3
		$tmp_dir   = "phar://$source_phar";
90 3
		$fs        = file_get_json("$tmp_dir/fs.json");
91 3
		$extracted = static::generic_extract($fs, $tmp_dir, $target_directory);
92 3
		if (!$extracted) {
93 3
			return false;
94
		}
95 3
		unlink($source_phar);
96 3
		$fs = array_keys($fs);
97
		/**
98
		 * Removing of old unnecessary files and directories
99
		 */
100
		foreach (
101 3
			array_diff(
102 3
				file_get_json("$fs_location_directory/fs_backup.json"),
103 3
				$fs
104
			) as $file
105
		) {
106 3
			$file = "$target_directory/$file";
107 3
			if (is_writable($file)) {
108 3
				unlink($file);
109
				// Recursively remove all empty parent directories
110 3
				while (!get_files_list($file = dirname($file), false, 'fd')) {
111 3
					rmdir($file);
112
				}
113
			}
114
		}
115 3
		unset($file, $dir);
116 3
		file_put_json("$fs_location_directory/fs.json", $fs);
117 3
		clearstatcache(true);
118 3
		if (function_exists('opcache_reset')) {
119 3
			opcache_reset();
120
		}
121 3
		return true;
122
	}
123
	/**
124
	 * Generic update for CleverStyle Framework (system and components), runs PHP scripts and does DB migrations after extracting of new distributive
125
	 *
126
	 * @param string     $target_directory
127
	 * @param string     $old_version
128
	 * @param array|null $db_array `$Config->components['modules'][$module]['db']` if module or system
129
	 *
130
	 * @throws \cs\ExitException
131
	 */
132 3
	public static function update_php_sql ($target_directory, $old_version, $db_array = null) {
133 3
		foreach (self::get_update_versions($target_directory) as $version) {
134 3
			if (version_compare($old_version, $version, '<')) {
135
				/**
136
				 * PHP update script
137
				 */
138 3
				_include_once("$target_directory/meta/update/$version.php", false);
139
				/**
140
				 * Database update
141
				 */
142 3
				if ($db_array) {
143 3
					self::execute_sql_from_directory("$target_directory/meta/update_db", $db_array, $version);
144
				}
145
			}
146
		}
147 3
	}
148
	/**
149
	 * @param string $target_directory
150
	 *
151
	 * @return string[]
152
	 */
153 3
	protected static function get_update_versions ($target_directory) {
154 3
		$update_versions = _mb_substr(get_files_list("$target_directory/meta/update"), 0, -4) ?: [];
155 3
		foreach (get_files_list("$target_directory/meta/update_db", false, 'd') ?: [] as $db) {
156
			/** @noinspection SlowArrayOperationsInLoopInspection */
157 3
			$update_versions = array_merge(
158 3
				$update_versions,
0 ignored issues
show
Bug introduced by
It seems like $update_versions can also be of type string; however, parameter $array1 of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

158
				/** @scrutinizer ignore-type */ $update_versions,
Loading history...
159 3
				get_files_list("$target_directory/meta/update_db/$db", false, 'd') ?: []
160
			);
161
		}
162 3
		$update_versions = array_unique($update_versions);
0 ignored issues
show
Bug introduced by
It seems like $update_versions can also be of type string; however, parameter $array of array_unique() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

162
		$update_versions = array_unique(/** @scrutinizer ignore-type */ $update_versions);
Loading history...
163 3
		usort($update_versions, 'version_compare');
164 3
		return $update_versions;
165
	}
166
	/**
167
	 * @param string $directory        Base path to SQL files
168
	 * @param array  $db_configuration Array in form [$db_name => $index]
169
	 * @param string $version          In case when we are working with update script we might have version subdirectory
170
	 *
171
	 * @throws \cs\ExitException
172
	 */
173 3
	public static function execute_sql_from_directory ($directory, $db_configuration, $version = '') {
174 3
		$Config = Config::instance();
175 3
		$Core   = Core::instance();
176 3
		$db     = DB::instance();
177 3
		time_limit_pause();
178 3
		foreach ($db_configuration as $db_name => $index) {
179 3
			$db_driver = $index == 0 ? $Core->db_driver : $Config->db[$index]['driver'];
180 3
			$sql_file  = "$directory/$db_name/$version/$db_driver.sql";
181 3
			if (file_exists($sql_file)) {
182
				/** @noinspection ExceptionsAnnotatingAndHandlingInspection */
183 3
				$db->db_prime($index)->transaction(
0 ignored issues
show
Bug introduced by
The method transaction() does not exist on cs\False_class. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

183
				$db->db_prime($index)->/** @scrutinizer ignore-call */ transaction(
Loading history...
184 3
					function ($cdb) use ($sql_file) {
185
						/**
186
						 * @var DB\_Abstract $cdb
187
						 */
188 3
						$cdb->q(
189 3
							explode(';', file_get_contents($sql_file))
190
						);
191 3
					}
192
				);
193
			}
194
		}
195 3
		time_limit_pause(false);
196 3
	}
197
}
198