LocationType - Use standard delete function which calls hooks
[civicrm-core.git] / CRM / Extension / MixinScanner.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 * The MixinScanner scans the list of actives extensions and their required mixins.
14 */
15 class CRM_Extension_MixinScanner {
16
17 /**
18 * @var CRM_Extension_Mapper
19 */
20 protected $mapper;
21
22 /**
23 * @var CRM_Extension_Manager
24 */
25 protected $manager;
26
27 /**
28 * @var string[]|null
29 * A list of base-paths which are implicitly supported by 'include' directives.
30 * Sorted with the longest paths first.
31 */
32 protected $relativeBases;
33
34 /**
35 * CRM_Extension_ClassLoader constructor.
36 * @param \CRM_Extension_Mapper|NULL $mapper
37 * @param \CRM_Extension_Manager|NULL $manager
38 * @param bool $relativize
39 * Whether to store paths in relative form.
40 * Enabling this may slow-down scanning a bit, and it has no benefit when for on-demand loaders.
41 * However, if the loader is cached, then it may make for smaller, more portable cache-file.
42 */
43 public function __construct(?\CRM_Extension_Mapper $mapper = NULL, \CRM_Extension_Manager $manager = NULL, $relativize = TRUE) {
44 $this->mapper = $mapper ?: CRM_Extension_System::singleton()->getMapper();
45 $this->manager = $manager ?: CRM_Extension_System::singleton()->getManager();
46 if ($relativize) {
47 $this->relativeBases = [Civi::paths()->getVariable('civicrm.root', 'path')];
48 // Previous drafts used `relativeBases=explode(include_path)`. However, this produces unstable results
49 // when flip through the phases of the lifecycle - because the include_path changes throughout the lifecycle.
50 usort($this->relativeBases, function($a, $b) {
51 return strlen($b) - strlen($a);
52 });
53 }
54 else {
55 $this->relativeBases = NULL;
56 }
57 }
58
59 /**
60 * @return \CRM_Extension_MixinLoader
61 */
62 public function createLoader() {
63 $l = new CRM_Extension_MixinLoader();
64
65 foreach ($this->getInstalledKeys() as $key) {
66 try {
67 $path = $this->mapper->keyToBasePath($key);
68 $l->addMixInfo($this->createMixInfo($path . DIRECTORY_SEPARATOR . CRM_Extension_Info::FILENAME));
69 $l->addFunctionFiles($this->findFunctionFiles("$path/mixin/*@*.mixin.php"));
70 $l->addFunctionFiles($this->findFunctionFiles("$path/mixin/*@*/mixin.php"), TRUE);
71 }
72 catch (CRM_Extension_Exception_ParseException $e) {
73 error_log(sprintf('MixinScanner: Failed to read extension (%s)', $key));
74 }
75 }
76
77 $l->addFunctionFiles($this->findFunctionFiles(Civi::paths()->getPath('[civicrm.root]/mixin/*@*.mixin.php')));
78 $l->addFunctionFiles($this->findFunctionFiles(Civi::paths()->getPath('[civicrm.root]/mixin/*@*/mixin.php')), TRUE);
79
80 return $l->compile();
81 }
82
83 /**
84 * @return array
85 */
86 private function getInstalledKeys() {
87 $keys = [];
88
89 $statuses = $this->manager->getStatuses();
90 ksort($statuses);
91 foreach ($statuses as $key => $status) {
92 if ($status === CRM_Extension_Manager::STATUS_INSTALLED) {
93 $keys[] = $key;
94 }
95 }
96
97 return $keys;
98 }
99
100 /**
101 * @param string $infoFile
102 * Path to the 'info.xml' file
103 * @return \CRM_Extension_MixInfo
104 * @throws \CRM_Extension_Exception_ParseException
105 */
106 private function createMixInfo(string $infoFile) {
107 $info = CRM_Extension_Info::loadFromFile($infoFile);
108 $instance = new CRM_Extension_MixInfo();
109 $instance->longName = $info->key;
110 $instance->shortName = $info->file;
111 $instance->path = rtrim(dirname($infoFile), '/' . DIRECTORY_SEPARATOR);
112 $instance->mixins = $info->mixins;
113 return $instance;
114 }
115
116 /**
117 * @param string $globPat
118 * @return array
119 * Ex: ['mix/xml-menu-autoload@1.0.mixin.php']
120 */
121 private function findFunctionFiles($globPat) {
122 $useRel = $this->relativeBases !== NULL;
123 $result = [];
124 $funcFiles = (array) glob($globPat);
125 sort($funcFiles);
126 foreach ($funcFiles as $shimFile) {
127 $shimFileRel = $useRel ? $this->relativize($shimFile) : $shimFile;
128 $result[] = $shimFileRel;
129 }
130 return $result;
131 }
132
133 /**
134 * Convert the absolute $file to an expression that is supported by 'include'.
135 *
136 * @param string $file
137 * @return string
138 */
139 private function relativize($file) {
140 foreach ($this->relativeBases as $relativeBase) {
141 if (CRM_Utils_File::isChildPath($relativeBase, $file)) {
142 return ltrim(CRM_Utils_File::relativize($file, $relativeBase), '/' . DIRECTORY_SEPARATOR);
143 }
144 }
145 return $file;
146 }
147
148 }