Commit 66165138 authored by totten's avatar totten Committed by GitHub

Merge pull request #11101 from JohnFF/feature/CRM-16243-sprint-b

CRM-16243 - Activate extension dependencies
parents 7844184f 664e998e
......@@ -42,6 +42,13 @@ class CRM_Admin_Form_Extensions extends CRM_Admin_Form {
public function preProcess() {
parent::preProcess();
$mainPage = new CRM_Admin_Page_Extensions();
$localExtensionRows = $mainPage->formatLocalExtensionRows();
$this->assign('localExtensionRows', $localExtensionRows);
$remoteExtensionRows = $mainPage->formatRemoteExtensionRows($localExtensionRows);
$this->assign('remoteExtensionRows', $remoteExtensionRows);
$this->_key = CRM_Utils_Request::retrieve('key', 'String',
$this, FALSE, 0
);
......@@ -180,12 +187,12 @@ class CRM_Admin_Form_Extensions extends CRM_Admin_Form {
}
if ($this->_action & CRM_Core_Action::ADD) {
CRM_Extension_System::singleton()->getManager()->install(array($this->_key));
civicrm_api3('Extension', 'install', array('keys' => $this->_key));
CRM_Core_Session::setStatus("", ts('Extension Installed'), "success");
}
if ($this->_action & CRM_Core_Action::ENABLE) {
CRM_Extension_System::singleton()->getManager()->enable(array($this->_key));
civicrm_api3('Extension', 'enable', array('keys' => $this->_key));
CRM_Core_Session::setStatus("", ts('Extension Enabled'), "success");
}
......
......@@ -125,8 +125,6 @@ class CRM_Admin_Page_Extensions extends CRM_Core_Page_Basic {
* Browse all options.
*/
public function browse() {
$mapper = CRM_Extension_System::singleton()->getMapper();
$manager = CRM_Extension_System::singleton()->getManager();
// build announcements at the top of the page
$this->assign('extAddNewEnabled', CRM_Extension_System::singleton()->getBrowser()->isEnabled());
......@@ -145,7 +143,23 @@ class CRM_Admin_Page_Extensions extends CRM_Core_Page_Basic {
// TODO: Debate whether to immediately detect changes in underlying source tree
// $manager->refresh();
// build list of local extensions
$localExtensionRows = $this->formatLocalExtensionRows();
$this->assign('localExtensionRows', $localExtensionRows);
$remoteExtensionRows = $this->formatRemoteExtensionRows($localExtensionRows);
$this->assign('remoteExtensionRows', $remoteExtensionRows);
}
/**
* Get the list of local extensions and format them as a table with
* status and action data.
*
* @return array
*/
public function formatLocalExtensionRows() {
$mapper = CRM_Extension_System::singleton()->getMapper();
$manager = CRM_Extension_System::singleton()->getManager();
$localExtensionRows = array(); // array($pseudo_id => extended_CRM_Extension_Info)
$keys = array_keys($manager->getStatuses());
sort($keys);
......@@ -203,8 +217,17 @@ class CRM_Admin_Page_Extensions extends CRM_Core_Page_Basic {
$localExtensionRows[$row['id']] = $row;
}
$this->assign('localExtensionRows', $localExtensionRows);
return $localExtensionRows;
}
/**
* Get the list of local extensions and format them as a table with
* status and action data.
*
* @param array $localExtensionRows
* @return array
*/
public function formatRemoteExtensionRows($localExtensionRows) {
try {
$remoteExtensions = CRM_Extension_System::singleton()->getBrowser()->getExtensions();
}
......@@ -232,13 +255,16 @@ class CRM_Admin_Page_Extensions extends CRM_Core_Page_Basic {
$row['id']
);
if (isset($localExtensionRows[$info->key])) {
if (version_compare($localExtensionRows[$info->key]['version'], $info->version, '<')) {
$row['is_upgradeable'] = TRUE;
if (array_key_exists('version', $localExtensionRows[$info->key])) {
if (version_compare($localExtensionRows[$info->key]['version'], $info->version, '<')) {
$row['is_upgradeable'] = TRUE;
}
}
}
$remoteExtensionRows[$row['id']] = $row;
}
$this->assign('remoteExtensionRows', $remoteExtensionRows);
return $remoteExtensionRows;
}
/**
......
......@@ -51,6 +51,12 @@ class CRM_Extension_Info {
*/
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.
*
......@@ -90,6 +96,27 @@ class CRM_Extension_Info {
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
......@@ -141,6 +168,12 @@ class CRM_Extension_Info {
);
}
}
elseif ($attr === 'requires') {
$this->requires = array();
foreach ($val->ext as $ext) {
$this->requires[] = (string) $ext;
}
}
else {
$this->$attr = CRM_Utils_XML::xmlObjToArray($val);
}
......
......@@ -222,6 +222,11 @@ class CRM_Extension_Manager {
$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:
......@@ -229,6 +234,11 @@ class CRM_Extension_Manager {
$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:
......@@ -292,6 +302,13 @@ class CRM_Extension_Manager {
// 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:
......@@ -568,4 +585,96 @@ class CRM_Extension_Manager {
}
}
/**
* 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, $info->requires);
}
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;
}
}
......@@ -340,6 +340,54 @@ 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(...).
* @throws \CRM_Extension_Exception
* @throws \Exception
*/
public function getAllInfos() {
foreach ($this->container->getKeys() as $key) {
$this->keyToInfo($key);
}
return $this->infos;
}
/**
* @param string $name
*
......
......@@ -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'
*
......@@ -53,7 +54,8 @@ function civicrm_api3_extension_install($params) {
}
try {
CRM_Extension_System::singleton()->getManager()->install($keys);
$manager = CRM_Extension_System::singleton()->getManager();
$manager->install($manager->findInstallRequirements($keys));
}
catch (CRM_Extension_Exception $e) {
return civicrm_api3_create_error($e->getMessage());
......@@ -69,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 ("*").',
);
}
/**
......@@ -113,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'
*
......@@ -124,7 +131,8 @@ function civicrm_api3_extension_enable($params) {
return civicrm_api3_create_success();
}
CRM_Extension_System::singleton()->getManager()->enable($keys);
$manager = CRM_Extension_System::singleton()->getManager();
$manager->enable($manager->findInstallRequirements($keys));
return civicrm_api3_create_success();
}
......@@ -143,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'
*
......@@ -173,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'
*
......@@ -392,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];
......
......@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "1d0ba40a540772d8ca8b73f72fff02d8",
"content-hash": "6abd711477a577550967549258a7aec0",
"packages": [
{
"name": "civicrm/civicrm-cxn-rpc",
......@@ -145,6 +145,46 @@
"homepage": "http://code.google.com/p/phpquery/",
"time": "2013-03-21T12:39:33+00:00"
},
{
"name": "marcj/topsort",
"version": "dev-1.0-php53",
"source": {
"type": "git",
"url": "https://github.com/totten/topsort.php.git",
"reference": "2765723d36f0e536d987e42cbc60de52209212bd"
},
"require": {
"php": ">=5.3"
},
"require-dev": {
"codeclimate/php-test-reporter": "dev-master",
"phpunit/phpunit": "~4.0",
"symfony/console": "~2.5"
},
"type": "library",
"autoload": {
"psr-4": {
"MJS\\TopSort\\": "src/",
"MJS\\TopSort\\Tests\\": "tests/Tests/"
}
},
"license": [
"MIT"
],
"authors": [
{
"name": "Marc J. Schmidt",
"email": "marc@marcjschmidt.de"
}
],
"description": "High-Performance TopSort/Dependency resolving algorithm",
"keywords": [
"dependency resolving",
"topological sort",
"topsort"
],
"time": "2016-03-25 21:38:05"
},
{
"name": "pclzip/pclzip",
"version": "2.8.2",
......@@ -1567,6 +1607,7 @@
"minimum-stability": "stable",
"stability-flags": {
"zetacomponents/mail": 20,
"marcj/topsort": 20,
"pear/validate_finance_creditcard": 20
},
"prefer-stable": false,
......
......@@ -31,6 +31,21 @@
<tr>
<td class="label">{ts}Development stage{/ts}</td><td>{$extension.develStage}</td>
</tr>
<tr>
<td class="label">{ts}Requires{/ts}</td>
<td>
{foreach from=$extension.requires item=ext}
{if array_key_exists($ext, $localExtensionRows)}
{$localExtensionRows.$ext.name} (already downloaded - {$ext})
{elseif array_key_exists($ext, $remoteExtensionRows)}
{$remoteExtensionRows.$ext.name} (not downloaded - {$ext})
{else}
{$ext} {ts}(not available){/ts}
{/if}
<br/>
{/foreach}
</td>
</tr>
<tr>
<td class="label">{ts}Compatible with{/ts}</td>
<td>
......
......@@ -34,7 +34,7 @@ Depends: CRM/common/enableDisableApi.tpl and CRM/common/jsortable.tpl
</tr>
<tr class="hiddenElement" id="crm-extensions-details-{$row.id}">
<td>
{include file="CRM/Admin/Page/ExtensionDetails.tpl" extension=$row}
{include file="CRM/Admin/Page/ExtensionDetails.tpl" extension=$row localExtensionRows=$localExtensionRows remoteExtensionRows=$remoteExtensionRows}
</td>
<td></td><td></td><td></td><td></td>
</tr>
......
......@@ -49,6 +49,22 @@ class CRM_Extension_InfoTest extends CiviUnitTestCase {
$this->assertEquals('test.foo', $info->key);
$this->assertEquals('foo', $info->file);
$this->assertEquals('zamboni', $info->typeInfo['extra']);
$this->assertEquals(array(), $info->requires);
}
public function testGood_string_extras() {
$data = "<extension key='test.bar' type='module'><file>testbar</file>
<classloader><psr4 prefix=\"Civi\\\" path=\"Civi\"/></classloader>
<requires><ext>org.civicrm.a</ext><ext>org.civicrm.b</ext></requires>
</extension>
";
$info = CRM_Extension_Info::loadFromString($data);
$this->assertEquals('test.bar', $info->key);
$this->assertEquals('testbar', $info->file);
$this->assertEquals('Civi\\', $info->classloader[0]['prefix']);
$this->assertEquals('Civi', $info->classloader[0]['path']);
$this->assertEquals(array('org.civicrm.a', 'org.civicrm.b'), $info->requires);
}
public function testBad_string() {
......
......@@ -104,6 +104,86 @@ class CRM_Extension_ManagerTest extends CiviUnitTestCase {
$this->assertEquals('installed', $manager->getStatus('test.whiz.bang')); // no side-effect
}
/**
* This is the same as testInstall_Disable_Uninstall, but we also install and remove a dependency.
*
* @throws \CRM_Extension_Exception
*/
public function test_InstallAuto_DisableDownstream_UninstallDownstream() {
$testingTypeManager = $this->getMock('CRM_Extension_Manager_Interface');
$manager = $this->_createManager(array(
self::TESTING_TYPE => $testingTypeManager,
));
$this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
$this->assertEquals('uninstalled', $manager->getStatus('test.foo.downstream'));
$this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
$testingTypeManager->expects($this->exactly(2))->method('onPreInstall');
$testingTypeManager->expects($this->exactly(2))->method('onPostInstall');
$this->assertEquals(array('test.foo.bar', 'test.foo.downstream'),
$manager->findInstallRequirements(array('test.foo.downstream')));
$manager->install(
$manager->findInstallRequirements(array('test.foo.downstream')));
$this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
$this->assertEquals('installed', $manager->getStatus('test.foo.downstream'));
$this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
$testingTypeManager->expects($this->once())->method('onPreDisable');
$testingTypeManager->expects($this->once())->method('onPostDisable');
$this->assertEquals(array('test.foo.downstream'),
$manager->findDisableRequirements(array('test.foo.downstream')));
$manager->disable(array('test.foo.downstream'));
$this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
$this->assertEquals('disabled', $manager->getStatus('test.foo.downstream'));
$this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
$testingTypeManager->expects($this->once())->method('onPreUninstall');
$testingTypeManager->expects($this->once())->method('onPostUninstall');
$manager->uninstall(array('test.foo.downstream'));
$this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
$this->assertEquals('uninstalled', $manager->getStatus('test.foo.downstream'));
$this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
}
public function test_InstallAuto_DisableUpstream() {
$testingTypeManager = $this->getMock('CRM_Extension_Manager_Interface');
$manager = $this->_createManager(array(
self::TESTING_TYPE => $testingTypeManager,
));
$this->assertEquals('uninstalled', $manager->getStatus('test.foo.bar'));
$this->assertEquals('uninstalled', $manager->getStatus('test.foo.downstream'));
$this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
$testingTypeManager->expects($this->exactly(2))->method('onPreInstall');
$testingTypeManager->expects($this->exactly(2))->method('onPostInstall');
$this->assertEquals(array('test.foo.bar', 'test.foo.downstream'),
$manager->findInstallRequirements(array('test.foo.downstream')));
$manager->install(
$manager->findInstallRequirements(array('test.foo.downstream')));
$this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
$this->assertEquals('installed', $manager->getStatus('test.foo.downstream'));
$this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
$testingTypeManager->expects($this->never())->method('onPreDisable');
$testingTypeManager->expects($this->never())->method('onPostDisable');
$this->assertEquals(array('test.foo.downstream', 'test.foo.bar'),
$manager->findDisableRequirements(array('test.foo.bar')));
try {
$manager->disable(array('test.foo.bar'));
$this->fail('Expected disable to fail due to dependency');
}
catch (CRM_Extension_Exception $e) {
$this->assertRegExp('/test.foo.downstream/', $e->getMessage());
}
// Status unchanged
$this->assertEquals('installed', $manager->getStatus('test.foo.bar'));
$this->assertEquals('installed', $manager->getStatus('test.foo.downstream'));
$this->assertEquals('uninstalled', $manager->getStatus('test.whiz.bang'));
}
/**
* Install an extension and then harshly remove the underlying source.
* Subseuently disable and uninstall.
......@@ -420,6 +500,9 @@ class CRM_Extension_ManagerTest extends CiviUnitTestCase {
mkdir("$basedir/weird/whizbang");
file_put_contents("$basedir/weird/whizbang/info.xml", "<extension key='test.whiz.bang' type='" . self::TESTING_TYPE . "'><file>oddball</file></extension>");
// not needed for now // file_put_contents("$basedir/weird/whizbang/oddball.php", "<?php\n");
mkdir("$basedir/weird/downstream");
file_put_contents("$basedir/weird/downstream/info.xml", "<extension key='test.foo.downstream' type='" . self::TESTING_TYPE . "'><file>oddball</file><requires><ext>test.foo.bar</ext></requires></extension>");
// not needed for now // file_put_contents("$basedir/weird/downstream/oddball.php", "<?php\n");
$c = new CRM_Extension_Container_Basic($basedir, 'http://example/basedir', $cache, $cacheKey);
return array($basedir, $c);
}
......
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment