First commit

This commit is contained in:
Theodotos Andreou 2018-01-14 13:10:16 +00:00
commit c6e2478c40
13918 changed files with 2303184 additions and 0 deletions

View file

@ -0,0 +1,285 @@
<?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 glues together the various parts of the extension
* system.
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
class CRM_Extension_Browser {
/**
* An URL for public extensions repository.
*
* Note: This default is now handled through setting/*.php.
*
* @deprecated
*/
const DEFAULT_EXTENSIONS_REPOSITORY = 'https://civicrm.org/extdir/ver={ver}|cms={uf}';
/**
* Relative path below remote repository URL for single extensions file.
*/
const SINGLE_FILE_PATH = '/single';
/**
* The name of the single JSON extension cache file.
*/
const CACHE_JSON_FILE = 'extensions.json';
// timeout for when the connection or the server is slow
const CHECK_TIMEOUT = 5;
/**
* @param string $repoUrl
* URL of the remote repository.
* @param string $indexPath
* Relative path of the 'index' file within the repository.
* @param string $cacheDir
* Local path in which to cache files.
*/
public function __construct($repoUrl, $indexPath, $cacheDir) {
$this->repoUrl = $repoUrl;
$this->cacheDir = $cacheDir;
$this->indexPath = empty($indexPath) ? self::SINGLE_FILE_PATH : $indexPath;
if ($cacheDir && !file_exists($cacheDir) && is_dir(dirname($cacheDir)) && is_writable(dirname($cacheDir))) {
CRM_Utils_File::createDir($cacheDir, FALSE);
}
}
/**
* Determine whether the system policy allows downloading new extensions.
*
* This is reflection of *policy* and *intent*; it does not indicate whether
* the browser will actually *work*. For that, see checkRequirements().
*
* @return bool
*/
public function isEnabled() {
return (FALSE !== $this->getRepositoryUrl());
}
/**
* @return string
*/
public function getRepositoryUrl() {
return $this->repoUrl;
}
/**
* Refresh the cache of remotely-available extensions.
*/
public function refresh() {
$file = $this->getTsPath();
if (file_exists($file)) {
unlink($file);
}
}
/**
* Determine whether downloading is supported.
*
* @return array
* List of error messages; empty if OK.
*/
public function checkRequirements() {
if (!$this->isEnabled()) {
return array();
}
$errors = array();
if (!$this->cacheDir || !is_dir($this->cacheDir) || !is_writable($this->cacheDir)) {
$civicrmDestination = urlencode(CRM_Utils_System::url('civicrm/admin/extensions', 'reset=1'));
$url = CRM_Utils_System::url('civicrm/admin/setting/path', "reset=1&civicrmDestination=${civicrmDestination}");
$errors[] = array(
'title' => ts('Directory Unwritable'),
'message' => ts('Your extensions cache directory (%1) is not web server writable. Please go to the <a href="%2">path setting page</a> and correct it.<br/>',
array(
1 => $this->cacheDir,
2 => $url,
)
),
);
}
return $errors;
}
/**
* Get a list of all available extensions.
*
* @return array
* ($key => CRM_Extension_Info)
*/
public function getExtensions() {
if (!$this->isEnabled() || count($this->checkRequirements())) {
return array();
}
$exts = array();
$remote = $this->_discoverRemote();
if (is_array($remote)) {
foreach ($remote as $dc => $e) {
$exts[$e->key] = $e;
}
}
return $exts;
}
/**
* Get a description of a particular extension.
*
* @param string $key
* Fully-qualified extension name.
*
* @return CRM_Extension_Info|NULL
*/
public function getExtension($key) {
// TODO optimize performance -- we don't need to fetch/cache the entire repo
$exts = $this->getExtensions();
if (array_key_exists($key, $exts)) {
return $exts[$key];
}
else {
return NULL;
}
}
/**
* @return array
* @throws CRM_Extension_Exception_ParseException
*/
private function _discoverRemote() {
$tsPath = $this->getTsPath();
$timestamp = FALSE;
if (file_exists($tsPath)) {
$timestamp = file_get_contents($tsPath);
}
// 3 minutes ago for now
$outdated = (int) $timestamp < (time() - 180) ? TRUE : FALSE;
if (!$timestamp || $outdated) {
$remotes = json_decode($this->grabRemoteJson(), TRUE);
}
else {
$remotes = json_decode($this->grabCachedJson(), TRUE);
}
$this->_remotesDiscovered = array();
foreach ((array) $remotes as $id => $xml) {
$ext = CRM_Extension_Info::loadFromString($xml);
$this->_remotesDiscovered[] = $ext;
}
if (file_exists(dirname($tsPath))) {
file_put_contents($tsPath, (string) time());
}
return $this->_remotesDiscovered;
}
/**
* Loads the extensions data from the cache file. If it is empty
* or doesn't exist, try fetching from remote instead.
*
* @return string
*/
private function grabCachedJson() {
$filename = $this->cacheDir . DIRECTORY_SEPARATOR . self::CACHE_JSON_FILE . '.' . md5($this->getRepositoryUrl());
$json = NULL;
if (file_exists($filename)) {
$json = file_get_contents($filename);
}
if (empty($json)) {
$json = $this->grabRemoteJson();
}
return $json;
}
/**
* Connects to public server and grabs the list of publicly available
* extensions.
*
* @return string
* @throws \CRM_Extension_Exception
*/
private function grabRemoteJson() {
ini_set('default_socket_timeout', self::CHECK_TIMEOUT);
set_error_handler(array('CRM_Extension_Browser', 'downloadError'));
if (!ini_get('allow_url_fopen')) {
ini_set('allow_url_fopen', 1);
}
if (FALSE === $this->getRepositoryUrl()) {
// don't check if the user has configured civi not to check an external
// url for extensions. See CRM-10575.
return array();
}
$filename = $this->cacheDir . DIRECTORY_SEPARATOR . self::CACHE_JSON_FILE . '.' . md5($this->getRepositoryUrl());
$url = $this->getRepositoryUrl() . $this->indexPath;
$status = CRM_Utils_HttpClient::singleton()->fetch($url, $filename);
ini_restore('allow_url_fopen');
ini_restore('default_socket_timeout');
restore_error_handler();
if ($status !== CRM_Utils_HttpClient::STATUS_OK) {
throw new CRM_Extension_Exception(ts('The CiviCRM public extensions directory at %1 could not be contacted - please check your webserver can make external HTTP requests or contact CiviCRM team on <a href="http://forum.civicrm.org/">CiviCRM forum</a>.', array(1 => $this->getRepositoryUrl())), 'connection_error');
}
// Don't call grabCachedJson here, that would risk infinite recursion
return file_get_contents($filename);
}
/**
* @return string
*/
private function getTsPath() {
return $this->cacheDir . DIRECTORY_SEPARATOR . 'timestamp.txt';
}
/**
* A dummy function required for suppressing download errors.
*
* @param $errorNumber
* @param $errorString
*/
public static function downloadError($errorNumber, $errorString) {
}
}

View file

@ -0,0 +1,153 @@
<?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 |
+--------------------------------------------------------------------+
*/
/**
*
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
* $Id$
*
*/
class CRM_Extension_ClassLoader {
/**
* @var CRM_Extension_Mapper
*/
protected $mapper;
/**
* @var CRM_Extension_Container_Interface
*/
protected $container;
/**
* @var CRM_Extension_Manager
*/
protected $manager;
/**
* @var \Composer\Autoload\ClassLoader
*/
protected $loader;
/**
* CRM_Extension_ClassLoader constructor.
* @param \CRM_Extension_Mapper $mapper
* @param \CRM_Extension_Container_Interface $container
* @param \CRM_Extension_Manager $manager
*/
public function __construct(\CRM_Extension_Mapper $mapper, \CRM_Extension_Container_Interface $container, \CRM_Extension_Manager $manager) {
$this->mapper = $mapper;
$this->container = $container;
$this->manager = $manager;
}
public function __destruct() {
$this->unregister();
}
/**
* Registers this instance as an autoloader.
* @return CRM_Extension_ClassLoader
*/
public function register() {
// In pre-installation environments, don't bother with caching.
if (!defined('CIVICRM_TEMPLATE_COMPILEDIR') || !defined('CIVICRM_DSN') || defined('CIVICRM_TEST') || \CRM_Utils_System::isInUpgradeMode()) {
return $this->buildClassLoader()->register();
}
$file = $this->getCacheFile();
if (file_exists($file)) {
$loader = require $file;
}
else {
$loader = $this->buildClassLoader();
$ser = serialize($loader);
file_put_contents($file,
sprintf("<?php\nreturn unserialize(%s);", var_export($ser, 1))
);
}
return $loader->register();
}
/**
* @return \Composer\Autoload\ClassLoader
* @throws \CRM_Extension_Exception
* @throws \Exception
*/
public function buildClassLoader() {
$loader = new \Composer\Autoload\ClassLoader();
$statuses = $this->manager->getStatuses();
foreach ($statuses as $key => $status) {
if ($status !== CRM_Extension_Manager::STATUS_INSTALLED) {
continue;
}
$path = $this->mapper->keyToBasePath($key);
$info = $this->mapper->keyToInfo($key);
if (!empty($info->classloader)) {
foreach ($info->classloader as $mapping) {
switch ($mapping['type']) {
case 'psr4':
$loader->addPsr4($mapping['prefix'], $path . '/' . $mapping['path']);
break;
}
$result[] = $mapping;
}
}
}
return $loader;
}
public function unregister() {
if ($this->loader) {
$this->loader->unregister();
$this->loader = NULL;
}
}
public function refresh() {
$this->unregister();
$file = $this->getCacheFile();
if (file_exists($file)) {
unlink($file);
}
$this->register();
}
/**
* @return string
*/
protected function getCacheFile() {
$envId = \CRM_Core_Config_Runtime::getId();
$file = CIVICRM_TEMPLATE_COMPILEDIR . "/CachedExtLoader.{$envId}.php";
return $file;
}
}

View file

