3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2018 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
29 * This class proivdes various helper functions for locating extensions
30 * data. It's designed for compatibility with pre-existing functions from
31 * CRM_Core_Extensions.
33 * Most of these helper functions originate with the first major iteration
34 * of extensions -- a time when every extension had one eponymous PHP class,
35 * when there was no PHP class-loader, and when there was special-case logic
36 * sprinkled around to handle loading of "extension classes".
38 * With module-extensions (Civi 4.2+), there are no eponymous classes --
39 * instead, module-extensions follow the same class-naming and class-loading
40 * practices as core (and don't require special-case logic for class
41 * loading). Consequently, the helpers in here aren't much used with
45 * @copyright CiviCRM LLC (c) 2004-2018
47 class CRM_Extension_Mapper
{
50 * An URL for public extensions repository.
54 * Extension info file name.
56 const EXT_TEMPLATES_DIRNAME
= 'templates';
59 * @var CRM_Extension_Container_Interface
64 * @var array (key => CRM_Extension_Info)
66 protected $infos = array();
71 protected $moduleExtensions = NULL;
74 * @var CRM_Utils_Cache_Interface
80 protected $civicrmPath;
82 protected $civicrmUrl;
85 * @param CRM_Extension_Container_Interface $container
86 * @param CRM_Utils_Cache_Interface $cache
87 * @param null $cacheKey
88 * @param null $civicrmPath
89 * @param null $civicrmUrl
91 public function __construct(CRM_Extension_Container_Interface
$container, CRM_Utils_Cache_Interface
$cache = NULL, $cacheKey = NULL, $civicrmPath = NULL, $civicrmUrl = NULL) {
92 $this->container
= $container;
93 $this->cache
= $cache;
94 $this->cacheKey
= $cacheKey;
96 $this->civicrmUrl
= rtrim($civicrmUrl, '/');
99 $config = CRM_Core_Config
::singleton();
100 $this->civicrmUrl
= rtrim($config->resourceBase
, '/');
103 $this->civicrmPath
= rtrim($civicrmPath, '/');
106 global $civicrm_root;
107 $this->civicrmPath
= rtrim($civicrm_root, '/');
112 * Given the class, provides extension's key.
115 * @param string $clazz
116 * Extension class name.
119 * name of extension key
121 public function classToKey($clazz) {
122 return str_replace('_', '.', $clazz);
126 * Given the class, provides extension path.
132 * full path the extension .php file
134 public function classToPath($clazz) {
135 $elements = explode('_', $clazz);
136 $key = implode('.', $elements);
137 return $this->keyToPath($key);
141 * Given the string, returns true or false if it's an extension key.
145 * A string which might be an extension key.
148 * true if given string is an extension name
150 public function isExtensionKey($key) {
151 // check if the string is an extension name or the class
152 return (strpos($key, '.') !== FALSE) ?
TRUE : FALSE;
156 * Given the string, returns true or false if it's an extension class name.
159 * @param string $clazz
160 * A string which might be an extension class name.
163 * true if given string is an extension class name
165 public function isExtensionClass($clazz) {
167 if (substr($clazz, 0, 4) != 'CRM_') {
168 return (bool) preg_match('/^[a-z0-9]+(_[a-z0-9]+)+$/', $clazz);
175 * Extension fully-qualified-name.
178 * @throws CRM_Extension_Exception
180 * @return CRM_Extension_Info
182 public function keyToInfo($key, $fresh = FALSE) {
183 if ($fresh ||
!array_key_exists($key, $this->infos
)) {
185 $this->infos
[$key] = CRM_Extension_Info
::loadFromFile($this->container
->getPath($key) . DIRECTORY_SEPARATOR
. CRM_Extension_Info
::FILENAME
);
187 catch (CRM_Extension_Exception
$e) {
188 // file has more detailed info, but we'll fallback to DB if it's missing -- DB has enough info to uninstall
189 $this->infos
[$key] = CRM_Extension_System
::singleton()->getManager()->createInfoFromDB($key);
190 if (!$this->infos
[$key]) {
195 return $this->infos
[$key];
199 * Given the key, provides extension's class name.
206 * name of extension's main class
208 public function keyToClass($key) {
209 return str_replace('.', '_', $key);
213 * Given the key, provides the path to file containing
214 * extension's main class.
221 * path to file containing extension's main class
223 public function keyToPath($key) {
224 $info = $this->keyToInfo($key);
225 return $this->container
->getPath($key) . DIRECTORY_SEPARATOR
. $info->file
. '.php';
229 * Given the key, provides the path to file containing
230 * extension's main class.
235 * local path of the extension source tree
237 public function keyToBasePath($key) {
238 if ($key == 'civicrm') {
239 return $this->civicrmPath
;
241 return $this->container
->getPath($key);
245 * Given the key, provides the path to file containing
246 * extension's main class.
253 * url for resources in this extension
255 public function keyToUrl($key) {
256 if ($key == 'civicrm') {
257 // CRM-12130 Workaround: If the domain's config_backend is NULL at the start of the request,
258 // then the Mapper is wrongly constructed with an empty value for $this->civicrmUrl.
259 if (empty($this->civicrmUrl
)) {
260 $config = CRM_Core_Config
::singleton();
261 return rtrim($config->resourceBase
, '/');
263 return $this->civicrmUrl
;
266 return $this->container
->getResUrl($key);
270 * Fetch the list of active extensions of type 'module'
273 * whether to forcibly reload extensions list from canonical store.
275 * array(array('prefix' => $, 'file' => $))
277 public function getActiveModuleFiles($fresh = FALSE) {
278 $config = CRM_Core_Config
::singleton();
279 if ($config->isUpgradeMode() ||
!defined('CIVICRM_DSN')) {
280 return array(); // hmm, ok
283 $moduleExtensions = NULL;
284 if ($this->cache
&& !$fresh) {
285 $moduleExtensions = $this->cache
->get($this->cacheKey
. '/moduleFiles');
288 if (!is_array($moduleExtensions)) {
289 // Check canonical module list
290 $moduleExtensions = array();
292 SELECT full_name, file
293 FROM civicrm_extension
297 $dao = CRM_Core_DAO
::executeQuery($sql);
298 while ($dao->fetch()) {
300 $moduleExtensions[] = array(
301 'prefix' => $dao->file
,
302 'filePath' => $this->keyToPath($dao->full_name
),
305 catch (CRM_Extension_Exception
$e) {
306 // Putting a stub here provides more consistency
307 // in how getActiveModuleFiles when racing between
308 // dirty file-removals and cache-clears.
309 CRM_Core_Session
::setStatus($e->getMessage(), '', 'error');
310 $moduleExtensions[] = array(
311 'prefix' => $dao->file
,
318 $this->cache
->set($this->cacheKey
. '/moduleFiles', $moduleExtensions);
321 return $moduleExtensions;
325 * Get a list of base URLs for all active modules.
328 * (string $extKey => string $baseUrl)
330 public function getActiveModuleUrls() {
331 // TODO optimization/caching
333 $urls['civicrm'] = $this->keyToUrl('civicrm');
334 foreach ($this->getModules() as $module) {
335 /** @var $module CRM_Core_Module */
336 if ($module->is_active
) {
337 $urls[$module->name
] = $this->keyToUrl($module->name
);
344 * Get a list of extension keys, filtered by the corresponding file path.
346 * @param string $pattern
347 * A file path. To search subdirectories, append "*".
348 * Ex: "/var/www/extensions/*"
349 * Ex: "/var/www/extensions/org.foo.bar"
351 * Array(string $key).
352 * Ex: array("org.foo.bar").
354 public function getKeysByPath($pattern) {
357 if (CRM_Utils_String
::endsWith($pattern, '*')) {
358 $prefix = rtrim($pattern, '*');
359 foreach ($this->container
->getKeys() as $key) {
360 $path = CRM_Utils_File
::addTrailingSlash($this->container
->getPath($key));
361 if (realpath($prefix) == realpath($path) || CRM_Utils_File
::isChildPath($prefix, $path)) {
367 foreach ($this->container
->getKeys() as $key) {
368 $path = CRM_Utils_File
::addTrailingSlash($this->container
->getPath($key));
369 if (realpath($pattern) == realpath($path)) {
380 * Ex: $result['org.civicrm.foobar'] = new CRM_Extension_Info(...).
381 * @throws \CRM_Extension_Exception
384 public function getAllInfos() {
385 foreach ($this->container
->getKeys() as $key) {
386 $this->keyToInfo($key);
392 * @param string $name
396 public function isActiveModule($name) {
397 $activeModules = $this->getActiveModuleFiles();
398 foreach ($activeModules as $activeModule) {
399 if ($activeModule['prefix'] == $name) {
407 * Get a list of all installed modules, including enabled and disabled ones
412 public function getModules() {
414 $dao = new CRM_Core_DAO_Extension();
415 $dao->type
= 'module';
417 while ($dao->fetch()) {
418 $result[] = new CRM_Core_Module($dao->full_name
, $dao->is_active
);
424 * Given the class, provides the template path.
427 * @param string $clazz
428 * Extension class name.
431 * path to extension's templates directory
433 public function getTemplatePath($clazz) {
434 $path = $this->container
->getPath($this->classToKey($clazz));
435 return $path . DIRECTORY_SEPARATOR
. self
::EXT_TEMPLATES_DIRNAME
;
437 $path = $this->classToPath($clazz);
438 $pathElm = explode(DIRECTORY_SEPARATOR, $path);
440 return implode(DIRECTORY_SEPARATOR, $pathElm) . DIRECTORY_SEPARATOR . self::EXT_TEMPLATES_DIRNAME;
445 * Given te class, provides the template name.
446 * @todo consider multiple templates, support for one template for now
449 * @param string $clazz
450 * Extension class name.
453 * extension's template name
455 public function getTemplateName($clazz) {
456 $info = $this->keyToInfo($this->classToKey($clazz));
457 return (string) $info->file
. '.tpl';
460 public function refresh() {
461 $this->infos
= array();
462 $this->moduleExtensions
= NULL;
464 $this->cache
->delete($this->cacheKey
. '/moduleFiles');
466 // FIXME: How can code so code wrong be so right?
467 CRM_Extension_System
::singleton()->getClassLoader()->refresh();