const STATUS_UNKNOWN = 'unknown';
/**
- * The extension is fully installed and enabled
+ * The extension is installed but the code is not accessible
*/
const STATUS_INSTALLED_MISSING = 'installed-missing';
/**
- * The extension is fully installed and enabled
+ * The extension was installed and is now disabled; the code is not accessible
*/
const STATUS_DISABLED_MISSING = 'disabled-missing';
public $fullContainer;
/**
- * @var CRM_Extension_Container_Basic|FALSE
+ * Default container.
+ *
+ * @var CRM_Extension_Container_Basic|false
*
* Note: Treat as private. This is only public to facilitate debugging.
*/
public $defaultContainer;
/**
+ * Mapper.
+ *
* @var CRM_Extension_Mapper
*
* Note: Treat as private. This is only public to facilitate debugging.
public $mapper;
/**
- * @var array (typeName => CRM_Extension_Manager_Interface)
+ * Type managers.
+ *
+ * @var array
+ *
+ * Format is (typeName => CRM_Extension_Manager_Interface)
*
* Note: Treat as private. This is only public to facilitate debugging.
*/
public $typeManagers;
/**
- * @var array (extensionKey => statusConstant)
+ * Statuses.
+ *
+ * @var array
+ *
+ * Format is (extensionKey => statusConstant)
*
* Note: Treat as private. This is only public to facilitate debugging.
*/
public $statuses;
/**
+ * Class constructor.
+ *
* @param CRM_Extension_Container_Interface $fullContainer
* @param CRM_Extension_Container_Basic|FALSE $defaultContainer
* @param CRM_Extension_Mapper $mapper
- * @param $typeManagers
+ * @param array $typeManagers
*/
public function __construct(CRM_Extension_Container_Interface $fullContainer, $defaultContainer, CRM_Extension_Mapper $mapper, $typeManagers) {
$this->fullContainer = $fullContainer;
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
+ // throws Exception
+ list ($oldInfo, $typeManager) = $this->_getInfoTypeHandler($newInfo->key);
$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(
+ 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.', [
1 => $newInfo->key,
2 => $oldPath,
- )));
+ ]));
}
break;
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
+ // throws Exception
+ list ($oldInfo, $typeManager) = $this->_getMissingInfoTypeHandler($newInfo->key);
break;
case self::STATUS_UNKNOWN:
/**
* Add records of the extension to the database -- and enable it
*
- * @param array $keys
- * List of extension keys.
+ * @param string|array $keys
+ * One or more extension keys.
* @throws CRM_Extension_Exception
*/
public function install($keys) {
+ $keys = (array) $keys;
$origStatuses = $this->getStatuses();
// TODO: to mitigate the risk of crashing during installation, scan
// keys/statuses/types before doing anything
+ // Check compatibility
+ $incompatible = [];
+ foreach ($keys as $key) {
+ if ($this->isIncompatible($key)) {
+ $incompatible[] = $key;
+ }
+ }
+ if ($incompatible) {
+ throw new CRM_Extension_Exception('Cannot install incompatible extension: ' . implode(', ', $incompatible));
+ }
+
foreach ($keys as $key) {
- list ($info, $typeManager) = $this->_getInfoTypeHandler($key); // throws Exception
+ /** @var CRM_Extension_Info $info */
+ /** @var CRM_Extension_Manager_Base $typeManager */
+ list ($info, $typeManager) = $this->_getInfoTypeHandler($key);
switch ($origStatuses[$key]) {
case self::STATUS_INSTALLED:
$schema->fixSchemaDifferences();
foreach ($keys as $key) {
- list ($info, $typeManager) = $this->_getInfoTypeHandler($key); // throws Exception
+ // throws Exception
+ list ($info, $typeManager) = $this->_getInfoTypeHandler($key);
switch ($origStatuses[$key]) {
case self::STATUS_INSTALLED:
}
/**
- * Add records of the extension to the database -- and enable it
+ * Disable extension without removing record from db.
*
- * @param array $keys
- * List of extension keys.
+ * @param string|array $keys
+ * One or more extension keys.
* @throws CRM_Extension_Exception
*/
public function disable($keys) {
+ $keys = (array) $keys;
$origStatuses = $this->getStatuses();
// TODO: to mitigate the risk of crashing during installation, scan
sort($keys);
$disableRequirements = $this->findDisableRequirements($keys);
- sort($disableRequirements); // This munges order, but makes it comparable.
+ // This munges order, but makes it comparable.
+ sort($disableRequirements);
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
+ // throws Exception
+ list ($info, $typeManager) = $this->_getInfoTypeHandler($key);
$typeManager->onPreDisable($info);
$this->_setExtensionActive($info, 0);
$typeManager->onPostDisable($info);
break;
case self::STATUS_INSTALLED_MISSING:
- list ($info, $typeManager) = $this->_getMissingInfoTypeHandler($key); // throws Exception
+ // throws Exception
+ list ($info, $typeManager) = $this->_getMissingInfoTypeHandler($key);
$typeManager->onPreDisable($info);
$this->_setExtensionActive($info, 0);
$typeManager->onPostDisable($info);
/**
* 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.
+ * @param string|array $keys
+ * One or more extension keys.
* @throws CRM_Extension_Exception
*/
public function uninstall($keys) {
+ $keys = (array) $keys;
$origStatuses = $this->getStatuses();
// TODO: to mitigate the risk of crashing during installation, scan
throw new CRM_Extension_Exception("Cannot uninstall extension; disable it first: $key");
case self::STATUS_DISABLED:
- list ($info, $typeManager) = $this->_getInfoTypeHandler($key); // throws Exception
+ // throws Exception
+ list ($info, $typeManager) = $this->_getInfoTypeHandler($key);
$typeManager->onPreUninstall($info);
$this->_removeExtensionEntry($info);
$typeManager->onPostUninstall($info);
break;
case self::STATUS_DISABLED_MISSING:
- list ($info, $typeManager) = $this->_getMissingInfoTypeHandler($key); // throws Exception
+ // throws Exception
+ list ($info, $typeManager) = $this->_getMissingInfoTypeHandler($key);
$typeManager->onPreUninstall($info);
$this->_removeExtensionEntry($info);
$typeManager->onPostUninstall($info);
* @param $key
*
* @return string
- * constant (STATUS_INSTALLED, STATUS_DISABLED, STATUS_UNINSTALLED, STATUS_UNKNOWN)
+ * constant self::STATUS_*
*/
public function getStatus($key) {
$statuses = $this->getStatuses();
}
}
+ /**
+ * Check if a given extension is incompatible with this version of CiviCRM
+ *
+ * @param $key
+ * @return bool|array
+ */
+ public function isIncompatible($key) {
+ $info = CRM_Extension_System::getCompatibilityInfo();
+ return $info[$key] ?? FALSE;
+ }
+
/**
* Determine the status of all extensions.
*
*/
public function getStatuses() {
if (!is_array($this->statuses)) {
- $this->statuses = array();
+ $compat = CRM_Extension_System::getCompatibilityInfo();
+
+ $this->statuses = [];
foreach ($this->fullContainer->getKeys() as $key) {
$this->statuses[$key] = self::STATUS_UNINSTALLED;
catch (CRM_Extension_Exception $e) {
$codeExists = FALSE;
}
- if ($dao->is_active) {
+ if (!empty($compat[$dao->full_name]['force-uninstall'])) {
+ $this->statuses[$dao->full_name] = self::STATUS_UNINSTALLED;
+ }
+ elseif ($dao->is_active) {
$this->statuses[$dao->full_name] = $codeExists ? self::STATUS_INSTALLED : self::STATUS_INSTALLED_MISSING;
}
else {
public function refresh() {
$this->statuses = NULL;
- $this->fullContainer->refresh(); // and, indirectly, defaultContainer
+ // and, indirectly, defaultContainer
+ $this->fullContainer->refresh();
$this->mapper->refresh();
}
*
* @throws CRM_Extension_Exception
* @return array
- * (0 => CRM_Extension_Info, 1 => CRM_Extension_Manager_Interface)
+ * [CRM_Extension_Info, CRM_Extension_Manager_Interface]
*/
private function _getInfoTypeHandler($key) {
- $info = $this->mapper->keyToInfo($key); // throws Exception
+ // throws Exception
+ $info = $this->mapper->keyToInfo($key);
if (array_key_exists($info->type, $this->typeManagers)) {
- return array($info, $this->typeManagers[$info->type]);
+ return [$info, $this->typeManagers[$info->type]];
}
else {
throw new CRM_Extension_Exception("Unrecognized extension type: " . $info->type);
*
* @throws CRM_Extension_Exception
* @return array
- * (0 => CRM_Extension_Info, 1 => CRM_Extension_Manager_Interface)
+ * [CRM_Extension_Info, 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]);
+ return [$info, $this->typeManagers[$info->type]];
}
else {
throw new CRM_Extension_Exception("Unrecognized extension type: " . $info->type);
* @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'),
- ));
+ CRM_Core_DAO::executeQuery('UPDATE civicrm_extension SET is_active = %1 where full_name = %2', [
+ 1 => [$isActive, 'Integer'],
+ 2 => [$info->key, 'String'],
+ ]);
}
/**
*/
public function findInstallRequirements($keys) {
$infos = $this->mapper->getAllInfos();
- $todoKeys = array_unique($keys); // array(string $key).
- $doneKeys = array(); // array(string $key => 1);
+ // array(string $key).
+ $todoKeys = array_unique($keys);
+ // array(string $key => 1);
+ $doneKeys = [];
$sorter = new \MJS\TopSort\Implementations\FixedArraySort();
while (!empty($todoKeys)) {
$info = @$infos[$key];
if ($this->getStatus($key) === self::STATUS_INSTALLED) {
- $sorter->add($key, array());
+ $sorter->add($key, []);
}
elseif ($info && $info->requires) {
$sorter->add($key, $info->requires);
$todoKeys = array_merge($todoKeys, $info->requires);
}
else {
- $sorter->add($key, array());
+ $sorter->add($key, []);
}
}
return $sorter->sort();
* List of extension keys, including dependencies, in order of removal.
*/
public function findDisableRequirements($keys) {
- $INSTALLED = array(
+ $INSTALLED = [
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();
+ $doneKeys = [];
$sorter = new \MJS\TopSort\Implementations\FixedArraySort();
while (!empty($todoKeys)) {
$todoKeys = array_merge($todoKeys, $requiredBys);
}
else {
- $sorter->add($key, array());
+ $sorter->add($key, []);
}
}
return $sorter->sort();
* @return array
*/
protected function filterInfosByStatus($infos, $filterStatuses) {
- $matches = array();
+ $matches = [];
foreach ($infos as $k => $v) {
if (in_array($this->getStatus($v->key), $filterStatuses)) {
$matches[$k] = $v;