@ -0,0 +1,282 @@
<?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 |
+--------------------------------------------------------------------+
*/
/**
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
/**
* An extension container is a locally-accessible source tree which can be
* scanned for extensions.
*/
class CRM_Extension_Container_Basic implements CRM_Extension_Container_Interface {
/**
* @var string
*
* Note: Treat as private. This is only public to facilitate debugging.
*/
public $baseDir;
/**
* @var string
*
* Note: Treat as private. This is only public to facilitate debugging.
*/
public $baseUrl;
/**
* @var CRM_Utils_Cache_Interface|NULL
*
* Note: Treat as private. This is only public to facilitate debugging.
*/
public $cache;
/**
* @var string the cache key used for any data stored by this container
*
* Note: Treat as private. This is only public to facilitate debugging.
*/
public $cacheKey;
/**
* @var array($key => $relPath)
*
* Note: Treat as private. This is only public to facilitate debugging.
*/
public $relPaths = FALSE;
/**
* @var array($key => $relUrl)
*
* Derived from $relPaths. On Unix systems (where file-paths and
* URL-paths both use '/' separator), this isn't necessary. On Windows
* systems, this is derived from $relPaths.
*
* Note: Treat as private. This is only public to facilitate debugging.
*/
public $relUrls = FALSE;
/**
* @param string $baseDir
* Local path to the container.
* @param string $baseUrl
* Public URL of the container.
* @param CRM_Utils_Cache_Interface $cache
* Cache in which to store extension metadata.
* @param string $cacheKey
* Unique name for this container.
*/
public function __construct($baseDir, $baseUrl, CRM_Utils_Cache_Interface $cache = NULL, $cacheKey = NULL) {
$this->cache = $cache;
$this->cacheKey = $cacheKey;
$this->baseDir = rtrim($baseDir, '/');
$this->baseUrl = rtrim($baseUrl, '/');
}
/**
* @inheritDoc
*
* @return array
*/
public function checkRequirements() {
$errors = array();
if (empty($this->baseDir) || !is_dir($this->baseDir)) {
$errors[] = array(
'title' => ts('Invalid Base Directory'),
'message' => ts('An extension container has been defined with a blank directory.'),
);
}
if (empty($this->baseUrl)) {
$errors[] = array(
'title' => ts('Invalid Base URL'),
'message' => ts('An extension container has been defined with a blank URL.'),
);
}
return $errors;
}
/**
* @inheritDoc
*
* @return array_keys
*/
public function getKeys() {
return array_keys($this->getRelPaths());
}
/**
* @inheritDoc
*/
public function getPath($key) {
return $this->baseDir . $this->getRelPath($key);
}
/**
* @inheritDoc
*/
public function getResUrl($key) {
if (!$this->baseUrl) {
CRM_Core_Session::setStatus(
ts('Failed to determine URL for extension (%1). Please update <a href="%2">Resource URLs</a>.',
array(
1 => $key,
2 => CRM_Utils_System::url('civicrm/admin/setting/url', 'reset=1'),
)
)
);
}
return $this->baseUrl . $this->getRelUrl($key);
}
/**
* @inheritDoc
*/
public function refresh() {
$this->relPaths = NULL;
if ($this->cache) {
$this->cache->delete($this->cacheKey);
}
}
/**
* @return string
*/
public function getBaseDir() {
return $this->baseDir;
}
/**
* Determine the relative path of an extension directory.
*
* @param string $key
* Extension name.
*
* @throws CRM_Extension_Exception_MissingException
* @return string
*/
protected function getRelPath($key) {
$keypaths = $this->getRelPaths();
if (!isset($keypaths[$key])) {
throw new CRM_Extension_Exception_MissingException("Failed to find extension: $key");
}
return $keypaths[$key];
}
/**
* Scan $basedir for a list of extension-keys
*
* @return array
* ($key => $relPath)
*/
protected function getRelPaths() {
if (!is_array($this->relPaths)) {
if ($this->cache) {
$this->relPaths = $this->cache->get($this->cacheKey);
}
if (!is_array($this->relPaths)) {
$this->relPaths = array();
$infoPaths = CRM_Utils_File::findFiles($this->baseDir, 'info.xml');
foreach ($infoPaths as $infoPath) {
$relPath = CRM_Utils_File::relativize(dirname($infoPath), $this->baseDir);
try {
$info = CRM_Extension_Info::loadFromFile($infoPath);
}
catch (CRM_Extension_Exception_ParseException $e) {
CRM_Core_Session::setStatus(ts('Parse error in extension: %1', array(
1 => $e->getMessage(),
)), '', 'error');
CRM_Core_Error::debug_log_message("Parse error in extension: " . $e->getMessage());
continue;
}
$this->relPaths[$info->key] = $relPath;
}
if ($this->cache) {
$this->cache->set($this->cacheKey, $this->relPaths);
}
}
}
return $this->relPaths;
}
/**
* Determine the relative path of an extension directory.
*
* @param string $key
* Extension name.
*
* @throws CRM_Extension_Exception_MissingException
* @return string
*/
protected function getRelUrl($key) {
$relUrls = $this->getRelUrls();
if (!isset($relUrls[$key])) {
throw new CRM_Extension_Exception_MissingException("Failed to find extension: $key");
}
return $relUrls[$key];
}
/**
* Scan $basedir for a list of extension-keys
*
* @return array
* ($key => $relUrl)
*/
protected function getRelUrls() {
if (DIRECTORY_SEPARATOR == '/') {
return $this->getRelPaths();
}
if (!is_array($this->relUrls)) {
$this->relUrls = self::convertPathsToUrls(DIRECTORY_SEPARATOR, $this->getRelPaths());
}
return $this->relUrls;
}
/**
* Convert a list of relative paths to relative URLs.
*
* Note: Treat as private. This is only public to facilitate testing.
*
* @param string $dirSep
* Directory separator ("/" or "\").
* @param array $relPaths
* Array($key => $relPath).
* @return array
* Array($key => $relUrl).
*/
public static function convertPathsToUrls($dirSep, $relPaths) {
$relUrls = array();
foreach ($relPaths as $key => $relPath) {
$relUrls[$key] = str_replace($dirSep, '/', $relPath);
}
return $relUrls;
}
}

View file

@ -0,0 +1,181 @@
<?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 |
+--------------------------------------------------------------------+
*/
/**
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
/**
* An extension container is a locally-accessible source tree which can be
* scanned for extensions.
*/
class CRM_Extension_Container_Collection implements CRM_Extension_Container_Interface {
/**
* @var array ($name => CRM_Extension_Container_Interface)
*
* Note: Treat as private. This is only public to facilitate debugging.
*/
public $containers;
/**
* @var CRM_Utils_Cache_Interface|NULL
*
* Note: Treat as private. This is only public to facilitate debugging.
*/
public $cache;
/**
* @var string the cache key used for any data stored by this container
*
* Note: Treat as private. This is only public to facilitate debugging.
*/
public $cacheKey;
/**
* @var array ($key => $containerName)
*
* Note: Treat as private. This is only public to facilitate debugging.
*/
public $k2c;
/**
* @param array $containers
* Array($name => CRM_Extension_Container_Interface) in order from highest
* priority (winners) to lowest priority (losers).
* @param CRM_Utils_Cache_Interface $cache
* Cache in which to store extension metadata.
* @param string $cacheKey
* Unique name for this container.
*/
public function __construct($containers, CRM_Utils_Cache_Interface $cache = NULL, $cacheKey = NULL) {
$this->containers = $containers;
$this->cache = $cache;
$this->cacheKey = $cacheKey;
}
/**
* @inheritDoc
*
* @return array
*/
public function checkRequirements() {
$errors = array();
foreach ($this->containers as $container) {
$errors = array_merge($errors, $container->checkRequirements());
}
return $errors;
}
/**
* @inheritDoc
*
* @return array_keys
*/
public function getKeys() {
$k2c = $this->getKeysToContainer();
return array_keys($k2c);
}
/**
* @inheritDoc
*
* @param string $key
*/
public function getPath($key) {
return $this->getContainer($key)->getPath($key);
}
/**
* @inheritDoc
*
* @param string $key
*/
public function getResUrl($key) {
return $this->getContainer($key)->getResUrl($key);
}
/**
* @inheritDoc
*/
public function refresh() {
if ($this->cache) {
$this->cache->delete($this->cacheKey);
}
foreach ($this->containers as $container) {
$container->refresh();
}
}
/**
* Get the container which defines a particular key.
*
* @param string $key
* Extension name.
*
* @throws CRM_Extension_Exception_MissingException
* @return CRM_Extension_Container_Interface
*/
public function getContainer($key) {
$k2c = $this->getKeysToContainer();
if (isset($k2c[$key]) && isset($this->containers[$k2c[$key]])) {
return $this->containers[$k2c[$key]];
}
else {
throw new CRM_Extension_Exception_MissingException("Unknown extension: $key");
}
}
/**
* Get a list of all keys in these containers -- and the
* name of the container which defines each key.
*
* @return array
* ($key => $containerName)
*/
public function getKeysToContainer() {
if ($this->cache) {
$k2c = $this->cache->get($this->cacheKey);
}
if (!isset($k2c) || !is_array($k2c)) {
$k2c = array();
$containerNames = array_reverse(array_keys($this->containers));
foreach ($containerNames as $name) {
$keys = $this->containers[$name]->getKeys();
foreach ($keys as $key) {
$k2c[$key] = $name;
}
}
if ($this->cache) {
$this->cache->set($this->cacheKey, $k2c);
}
}
return $k2c;
}
}

View file

@ -0,0 +1,77 @@
<?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 |
+--------------------------------------------------------------------+
*/
/**
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
/**
* The default container is just a basic container which can be configured via
* the web UI.
*/
class CRM_Extension_Container_Default extends CRM_Extension_Container_Basic {
/**
* @inheritDoc
*
* @return array
*/
public function checkRequirements() {
$errors = array();
// In current configuration, we don't construct the default container
// unless baseDir is set, so this error condition is more theoretical.
if (empty($this->baseDir) || !is_dir($this->baseDir)) {
$civicrmDestination = urlencode(CRM_Utils_System::url('civicrm/admin/extensions', 'reset=1'));
$url = CRM_Utils_System::url('civicrm/admin/setting/path', "reset=1&civicrmDestination=${civicrmDestination}");
$errors[] = array(
'title' => ts('Invalid Base Directory'),
'message' => ts('The extensions directory is not properly set. Please go to the <a href="%1">path setting page</a> and correct it.<br/>',
array(
1 => $url,
)
),
);
}
if (empty($this->baseUrl)) {
$civicrmDestination = urlencode(CRM_Utils_System::url('civicrm/admin/extensions', 'reset=1'));
$url = CRM_Utils_System::url('civicrm/admin/setting/url', "reset=1&civicrmDestination=${civicrmDestination}");
$errors[] = array(
'title' => ts('Invalid Base URL'),
'message' => ts('The extensions URL is not properly set. Please go to the <a href="%1">URL setting page</a> and correct it.<br/>',
array(
1 => $url,
)
),
);
}
return $errors;
}
}

View file

