Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dev/core#2547 - Add base-upgrader to core #20090

Merged
merged 7 commits into from
Apr 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CRM/Extension/Info.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ class CRM_Extension_Info {
*/
public $maintainer = NULL;

/**
* @var string|null
* The name of a class which handles the install/upgrade lifecycle.
* @see \CRM_Extension_Upgrader_Interface
*/
public $upgrader = NULL;

/**
* Load extension info an XML file.
*
Expand Down Expand Up @@ -150,6 +157,7 @@ public function parse($info) {
$this->type = (string) $info->attributes()->type;
$this->file = (string) $info->file;
$this->label = (string) $info->name;
$this->upgrader = (string) $info->upgrader;

// Convert first level variables to CRM_Core_Extension properties
// and deeper into arrays. An exception for URLS section, since
Expand Down
3 changes: 3 additions & 0 deletions CRM/Extension/Manager/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ private function callHook(CRM_Extension_Info $info, $hookName) {
if (function_exists($fnName)) {
$fnName();
}
if ($info->upgrader) {
$this->mapper->getUpgrader($info->key)->notify($hookName);
}
}

/**
Expand Down
38 changes: 38 additions & 0 deletions CRM/Extension/Mapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ class CRM_Extension_Mapper {

protected $civicrmUrl;

/**
* @var array
* Array(string $extKey => CRM_Extension_Upgrader_Interface $upgrader)
*/
protected $upgraders = [];

/**
* @param CRM_Extension_Container_Interface $container
* @param CRM_Utils_Cache_Interface $cache
Expand Down Expand Up @@ -555,4 +561,36 @@ public function getUpgradeLink($remoteExtensionInfo, $localExtensionInfo) {
}
}

/**
* @param string $key
* Long name of the extension.
* Ex: 'org.example.myext'
*
* @return \CRM_Extension_Upgrader_Interface
*/
public function getUpgrader(string $key) {
if (!array_key_exists($key, $this->upgraders)) {
$this->upgraders[$key] = NULL;

try {
$info = $this->keyToInfo($key);
}
catch (CRM_Extension_Exception_ParseException $e) {
CRM_Core_Session::setStatus(ts('Parse error in extension: %1', [
1 => $e->getMessage(),
]), '', 'error');
CRM_Core_Error::debug_log_message("Parse error in extension: " . $e->getMessage());
return NULL;
}

if (!empty($info->upgrader)) {
$class = $info->upgrader;
$u = new $class();
$u->init(['key' => $key]);
$this->upgraders[$key] = $u;
}
}
return $this->upgraders[$key];
}

}
134 changes: 134 additions & 0 deletions CRM/Extension/Upgrader/Base.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php

/**
* Base class which provides helpers to execute upgrade logic.
*
* LIFECYCLE METHODS: Subclasses may optionally define install(), postInstall(),
* uninstall(), enable(), disable().
*
* UPGRADE METHODS: Subclasses may define any number of methods named "upgrade_NNNN()".
* Each value of NNNN is treated as a new schema revision. (See also: RevisionsTrait)
*
* QUEUE METHODS: Upgrade tasks execute within a queue. If an upgrader needs to perform
* a large amount of work, it can use "addTask()" / "prependTask()" / "appendTask()".
* (See also: QueueTrait)
*
* EXECUTE METHODS: When writing lifecycle methods, upgrade methods, or queue
* tasks, you may wish to execute common steps like "run a SQL file".
* (See also: TasksTrait)
*/
class CRM_Extension_Upgrader_Base implements CRM_Extension_Upgrader_Interface {

use CRM_Extension_Upgrader_IdentityTrait;
use CRM_Extension_Upgrader_QueueTrait;
use CRM_Extension_Upgrader_RevisionsTrait;
use CRM_Extension_Upgrader_TasksTrait;

/**
* {@inheritDoc}
*/
public function notify(string $event, array $params = []) {
$cb = [$this, 'on' . ucfirst($event)];
return is_callable($cb) ? call_user_func_array($cb, $params) : NULL;
}

// ******** Hook delegates ********

/**
* @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_install
*/
public function onInstall() {
$files = glob($this->getExtensionDir() . '/sql/*_install.sql');
if (is_array($files)) {
foreach ($files as $file) {
CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file);
}
}
$files = glob($this->getExtensionDir() . '/sql/*_install.mysql.tpl');
if (is_array($files)) {
foreach ($files as $file) {
$this->executeSqlTemplate($file);
}
}
$files = glob($this->getExtensionDir() . '/xml/*_install.xml');
if (is_array($files)) {
foreach ($files as $file) {
$this->executeCustomDataFileByAbsPath($file);
}
}
if (is_callable([$this, 'install'])) {
$this->install();
}
}

