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 | /* |
||
4 | * This file is part of the puli/manager package. |
||
5 | * |
||
6 | * (c) Bernhard Schussek <[email protected]> |
||
7 | * |
||
8 | * For the full copyright and license information, please view the LICENSE |
||
9 | * file that was distributed with this source code. |
||
10 | */ |
||
11 | |||
12 | namespace Puli\Manager\Module; |
||
13 | |||
14 | use Exception; |
||
15 | use Puli\Manager\Api\Context\ProjectContext; |
||
16 | use Puli\Manager\Api\Environment; |
||
17 | use Puli\Manager\Api\FileNotFoundException; |
||
18 | use Puli\Manager\Api\InvalidConfigException; |
||
19 | use Puli\Manager\Api\Module\DependencyFile; |
||
20 | use Puli\Manager\Api\Module\InstallInfo; |
||
21 | use Puli\Manager\Api\Module\Module; |
||
22 | use Puli\Manager\Api\Module\ModuleFile; |
||
23 | use Puli\Manager\Api\Module\ModuleList; |
||
24 | use Puli\Manager\Api\Module\ModuleManager; |
||
25 | use Puli\Manager\Api\Module\NameConflictException; |
||
26 | use Puli\Manager\Api\Module\RootModule; |
||
27 | use Puli\Manager\Api\Module\RootModuleFile; |
||
28 | use Puli\Manager\Api\Module\UnsupportedVersionException; |
||
29 | use Puli\Manager\Api\NoDirectoryException; |
||
30 | use Puli\Manager\Assert\Assert; |
||
31 | use Puli\Manager\Json\JsonStorage; |
||
32 | use Webmozart\Expression\Expr; |
||
33 | use Webmozart\Expression\Expression; |
||
34 | use Webmozart\PathUtil\Path; |
||
35 | |||
36 | /** |
||
37 | * Manages the module repository of a Puli project. |
||
38 | * |
||
39 | * @since 1.0 |
||
40 | * |
||
41 | * @author Bernhard Schussek <[email protected]> |
||
42 | */ |
||
43 | class ModuleManagerImpl implements ModuleManager |
||
44 | { |
||
45 | /** |
||
46 | * @var ProjectContext |
||
47 | */ |
||
48 | private $context; |
||
49 | |||
50 | /** |
||
51 | * @var string |
||
52 | */ |
||
53 | private $rootDir; |
||
54 | |||
55 | /** |
||
56 | * @var RootModuleFile |
||
57 | */ |
||
58 | private $rootModuleFile; |
||
59 | |||
60 | /** |
||
61 | * @var JsonStorage |
||
62 | */ |
||
63 | private $jsonStorage; |
||
64 | |||
65 | /** |
||
66 | * @var ModuleList |
||
67 | */ |
||
68 | private $modules; |
||
69 | |||
70 | /** |
||
71 | * @var DependencyFile[] |
||
72 | */ |
||
73 | private $dependencyFilesByInstallerName = array(); |
||
0 ignored issues
–
show
|
|||
74 | |||
75 | /** |
||
76 | * Loads the module repository for a given project. |
||
77 | * |
||
78 | * @param ProjectContext $context The project context. |
||
79 | * @param JsonStorage $jsonStorage The module file storage. |
||
80 | * |
||
81 | * @throws FileNotFoundException If the install path of a module not exist. |
||
82 | * @throws NoDirectoryException If the install path of a module points to a file. |
||
83 | * @throws InvalidConfigException If a configuration file contains invalid configuration. |
||
84 | * @throws NameConflictException If a module has the same name as another loaded module. |
||
85 | */ |
||
86 | 53 | public function __construct(ProjectContext $context, JsonStorage $jsonStorage) |
|
87 | { |
||
88 | 53 | $this->context = $context; |
|
89 | 53 | $this->rootDir = $context->getRootDirectory(); |
|
90 | 53 | $this->rootModuleFile = $context->getRootModuleFile(); |
|
91 | 53 | $this->jsonStorage = $jsonStorage; |
|
92 | 53 | } |
|
93 | |||
94 | /** |
||
95 | * {@inheritdoc} |
||
96 | */ |
||
97 | 12 | public function installModule($installPath, $name = null, $installerName = InstallInfo::DEFAULT_INSTALLER_NAME, $env = Environment::PROD) |
|
98 | { |
||
99 | 12 | Assert::string($installPath, 'The install path must be a string. Got: %s'); |
|
100 | 12 | Assert::string($installerName, 'The installer name must be a string. Got: %s'); |
|
101 | 12 | Assert::oneOf($env, Environment::all(), 'The environment must be one of: %2$s. Got: %s'); |
|
102 | 12 | Assert::nullOrModuleName($name); |
|
103 | |||
104 | 11 | $this->assertModulesLoaded(); |
|
105 | |||
106 | 11 | $installPath = Path::makeAbsolute($installPath, $this->rootDir); |
|
107 | |||
108 | 11 | foreach ($this->modules as $module) { |
|
109 | 11 | if ($installPath === $module->getInstallPath()) { |
|
110 | 11 | return; |
|
111 | } |
||
112 | } |
||
113 | |||
114 | 10 | if (null === $name && $moduleFile = $this->loadModuleFile($installPath)) { |
|
115 | // Read the name from the module file |
||
116 | 6 | $name = $moduleFile->getModuleName(); |
|
117 | } |
||
118 | |||
119 | 8 | if (null === $name) { |
|
120 | 1 | throw new InvalidConfigException(sprintf( |
|
121 | 'Could not find a name for the module at %s. The name should '. |
||
122 | 'either be passed to the installer or be set in the "name" '. |
||
123 | 1 | 'property of %s.', |
|
124 | $installPath, |
||
125 | 1 | $installPath.'/puli.json' |
|
126 | )); |
||
127 | } |
||
128 | |||
129 | 7 | if ($this->modules->contains($name)) { |
|
130 | 1 | throw NameConflictException::forName($name); |
|
131 | } |
||
132 | |||
133 | 6 | $relInstallPath = Path::makeRelative($installPath, $this->rootDir); |
|
134 | 6 | $installInfo = new InstallInfo($name, $relInstallPath); |
|
135 | 6 | $installInfo->setInstallerName($installerName); |
|
136 | 6 | $installInfo->setEnvironment($env); |
|
137 | |||
138 | 6 | $module = $this->loadModule($installInfo); |
|
139 | |||
140 | 6 | $this->assertNoLoadErrors($module); |
|
141 | 5 | $this->rootModuleFile->addInstallInfo($installInfo); |
|
142 | |||
143 | try { |
||
144 | 5 | $this->jsonStorage->saveRootModuleFile($this->rootModuleFile); |
|
145 | } catch (Exception $e) { |
||
146 | $this->rootModuleFile->removeInstallInfo($name); |
||
147 | |||
148 | throw $e; |
||
149 | } |
||
150 | |||
151 | 5 | $this->modules->add($module); |
|
152 | 5 | } |
|
153 | |||
154 | /** |
||
155 | * {@inheritdoc} |
||
156 | */ |
||
157 | 8 | public function renameModule($name, $newName) |
|
158 | { |
||
159 | 8 | $module = $this->getModule($name); |
|
160 | |||
161 | 8 | if ($name === $newName) { |
|
162 | 2 | return; |
|
163 | } |
||
164 | |||
165 | 6 | if ($this->modules->contains($newName)) { |
|
166 | 2 | throw NameConflictException::forName($newName); |
|
167 | } |
||
168 | |||
169 | 4 | if ($module instanceof RootModule) { |
|
170 | 2 | $this->renameRootModule($module, $newName); |
|
171 | } else { |
||
172 | 2 | $this->renameNonRootModule($module, $newName); |
|
173 | } |
||
174 | 2 | } |
|
175 | |||
176 | /** |
||
177 | * {@inheritdoc} |
||
178 | */ |
||
179 | 4 | public function removeModule($name) |
|
180 | { |
||
181 | // Only check that this is a string. The error message "not found" is |
||
182 | // more helpful than e.g. "module name must contain /". |
||
183 | 4 | Assert::string($name, 'The module name must be a string. Got: %s'); |
|
184 | |||
185 | 4 | $this->assertModulesLoaded(); |
|
186 | |||
187 | 4 | if ($this->rootModuleFile->hasInstallInfo($name)) { |
|
188 | 2 | $installInfo = $this->rootModuleFile->getInstallInfo($name); |
|
189 | 2 | $this->rootModuleFile->removeInstallInfo($name); |
|
190 | |||
191 | try { |
||
192 | 2 | $this->jsonStorage->saveRootModuleFile($this->rootModuleFile); |
|
193 | 1 | } catch (Exception $e) { |
|
194 | 1 | $this->rootModuleFile->addInstallInfo($installInfo); |
|
195 | |||
196 | 1 | throw $e; |
|
197 | } |
||
198 | } |
||
199 | |||
200 | 3 | $this->modules->remove($name); |
|
201 | 3 | } |
|
202 | |||
203 | /** |
||
204 | * {@inheritdoc} |
||
205 | */ |
||
206 | 3 | public function removeModules(Expression $expr) |
|
207 | { |
||
208 | 3 | $this->assertModulesLoaded(); |
|
209 | |||
210 | 3 | $installInfos = $this->rootModuleFile->getInstallInfos(); |
|
211 | 3 | $modules = $this->modules->toArray(); |
|
212 | |||
213 | 3 | foreach ($this->modules->getInstalledModules() as $module) { |
|
214 | 3 | if ($expr->evaluate($module)) { |
|
215 | 3 | $this->rootModuleFile->removeInstallInfo($module->getName()); |
|
216 | 3 | $this->modules->remove($module->getName()); |
|
217 | } |
||
218 | } |
||
219 | |||
220 | 3 | if (!$installInfos) { |
|
0 ignored issues
–
show
The expression
$installInfos of type Puli\Manager\Api\Module\InstallInfo[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||
221 | return; |
||
222 | } |
||
223 | |||
224 | try { |
||
225 | 3 | $this->jsonStorage->saveRootModuleFile($this->rootModuleFile); |
|
226 | 1 | } catch (Exception $e) { |
|
227 | 1 | $this->rootModuleFile->setInstallInfos($installInfos); |
|
228 | 1 | $this->modules->replace($modules); |
|
229 | |||
230 | 1 | throw $e; |
|
231 | } |
||
232 | 2 | } |
|
233 | |||
234 | /** |
||
235 | * {@inheritdoc} |
||
236 | */ |
||
237 | 1 | public function clearModules() |
|
238 | { |
||
239 | 1 | $this->removeModules(Expr::true()); |
|
240 | 1 | } |
|
241 | |||
242 | /** |
||
243 | * {@inheritdoc} |
||
244 | */ |
||
245 | 10 | public function getModule($name) |
|
246 | { |
||
247 | 10 | Assert::string($name, 'The module name must be a string. Got: %s'); |
|
248 | |||
249 | 10 | $this->assertModulesLoaded(); |
|
250 | |||
251 | 10 | return $this->modules->get($name); |
|
252 | } |
||
253 | |||
254 | /** |
||
255 | * {@inheritdoc} |
||
256 | */ |
||
257 | 1 | public function getRootModule() |
|
258 | { |
||
259 | 1 | $this->assertModulesLoaded(); |
|
260 | |||
261 | 1 | return $this->modules->getRootModule(); |
|
262 | } |
||
263 | |||
264 | /** |
||
265 | * {@inheritdoc} |
||
266 | */ |
||
267 | 24 | public function getModules() |
|
268 | { |
||
269 | 24 | $this->assertModulesLoaded(); |
|
270 | |||
271 | // Never return he original collection |
||
272 | 24 | return clone $this->modules; |
|
273 | } |
||
274 | |||
275 | /** |
||
276 | * {@inheritdoc} |
||
277 | */ |
||
278 | 4 | public function findModules(Expression $expr) |
|
279 | { |
||
280 | 4 | $this->assertModulesLoaded(); |
|
281 | |||
282 | 4 | $modules = new ModuleList(); |
|
283 | |||
284 | 4 | foreach ($this->modules as $module) { |
|
285 | 4 | if ($expr->evaluate($module)) { |
|
286 | 4 | $modules->add($module); |
|
287 | } |
||
288 | } |
||
289 | |||
290 | 4 | return $modules; |
|
291 | } |
||
292 | |||
293 | /** |
||
294 | * {@inheritdoc} |
||
295 | */ |
||
296 | 11 | public function hasModule($name) |
|
297 | { |
||
298 | 11 | Assert::string($name, 'The module name must be a string. Got: %s'); |
|
299 | |||
300 | 11 | $this->assertModulesLoaded(); |
|
301 | |||
302 | 11 | return $this->modules->contains($name); |
|
303 | } |
||
304 | |||
305 | /** |
||
306 | * {@inheritdoc} |
||
307 | */ |
||
308 | 1 | View Code Duplication | public function hasModules(Expression $expr = null) |
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
309 | { |
||
310 | 1 | $this->assertModulesLoaded(); |
|
311 | |||
312 | 1 | if (!$expr) { |
|
313 | 1 | return !$this->modules->isEmpty(); |
|
314 | } |
||
315 | |||
316 | 1 | foreach ($this->modules as $module) { |
|
317 | 1 | if ($expr->evaluate($module)) { |
|
318 | 1 | return true; |
|
319 | } |
||
320 | } |
||
321 | |||
322 | 1 | return false; |
|
323 | } |
||
324 | |||
325 | /** |
||
326 | * {@inheritdoc} |
||
327 | */ |
||
328 | 1 | public function getContext() |
|
329 | { |
||
330 | 1 | return $this->context; |
|
331 | } |
||
332 | |||
333 | /** |
||
334 | * Loads all modules referenced by the install file. |
||
335 | * |
||
336 | * @throws FileNotFoundException If the install path of a module not exist. |
||
337 | * @throws NoDirectoryException If the install path of a module points to a |
||
338 | * file. |
||
339 | * @throws InvalidConfigException If a module is not configured correctly. |
||
340 | * @throws NameConflictException If a module has the same name as another |
||
341 | * loaded module. |
||
342 | */ |
||
343 | 52 | private function loadModules() |
|
344 | { |
||
345 | 52 | $this->modules = new ModuleList(); |
|
346 | 52 | $this->modules->add(new RootModule($this->rootModuleFile, $this->rootDir)); |
|
347 | |||
348 | 52 | foreach ($this->rootModuleFile->getInstallInfos() as $installInfo) { |
|
349 | 51 | $this->modules->add($this->loadModule($installInfo)); |
|
350 | } |
||
351 | 52 | } |
|
352 | |||
353 | /** |
||
354 | * Loads a module for the given install info. |
||
355 | * |
||
356 | * @param InstallInfo $installInfo The install info. |
||
357 | * |
||
358 | * @return Module The module. |
||
359 | */ |
||
360 | 52 | private function loadModule(InstallInfo $installInfo) |
|
361 | { |
||
362 | 52 | $installPath = Path::makeAbsolute($installInfo->getInstallPath(), $this->rootDir); |
|
363 | 52 | $moduleFile = null; |
|
364 | 52 | $loadError = null; |
|
365 | |||
366 | // Catch and log exceptions so that single modules cannot break |
||
367 | // the whole repository |
||
368 | try { |
||
369 | 52 | $moduleFile = $this->loadModuleFile($installPath); |
|
370 | 7 | } catch (InvalidConfigException $loadError) { |
|
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
|
|||
371 | 6 | } catch (UnsupportedVersionException $loadError) { |
|
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
|
|||
372 | 4 | } catch (FileNotFoundException $loadError) { |
|
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
|
|||
373 | 1 | } catch (NoDirectoryException $loadError) { |
|
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
|
|||
374 | } |
||
375 | |||
376 | 52 | $loadErrors = $loadError ? array($loadError) : array(); |
|
377 | |||
378 | 52 | return new Module($moduleFile, $installPath, $installInfo, $loadErrors); |
|
379 | } |
||
380 | |||
381 | /** |
||
382 | * Loads the module file for the module at the given install path. |
||
383 | * |
||
384 | * @param string $installPath The absolute install path of the module |
||
385 | * |
||
386 | * @return ModuleFile|null The loaded module file or `null` if none |
||
387 | * could be found. |
||
388 | */ |
||
389 | 52 | private function loadModuleFile($installPath) |
|
390 | { |
||
391 | 52 | if (!file_exists($installPath)) { |
|
392 | 4 | throw FileNotFoundException::forPath($installPath); |
|
393 | } |
||
394 | |||
395 | 51 | if (!is_dir($installPath)) { |
|
396 | 2 | throw new NoDirectoryException(sprintf( |
|
397 | 2 | 'The path %s is a file. Expected a directory.', |
|
398 | $installPath |
||
399 | )); |
||
400 | } |
||
401 | |||
402 | try { |
||
403 | 50 | return $this->jsonStorage->loadModuleFile($installPath.'/puli.json'); |
|
404 | 4 | } catch (FileNotFoundException $e) { |
|
405 | // Modules without module files are ok |
||
406 | 1 | return null; |
|
407 | } |
||
408 | } |
||
409 | |||
410 | 52 | private function assertModulesLoaded() |
|
411 | { |
||
412 | 52 | if (!$this->modules) { |
|
413 | 52 | $this->loadModules(); |
|
414 | } |
||
415 | 52 | } |
|
416 | |||
417 | 6 | private function assertNoLoadErrors(Module $module) |
|
418 | { |
||
419 | 6 | $loadErrors = $module->getLoadErrors(); |
|
420 | |||
421 | 6 | if (count($loadErrors) > 0) { |
|
422 | // Rethrow first error |
||
423 | 1 | throw reset($loadErrors); |
|
424 | } |
||
425 | 5 | } |
|
426 | |||
427 | 2 | private function renameRootModule(RootModule $module, $newName) |
|
428 | { |
||
429 | 2 | $moduleFile = $module->getModuleFile(); |
|
430 | 2 | $previousName = $moduleFile->getModuleName(); |
|
431 | 2 | $moduleFile->setModuleName($newName); |
|
432 | |||
433 | try { |
||
434 | 2 | $this->jsonStorage->saveRootModuleFile($this->rootModuleFile); |
|
435 | 1 | } catch (Exception $e) { |
|
436 | 1 | $moduleFile->setModuleName($previousName); |
|
437 | |||
438 | 1 | throw $e; |
|
439 | } |
||
440 | |||
441 | 1 | $this->modules->remove($module->getName()); |
|
442 | 1 | $this->modules->add(new RootModule($moduleFile, $module->getInstallPath())); |
|
0 ignored issues
–
show
It seems like
$moduleFile defined by $module->getModuleFile() on line 429 can be null ; however, Puli\Manager\Api\Module\RootModule::__construct() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
443 | 1 | } |
|
444 | |||
445 | 2 | private function renameNonRootModule(Module $module, $newName) |
|
446 | { |
||
447 | 2 | $previousInstallInfo = $module->getInstallInfo(); |
|
448 | |||
449 | 2 | $installInfo = new InstallInfo($newName, $previousInstallInfo->getInstallPath()); |
|
450 | 2 | $installInfo->setInstallerName($previousInstallInfo->getInstallerName()); |
|
451 | |||
452 | 2 | foreach ($previousInstallInfo->getDisabledBindingUuids() as $uuid) { |
|
453 | 1 | $installInfo->addDisabledBindingUuid($uuid); |
|
454 | } |
||
455 | |||
456 | 2 | $this->rootModuleFile->removeInstallInfo($module->getName()); |
|
457 | 2 | $this->rootModuleFile->addInstallInfo($installInfo); |
|
458 | |||
459 | try { |
||
460 | 2 | $this->jsonStorage->saveRootModuleFile($this->rootModuleFile); |
|
461 | 1 | } catch (Exception $e) { |
|
462 | 1 | $this->rootModuleFile->removeInstallInfo($newName); |
|
463 | 1 | $this->rootModuleFile->addInstallInfo($previousInstallInfo); |
|
464 | |||
465 | 1 | throw $e; |
|
466 | } |
||
467 | |||
468 | 1 | $this->modules->remove($module->getName()); |
|
469 | 1 | $this->modules->add(new Module( |
|
470 | 1 | $module->getModuleFile(), |
|
471 | 1 | $module->getInstallPath(), |
|
472 | $installInfo, |
||
473 | 1 | $module->getLoadErrors() |
|
474 | )); |
||
475 | 1 | } |
|
476 | } |
||
477 |
This check marks private properties in classes that are never used. Those properties can be removed.