471 lines
13 KiB
PHP
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();
|
||
|
}
|
||
|
|
||
|
}
|