Issues (1358)

modules/Composer/Composer.php (2 issues)

1
<?php
2
/**
3
 * @package  Composer
4
 * @category modules
5
 * @author   Nazar Mokrynskyi <[email protected]>
6
 * @license  0BSD
7
 */
8
namespace cs\modules\Composer;
9
use
10
	cs\Config,
11
	cs\Event,
12
	cs\Singleton,
13
	Symfony\Component\Console\Input\ArrayInput;
0 ignored issues
show
The type Symfony\Component\Console\Input\ArrayInput was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
14
15
/**
16
 * Provides next events:
17
 *  Composer/generate_package
18
 *  [
19
 *   'package' => &$package, //Composer package generated, might be modified
20
 *   'meta'    => $meta      //Parsed `meta.json` for component, package is generated for
21
 *  ]
22
 *
23
 *  Composer/generate_composer_json
24
 *  [
25
 *   'composer_json' => &$composer_json //`composer.json` structure that will be used for dependencies installation, might be modified
26
 *   'auth_json'     => &$auth_json     //`auth.json` structure that will be used for auth credentials during dependencies installation, might be modified
27
 *  ]
28
 *
29
 *  Composer/Composer
30
 *  [
31
 *   'Composer' => $Composer //Instance of `\Composer\Composer`, so that it is possible, for instance, to inject some Composer plugins manually
32
 *  ]
33
 *
34
 *  Composer/updated
35
 *  [
36
 *   'composer_json' => $composer_json, //`composer.json` structure that was used for dependencies installation
37
 *   'composer_lock' => $composer_lock  //`composer.lock` structure that was generated during dependencies installation
38
 *   'composer_root' => $composer_root  //Path to directory where dependencies were installed, and where `composer.json` and `composer.lock` are located
39
 *  ]
40
 *
41
 * @method static $this instance($check = false)
42
 */
