This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * This file is part of the Composer Merge plugin. |
||
4 | * |
||
5 | * Copyright (C) 2015 Bryan Davis, Wikimedia Foundation, and contributors |
||
6 | * |
||
7 | * This software may be modified and distributed under the terms of the MIT |
||
8 | * license. See the LICENSE file for details. |
||
9 | */ |
||
10 | |||
11 | namespace Wikimedia\Composer; |
||
12 | |||
13 | use Wikimedia\Composer\Merge\ExtraPackage; |
||
14 | use Wikimedia\Composer\Merge\MissingFileException; |
||
15 | use Wikimedia\Composer\Merge\PluginState; |
||
16 | |||
17 | use Composer\Composer; |
||
18 | use Composer\DependencyResolver\Operation\InstallOperation; |
||
19 | use Composer\EventDispatcher\Event as BaseEvent; |
||
20 | use Composer\EventDispatcher\EventSubscriberInterface; |
||
21 | use Composer\Factory; |
||
22 | use Composer\Installer; |
||
23 | use Composer\Installer\InstallerEvent; |
||
24 | use Composer\Installer\InstallerEvents; |
||
25 | use Composer\Installer\PackageEvent; |
||
26 | use Composer\Installer\PackageEvents; |
||
27 | use Composer\IO\IOInterface; |
||
28 | use Composer\Package\RootPackageInterface; |
||
29 | use Composer\Plugin\PluginInterface; |
||
30 | use Composer\Script\Event as ScriptEvent; |
||
31 | use Composer\Script\ScriptEvents; |
||
32 | |||
33 | /** |
||
34 | * Composer plugin that allows merging multiple composer.json files. |
||
35 | * |
||
36 | * When installed, this plugin will look for a "merge-plugin" key in the |
||
37 | * composer configuration's "extra" section. The value for this key is |
||
38 | * a set of options configuring the plugin. |
||
39 | * |
||
40 | * An "include" setting is required. The value of this setting can be either |
||
41 | * a single value or an array of values. Each value is treated as a glob() |
||
42 | * pattern identifying additional composer.json style configuration files to |
||
43 | * merge into the configuration for the current compser execution. |
||
44 | * |
||
45 | * The "autoload", "autoload-dev", "conflict", "provide", "replace", |
||
46 | * "repositories", "require", "require-dev", and "suggest" sections of the |
||
47 | * found configuration files will be merged into the root package |
||
48 | * configuration as though they were directly included in the top-level |
||
49 | * composer.json file. |
||
50 | * |
||
51 | * If included files specify conflicting package versions for "require" or |
||
52 | * "require-dev", the normal Composer dependency solver process will be used |
||
53 | * to attempt to resolve the conflict. Specifying the 'replace' key as true will |
||
54 | * change this default behaviour so that the last-defined version of a package |
||
55 | * will win, allowing for force-overrides of package defines. |
||
56 | * |
||
57 | * By default the "extra" section is not merged. This can be enabled by |
||
58 | * setitng the 'merge-extra' key to true. In normal mode, when the same key is |
||
59 | * found in both the original and the imported extra section, the version in |
||
60 | * the original config is used and the imported version is skipped. If |
||
61 | * 'replace' mode is active, this behaviour changes so the imported version of |
||
62 | * the key is used, replacing the version in the original config. |
||
63 | * |
||
64 | * |
||
65 | * @code |
||
66 | * { |
||
67 | * "require": { |
||
68 | * "wikimedia/composer-merge-plugin": "dev-master" |
||
69 | * }, |
||
70 | * "extra": { |
||
71 | * "merge-plugin": { |
||
72 | * "include": [ |
||
73 | * "composer.local.json" |
||
74 | * ] |
||
75 | * } |
||
76 | * } |
||
77 | * } |
||
78 | * @endcode |
||
79 | * |
||
80 | * @author Bryan Davis <[email protected]> |
||
81 | */ |
||
82 | class MergePlugin implements PluginInterface, EventSubscriberInterface |
||
83 | { |
||
84 | |||
85 | /** |
||
86 | * Offical package name |
||
87 | */ |
||
88 | const PACKAGE_NAME = 'wikimedia/composer-merge-plugin'; |
||
89 | |||
90 | /** |
||
91 | * Name of the composer 1.1 init event. |
||
92 | */ |
||
93 | const COMPAT_PLUGINEVENTS_INIT = 'init'; |
||
94 | |||
95 | /** |
||
96 | * Priority that plugin uses to register callbacks. |
||
97 | */ |
||
98 | const CALLBACK_PRIORITY = 50000; |
||
99 | |||
100 | /** |
||
101 | * @var Composer $composer |
||
102 | */ |
||
103 | protected $composer; |
||
104 | |||
105 | /** |
||
106 | * @var PluginState $state |
||
107 | */ |
||
108 | protected $state; |
||
109 | |||
110 | /** |
||
111 | * @var Logger $logger |
||
112 | */ |
||
113 | protected $logger; |
||
114 | |||
115 | /** |
||
116 | * Files that have already been fully processed |
||
117 | * |
||
118 | * @var string[] $loaded |
||
119 | */ |
||
120 | protected $loaded = array(); |
||
121 | |||
122 | /** |
||
123 | * Files that have already been partially processed |
||
124 | * |
||
125 | * @var string[] $loadedNoDev |
||
126 | */ |
||
127 | protected $loadedNoDev = array(); |
||
128 | |||
129 | /** |
||
130 | * {@inheritdoc} |
||
131 | */ |
||
132 | 195 | public function activate(Composer $composer, IOInterface $io) |
|
133 | { |
||
134 | 195 | $this->composer = $composer; |
|
135 | 195 | $this->state = new PluginState($this->composer); |
|
136 | 195 | $this->logger = new Logger('merge-plugin', $io); |
|
137 | 195 | } |
|
138 | |||
139 | /** |
||
140 | * {@inheritdoc} |
||
141 | */ |
||
142 | 5 | public static function getSubscribedEvents() |
|
143 | { |
||
144 | return array( |
||
145 | // Use our own constant to make this event optional. Once |
||
146 | // composer-1.1 is required, this can use PluginEvents::INIT |
||
147 | // instead. |
||
148 | 5 | self::COMPAT_PLUGINEVENTS_INIT => |
|
149 | 5 | array('onInit', self::CALLBACK_PRIORITY), |
|
150 | 5 | InstallerEvents::PRE_DEPENDENCIES_SOLVING => |
|
151 | 5 | array('onDependencySolve', self::CALLBACK_PRIORITY), |
|
152 | 5 | PackageEvents::POST_PACKAGE_INSTALL => |
|
153 | 5 | array('onPostPackageInstall', self::CALLBACK_PRIORITY), |
|
154 | 5 | ScriptEvents::POST_INSTALL_CMD => |
|
155 | 5 | array('onPostInstallOrUpdate', self::CALLBACK_PRIORITY), |
|
156 | 5 | ScriptEvents::POST_UPDATE_CMD => |
|
157 | 5 | array('onPostInstallOrUpdate', self::CALLBACK_PRIORITY), |
|
158 | 5 | ScriptEvents::PRE_AUTOLOAD_DUMP => |
|
159 | 5 | array('onInstallUpdateOrDump', self::CALLBACK_PRIORITY), |
|
160 | 5 | ScriptEvents::PRE_INSTALL_CMD => |
|
161 | 5 | array('onInstallUpdateOrDump', self::CALLBACK_PRIORITY), |
|
162 | 5 | ScriptEvents::PRE_UPDATE_CMD => |
|
163 | 5 | array('onInstallUpdateOrDump', self::CALLBACK_PRIORITY), |
|
164 | 1 | ); |
|
165 | } |
||
166 | |||
167 | /** |
||
168 | * Handle an event callback for initialization. |
||
169 | * |
||
170 | * @param \Composer\EventDispatcher\Event $event |
||
171 | */ |
||
172 | 70 | public function onInit(BaseEvent $event) |
|
0 ignored issues
–
show
|
|||
173 | { |
||
174 | 70 | $this->state->loadSettings(); |
|
175 | // It is not possible to know if the user specified --dev or --no-dev |
||
176 | // so assume it is false. The dev section will be merged later when |
||
177 | // the other events fire. |
||
178 | 70 | $this->state->setDevMode(false); |
|
179 | 70 | $this->mergeFiles($this->state->getIncludes(), false); |
|
180 | 70 | $this->mergeFiles($this->state->getRequires(), true); |
|
181 | 70 | } |
|
182 | |||
183 | /** |
||
184 | * Handle an event callback for an install, update or dump command by |
||
185 | * checking for "merge-plugin" in the "extra" data and merging package |
||
186 | * contents if found. |
||
187 | * |
||
188 | * @param ScriptEvent $event |
||
189 | */ |
||
190 | 175 | public function onInstallUpdateOrDump(ScriptEvent $event) |
|
191 | { |
||
192 | 175 | $this->state->loadSettings(); |
|
193 | 175 | $this->state->setDevMode($event->isDevMode()); |
|
194 | 175 | $this->mergeFiles($this->state->getIncludes(), false); |
|
195 | 175 | $this->mergeFiles($this->state->getRequires(), true); |
|
196 | |||
197 | 170 | if ($event->getName() === ScriptEvents::PRE_AUTOLOAD_DUMP) { |
|
198 | 170 | $this->state->setDumpAutoloader(true); |
|
199 | 170 | $flags = $event->getFlags(); |
|
200 | 170 | if (isset($flags['optimize'])) { |
|
201 | 170 | $this->state->setOptimizeAutoloader($flags['optimize']); |
|
202 | 34 | } |
|
203 | 34 | } |
|
204 | 170 | } |
|
205 | |||
206 | /** |
||
207 | * Find configuration files matching the configured glob patterns and |
||
208 | * merge their contents with the master package. |
||
209 | * |
||
210 | * @param array $patterns List of files/glob patterns |
||
211 | * @param bool $required Are the patterns required to match files? |
||
212 | * @throws MissingFileException when required and a pattern returns no |
||
213 | * results |
||
214 | */ |
||
215 | 175 | protected function mergeFiles(array $patterns, $required = false) |
|
216 | { |
||
217 | 175 | $root = $this->composer->getPackage(); |
|
218 | |||
219 | 175 | $files = array_map( |
|
220 | 175 | function ($files, $pattern) use ($required) { |
|
221 | 175 | if ($required && !$files) { |
|
222 | 5 | throw new MissingFileException( |
|
223 | 5 | "merge-plugin: No files matched required '{$pattern}'" |
|
224 | 1 | ); |
|
225 | } |
||
226 | 170 | return $files; |
|
227 | 175 | }, |
|
228 | 175 | array_map('glob', $patterns), |
|
229 | 70 | $patterns |
|
230 | 35 | ); |
|
231 | |||
232 | 175 | foreach (array_reduce($files, 'array_merge', array()) as $path) { |
|
233 | 170 | $this->mergeFile($root, $path); |
|
234 | 35 | } |
|
235 | 175 | } |
|
236 | |||
237 | /** |
||
238 | * Read a JSON file and merge its contents |
||
239 | * |
||
240 | * @param RootPackageInterface $root |
||
241 | * @param string $path |
||
242 | */ |
||
243 | 170 | protected function mergeFile(RootPackageInterface $root, $path) |
|
244 | { |
||
245 | 170 | if (isset($this->loaded[$path]) || |
|
246 | 170 | (isset($this->loadedNoDev[$path]) && !$this->state->isDevMode()) |
|
247 | 34 | ) { |
|
248 | 170 | $this->logger->debug( |
|
249 | 170 | "Already merged <comment>$path</comment> completely" |
|
250 | 34 | ); |
|
251 | 170 | return; |
|
252 | } |
||
253 | |||
254 | 170 | $package = new ExtraPackage($path, $this->composer, $this->logger); |
|
255 | |||
256 | 170 | if (isset($this->loadedNoDev[$path])) { |
|
257 | 70 | $this->logger->info( |
|
258 | 70 | "Loading -dev sections of <comment>{$path}</comment>..." |
|
259 | 14 | ); |
|
260 | 70 | $package->mergeDevInto($root, $this->state); |
|
261 | 14 | } else { |
|
262 | 170 | $this->logger->info("Loading <comment>{$path}</comment>..."); |
|
263 | 170 | $package->mergeInto($root, $this->state); |
|
264 | } |
||
265 | |||
266 | 170 | if ($this->state->isDevMode()) { |
|
267 | 165 | $this->loaded[$path] = true; |
|
268 | 33 | } else { |
|
269 | 75 | $this->loadedNoDev[$path] = true; |
|
270 | } |
||
271 | |||
272 | 170 | if ($this->state->recurseIncludes()) { |
|
273 | 165 | $this->mergeFiles($package->getIncludes(), false); |
|
274 | 165 | $this->mergeFiles($package->getRequires(), true); |
|
275 | 33 | } |
|
276 | 170 | } |
|
277 | |||
278 | /** |
||
279 | * Handle an event callback for pre-dependency solving phase of an install |
||
280 | * or update by adding any duplicate package dependencies found during |
||
281 | * initial merge processing to the request that will be processed by the |
||
282 | * dependency solver. |
||
283 | * |
||
284 | * @param InstallerEvent $event |
||
285 | */ |
||
286 | 170 | public function onDependencySolve(InstallerEvent $event) |
|
287 | { |
||
288 | 170 | $request = $event->getRequest(); |
|
289 | 170 | foreach ($this->state->getDuplicateLinks('require') as $link) { |
|
290 | 25 | $this->logger->info( |
|
291 | 25 | "Adding dependency <comment>{$link}</comment>" |
|
292 | 5 | ); |
|
293 | 25 | $request->install($link->getTarget(), $link->getConstraint()); |
|
294 | 34 | } |
|
295 | |||
296 | // Issue #113: Check devMode of event rather than our global state. |
||
297 | // Composer fires the PRE_DEPENDENCIES_SOLVING event twice for |
||
298 | // `--no-dev` operations to decide which packages are dev only |
||
299 | // requirements. |
||
300 | 170 | if ($this->state->shouldMergeDev() && $event->isDevMode()) { |
|
301 | 165 | foreach ($this->state->getDuplicateLinks('require-dev') as $link) { |
|
302 | 10 | $this->logger->info( |
|
303 | 10 | "Adding dev dependency <comment>{$link}</comment>" |
|
304 | 2 | ); |
|
305 | 10 | $request->install($link->getTarget(), $link->getConstraint()); |
|
306 | 33 | } |
|
307 | 33 | } |
|
308 | 170 | } |
|
309 | |||
310 | /** |
||
311 | * Handle an event callback following installation of a new package by |
||
312 | * checking to see if the package that was installed was our plugin. |
||
313 | * |
||
314 | * @param PackageEvent $event |
||
315 | */ |
||
316 | 15 | public function onPostPackageInstall(PackageEvent $event) |
|
317 | { |
||
318 | 15 | $op = $event->getOperation(); |
|
319 | 15 | if ($op instanceof InstallOperation) { |
|
320 | 15 | $package = $op->getPackage()->getName(); |
|
321 | 15 | if ($package === self::PACKAGE_NAME) { |
|
322 | 10 | $this->logger->info('composer-merge-plugin installed'); |
|
323 | 10 | $this->state->setFirstInstall(true); |
|
324 | 10 | $this->state->setLocked( |
|
325 | 10 | $event->getComposer()->getLocker()->isLocked() |
|
326 | 2 | ); |
|
327 | 2 | } |
|
328 | 3 | } |
|
329 | 15 | } |
|
330 | |||
331 | /** |
||
332 | * Handle an event callback following an install or update command. If our |
||
333 | * plugin was installed during the run then trigger an update command to |
||
334 | * process any merge-patterns in the current config. |
||
335 | * |
||
336 | * @param ScriptEvent $event |
||
337 | */ |
||
338 | 170 | public function onPostInstallOrUpdate(ScriptEvent $event) |
|
339 | { |
||
340 | // @codeCoverageIgnoreStart |
||
341 | if ($this->state->isFirstInstall()) { |
||
342 | $this->state->setFirstInstall(false); |
||
343 | $this->logger->info( |
||
344 | '<comment>' . |
||
345 | 'Running additional update to apply merge settings' . |
||
346 | '</comment>' |
||
347 | ); |
||
348 | |||
349 | $config = $this->composer->getConfig(); |
||
350 | |||
351 | $preferSource = $config->get('preferred-install') == 'source'; |
||
352 | $preferDist = $config->get('preferred-install') == 'dist'; |
||
353 | |||
354 | $installer = Installer::create( |
||
355 | $event->getIO(), |
||
356 | // Create a new Composer instance to ensure full processing of |
||
357 | // the merged files. |
||
358 | Factory::create($event->getIO(), null, false) |
||
359 | ); |
||
360 | |||
361 | $installer->setPreferSource($preferSource); |
||
362 | $installer->setPreferDist($preferDist); |
||
363 | $installer->setDevMode($event->isDevMode()); |
||
364 | $installer->setDumpAutoloader($this->state->shouldDumpAutoloader()); |
||
365 | $installer->setOptimizeAutoloader( |
||
366 | $this->state->shouldOptimizeAutoloader() |
||
367 | ); |
||
368 | |||
369 | if ($this->state->forceUpdate()) { |
||
370 | // Force update mode so that new packages are processed rather |
||
371 | // than just telling the user that composer.json and |
||
372 | // composer.lock don't match. |
||
373 | $installer->setUpdate(true); |
||
374 | } |
||
375 | |||
376 | $installer->run(); |
||
377 | } |
||
378 | // @codeCoverageIgnoreEnd |
||
379 | 170 | } |
|
380 | } |
||
381 | // vim:sw=4:ts=4:sts=4:et: |
||
382 |
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.