From: Tim Otten Date: Sat, 26 Mar 2016 21:14:11 +0000 (-0700) Subject: CRM-16243 - Extension API - Manage extensions by path X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=3b3f6d236263481cc682e2bd5a526fead88c4d2a;p=civicrm-core.git CRM-16243 - Extension API - Manage extensions by path This allows one to enable or disable a series of extensions by path. This should be useful, for example, when integrating with `composer` or `drush make`. Without any knowledge of the specific extensions involved, one might: * If you download a bunch of extensions to a common dir (e.g. composer's `vendor/`) and need to enable them all, then run `cv api extension.install path=$PWD/vendor/*` (circa `post-install-cmd`) * If you're deleting a specific directory (e.g. via composer's `uninstall`), then remove it gracefully from the DB by running `cv api extension.disable path=$PKGDIR` (circa `pre-package-uninstall`) --- diff --git a/CRM/Extension/Mapper.php b/CRM/Extension/Mapper.php index 74fb01ab92..12f624529b 100644 --- a/CRM/Extension/Mapper.php +++ b/CRM/Extension/Mapper.php @@ -340,6 +340,41 @@ class CRM_Extension_Mapper { 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(...). diff --git a/api/v3/Extension.php b/api/v3/Extension.php index aa0e38d912..9e5fcf1c2e 100644 --- a/api/v3/Extension.php +++ b/api/v3/Extension.php @@ -41,6 +41,7 @@ define('API_V3_EXTENSION_DELIMITER', ','); * Input parameters. * - key: string, eg "com.example.myextension" * - keys: array of string, eg array("com.example.myextension1", "com.example.myextension2") + * - path: string, e.g. "/var/www/extensions/*" * * Using 'keys' should be more performant than making multiple API calls with 'key' * @@ -70,11 +71,15 @@ function civicrm_api3_extension_install($params) { function _civicrm_api3_extension_install_spec(&$fields) { $fields['keys'] = array( 'title' => 'Extension Key(s)', - 'api.required' => 1, 'api.aliases' => array('key'), 'type' => CRM_Utils_Type::T_STRING, 'description' => 'Fully qualified name of one or more extensions', ); + $fields['path'] = array( + 'title' => 'Extension Path', + 'type' => CRM_Utils_Type::T_STRING, + 'description' => 'The path to the extension. May use wildcard ("*").', + ); } /** @@ -114,6 +119,7 @@ function civicrm_api3_extension_upgrade() { * Input parameters. * - key: string, eg "com.example.myextension" * - keys: array of string, eg array("com.example.myextension1", "com.example.myextension2") + * - path: string, e.g. "/var/www/vendor/foo/myext" or "/var/www/vendor/*" * * Using 'keys' should be more performant than making multiple API calls with 'key' * @@ -145,6 +151,7 @@ function _civicrm_api3_extension_enable_spec(&$fields) { * Input parameters. * - key: string, eg "com.example.myextension" * - keys: array of string, eg array("com.example.myextension1", "com.example.myextension2") + * - path: string, e.g. "/var/www/vendor/foo/myext" or "/var/www/vendor/*" * * Using 'keys' should be more performant than making multiple API calls with 'key' * @@ -175,6 +182,7 @@ function _civicrm_api3_extension_disable_spec(&$fields) { * Input parameters. * - key: string, eg "com.example.myextension" * - keys: array of string, eg array("com.example.myextension1", "com.example.myextension2") + * - path: string, e.g. "/var/www/vendor/foo/myext" or "/var/www/vendor/*" * * Using 'keys' should be more performant than making multiple API calls with 'key' * @@ -394,11 +402,16 @@ function civicrm_api3_extension_getremote($params) { * * @param array $params * @param string $key - * API request params with 'keys'. + * API request params with 'keys' or 'path'. + * - keys: A comma-delimited list of extension names + * - path: An absolute directory path. May append '*' to match all sub-directories. * * @return array */ function _civicrm_api3_getKeys($params, $key = 'keys') { + if ($key == 'path') { + return CRM_Extension_System::singleton()->getMapper()->getKeysByPath($params['path']); + } if (isset($params[$key])) { if (is_array($params[$key])) { return $params[$key]; @@ -408,7 +421,5 @@ function _civicrm_api3_getKeys($params, $key = 'keys') { } return explode(API_V3_EXTENSION_DELIMITER, $params[$key]); } - else { - return array(); - } + throw new API_Exception("Missing required parameter: key, keys, or path"); } diff --git a/tests/phpunit/CRM/Extension/MapperTest.php b/tests/phpunit/CRM/Extension/MapperTest.php index c84b53e6a1..748d6ab16e 100644 --- a/tests/phpunit/CRM/Extension/MapperTest.php +++ b/tests/phpunit/CRM/Extension/MapperTest.php @@ -5,6 +5,22 @@ * @group headless */ class CRM_Extension_MapperTest extends CiviUnitTestCase { + + /** + * @var string + */ + protected $basedir, $basedir2; + + /** + * @var CRM_Extension_Container_Interface + */ + protected $container, $containerWithSlash; + + /** + * @var CRM_Extension_Mapper + */ + protected $mapper, $mapperWithSlash; + public function setUp() { parent::setUp(); list ($this->basedir, $this->container) = $this->_createContainer(); @@ -72,6 +88,27 @@ class CRM_Extension_MapperTest extends CiviUnitTestCase { $this->assertEquals(rtrim($config->resourceBase, '/'), $this->mapperWithSlash->keyToUrl('civicrm')); } + public function testGetKeysByPath() { + $mappers = array( + $this->basedir => $this->mapper, + $this->basedir2 => $this->mapperWithSlash, + ); + foreach ($mappers as $basedir => $mapper) { + /** @var CRM_Extension_Mapper $mapper */ + $this->assertEquals(array(), $mapper->getKeysByPath($basedir)); + $this->assertEquals(array(), $mapper->getKeysByPath($basedir . '/weird')); + $this->assertEquals(array(), $mapper->getKeysByPath($basedir . '/weird/')); + $this->assertEquals(array(), $mapper->getKeysByPath($basedir . '/weird//')); + $this->assertEquals(array('test.foo.bar'), $mapper->getKeysByPath($basedir . '/*')); + $this->assertEquals(array('test.foo.bar'), $mapper->getKeysByPath($basedir . '//*')); + $this->assertEquals(array('test.foo.bar'), $mapper->getKeysByPath($basedir . '/weird/*')); + $this->assertEquals(array('test.foo.bar'), $mapper->getKeysByPath($basedir . '/weird/foobar')); + $this->assertEquals(array('test.foo.bar'), $mapper->getKeysByPath($basedir . '/weird/foobar/')); + $this->assertEquals(array('test.foo.bar'), $mapper->getKeysByPath($basedir . '/weird/foobar//')); + $this->assertEquals(array('test.foo.bar'), $mapper->getKeysByPath($basedir . '/weird/foobar/*')); + } + } + /** * @param CRM_Utils_Cache_Interface $cache * @param null $cacheKey