Remove unused code
[civicrm-core.git] / CRM / Extension / Mapper.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
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 |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 * This class proivdes various helper functions for locating extensions
14 * data. It's designed for compatibility with pre-existing functions from
15 * CRM_Core_Extensions.
16 *
17 * Most of these helper functions originate with the first major iteration
18 * of extensions -- a time when every extension had one eponymous PHP class,
19 * when there was no PHP class-loader, and when there was special-case logic
20 * sprinkled around to handle loading of "extension classes".
21 *
22 * With module-extensions (Civi 4.2+), there are no eponymous classes --
23 * instead, module-extensions follow the same class-naming and class-loading
24 * practices as core (and don't require special-case logic for class
25 * loading). Consequently, the helpers in here aren't much used with
26 * module-extensions.
27 *
28 * @package CRM
ca5cec67 29 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
30 */
31class CRM_Extension_Mapper {
32
33 /**
fe482240 34 * An URL for public extensions repository.
6a488035 35 */
6a488035
TO
36
37 /**
fe482240 38 * Extension info file name.
6a488035
TO
39 */
40 const EXT_TEMPLATES_DIRNAME = 'templates';
41
42 /**
43 * @var CRM_Extension_Container_Interface
44 */
45 protected $container;
46
47 /**
51dda21e
SL
48 * @var \CRM_Extension_Info[]
49 * (key => CRM_Extension_Info)
6a488035 50 */
be2fb01f 51 protected $infos = [];
6a488035
TO
52
53 /**
54 * @var array
55 */
56 protected $moduleExtensions = NULL;
57
58 /**
59 * @var CRM_Utils_Cache_Interface
60 */
61 protected $cache;
62
63 protected $cacheKey;
64
65 protected $civicrmPath;
66
67 protected $civicrmUrl;
68
e0ef6999
EM
69 /**
70 * @param CRM_Extension_Container_Interface $container
71 * @param CRM_Utils_Cache_Interface $cache
72 * @param null $cacheKey
73 * @param null $civicrmPath
74 * @param null $civicrmUrl
75 */
6a488035
TO
76 public function __construct(CRM_Extension_Container_Interface $container, CRM_Utils_Cache_Interface $cache = NULL, $cacheKey = NULL, $civicrmPath = NULL, $civicrmUrl = NULL) {
77 $this->container = $container;
78 $this->cache = $cache;
79 $this->cacheKey = $cacheKey;
80 if ($civicrmUrl) {
81 $this->civicrmUrl = rtrim($civicrmUrl, '/');
0db6c3e1
TO
82 }
83 else {
6a488035
TO
84 $config = CRM_Core_Config::singleton();
85 $this->civicrmUrl = rtrim($config->resourceBase, '/');
86 }
87 if ($civicrmPath) {
b3a4b879 88 $this->civicrmPath = rtrim($civicrmPath, '/');
0db6c3e1
TO
89 }
90 else {
6a488035 91 global $civicrm_root;
b3a4b879 92 $this->civicrmPath = rtrim($civicrm_root, '/');
6a488035
TO
93 }
94 }
95
96 /**
97 * Given the class, provides extension's key.
98 *
6a488035 99 *
f41911fd
TO
100 * @param string $clazz
101 * Extension class name.
6a488035 102 *
a6c01b45
CW
103 * @return string
104 * name of extension key
6a488035
TO
105 */
106 public function classToKey($clazz) {
107 return str_replace('_', '.', $clazz);
108 }
109
110 /**
111 * Given the class, provides extension path.
112 *
6a488035 113 *
6c8f6e67
EM
114 * @param $clazz
115 *
a6c01b45
CW
116 * @return string
117 * full path the extension .php file
6a488035
TO
118 */
119 public function classToPath($clazz) {
120 $elements = explode('_', $clazz);
121 $key = implode('.', $elements);
122 return $this->keyToPath($key);
123 }
124
125 /**
126 * Given the string, returns true or false if it's an extension key.
127 *
6a488035 128 *
f41911fd
TO
129 * @param string $key
130 * A string which might be an extension key.
6a488035 131 *
e7483cbe 132 * @return bool
a6c01b45 133 * true if given string is an extension name
6a488035
TO
134 */
135 public function isExtensionKey($key) {
136 // check if the string is an extension name or the class
137 return (strpos($key, '.') !== FALSE) ? TRUE : FALSE;
138 }
139
140 /**
141 * Given the string, returns true or false if it's an extension class name.
142 *
6a488035 143 *
f41911fd
TO
144 * @param string $clazz
145 * A string which might be an extension class name.
6a488035 146 *
e7483cbe 147 * @return bool
a6c01b45 148 * true if given string is an extension class name
6a488035
TO
149 */
150 public function isExtensionClass($clazz) {
151
152 if (substr($clazz, 0, 4) != 'CRM_') {
153 return (bool) preg_match('/^[a-z0-9]+(_[a-z0-9]+)+$/', $clazz);
154 }
155 return FALSE;
156 }
157
158 /**
f41911fd
TO
159 * @param string $key
160 * Extension fully-qualified-name.
77b97be7
EM
161 * @param bool $fresh
162 *
163 * @throws CRM_Extension_Exception
164 * @throws Exception
c490a46a 165 * @return CRM_Extension_Info
6a488035
TO
166 */
167 public function keyToInfo($key, $fresh = FALSE) {
168 if ($fresh || !array_key_exists($key, $this->infos)) {
169 try {
170 $this->infos[$key] = CRM_Extension_Info::loadFromFile($this->container->getPath($key) . DIRECTORY_SEPARATOR . CRM_Extension_Info::FILENAME);
0db6c3e1
TO
171 }
172 catch (CRM_Extension_Exception $e) {
6a488035
TO
173 // file has more detailed info, but we'll fallback to DB if it's missing -- DB has enough info to uninstall
174 $this->infos[$key] = CRM_Extension_System::singleton()->getManager()->createInfoFromDB($key);
175 if (!$this->infos[$key]) {
176 throw $e;
177 }
178 }
179 }
180 return $this->infos[$key];
181 }
182
183 /**
184 * Given the key, provides extension's class name.
185 *
6a488035 186 *
f41911fd
TO
187 * @param string $key
188 * Extension key.
6a488035 189 *
a6c01b45
CW
190 * @return string
191 * name of extension's main class
6a488035
TO
192 */
193 public function keyToClass($key) {
194 return str_replace('.', '_', $key);
195 }
196
197 /**
198 * Given the key, provides the path to file containing
199 * extension's main class.
200 *
6a488035 201 *
f41911fd
TO
202 * @param string $key
203 * Extension key.
6a488035 204 *
a6c01b45
CW
205 * @return string
206 * path to file containing extension's main class
6a488035
TO
207 */
208 public function keyToPath($key) {
209 $info = $this->keyToInfo($key);
210 return $this->container->getPath($key) . DIRECTORY_SEPARATOR . $info->file . '.php';
211 }
212
213 /**
214 * Given the key, provides the path to file containing
215 * extension's main class.
216 *
f41911fd
TO
217 * @param string $key
218 * Extension key.
a6c01b45
CW
219 * @return string
220 * local path of the extension source tree
6a488035
TO
221 */
222 public function keyToBasePath($key) {
223 if ($key == 'civicrm') {
224 return $this->civicrmPath;
225 }
226 return $this->container->getPath($key);
227 }
228
229 /**
230 * Given the key, provides the path to file containing
231 * extension's main class.
232 *
6a488035 233 *
f41911fd
TO
234 * @param string $key
235 * Extension key.
6a488035 236 *
a6c01b45
CW
237 * @return string
238 * url for resources in this extension
6a488035
TO
239 */
240 public function keyToUrl($key) {
241 if ($key == 'civicrm') {
dee7a2b1
PJ
242 // CRM-12130 Workaround: If the domain's config_backend is NULL at the start of the request,
243 // then the Mapper is wrongly constructed with an empty value for $this->civicrmUrl.
3d4a4ccf
PJ
244 if (empty($this->civicrmUrl)) {
245 $config = CRM_Core_Config::singleton();
246 return rtrim($config->resourceBase, '/');
247 }
6a488035
TO
248 return $this->civicrmUrl;
249 }
250
251 return $this->container->getResUrl($key);
252 }
253
254 /**
255 * Fetch the list of active extensions of type 'module'
256 *
5a4f6742
CW
257 * @param bool $fresh
258 * whether to forcibly reload extensions list from canonical store.
a6c01b45
CW
259 * @return array
260 * array(array('prefix' => $, 'file' => $))
6a488035
TO
261 */
262 public function getActiveModuleFiles($fresh = FALSE) {
62f662b0 263 if (!defined('CIVICRM_DSN')) {
264 // hmm, ok
265 return [];
6a488035
TO
266 }
267
268 $moduleExtensions = NULL;
269 if ($this->cache && !$fresh) {
ec2dd0bd 270 $moduleExtensions = $this->cache->get($this->cacheKey . '_moduleFiles');
6a488035
TO
271 }
272
273 if (!is_array($moduleExtensions)) {
6542d699
TO
274 $compat = CRM_Extension_System::getCompatibilityInfo();
275
6a488035 276 // Check canonical module list
be2fb01f 277 $moduleExtensions = [];
6a488035
TO
278 $sql = '
279 SELECT full_name, file
280 FROM civicrm_extension
281 WHERE is_active = 1
282 AND type = "module"
283 ';
284 $dao = CRM_Core_DAO::executeQuery($sql);
285 while ($dao->fetch()) {
6542d699
TO
286 if (!empty($compat[$dao->full_name]['force-uninstall'])) {
287 continue;
288 }
6a488035 289 try {
be2fb01f 290 $moduleExtensions[] = [
6a488035
TO
291 'prefix' => $dao->file,
292 'filePath' => $this->keyToPath($dao->full_name),
be2fb01f 293 ];
0db6c3e1
TO
294 }
295 catch (CRM_Extension_Exception $e) {
6a488035
TO
296 // Putting a stub here provides more consistency
297 // in how getActiveModuleFiles when racing between
298 // dirty file-removals and cache-clears.
299 CRM_Core_Session::setStatus($e->getMessage(), '', 'error');
be2fb01f 300 $moduleExtensions[] = [
6a488035
TO
301 'prefix' => $dao->file,
302 'filePath' => NULL,
be2fb01f 303 ];
6a488035
TO
304 }
305 }
306
307 if ($this->cache) {
ec2dd0bd 308 $this->cache->set($this->cacheKey . '_moduleFiles', $moduleExtensions);
6a488035
TO
309 }
310 }
311 return $moduleExtensions;
312 }
313
e7ff7042 314 /**
fe482240 315 * Get a list of base URLs for all active modules.
e7ff7042 316 *
a6c01b45
CW
317 * @return array
318 * (string $extKey => string $baseUrl)
e7ff7042
TO
319 */
320 public function getActiveModuleUrls() {
321 // TODO optimization/caching
be2fb01f 322 $urls = [];
e7ff7042
TO
323 $urls['civicrm'] = $this->keyToUrl('civicrm');
324 foreach ($this->getModules() as $module) {
325 /** @var $module CRM_Core_Module */
326 if ($module->is_active) {
327 $urls[$module->name] = $this->keyToUrl($module->name);
328 }
329 }
330 return $urls;
331 }
332
3b3f6d23
TO
333 /**
334 * Get a list of extension keys, filtered by the corresponding file path.
335 *
336 * @param string $pattern
337 * A file path. To search subdirectories, append "*".
338 * Ex: "/var/www/extensions/*"
339 * Ex: "/var/www/extensions/org.foo.bar"
340 * @return array
341 * Array(string $key).
342 * Ex: array("org.foo.bar").
343 */
344 public function getKeysByPath($pattern) {
be2fb01f 345 $keys = [];
3b3f6d23
TO
346
347 if (CRM_Utils_String::endsWith($pattern, '*')) {
348 $prefix = rtrim($pattern, '*');
349 foreach ($this->container->getKeys() as $key) {
350 $path = CRM_Utils_File::addTrailingSlash($this->container->getPath($key));
351 if (realpath($prefix) == realpath($path) || CRM_Utils_File::isChildPath($prefix, $path)) {
352 $keys[] = $key;
353 }
354 }
355 }
356 else {
357 foreach ($this->container->getKeys() as $key) {
358 $path = CRM_Utils_File::addTrailingSlash($this->container->getPath($key));
359 if (realpath($pattern) == realpath($path)) {
360 $keys[] = $key;
361 }
362 }
363 }
364
365 return $keys;
366 }
367
f8a7cfff
TO
368 /**
369 * @return array
370 * Ex: $result['org.civicrm.foobar'] = new CRM_Extension_Info(...).
371 * @throws \CRM_Extension_Exception
372 * @throws \Exception
373 */
374 public function getAllInfos() {
375 foreach ($this->container->getKeys() as $key) {
376 $this->keyToInfo($key);
377 }
378 return $this->infos;
379 }
380
e0ef6999 381 /**
100fef9d 382 * @param string $name
e0ef6999
EM
383 *
384 * @return bool
385 */
6a488035
TO
386 public function isActiveModule($name) {
387 $activeModules = $this->getActiveModuleFiles();
388 foreach ($activeModules as $activeModule) {
389 if ($activeModule['prefix'] == $name) {
390 return TRUE;
391 }
392 }
393 return FALSE;
394 }
395
396 /**
397 * Get a list of all installed modules, including enabled and disabled ones
398 *
a6c01b45
CW
399 * @return array
400 * CRM_Core_Module
6a488035
TO
401 */
402 public function getModules() {
be2fb01f 403 $result = [];
6a488035
TO
404 $dao = new CRM_Core_DAO_Extension();
405 $dao->type = 'module';
406 $dao->find();
407 while ($dao->fetch()) {
408 $result[] = new CRM_Core_Module($dao->full_name, $dao->is_active);
409 }
410 return $result;
411 }
412
413 /**
414 * Given the class, provides the template path.
415 *
6a488035 416 *
f41911fd
TO
417 * @param string $clazz
418 * Extension class name.
6a488035 419 *
a6c01b45
CW
420 * @return string
421 * path to extension's templates directory
6a488035
TO
422 */
423 public function getTemplatePath($clazz) {
424 $path = $this->container->getPath($this->classToKey($clazz));
425 return $path . DIRECTORY_SEPARATOR . self::EXT_TEMPLATES_DIRNAME;
426 /*
427 $path = $this->classToPath($clazz);
428 $pathElm = explode(DIRECTORY_SEPARATOR, $path);
429 array_pop($pathElm);
430 return implode(DIRECTORY_SEPARATOR, $pathElm) . DIRECTORY_SEPARATOR . self::EXT_TEMPLATES_DIRNAME;
e70a7fc0 431 */
6a488035
TO
432 }
433
434 /**
435 * Given te class, provides the template name.
436 * @todo consider multiple templates, support for one template for now
437 *
6a488035 438 *
f41911fd
TO
439 * @param string $clazz
440 * Extension class name.
6a488035 441 *
a6c01b45
CW
442 * @return string
443 * extension's template name
6a488035
TO
444 */
445 public function getTemplateName($clazz) {
446 $info = $this->keyToInfo($this->classToKey($clazz));
447 return (string) $info->file . '.tpl';
448 }
449
450 public function refresh() {
be2fb01f 451 $this->infos = [];
6a488035
TO
452 $this->moduleExtensions = NULL;
453 if ($this->cache) {
ec2dd0bd 454 $this->cache->delete($this->cacheKey . '_moduleFiles');
6a488035 455 }
85c7eb67
TO
456 // FIXME: How can code so code wrong be so right?
457 CRM_Extension_System::singleton()->getClassLoader()->refresh();
6a488035 458 }
96025800 459
6a488035 460}