@ -0,0 +1,70 @@
<?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 |
+--------------------------------------------------------------------+
*/
/**
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
/**
* An extension container is a locally-accessible source tree which can be
* scanned for extensions.
*/
interface CRM_Extension_Container_Interface {
/**
* Determine if any unmet requirements prevent use of this container.
*/
public function checkRequirements();
/**
* Get a list of extensions available in this container.
*/
public function getKeys();
/**
* Determine the main .php file for an extension
*
* @param string $key
* Fully-qualified extension name.
*/
public function getPath($key);
/**
* Determine the base URL for resources provided by the extension.
*
* @param string $key
* Fully-qualified extension name.
*/
public function getResUrl($key);
/**
* Scan the container for available extensions.
*/
public function refresh();
}

View file

@ -0,0 +1,104 @@
<?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 |
+--------------------------------------------------------------------+
*/
/**
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
/**
* An extension container is a locally-accessible source tree which can be
* scanned for extensions.
*/
class CRM_Extension_Container_Static implements CRM_Extension_Container_Interface {
/**
* @param array $exts
* Array(string $key => array $spec) List of extensions.
*/
public function __construct($exts) {
$this->exts = $exts;
}
/**
* @inheritDoc
*/
public function checkRequirements() {
return array();
}
/**
* @inheritDoc
*/
public function getName() {
return $this->name;
}
/**
* @inheritDoc
*/
public function getKeys() {
return array_keys($this->exts);
}
/**
* @inheritDoc
*/
public function getPath($key) {
$e = $this->getExt($key);
return $e['path'];
}
/**
* @inheritDoc
*/
public function getResUrl($key) {
$e = $this->getExt($key);
return $e['resUrl'];
}
/**
* @inheritDoc
*/
public function refresh() {
}
/**
* @param string $key
* Extension name.
*
* @throws CRM_Extension_Exception_MissingException
*/
protected function getExt($key) {
if (isset($this->exts[$key])) {
return $this->exts[$key];
}
else {
throw new CRM_Extension_Exception_MissingException("Missing extension: $key");
}
}
}

View file

@ -0,0 +1,224 @@
<?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 handles downloads of remotely-provided extensions
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
class CRM_Extension_Downloader {
/**
* @var CRM_Extension_Container_Basic the place where downloaded extensions are ultimately stored
*/
public $container;
/**
* @var string local path to a temporary data directory
*/
public $tmpDir;
/**
* @param CRM_Extension_Manager $manager
* @param string $containerDir
* The place to store downloaded & extracted extensions.
* @param string $tmpDir
*/
public function __construct(CRM_Extension_Manager $manager, $containerDir, $tmpDir) {
$this->manager = $manager;
$this->containerDir = $containerDir;
$this->tmpDir = $tmpDir;
}
/**
* Determine whether downloading is supported.
*
* @return array
* list of error messages; empty if OK
*/
public function checkRequirements() {
$errors = array();
if (!$this->containerDir || !is_dir($this->containerDir) || !is_writable($this->containerDir)) {
$civicrmDestination = urlencode(CRM_Utils_System::url('civicrm/admin/extensions', 'reset=1'));
$url = CRM_Utils_System::url('civicrm/admin/setting/path', "reset=1&civicrmDestination=${civicrmDestination}");
$errors[] = array(
'title' => ts('Directory Unwritable'),
'message' => ts("Your extensions directory is not set or is not writable. Click <a href='%1'>here</a> to set the extensions directory.",
array(
1 => $url,
)
),
);
}
if (!class_exists('ZipArchive')) {
$errors[] = array(
'title' => ts('ZIP Support Required'),
'message' => ts('You will not be able to install extensions at this time because your installation of PHP does not support ZIP archives. Please ask your system administrator to install the standard PHP-ZIP extension.'),
);
}
if (empty($errors) && !CRM_Utils_HttpClient::singleton()->isRedirectSupported()) {
CRM_Core_Session::setStatus(ts('WARNING: The downloader may be unable to download files which require HTTP redirection. This may be a configuration issue with PHP\'s open_basedir or safe_mode.'));
CRM_Core_Error::debug_log_message('WARNING: The downloader may be unable to download files which require HTTP redirection. This may be a configuration issue with PHP\'s open_basedir or safe_mode.');
}
return $errors;
}
/**
* Install or upgrade an extension from a remote URL.
*
* @param string $key
* The name of the extension being installed.
* @param string $downloadUrl
* URL of a .zip file.
* @return bool
* TRUE for success
* @throws CRM_Extension_Exception
*/
public function download($key, $downloadUrl) {
$filename = $this->tmpDir . DIRECTORY_SEPARATOR . $key . '.zip';
$destDir = $this->containerDir . DIRECTORY_SEPARATOR . $key;
if (!$downloadUrl) {
CRM_Core_Error::fatal('Cannot install this extension - downloadUrl is not set!');
}
if (!$this->fetch($downloadUrl, $filename)) {
return FALSE;
}
$extractedZipPath = $this->extractFiles($key, $filename);
if (!$extractedZipPath) {
return FALSE;
}
if (!$this->validateFiles($key, $extractedZipPath)) {
return FALSE;
}
$this->manager->replace($extractedZipPath);
return TRUE;
}
/**
* Download the remote zipfile.
*
* @param string $remoteFile
* URL of a .zip file.
* @param string $localFile
* Path at which to store the .zip file.
* @return bool
* Whether the download was successful.
*/
public function fetch($remoteFile, $localFile) {
$result = CRM_Utils_HttpClient::singleton()->fetch($remoteFile, $localFile);
switch ($result) {
case CRM_Utils_HttpClient::STATUS_OK:
return TRUE;
default:
return FALSE;
}
}
/**
* Extract an extension from a zip file.
*
* @param string $key
* The name of the extension being installed; this usually matches the basedir in the .zip.
* @param string $zipFile
* The local path to a .zip file.
* @return string|FALSE
* zip file path
*/
public function extractFiles($key, $zipFile) {
$config = CRM_Core_Config::singleton();
$zip = new ZipArchive();
$res = $zip->open($zipFile);
if ($res === TRUE) {
$zipSubDir = CRM_Utils_Zip::guessBasedir($zip, $key);
if ($zipSubDir === FALSE) {
CRM_Core_Session::setStatus(ts('Unable to extract the extension: bad directory structure'), '', 'error');
return FALSE;
}
$extractedZipPath = $this->tmpDir . DIRECTORY_SEPARATOR . $zipSubDir;
if (is_dir($extractedZipPath)) {
if (!CRM_Utils_File::cleanDir($extractedZipPath, TRUE, FALSE)) {
CRM_Core_Session::setStatus(ts('Unable to extract the extension: %1 cannot be cleared', array(1 => $extractedZipPath)), ts('Installation Error'), 'error');
return FALSE;
}
}
if (!$zip->extractTo($this->tmpDir)) {
CRM_Core_Session::setStatus(ts('Unable to extract the extension to %1.', array(1 => $this->tmpDir)), ts('Installation Error'), 'error');
return FALSE;
}
$zip->close();
}
else {
CRM_Core_Session::setStatus(ts('Unable to extract the extension.'), '', 'error');
return FALSE;
}
return $extractedZipPath;
}
/**
* Validate that $extractedZipPath contains valid for extension $key
*
* @param $key
* @param $extractedZipPath
*
* @return bool
*/
public function validateFiles($key, $extractedZipPath) {
$filename = $extractedZipPath . DIRECTORY_SEPARATOR . CRM_Extension_Info::FILENAME;
if (!is_readable($filename)) {
CRM_Core_Session::setStatus(ts('Failed reading data from %1 during installation', array(1 => $filename)), ts('Installation Error'), 'error');
return FALSE;
}
try {
$newInfo = CRM_Extension_Info::loadFromFile($filename);
}
catch (Exception $e) {
CRM_Core_Session::setStatus(ts('Failed reading data from %1 during installation', array(1 => $filename)), ts('Installation Error'), 'error');
return FALSE;
}
if ($newInfo->key != $key) {
CRM_Core_Error::fatal('Cannot install - there are differences between extdir XML file and archive XML file!');
}
return TRUE;
}
}

View file

@ -0,0 +1,35 @@
<?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 |
+--------------------------------------------------------------------+
*/
/**
* Class CRM_Extension_Exception
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
class CRM_Extension_Exception extends CRM_Core_Exception {
}

View file

@ -0,0 +1,38 @@
<?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 |
+--------------------------------------------------------------------+
*/
/**
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
/**
* An extension management operation failed because it would
* break a dependency.
*/
class CRM_Extension_Exception_DependencyException extends CRM_Extension_Exception {
}

View file

@ -0,0 +1,38 @@
<?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 |
+--------------------------------------------------------------------+
*/
/**
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
/**
* An extension could not be located
*/
class CRM_Extension_Exception_MissingException extends CRM_Extension_Exception {
}

View file

@ -0,0 +1,37 @@
<?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 |
+--------------------------------------------------------------------+
*/
/**
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
/**
* Error reading XML for an extension
*/
class CRM_Extension_Exception_ParseException extends CRM_Extension_Exception {
}

View file