/**
* @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_postInstall
*/
public function onPostInstall() {
$revisions = $this->getRevisions();
if (!empty($revisions)) {
$this->setCurrentRevision(max($revisions));
}
if (is_callable([$this, 'postInstall'])) {
$this->postInstall();
}
}

/**
* @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_unnstall
*/
public function onUninstall() {
$files = glob($this->getExtensionDir() . '/sql/*_uninstall.mysql.tpl');
if (is_array($files)) {
foreach ($files as $file) {
$this->executeSqlTemplate($file);
}
}
if (is_callable([$this, 'uninstall'])) {
$this->uninstall();
}
$files = glob($this->getExtensionDir() . '/sql/*_uninstall.sql');
if (is_array($files)) {
foreach ($files as $file) {
CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file);
}
}
}

/**
* @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_enable
*/
public function onEnable() {
// stub for possible future use
if (is_callable([$this, 'enable'])) {
$this->enable();
}
}

/**
* @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_disable
*/
public function onDisable() {
// stub for possible future use
if (is_callable([$this, 'disable'])) {
$this->disable();
}
}

/**
* @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_upgrade
*/
public function onUpgrade($op, CRM_Queue_Queue $queue = NULL) {
switch ($op) {
case 'check':
return [$this->hasPendingRevisions()];

case 'enqueue':
$this->setQueue($queue);
return $this->enqueuePendingRevisions();

default:
}
}

}
55 changes: 55 additions & 0 deletions CRM/Extension/Upgrader/IdentityTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php
/*
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC. All rights reserved. |
| |
| This work is published under the GNU AGPLv3 license with some |
| permitted exceptions and without any warranty. For full license |
| and copyright information, see https://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/

/**
* Track minimal information which identifies the target extension.
*/
trait CRM_Extension_Upgrader_IdentityTrait {

/**
* @var string
* eg 'com.example.myextension'
*/
protected $extensionName;

/**
* @var string
* full path to the extension's source tree
*/
protected $extensionDir;

/**
* {@inheritDoc}
*/
public function init(array $params) {
$this->extensionName = $params['key'];
$system = CRM_Extension_System::singleton();
$mapper = $system->getMapper();
$this->extensionDir = $mapper->keyToBasePath($this->extensionName);
}

/**
* @return string
* Ex: 'org.example.foobar'
*/
public function getExtensionKey() {
return $this->extensionName;
}

/**
* @return string
* Ex: '/var/www/sites/default/ext/org.example.foobar'
*/
public function getExtensionDir() {
return $this->extensionDir;
}

}
29 changes: 29 additions & 0 deletions CRM/Extension/Upgrader/Interface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

/**
* An "upgrader" is a class that handles the DB install+upgrade lifecycle
* for an extension.
*/
interface CRM_Extension_Upgrader_Interface {

/**
* @param array $params
* - string $key: Long form name ('org.example.myext')
*/
public function init(array $params);

/**
* Notify the upgrader about a key lifecycle event, such as installation or uninstallation.
*
* Each event corresponds to a hook, such as `hook_civicrm_install` or `hook_civicrm_upgrade`.
*
* @param string $event
* One of the following: 'install', 'onPostInstall', 'enable', 'disable', 'uninstall', 'upgrade'
* @param array $params
* Any data that would ordinarily be provided via the equivalent hook.
*
* @return mixed
*/
public function notify(string $event, array $params = []);

}
Loading