drupal-civicrm/sites/all/modules/civicrm/CRM/Extension/Mapper.php
2018-01-14 13:10:16 +00:00

471 lines
13 KiB
PHP

<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.7 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
/**
* This class proivdes various helper functions for locating extensions
* data. It's designed for compatibility with pre-existing functions from
* CRM_Core_Extensions.
*
* Most of these helper functions originate with the first major iteration
* of extensions -- a time when every extension had one eponymous PHP class,
* when there was no PHP class-loader, and when there was special-case logic
* sprinkled around to handle loading of "extension classes".
*
* With module-extensions (Civi 4.2+), there are no eponymous classes --
* instead, module-extensions follow the same class-naming and class-loading
* practices as core (and don't require special-case logic for class
* loading). Consequently, the helpers in here aren't much used with
* module-extensions.
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
class CRM_Extension_Mapper {
/**
* An URL for public extensions repository.
*/
/**
* Extension info file name.
*/
const EXT_TEMPLATES_DIRNAME = 'templates';
/**
* @var CRM_Extension_Container_Interface
*/
protected $container;
/**
* @var array (key => CRM_Extension_Info)
*/
protected $infos = array();
/**
* @var array
*/
protected $moduleExtensions = NULL;
/**
* @var CRM_Utils_Cache_Interface
*/
protected $cache;
protected $cacheKey;
protected $civicrmPath;
protected $civicrmUrl;
/**
* @param CRM_Extension_Container_Interface $container
* @param CRM_Utils_Cache_Interface $cache
* @param null $cacheKey
* @param null $civicrmPath
* @param null $civicrmUrl
*/
public function __construct(CRM_Extension_Container_Interface $container, CRM_Utils_Cache_Interface $cache = NULL, $cacheKey = NULL, $civicrmPath = NULL, $civicrmUrl = NULL) {
$this->container = $container;
$this->cache = $cache;
$this->cacheKey = $cacheKey;
if ($civicrmUrl) {
$this->civicrmUrl = rtrim($civicrmUrl, '/');
}
else {
$config = CRM_Core_Config::singleton();
$this->civicrmUrl = rtrim($config->resourceBase, '/');
}
if ($civicrmPath) {
$this->civicrmPath = rtrim($civicrmPath, '/');
}
else {
global $civicrm_root;
$this->civicrmPath = rtrim($civicrm_root, '/');
}
}
/**
* Given the class, provides extension's key.
*
*
* @param string $clazz
* Extension class name.
*
* @return string
* name of extension key
*/
public function classToKey($clazz) {
return str_replace('_', '.', $clazz);
}
/**
* Given the class, provides extension path.
*
*
* @param $clazz
*
* @return string
* full path the extension .php file
*/
public function classToPath($clazz) {
$elements = explode('_', $clazz);
$key = implode('.', $elements);
return $this->keyToPath($key);
}
/**
* Given the string, returns true or false if it's an extension key.
*
*
* @param string $key
* A string which might be an extension key.
*
* @return bool
* true if given string is an extension name
*/
public function isExtensionKey($key) {
// check if the string is an extension name or the class
return (strpos($key, '.') !== FALSE) ? TRUE : FALSE;
}
/**
* Given the string, returns true or false if it's an extension class name.
*
*
* @param string $clazz
* A string which might be an extension class name.
*
* @return bool
* true if given string is an extension class name
*/
public function isExtensionClass($clazz) {
if (substr($clazz, 0, 4) != 'CRM_') {
return (bool) preg_match('/^[a-z0-9]+(_[a-z0-9]+)+$/', $clazz);
}
return FALSE;
}
/**
* @param string $key
* Extension fully-qualified-name.
* @param bool $fresh
*
* @throws CRM_Extension_Exception
* @throws Exception
* @return CRM_Extension_Info
*/
public function keyToInfo($key, $fresh = FALSE) {
if ($fresh || !array_key_exists($key, $this->infos)) {
try {
$this->infos[$key] = CRM_Extension_Info::loadFromFile($this->container->getPath($key) . DIRECTORY_SEPARATOR . CRM_Extension_Info::FILENAME);
}
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]) {
throw $e;
}
}
}
return $this->infos[$key];
}
/**
* Given the key, provides extension's class name.
*
*
* @param string $key
* Extension key.
*
* @return string
* name of extension's main class
*/
public function keyToClass($key) {
return str_replace('.', '_', $key);
}
/**
* Given the key, provides the path to file containing
* extension's main class.
*
*
* @param string $key
* Extension key.
*
* @return string
* path to file containing extension's main class
*/
public function keyToPath($key) {
$info = $this->keyToInfo($key);
return $this->container->getPath($key) . DIRECTORY_SEPARATOR . $info->file . '.php';
}
/**
* Given the key, provides the path to file containing
* extension's main class.
*
* @param string $key
* Extension key.
* @return string
* local path of the extension source tree
*/
public function keyToBasePath($key) {
if ($key == 'civicrm') {
return $this->civicrmPath;
}
return $this->container->getPath($key);
}
/**
* Given the key, provides the path to file containing
* extension's main class.
*
*
* @param string $key
* Extension key.
*
* @return string
* url for resources in this extension
*/
public function keyToUrl($key) {
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)) {
$config = CRM_Core_Config::singleton();
return rtrim($config->resourceBase, '/');
}
return $this->civicrmUrl;
}
return $this->container->getResUrl($key);
}
/**
* Fetch the list of active extensions of type 'module'
*
* @param bool $fresh
* whether to forcibly reload extensions list from canonical store.
* @return array
* array(array('prefix' => $, 'file' => $))
*/
public function getActiveModuleFiles($fresh = FALSE) {
$config = CRM_Core_Config::singleton();
if ($config->isUpgradeMode() || !defined('CIVICRM_DSN')) {
return array(); // hmm, ok
}
$moduleExtensions = NULL;
if ($this->cache && !$fresh) {
$moduleExtensions = $this->cache->get($this->cacheKey . '/moduleFiles');
}
if (!is_array($moduleExtensions)) {
// Check canonical module list
$moduleExtensions = array();
$sql = '
SELECT full_name, file
FROM civicrm_extension
WHERE is_active = 1
AND type = "module"
';
$dao = CRM_Core_DAO::executeQuery($sql);
while ($dao->fetch()) {
try {
$moduleExtensions[] = array(
'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[] = array(
'prefix' => $dao->file,
'filePath' => NULL,
);
}
}
if ($this->cache) {
$this->cache->set($this->cacheKey . '/moduleFiles', $moduleExtensions);
}
}
return $moduleExtensions;
}
/**
* Get a list of base URLs for all active modules.
*
* @return array
* (string $extKey => string $baseUrl)
*/
public function getActiveModuleUrls() {
// TODO optimization/caching
$urls = array();
$urls['civicrm'] = $this->keyToUrl('civicrm');
foreach ($this->getModules() as $module) {
/** @var $module CRM_Core_Module */
if ($module->is_active) {
$urls[$module->name] = $this->keyToUrl($module->name);
}
}
return $urls;
}
/**
* Get a list of extension keys, filtered by the corresponding file path.
*
* @param string $pattern
* A file path. To search subdirectories, append "*".
* Ex: "/var/www/extensions/*"
* Ex: "/var/www/extensions/org.foo.bar"
* @return array
* Array(string $key).
* Ex: array("org.foo.bar").
*/
public function getKeysByPath($pattern) {
$keys = array();
if (CRM_Utils_String::endsWith($pattern, '*')) {
$prefix = rtrim($pattern, '*');
foreach ($this->container->getKeys() as $key) {
$path = CRM_Utils_File::addTrailingSlash($this->container->getPath($key));
if (realpath($prefix) == realpath($path) || CRM_Utils_File::isChildPath($prefix, $path)) {
$keys[] = $key;
}
}
}
else {
foreach ($this->container->getKeys() as $key) {
$path = CRM_Utils_File::addTrailingSlash($this->container->getPath($key));
if (realpath($pattern) == realpath($path)) {
$keys[] = $key;
}
}
}
return $keys;
}
/**
* @return array
* Ex: $result['org.civicrm.foobar'] = new CRM_Extension_Info(...).
* @throws \CRM_Extension_Exception
* @throws \Exception
*/
public function getAllInfos() {
foreach ($this->container->getKeys() as $key) {
$this->keyToInfo($key);
}
return $this->infos;
}
/**
* @param string $name
*
* @return bool
*/
public function isActiveModule($name) {
$activeModules = $this->getActiveModuleFiles();
foreach ($activeModules as $activeModule) {
if ($activeModule['prefix'] == $name) {
return TRUE;
}
}
return FALSE;
}
/**
* Get a list of all installed modules, including enabled and disabled ones
*
* @return array
* CRM_Core_Module
*/
public function getModules() {
$result = array();
$dao = new CRM_Core_DAO_Extension();
$dao->type = 'module';
$dao->find();
while ($dao->fetch()) {
$result[] = new CRM_Core_Module($dao->full_name, $dao->is_active);
}
return $result;
}
/**
* Given the class, provides the template path.
*
*
* @param string $clazz
* Extension class name.
*
* @return string
* path to extension's templates directory
*/
public function getTemplatePath($clazz) {
$path = $this->container->getPath($this->classToKey($clazz));
return $path . DIRECTORY_SEPARATOR . self::EXT_TEMPLATES_DIRNAME;
/*
$path = $this->classToPath($clazz);
$pathElm = explode(DIRECTORY_SEPARATOR, $path);
array_pop($pathElm);
return implode(DIRECTORY_SEPARATOR, $pathElm) . DIRECTORY_SEPARATOR . self::EXT_TEMPLATES_DIRNAME;
*/
}
/**
* Given te class, provides the template name.
* @todo consider multiple templates, support for one template for now
*
*
* @param string $clazz
* Extension class name.
*
* @return string
* extension's template name
*/
public function getTemplateName($clazz) {
$info = $this->keyToInfo($this->classToKey($clazz));
return (string) $info->file . '.tpl';
}
public function refresh() {
$this->infos = array();
$this->moduleExtensions = NULL;
if ($this->cache) {
$this->cache->delete($this->cacheKey . '/moduleFiles');
}
// FIXME: How can code so code wrong be so right?
CRM_Extension_System::singleton()->getClassLoader()->refresh();
}
}