From 304fc59ffa56105f6c7a7a494e40d509e46c3c1c Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Wed, 23 Feb 2022 19:40:54 -0500 Subject: [PATCH] APIv4 - use entityTypes event to load custom records This standardizes things to use the ActionObjectProvider service more like it was intended, with getEntityNames now returning what it's supposed to, and removing a direct-cache-access hack from CoreUtil, in favor of using the service. --- Civi/Api4/Action/Entity/Get.php | 111 +------------------- Civi/Api4/Provider/ActionObjectProvider.php | 62 ++++++++++- Civi/Api4/Provider/CustomEntityProvider.php | 66 ++++++++++++ Civi/Api4/Utils/CoreUtil.php | 17 +-- Civi/Core/Container.php | 1 + 5 files changed, 132 insertions(+), 125 deletions(-) create mode 100644 Civi/Api4/Provider/CustomEntityProvider.php diff --git a/Civi/Api4/Action/Entity/Get.php b/Civi/Api4/Action/Entity/Get.php index 69d64e409b..9a8ccbb5f9 100644 --- a/Civi/Api4/Action/Entity/Get.php +++ b/Civi/Api4/Action/Entity/Get.php @@ -12,11 +12,6 @@ namespace Civi\Api4\Action\Entity; -use Civi\Api4\CustomValue; -use Civi\Api4\Service\Schema\Joinable\CustomGroupJoinable; -use Civi\Api4\Utils\CoreUtil; -use Civi\Core\Event\GenericHookEvent; - /** * Get the names & docblocks of all APIv4 entities. * @@ -33,111 +28,11 @@ class Get extends \Civi\Api4\Generic\BasicGetAction { protected $includeCustom; /** - * Scan all api directories to discover entities + * Returns all APIv4 entities */ protected function getRecords() { - $cache = \Civi::cache('metadata'); - $entities = $cache->get('api4.entities.info', []); - - if (!$entities) { - // Load entities declared in API files - foreach ($this->getAllApiClasses() as $className) { - $this->loadEntity($className, $entities); - } - // Load entities based on custom data - $entities = array_merge($entities, $this->getCustomEntities()); - // Allow extensions to modify the list of entities - $event = GenericHookEvent::create(['entities' => &$entities]); - \Civi::dispatcher()->dispatch('civi.api4.entityTypes', $event); - ksort($entities); - $cache->set('api4.entities.info', $entities); - } - - return $entities; - } - - /** - * @param \Civi\Api4\Generic\AbstractEntity $className - * @param array $entities - */ - private function loadEntity($className, array &$entities) { - $info = $className::getInfo(); - $daoName = $info['dao'] ?? NULL; - // Only include DAO entities from enabled components - if (!$daoName || !defined("{$daoName}::COMPONENT") || \CRM_Core_Component::isEnabled($daoName::COMPONENT)) { - $entities[$info['name']] = $info; - } - } - - /** - * @return \Civi\Api4\Generic\AbstractEntity[] - */ - private function getAllApiClasses() { - $classNames = []; - $locations = array_merge([\Civi::paths()->getPath('[civicrm.root]/Civi.php')], - array_column(\CRM_Extension_System::singleton()->getMapper()->getActiveModuleFiles(), 'filePath') - ); - foreach ($locations as $location) { - $dir = \CRM_Utils_File::addTrailingSlash(dirname($location)) . 'Civi/Api4'; - if (is_dir($dir)) { - foreach (glob("$dir/*.php") as $file) { - $className = 'Civi\Api4\\' . basename($file, '.php'); - if (is_a($className, 'Civi\Api4\Generic\AbstractEntity', TRUE)) { - $classNames[] = $className; - } - } - } - } - return $classNames; - } - - /** - * Get custom-field pseudo-entities - * - * @return array[] - */ - private function getCustomEntities() { - $entities = []; - $baseInfo = CustomValue::getInfo(); - $select = \CRM_Utils_SQL_Select::from('civicrm_custom_group') - ->where('is_multiple = 1') - ->where('is_active = 1') - ->toSQL(); - $group = \CRM_Core_DAO::executeQuery($select); - while ($group->fetch()) { - $fieldName = 'Custom_' . $group->name; - $baseEntity = CoreUtil::getApiClass(CustomGroupJoinable::getEntityFromExtends($group->extends)); - $entities[$fieldName] = [ - 'name' => $fieldName, - 'title' => $group->title, - 'title_plural' => $group->title, - 'description' => ts('Custom group for %1', [1 => $baseEntity::getInfo()['title_plural']]), - 'paths' => [ - 'view' => "civicrm/contact/view/cd?reset=1&gid={$group->id}&recId=[id]&multiRecordDisplay=single", - ], - ] + $baseInfo; - if (!empty($group->icon)) { - $entities[$fieldName]['icon'] = $group->icon; - } - if (!empty($group->help_pre)) { - $entities[$fieldName]['comment'] = $this->plainTextify($group->help_pre); - } - if (!empty($group->help_post)) { - $pre = empty($entities[$fieldName]['comment']) ? '' : $entities[$fieldName]['comment'] . "\n\n"; - $entities[$fieldName]['comment'] = $pre . $this->plainTextify($group->help_post); - } - } - return $entities; - } - - /** - * Convert html to plain text. - * - * @param $input - * @return mixed - */ - private function plainTextify($input) { - return html_entity_decode(strip_tags($input), ENT_QUOTES | ENT_HTML5, 'UTF-8'); + $provider = \Civi::service('action_object_provider'); + return $provider->getEntities(); } } diff --git a/Civi/Api4/Provider/ActionObjectProvider.php b/Civi/Api4/Provider/ActionObjectProvider.php index ced568e7b7..2c284e3cc0 100644 --- a/Civi/Api4/Provider/ActionObjectProvider.php +++ b/Civi/Api4/Provider/ActionObjectProvider.php @@ -16,6 +16,7 @@ use Civi\API\Provider\ProviderInterface; use Civi\Api4\Generic\AbstractAction; use Civi\API\Events; use Civi\Api4\Utils\ReflectionUtils; +use Civi\Core\Event\GenericHookEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** @@ -127,8 +128,7 @@ class ActionObjectProvider implements EventSubscriberInterface, ProviderInterfac * @return array */ public function getEntityNames($version) { - /** FIXME */ - return []; + return $version === 4 ? array_keys($this->getEntities()) : []; } /** @@ -142,4 +142,62 @@ class ActionObjectProvider implements EventSubscriberInterface, ProviderInterfac return []; } + /** + * Get all APIv4 entities + */ + public function getEntities() { + $cache = \Civi::cache('metadata'); + $entities = $cache->get('api4.entities.info', []); + + if (!$entities) { + // Load entities declared in API files + foreach ($this->getAllApiClasses() as $className) { + $this->loadEntity($className, $entities); + } + // Allow extensions to modify the list of entities + $event = GenericHookEvent::create(['entities' => &$entities]); + \Civi::dispatcher()->dispatch('civi.api4.entityTypes', $event); + ksort($entities); + $cache->set('api4.entities.info', $entities); + } + + return $entities; + } + + /** + * @param \Civi\Api4\Generic\AbstractEntity $className + * @param array $entities + */ + private function loadEntity($className, array &$entities) { + $info = $className::getInfo(); + $daoName = $info['dao'] ?? NULL; + // Only include DAO entities from enabled components + if (!$daoName || !defined("{$daoName}::COMPONENT") || \CRM_Core_Component::isEnabled($daoName::COMPONENT)) { + $entities[$info['name']] = $info; + } + } + + /** + * Scan all api directories to discover entities + * @return \Civi\Api4\Generic\AbstractEntity[] + */ + private function getAllApiClasses() { + $classNames = []; + $locations = array_merge([\Civi::paths()->getPath('[civicrm.root]/Civi.php')], + array_column(\CRM_Extension_System::singleton()->getMapper()->getActiveModuleFiles(), 'filePath') + ); + foreach ($locations as $location) { + $dir = \CRM_Utils_File::addTrailingSlash(dirname($location)) . 'Civi/Api4'; + if (is_dir($dir)) { + foreach (glob("$dir/*.php") as $file) { + $className = 'Civi\Api4\\' . basename($file, '.php'); + if (is_a($className, 'Civi\Api4\Generic\AbstractEntity', TRUE)) { + $classNames[] = $className; + } + } + } + } + return $classNames; + } + } diff --git a/Civi/Api4/Provider/CustomEntityProvider.php b/Civi/Api4/Provider/CustomEntityProvider.php new file mode 100644 index 0000000000..7763cd0a94 --- /dev/null +++ b/Civi/Api4/Provider/CustomEntityProvider.php @@ -0,0 +1,66 @@ +where('is_multiple = 1') + ->where('is_active = 1') + ->toSQL(); + $group = \CRM_Core_DAO::executeQuery($select); + while ($group->fetch()) { + $fieldName = 'Custom_' . $group->name; + $baseEntity = CoreUtil::getApiClass(CustomGroupJoinable::getEntityFromExtends($group->extends)); + $e->entities[$fieldName] = [ + 'name' => $fieldName, + 'title' => $group->title, + 'title_plural' => $group->title, + 'description' => ts('Custom group for %1', [1 => $baseEntity::getInfo()['title_plural']]), + 'paths' => [ + 'view' => "civicrm/contact/view/cd?reset=1&gid={$group->id}&recId=[id]&multiRecordDisplay=single", + ], + ] + $baseInfo; + if (!empty($group->icon)) { + $e->entities[$fieldName]['icon'] = $group->icon; + } + if (!empty($group->help_pre)) { + $e->entities[$fieldName]['comment'] = self::plainTextify($group->help_pre); + } + if (!empty($group->help_post)) { + $pre = empty($e->entities[$fieldName]['comment']) ? '' : $e->entities[$fieldName]['comment'] . "\n\n"; + $e->entities[$fieldName]['comment'] = $pre . self::plainTextify($group->help_post); + } + } + } + + /** + * Convert html to plain text. + * + * @param $input + * @return mixed + */ + private static function plainTextify($input) { + return html_entity_decode(strip_tags($input), ENT_QUOTES | ENT_HTML5, 'UTF-8'); + } + +} diff --git a/Civi/Api4/Utils/CoreUtil.php b/Civi/Api4/Utils/CoreUtil.php index d6d6320ca4..fbbde7780a 100644 --- a/Civi/Api4/Utils/CoreUtil.php +++ b/Civi/Api4/Utils/CoreUtil.php @@ -14,7 +14,6 @@ namespace Civi\Api4\Utils; use Civi\API\Exception\NotImplementedException; use Civi\API\Request; -use Civi\Api4\Entity; use Civi\Api4\Event\CreateApi4RequestEvent; use CRM_Core_DAO_AllCoreTables as AllCoreTables; @@ -53,20 +52,8 @@ class CoreUtil { * @return mixed */ public static function getInfoItem(string $entityName, string $keyToReturn) { - // Because this function might be called thousands of times per request, read directly - // from the cache set by Apiv4 Entity.get to avoid the processing overhead of the API wrapper. - $cached = \Civi::cache('metadata')->get('api4.entities.info'); - if ($cached) { - $info = $cached[$entityName] ?? NULL; - } - // If the cache is empty, calling Entity.get will populate it and we'll use it next time. - else { - $info = Entity::get(FALSE) - ->addWhere('name', '=', $entityName) - ->addSelect($keyToReturn) - ->execute()->first(); - } - return $info ? $info[$keyToReturn] ?? NULL : NULL; + $provider = \Civi::service('action_object_provider'); + return $provider->getEntities()[$entityName][$keyToReturn] ?? NULL; } /** diff --git a/Civi/Core/Container.php b/Civi/Core/Container.php index a6e6d555af..c7573f0d09 100644 --- a/Civi/Core/Container.php +++ b/Civi/Core/Container.php @@ -406,6 +406,7 @@ class Container { $dispatcher->addListener('civi.api4.validate', $aliasMethodEvent('civi.api4.validate', 'getEntityName'), 100); $dispatcher->addListener('civi.api4.authorizeRecord', $aliasMethodEvent('civi.api4.authorizeRecord', 'getEntityName'), 100); + $dispatcher->addListener('civi.api4.entityTypes', ['\Civi\Api4\Provider\CustomEntityProvider', 'addCustomEntities'], 100); $dispatcher->addListener('civi.core.install', ['\Civi\Core\InstallationCanary', 'check']); $dispatcher->addListener('civi.core.install', ['\Civi\Core\DatabaseInitializer', 'initialize']); -- 2.25.1