@ -0,0 +1,183 @@
<?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 |
+--------------------------------------------------------------------+
*/
/**
* Metadata for an extension (e.g. the extension's "info.xml" file)
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
class CRM_Extension_Info {
/**
* Extension info file name.
*/
const FILENAME = 'info.xml';
public $key = NULL;
public $type = NULL;
public $name = NULL;
public $label = NULL;
public $file = NULL;
/**
* @var array
* Each item is a specification like:
* array('type'=>'psr4', 'namespace'=>'Foo\Bar', 'path'=>'/foo/bar').
*/
public $classloader = array();
/**
* @var array
* Each item is they key-name of an extension required by this extension.
*/
public $requires = array();
/**
* Load extension info an XML file.
*
* @param $file
*
* @throws CRM_Extension_Exception_ParseException
* @return CRM_Extension_Info
*/
public static function loadFromFile($file) {
list ($xml, $error) = CRM_Utils_XML::parseFile($file);
if ($xml === FALSE) {
throw new CRM_Extension_Exception_ParseException("Failed to parse info XML: $error");
}
$instance = new CRM_Extension_Info();
$instance->parse($xml);
return $instance;
}
/**
* Load extension info a string.
*
* @param string $string
* XML content.
*
* @throws CRM_Extension_Exception_ParseException
* @return CRM_Extension_Info
*/
public static function loadFromString($string) {
list ($xml, $error) = CRM_Utils_XML::parseString($string);
if ($xml === FALSE) {
throw new CRM_Extension_Exception_ParseException("Failed to parse info XML: $string");
}
$instance = new CRM_Extension_Info();
$instance->parse($xml);
return $instance;
}
/**
* Build a reverse-dependency map.
*
* @param array $infos
* The universe of available extensions.
* Ex: $infos['org.civicrm.foobar'] = new CRM_Extension_Info().
* @return array
* If "org.civicrm.api" is required by "org.civicrm.foo", then return
* array('org.civicrm.api' => array(CRM_Extension_Info[org.civicrm.foo])).
* Array(string $key => array $requiredBys).
*/
public static function buildReverseMap($infos) {
$revMap = array();
foreach ($infos as $info) {
foreach ($info->requires as $key) {
$revMap[$key][] = $info;
}
}
return $revMap;
}
/**
* @param null $key
* @param null $type
* @param null $name
* @param null $label
* @param null $file
*/
public function __construct($key = NULL, $type = NULL, $name = NULL, $label = NULL, $file = NULL) {
$this->key = $key;
$this->type = $type;
$this->name = $name;
$this->label = $label;
$this->file = $file;
}
/**
* Copy attributes from an XML document to $this
*
* @param SimpleXMLElement $info
*/
public function parse($info) {
$this->key = (string) $info->attributes()->key;
$this->type = (string) $info->attributes()->type;
$this->file = (string) $info->file;
$this->label = (string) $info->name;
// Convert first level variables to CRM_Core_Extension properties
// and deeper into arrays. An exception for URLS section, since
// we want them in special format.
foreach ($info as $attr => $val) {
if (count($val->children()) == 0) {
$this->$attr = (string) $val;
}
elseif ($attr === 'urls') {
$this->urls = array();
foreach ($val->url as $url) {
$urlAttr = (string) $url->attributes()->desc;
$this->urls[$urlAttr] = (string) $url;
}
ksort($this->urls);
}
elseif ($attr === 'classloader') {
$this->classloader = array();
foreach ($val->psr4 as $psr4) {
$this->classloader[] = array(
'type' => 'psr4',
'prefix' => (string) $psr4->attributes()->prefix,
'path' => (string) $psr4->attributes()->path,
);
}
}
elseif ($attr === 'requires') {
$this->requires = array();
foreach ($val->ext as $ext) {
$this->requires[] = (string) $ext;
}
}
else {
$this->$attr = CRM_Utils_XML::xmlObjToArray($val);
}
}
}
}

View file

