From 82376c1942f709a3c73a5f3f6903603bf7ba0bde Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Fri, 28 Mar 2014 03:46:10 -0700 Subject: [PATCH] CRM-14370 - API Kernel - Implement getEntityNames, getActionNames Conflicts: Civi/API/Provider/DoctrineCrudProvider.php Civi/API/Registry.php Civi/Core/Container.php tests/phpunit/Civi/API/Provider/DoctrineCrudProviderTest.php --- CRM/Utils/Array.php | 14 +++ Civi/API/Kernel.php | 59 ++++++++++ Civi/API/Provider/MagicFunctionProvider.php | 54 ++++++++- Civi/API/Provider/ProviderInterface.php | 19 +++ Civi/API/Provider/ReflectionProvider.php | 121 ++++++++++++++++++++ Civi/Core/Container.php | 11 +- api/v3/Entity.php | 40 ------- api/v3/Generic/Getactions.php | 2 +- tests/phpunit/CRM/Utils/ArrayTest.php | 10 ++ 9 files changed, 283 insertions(+), 47 deletions(-) create mode 100644 Civi/API/Provider/ReflectionProvider.php diff --git a/CRM/Utils/Array.php b/CRM/Utils/Array.php index 458d45d993..9515244557 100644 --- a/CRM/Utils/Array.php +++ b/CRM/Utils/Array.php @@ -354,6 +354,20 @@ class CRM_Utils_Array { return FALSE; } + /** + * @param $subset + * @param $superset + * @return bool TRUE if $subset is a subset of $superset + */ + static function isSubset($subset, $superset) { + foreach ($subset as $expected) { + if (!in_array($expected, $superset)) { + return FALSE; + } + } + return TRUE; + } + /** * Recursively copies all values of an array into a new array. * diff --git a/Civi/API/Kernel.php b/Civi/API/Kernel.php index 87d65ad54a..685c5bbfc7 100644 --- a/Civi/API/Kernel.php +++ b/Civi/API/Kernel.php @@ -173,6 +173,37 @@ class Kernel { return $event->getResponse(); } + /** + * @param int $version + * @return array + */ + public function getEntityNames($version) { + // Question: Would it better to eliminate $this->apiProviders and just use $this->dispatcher? + $entityNames = array(); + foreach ($this->getApiProviders() as $provider) { + $entityNames = array_merge($entityNames, $provider->getEntityNames($version)); + } + $entityNames = array_unique($entityNames); + sort($entityNames); + return $entityNames; + } + + /** + * @param int $version + * @param string $entity + * @return array + */ + public function getActionNames($version, $entity) { + // Question: Would it better to eliminate $this->apiProviders and just use $this->dispatcher? + $actionNames = array(); + foreach ($this->getApiProviders() as $provider) { + $actionNames = array_merge($actionNames, $provider->getActionNames($version, $entity)); + } + $actionNames = array_unique($actionNames); + sort($actionNames); + return $actionNames; + } + /** * @param \Exception $e * @param array $apiRequest @@ -279,4 +310,32 @@ class Kernel { } return $result; } + + /** + * @return array + */ + public function getApiProviders() { + return $this->apiProviders; + } + + /** + * @param array $apiProviders + */ + public function setApiProviders($apiProviders) { + $this->apiProviders = $apiProviders; + } + + /** + * @return \Symfony\Component\EventDispatcher\EventDispatcher + */ + public function getDispatcher() { + return $this->dispatcher; + } + + /** + * @param \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher + */ + public function setDispatcher($dispatcher) { + $this->dispatcher = $dispatcher; + } } \ No newline at end of file diff --git a/Civi/API/Provider/MagicFunctionProvider.php b/Civi/API/Provider/MagicFunctionProvider.php index 537b1987d0..ce14050bad 100644 --- a/Civi/API/Provider/MagicFunctionProvider.php +++ b/Civi/API/Provider/MagicFunctionProvider.php @@ -62,6 +62,9 @@ class MagicFunctionProvider implements EventSubscriberInterface, ProviderInterfa } } + /** + * {inheritdoc} + */ public function invoke($apiRequest) { $function = $apiRequest['function']; if ($apiRequest['function'] && $apiRequest['is_generic']) { @@ -76,11 +79,52 @@ class MagicFunctionProvider implements EventSubscriberInterface, ProviderInterfa return $result; } - public function getActionNames($entity, $version) { - // TODO don't recurse into civicrm_api - $r = civicrm_api('Entity', 'Get', array('version' => $version)); - if (!in_array($entity, $r['values'])) { - throw new \Civi\API\Exception\NotImplementedException("Entity " . $entity . " invalid. Use api.entity.get to have the list", array('entity' => $r['values'])); + /** + * {inheritdoc} + */ + function getEntityNames($version) { + $entities = array(); + $include_dirs = array_unique(explode(PATH_SEPARATOR, get_include_path())); + #$include_dirs = array(dirname(__FILE__). '/../../'); + foreach ($include_dirs as $include_dir) { + $api_dir = implode(DIRECTORY_SEPARATOR, array($include_dir, 'api', 'v' . $version)); + if (! is_dir($api_dir)) { + continue; + } + $iterator = new \DirectoryIterator($api_dir); + foreach ($iterator as $fileinfo) { + $file = $fileinfo->getFilename(); + + // Check for entities with a master file ("api/v3/MyEntity.php") + $parts = explode(".", $file); + if (end($parts) == "php" && $file != "utils.php" && !preg_match('/Tests?.php$/', $file) ) { + // without the ".php" + $entities[] = substr($file, 0, -4); + } + + // Check for entities with standalone action files ("api/v3/MyEntity/MyAction.php") + $action_dir = $api_dir . DIRECTORY_SEPARATOR . $file; + if (preg_match('/^[A-Z][A-Za-z0-9]*$/', $file) && is_dir($action_dir)) { + if (count(glob("$action_dir/[A-Z]*.php")) > 0) { + $entities[] = $file; + } + } + } + } + $entities = array_diff($entities, array('Generic')); + $entities = array_unique($entities); + sort($entities); + + return $entities; + } + + /** + * {inheritdoc} + */ + public function getActionNames($version, $entity) { + $entities = $this->getEntityNames($version); + if (!in_array($entity, $entities)) { + return array(); } $this->loadEntity($entity, $version); diff --git a/Civi/API/Provider/ProviderInterface.php b/Civi/API/Provider/ProviderInterface.php index 5fafe53307..0d8e9abfb0 100644 --- a/Civi/API/Provider/ProviderInterface.php +++ b/Civi/API/Provider/ProviderInterface.php @@ -33,5 +33,24 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; * An API "provider" provides a means to execute API requests. */ interface ProviderInterface { + /** + * @param array $apiRequest + * @return array structured response data (per civicrm_api3_create_success) + * @see civicrm_api3_create_success + * @throws \API_Exception + */ function invoke($apiRequest); + + /** + * @param int $version + * @return array + */ + function getEntityNames($version); + + /** + * @param int $version + * @param string $entity + * @return array + */ + function getActionNames($version, $entity); } \ No newline at end of file diff --git a/Civi/API/Provider/ReflectionProvider.php b/Civi/API/Provider/ReflectionProvider.php new file mode 100644 index 0000000000..addaa3145d --- /dev/null +++ b/Civi/API/Provider/ReflectionProvider.php @@ -0,0 +1,121 @@ + array( + array('onApiResolve', Events::W_EARLY), // TODO decide if we really want to override others + ), + Events::AUTHORIZE => array( + array('onApiAuthorize', Events::W_EARLY), // TODO decide if we really want to override others + ), + ); + } + + /** + * @var \Civi\API\Kernel + */ + private $apiKernel; + + /** + * @var array (string $entityName => array(string $actionName)) + */ + private $actions; + + /** + * @param \Civi\API\Kernel $apiKernel + */ + public function __construct($apiKernel) { + $this->apiKernel = $apiKernel; + $this->actions = array( + 'Entity' => array('get', 'getactions'), + '*' => array('getactions'), // 'getfields' + ); + } + + public function onApiResolve(\Civi\API\Event\ResolveEvent $event) { + $apiRequest = $event->getApiRequest(); + $actions = isset($this->actions[$apiRequest['entity']]) ? $this->actions[$apiRequest['entity']] : $this->actions['*']; + if (in_array($apiRequest['action'], $actions)) { + $apiRequest['is_metadata'] = TRUE; + $event->setApiRequest($apiRequest); + $event->setApiProvider($this); + $event->stopPropagation(); // TODO decide if we really want to override others + } + } + + public function onApiAuthorize(\Civi\API\Event\AuthorizeEvent $event) { + $apiRequest = $event->getApiRequest(); + if (isset($apiRequest['is_metadata'])) { + // if (\CRM_Core_Permission::check('access AJAX API') || \CRM_Core_Permission::check('access CiviCRM')) { + $event->authorize(); + $event->stopPropagation(); + // } + } + } + + /** + * {inheritdoc} + */ + public function invoke($apiRequest) { + if ($apiRequest['entity'] == 'Entity' && $apiRequest['action'] == 'get') { + return civicrm_api3_create_success($this->apiKernel->getEntityNames($apiRequest['version'])); + } + switch ($apiRequest['action']) { + case 'getactions': + return civicrm_api3_create_success($this->apiKernel->getActionNames($apiRequest['version'], $apiRequest['entity'])); +// case 'getfields': +// return $this->doGetFields($apiRequest); + default: + } + + // We shouldn't get here because onApiResolve() checks $this->actions + throw new \API_Exception("Unsupported action (" . $apiRequest['entity'] . '.' . $apiRequest['action'] . ']'); + } + + /** + * {inheritdoc} + */ + function getEntityNames($version) { + return array('Entity'); + } + + /** + * {inheritdoc} + */ + function getActionNames($version, $entity) { + return isset($this->actions[$entity]) ? $this->actions[$entity] : $this->actions['*']; + } +} \ No newline at end of file diff --git a/Civi/Core/Container.php b/Civi/Core/Container.php index 37616734c8..a9923a8fa3 100644 --- a/Civi/Core/Container.php +++ b/Civi/Core/Container.php @@ -103,7 +103,16 @@ class Container { \CRM_Utils_API_MatchOption::singleton(), ))); $dispatcher->addSubscriber(new \Civi\API\Subscriber\XDebugSubscriber()); - $kernel = new \Civi\API\Kernel($dispatcher, array()); + $kernel = new \Civi\API\Kernel($dispatcher); + + $reflectionProvider = new \Civi\API\Provider\ReflectionProvider($kernel); + $dispatcher->addSubscriber($reflectionProvider); + + $kernel->setApiProviders(array( + $reflectionProvider, + $magicFunctionProvider, + )); + return $kernel; } } diff --git a/api/v3/Entity.php b/api/v3/Entity.php index 3a38c7961a..ef461a7d08 100644 --- a/api/v3/Entity.php +++ b/api/v3/Entity.php @@ -2,46 +2,6 @@ require_once 'api/v3/utils.php'; -/** - * returns the list of all the entities that you can manipulate via the api. The entity of this API call is the entity, that isn't a real civicrm entity as in something stored in the DB, but an abstract meta object. My head is going to explode. In a meta way. - */ -function civicrm_api3_entity_get($params) { - - civicrm_api3_verify_mandatory($params); - $entities = array(); - $include_dirs = array_unique(explode(PATH_SEPARATOR, get_include_path())); - #$include_dirs = array(dirname(__FILE__). '/../../'); - foreach ($include_dirs as $include_dir) { - $api_dir = implode(DIRECTORY_SEPARATOR, array($include_dir, 'api', 'v3')); - if (! is_dir($api_dir)) { - continue; - } - $iterator = new DirectoryIterator($api_dir); - foreach ($iterator as $fileinfo) { - $file = $fileinfo->getFilename(); - - // Check for entities with a master file ("api/v3/MyEntity.php") - $parts = explode(".", $file); - if (end($parts) == "php" && $file != "utils.php" && !preg_match('/Tests?.php$/', $file) ) { - // without the ".php" - $entities[] = substr($file, 0, -4); - } - - // Check for entities with standalone action files ("api/v3/MyEntity/MyAction.php") - $action_dir = $api_dir . DIRECTORY_SEPARATOR . $file; - if (preg_match('/^[A-Z][A-Za-z0-9]*$/', $file) && is_dir($action_dir)) { - if (count(glob("$action_dir/[A-Z]*.php")) > 0) { - $entities[] = $file; - } - } - } - } - $entities = array_diff($entities, array('Generic')); - $entities = array_unique($entities); - sort($entities); - return civicrm_api3_create_success($entities); -} - /** * Placeholder function. This should never be called, as it doesn't have any meaning */ diff --git a/api/v3/Generic/Getactions.php b/api/v3/Generic/Getactions.php index a10990dc8d..9cfbbd67a9 100644 --- a/api/v3/Generic/Getactions.php +++ b/api/v3/Generic/Getactions.php @@ -29,6 +29,6 @@ function civicrm_api3_generic_getActions($apiRequest) { civicrm_api3_verify_mandatory($apiRequest, NULL, array('entity')); $mfp = \Civi\Core\Container::singleton()->get('magic_function_provider'); - $actions = $mfp->getActionNames($apiRequest['entity'], $apiRequest['version']); + $actions = $mfp->getActionNames($apiRequest['version'], $apiRequest['entity']); return civicrm_api3_create_success($actions); } diff --git a/tests/phpunit/CRM/Utils/ArrayTest.php b/tests/phpunit/CRM/Utils/ArrayTest.php index 2d31965abc..2118c25b2f 100644 --- a/tests/phpunit/CRM/Utils/ArrayTest.php +++ b/tests/phpunit/CRM/Utils/ArrayTest.php @@ -119,4 +119,14 @@ class CRM_Utils_ArrayTest extends CiviUnitTestCase { array('base data' => 1, 'dim1' => 'b', 'dim2' => 'beta', 'dim3' => 'two'), ), $actual); } + + function testIsSubset() { + $this->assertTrue(CRM_Utils_Array::isSubset(array(), array())); + $this->assertTrue(CRM_Utils_Array::isSubset(array('a'), array('a'))); + $this->assertTrue(CRM_Utils_Array::isSubset(array('a'), array('b','a','c'))); + $this->assertTrue(CRM_Utils_Array::isSubset(array('b','d'), array('a','b','c','d'))); + $this->assertFalse(CRM_Utils_Array::isSubset(array('a'), array())); + $this->assertFalse(CRM_Utils_Array::isSubset(array('a'), array('b'))); + $this->assertFalse(CRM_Utils_Array::isSubset(array('a'), array('b','c','d'))); + } } -- 2.25.1