Completed
Push — master ( 9d1026...803f74 )
by Nazar
04:43
created

Composer::cleanup()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
/**
3
 * @package   Composer
4
 * @category  modules
5
 * @author    Nazar Mokrynskyi <[email protected]>
6
 * @copyright Copyright (c) 2015-2016, Nazar Mokrynskyi
7
 * @license   MIT License, see license.txt
8
 */
9
namespace cs\modules\Composer;
10
use
11
	cs\Config,
12
	cs\Event,
13
	cs\Singleton,
14
	Symfony\Component\Console\Input\ArrayInput;
15
16
/**
17
 * Provides next events:
18
 *  Composer/generate_package
19
 *  [
20
 *   'package' => &$package, //Composer package generated, might be modified
21
 *   'meta'    => $meta      //Parsed `meta.json` for component, package is generated for
22
 *  ]
23
 *
24
 *  Composer/generate_composer_json
25
 *  [
26
 *   'composer_json' => &$composer_json //`composer.json` structure that will be used for dependencies installation, might be modified
27
 *   'auth_json'     => &$auth_json     //`auth.json` structure that will be used for auth credentials during dependencies installation, might be modified
28
 *  ]
29
 *
30
 *  Composer/Composer
31
 *  [
32
 *   'Composer' => $Composer //Instance of `\Composer\Composer`, so that it is possible, for instance, to inject some plugins manually
33
 *  ]
34
 *
35
 *  Composer/updated
36
 *  [
37
 *   'composer_json' => $composer_json, //`composer.json` structure that was used for dependencies installation
38
 *   'composer_lock' => $composer_lock  //`composer.lock` structure that was generated during dependencies installation
39
 *   'composer_root' => $composer_root  //Path to directory where dependencies were installed, and where `composer.json` and `composer.lock` are located
40
 *  ]
41
 *
42
 * @method static Composer instance($check = false)
43
 */
44
class Composer {
45
	use
46
		Singleton;
47
	const MODE_ADD    = 1;
48
	const MODE_DELETE = 2;
49
	/**
50
	 * Force update even if nothing changed
51
	 *
52
	 * @var bool
53
	 */
54
	protected $force_update = false;
55
	protected function construct () {
56
		require_once 'phar://'.__DIR__.'/composer.phar/src/bootstrap.php';
57
	}
58
	/**
59
	 * Update composer even if nothing changed
60
	 *
61
	 * @return array
62
	 */
63
	function force_update () {
64
		$this->force_update = true;
65
		return $this->update();
66
	}
67
	/**
68
	 * Update composer
69
	 *
70
	 * @param string $component_name Is specified if called before component actually installed (to satisfy dependencies)
0 ignored issues
show
Documentation introduced by
Should the type for parameter $component_name not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
71
	 * @param string $category       `modules` or `plugins`
72
	 * @param int    $mode           Composer::MODE_ADD or Composer::MODE_DELETE
73
	 *
74
	 * @return array Array with `code` and `description` elements, first represents status code returned by composer, second contains ANSI text returned by
75
	 *               composer
76
	 */
77
	function update ($component_name = null, $category = 'modules', $mode = self::MODE_ADD) {
78
		time_limit_pause();
79
		$storage     = STORAGE.'/Composer';
80
		$status_code = 0;
81
		$description = '';
82
		$this->prepare($storage);
83
		$composer_json = $this->generate_composer_json($component_name, $category, $mode);
84
		$Config        = Config::instance();
85
		$auth_json     = _json_decode($Config->module('Composer')->auth_json ?: '[]');
86
		$Event         = Event::instance();
87
		$Event->fire(
88
			'Composer/generate_composer_json',
89
			[
90
				'composer_json' => &$composer_json,
91
				'auth_json'     => &$auth_json
92
			]
93
		);
94
		if ($composer_json['repositories']) {
95
			$this->file_put_json("$storage/tmp/composer.json", $composer_json);
96
			$this->file_put_json("$storage/tmp/auth.json", $auth_json);
97
			if (
98
				$this->force_update ||
99
				!file_exists("$storage/composer.json") ||
100
				md5_file("$storage/tmp/composer.json") != md5_file("$storage/composer.json")
101
			) {
102
				$this->force_update = false;
103
				$application        = new Application(
104
					function ($Composer) use ($Event) {
105
						$Event->fire(
106
							'Composer/Composer',
107
							[
108
								'Composer' => $Composer
109
							]
110
						);
111
					}
112
				);
113
				$verbosity          = !DEBUG && $Config->core['simple_admin_mode'] ? '-vv' : '-vvv';
114
				$input              = new ArrayInput(
115
					[
116
						'command'       => 'update',
117
						'--working-dir' => "$storage/tmp",
118
						'--no-dev'      => true,
119
						'--ansi'        => true,
120
						'--prefer-dist' => true,
121
						$verbosity      => true
122
					]
123
				);
124
				$output             = new Output;
125
				$output->set_stream(fopen("$storage/last_execution.log", 'w'));
126
				$application->setAutoExit(false);
127
				$status_code = $application->run($input, $output);
128
				$description = $output->fetch();
129
				if ($status_code == 0) {
130
					rmdir_recursive("$storage/vendor");
131
					@unlink("$storage/composer.json");
132
					@unlink("$storage/composer.lock");
133
					rename("$storage/tmp/vendor", "$storage/vendor");
134
					rename("$storage/tmp/composer.json", "$storage/composer.json");
135
					rename("$storage/tmp/composer.lock", "$storage/composer.lock");
136
					$Event->fire(
137
						'Composer/updated',
138
						[
139
							'composer_json' => file_get_json("$storage/composer.json"),
140
							'composer_lock' => file_get_json("$storage/composer.lock"),
141
							'composer_root' => $storage
142
						]
143
					);
144
				}
145
			}
146
		} else {
147
			rmdir_recursive("$storage/vendor");
148
			@unlink("$storage/composer.json");
149
			@unlink("$storage/composer.lock");
150
		}
151
		$this->cleanup($storage);
152
		time_limit_pause(false);
153
		return [
154
			'code'        => $status_code,
155
			'description' => $description
156
		];
157
	}
158
	/**
159
	 * @param string $filename
160
	 * @param array  $data
161
	 *
162
	 * @return int
163
	 */
164
	protected function file_put_json ($filename, $data) {
165
		return file_put_contents($filename, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
166
	}
167
	/**
168
	 * @param string $storage
169
	 */
170
	protected function prepare ($storage) {
171
		if (!is_dir($storage)) {
172
			@mkdir($storage, 0770);
173
		}
174
		rmdir_recursive("$storage/home");
175
		@mkdir("$storage/home", 0770);
176
		rmdir_recursive("$storage/tmp");
177
		@mkdir("$storage/tmp", 0770);
178
		putenv("COMPOSER_HOME=$storage/home");
179
		@ini_set('display_errors', 1);
180
		@ini_set('memory_limit', '512M');
181
		@unlink("$storage/last_execution.log");
182
	}
183
	/**
184
	 * @param string $component_name
185
	 * @param string $category `modules` or `plugins`
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, $category, $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
				$category == 'modules' &&
200
				$mode == self::MODE_DELETE
201
			) {
202
				continue;
203
			}
204
			if (
205
				file_exists(MODULES."/$module/meta.json") &&
206
				(
207
					!$Config->module($module)->uninstalled() ||
208
					(
209
						$component_name == $module &&
210
						$category == 'modules'
211
					)
212
				)
213
			) {
214
				$this->generate_package(
215
					$composer,
216
					file_get_json(MODULES."/$module/meta.json")
217
				);
218
			}
219
		}
220
		foreach (
221
			array_merge(
222
				$Config->components['plugins'],
223
				$category == 'plugins' && $component_name ? [$component_name] : []
224
			) as $plugin
225
		) {
226
			if (
227
				$plugin == $component_name &&
228
				$category == 'plugins' &&
229
				$mode == self::MODE_DELETE
230
			) {
231
				continue;
232
			}
233
			if (file_exists(PLUGINS."/$plugin/meta.json")) {
234
				$this->generate_package(
235
					$composer,
236
					file_get_json(PLUGINS."/$plugin/meta.json")
237
				);
238
			}
239
		}
240
		return $composer;
241
	}
242
	/**
243
	 * @param array $composer
244
	 * @param array $meta
245
	 */
246
	protected function generate_package (&$composer, $meta) {
247
		if (!isset($meta['version'])) {
248
			return;
249
		}
250
		$package_name = "$meta[category]/$meta[package]";
251
		$package      = [
252
			'name'    => $package_name,
253
			'version' => "1.0.0+$meta[version]",
254
			'require' => isset($meta['require_composer']) ? $meta['require_composer'] : [],
255
			'dist'    => [
256
				'url'  => __DIR__.'/empty.zip',
257
				'type' => 'zip'
258
			]
259
		];
260
		if ($meta['package'] == 'Composer') {
261
			$package['replace'] = file_get_json(__DIR__.'/packages_bundled_with_system.json');
262
		}
263
		Event::instance()->fire(
264
			'Composer/generate_package',
265
			[
266
				'package' => &$package,
267
				'meta'    => $meta
268
			]
269
		);
270
		if (!$package['require'] && !isset($package['replace'])) {
271
			return;
272
		}
273
		$composer['repositories'][]         = [
274
			'type'    => 'package',
275
			'package' => $package
276
		];
277
		/**
278
		 * 1.0.0 at the beginning in order to ignore stability issue: https://github.com/composer/composer/issues/4889
279
		 */
280
		$composer['require'][$package_name] = "1.0.0+$meta[version]";
281
	}
282
	/**
283
	 * @param string $storage
284
	 */
285
	protected function cleanup ($storage) {
286
		rmdir_recursive("$storage/home");
287
		rmdir_recursive("$storage/tmp");
288
	}
289
}
290