@ -0,0 +1,680 @@
<?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 |
+--------------------------------------------------------------------+
*/
/**
* The extension manager handles installing, disabling enabling, and
* uninstalling extensions.
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
class CRM_Extension_Manager {
/**
* The extension is fully installed and enabled.
*/
const STATUS_INSTALLED = 'installed';
/**
* The extension config has been applied to database but deactivated.
*/
const STATUS_DISABLED = 'disabled';
/**
* The extension code is visible, but nothing has been applied to DB
*/
const STATUS_UNINSTALLED = 'uninstalled';
/**
* The extension code is not locally accessible
*/
const STATUS_UNKNOWN = 'unknown';
/**
* The extension is fully installed and enabled
*/
const STATUS_INSTALLED_MISSING = 'installed-missing';
/**
* The extension is fully installed and enabled
*/
const STATUS_DISABLED_MISSING = 'disabled-missing';
/**
* @var CRM_Extension_Container_Interface
*
* Note: Treat as private. This is only public to facilitate debugging.
*/
public $fullContainer;
/**
* @var CRM_Extension_Container_Basic|FALSE
*
* Note: Treat as private. This is only public to facilitate debugging.
*/
public $defaultContainer;
/**
* @var CRM_Extension_Mapper
*
* Note: Treat as private. This is only public to facilitate debugging.
*/
public $mapper;
/**
* @var array (typeName => CRM_Extension_Manager_Interface)
*
* Note: Treat as private. This is only public to facilitate debugging.
*/
public $typeManagers;
/**
* @var array (extensionKey => statusConstant)
*
* Note: Treat as private. This is only public to facilitate debugging.
*/
public $statuses;
/**
* @param CRM_Extension_Container_Interface $fullContainer
* @param CRM_Extension_Container_Basic|FALSE $defaultContainer
* @param CRM_Extension_Mapper $mapper
* @param $typeManagers
*/
public function __construct(CRM_Extension_Container_Interface $fullContainer, $defaultContainer, CRM_Extension_Mapper $mapper, $typeManagers) {
$this->fullContainer = $fullContainer;
$this->defaultContainer = $defaultContainer;
$this->mapper = $mapper;
$this->typeManagers = $typeManagers;
}
/**
* Install or upgrade the code for an extension -- and perform any
* necessary database changes (eg replacing extension metadata).
*
* This only works if the extension is stored in the default container.
*
* @param string $tmpCodeDir
* Path to a local directory containing a copy of the new (inert) code.
* @throws CRM_Extension_Exception
*/
public function replace($tmpCodeDir) {
if (!$this->defaultContainer) {
throw new CRM_Extension_Exception("Default extension container is not configured");
}
$newInfo = CRM_Extension_Info::loadFromFile($tmpCodeDir . DIRECTORY_SEPARATOR . CRM_Extension_Info::FILENAME);
$oldStatus = $this->getStatus($newInfo->key);
// find $tgtPath, $oldInfo, $typeManager
switch ($oldStatus) {
case self::STATUS_UNINSTALLED:
case self::STATUS_INSTALLED:
case self::STATUS_DISABLED:
// There is an old copy of the extension. Try to install in the same place -- but it must go somewhere in the default-container
list ($oldInfo, $typeManager) = $this->_getInfoTypeHandler($newInfo->key); // throws Exception
$tgtPath = $this->fullContainer->getPath($newInfo->key);
if (!CRM_Utils_File::isChildPath($this->defaultContainer->getBaseDir(), $tgtPath)) {
// force installation in the default-container
$oldPath = $tgtPath;
$tgtPath = $this->defaultContainer->getBaseDir() . DIRECTORY_SEPARATOR . $newInfo->key;
CRM_Core_Session::setStatus(ts('A copy of the extension (%1) is in a system folder (%2). The system copy will be preserved, but the new copy will be used.', array(
1 => $newInfo->key,
2 => $oldPath,
)));
}
break;
case self::STATUS_INSTALLED_MISSING:
case self::STATUS_DISABLED_MISSING:
// the extension does not exist in any container; we're free to put it anywhere
$tgtPath = $this->defaultContainer->getBaseDir() . DIRECTORY_SEPARATOR . $newInfo->key;
list ($oldInfo, $typeManager) = $this->_getMissingInfoTypeHandler($newInfo->key); // throws Exception
break;
case self::STATUS_UNKNOWN:
// the extension does not exist in any container; we're free to put it anywhere
$tgtPath = $this->defaultContainer->getBaseDir() . DIRECTORY_SEPARATOR . $newInfo->key;
$oldInfo = $typeManager = NULL;
break;
default:
throw new CRM_Extension_Exception("Cannot install or enable extension: {$newInfo->key}");
}
// move the code!
switch ($oldStatus) {
case self::STATUS_UNINSTALLED:
case self::STATUS_UNKNOWN:
// There are no DB records to worry about, so we'll just put the files in place
if (!CRM_Utils_File::replaceDir($tmpCodeDir, $tgtPath)) {
throw new CRM_Extension_Exception("Failed to move $tmpCodeDir to $tgtPath");
}
break;
case self::STATUS_INSTALLED:
case self::STATUS_INSTALLED_MISSING:
case self::STATUS_DISABLED:
case self::STATUS_DISABLED_MISSING:
// There are DB records; coordinate the file placement with the DB updates
$typeManager->onPreReplace($oldInfo, $newInfo);
if (!CRM_Utils_File::replaceDir($tmpCodeDir, $tgtPath)) {
throw new CRM_Extension_Exception("Failed to move $tmpCodeDir to $tgtPath");
}
$this->_updateExtensionEntry($newInfo);
$typeManager->onPostReplace($oldInfo, $newInfo);
break;
default:
throw new CRM_Extension_Exception("Cannot install or enable extension: {$newInfo->key}");
}
$this->refresh();
CRM_Core_Invoke::rebuildMenuAndCaches(TRUE);
}
/**
* Add records of the extension to the database -- and enable it
*
* @param array $keys
* List of extension keys.
* @throws CRM_Extension_Exception
*/
public function install($keys) {
$origStatuses = $this->getStatuses();
// TODO: to mitigate the risk of crashing during installation, scan
// keys/statuses/types before doing anything
foreach ($keys as $key) {
list ($info, $typeManager) = $this->_getInfoTypeHandler($key); // throws Exception
switch ($origStatuses[$key]) {
case self::STATUS_INSTALLED:
// ok, nothing to do
break;
case self::STATUS_DISABLED:
// re-enable it
$typeManager->onPreEnable($info);
$this->_setExtensionActive($info, 1);
$typeManager->onPostEnable($info);
// A full refresh would be preferrable but very slow. This at least allows
// later extensions to access classes from earlier extensions.
$this->statuses = NULL;
$this->mapper->refresh();
break;
case self::STATUS_UNINSTALLED:
// install anew
$typeManager->onPreInstall($info);
$this->_createExtensionEntry($info);
$typeManager->onPostInstall($info);
// A full refresh would be preferrable but very slow. This at least allows
// later extensions to access classes from earlier extensions.
$this->statuses = NULL;
$this->mapper->refresh();
break;
case self::STATUS_UNKNOWN:
default:
throw new CRM_Extension_Exception("Cannot install or enable extension: $key");
}
}
$this->statuses = NULL;
$this->mapper->refresh();
CRM_Core_Invoke::rebuildMenuAndCaches(TRUE);
$schema = new CRM_Logging_Schema();
$schema->fixSchemaDifferences();
foreach ($keys as $key) {
list ($info, $typeManager) = $this->_getInfoTypeHandler($key); // throws Exception
switch ($origStatuses[$key]) {
case self::STATUS_INSTALLED:
// ok, nothing to do
break;
case self::STATUS_DISABLED:
// re-enable it
break;
case self::STATUS_UNINSTALLED:
// install anew
$typeManager->onPostPostInstall($info);
break;
case self::STATUS_UNKNOWN:
default:
throw new CRM_Extension_Exception("Cannot install or enable extension: $key");
}
}
}
/**
* Add records of the extension to the database -- and enable it
*
* @param array $keys
* List of extension keys.
* @throws CRM_Extension_Exception
*/
public function enable($keys) {
$this->install($keys);
}
/**
* Add records of the extension to the database -- and enable it
*
* @param array $keys
* List of extension keys.
* @throws CRM_Extension_Exception
*/
public function disable($keys) {
$origStatuses = $this->getStatuses();
// TODO: to mitigate the risk of crashing during installation, scan
// keys/statuses/types before doing anything
sort($keys);
$disableRequirements = $this->findDisableRequirements($keys);
sort($disableRequirements); // This munges order, but makes it comparable.
if ($keys !== $disableRequirements) {
throw new CRM_Extension_Exception_DependencyException("Cannot disable extension due dependencies. Consider disabling all these: " . implode(',', $disableRequirements));
}
foreach ($keys as $key) {
switch ($origStatuses[$key]) {
case self::STATUS_INSTALLED:
list ($info, $typeManager) = $this->_getInfoTypeHandler($key); // throws Exception
$typeManager->onPreDisable($info);
$this->_setExtensionActive($info, 0);
$typeManager->onPostDisable($info);
break;
case self::STATUS_INSTALLED_MISSING:
list ($info, $typeManager) = $this->_getMissingInfoTypeHandler($key); // throws Exception
$typeManager->onPreDisable($info);
$this->_setExtensionActive($info, 0);
$typeManager->onPostDisable($info);
break;
case self::STATUS_DISABLED:
case self::STATUS_DISABLED_MISSING:
case self::STATUS_UNINSTALLED:
// ok, nothing to do
break;
case self::STATUS_UNKNOWN:
default:
throw new CRM_Extension_Exception("Cannot disable unknown extension: $key");
}
}
$this->statuses = NULL;
$this->mapper->refresh();
CRM_Core_Invoke::rebuildMenuAndCaches(TRUE);
}
/**
* Remove all database references to an extension.
*
* Add records of the extension to the database -- and enable it
*
* @param array $keys
* List of extension keys.
* @throws CRM_Extension_Exception
*/
public function uninstall($keys) {
$origStatuses = $this->getStatuses();
// TODO: to mitigate the risk of crashing during installation, scan
// keys/statuses/types before doing anything
foreach ($keys as $key) {
switch ($origStatuses[$key]) {
case self::STATUS_INSTALLED:
case self::STATUS_INSTALLED_MISSING:
throw new CRM_Extension_Exception("Cannot uninstall extension; disable it first: $key");
case self::STATUS_DISABLED:
list ($info, $typeManager) = $this->_getInfoTypeHandler($key); // throws Exception
$typeManager->onPreUninstall($info);
$this->_removeExtensionEntry($info);
$typeManager->onPostUninstall($info);
break;
case self::STATUS_DISABLED_MISSING:
list ($info, $typeManager) = $this->_getMissingInfoTypeHandler($key); // throws Exception
$typeManager->onPreUninstall($info);
$this->_removeExtensionEntry($info);
$typeManager->onPostUninstall($info);
break;
case self::STATUS_UNINSTALLED:
// ok, nothing to do
break;
case self::STATUS_UNKNOWN:
default:
throw new CRM_Extension_Exception("Cannot disable unknown extension: $key");
}
}
$this->statuses = NULL;
$this->mapper->refresh();
CRM_Core_Invoke::rebuildMenuAndCaches(TRUE);
}
/**
* Determine the status of an extension.
*
* @param $key
*
* @return string
* constant (STATUS_INSTALLED, STATUS_DISABLED, STATUS_UNINSTALLED, STATUS_UNKNOWN)
*/
public function getStatus($key) {
$statuses = $this->getStatuses();
if (array_key_exists($key, $statuses)) {
return $statuses[$key];
}
else {
return self::STATUS_UNKNOWN;
}
}
/**
* Determine the status of all extensions.
*
* @return array
* ($key => status_constant)
*/
public function getStatuses() {
if (!is_array($this->statuses)) {
$this->statuses = array();
foreach ($this->fullContainer->getKeys() as $key) {
$this->statuses[$key] = self::STATUS_UNINSTALLED;
}
$sql = '
SELECT full_name, is_active
FROM civicrm_extension
';
$dao = CRM_Core_DAO::executeQuery($sql);
while ($dao->fetch()) {
try {
$path = $this->fullContainer->getPath($dao->full_name);
$codeExists = !empty($path) && is_dir($path);
}
catch (CRM_Extension_Exception $e) {
$codeExists = FALSE;
}
if ($dao->is_active) {
$this->statuses[$dao->full_name] = $codeExists ? self::STATUS_INSTALLED : self::STATUS_INSTALLED_MISSING;
}
else {
$this->statuses[$dao->full_name] = $codeExists ? self::STATUS_DISABLED : self::STATUS_DISABLED_MISSING;
}
}
}
return $this->statuses;
}
public function refresh() {
$this->statuses = NULL;
$this->fullContainer->refresh(); // and, indirectly, defaultContainer
$this->mapper->refresh();
}
// ----------------------
/**
* Find the $info and $typeManager for a $key
*
* @param $key
*
* @throws CRM_Extension_Exception
* @return array
* (0 => CRM_Extension_Info, 1 => CRM_Extension_Manager_Interface)
*/
private function _getInfoTypeHandler($key) {
$info = $this->mapper->keyToInfo($key); // throws Exception
if (array_key_exists($info->type, $this->typeManagers)) {
return array($info, $this->typeManagers[$info->type]);
}
else {
throw new CRM_Extension_Exception("Unrecognized extension type: " . $info->type);
}
}
/**
* Find the $info and $typeManager for a $key
*
* @param $key
*
* @throws CRM_Extension_Exception
* @return array
* (0 => CRM_Extension_Info, 1 => CRM_Extension_Manager_Interface)
*/
private function _getMissingInfoTypeHandler($key) {
$info = $this->createInfoFromDB($key);
if ($info) {
if (array_key_exists($info->type, $this->typeManagers)) {
return array($info, $this->typeManagers[$info->type]);
}
else {
throw new CRM_Extension_Exception("Unrecognized extension type: " . $info->type);
}
}
else {
throw new CRM_Extension_Exception("Failed to reconstruct missing extension: " . $key);
}
}
/**
* @param CRM_Extension_Info $info
*
* @return bool
*/
private function _createExtensionEntry(CRM_Extension_Info $info) {
$dao = new CRM_Core_DAO_Extension();
$dao->label = $info->label;
$dao->name = $info->name;
$dao->full_name = $info->key;
$dao->type = $info->type;
$dao->file = $info->file;
$dao->is_active = 1;
return (bool) ($dao->insert());
}
/**
* @param CRM_Extension_Info $info
*
* @return bool
*/
private function _updateExtensionEntry(CRM_Extension_Info $info) {
$dao = new CRM_Core_DAO_Extension();
$dao->full_name = $info->key;
if ($dao->find(TRUE)) {
$dao->label = $info->label;
$dao->name = $info->name;
$dao->full_name = $info->key;
$dao->type = $info->type;
$dao->file = $info->file;
$dao->is_active = 1;
return (bool) ($dao->update());
}
else {
return $this->_createExtensionEntry($info);
}
}
/**
* @param CRM_Extension_Info $info
*
* @throws CRM_Extension_Exception
*/
private function _removeExtensionEntry(CRM_Extension_Info $info) {
$dao = new CRM_Core_DAO_Extension();
$dao->full_name = $info->key;
if ($dao->find(TRUE)) {
if (CRM_Core_BAO_Extension::del($dao->id)) {
CRM_Core_Session::setStatus(ts('Selected option value has been deleted.'), ts('Deleted'), 'success');
}
else {
throw new CRM_Extension_Exception("Failed to remove extension entry");
}
} // else: post-condition already satisified
}
/**
* @param CRM_Extension_Info $info
* @param $isActive
*/
private function _setExtensionActive(CRM_Extension_Info $info, $isActive) {
CRM_Core_DAO::executeQuery('UPDATE civicrm_extension SET is_active = %1 where full_name = %2', array(
1 => array($isActive, 'Integer'),
2 => array($info->key, 'String'),
));
}
/**
* Auto-generate a place-holder for a missing extension using info from
* database.
*
* @param $key
* @return CRM_Extension_Info|NULL
*/
public function createInfoFromDB($key) {
$dao = new CRM_Core_DAO_Extension();
$dao->full_name = $key;
if ($dao->find(TRUE)) {
$info = new CRM_Extension_Info($dao->full_name, $dao->type, $dao->name, $dao->label, $dao->file);
return $info;
}
else {
return NULL;
}
}
/**
* Build a list of extensions to install, in an order that will satisfy dependencies.
*
* @param array $keys
* List of extensions to install.
* @return array
* List of extension keys, including dependencies, in order of installation.
*/
public function findInstallRequirements($keys) {
$infos = $this->mapper->getAllInfos();
$todoKeys = array_unique($keys); // array(string $key).
$doneKeys = array(); // array(string $key => 1);
$sorter = new \MJS\TopSort\Implementations\FixedArraySort();
while (!empty($todoKeys)) {
$key = array_shift($todoKeys);
if (isset($doneKeys[$key])) {
continue;
}
$doneKeys[$key] = 1;
/** @var CRM_Extension_Info $info */
$info = @$infos[$key];
if ($this->getStatus($key) === self::STATUS_INSTALLED) {
$sorter->add($key, array());
}
elseif ($info && $info->requires) {
$sorter->add($key, $info->requires);
$todoKeys = array_merge($todoKeys, $info->requires);
}
else {
$sorter->add($key, array());
}
}
return $sorter->sort();
}
/**
* Build a list of extensions to remove, in an order that will satisfy dependencies.
*
* @param array $keys
* List of extensions to install.
* @return array
* List of extension keys, including dependencies, in order of removal.
*/
public function findDisableRequirements($keys) {
$INSTALLED = array(
self::STATUS_INSTALLED,
self::STATUS_INSTALLED_MISSING,
);
$installedInfos = $this->filterInfosByStatus($this->mapper->getAllInfos(), $INSTALLED);
$revMap = CRM_Extension_Info::buildReverseMap($installedInfos);
$todoKeys = array_unique($keys);
$doneKeys = array();
$sorter = new \MJS\TopSort\Implementations\FixedArraySort();
while (!empty($todoKeys)) {
$key = array_shift($todoKeys);
if (isset($doneKeys[$key])) {
continue;
}
$doneKeys[$key] = 1;
if (isset($revMap[$key])) {
$requiredBys = CRM_Utils_Array::collect('key',
$this->filterInfosByStatus($revMap[$key], $INSTALLED));
$sorter->add($key, $requiredBys);
$todoKeys = array_merge($todoKeys, $requiredBys);
}
else {
$sorter->add($key, array());
}
}
return $sorter->sort();
}
/**
* @param $infos
* @param $filterStatuses
* @return array
*/
protected function filterInfosByStatus($infos, $filterStatuses) {
$matches = array();
foreach ($infos as $k => $v) {
if (in_array($this->getStatus($v->key), $filterStatuses)) {
$matches[$k] = $v;
}
}
return $matches;
}
}

View file

