Mercurial > hg > isophonics-drupal-site
comparison core/lib/Drupal/Core/Extension/ModuleHandler.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 1fec387a4317 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\Core\Extension; | |
4 | |
5 use Drupal\Component\Graph\Graph; | |
6 use Drupal\Component\Utility\NestedArray; | |
7 use Drupal\Core\Cache\CacheBackendInterface; | |
8 | |
9 /** | |
10 * Class that manages modules in a Drupal installation. | |
11 */ | |
12 class ModuleHandler implements ModuleHandlerInterface { | |
13 | |
14 /** | |
15 * List of loaded files. | |
16 * | |
17 * @var array | |
18 * An associative array whose keys are file paths of loaded files, relative | |
19 * to the application's root directory. | |
20 */ | |
21 protected $loadedFiles; | |
22 | |
23 /** | |
24 * List of installed modules. | |
25 * | |
26 * @var \Drupal\Core\Extension\Extension[] | |
27 */ | |
28 protected $moduleList; | |
29 | |
30 /** | |
31 * Boolean indicating whether modules have been loaded. | |
32 * | |
33 * @var bool | |
34 */ | |
35 protected $loaded = FALSE; | |
36 | |
37 /** | |
38 * List of hook implementations keyed by hook name. | |
39 * | |
40 * @var array | |
41 */ | |
42 protected $implementations; | |
43 | |
44 /** | |
45 * List of hooks where the implementations have been "verified". | |
46 * | |
47 * @var true[] | |
48 * Associative array where keys are hook names. | |
49 */ | |
50 protected $verified; | |
51 | |
52 /** | |
53 * Information returned by hook_hook_info() implementations. | |
54 * | |
55 * @var array | |
56 */ | |
57 protected $hookInfo; | |
58 | |
59 /** | |
60 * Cache backend for storing module hook implementation information. | |
61 * | |
62 * @var \Drupal\Core\Cache\CacheBackendInterface | |
63 */ | |
64 protected $cacheBackend; | |
65 | |
66 /** | |
67 * Whether the cache needs to be written. | |
68 * | |
69 * @var bool | |
70 */ | |
71 protected $cacheNeedsWriting = FALSE; | |
72 | |
73 /** | |
74 * List of alter hook implementations keyed by hook name(s). | |
75 * | |
76 * @var array | |
77 */ | |
78 protected $alterFunctions; | |
79 | |
80 /** | |
81 * The app root. | |
82 * | |
83 * @var string | |
84 */ | |
85 protected $root; | |
86 | |
87 /** | |
88 * A list of module include file keys. | |
89 * | |
90 * @var array | |
91 */ | |
92 protected $includeFileKeys = []; | |
93 | |
94 /** | |
95 * Constructs a ModuleHandler object. | |
96 * | |
97 * @param string $root | |
98 * The app root. | |
99 * @param array $module_list | |
100 * An associative array whose keys are the names of installed modules and | |
101 * whose values are Extension class parameters. This is normally the | |
102 * %container.modules% parameter being set up by DrupalKernel. | |
103 * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend | |
104 * Cache backend for storing module hook implementation information. | |
105 * | |
106 * @see \Drupal\Core\DrupalKernel | |
107 * @see \Drupal\Core\CoreServiceProvider | |
108 */ | |
109 public function __construct($root, array $module_list, CacheBackendInterface $cache_backend) { | |
110 $this->root = $root; | |
111 $this->moduleList = []; | |
112 foreach ($module_list as $name => $module) { | |
113 $this->moduleList[$name] = new Extension($this->root, $module['type'], $module['pathname'], $module['filename']); | |
114 } | |
115 $this->cacheBackend = $cache_backend; | |
116 } | |
117 | |
118 /** | |
119 * {@inheritdoc} | |
120 */ | |
121 public function load($name) { | |
122 if (isset($this->loadedFiles[$name])) { | |
123 return TRUE; | |
124 } | |
125 | |
126 if (isset($this->moduleList[$name])) { | |
127 $this->moduleList[$name]->load(); | |
128 $this->loadedFiles[$name] = TRUE; | |
129 return TRUE; | |
130 } | |
131 return FALSE; | |
132 } | |
133 | |
134 /** | |
135 * {@inheritdoc} | |
136 */ | |
137 public function loadAll() { | |
138 if (!$this->loaded) { | |
139 foreach ($this->moduleList as $name => $module) { | |
140 $this->load($name); | |
141 } | |
142 $this->loaded = TRUE; | |
143 } | |
144 } | |
145 | |
146 /** | |
147 * {@inheritdoc} | |
148 */ | |
149 public function reload() { | |
150 $this->loaded = FALSE; | |
151 $this->loadAll(); | |
152 } | |
153 | |
154 /** | |
155 * {@inheritdoc} | |
156 */ | |
157 public function isLoaded() { | |
158 return $this->loaded; | |
159 } | |
160 | |
161 /** | |
162 * {@inheritdoc} | |
163 */ | |
164 public function getModuleList() { | |
165 return $this->moduleList; | |
166 } | |
167 | |
168 /** | |
169 * {@inheritdoc} | |
170 */ | |
171 public function getModule($name) { | |
172 if (isset($this->moduleList[$name])) { | |
173 return $this->moduleList[$name]; | |
174 } | |
175 throw new \InvalidArgumentException(sprintf('The module %s does not exist.', $name)); | |
176 } | |
177 | |
178 /** | |
179 * {@inheritdoc} | |
180 */ | |
181 public function setModuleList(array $module_list = []) { | |
182 $this->moduleList = $module_list; | |
183 // Reset the implementations, so a new call triggers a reloading of the | |
184 // available hooks. | |
185 $this->resetImplementations(); | |
186 } | |
187 | |
188 /** | |
189 * {@inheritdoc} | |
190 */ | |
191 public function addModule($name, $path) { | |
192 $this->add('module', $name, $path); | |
193 } | |
194 | |
195 /** | |
196 * {@inheritdoc} | |
197 */ | |
198 public function addProfile($name, $path) { | |
199 $this->add('profile', $name, $path); | |
200 } | |
201 | |
202 /** | |
203 * Adds a module or profile to the list of currently active modules. | |
204 * | |
205 * @param string $type | |
206 * The extension type; either 'module' or 'profile'. | |
207 * @param string $name | |
208 * The module name; e.g., 'node'. | |
209 * @param string $path | |
210 * The module path; e.g., 'core/modules/node'. | |
211 */ | |
212 protected function add($type, $name, $path) { | |
213 $pathname = "$path/$name.info.yml"; | |
214 $filename = file_exists($this->root . "/$path/$name.$type") ? "$name.$type" : NULL; | |
215 $this->moduleList[$name] = new Extension($this->root, $type, $pathname, $filename); | |
216 $this->resetImplementations(); | |
217 } | |
218 | |
219 /** | |
220 * {@inheritdoc} | |
221 */ | |
222 public function buildModuleDependencies(array $modules) { | |
223 foreach ($modules as $module) { | |
224 $graph[$module->getName()]['edges'] = []; | |
225 if (isset($module->info['dependencies']) && is_array($module->info['dependencies'])) { | |
226 foreach ($module->info['dependencies'] as $dependency) { | |
227 $dependency_data = static::parseDependency($dependency); | |
228 $graph[$module->getName()]['edges'][$dependency_data['name']] = $dependency_data; | |
229 } | |
230 } | |
231 } | |
232 $graph_object = new Graph($graph); | |
233 $graph = $graph_object->searchAndSort(); | |
234 foreach ($graph as $module_name => $data) { | |
235 $modules[$module_name]->required_by = isset($data['reverse_paths']) ? $data['reverse_paths'] : []; | |
236 $modules[$module_name]->requires = isset($data['paths']) ? $data['paths'] : []; | |
237 $modules[$module_name]->sort = $data['weight']; | |
238 } | |
239 return $modules; | |
240 } | |
241 | |
242 /** | |
243 * {@inheritdoc} | |
244 */ | |
245 public function moduleExists($module) { | |
246 return isset($this->moduleList[$module]); | |
247 } | |
248 | |
249 /** | |
250 * {@inheritdoc} | |
251 */ | |
252 public function loadAllIncludes($type, $name = NULL) { | |
253 foreach ($this->moduleList as $module => $filename) { | |
254 $this->loadInclude($module, $type, $name); | |
255 } | |
256 } | |
257 | |
258 /** | |
259 * {@inheritdoc} | |
260 */ | |
261 public function loadInclude($module, $type, $name = NULL) { | |
262 if ($type == 'install') { | |
263 // Make sure the installation API is available | |
264 include_once $this->root . '/core/includes/install.inc'; | |
265 } | |
266 | |
267 $name = $name ?: $module; | |
268 $key = $type . ':' . $module . ':' . $name; | |
269 if (isset($this->includeFileKeys[$key])) { | |
270 return $this->includeFileKeys[$key]; | |
271 } | |
272 if (isset($this->moduleList[$module])) { | |
273 $file = $this->root . '/' . $this->moduleList[$module]->getPath() . "/$name.$type"; | |
274 if (is_file($file)) { | |
275 require_once $file; | |
276 $this->includeFileKeys[$key] = $file; | |
277 return $file; | |
278 } | |
279 else { | |
280 $this->includeFileKeys[$key] = FALSE; | |
281 } | |
282 } | |
283 return FALSE; | |
284 } | |
285 | |
286 /** | |
287 * {@inheritdoc} | |
288 */ | |
289 public function getHookInfo() { | |
290 if (!isset($this->hookInfo)) { | |
291 if ($cache = $this->cacheBackend->get('hook_info')) { | |
292 $this->hookInfo = $cache->data; | |
293 } | |
294 else { | |
295 $this->buildHookInfo(); | |
296 $this->cacheBackend->set('hook_info', $this->hookInfo); | |
297 } | |
298 } | |
299 return $this->hookInfo; | |
300 } | |
301 | |
302 /** | |
303 * Builds hook_hook_info() information. | |
304 * | |
305 * @see \Drupal\Core\Extension\ModuleHandler::getHookInfo() | |
306 */ | |
307 protected function buildHookInfo() { | |
308 $this->hookInfo = []; | |
309 // Make sure that the modules are loaded before checking. | |
310 $this->reload(); | |
311 // $this->invokeAll() would cause an infinite recursion. | |
312 foreach ($this->moduleList as $module => $filename) { | |
313 $function = $module . '_hook_info'; | |
314 if (function_exists($function)) { | |
315 $result = $function(); | |
316 if (isset($result) && is_array($result)) { | |
317 $this->hookInfo = NestedArray::mergeDeep($this->hookInfo, $result); | |
318 } | |
319 } | |
320 } | |
321 } | |
322 | |
323 /** | |
324 * {@inheritdoc} | |
325 */ | |
326 public function getImplementations($hook) { | |
327 $implementations = $this->getImplementationInfo($hook); | |
328 return array_keys($implementations); | |
329 } | |
330 | |
331 /** | |
332 * {@inheritdoc} | |
333 */ | |
334 public function writeCache() { | |
335 if ($this->cacheNeedsWriting) { | |
336 $this->cacheBackend->set('module_implements', $this->implementations); | |
337 $this->cacheNeedsWriting = FALSE; | |
338 } | |
339 } | |
340 | |
341 /** | |
342 * {@inheritdoc} | |
343 */ | |
344 public function resetImplementations() { | |
345 $this->implementations = NULL; | |
346 $this->hookInfo = NULL; | |
347 $this->alterFunctions = NULL; | |
348 // We maintain a persistent cache of hook implementations in addition to the | |
349 // static cache to avoid looping through every module and every hook on each | |
350 // request. Benchmarks show that the benefit of this caching outweighs the | |
351 // additional database hit even when using the default database caching | |
352 // backend and only a small number of modules are enabled. The cost of the | |
353 // $this->cacheBackend->get() is more or less constant and reduced further | |
354 // when non-database caching backends are used, so there will be more | |
355 // significant gains when a large number of modules are installed or hooks | |
356 // invoked, since this can quickly lead to | |
357 // \Drupal::moduleHandler()->implementsHook() being called several thousand | |
358 // times per request. | |
359 $this->cacheBackend->set('module_implements', []); | |
360 $this->cacheBackend->delete('hook_info'); | |
361 } | |
362 | |
363 /** | |
364 * {@inheritdoc} | |
365 */ | |
366 public function implementsHook($module, $hook) { | |
367 $function = $module . '_' . $hook; | |
368 if (function_exists($function)) { | |
369 return TRUE; | |
370 } | |
371 // If the hook implementation does not exist, check whether it lives in an | |
372 // optional include file registered via hook_hook_info(). | |
373 $hook_info = $this->getHookInfo(); | |
374 if (isset($hook_info[$hook]['group'])) { | |
375 $this->loadInclude($module, 'inc', $module . '.' . $hook_info[$hook]['group']); | |
376 if (function_exists($function)) { | |
377 return TRUE; | |
378 } | |
379 } | |
380 return FALSE; | |
381 } | |
382 | |
383 /** | |
384 * {@inheritdoc} | |
385 */ | |
386 public function invoke($module, $hook, array $args = []) { | |
387 if (!$this->implementsHook($module, $hook)) { | |
388 return; | |
389 } | |
390 $function = $module . '_' . $hook; | |
391 return call_user_func_array($function, $args); | |
392 } | |
393 | |
394 /** | |
395 * {@inheritdoc} | |
396 */ | |
397 public function invokeAll($hook, array $args = []) { | |
398 $return = []; | |
399 $implementations = $this->getImplementations($hook); | |
400 foreach ($implementations as $module) { | |
401 $function = $module . '_' . $hook; | |
402 $result = call_user_func_array($function, $args); | |
403 if (isset($result) && is_array($result)) { | |
404 $return = NestedArray::mergeDeep($return, $result); | |
405 } | |
406 elseif (isset($result)) { | |
407 $return[] = $result; | |
408 } | |
409 } | |
410 | |
411 return $return; | |
412 } | |
413 | |
414 /** | |
415 * {@inheritdoc} | |
416 */ | |
417 public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) { | |
418 // Most of the time, $type is passed as a string, so for performance, | |
419 // normalize it to that. When passed as an array, usually the first item in | |
420 // the array is a generic type, and additional items in the array are more | |
421 // specific variants of it, as in the case of array('form', 'form_FORM_ID'). | |
422 if (is_array($type)) { | |
423 $cid = implode(',', $type); | |
424 $extra_types = $type; | |
425 $type = array_shift($extra_types); | |
426 // Allow if statements in this function to use the faster isset() rather | |
427 // than !empty() both when $type is passed as a string, or as an array | |
428 // with one item. | |
429 if (empty($extra_types)) { | |
430 unset($extra_types); | |
431 } | |
432 } | |
433 else { | |
434 $cid = $type; | |
435 } | |
436 | |
437 // Some alter hooks are invoked many times per page request, so store the | |
438 // list of functions to call, and on subsequent calls, iterate through them | |
439 // quickly. | |
440 if (!isset($this->alterFunctions[$cid])) { | |
441 $this->alterFunctions[$cid] = []; | |
442 $hook = $type . '_alter'; | |
443 $modules = $this->getImplementations($hook); | |
444 if (!isset($extra_types)) { | |
445 // For the more common case of a single hook, we do not need to call | |
446 // function_exists(), since $this->getImplementations() returns only | |
447 // modules with implementations. | |
448 foreach ($modules as $module) { | |
449 $this->alterFunctions[$cid][] = $module . '_' . $hook; | |
450 } | |
451 } | |
452 else { | |
453 // For multiple hooks, we need $modules to contain every module that | |
454 // implements at least one of them. | |
455 $extra_modules = []; | |
456 foreach ($extra_types as $extra_type) { | |
457 $extra_modules = array_merge($extra_modules, $this->getImplementations($extra_type . '_alter')); | |
458 } | |
459 // If any modules implement one of the extra hooks that do not implement | |
460 // the primary hook, we need to add them to the $modules array in their | |
461 // appropriate order. $this->getImplementations() can only return | |
462 // ordered implementations of a single hook. To get the ordered | |
463 // implementations of multiple hooks, we mimic the | |
464 // $this->getImplementations() logic of first ordering by | |
465 // $this->getModuleList(), and then calling | |
466 // $this->alter('module_implements'). | |
467 if (array_diff($extra_modules, $modules)) { | |
468 // Merge the arrays and order by getModuleList(). | |
469 $modules = array_intersect(array_keys($this->moduleList), array_merge($modules, $extra_modules)); | |
470 // Since $this->getImplementations() already took care of loading the | |
471 // necessary include files, we can safely pass FALSE for the array | |
472 // values. | |
473 $implementations = array_fill_keys($modules, FALSE); | |
474 // Let modules adjust the order solely based on the primary hook. This | |
475 // ensures the same module order regardless of whether this if block | |
476 // runs. Calling $this->alter() recursively in this way does not | |
477 // result in an infinite loop, because this call is for a single | |
478 // $type, so we won't end up in this code block again. | |
479 $this->alter('module_implements', $implementations, $hook); | |
480 $modules = array_keys($implementations); | |
481 } | |
482 foreach ($modules as $module) { | |
483 // Since $modules is a merged array, for any given module, we do not | |
484 // know whether it has any particular implementation, so we need a | |
485 // function_exists(). | |
486 $function = $module . '_' . $hook; | |
487 if (function_exists($function)) { | |
488 $this->alterFunctions[$cid][] = $function; | |
489 } | |
490 foreach ($extra_types as $extra_type) { | |
491 $function = $module . '_' . $extra_type . '_alter'; | |
492 if (function_exists($function)) { | |
493 $this->alterFunctions[$cid][] = $function; | |
494 } | |
495 } | |
496 } | |
497 } | |
498 } | |
499 | |
500 foreach ($this->alterFunctions[$cid] as $function) { | |
501 $function($data, $context1, $context2); | |
502 } | |
503 } | |
504 | |
505 /** | |
506 * Provides information about modules' implementations of a hook. | |
507 * | |
508 * @param string $hook | |
509 * The name of the hook (e.g. "help" or "menu"). | |
510 * | |
511 * @return mixed[] | |
512 * An array whose keys are the names of the modules which are implementing | |
513 * this hook and whose values are either a string identifying a file in | |
514 * which the implementation is to be found, or FALSE, if the implementation | |
515 * is in the module file. | |
516 */ | |
517 protected function getImplementationInfo($hook) { | |
518 if (!isset($this->implementations)) { | |
519 $this->implementations = []; | |
520 $this->verified = []; | |
521 if ($cache = $this->cacheBackend->get('module_implements')) { | |
522 $this->implementations = $cache->data; | |
523 } | |
524 } | |
525 if (!isset($this->implementations[$hook])) { | |
526 // The hook is not cached, so ensure that whether or not it has | |
527 // implementations, the cache is updated at the end of the request. | |
528 $this->cacheNeedsWriting = TRUE; | |
529 // Discover implementations. | |
530 $this->implementations[$hook] = $this->buildImplementationInfo($hook); | |
531 // Implementations are always "verified" as part of the discovery. | |
532 $this->verified[$hook] = TRUE; | |
533 } | |
534 elseif (!isset($this->verified[$hook])) { | |
535 if (!$this->verifyImplementations($this->implementations[$hook], $hook)) { | |
536 // One or more of the implementations did not exist and need to be | |
537 // removed in the cache. | |
538 $this->cacheNeedsWriting = TRUE; | |
539 } | |
540 $this->verified[$hook] = TRUE; | |
541 } | |
542 return $this->implementations[$hook]; | |
543 } | |
544 | |
545 /** | |
546 * Builds hook implementation information for a given hook name. | |
547 * | |
548 * @param string $hook | |
549 * The name of the hook (e.g. "help" or "menu"). | |
550 * | |
551 * @return mixed[] | |
552 * An array whose keys are the names of the modules which are implementing | |
553 * this hook and whose values are either a string identifying a file in | |
554 * which the implementation is to be found, or FALSE, if the implementation | |
555 * is in the module file. | |
556 * | |
557 * @throws \RuntimeException | |
558 * Exception thrown when an invalid implementation is added by | |
559 * hook_module_implements_alter(). | |
560 * | |
561 * @see \Drupal\Core\Extension\ModuleHandler::getImplementationInfo() | |
562 */ | |
563 protected function buildImplementationInfo($hook) { | |
564 $implementations = []; | |
565 $hook_info = $this->getHookInfo(); | |
566 foreach ($this->moduleList as $module => $extension) { | |
567 $include_file = isset($hook_info[$hook]['group']) && $this->loadInclude($module, 'inc', $module . '.' . $hook_info[$hook]['group']); | |
568 // Since $this->implementsHook() may needlessly try to load the include | |
569 // file again, function_exists() is used directly here. | |
570 if (function_exists($module . '_' . $hook)) { | |
571 $implementations[$module] = $include_file ? $hook_info[$hook]['group'] : FALSE; | |
572 } | |
573 } | |
574 // Allow modules to change the weight of specific implementations, but avoid | |
575 // an infinite loop. | |
576 if ($hook != 'module_implements_alter') { | |
577 // Remember the original implementations, before they are modified with | |
578 // hook_module_implements_alter(). | |
579 $implementations_before = $implementations; | |
580 // Verify implementations that were added or modified. | |
581 $this->alter('module_implements', $implementations, $hook); | |
582 // Verify new or modified implementations. | |
583 foreach (array_diff_assoc($implementations, $implementations_before) as $module => $group) { | |
584 // If an implementation of hook_module_implements_alter() changed or | |
585 // added a group, the respective file needs to be included. | |
586 if ($group) { | |
587 $this->loadInclude($module, 'inc', "$module.$group"); | |
588 } | |
589 // If a new implementation was added, verify that the function exists. | |
590 if (!function_exists($module . '_' . $hook)) { | |
591 throw new \RuntimeException("An invalid implementation {$module}_{$hook} was added by hook_module_implements_alter()"); | |
592 } | |
593 } | |
594 } | |
595 return $implementations; | |
596 } | |
597 | |
598 /** | |
599 * Verifies an array of implementations loaded from the cache, by including | |
600 * the lazy-loaded $module.$group.inc, and checking function_exists(). | |
601 * | |
602 * @param string[] $implementations | |
603 * Implementation "group" by module name. | |
604 * @param string $hook | |
605 * The hook name. | |
606 * | |
607 * @return bool | |
608 * TRUE, if all implementations exist. | |
609 * FALSE, if one or more implementations don't exist and need to be removed | |
610 * from the cache. | |
611 */ | |
612 protected function verifyImplementations(&$implementations, $hook) { | |
613 $all_valid = TRUE; | |
614 foreach ($implementations as $module => $group) { | |
615 // If this hook implementation is stored in a lazy-loaded file, include | |
616 // that file first. | |
617 if ($group) { | |
618 $this->loadInclude($module, 'inc', "$module.$group"); | |
619 } | |
620 // It is possible that a module removed a hook implementation without | |
621 // the implementations cache being rebuilt yet, so we check whether the | |
622 // function exists on each request to avoid undefined function errors. | |
623 // Since ModuleHandler::implementsHook() may needlessly try to | |
624 // load the include file again, function_exists() is used directly here. | |
625 if (!function_exists($module . '_' . $hook)) { | |
626 // Clear out the stale implementation from the cache and force a cache | |
627 // refresh to forget about no longer existing hook implementations. | |
628 unset($implementations[$module]); | |
629 // One of the implementations did not exist and needs to be removed in | |
630 // the cache. | |
631 $all_valid = FALSE; | |
632 } | |
633 } | |
634 return $all_valid; | |
635 } | |
636 | |
637 /** | |
638 * Parses a dependency for comparison by drupal_check_incompatibility(). | |
639 * | |
640 * @param $dependency | |
641 * A dependency string, which specifies a module dependency, and optionally | |
642 * the project it comes from and versions that are supported. Supported | |
643 * formats include: | |
644 * - 'module' | |
645 * - 'project:module' | |
646 * - 'project:module (>=version, version)' | |
647 * | |
648 * @return | |
649 * An associative array with three keys: | |
650 * - 'name' includes the name of the thing to depend on (e.g. 'foo'). | |
651 * - 'original_version' contains the original version string (which can be | |
652 * used in the UI for reporting incompatibilities). | |
653 * - 'versions' is a list of associative arrays, each containing the keys | |
654 * 'op' and 'version'. 'op' can be one of: '=', '==', '!=', '<>', '<', | |
655 * '<=', '>', or '>='. 'version' is one piece like '4.5-beta3'. | |
656 * Callers should pass this structure to drupal_check_incompatibility(). | |
657 * | |
658 * @see drupal_check_incompatibility() | |
659 */ | |
660 public static function parseDependency($dependency) { | |
661 $value = []; | |
662 // Split out the optional project name. | |
663 if (strpos($dependency, ':') !== FALSE) { | |
664 list($project_name, $dependency) = explode(':', $dependency); | |
665 $value['project'] = $project_name; | |
666 } | |
667 // We use named subpatterns and support every op that version_compare | |
668 // supports. Also, op is optional and defaults to equals. | |
669 $p_op = '(?<operation>!=|==|=|<|<=|>|>=|<>)?'; | |
670 // Core version is always optional: 8.x-2.x and 2.x is treated the same. | |
671 $p_core = '(?:' . preg_quote(\Drupal::CORE_COMPATIBILITY) . '-)?'; | |
672 $p_major = '(?<major>\d+)'; | |
673 // By setting the minor version to x, branches can be matched. | |
674 $p_minor = '(?<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)'; | |
675 $parts = explode('(', $dependency, 2); | |
676 $value['name'] = trim($parts[0]); | |
677 if (isset($parts[1])) { | |
678 $value['original_version'] = ' (' . $parts[1]; | |
679 foreach (explode(',', $parts[1]) as $version) { | |
680 if (preg_match("/^\s*$p_op\s*$p_core$p_major\.$p_minor/", $version, $matches)) { | |
681 $op = !empty($matches['operation']) ? $matches['operation'] : '='; | |
682 if ($matches['minor'] == 'x') { | |
683 // Drupal considers "2.x" to mean any version that begins with | |
684 // "2" (e.g. 2.0, 2.9 are all "2.x"). PHP's version_compare(), | |
685 // on the other hand, treats "x" as a string; so to | |
686 // version_compare(), "2.x" is considered less than 2.0. This | |
687 // means that >=2.x and <2.x are handled by version_compare() | |
688 // as we need, but > and <= are not. | |
689 if ($op == '>' || $op == '<=') { | |
690 $matches['major']++; | |
691 } | |
692 // Equivalence can be checked by adding two restrictions. | |
693 if ($op == '=' || $op == '==') { | |
694 $value['versions'][] = ['op' => '<', 'version' => ($matches['major'] + 1) . '.x']; | |
695 $op = '>='; | |
696 } | |
697 } | |
698 $value['versions'][] = ['op' => $op, 'version' => $matches['major'] . '.' . $matches['minor']]; | |
699 } | |
700 } | |
701 } | |
702 return $value; | |
703 } | |
704 | |
705 /** | |
706 * {@inheritdoc} | |
707 */ | |
708 public function getModuleDirectories() { | |
709 $dirs = []; | |
710 foreach ($this->getModuleList() as $name => $module) { | |
711 $dirs[$name] = $this->root . '/' . $module->getPath(); | |
712 } | |
713 return $dirs; | |
714 } | |
715 | |
716 /** | |
717 * {@inheritdoc} | |
718 */ | |
719 public function getName($module) { | |
720 $info = system_get_info('module', $module); | |
721 return isset($info['name']) ? $info['name'] : $module; | |
722 } | |
723 | |
724 } |