From: Tim Otten Date: Tue, 27 Jul 2021 05:20:35 +0000 (-0700) Subject: WorkflowMessageExample - Add API for searching examples X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=747156dd69084845ae5e3fe46cd8e12dc43baec3;p=civicrm-core.git WorkflowMessageExample - Add API for searching examples --- diff --git a/Civi/Api4/Action/WorkflowMessageExample/Get.php b/Civi/Api4/Action/WorkflowMessageExample/Get.php new file mode 100644 index 0000000000..c084fc7069 --- /dev/null +++ b/Civi/Api4/Action/WorkflowMessageExample/Get.php @@ -0,0 +1,65 @@ +select !== [] && !in_array('name', $this->select)) { + $this->select[] = 'name'; + } + parent::_run($result); + } + + protected function getRecords() { + $this->_scanner = new Examples(); + $all = $this->_scanner->findAll(); + foreach ($all as &$example) { + $example['tags'] = !empty($example['tags']) ? \CRM_Utils_Array::implodePadded($example['tags']) : ''; + } + return $all; + } + + protected function selectArray($values) { + $result = parent::selectArray($values); + + $heavyFields = array_intersect(['data', 'asserts'], $this->select ?: []); + if (!empty($heavyFields)) { + foreach ($result as &$item) { + $heavy = $this->_scanner->getHeavy($item['name']); + $item = array_merge($item, \CRM_Utils_Array::subset($heavy, $heavyFields)); + } + } + + return $result; + } + +} diff --git a/Civi/Api4/WorkflowMessageExample.php b/Civi/Api4/WorkflowMessageExample.php new file mode 100644 index 0000000000..6b05bc44e1 --- /dev/null +++ b/Civi/Api4/WorkflowMessageExample.php @@ -0,0 +1,93 @@ +setCheckPermissions($checkPermissions); + } + + /** + * @param bool $checkPermissions + * @return Generic\BasicGetFieldsAction + */ + public static function getFields($checkPermissions = TRUE) { + return (new Generic\BasicGetFieldsAction(__CLASS__, __FUNCTION__, function () { + return [ + [ + 'name' => 'name', + 'title' => 'Example Name', + 'data_type' => 'String', + ], + [ + 'name' => 'title', + 'title' => 'Example Title', + 'data_type' => 'String', + ], + [ + 'name' => 'workflow', + 'title' => 'Workflow Name', + 'data_type' => 'String', + ], + [ + 'name' => 'file', + 'title' => 'File Path', + 'data_type' => 'String', + 'description' => 'If the example is loaded from a file, this is the location.', + ], + [ + 'name' => 'tags', + 'title' => 'Tags', + 'data_type' => 'String', + 'serialize' => \CRM_Core_DAO::SERIALIZE_SEPARATOR_BOOKEND, + ], + [ + 'name' => 'data', + 'title' => 'Example data', + 'data_type' => 'String', + 'serialize' => \CRM_Core_DAO::SERIALIZE_JSON, + ], + [ + 'name' => 'asserts', + 'title' => 'Test assertions', + 'data_type' => 'String', + 'serialize' => \CRM_Core_DAO::SERIALIZE_JSON, + ], + ]; + }))->setCheckPermissions($checkPermissions); + } + + /** + * @return array + */ + public static function permissions() { + return [ + // FIXME: Perhaps use 'edit message templates' or similar? + "meta" => ["access CiviCRM"], + "default" => ["administer CiviCRM"], + ]; + } + +} diff --git a/Civi/WorkflowMessage/Examples.php b/Civi/WorkflowMessage/Examples.php new file mode 100644 index 0000000000..52ec6dae42 --- /dev/null +++ b/Civi/WorkflowMessage/Examples.php @@ -0,0 +1,172 @@ +cache = $cache ?: \Civi::cache('short' /* long */); + $this->cacheKey = \CRM_Utils_String::munge(__CLASS__); + } + + /** + * Get a list of all examples, including basic metadata (name, title, workflow). + * + * @return array + * Ex: ['my_example' => ['title' => ..., 'workflow' => ..., 'tags' => ...]] + * @throws \ReflectionException + */ + public function findAll(): array { + $all = $this->cache->get($this->cacheKey); + if ($all === NULL) { + $all = []; + $wfClasses = Invasive::call([WorkflowMessage::class, 'getWorkflowNameClassMap']); + foreach ($wfClasses as $workflow => $class) { + try { + $classFile = (new \ReflectionClass($class))->getFileName(); + } + catch (\ReflectionException $e) { + throw new \RuntimeException("Failed to locate workflow class ($class)", 0, $e); + } + $classDir = preg_replace('/\.php$/', '', $classFile); + if (is_dir($classDir)) { + $all = array_merge($all, $this->scanDir($classDir, $workflow)); + } + } + } + return $all; + } + + /** + * @param string $dir + * @param string $workflow + * @return array + * Ex: ['my_example' => ['title' => ..., 'workflow' => ..., 'tags' => ...]] + */ + protected function scanDir($dir, $workflow) { + $all = []; + $files = (array) glob($dir . "/*.ex.php"); + foreach ($files as $file) { + $name = $workflow . '.' . preg_replace('/\.ex.php/', '', basename($file)); + $scanRecord = [ + 'name' => $name, + 'title' => $name, + 'workflow' => $workflow, + 'tags' => [], + 'file' => $file, + // ^^ relativize? + ]; + $rawRecord = $this->loadFile($file); + $all[$name] = array_merge($scanRecord, \CRM_Utils_Array::subset($rawRecord, ['name', 'title', 'workflow', 'tags'])); + } + return $all; + } + + /** + * Load an example data file (based on its file path). + * + * @param string $_exFile + * Loadable PHP filename. + * @return array + * The raw/unevaluated dataset. + */ + public function loadFile($_exFile): array { + // Isolate variables. + // If you need export values, use something like `extract($_tplVars);` + return require $_exFile; + } + + /** + * Get example data (based on its symbolic name). + * + * @param string|string[] $nameOrPath + * Ex: "foo" -> load all the data from example "foo" + * Ex: "foo.b.a.r" -> load the example "foo" and pull out the data from $foo['b']['a']['r'] + * Ex: ["foo","b","a","r"] - Same as above. But there is no ambiguity with nested dots. + * @return array + */ + public function get($nameOrPath) { + $path = is_array($nameOrPath) ? $nameOrPath : explode('.', $nameOrPath); + $exampleName = array_shift($path) . '.' . array_shift($path); + return \CRM_Utils_Array::pathGet($this->getHeavy($exampleName), $path); + } + + /** + * Get one of the "heavy" properties. + * + * @param string $name + * @return array + * @throws \ReflectionException + */ + public function getHeavy(string $name): array { + if (isset($this->heavyCache[$name])) { + return $this->heavyCache[$name]; + + } + $all = $this->findAll(); + if (!isset($all[$name])) { + throw new \RuntimeException("Cannot load example ($name)"); + } + $heavyRecord = $all[$name]; + $loaded = $this->loadFile($all[$name]['file']); + foreach (['data', 'asserts'] as $heavyField) { + if (isset($loaded[$heavyField])) { + $heavyRecord[$heavyField] = $loaded[$heavyField] instanceof \Closure + ? call_user_func($loaded[$heavyField], $this) + : $loaded[$heavyField]; + } + } + + $this->heavyCache[$name] = $heavyRecord; + return $this->heavyCache[$name]; + } + + /** + * Get an example and merge/extend it with more data. + * + * @param string|string[] $nameOrPath + * Ex: "foo" -> load all the data from example "foo" + * Ex: "foo.b.a.r" -> load the example "foo" and pull out the data from $foo['b']['a']['r'] + * Ex: ["foo","b","a","r"] - Same as above. But there is no ambiguity with nested dots. + * @param array $overrides + * Data to add. + * @return array + * The result of merging the original example with the $overrides. + */ + public function extend($nameOrPath, $overrides = []) { + $data = $this->get($nameOrPath); + \CRM_Utils_Array::extend($data, $overrides); + return $data; + } + +} diff --git a/Civi/WorkflowMessage/GenericWorkflowMessage/alex.ex.php b/Civi/WorkflowMessage/GenericWorkflowMessage/alex.ex.php new file mode 100644 index 0000000000..a2ccd32167 --- /dev/null +++ b/Civi/WorkflowMessage/GenericWorkflowMessage/alex.ex.php @@ -0,0 +1,79 @@ + [], + 'data' => function(\Civi\WorkflowMessage\Examples $examples) { + return [ + 'modelProps' => [ + 'contact' => [ + 'contact_id' => '100', + 'contact_type' => 'Individual', + 'contact_sub_type' => NULL, + 'sort_name' => 'D\u00edaz, Alex', + 'display_name' => 'Dr. Alex D\u00edaz', + 'do_not_email' => '1', + 'do_not_phone' => '1', + 'do_not_mail' => '0', + 'do_not_sms' => '0', + 'do_not_trade' => '0', + 'is_opt_out' => '0', + 'legal_identifier' => NULL, + 'external_identifier' => NULL, + 'nick_name' => NULL, + 'legal_name' => NULL, + 'image_URL' => NULL, + 'preferred_communication_method' => NULL, + 'preferred_language' => NULL, + 'preferred_mail_format' => 'Both', + 'first_name' => 'Alex', + 'middle_name' => '', + 'last_name' => 'D\u00edaz', + 'prefix_id' => '4', + 'suffix_id' => NULL, + 'formal_title' => NULL, + 'communication_style_id' => NULL, + 'job_title' => NULL, + 'gender_id' => '1', + 'birth_date' => '1994-04-21', + 'is_deceased' => '0', + 'deceased_date' => NULL, + 'household_name' => NULL, + 'organization_name' => NULL, + 'sic_code' => NULL, + 'contact_is_deleted' => '0', + 'current_employer' => NULL, + 'address_id' => NULL, + 'street_address' => NULL, + 'supplemental_address_1' => NULL, + 'supplemental_address_2' => NULL, + 'supplemental_address_3' => NULL, + 'city' => NULL, + 'postal_code_suffix' => NULL, + 'postal_code' => NULL, + 'geo_code_1' => NULL, + 'geo_code_2' => NULL, + 'state_province_id' => NULL, + 'country_id' => NULL, + 'phone_id' => '7', + 'phone_type_id' => '1', + 'phone' => '293-6934', + 'email_id' => '7', + 'email' => 'daz.alex67@testing.net', + 'on_hold' => '0', + 'im_id' => NULL, + 'provider_id' => NULL, + 'im' => NULL, + 'worldregion_id' => NULL, + 'world_region' => NULL, + 'languages' => NULL, + 'individual_prefix' => 'Dr.', + 'individual_suffix' => NULL, + 'communication_style' => NULL, + 'gender' => 'Female', + 'state_province_name' => NULL, + 'state_province' => NULL, + 'country' => NULL, + ], + ], + ]; + }, +]; diff --git a/tests/phpunit/api/v4/Entity/WorkflowMessageExampleTest.php b/tests/phpunit/api/v4/Entity/WorkflowMessageExampleTest.php new file mode 100644 index 0000000000..39659455d3 --- /dev/null +++ b/tests/phpunit/api/v4/Entity/WorkflowMessageExampleTest.php @@ -0,0 +1,59 @@ +getPath('[civicrm.root]/Civi/WorkflowMessage/GenericWorkflowMessage/alex.ex.php'); + $workflow = 'generic'; + $name = 'generic.alex'; + + $this->assertTrue(file_exists($file), "Expect find canary file ($file)"); + + $get = \Civi\Api4\WorkflowMessageExample::get() + ->addWhere('name', '=', $name) + ->execute() + ->single(); + $this->assertEquals($workflow, $get['workflow']); + $this->assertTrue(!isset($get['data'])); + $this->assertTrue(!isset($get['asserts'])); + + $get = \Civi\Api4\WorkflowMessageExample::get() + ->addWhere('name', '=', $name) + ->addSelect('workflow', 'data') + ->execute() + ->single(); + $this->assertEquals($workflow, $get['workflow']); + $this->assertEquals(100, $get['data']['modelProps']['contact']['contact_id']); + } + +}