@ -0,0 +1,148 @@
<?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 |
+--------------------------------------------------------------------+
*/
/**
* The extension manager handles installing, disabling enabling, and
* uninstalling extensions.
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
class CRM_Extension_Manager_Base implements CRM_Extension_Manager_Interface {
/**
* @var bool hether to automatically uninstall and install during 'replace'
*/
public $autoReplace;
/**
* @param bool $autoReplace
* Whether to automatically uninstall and install during 'replace'.
*/
public function __construct($autoReplace = FALSE) {
$this->autoReplace = $autoReplace;
}
/**
* @inheritDoc
*
* @param CRM_Extension_Info $info
*/
public function onPreInstall(CRM_Extension_Info $info) {
}
/**
* @inheritDoc
*
* @param CRM_Extension_Info $info
*/
public function onPostInstall(CRM_Extension_Info $info) {
}
/**
* @inheritDoc
*
* @param CRM_Extension_Info $info
*/
public function onPostPostInstall(CRM_Extension_Info $info) {
}
/**
* @inheritDoc
*
* @param CRM_Extension_Info $info
*/
public function onPreEnable(CRM_Extension_Info $info) {
}
/**
* @inheritDoc
*
* @param CRM_Extension_Info $info
*/
public function onPostEnable(CRM_Extension_Info $info) {
}
/**
* @inheritDoc
*
* @param CRM_Extension_Info $info
*/
public function onPreDisable(CRM_Extension_Info $info) {
}
/**
* @inheritDoc
*
* @param CRM_Extension_Info $info
*/
public function onPostDisable(CRM_Extension_Info $info) {
}
/**
* @inheritDoc
*
* @param CRM_Extension_Info $info
*/
public function onPreUninstall(CRM_Extension_Info $info) {
}
/**
* @inheritDoc
*
* @param CRM_Extension_Info $info
*/
public function onPostUninstall(CRM_Extension_Info $info) {
}
/**
* @inheritDoc
*
* @param CRM_Extension_Info $oldInfo
* @param CRM_Extension_Info $newInfo
*/
public function onPreReplace(CRM_Extension_Info $oldInfo, CRM_Extension_Info $newInfo) {
if ($this->autoReplace) {
$this->onPreUninstall($oldInfo);
$this->onPostUninstall($oldInfo);
}
}
/**
* @inheritDoc
*
* @param CRM_Extension_Info $oldInfo
* @param CRM_Extension_Info $newInfo
*/
public function onPostReplace(CRM_Extension_Info $oldInfo, CRM_Extension_Info $newInfo) {
if ($this->autoReplace) {
$this->onPreInstall($oldInfo);
$this->onPostInstall($oldInfo);
}
}
}

View file

@ -0,0 +1,122 @@
<?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 |
+--------------------------------------------------------------------+
*/
/**
* The extension manager handles installing, disabling enabling, and
* uninstalling extensions.
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
interface CRM_Extension_Manager_Interface {
/**
* Perform type-specific installation logic (before marking the
* extension as installed or clearing the caches).
*
* @param CRM_Extension_Info $info
*/
public function onPreInstall(CRM_Extension_Info $info);
/**
* Perform type-specific installation logic (after marking the
* extension as installed but before clearing the caches).
*
* @param CRM_Extension_Info $info
*/
public function onPostInstall(CRM_Extension_Info $info);
/**
* Perform type-specific installation logic (after marking the
* extension as installed and clearing the caches).
*
* @param CRM_Extension_Info $info
*/
public function onPostPostInstall(CRM_Extension_Info $info);
/**
* @param CRM_Extension_Info $info
*/
public function onPreEnable(CRM_Extension_Info $info);
/**
* @param CRM_Extension_Info $info
*/
public function onPostEnable(CRM_Extension_Info $info);
/**
* Perform type-specific removal logic (before updating the extension
* row in the "civicrm_extension" table).
*
* @param CRM_Extension_Info $info
* May be generated from xml or DB (which is lossy).
* @see CRM_Extension_Manager::createInfoFromDB
*/
public function onPreDisable(CRM_Extension_Info $info);
/**
* Perform type-specific removal logic (after updating the extension
* row in the "civicrm_extension" table).
*
* @param CRM_Extension_Info $info
* May be generated from xml or DB (which is lossy).
* @see CRM_Extension_Manager::createInfoFromDB
*/
public function onPostDisable(CRM_Extension_Info $info);
/**
* Perform type-specific removal logic (before removing the extension
* row in the "civicrm_extension" table).
*
* @param CRM_Extension_Info $info
* May be generated from xml or DB (which is lossy).
* @see CRM_Extension_Manager::createInfoFromDB
*/
public function onPreUninstall(CRM_Extension_Info $info);
/**
* Perform type-specific removal logic (after removing the extension
* row in the "civicrm_extension" table).
*
* @param CRM_Extension_Info $info
* May be generated from xml or DB (which is lossy).
* @see CRM_Extension_Manager::createInfoFromDB
*/
public function onPostUninstall(CRM_Extension_Info $info);
/**
* @param CRM_Extension_Info $oldInfo
* @param CRM_Extension_Info $newInfo
*/
public function onPreReplace(CRM_Extension_Info $oldInfo, CRM_Extension_Info $newInfo);
/**
* @param CRM_Extension_Info $oldInfo
* @param CRM_Extension_Info $newInfo
*/
public function onPostReplace(CRM_Extension_Info $oldInfo, CRM_Extension_Info $newInfo);
}

View file

@ -0,0 +1,110 @@
<?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 stores logic for managing CiviCRM extensions.
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
class CRM_Extension_Manager_Module extends CRM_Extension_Manager_Base {
/**
* @param CRM_Extension_Mapper $mapper
*/
public function __construct(CRM_Extension_Mapper $mapper) {
parent::__construct(FALSE);
$this->mapper = $mapper;
}
/**
* @param CRM_Extension_Info $info
*/
public function onPreInstall(CRM_Extension_Info $info) {
$this->callHook($info, 'install');
$this->callHook($info, 'enable');
}
/**
* @param CRM_Extension_Info $info
*/
public function onPostPostInstall(CRM_Extension_Info $info) {
$this->callHook($info, 'postInstall');
}
/**
* @param CRM_Extension_Info $info
* @param string $hookName
*/
private function callHook(CRM_Extension_Info $info, $hookName) {
try {
$file = $this->mapper->keyToPath($info->key);
}
catch (CRM_Extension_Exception $e) {
return;
}
if (!file_exists($file)) {
return;
}
include_once $file;
$fnName = "{$info->file}_civicrm_{$hookName}";
if (function_exists($fnName)) {
$fnName();
}
}
/**
* @param CRM_Extension_Info $info
*
* @return bool
*/
public function onPreUninstall(CRM_Extension_Info $info) {
$this->callHook($info, 'uninstall');
return TRUE;
}
/**
* @param CRM_Extension_Info $info
*/
public function onPostUninstall(CRM_Extension_Info $info) {
}
/**
* @param CRM_Extension_Info $info
*/
public function onPreDisable(CRM_Extension_Info $info) {
$this->callHook($info, 'disable');
}
/**
* @param CRM_Extension_Info $info
*/
public function onPreEnable(CRM_Extension_Info $info) {
$this->callHook($info, 'enable');
}
}

View file

