* @param bool $fresh
*
* @throws CRM_Extension_Exception
- * @throws Exception
+ *
* @return CRM_Extension_Info
*/
public function keyToInfo($key, $fresh = FALSE) {
}
catch (CRM_Extension_Exception $e) {
// file has more detailed info, but we'll fallback to DB if it's missing -- DB has enough info to uninstall
- $this->infos[$key] = CRM_Extension_System::singleton()->getManager()->createInfoFromDB($key);
- if (!$this->infos[$key]) {
+ $dbInfo = CRM_Extension_System::singleton()->getManager()->createInfoFromDB($key);
+ if (!$dbInfo) {
throw $e;
}
+ $this->infos[$key] = $dbInfo;
}
}
return $this->infos[$key];
*
* @return string
* url for resources in this extension
+ *
+ * @throws \CRM_Extension_Exception_MissingException
*/
public function keyToUrl($key) {
- if ($key == 'civicrm') {
+ if ($key === 'civicrm') {
// CRM-12130 Workaround: If the domain's config_backend is NULL at the start of the request,
// then the Mapper is wrongly constructed with an empty value for $this->civicrmUrl.
if (empty($this->civicrmUrl)) {
* @param bool $fresh
* whether to forcibly reload extensions list from canonical store.
* @return array
- * array(array('prefix' => $, 'file' => $))
+ * array(array('prefix' => $, 'fullName' => $, 'filePath' => $))
*/
public function getActiveModuleFiles($fresh = FALSE) {
if (!defined('CIVICRM_DSN')) {
return [];
}
+ // The list of module files is cached in two tiers. The tiers are slightly
+ // different:
+ //
+ // 1. The persistent tier (cache) stores
+ // names WITHOUT absolute paths.
+ // 2. The ephemeral/thread-local tier (statics) stores names
+ // WITH absolute paths.
+ // Return static value instead of re-running query
+ if (isset(Civi::$statics[__CLASS__]['moduleExtensions']) && !$fresh) {
+ return Civi::$statics[__CLASS__]['moduleExtensions'];
+ }
+
$moduleExtensions = NULL;
+
+ // Checked if it's stored in the persistent cache.
if ($this->cache && !$fresh) {
$moduleExtensions = $this->cache->get($this->cacheKey . '_moduleFiles');
}
+ // If cache is empty we build it from database.
if (!is_array($moduleExtensions)) {
$compat = CRM_Extension_System::getCompatibilityInfo();
if (!empty($compat[$dao->full_name]['force-uninstall'])) {
continue;
}
- try {
- $moduleExtensions[] = [
- 'prefix' => $dao->file,
- 'filePath' => $this->keyToPath($dao->full_name),
- ];
- }
- catch (CRM_Extension_Exception $e) {
- // Putting a stub here provides more consistency
- // in how getActiveModuleFiles when racing between
- // dirty file-removals and cache-clears.
- CRM_Core_Session::setStatus($e->getMessage(), '', 'error');
- $moduleExtensions[] = [
- 'prefix' => $dao->file,
- 'filePath' => NULL,
- ];
- }
+ $moduleExtensions[] = [
+ 'prefix' => $dao->file,
+ 'fullName' => $dao->full_name,
+ 'filePath' => NULL,
+ ];
}
if ($this->cache) {
$this->cache->set($this->cacheKey . '_moduleFiles', $moduleExtensions);
}
}
+
+ // Since we're not caching the full path we add it now.
+ array_walk($moduleExtensions, function(&$value, $key) {
+ try {
+ if (!$value['filePath']) {
+ $value['filePath'] = $this->keyToPath($value['fullName']);
+ }
+ }
+ catch (CRM_Extension_Exception $e) {
+ // Putting a stub here provides more consistency
+ // in how getActiveModuleFiles when racing between
+ // dirty file-removals and cache-clears.
+ CRM_Core_Session::setStatus($e->getMessage(), '', 'error');
+ $value['filePath'] = NULL;
+ }
+ });
+
+ Civi::$statics[__CLASS__]['moduleExtensions'] = $moduleExtensions;
+
return $moduleExtensions;
}
*
* @return array
* (string $extKey => string $baseUrl)
+ *
+ * @throws \CRM_Extension_Exception_MissingException
*/
public function getActiveModuleUrls() {
// TODO optimization/caching
foreach ($this->getModules() as $module) {
/** @var $module CRM_Core_Module */
if ($module->is_active) {
- $urls[$module->name] = $this->keyToUrl($module->name);
+ try {
+ $urls[$module->name] = $this->keyToUrl($module->name);
+ }
+ catch (CRM_Extension_Exception_MissingException $e) {
+ CRM_Core_Session::setStatus(ts('An enabled extension is missing from the extensions directory') . ':' . $module->name);
+ }
}
}
return $urls;
return $keys;
}
+ /**
+ * Get a list of extensions which match a given tag.
+ *
+ * @param string $tag
+ * Ex: 'foo'
+ * @return array
+ * Array(string $key).
+ * Ex: array("org.foo.bar").
+ */
+ public function getKeysByTag($tag) {
+ $allTags = $this->getAllTags();
+ return $allTags[$tag] ?? [];
+ }
+
+ /**
+ * Get a list of extension tags.
+ *
+ * @return array
+ * Ex: ['form-building' => ['org.civicrm.afform-gui', 'org.civicrm.afform-html']]
+ */
+ public function getAllTags() {
+ $tags = Civi::cache('short')->get('extension_tags', NULL);
+ if ($tags !== NULL) {
+ return $tags;
+ }
+
+ $tags = [];
+ $allInfos = $this->getAllInfos();
+ foreach ($allInfos as $key => $info) {
+ foreach ($info->tags as $tag) {
+ $tags[$tag][] = $key;
+ }
+ }
+ return $tags;
+ }
+
/**
* @return array
* Ex: $result['org.civicrm.foobar'] = new CRM_Extension_Info(...).
*/
public function getAllInfos() {
foreach ($this->container->getKeys() as $key) {
- $this->keyToInfo($key);
+ try {
+ $this->keyToInfo($key);
+ }
+ catch (CRM_Extension_Exception_ParseException $e) {
+ CRM_Core_Session::setStatus(ts('Parse error in extension: %1', [
+ 1 => $e->getMessage(),
+ ]), '', 'error');
+ CRM_Core_Error::debug_log_message("Parse error in extension: " . $e->getMessage());
+ continue;
+ }
}
return $this->infos;
}