43
class Composer {
44
	use
45
		Singleton;
46
	const MODE_ADD    = 1;
47
	const MODE_DELETE = 2;
48
	/**
49
	 * Force update even if nothing changed
50
	 *
51
	 * @var bool
52
	 */
53
	protected $force_update = false;
54
	protected function construct () {
55
		require_once 'phar://'.__DIR__.'/composer.phar/src/bootstrap.php';
56
	}
57
	/**
58
	 * Update composer even if nothing changed
59
	 *
60
	 * @return array
61
	 */
62
	public function force_update () {
63
		$this->force_update = true;
64
		return $this->update();
65
	}
66
	/**
67
	 * Update composer
68
	 *
69
	 * @param null|string $component_name Is specified if called before component actually installed (to satisfy dependencies)
70
	 * @param int         $mode           Composer::MODE_ADD or Composer::MODE_DELETE
71
	 *
72
	 * @return array Array with `code` and `description` elements, first represents status code returned by composer, second contains ANSI text returned by
73
	 *               composer
74
	 */
75
	public function update ($component_name = null, $mode = self::MODE_ADD) {
76
		time_limit_pause();
77
		$storage     = STORAGE.'/Composer';
78
		$status_code = 0;
79
		$description = '';
80
		$this->prepare($storage);
81
		$composer_json = $this->generate_composer_json($component_name, $mode);
82
		$Config        = Config::instance();
83
		$auth_json     = _json_decode($Config->module('Composer')->auth_json ?: '[]');
0 ignored issues
show
Bug Best Practice introduced by
The property auth_json does not exist on cs\Config\Module_Properties. Since you implemented __get, consider adding a @property annotation.
Loading history...
84
		$Event         = Event::instance();
85
		$Event->fire(
86
			'Composer/generate_composer_json',
87
			[
88
				'composer_json' => &$composer_json,
89
				'auth_json'     => &$auth_json
90
			]
91
		);
92
		if ($composer_json['repositories']) {
93
			$this->file_put_json("$storage/tmp/composer.json", $composer_json);
94
			$this->file_put_json("$storage/tmp/auth.json", $auth_json);
95
			if (
96
				$this->force_update ||
97
				!file_exists("$storage/composer.json") ||
98
				md5_file("$storage/tmp/composer.json") != md5_file("$storage/composer.json")
99
			) {
100
				$this->force_update = false;
101
				$Application        = new Application(
102
					function ($Composer) use ($Event, &$Application) {
103
						$Event->fire(
104
							'Composer/Composer',
105
							[
106
								'Application' => $Application,
107
								'Composer'    => $Composer
108
							]
109
						);
110
					}
111
				);
112
				$verbosity          = !DEBUG && $Config->core['simple_admin_mode'] ? '-vv' : '-vvv';
113
				$input              = new ArrayInput(
114
					[
115
						'command'               => 'update',
116
						'--working-dir'         => "$storage/tmp",
117
						'--no-dev'              => true,
118
						'--ansi'                => true,
119
						'--prefer-dist'         => true,
120
						'--optimize-autoloader' => true,
121
						$verbosity              => true
122
					]
123
				);
124
				$output             = new Output;
125
				$Application->setAutoExit(false);
126
				$status_code = $Application->run($input, $output);
127
				$description = $output->get_buffer();
128
				if ($status_code == 0) {
129
					rmdir_recursive("$storage/vendor");
130
					@unlink("$storage/composer.json");
131
					@unlink("$storage/composer.lock");
132
					rename("$storage/tmp/vendor", "$storage/vendor");
133
					rename("$storage/tmp/composer.json", "$storage/composer.json");
134
					rename("$storage/tmp/composer.lock", "$storage/composer.lock");
135
					$Event->fire(
136
						'Composer/updated',
137
						[
138
							'composer_json' => file_get_json("$storage/composer.json"),
139
							'composer_lock' => file_get_json("$storage/composer.lock"),
140
							'composer_root' => $storage
141
						]
142
					);
143
				}
144
			}
145
		} else {
146
			rmdir_recursive("$storage/vendor");
147
			@unlink("$storage/composer.json");
148
			@unlink("$storage/composer.lock");
149
		}
150
		$this->cleanup($storage);
151
		time_limit_pause(false);
152
		return [
153
			'code'        => $status_code,
154
			'description' => $description
155
		];
156
	}
157
	/**
158
	 * @param string $filename
159
	 * @param array  $data
160
	 *
161
	 * @return int
162
	 */
163
	protected function file_put_json ($filename, $data) {
164
		return file_put_contents($filename, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
165
	}
166
	/**
167
	 * @param string $storage
168
	 */
169
	protected function prepare ($storage) {
170
		if (!is_dir($storage)) {
171
			/** @noinspection MkdirRaceConditionInspection */
172
			@mkdir($storage, 0770);
173
		}
174
		/** @noinspection MkdirRaceConditionInspection */
175
		@mkdir("$storage/home", 0770);
176
		rmdir_recursive("$storage/tmp");
177
		/** @noinspection MkdirRaceConditionInspection */
178
		@mkdir("$storage/tmp", 0770);
179
		putenv("COMPOSER_HOME=$storage/home");
180
		@ini_set('display_errors', 1);
181
		@ini_set('memory_limit', '512M');
182
		@unlink("$storage/last_execution.log");
183
	}
184
	/**
185
	 * @param string $component_name
186
	 * @param int    $mode `self::MODE_ADD` or `self::MODE_DELETE`
187
	 *
188
	 * @return array Resulting `composer.json` structure in form of array
189
	 */
190
	protected function generate_composer_json ($component_name, $mode) {
191
		$composer = [
192
			'repositories' => [],
193
			'require'      => []
194
		];
195
		$Config   = Config::instance();
196
		foreach (array_keys($Config->components['modules']) as $module) {
197
			if (
198
				$module == $component_name &&
199
				$mode == self::MODE_DELETE
200
			) {
201
				continue;
202
			}
203
			if (
204
				file_exists(MODULES."/$module/meta.json") &&
205
				(
206
					!$Config->module($module)->uninstalled() ||
207
					$component_name == $module
208
				)
209
			) {
210
				$this->generate_package(
211
					$composer,
212
					file_get_json(MODULES."/$module/meta.json")
213
				);
214
			}
215
		}
216
		return $composer;
217
	}
218
	/**
219
	 * @param array $composer
220
	 * @param array $meta
221
	 */
222
	protected function generate_package (&$composer, $meta) {
223
		if (!isset($meta['version'])) {
224
			return;
225
		}
226
		$package_name = "$meta[category]/$meta[package]";
227
		$package      = [
228
			'name'    => $package_name,
229
			'version' => $meta['version'],
230
			'type'    => 'metapackage',
231
			'require' => isset($meta['require_composer']) ? $meta['require_composer'] : []
232
		];
233
		if ($meta['package'] == 'Composer') {
234
			$package['replace'] = file_get_json(__DIR__.'/packages_bundled_with_system.json');
235
		}
236
		Event::instance()->fire(
237
			'Composer/generate_package',
238
			[
239
				'package' => &$package,
240
				'meta'    => $meta
241
			]
242
		);
243
		if (!$package['require'] && !isset($package['replace'])) {
244
			return;
245
		}
246
247
		$package                            = array_filter(
248
			$package,
249
			function ($value) {
250
				return $value !== [];
251
			}
252
		);
253
		$composer['repositories'][]         = [
254
			'type'    => 'package',
255
			'package' => $package
256
		];
257
		$composer['require'][$package_name] = $meta['version'];
258
	}
259
	/**
260
	 * @param string $storage
261
	 */
262
	protected function cleanup ($storage) {
263
		rmdir_recursive("$storage/tmp");
264
	}
265
}
266