@ -0,0 +1,282 @@
<?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 stores logic for managing CiviCRM extensions.
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
class CRM_Extension_Manager_Payment extends CRM_Extension_Manager_Base {
/**
* @var CRM_Extension_Mapper
*/
protected $mapper;
/**
* @param CRM_Extension_Mapper $mapper
*/
public function __construct(CRM_Extension_Mapper $mapper) {
parent::__construct(TRUE);
$this->mapper = $mapper;
}
/**
* @inheritDoc
*
* @param CRM_Extension_Info $info
*/
public function onPreInstall(CRM_Extension_Info $info) {
$paymentProcessorTypes = $this->_getAllPaymentProcessorTypes('class_name');
if (array_key_exists($info->key, $paymentProcessorTypes)) {
CRM_Core_Error::fatal('This payment processor type is already installed.');
}
$ppByName = $this->_getAllPaymentProcessorTypes('name');
if (array_key_exists($info->name, $ppByName)) {
CRM_Core_Error::fatal('This payment processor type already exists.');
}
$dao = new CRM_Financial_DAO_PaymentProcessorType();
$dao->is_active = 1;
$dao->class_name = trim($info->key);
$dao->title = trim($info->name) . ' (' . trim($info->key) . ')';
$dao->name = trim($info->name);
$dao->description = trim($info->description);
$dao->user_name_label = trim($info->typeInfo['userNameLabel']);
$dao->password_label = trim($info->typeInfo['passwordLabel']);
$dao->signature_label = trim($info->typeInfo['signatureLabel']);
$dao->subject_label = trim($info->typeInfo['subjectLabel']);
$dao->url_site_default = trim($info->typeInfo['urlSiteDefault']);
$dao->url_api_default = trim($info->typeInfo['urlApiDefault']);
$dao->url_recur_default = trim($info->typeInfo['urlRecurDefault']);
$dao->url_site_test_default = trim($info->typeInfo['urlSiteTestDefault']);
$dao->url_api_test_default = trim($info->typeInfo['urlApiTestDefault']);
$dao->url_recur_test_default = trim($info->typeInfo['urlRecurTestDefault']);
$dao->url_button_default = trim($info->typeInfo['urlButtonDefault']);
$dao->url_button_test_default = trim($info->typeInfo['urlButtonTestDefault']);
switch (trim($info->typeInfo['billingMode'])) {
case 'form':
$dao->billing_mode = CRM_Core_Payment::BILLING_MODE_FORM;
break;
case 'button':
$dao->billing_mode = CRM_Core_Payment::BILLING_MODE_BUTTON;
break;
case 'notify':
$dao->billing_mode = CRM_Core_Payment::BILLING_MODE_NOTIFY;
break;
default:
CRM_Core_Error::fatal('Billing mode in info file has wrong value.');
}
$dao->is_recur = trim($info->typeInfo['isRecur']);
$dao->payment_type = trim($info->typeInfo['paymentType']);
$dao->save();
}
/**
* @inheritDoc
*
* @param CRM_Extension_Info $info
*/
public function onPostInstall(CRM_Extension_Info $info) {
$this->_runPaymentHook($info, 'install');
}
/**
* @inheritDoc
*
* @param CRM_Extension_Info $info
*/
public function onPreUninstall(CRM_Extension_Info $info) {
$paymentProcessorTypes = $this->_getAllPaymentProcessorTypes('class_name');
if (!array_key_exists($info->key, $paymentProcessorTypes)) {
CRM_Core_Error::fatal('This payment processor type is not registered.');
}
$dao = new CRM_Financial_DAO_PaymentProcessor();
$dao->payment_processor_type_id = $paymentProcessorTypes[$info->key];
$dao->find();
while ($dao->fetch()) {
throw new CRM_Extension_Exception_DependencyException('payment');
}
$this->_runPaymentHook($info, 'uninstall');
return CRM_Financial_BAO_PaymentProcessorType::del($paymentProcessorTypes[$info->key]);
}
/**
* @inheritDoc
*
* @param CRM_Extension_Info $info
*/
public function onPreDisable(CRM_Extension_Info $info) {
// HMM? // if ($this->type == 'payment' && $this->status != 'missing') {
$this->_runPaymentHook($info, 'disable');
$paymentProcessorTypes = $this->_getAllPaymentProcessorTypes('class_name');
CRM_Financial_BAO_PaymentProcessorType::setIsActive($paymentProcessorTypes[$info->key], 0);
}
/**
* @inheritDoc
*
* @param CRM_Extension_Info $info
*/
public function onPreEnable(CRM_Extension_Info $info) {
$paymentProcessorTypes = $this->_getAllPaymentProcessorTypes('class_name');
CRM_Financial_BAO_PaymentProcessorType::setIsActive($paymentProcessorTypes[$info->key], 1);
}
/**
* @inheritDoc
*
* @param CRM_Extension_Info $info
*/
public function onPostEnable(CRM_Extension_Info $info) {
// HMM? // if ($this->type == 'payment' && $this->status != 'missing') {
$this->_runPaymentHook($info, 'enable');
}
/**
* @param string $attr
* The attribute used to key the array.
* @return array
* ($attr => $id)
*/
private function _getAllPaymentProcessorTypes($attr) {
$ppt = array();
$dao = new CRM_Financial_DAO_PaymentProcessorType();
$dao->find();
while ($dao->fetch()) {
$ppt[$dao->$attr] = $dao->id;
}
return $ppt;
}
/**
* Run hooks in the payment processor class.
* Load requested payment processor and call the method specified.
*
* @param CRM_Extension_Info $info
* @param string $method
* The method to call in the payment processor class.
*/
private function _runPaymentHook(CRM_Extension_Info $info, $method) {
// Not concerned about performance at this stage, as these are seldom performed tasks
// (payment processor enable/disable/install/uninstall). May wish to implement some
// kind of registry/caching system if more hooks are added.
try {
$paymentClass = $this->mapper->keyToClass($info->key, 'payment');
$file = $this->mapper->classToPath($paymentClass);
if (!file_exists($file)) {
CRM_Core_Session::setStatus(ts('Failed to load file (%3) for payment processor (%1) while running "%2"', array(
1 => $info->key,
2 => $method,
3 => $file,
)), '', 'error');
return;
}
else {
require_once $file;
}
}
catch (CRM_Extension_Exception $e) {
CRM_Core_Session::setStatus(ts('Failed to determine file path for payment processor (%1) while running "%2"', array(
1 => $info->key,
2 => $method,
)), '', 'error');
return;
}
$processorDAO = CRM_Core_DAO::executeQuery(
" SELECT pp.id, ppt.class_name
FROM civicrm_extension ext
INNER JOIN civicrm_payment_processor_type ppt
ON ext.name = ppt.name
LEFT JOIN civicrm_payment_processor pp
ON ppt.id = pp.payment_processor_type_id
WHERE ext.type = 'payment'
AND ext.full_name = %1
",
array(
1 => array($info->key, 'String'),
)
);
while ($processorDAO->fetch()) {
$class_name = $processorDAO->class_name;
$processor_id = $processorDAO->id;
}
if (empty($class_name)) {
CRM_Core_Error::fatal("Unable to find payment processor in " . __CLASS__ . '::' . __METHOD__);
}
// In the case of uninstall, check for instances of PP first.
// Don't run hook if any are found.
if ($method == 'uninstall' && $processor_id > 0) {
return;
}
switch ($method) {
case 'install':
case 'uninstall':
case 'enable':
case 'disable':
// Instantiate PP - the getClass function allows us to do this when no payment processor instances exist.
$processorInstance = Civi\Payment\System::singleton()->getByClass($class_name);
// Does PP implement this method, and can we call it?
if (method_exists($processorInstance, $method) && is_callable(array(
$processorInstance,
$method,
))
) {
// If so, call it ...
$processorInstance->$method();
}
break;
default:
CRM_Core_Session::setStatus(ts("Unrecognized payment hook (%1) in %2::%3",
array(1 => $method, 2 => __CLASS__, 3 => __METHOD__)),
'', 'error');
}
}
}

View file

@ -0,0 +1,140 @@
<?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 stores logic for managing CiviCRM extensions.
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
class CRM_Extension_Manager_Report extends CRM_Extension_Manager_Base {
const REPORT_GROUP_NAME = 'report_template';
/**
* CRM_Extension_Manager_Report constructor.
*/
public function __construct() {
parent::__construct(TRUE);
$this->groupId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup',
self::REPORT_GROUP_NAME, 'id', 'name'
);
}
/**
* @param CRM_Extension_Info $info
*
* @throws Exception
*/
public function onPreInstall(CRM_Extension_Info $info) {
$customReports = $this->getCustomReportsByName();
if (array_key_exists($info->key, $customReports)) {
CRM_Core_Error::fatal('This report is already registered.');
}
if ($info->typeInfo['component'] === 'Contact') {
$compId = 'null';
}
else {
$comp = CRM_Core_Component::get($info->typeInfo['component']);
$compId = $comp->componentID;
}
if (empty($compId)) {
CRM_Core_Error::fatal("Component for which you're trying to install the extension (" . $info->typeInfo['component'] . ") is currently disabled.");
}
$weight = CRM_Utils_Weight::getDefaultWeight('CRM_Core_DAO_OptionValue',
array('option_group_id' => $this->groupId)
);
$ids = array();
$params = array(
'label' => $info->label . ' (' . $info->key . ')',
'value' => $info->typeInfo['reportUrl'],
'name' => $info->key,
'weight' => $weight,
'description' => $info->label . ' (' . $info->key . ')',
'component_id' => $compId,
'option_group_id' => $this->groupId,
'is_active' => 1,
);
$optionValue = CRM_Core_BAO_OptionValue::add($params, $ids);
}
/**
* @param CRM_Extension_Info $info
*
* @return bool
*/
public function onPreUninstall(CRM_Extension_Info $info) {
// if( !array_key_exists( $info->key, $this->customReports ) ) {
// CRM_Core_Error::fatal( 'This report is not registered.' );
// }
$customReports = $this->getCustomReportsByName();
$cr = $this->getCustomReportsById();
$id = $cr[$customReports[$info->key]];
$optionValue = CRM_Core_BAO_OptionValue::del($id);
return $optionValue ? TRUE : FALSE;
}
/**
* @param CRM_Extension_Info $info
*/
public function onPreDisable(CRM_Extension_Info $info) {
$customReports = $this->getCustomReportsByName();
$cr = $this->getCustomReportsById();
$id = $cr[$customReports[$info->key]];
$optionValue = CRM_Core_BAO_OptionValue::setIsActive($id, 0);
}
/**
* @param CRM_Extension_Info $info
*/
public function onPreEnable(CRM_Extension_Info $info) {
$customReports = $this->getCustomReportsByName();
$cr = $this->getCustomReportsById();
$id = $cr[$customReports[$info->key]];
$optionValue = CRM_Core_BAO_OptionValue::setIsActive($id, 1);
}
/**
* @return array
*/
public function getCustomReportsByName() {
return CRM_Core_OptionGroup::values(self::REPORT_GROUP_NAME, TRUE, FALSE, FALSE, NULL, 'name', FALSE, TRUE);
}
/**
* @return array
*/
public function getCustomReportsById() {
return CRM_Core_OptionGroup::values(self::REPORT_GROUP_NAME, FALSE, FALSE, FALSE, NULL, 'id', FALSE, TRUE);
}
}

View file

@ -0,0 +1,133 @@
<?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 stores logic for managing CiviCRM extensions.
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
class CRM_Extension_Manager_Search extends CRM_Extension_Manager_Base {
const CUSTOM_SEARCH_GROUP_NAME = 'custom_search';
/**
* CRM_Extension_Manager_Search constructor.
*/
public function __construct() {
parent::__construct(TRUE);
$this->groupId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup',
self::CUSTOM_SEARCH_GROUP_NAME, 'id', 'name'
);
}
/**
* @param CRM_Extension_Info $info
*
* @return bool
* @throws Exception
*/
public function onPreInstall(CRM_Extension_Info $info) {
$customSearchesByName = $this->getCustomSearchesByName();
if (array_key_exists($info->key, $customSearchesByName)) {
CRM_Core_Error::fatal('This custom search is already registered.');
}
$weight = CRM_Utils_Weight::getDefaultWeight('CRM_Core_DAO_OptionValue',
array('option_group_id' => $this->groupId)
);
$params = array(
'option_group_id' => $this->groupId,
'weight' => $weight,
'description' => $info->label . ' (' . $info->key . ')',
'name' => $info->key,
'value' => max($customSearchesByName) + 1,
'label' => $info->key,
'is_active' => 1,
);
$ids = array();
$optionValue = CRM_Core_BAO_OptionValue::add($params, $ids);
return $optionValue ? TRUE : FALSE;
}
/**
* @param CRM_Extension_Info $info
*
* @return bool
* @throws Exception
*/
public function onPreUninstall(CRM_Extension_Info $info) {
$customSearchesByName = $this->getCustomSearchesByName();
if (!array_key_exists($info->key, $customSearchesByName)) {
CRM_Core_Error::fatal('This custom search is not registered.');
}
$cs = $this->getCustomSearchesById();
$id = $cs[$customSearchesByName[$info->key]];
$optionValue = CRM_Core_BAO_OptionValue::del($id);
return TRUE;
}
/**
* @param CRM_Extension_Info $info
*/
public function onPreDisable(CRM_Extension_Info $info) {
$customSearchesByName = $this->getCustomSearchesByName();
$cs = $this->getCustomSearchesById();
$id = $cs[$customSearchesByName[$info->key]];
$optionValue = CRM_Core_BAO_OptionValue::setIsActive($id, 0);
}
/**
* @param CRM_Extension_Info $info
*/
public function onPreEnable(CRM_Extension_Info $info) {
$customSearchesByName = $this->getCustomSearchesByName();
$cs = $this->getCustomSearchesById();
$id = $cs[$customSearchesByName[$info->key]];
$optionValue = CRM_Core_BAO_OptionValue::setIsActive($id, 1);
}
/**
* @return array
*/
protected function getCustomSearchesByName() {
return CRM_Core_OptionGroup::values(self::CUSTOM_SEARCH_GROUP_NAME, TRUE, FALSE, FALSE, NULL, 'name', FALSE, TRUE);
}
/**
* @return array
*/
protected function getCustomSearchesById() {
return CRM_Core_OptionGroup::values(self::CUSTOM_SEARCH_GROUP_NAME, FALSE, FALSE, FALSE, NULL, 'id', FALSE, TRUE);
}
}

View file

@ -0,0 +1,470 @@
<?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();
}
}

View file

@ -0,0 +1,339 @@
<?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 glues together the various parts of the extension
* system.
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
class CRM_Extension_System {
private static $singleton;
private $cache = NULL;
private $fullContainer = NULL;
private $defaultContainer = NULL;
private $mapper = NULL;
private $manager = NULL;
private $browser = NULL;
private $downloader = NULL;
/**
* @var CRM_Extension_ClassLoader
* */
private $classLoader;
/**
* The URL of the remote extensions repository.
*
* @var string|FALSE
*/
private $_repoUrl = NULL;
/**
* @var array
* Construction parameters. These are primarily retained so
* that they can influence the cache name.
*/
protected $parameters;
/**
* @param bool $fresh
* TRUE to force creation of a new system.
*
* @return CRM_Extension_System
*/
public static function singleton($fresh = FALSE) {
if (!self::$singleton || $fresh) {
if (self::$singleton) {
self::$singleton = new CRM_Extension_System(self::$singleton->parameters);
}
else {
self::$singleton = new CRM_Extension_System();
}
}
return self::$singleton;
}
/**
* @param CRM_Extension_System $singleton
* The new, singleton extension system.
*/
public static function setSingleton(CRM_Extension_System $singleton) {
self::$singleton = $singleton;
}
/**
* @param array $parameters
* List of configuration values required by the extension system.
* Missing values will be guessed based on $config.
*/
public function __construct($parameters = array()) {
$config = CRM_Core_Config::singleton();
$parameters['extensionsDir'] = CRM_Utils_Array::value('extensionsDir', $parameters, $config->extensionsDir);
$parameters['extensionsURL'] = CRM_Utils_Array::value('extensionsURL', $parameters, $config->extensionsURL);
$parameters['resourceBase'] = CRM_Utils_Array::value('resourceBase', $parameters, $config->resourceBase);
$parameters['uploadDir'] = CRM_Utils_Array::value('uploadDir', $parameters, $config->uploadDir);
$parameters['userFrameworkBaseURL'] = CRM_Utils_Array::value('userFrameworkBaseURL', $parameters, $config->userFrameworkBaseURL);
if (!array_key_exists('civicrm_root', $parameters)) {
$parameters['civicrm_root'] = $GLOBALS['civicrm_root'];
}
if (!array_key_exists('cmsRootPath', $parameters)) {
$parameters['cmsRootPath'] = $config->userSystem->cmsRootPath();
}
if (!array_key_exists('domain_id', $parameters)) {
$parameters['domain_id'] = CRM_Core_Config::domainID();
}
ksort($parameters); // guaranteed ordering - useful for md5(serialize($parameters))
$this->parameters = $parameters;
}
/**
* Get a container which represents all available extensions.
*
* @return CRM_Extension_Container_Interface
*/
public function getFullContainer() {
if ($this->fullContainer === NULL) {
$containers = array();
if ($this->getDefaultContainer()) {
$containers['default'] = $this->getDefaultContainer();
}
$containers['civiroot'] = new CRM_Extension_Container_Basic(
$this->parameters['civicrm_root'],
$this->parameters['resourceBase'],
$this->getCache(),
'civiroot'
);
// TODO: CRM_Extension_Container_Basic( /sites/all/modules )
// TODO: CRM_Extension_Container_Basic( /sites/$domain/modules
// TODO: CRM_Extension_Container_Basic( /modules )
// TODO: CRM_Extension_Container_Basic( /vendors )
// At time of writing, D6, D7, and WP support cmsRootPath() but J does not
if (NULL !== $this->parameters['cmsRootPath']) {
$vendorPath = $this->parameters['cmsRootPath'] . DIRECTORY_SEPARATOR . 'vendor';
if (is_dir($vendorPath)) {
$containers['cmsvendor'] = new CRM_Extension_Container_Basic(
$vendorPath,
CRM_Utils_File::addTrailingSlash($this->parameters['userFrameworkBaseURL'], '/') . 'vendor',
$this->getCache(),
'cmsvendor'
);
}
}
$this->fullContainer = new CRM_Extension_Container_Collection($containers, $this->getCache(), 'full');
}
return $this->fullContainer;
}
/**
* Get the container to which new extensions are installed.
*
* This container should be a particular, writeable directory.
*
* @return CRM_Extension_Container_Default|FALSE (false if not configured)
*/
public function getDefaultContainer() {
if ($this->defaultContainer === NULL) {
if ($this->parameters['extensionsDir']) {
$this->defaultContainer = new CRM_Extension_Container_Default($this->parameters['extensionsDir'], $this->parameters['extensionsURL'], $this->getCache(), 'default');
}
else {
$this->defaultContainer = FALSE;
}
}
return $this->defaultContainer;
}
/**
* Get the service which provides runtime information about extensions.
*
* @return CRM_Extension_Mapper
*/
public function getMapper() {
if ($this->mapper === NULL) {
$this->mapper = new CRM_Extension_Mapper($this->getFullContainer(), $this->getCache(), 'mapper');
}
return $this->mapper;
}
/**
* @return \CRM_Extension_ClassLoader
*/
public function getClassLoader() {
if ($this->classLoader === NULL) {
$this->classLoader = new CRM_Extension_ClassLoader($this->getMapper(), $this->getFullContainer(), $this->getManager());
}
return $this->classLoader;
}
/**
* Get the service for enabling and disabling extensions.
*
* @return CRM_Extension_Manager
*/
public function getManager() {
if ($this->manager === NULL) {
$typeManagers = array(
'payment' => new CRM_Extension_Manager_Payment($this->getMapper()),
'report' => new CRM_Extension_Manager_Report(),
'search' => new CRM_Extension_Manager_Search(),
'module' => new CRM_Extension_Manager_Module($this->getMapper()),
);
$this->manager = new CRM_Extension_Manager($this->getFullContainer(), $this->getDefaultContainer(), $this->getMapper(), $typeManagers);
}
return $this->manager;
}
/**
* Get the service for finding remotely-available extensions
*
* @return CRM_Extension_Browser
*/
public function getBrowser() {
if ($this->browser === NULL) {
$cacheDir = NULL;
if (!empty($this->parameters['uploadDir'])) {
$cacheDir = CRM_Utils_File::addTrailingSlash($this->parameters['uploadDir']) . 'cache';
}
$this->browser = new CRM_Extension_Browser($this->getRepositoryUrl(), '', $cacheDir);
}
return $this->browser;
}
/**
* Get the service for loading code from remotely-available extensions
*
* @return CRM_Extension_Downloader
*/
public function getDownloader() {
if ($this->downloader === NULL) {
$basedir = ($this->getDefaultContainer() ? $this->getDefaultContainer()->getBaseDir() : NULL);
$this->downloader = new CRM_Extension_Downloader(
$this->getManager(),
$basedir,
CRM_Utils_File::tempdir() // WAS: $config->extensionsDir . DIRECTORY_SEPARATOR . 'tmp';
);
}
return $this->downloader;
}
/**
* @return CRM_Utils_Cache_Interface
*/
public function getCache() {
if ($this->cache === NULL) {
$cacheGroup = md5(serialize(array('ext', $this->parameters)));
// Extension system starts before container. Manage our own cache.
$this->cache = CRM_Utils_Cache::create(array(
'name' => $cacheGroup,
'type' => array('*memory*', 'SqlGroup', 'ArrayCache'),
'prefetch' => TRUE,
));
}
return $this->cache;
}
/**
* Determine the URL which provides a feed of available extensions.
*
* @return string|FALSE
*/
public function getRepositoryUrl() {
if (empty($this->_repoUrl) && $this->_repoUrl !== FALSE) {
$url = Civi::settings()->get('ext_repo_url');
// boolean false means don't try to check extensions
// CRM-10575
if ($url === FALSE) {
$this->_repoUrl = FALSE;
}
else {
$this->_repoUrl = CRM_Utils_System::evalUrl($url);
}
}
return $this->_repoUrl;
}
/**
* Take an extension's raw XML info and add information about the
* extension's status on the local system.
*
* The result format resembles the old CRM_Core_Extensions_Extension.
*
* @param CRM_Extension_Info $obj
*
* @return array
*/
public static function createExtendedInfo(CRM_Extension_Info $obj) {
$mapper = CRM_Extension_System::singleton()->getMapper();
$manager = CRM_Extension_System::singleton()->getManager();
$extensionRow = (array) $obj;
try {
$extensionRow['path'] = $mapper->keyToBasePath($obj->key);
}
catch (CRM_Extension_Exception $e) {
$extensionRow['path'] = '';
}
$extensionRow['status'] = $manager->getStatus($obj->key);
switch ($extensionRow['status']) {
case CRM_Extension_Manager::STATUS_UNINSTALLED:
$extensionRow['statusLabel'] = ''; // ts('Uninstalled');
break;
case CRM_Extension_Manager::STATUS_DISABLED:
$extensionRow['statusLabel'] = ts('Disabled');
break;
case CRM_Extension_Manager::STATUS_INSTALLED:
$extensionRow['statusLabel'] = ts('Enabled'); // ts('Installed');
break;
case CRM_Extension_Manager::STATUS_DISABLED_MISSING:
$extensionRow['statusLabel'] = ts('Disabled (Missing)');
break;
case CRM_Extension_Manager::STATUS_INSTALLED_MISSING:
$extensionRow['statusLabel'] = ts('Enabled (Missing)'); // ts('Installed');
break;
default:
$extensionRow['statusLabel'] = '(' . $extensionRow['status'] . ')';
}
return $extensionRow;
}
}

View file

@ -0,0 +1,73 @@
<?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 stores logic for managing schema upgrades in CiviCRM extensions.
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2017
*/
class CRM_Extension_Upgrades {
const QUEUE_NAME = 'ext-upgrade';
/**
* Determine whether any extensions have pending upgrades.
*
* @return bool
*/
public static function hasPending() {
$checks = CRM_Utils_Hook::upgrade('check');
if (is_array($checks)) {
foreach ($checks as $check) {
if ($check) {
return TRUE;
}
}
}
return FALSE;
}
/**
* Fill a queue with upgrade tasks.
*
* @return CRM_Queue_Queue
*/
public static function createQueue() {
$queue = CRM_Queue_Service::singleton()->create(array(
'type' => 'Sql',
'name' => self::QUEUE_NAME,
'reset' => TRUE,
));
CRM_Utils_Hook::upgrade('enqueue', $queue);
return $queue;
}
}