From 747156dd69084845ae5e3fe46cd8e12dc43baec3 Mon Sep 17 00:00:00 2001
From: Tim Otten
Date: Mon, 26 Jul 2021 22:20:35 -0700
Subject: [PATCH 01/12] WorkflowMessageExample - Add API for searching examples
---
.../Action/WorkflowMessageExample/Get.php | 65 +++++++
Civi/Api4/WorkflowMessageExample.php | 93 ++++++++++
Civi/WorkflowMessage/Examples.php | 172 ++++++++++++++++++
.../GenericWorkflowMessage/alex.ex.php | 79 ++++++++
.../v4/Entity/WorkflowMessageExampleTest.php | 59 ++++++
5 files changed, 468 insertions(+)
create mode 100644 Civi/Api4/Action/WorkflowMessageExample/Get.php
create mode 100644 Civi/Api4/WorkflowMessageExample.php
create mode 100644 Civi/WorkflowMessage/Examples.php
create mode 100644 Civi/WorkflowMessage/GenericWorkflowMessage/alex.ex.php
create mode 100644 tests/phpunit/api/v4/Entity/WorkflowMessageExampleTest.php
diff --git a/Civi/Api4/Action/WorkflowMessageExample/Get.php b/Civi/Api4/Action/WorkflowMessageExample/Get.php
new file mode 100644
index 000000000000..c084fc706931
--- /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 000000000000..6b05bc44e10d
--- /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 000000000000..52ec6dae4237
--- /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 000000000000..a2ccd3216782
--- /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 000000000000..39659455d358
--- /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']);
+ }
+
+}
From 4f22d37ee370018e214e9fca03fd7f7dd26d517e Mon Sep 17 00:00:00 2001
From: Tim Otten
Date: Mon, 5 Jul 2021 23:32:29 -0700
Subject: [PATCH 02/12] CaseActivity - Define class-model and unit-test for
workflow message. Add base test-trait.
---
CRM/Case/WorkflowMessage/CaseActivity.php | 111 ++++++++++++++++++
.../CaseActivity/adhoc_1.ex.php | 32 +++++
.../CaseActivity/class_1.ex.php | 33 ++++++
Civi/Test/WorkflowMessageTestTrait.php | 82 +++++++++++++
Civi/Token/TokenCompatSubscriber.php | 2 +-
.../Case/WorkflowMessage/CaseActivityTest.php | 79 +++++++++++++
.../CRM/Core/BAO/MessageTemplateTest.php | 45 ++++---
7 files changed, 360 insertions(+), 24 deletions(-)
create mode 100644 CRM/Case/WorkflowMessage/CaseActivity.php
create mode 100644 CRM/Case/WorkflowMessage/CaseActivity/adhoc_1.ex.php
create mode 100644 CRM/Case/WorkflowMessage/CaseActivity/class_1.ex.php
create mode 100644 Civi/Test/WorkflowMessageTestTrait.php
create mode 100644 tests/phpunit/CRM/Case/WorkflowMessage/CaseActivityTest.php
diff --git a/CRM/Case/WorkflowMessage/CaseActivity.php b/CRM/Case/WorkflowMessage/CaseActivity.php
new file mode 100644
index 000000000000..59380ba3c131
--- /dev/null
+++ b/CRM/Case/WorkflowMessage/CaseActivity.php
@@ -0,0 +1,111 @@
+ 123, 'display_name' => 'Bob Roberts', role => 'FIXME']
+ *
+ * @var array|null
+ * @scope tokenContext, tplParams
+ * @required
+ */
+ public $contact;
+
+ /**
+ * @var int
+ * @scope tplParams as client_id
+ * @required
+ */
+ public $clientId;
+
+ /**
+ * @var string
+ * @scope tplParams
+ * @required
+ */
+ public $activitySubject;
+
+ /**
+ * @var string
+ * @scope tplParams
+ * @required
+ */
+ public $activityTypeName;
+
+ /**
+ * Unique ID for this activity. Unique and difficult to guess.
+ *
+ * @var string
+ * @scope tplParams
+ * @required
+ */
+ public $idHash;
+
+ /**
+ * @var bool
+ * @scope tplParams
+ * @required
+ */
+ public $isCaseActivity;
+
+ /**
+ * @var string
+ * @scope tplParams
+ */
+ public $editActURL;
+
+ /**
+ * @var string
+ * @scope tplParams
+ */
+ public $viewActURL;
+
+ /**
+ * @var string
+ * @scope tplParams
+ */
+ public $manageCaseURL;
+
+ /**
+ * List of conventional activity fields.
+ *
+ * Example: [['label' => ..., 'category' => ..., 'type' => ..., 'value' => ...]]
+ *
+ * @var array
+ * @scope tplParams as activity.fields
+ * @required
+ */
+ public $activityFields;
+
+ /**
+ * List of custom activity fields, grouped by CustomGroup.
+ *
+ * Example: ['My Custom Stuff' => [['label' => ..., 'category' => ..., 'type' => ..., 'value' => ...]]]
+ *
+ * @var array
+ * @scope tplParams as activity.customGroups
+ */
+ public $activityCustomGroups = [];
+
+}
diff --git a/CRM/Case/WorkflowMessage/CaseActivity/adhoc_1.ex.php b/CRM/Case/WorkflowMessage/CaseActivity/adhoc_1.ex.php
new file mode 100644
index 000000000000..47c9900e4d3d
--- /dev/null
+++ b/CRM/Case/WorkflowMessage/CaseActivity/adhoc_1.ex.php
@@ -0,0 +1,32 @@
+ ts('Case Activity (Adhoc-style example)'),
+ 'tags' => [],
+ 'data' => function (\Civi\WorkflowMessage\Examples $examples) {
+ $contact = $examples->extend('generic.alex.data.modelProps.contact', [
+ 'role' => 'myrole',
+ ]);
+ return [
+ 'tokenContext' => [
+ 'contact' => $contact,
+ ],
+ 'tplParams' => [
+ 'contact' => $contact,
+ 'isCaseActivity' => 1,
+ 'client_id' => 101,
+ 'activityTypeName' => 'Follow up',
+ 'activitySubject' => 'Test 123',
+ 'idHash' => 'abcdefg',
+ 'activity' => [
+ 'fields' => [
+ [
+ 'label' => 'Case ID',
+ 'type' => 'String',
+ 'value' => '1234',
+ ],
+ ],
+ ],
+ ],
+ ];
+ },
+];
diff --git a/CRM/Case/WorkflowMessage/CaseActivity/class_1.ex.php b/CRM/Case/WorkflowMessage/CaseActivity/class_1.ex.php
new file mode 100644
index 000000000000..65f413ecf1b5
--- /dev/null
+++ b/CRM/Case/WorkflowMessage/CaseActivity/class_1.ex.php
@@ -0,0 +1,33 @@
+ ts('Case Activity (Class-style example)'),
+ 'tags' => ['phpunit', 'preview'],
+ 'data' => function (\Civi\WorkflowMessage\Examples $examples) {
+ return $examples->extend('generic.alex.data', [
+ 'modelProps' => [
+ 'contact' => [
+ 'role' => 'myrole',
+ ],
+ 'isCaseActivity' => 1,
+ 'clientId' => 101,
+ 'activityTypeName' => 'Follow up',
+ 'activityFields' => [
+ [
+ 'label' => 'Case ID',
+ 'type' => 'String',
+ 'value' => '1234',
+ ],
+ ],
+ 'activitySubject' => 'Test 123',
+ 'activityCustomGroups' => [],
+ 'idHash' => 'abcdefg',
+ ],
+ ]);
+ },
+ 'asserts' => [
+ 'default' => [
+ ['for' => 'subject', 'regex' => '/\[case #abcdefg\] Test 123/'],
+ ['for' => 'text', 'regex' => '/Your Case Role\(s\) : myrole/'],
+ ],
+ ],
+];
diff --git a/Civi/Test/WorkflowMessageTestTrait.php b/Civi/Test/WorkflowMessageTestTrait.php
new file mode 100644
index 000000000000..395721bedeb1
--- /dev/null
+++ b/Civi/Test/WorkflowMessageTestTrait.php
@@ -0,0 +1,82 @@
+getWorkflowClass();
+ return $class::WORKFLOW;
+ }
+
+ /**
+ * @return \Civi\Api4\Generic\AbstractGetAction
+ * @throws \API_Exception
+ */
+ protected function findExamples(): \Civi\Api4\Generic\AbstractGetAction {
+ return \Civi\Api4\WorkflowMessageExample::get(0)
+ ->setSelect(['name', 'title', 'workflow', 'tags', 'data', 'asserts'])
+ ->addWhere('workflow', '=', $this->getWorkflowName())
+ ->addWhere('tags', 'CONTAINS', 'phpunit');
+ }
+
+ /**
+ * @param array $exampleProps
+ * @param string $exampleName
+ * @throws \Civi\WorkflowMessage\Exception\WorkflowMessageException
+ */
+ protected function assertConstructorEquivalence(array $exampleProps, $exampleName = ''): void {
+ $class = $this->getWorkflowClass();
+ $instances = [];
+ $instances["factory_$exampleName"] = WorkflowMessage::create($this->getWorkflowName(), $exampleProps);
+ $instances["class_$exampleName"] = new $class($exampleProps);
+
+ /** @var \Civi\WorkflowMessage\WorkflowMessageInterface $refInstance */
+ /** @var \Civi\WorkflowMessage\WorkflowMessageInterface $cmpInstance */
+
+ $refName = $refInstance = NULL;
+ $comparisons = 0;
+ foreach ($instances as $cmpName => $cmpInstance) {
+ if ($refName === NULL) {
+ $refName = $cmpName;
+ $refInstance = $cmpInstance;
+ continue;
+ }
+
+ $this->assertSameWorkflowMessage($refInstance, $cmpInstance, "Compare $refName vs $cmpName: ");
+ $comparisons++;
+ }
+ $this->assertEquals(1, $comparisons);
+ }
+
+ /**
+ * @param \Civi\WorkflowMessage\WorkflowMessageInterface $refInstance
+ * @param \Civi\WorkflowMessage\WorkflowMessageInterface $cmpInstance
+ * @param string|null $prefix
+ */
+ protected function assertSameWorkflowMessage(\Civi\WorkflowMessage\WorkflowMessageInterface $refInstance, \Civi\WorkflowMessage\WorkflowMessageInterface $cmpInstance, ?string $prefix = NULL): void {
+ if ($prefix === NULL) {
+ $prefix = sprintf('[%s] ', $this->getWorkflowName());
+ }
+ $this->assertEquals($refInstance->export('tplParams'), $cmpInstance->export('tplParams'), "{$prefix}Should have same export(tplParams)");
+ $this->assertEquals($refInstance->export('tokenContext'), $cmpInstance->export('tokenContext'), "{$prefix}should have same export(tokenContext)");
+ $this->assertEquals($refInstance->export('envelope'), $cmpInstance->export('envelope'), "{$prefix}Should have same export(envelope)");
+ $refExportAll = WorkflowMessage::exportAll($refInstance);
+ $cmpExportAll = WorkflowMessage::exportAll($cmpInstance);
+ $this->assertEquals($refExportAll, $cmpExportAll, "{$prefix}Should have same exportAll()");
+ }
+
+}
diff --git a/Civi/Token/TokenCompatSubscriber.php b/Civi/Token/TokenCompatSubscriber.php
index 417450ef93b3..8e9abec17287 100644
--- a/Civi/Token/TokenCompatSubscriber.php
+++ b/Civi/Token/TokenCompatSubscriber.php
@@ -303,7 +303,7 @@ public function onRender(TokenRenderEvent $e) {
$e->string = \CRM_Utils_Token::replaceDomainTokens($e->string, $domain, $isHtml, $e->message['tokens'], $useSmarty);
if (!empty($e->context['contact'])) {
- \CRM_Utils_Token::replaceGreetingTokens($e->string, $e->context['contact'], $e->context['contact']['contact_id'], NULL, $useSmarty);
+ \CRM_Utils_Token::replaceGreetingTokens($e->string, $e->context['contact'], $e->context['contact']['contact_id'] ?? $e->context['contactId'], NULL, $useSmarty);
}
if ($useSmarty) {
diff --git a/tests/phpunit/CRM/Case/WorkflowMessage/CaseActivityTest.php b/tests/phpunit/CRM/Case/WorkflowMessage/CaseActivityTest.php
new file mode 100644
index 000000000000..5a21f5be7c83
--- /dev/null
+++ b/tests/phpunit/CRM/Case/WorkflowMessage/CaseActivityTest.php
@@ -0,0 +1,79 @@
+setSelect(['name', 'data'])
+ ->addWhere('name', 'IN', ['case_activity.adhoc_1', 'case_activity.class_1'])
+ ->execute()
+ ->indexBy('name')
+ ->column('data');
+ $byAdhoc = Civi\WorkflowMessage\WorkflowMessage::create('case_activity', $examples['case_activity.adhoc_1']);
+ $byClass = new CRM_Case_WorkflowMessage_CaseActivity($examples['case_activity.class_1']);
+ $this->assertSameWorkflowMessage($byClass, $byAdhoc, 'Compare byClass and byAdhoc: ');
+ }
+
+ /**
+ * Ensure that various methods of constructing a WorkflowMessage all produce similar results.
+ *
+ * To see this, we take all the example data and use it with diff constructors.
+ */
+ public function testConstructorEquivalence() {
+ $examples = $this->findExamples()->execute()->indexBy('name')->column('data');
+ $this->assertTrue(count($examples) >= 1, 'Must have at least one example data-set');
+ foreach ($examples as $example) {
+ $this->assertConstructorEquivalence($example);
+ }
+ }
+
+ /**
+ * Basic canary test fetching a specific example.
+ *
+ * @throws \API_Exception
+ * @throws \Civi\API\Exception\UnauthorizedException
+ */
+ public function testExampleGet() {
+ $file = \Civi::paths()->getPath('[civicrm.root]/CRM/Case/WorkflowMessage/CaseActivity/class_1.ex.php');
+ $workflow = 'case_activity';
+ $name = 'case_activity.class_1';
+
+ $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']);
+ $this->assertEquals('myrole', $get['data']['modelProps']['contact']['role']);
+ }
+
+}
diff --git a/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php b/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php
index b288a7ca98ec..f3ae76088940 100644
--- a/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php
+++ b/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php
@@ -195,38 +195,37 @@ public function testCaseActivityCopyTemplate():void {
$client_id = $this->individualCreate();
$contact_id = $this->individualCreate();
- $tplParams = [
- 'isCaseActivity' => 1,
- 'client_id' => $client_id,
- // activityTypeName means label here not name, but it's ok because label is desired here (dev/core#1116-ok-label)
- 'activityTypeName' => 'Follow up',
- 'activity' => [
- 'fields' => [
+ $msg = \Civi\WorkflowMessage\WorkflowMessage::create('case_activity', [
+ 'modelProps' => [
+ 'contactId' => $contact_id,
+ 'contact' => ['role' => 'Sand grain counter'],
+ 'isCaseActivity' => 1,
+ 'clientId' => $client_id,
+ // activityTypeName means label here not name, but it's ok because label is desired here (dev/core#1116-ok-label)
+ 'activityTypeName' => 'Follow up',
+ 'activityFields' => [
[
'label' => 'Case ID',
'type' => 'String',
'value' => '1234',
],
],
+ 'activitySubject' => 'Test 123',
+ 'idHash' => substr(sha1(CIVICRM_SITE_KEY . '1234'), 0, 7),
],
- 'activitySubject' => 'Test 123',
- 'idHash' => substr(sha1(CIVICRM_SITE_KEY . '1234'), 0, 7),
- 'contact' => ['role' => 'Sand grain counter'],
- ];
+ ]);
- [, $subject, $message] = CRM_Core_BAO_MessageTemplate::sendTemplate(
- [
- 'valueName' => 'case_activity',
- 'contactId' => $contact_id,
- 'tplParams' => $tplParams,
- 'from' => 'admin@example.com',
- 'toName' => 'Demo',
- 'toEmail' => 'admin@example.com',
- 'attachments' => NULL,
- ]
- );
+ $this->assertEquals([], \Civi\Test\Invasive::get([$msg, '_extras']));
+
+ [, $subject, $message] = $msg->sendTemplate([
+ 'valueName' => 'case_activity',
+ 'from' => 'admin@example.com',
+ 'toName' => 'Demo',
+ 'toEmail' => 'admin@example.com',
+ 'attachments' => NULL,
+ ]);
- $this->assertEquals('[case #' . $tplParams['idHash'] . '] Test 123', $subject);
+ $this->assertEquals('[case #' . $msg->getIdHash() . '] Test 123', $subject);
$this->assertStringContainsString('Your Case Role(s) : Sand grain counter', $message);
$this->assertStringContainsString('Case ID : 1234', $message);
}
From ebd92dafbecd02b9fa3431b403ae04fc7cf88cf6 Mon Sep 17 00:00:00 2001
From: Tim Otten
Date: Tue, 13 Jul 2021 17:21:52 -0700
Subject: [PATCH 03/12] Add WorkflowMessage.get and WorkflowMessage.render APIs
---
CRM/Core/Permission.php | 4 +
Civi/Api4/Action/WorkflowMessage/Render.php | 168 ++++++++++++++++++
Civi/Api4/WorkflowMessage.php | 120 +++++++++++++
Civi/WorkflowMessage/WorkflowMessage.php | 35 ++++
.../api/v4/Entity/WorkflowMessageTest.php | 94 ++++++++++
5 files changed, 421 insertions(+)
create mode 100644 Civi/Api4/Action/WorkflowMessage/Render.php
create mode 100644 Civi/Api4/WorkflowMessage.php
create mode 100644 tests/phpunit/api/v4/Entity/WorkflowMessageTest.php
diff --git a/CRM/Core/Permission.php b/CRM/Core/Permission.php
index 5166b65466ae..2ad9e9977e06 100644
--- a/CRM/Core/Permission.php
+++ b/CRM/Core/Permission.php
@@ -841,6 +841,10 @@ public static function getCorePermissions() {
$prefix . ts('administer payment processors'),
ts('Add, Update, or Disable Payment Processors'),
],
+ 'render templates' => [
+ $prefix . ts('render templates'),
+ ts('Render open-ended template content. (Additional constraints may apply to autoloaded records and specific notations.)'),
+ ],
'edit message templates' => [
$prefix . ts('edit message templates'),
],
diff --git a/Civi/Api4/Action/WorkflowMessage/Render.php b/Civi/Api4/Action/WorkflowMessage/Render.php
new file mode 100644
index 000000000000..86611072b9f7
--- /dev/null
+++ b/Civi/Api4/Action/WorkflowMessage/Render.php
@@ -0,0 +1,168 @@
+Hello {contact.first_name}!
`)
+ */
+ protected $messageTemplate;
+
+ /**
+ * @var \Civi\WorkflowMessage\WorkflowMessageInterface
+ */
+ protected $_model;
+
+ public function _run(\Civi\Api4\Generic\Result $result) {
+ $this->validateValues();
+
+ $r = \CRM_Core_BAO_MessageTemplate::renderTemplate([
+ 'model' => $this->_model,
+ 'messageTemplate' => $this->getMessageTemplate(),
+ 'messageTemplateId' => $this->getMessageTemplateId(),
+ ]);
+
+ $result[] = \CRM_Utils_Array::subset($r, ['subject', 'html', 'text']);
+ }
+
+ /**
+ * The token-processor supports a range of context parameters. We enforce different security rules for each kind of input.
+ *
+ * Broadly, this distinguishes between a few values:
+ * - Autoloaded data (e.g. 'contactId', 'activityId'). We need to ensure that the specific records are visible and extant.
+ * - Inputted data (e.g. 'contact'). We merely ensure that these are type-correct.
+ * - Prohibited/extended options, e.g. 'smarty'
+ */
+ protected function validateValues() {
+ $rows = [$this->getValues()];
+ $e = new ValidateValuesEvent($this, $rows, new \CRM_Utils_LazyArray(function () use ($rows) {
+ return array_map(
+ function ($row) {
+ return ['old' => NULL, 'new' => $row];
+ },
+ $rows
+ );
+ }));
+ $this->onValidateValues($e);
+ \Civi::dispatcher()->dispatch('civi.api4.validate', $e);
+ if (!empty($e->errors)) {
+ throw $e->toException();
+ }
+ }
+
+ protected function onValidateValues(ValidateValuesEvent $e) {
+ $errorWeightMap = \CRM_Core_Error_Log::getMap();
+ $errorWeight = $errorWeightMap[$this->getErrorLevel()];
+
+ if (count($e->records) !== 1) {
+ throw new \CRM_Core_Exception("Expected exactly one record to validate");
+ }
+ foreach ($e->records as $recordKey => $record) {
+ /** @var \Civi\WorkflowMessage\WorkflowMessageInterface $w */
+ $w = $this->_model = WorkflowMessage::create($this->getWorkflow(), [
+ 'modelProps' => $record,
+ ]);
+ $fields = $w->getFields();
+
+ $unknowns = array_diff(array_keys($record), array_keys($fields));
+ foreach ($unknowns as $fieldName) {
+ $e->addError($recordKey, $fieldName, 'unknown_field', ts('Unknown field (%1). Templates may only be executed with supported fields.', [
+ 1 => $fieldName,
+ ]));
+ }
+
+ // Merge intrinsic validations
+ foreach ($w->validate() as $issue) {
+ if ($errorWeightMap[$issue['severity']] < $errorWeight) {
+ $e->addError($recordKey, $issue['fields'], $issue['name'], $issue['message']);
+ }
+ }
+
+ // Add checks which don't fit in WFM::validate
+ foreach ($fields as $fieldName => $fieldSpec) {
+ $fieldValue = $record[$fieldName] ?? NULL;
+ if ($fieldSpec->getFkEntity() && !empty($fieldValue)) {
+ if (!empty($params['check_permissions']) && !\Civi\Api4\Utils\CoreUtil::checkAccessDelegated($fieldSpec->getFkEntity(), 'get', ['id' => $fieldValue], CRM_Core_Session::getLoggedInContactID() ?: 0)) {
+ $e->addError($recordKey, $fieldName, 'nonexistent_id', ts('Referenced record does not exist or is not visible (%1).', [
+ 1 => $this->getWorkflow() . '::' . $fieldName,
+ ]));
+ }
+ }
+ }
+ }
+ }
+
+ public function fields() {
+ return [];
+ // We don't currently get the name of the workflow. But if we do...
+ //$item = \Civi\WorkflowMessage\WorkflowMessage::create($this->workflow);
+ ///** @var \Civi\WorkflowMessage\FieldSpec[] $fields */
+ //$fields = $item->getFields();
+ //$array = [];
+ //foreach ($fields as $name => $field) {
+ // $array[$name] = $field->toArray();
+ //}
+ //return $array;
+ }
+
+}
diff --git a/Civi/Api4/WorkflowMessage.php b/Civi/Api4/WorkflowMessage.php
new file mode 100644
index 000000000000..565bff795311
--- /dev/null
+++ b/Civi/Api4/WorkflowMessage.php
@@ -0,0 +1,120 @@
+`MessageTemplate.workflow_name`).
+ * The WorkflowMessage defines the _contract_ or _processing_ of the
+ * message, and the MessageTemplate defines the _literal prose_. The prose
+ * would change frequently (eg for different deployments, locales, timeframes,
+ * and other whims), but contract would change conservatively (eg with a
+ * code-update and with some attention to backward-compatibility/change-management).
+ *
+ * @searchable none
+ * @since 5.43
+ * @package Civi\Api4
+ */
+class WorkflowMessage extends Generic\AbstractEntity {
+
+ /**
+ * @param bool $checkPermissions
+ * @return Generic\BasicGetAction
+ */
+ public static function get($checkPermissions = TRUE) {
+ return (new Generic\BasicGetAction(__CLASS__, __FUNCTION__, function ($get) {
+ return \Civi\WorkflowMessage\WorkflowMessage::getWorkflowSpecs();
+ }))->setCheckPermissions($checkPermissions);
+ }
+
+ /**
+ * @param bool $checkPermissions
+ *
+ * @return \Civi\Api4\Action\WorkflowMessage\Render
+ */
+ public static function render($checkPermissions = TRUE) {
+ return (new Action\WorkflowMessage\Render(__CLASS__, __FUNCTION__))
+ ->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' => 'Name',
+ 'data_type' => 'String',
+ ],
+ [
+ 'name' => 'group',
+ 'title' => 'Group',
+ 'data_type' => 'String',
+ ],
+ [
+ 'name' => 'class',
+ 'title' => 'Class',
+ 'data_type' => 'String',
+ ],
+ [
+ 'name' => 'description',
+ 'title' => 'Description',
+ 'data_type' => 'String',
+ ],
+ ];
+ }))->setCheckPermissions($checkPermissions);
+ }
+
+ public static function permissions() {
+ return [
+ 'meta' => ['access CiviCRM'],
+ 'default' => ['administer CiviCRM'],
+ 'render' => [
+ // nested array = OR
+ [
+ 'edit message templates',
+ 'edit user-driven message templates',
+ 'edit system workflow message templates',
+ 'render templates',
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public static function getInfo() {
+ $info = parent::getInfo();
+ $info['primary_key'] = ['name'];
+ return $info;
+ }
+
+}
diff --git a/Civi/WorkflowMessage/WorkflowMessage.php b/Civi/WorkflowMessage/WorkflowMessage.php
index 3647286ef558..5ad47eaed474 100644
--- a/Civi/WorkflowMessage/WorkflowMessage.php
+++ b/Civi/WorkflowMessage/WorkflowMessage.php
@@ -12,6 +12,7 @@
namespace Civi\WorkflowMessage;
+use Civi\Api4\Utils\ReflectionUtils;
use Civi\WorkflowMessage\Exception\WorkflowMessageException;
/**
@@ -170,4 +171,38 @@ public static function getWorkflowNameClassMap() {
return $map;
}
+ /**
+ * Get general description of available workflow-messages.
+ *
+ * @return array
+ * Array(string $workflowName => string $className).
+ * Ex: ["case_activity" => ["name" => "case_activity", "group" => "msg_workflow_case"]
+ * @internal
+ */
+ public static function getWorkflowSpecs() {
+ $compute = function() {
+ $keys = ['name', 'group', 'class', 'description', 'comment'];
+ $list = [];
+ foreach (self::getWorkflowNameClassMap() as $name => $class) {
+ $specs = [
+ 'name' => $name,
+ 'group' => \CRM_Utils_Constant::value($class . '::GROUP'),
+ 'class' => $class,
+ ];
+ $list[$name] = \CRM_Utils_Array::subset(
+ array_merge(ReflectionUtils::getCodeDocs(new \ReflectionClass($class)), $specs),
+ $keys);
+ }
+ return $list;
+ };
+
+ $cache = \Civi::cache('long');
+ $cacheKey = 'WorkflowMessage-' . __FUNCTION__;
+ $list = $cache->get($cacheKey);
+ if ($list === NULL) {
+ $cache->set($cacheKey, $list = $compute());
+ }
+ return $list;
+ }
+
}
diff --git a/tests/phpunit/api/v4/Entity/WorkflowMessageTest.php b/tests/phpunit/api/v4/Entity/WorkflowMessageTest.php
new file mode 100644
index 000000000000..056dc396bb54
--- /dev/null
+++ b/tests/phpunit/api/v4/Entity/WorkflowMessageTest.php
@@ -0,0 +1,94 @@
+addWhere('name', 'LIKE', 'case%')
+ ->execute()
+ ->indexBy('name');
+ $this->assertTrue(isset($result['case_activity']));
+ }
+
+ public function testRenderDefaultTemplate() {
+ $ex = \Civi\Api4\WorkflowMessageExample::get(0)
+ ->addWhere('name', '=', 'case_activity.class_1')
+ ->addSelect('data', 'workflow')
+ ->addChain('render', WorkflowMessage::render()
+ ->setWorkflow('$workflow')
+ ->setValues('$data.modelProps'))
+ ->execute()
+ ->single();
+ $result = $ex['render'][0];
+ $this->assertRegExp('/Case ID : 1234/', $result['text']);
+ }
+
+ public function testRenderCustomTemplate() {
+ $ex = \Civi\Api4\WorkflowMessageExample::get(0)
+ ->addWhere('name', '=', 'case_activity.class_1')
+ ->addSelect('data')
+ ->execute()
+ ->single();
+ $result = \Civi\Api4\WorkflowMessage::render(0)
+ ->setWorkflow('case_activity')
+ ->setValues($ex['data']['modelProps'])
+ ->setMessageTemplate([
+ 'msg_text' => 'The role is {$contact.role}.',
+ ])
+ ->execute()
+ ->single();
+ $this->assertRegExp('/The role is myrole./', $result['text']);
+ }
+
+ public function testRenderExamples() {
+ $examples = \Civi\Api4\WorkflowMessageExample::get(0)
+ ->addWhere('tags', 'CONTAINS', 'phpunit')
+ ->addSelect('name', 'workflow', 'data', 'asserts')
+ ->execute();
+ $this->assertTrue($examples->rowCount >= 1);
+ foreach ($examples as $example) {
+ $this->assertTrue(!empty($example['data']['modelProps']), sprintf("Example (%s) is tagged phpunit. It should have modelProps.", $example['name']));
+ $this->assertTrue(!empty($example['asserts']['default']), sprintf("Example (%s) is tagged phpunit. It should have assertions.", $example['name']));
+ $result = \Civi\Api4\WorkflowMessage::render(0)
+ ->setWorkflow($example['workflow'])
+ ->setValues($example['data']['modelProps'])
+ ->execute()
+ ->single();
+ foreach ($example['asserts']['default'] as $num => $assert) {
+ $msg = sprintf('Check assertion(%s) on example (%s)', $num, $example['name']);
+ if (isset($assert['regex'])) {
+ $this->assertRegExp($assert['regex'], $result[$assert['for']], $msg);
+ }
+ else {
+ $this->fail('Unrecognized assertion: ' . json_encode($assert));
+ }
+ }
+ }
+ }
+
+}
From 8f73531e89d16793044fd4530b8cac784a59f76e Mon Sep 17 00:00:00 2001
From: Tim Otten
Date: Thu, 29 Jul 2021 00:27:05 -0700
Subject: [PATCH 04/12] Add WorkflowMessage.getTemplateFields API
---
.../WorkflowMessage/GetTemplateFields.php | 69 +++++++++++++++++++
Civi/Api4/WorkflowMessage.php | 9 +++
2 files changed, 78 insertions(+)
create mode 100644 Civi/Api4/Action/WorkflowMessage/GetTemplateFields.php
diff --git a/Civi/Api4/Action/WorkflowMessage/GetTemplateFields.php b/Civi/Api4/Action/WorkflowMessage/GetTemplateFields.php
new file mode 100644
index 000000000000..bef1735e03d5
--- /dev/null
+++ b/Civi/Api4/Action/WorkflowMessage/GetTemplateFields.php
@@ -0,0 +1,69 @@
+workflow);
+ /** @var \Civi\WorkflowMessage\FieldSpec[] $fields */
+ $fields = $item->getFields();
+ $array = [];
+ $genericExamples = [
+ 'string[]' => ['example-string1', 'example-string2...'],
+ 'string' => 'example-string',
+ 'int[]' => [1, 2, 3],
+ 'int' => 123,
+ 'double[]' => [1.23, 4.56],
+ 'double' => 1.23,
+ 'array' => [],
+ ];
+
+ switch ($this->format) {
+ case 'metadata':
+ foreach ($fields as $name => $field) {
+ $array[$name] = $field->toArray();
+ }
+ return $array;
+
+ case 'example':
+ foreach ($fields as $name => $field) {
+ $array[$name] = NULL;
+ foreach (array_intersect(array_keys($genericExamples), $field->getType()) as $ex) {
+ $array[$name] = $genericExamples[$ex];
+ }
+ }
+ ksort($array);
+ return [$array];
+
+ default:
+ throw new \RuntimeException("Unrecognized format");
+ }
+ }
+
+}
diff --git a/Civi/Api4/WorkflowMessage.php b/Civi/Api4/WorkflowMessage.php
index 565bff795311..a206214bb09d 100644
--- a/Civi/Api4/WorkflowMessage.php
+++ b/Civi/Api4/WorkflowMessage.php
@@ -61,6 +61,15 @@ public static function render($checkPermissions = TRUE) {
->setCheckPermissions($checkPermissions);
}
+ /**
+ * @param bool $checkPermissions
+ * @return Generic\BasicGetFieldsAction
+ */
+ public static function getTemplateFields($checkPermissions = TRUE) {
+ return (new Action\WorkflowMessage\GetTemplateFields(__CLASS__, __FUNCTION__))
+ ->setCheckPermissions($checkPermissions);
+ }
+
/**
* @param bool $checkPermissions
* @return Generic\BasicGetFieldsAction
From 8cbb82e5ee2921372705a0e33044972bc74f5e63 Mon Sep 17 00:00:00 2001
From: Tim Otten
Date: Wed, 1 Sep 2021 15:24:45 -0700
Subject: [PATCH 05/12] WorkflowMessage - Track 'support' level for each
message
---
CRM/Case/WorkflowMessage/CaseActivity.php | 1 +
Civi/Api4/WorkflowMessage.php | 10 ++++++++++
Civi/WorkflowMessage/GenericWorkflowMessage.php | 4 ++++
Civi/WorkflowMessage/WorkflowMessage.php | 2 +-
4 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/CRM/Case/WorkflowMessage/CaseActivity.php b/CRM/Case/WorkflowMessage/CaseActivity.php
index 59380ba3c131..166f2a57f251 100644
--- a/CRM/Case/WorkflowMessage/CaseActivity.php
+++ b/CRM/Case/WorkflowMessage/CaseActivity.php
@@ -15,6 +15,7 @@
* the configuration/add-ons) additional copies may be sent.
*
* @see CRM_Case_BAO_Case::sendActivityCopy
+ * @support template-only
*/
class CRM_Case_WorkflowMessage_CaseActivity extends Civi\WorkflowMessage\GenericWorkflowMessage {
diff --git a/Civi/Api4/WorkflowMessage.php b/Civi/Api4/WorkflowMessage.php
index a206214bb09d..5369e84df1b4 100644
--- a/Civi/Api4/WorkflowMessage.php
+++ b/Civi/Api4/WorkflowMessage.php
@@ -97,6 +97,16 @@ public static function getFields($checkPermissions = TRUE) {
'title' => 'Description',
'data_type' => 'String',
],
+ [
+ 'name' => 'support',
+ 'title' => 'Support Level',
+ 'options' => [
+ 'experimental' => ts('Experimental: Message may change substantively with no special communication or facilitation.'),
+ 'template-only' => ts('Template Support: Changes affecting the content of the message-template will get active support/facilitation.'),
+ 'full' => ts('Full Support: All changes affecting message-templates or message-senders will get active support/facilitation.'),
+ ],
+ 'data_type' => 'String',
+ ],
];
}))->setCheckPermissions($checkPermissions);
}
diff --git a/Civi/WorkflowMessage/GenericWorkflowMessage.php b/Civi/WorkflowMessage/GenericWorkflowMessage.php
index 862c3a8d42cf..0c072a485e10 100644
--- a/Civi/WorkflowMessage/GenericWorkflowMessage.php
+++ b/Civi/WorkflowMessage/GenericWorkflowMessage.php
@@ -24,6 +24,10 @@
* @method int|null getContactId()
* @method $this setContact(array|null $contact)
* @method array|null getContact()
+ *
+ * @support template-only
+ * GenericWorkflowMessage should aim for "full" support, but it's prudent to keep
+ * it flexible for the first few months. Consider updating to "full" after Dec 2021.
*/
class GenericWorkflowMessage implements WorkflowMessageInterface {
diff --git a/Civi/WorkflowMessage/WorkflowMessage.php b/Civi/WorkflowMessage/WorkflowMessage.php
index 5ad47eaed474..32593418bbdd 100644
--- a/Civi/WorkflowMessage/WorkflowMessage.php
+++ b/Civi/WorkflowMessage/WorkflowMessage.php
@@ -181,7 +181,7 @@ public static function getWorkflowNameClassMap() {
*/
public static function getWorkflowSpecs() {
$compute = function() {
- $keys = ['name', 'group', 'class', 'description', 'comment'];
+ $keys = ['name', 'group', 'class', 'description', 'comment', 'support'];
$list = [];
foreach (self::getWorkflowNameClassMap() as $name => $class) {
$specs = [
From e43a9841d2f89565768c1d14d9df69750caf7a5c Mon Sep 17 00:00:00 2001
From: Tim Otten
Date: Wed, 1 Sep 2021 15:25:25 -0700
Subject: [PATCH 06/12] (NFC) CaseActivity Msg - Clarify `contactId` vs
`clientId`
---
CRM/Case/WorkflowMessage/CaseActivity.php | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/CRM/Case/WorkflowMessage/CaseActivity.php b/CRM/Case/WorkflowMessage/CaseActivity.php
index 166f2a57f251..738f57cd26c0 100644
--- a/CRM/Case/WorkflowMessage/CaseActivity.php
+++ b/CRM/Case/WorkflowMessage/CaseActivity.php
@@ -23,20 +23,29 @@ class CRM_Case_WorkflowMessage_CaseActivity extends Civi\WorkflowMessage\Generic
const WORKFLOW = 'case_activity';
/**
- * The recipient.
+ * The recipient of the notification. The `{contact.*}` tokens will reference this person.
*
* Example: ['contact_id' => 123, 'display_name' => 'Bob Roberts', role => 'FIXME']
*
* @var array|null
* @scope tokenContext, tplParams
+ * @fkEntity Contact
* @required
*/
public $contact;
/**
+ * The primary contact associated with this case (eg `civicrm_case_contact.contact_id`).
+ *
+ * Existing callers are inconsistent about setting this parameter.
+ *
+ * By default, CiviCRM allows one client on any given case, and this should reflect
+ * that contact. However, some systems may enable multiple clients per case.
+ * This field may not make sense in the long-term.
+ *
* @var int
* @scope tplParams as client_id
- * @required
+ * @fkEntity Contact
*/
public $clientId;
From ad6ea1b1e1993f316a528fbc891ef71ca718d97e Mon Sep 17 00:00:00 2001
From: Tim Otten
Date: Wed, 8 Sep 2021 21:13:05 -0700
Subject: [PATCH 07/12] (REF) WorkflowMessageExample - Set data_type=Array.
Remove serialization bits.
---
Civi/Api4/Action/WorkflowMessageExample/Get.php | 8 ++------
Civi/Api4/WorkflowMessageExample.php | 7 +++++--
2 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/Civi/Api4/Action/WorkflowMessageExample/Get.php b/Civi/Api4/Action/WorkflowMessageExample/Get.php
index c084fc706931..64cbc1f5700e 100644
--- a/Civi/Api4/Action/WorkflowMessageExample/Get.php
+++ b/Civi/Api4/Action/WorkflowMessageExample/Get.php
@@ -33,6 +33,7 @@ class Get extends BasicGetAction {
private $_scanner;
public function _run(Result $result) {
+ $this->_scanner = new Examples();
if ($this->select !== [] && !in_array('name', $this->select)) {
$this->select[] = 'name';
}
@@ -40,12 +41,7 @@ public function _run(Result $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;
+ return $this->_scanner->findAll();
}
protected function selectArray($values) {
diff --git a/Civi/Api4/WorkflowMessageExample.php b/Civi/Api4/WorkflowMessageExample.php
index 6b05bc44e10d..c672e6d91c05 100644
--- a/Civi/Api4/WorkflowMessageExample.php
+++ b/Civi/Api4/WorkflowMessageExample.php
@@ -60,8 +60,11 @@ public static function getFields($checkPermissions = TRUE) {
[
'name' => 'tags',
'title' => 'Tags',
- 'data_type' => 'String',
- 'serialize' => \CRM_Core_DAO::SERIALIZE_SEPARATOR_BOOKEND,
+ 'data_type' => 'Array',
+ 'options' => [
+ 'preview' => ts('Preview: Display as an example in the "Preview" dialog'),
+ 'phpunit' => ts('PHPUnit: Run basic sniff tests in PHPUnit using this example'),
+ ],
],
[
'name' => 'data',
From bee4821d961619ea731514fd665c2b3d07ed9f20 Mon Sep 17 00:00:00 2001
From: Tim Otten
Date: Thu, 9 Sep 2021 20:51:29 -0700
Subject: [PATCH 08/12] (REF) Rename WorkflowMessageExample => ExampleData
---
.../Action/{WorkflowMessageExample => ExampleData}/Get.php | 2 +-
Civi/Api4/{WorkflowMessageExample.php => ExampleData.php} | 4 ++--
Civi/Test/WorkflowMessageTestTrait.php | 2 +-
tests/phpunit/CRM/Case/WorkflowMessage/CaseActivityTest.php | 6 +++---
.../{WorkflowMessageExampleTest.php => ExampleDataTest.php} | 6 +++---
tests/phpunit/api/v4/Entity/WorkflowMessageTest.php | 6 +++---
6 files changed, 13 insertions(+), 13 deletions(-)
rename Civi/Api4/Action/{WorkflowMessageExample => ExampleData}/Get.php (97%)
rename Civi/Api4/{WorkflowMessageExample.php => ExampleData.php} (94%)
rename tests/phpunit/api/v4/Entity/{WorkflowMessageExampleTest.php => ExampleDataTest.php} (90%)
diff --git a/Civi/Api4/Action/WorkflowMessageExample/Get.php b/Civi/Api4/Action/ExampleData/Get.php
similarity index 97%
rename from Civi/Api4/Action/WorkflowMessageExample/Get.php
rename to Civi/Api4/Action/ExampleData/Get.php
index 64cbc1f5700e..628154867584 100644
--- a/Civi/Api4/Action/WorkflowMessageExample/Get.php
+++ b/Civi/Api4/Action/ExampleData/Get.php
@@ -10,7 +10,7 @@
+--------------------------------------------------------------------+
*/
-namespace Civi\Api4\Action\WorkflowMessageExample;
+namespace Civi\Api4\Action\ExampleData;
use Civi\Api4\Generic\BasicGetAction;
use Civi\Api4\Generic\Result;
diff --git a/Civi/Api4/WorkflowMessageExample.php b/Civi/Api4/ExampleData.php
similarity index 94%
rename from Civi/Api4/WorkflowMessageExample.php
rename to Civi/Api4/ExampleData.php
index c672e6d91c05..987581195b57 100644
--- a/Civi/Api4/WorkflowMessageExample.php
+++ b/Civi/Api4/ExampleData.php
@@ -18,14 +18,14 @@
* @since 5.43
* @package Civi\Api4
*/
-class WorkflowMessageExample extends \Civi\Api4\Generic\AbstractEntity {
+class ExampleData extends \Civi\Api4\Generic\AbstractEntity {
/**
* @param bool $checkPermissions
* @return Generic\AbstractGetAction
*/
public static function get($checkPermissions = TRUE) {
- return (new Action\WorkflowMessageExample\Get(__CLASS__, __FILE__))
+ return (new Action\ExampleData\Get(__CLASS__, __FILE__))
->setCheckPermissions($checkPermissions);
}
diff --git a/Civi/Test/WorkflowMessageTestTrait.php b/Civi/Test/WorkflowMessageTestTrait.php
index 395721bedeb1..71f3fe2968a0 100644
--- a/Civi/Test/WorkflowMessageTestTrait.php
+++ b/Civi/Test/WorkflowMessageTestTrait.php
@@ -27,7 +27,7 @@ public function getWorkflowName(): string {
* @throws \API_Exception
*/
protected function findExamples(): \Civi\Api4\Generic\AbstractGetAction {
- return \Civi\Api4\WorkflowMessageExample::get(0)
+ return \Civi\Api4\ExampleData::get(0)
->setSelect(['name', 'title', 'workflow', 'tags', 'data', 'asserts'])
->addWhere('workflow', '=', $this->getWorkflowName())
->addWhere('tags', 'CONTAINS', 'phpunit');
diff --git a/tests/phpunit/CRM/Case/WorkflowMessage/CaseActivityTest.php b/tests/phpunit/CRM/Case/WorkflowMessage/CaseActivityTest.php
index 5a21f5be7c83..3ca0fa32f413 100644
--- a/tests/phpunit/CRM/Case/WorkflowMessage/CaseActivityTest.php
+++ b/tests/phpunit/CRM/Case/WorkflowMessage/CaseActivityTest.php
@@ -21,7 +21,7 @@ public function getWorkflowClass(): string {
}
public function testAdhocClassEquiv() {
- $examples = \Civi\Api4\WorkflowMessageExample::get(0)
+ $examples = \Civi\Api4\ExampleData::get(0)
->setSelect(['name', 'data'])
->addWhere('name', 'IN', ['case_activity.adhoc_1', 'case_activity.class_1'])
->execute()
@@ -58,7 +58,7 @@ public function testExampleGet() {
$this->assertTrue(file_exists($file), "Expect find canary file ($file)");
- $get = \Civi\Api4\WorkflowMessageExample::get()
+ $get = \Civi\Api4\ExampleData::get()
->addWhere('name', '=', $name)
->execute()
->single();
@@ -66,7 +66,7 @@ public function testExampleGet() {
$this->assertTrue(!isset($get['data']));
$this->assertTrue(!isset($get['asserts']));
- $get = \Civi\Api4\WorkflowMessageExample::get()
+ $get = \Civi\Api4\ExampleData::get()
->addWhere('name', '=', $name)
->addSelect('workflow', 'data')
->execute()
diff --git a/tests/phpunit/api/v4/Entity/WorkflowMessageExampleTest.php b/tests/phpunit/api/v4/Entity/ExampleDataTest.php
similarity index 90%
rename from tests/phpunit/api/v4/Entity/WorkflowMessageExampleTest.php
rename to tests/phpunit/api/v4/Entity/ExampleDataTest.php
index 39659455d358..540bf3311fdd 100644
--- a/tests/phpunit/api/v4/Entity/WorkflowMessageExampleTest.php
+++ b/tests/phpunit/api/v4/Entity/ExampleDataTest.php
@@ -24,7 +24,7 @@
/**
* @group headless
*/
-class WorkflowMessageExampleTest extends UnitTestCase {
+class ExampleDataTest extends UnitTestCase {
/**
* Basic canary test fetching a specific example.
@@ -39,7 +39,7 @@ public function testGet() {
$this->assertTrue(file_exists($file), "Expect find canary file ($file)");
- $get = \Civi\Api4\WorkflowMessageExample::get()
+ $get = \Civi\Api4\ExampleData::get()
->addWhere('name', '=', $name)
->execute()
->single();
@@ -47,7 +47,7 @@ public function testGet() {
$this->assertTrue(!isset($get['data']));
$this->assertTrue(!isset($get['asserts']));
- $get = \Civi\Api4\WorkflowMessageExample::get()
+ $get = \Civi\Api4\ExampleData::get()
->addWhere('name', '=', $name)
->addSelect('workflow', 'data')
->execute()
diff --git a/tests/phpunit/api/v4/Entity/WorkflowMessageTest.php b/tests/phpunit/api/v4/Entity/WorkflowMessageTest.php
index 056dc396bb54..0707c13ddacc 100644
--- a/tests/phpunit/api/v4/Entity/WorkflowMessageTest.php
+++ b/tests/phpunit/api/v4/Entity/WorkflowMessageTest.php
@@ -36,7 +36,7 @@ public function testGet() {
}
public function testRenderDefaultTemplate() {
- $ex = \Civi\Api4\WorkflowMessageExample::get(0)
+ $ex = \Civi\Api4\ExampleData::get(0)
->addWhere('name', '=', 'case_activity.class_1')
->addSelect('data', 'workflow')
->addChain('render', WorkflowMessage::render()
@@ -49,7 +49,7 @@ public function testRenderDefaultTemplate() {
}
public function testRenderCustomTemplate() {
- $ex = \Civi\Api4\WorkflowMessageExample::get(0)
+ $ex = \Civi\Api4\ExampleData::get(0)
->addWhere('name', '=', 'case_activity.class_1')
->addSelect('data')
->execute()
@@ -66,7 +66,7 @@ public function testRenderCustomTemplate() {
}
public function testRenderExamples() {
- $examples = \Civi\Api4\WorkflowMessageExample::get(0)
+ $examples = \Civi\Api4\ExampleData::get(0)
->addWhere('tags', 'CONTAINS', 'phpunit')
->addSelect('name', 'workflow', 'data', 'asserts')
->execute();
From a37f134c0039de2fc076d570010e14cddadd3043 Mon Sep 17 00:00:00 2001
From: Tim Otten
Date: Thu, 9 Sep 2021 21:17:32 -0700
Subject: [PATCH 09/12] ExampleData - Declare the PK field to be 'name'. Add
'type=>Extra' for heavy fields.
---
Civi/Api4/ExampleData.php | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/Civi/Api4/ExampleData.php b/Civi/Api4/ExampleData.php
index 987581195b57..c53f430b1b9b 100644
--- a/Civi/Api4/ExampleData.php
+++ b/Civi/Api4/ExampleData.php
@@ -67,12 +67,14 @@ public static function getFields($checkPermissions = TRUE) {
],
],
[
+ 'type' => 'Extra',
'name' => 'data',
'title' => 'Example data',
'data_type' => 'String',
'serialize' => \CRM_Core_DAO::SERIALIZE_JSON,
],
[
+ 'type' => 'Extra',
'name' => 'asserts',
'title' => 'Test assertions',
'data_type' => 'String',
@@ -93,4 +95,13 @@ public static function permissions() {
];
}
+ /**
+ * @inheritDoc
+ */
+ public static function getInfo() {
+ $info = parent::getInfo();
+ $info['primary_key'] = ['name'];
+ return $info;
+ }
+
}
From 2cc4b0c077cc269b84fe21759473056118b7bab2 Mon Sep 17 00:00:00 2001
From: Tim Otten
Date: Mon, 13 Sep 2021 17:05:24 -0700
Subject: [PATCH 10/12] (REF-1) Convert Civi/WorkflowMessage/Examples.php to
Civi/Test/ExampleDataLoader.php
---
Civi/Api4/Action/ExampleData/Get.php | 17 +-
Civi/Test.php | 23 +++
Civi/Test/ExampleDataInterface.php | 32 ++++
Civi/Test/ExampleDataLoader.php | 135 ++++++++++++++
Civi/WorkflowMessage/Examples.php | 172 ------------------
.../phpunit/api/v4/Entity/ExampleDataTest.php | 21 ++-
6 files changed, 211 insertions(+), 189 deletions(-)
create mode 100644 Civi/Test/ExampleDataInterface.php
create mode 100644 Civi/Test/ExampleDataLoader.php
delete mode 100644 Civi/WorkflowMessage/Examples.php
diff --git a/Civi/Api4/Action/ExampleData/Get.php b/Civi/Api4/Action/ExampleData/Get.php
index 628154867584..9f91c82221f5 100644
--- a/Civi/Api4/Action/ExampleData/Get.php
+++ b/Civi/Api4/Action/ExampleData/Get.php
@@ -14,7 +14,7 @@
use Civi\Api4\Generic\BasicGetAction;
use Civi\Api4\Generic\Result;
-use Civi\WorkflowMessage\Examples;
+use Civi\Test\ExampleDataLoader;
/**
* Get a list of example data-sets.
@@ -27,13 +27,7 @@
*/
class Get extends BasicGetAction {
- /**
- * @var \Civi\WorkflowMessage\Examples
- */
- private $_scanner;
-
public function _run(Result $result) {
- $this->_scanner = new Examples();
if ($this->select !== [] && !in_array('name', $this->select)) {
$this->select[] = 'name';
}
@@ -41,16 +35,19 @@ public function _run(Result $result) {
}
protected function getRecords() {
- return $this->_scanner->findAll();
+ return \Civi\Test::examples()->getMetas();
}
protected function selectArray($values) {
$result = parent::selectArray($values);
- $heavyFields = array_intersect(['data', 'asserts'], $this->select ?: []);
+ $heavyFields = array_intersect(
+ explode(',', ExampleDataLoader::HEAVY_FIELDS),
+ $this->select ?: []
+ );
if (!empty($heavyFields)) {
foreach ($result as &$item) {
- $heavy = $this->_scanner->getHeavy($item['name']);
+ $heavy = \Civi\Test::examples()->getFull($item['name']);
$item = array_merge($item, \CRM_Utils_Array::subset($heavy, $heavyFields));
}
}
diff --git a/Civi/Test.php b/Civi/Test.php
index cf9c841ab798..ff498544235d 100644
--- a/Civi/Test.php
+++ b/Civi/Test.php
@@ -182,6 +182,29 @@ public static function data() {
return self::$singletons['data'];
}
+ /**
+ * @return \Civi\Test\ExampleDataLoader
+ */
+ public static function examples(): \Civi\Test\ExampleDataLoader {
+ if (!isset(self::$singletons['examples'])) {
+ self::$singletons['examples'] = new \Civi\Test\ExampleDataLoader();
+ }
+ return self::$singletons['examples'];
+ }
+
+ /**
+ * @param string $name
+ * Symbolic name of the data-set.
+ * @return array
+ */
+ public static function example(string $name): array {
+ $result = static::examples()->getFull($name);
+ if ($result === NULL) {
+ throw new \CRM_Core_Exception("Failed to load example data-set: $name");
+ }
+ return $result;
+ }
+
/**
* Prepare and execute a batch of SQL statements.
*
diff --git a/Civi/Test/ExampleDataInterface.php b/Civi/Test/ExampleDataInterface.php
new file mode 100644
index 000000000000..1389b05cdd3d
--- /dev/null
+++ b/Civi/Test/ExampleDataInterface.php
@@ -0,0 +1,32 @@
+build($example);`
+ * - They are not generated by '$ex->getExamples();'
+ * - They are returned by `$this->getFull()`
+ * - They are not returned by `$this->getMeta()`.
+ */
+ const HEAVY_FIELDS = 'data,asserts';
+
+ /**
+ * @var array|null
+ */
+ private $metas;
+
+ /**
+ * Get a list of all examples, including basic metadata (name, title, workflow).
+ *
+ * @return array
+ * Ex: ['my_example' => ['title' => ..., 'workflow' => ..., 'tags' => ...]]
+ * @throws \ReflectionException
+ */
+ public function getMetas(): array {
+ if ($this->metas === NULL) {
+ // $cache = new \CRM_Utils_Cache_NoCache([]);
+ $cache = \CRM_Utils_Constant::value('CIVICRM_TEST') ? new \CRM_Utils_Cache_NoCache([]) : \Civi::cache('long');
+ $cacheKey = \CRM_Utils_String::munge(__CLASS__);
+ $this->metas = $cache->get($cacheKey);
+ if ($this->metas === NULL) {
+ $this->metas = $this->findMetas();
+ $cache->set($cacheKey, $this->metas);
+ }
+ }
+ return $this->metas;
+ }
+
+ public function getMeta(string $name): ?array {
+ $all = $this->getMetas();
+ return $all[$name] ?? NULL;
+ }
+
+ /**
+ * @param string $name
+ *
+ * @return array|null
+ */
+ public function getFull(string $name): ?array {
+ $example = $this->getMeta($name);
+ if ($example === NULL) {
+ return NULL;
+ }
+
+ if ($example['file']) {
+ include_once $example['file'];
+ }
+ $obj = new $example['class']();
+ $obj->build($example);
+ return $example;
+ }
+
+ /**
+ * Get a list of all examples, including basic metadata (name, title, workflow).
+ *
+ * @return array
+ * Ex: ['my_example' => ['title' => ..., 'workflow' => ..., 'tags' => ...]]
+ * @throws \ReflectionException
+ */
+ protected function findMetas(): array {
+ $classes = array_merge(
+ // This scope of search is decidedly narrow - it should probably be expanded.
+ $this->scanExampleClasses(\Civi::paths()->getPath('[civicrm.root]/'), 'CRM/*/WorkflowMessage', '_'),
+ $this->scanExampleClasses(\Civi::paths()->getPath('[civicrm.root]/'), 'Civi/*/WorkflowMessage', '\\'),
+ $this->scanExampleClasses(\Civi::paths()->getPath('[civicrm.root]/'), 'Civi/WorkflowMessage', '\\'),
+ $this->scanExampleClasses(\Civi::paths()->getPath('[civicrm.root]/tests/phpunit/'), 'CRM/*/WorkflowMessage', '_'),
+ $this->scanExampleClasses(\Civi::paths()->getPath('[civicrm.root]/tests/phpunit/'), 'Civi/*/WorkflowMessage', '\\')
+ );
+
+ $all = [];
+ foreach ($classes as $file => $class) {
+ require_once $file;
+ $obj = new $class();
+ $offset = 0;
+ foreach ($obj->getExamples() as $example) {
+ $example['file'] = $file;
+ $example['class'] = $class;
+ if (!isset($example['name'])) {
+ $example['name'] = $example['class'] . '#' . $offset;
+ }
+ $all[$example['name']] = $example;
+ $offset++;
+ }
+ }
+
+ return $all;
+ }
+
+ /**
+ * @param $classRoot
+ * Ex: Civi root dir.
+ * @param $classDir
+ * Folder to search (within the parent).
+ * @param $classDelim
+ * Namespace separator, eg underscore or backslash.
+ * @return array
+ * Array(string $relativeFileName => string $className).
+ */
+ private function scanExampleClasses($classRoot, $classDir, $classDelim): array {
+ $r = [];
+ $exDirs = (array) glob($classRoot . $classDir);
+ foreach ($exDirs as $exDir) {
+ foreach (\CRM_Utils_File::findFiles($exDir, '*.ex.php') as $file) {
+ $file = str_replace(DIRECTORY_SEPARATOR, '/', $file);
+ $file = \CRM_Utils_File::relativize($file, \CRM_Utils_File::addTrailingSlash($classRoot, '/'));
+ $class = str_replace('/', $classDelim, preg_replace('/\.ex\.php$/', '', $file));
+ $r[$file] = $class;
+ }
+ }
+ return $r;
+ }
+
+}
diff --git a/Civi/WorkflowMessage/Examples.php b/Civi/WorkflowMessage/Examples.php
deleted file mode 100644
index 52ec6dae4237..000000000000
--- a/Civi/WorkflowMessage/Examples.php
+++ /dev/null
@@ -1,172 +0,0 @@
-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/tests/phpunit/api/v4/Entity/ExampleDataTest.php b/tests/phpunit/api/v4/Entity/ExampleDataTest.php
index 540bf3311fdd..88ace338ffb6 100644
--- a/tests/phpunit/api/v4/Entity/ExampleDataTest.php
+++ b/tests/phpunit/api/v4/Entity/ExampleDataTest.php
@@ -33,9 +33,8 @@ class ExampleDataTest extends UnitTestCase {
* @throws \Civi\API\Exception\UnauthorizedException
*/
public function testGet() {
- $file = \Civi::paths()->getPath('[civicrm.root]/Civi/WorkflowMessage/GenericWorkflowMessage/alex.ex.php');
- $workflow = 'generic';
- $name = 'generic.alex';
+ $file = \Civi::paths()->getPath('[civicrm.root]/Civi/WorkflowMessage/GenericWorkflowMessage/Alex.ex.php');
+ $name = 'workflow/generic/alex';
$this->assertTrue(file_exists($file), "Expect find canary file ($file)");
@@ -43,16 +42,24 @@ public function testGet() {
->addWhere('name', '=', $name)
->execute()
->single();
- $this->assertEquals($workflow, $get['workflow']);
- $this->assertTrue(!isset($get['data']));
- $this->assertTrue(!isset($get['asserts']));
+ $this->assertEquals($name, $get['name']);
+ $this->assertTrue(!isset($get['data']), 'Default "get" should not return "data"');
+ $this->assertTrue(!isset($get['asserts']), 'Default "get" should not return "asserts"');
+
+ $get = \Civi\Api4\ExampleData::get()
+ ->addWhere('name', 'LIKE', 'workflow/generic/%')
+ ->execute();
+ $this->assertTrue($get->count() > 0);
+ foreach ($get as $gotten) {
+ $this->assertStringStartsWith('workflow/generic/', $gotten['name']);
+ }
$get = \Civi\Api4\ExampleData::get()
->addWhere('name', '=', $name)
->addSelect('workflow', 'data')
->execute()
->single();
- $this->assertEquals($workflow, $get['workflow']);
+ $this->assertEquals($name, $get['name']);
$this->assertEquals(100, $get['data']['modelProps']['contact']['contact_id']);
}
From 8e3b2970429e5d38428361ca298bf53713f6aeb8 Mon Sep 17 00:00:00 2001
From: Tim Otten
Date: Mon, 13 Sep 2021 17:06:06 -0700
Subject: [PATCH 11/12] (REF-2) Convert WorkflowMessage examples to use
ExampleData classes
---
.../CaseActivity/CaseAdhocExample.ex.php | 49 ++++++++++++
.../CaseActivity/CaseModelExample.ex.php | 49 ++++++++++++
.../CaseActivity/adhoc_1.ex.php | 32 --------
.../CaseActivity/class_1.ex.php | 33 --------
Civi/Test/WorkflowMessageTestTrait.php | 4 +-
.../{alex.ex.php => Alex.ex.php} | 29 +++++--
.../WorkflowMessageExample.php | 79 +++++++++++++++++++
.../Case/WorkflowMessage/CaseActivityTest.php | 17 ++--
.../phpunit/api/v4/Entity/ExampleDataTest.php | 2 +-
.../api/v4/Entity/WorkflowMessageTest.php | 12 +--
10 files changed, 217 insertions(+), 89 deletions(-)
create mode 100644 CRM/Case/WorkflowMessage/CaseActivity/CaseAdhocExample.ex.php
create mode 100644 CRM/Case/WorkflowMessage/CaseActivity/CaseModelExample.ex.php
delete mode 100644 CRM/Case/WorkflowMessage/CaseActivity/adhoc_1.ex.php
delete mode 100644 CRM/Case/WorkflowMessage/CaseActivity/class_1.ex.php
rename Civi/WorkflowMessage/GenericWorkflowMessage/{alex.ex.php => Alex.ex.php} (85%)
create mode 100644 Civi/WorkflowMessage/WorkflowMessageExample.php
diff --git a/CRM/Case/WorkflowMessage/CaseActivity/CaseAdhocExample.ex.php b/CRM/Case/WorkflowMessage/CaseActivity/CaseAdhocExample.ex.php
new file mode 100644
index 000000000000..e05b2590ccf6
--- /dev/null
+++ b/CRM/Case/WorkflowMessage/CaseActivity/CaseAdhocExample.ex.php
@@ -0,0 +1,49 @@
+ "workflow/{$this->wfName}/{$this->exName}",
+ 'title' => ts('Case Activity (Adhoc-style example)'),
+ 'tags' => [],
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function build(array &$example): void {
+ $alex = \Civi\Test::example('workflow/generic/Alex');
+ $contact = $this->extend($alex['data']['modelProps']['contact'], [
+ 'role' => 'myrole',
+ ]);
+ $example['data'] = [
+ 'workflow' => $this->wfName,
+ 'tokenContext' => [
+ 'contact' => $contact,
+ ],
+ 'tplParams' => [
+ 'contact' => $contact,
+ 'isCaseActivity' => 1,
+ 'client_id' => 101,
+ 'activityTypeName' => 'Follow up',
+ 'activitySubject' => 'Test 123',
+ 'idHash' => 'abcdefg',
+ 'activity' => [
+ 'fields' => [
+ [
+ 'label' => 'Case ID',
+ 'type' => 'String',
+ 'value' => '1234',
+ ],
+ ],
+ ],
+ ],
+ ];
+ }
+
+}
diff --git a/CRM/Case/WorkflowMessage/CaseActivity/CaseModelExample.ex.php b/CRM/Case/WorkflowMessage/CaseActivity/CaseModelExample.ex.php
new file mode 100644
index 000000000000..b661afa0772e
--- /dev/null
+++ b/CRM/Case/WorkflowMessage/CaseActivity/CaseModelExample.ex.php
@@ -0,0 +1,49 @@
+ "workflow/{$this->wfName}/{$this->exName}",
+ 'title' => ts('Case Activity (Class-style example)'),
+ 'tags' => ['phpunit', 'preview'],
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function build(array &$example): void {
+ $alex = \Civi\Test::example('workflow/generic/Alex');
+ $example['data'] = $this->extend($alex['data'], [
+ 'workflow' => $this->wfName,
+ 'modelProps' => [
+ 'contact' => [
+ 'role' => 'myrole',
+ ],
+ 'isCaseActivity' => 1,
+ 'clientId' => 101,
+ 'activityTypeName' => 'Follow up',
+ 'activityFields' => [
+ [
+ 'label' => 'Case ID',
+ 'type' => 'String',
+ 'value' => '1234',
+ ],
+ ],
+ 'activitySubject' => 'Test 123',
+ 'activityCustomGroups' => [],
+ 'idHash' => 'abcdefg',
+ ],
+ ]);
+ $example['asserts'] = [
+ 'default' => [
+ ['for' => 'subject', 'regex' => '/\[case #abcdefg\] Test 123/'],
+ ['for' => 'text', 'regex' => '/Your Case Role\(s\) : myrole/'],
+ ],
+ ];
+ }
+
+}
diff --git a/CRM/Case/WorkflowMessage/CaseActivity/adhoc_1.ex.php b/CRM/Case/WorkflowMessage/CaseActivity/adhoc_1.ex.php
deleted file mode 100644
index 47c9900e4d3d..000000000000
--- a/CRM/Case/WorkflowMessage/CaseActivity/adhoc_1.ex.php
+++ /dev/null
@@ -1,32 +0,0 @@
- ts('Case Activity (Adhoc-style example)'),
- 'tags' => [],
- 'data' => function (\Civi\WorkflowMessage\Examples $examples) {
- $contact = $examples->extend('generic.alex.data.modelProps.contact', [
- 'role' => 'myrole',
- ]);
- return [
- 'tokenContext' => [
- 'contact' => $contact,
- ],
- 'tplParams' => [
- 'contact' => $contact,
- 'isCaseActivity' => 1,
- 'client_id' => 101,
- 'activityTypeName' => 'Follow up',
- 'activitySubject' => 'Test 123',
- 'idHash' => 'abcdefg',
- 'activity' => [
- 'fields' => [
- [
- 'label' => 'Case ID',
- 'type' => 'String',
- 'value' => '1234',
- ],
- ],
- ],
- ],
- ];
- },
-];
diff --git a/CRM/Case/WorkflowMessage/CaseActivity/class_1.ex.php b/CRM/Case/WorkflowMessage/CaseActivity/class_1.ex.php
deleted file mode 100644
index 65f413ecf1b5..000000000000
--- a/CRM/Case/WorkflowMessage/CaseActivity/class_1.ex.php
+++ /dev/null
@@ -1,33 +0,0 @@
- ts('Case Activity (Class-style example)'),
- 'tags' => ['phpunit', 'preview'],
- 'data' => function (\Civi\WorkflowMessage\Examples $examples) {
- return $examples->extend('generic.alex.data', [
- 'modelProps' => [
- 'contact' => [
- 'role' => 'myrole',
- ],
- 'isCaseActivity' => 1,
- 'clientId' => 101,
- 'activityTypeName' => 'Follow up',
- 'activityFields' => [
- [
- 'label' => 'Case ID',
- 'type' => 'String',
- 'value' => '1234',
- ],
- ],
- 'activitySubject' => 'Test 123',
- 'activityCustomGroups' => [],
- 'idHash' => 'abcdefg',
- ],
- ]);
- },
- 'asserts' => [
- 'default' => [
- ['for' => 'subject', 'regex' => '/\[case #abcdefg\] Test 123/'],
- ['for' => 'text', 'regex' => '/Your Case Role\(s\) : myrole/'],
- ],
- ],
-];
diff --git a/Civi/Test/WorkflowMessageTestTrait.php b/Civi/Test/WorkflowMessageTestTrait.php
index 71f3fe2968a0..1c4bd7780510 100644
--- a/Civi/Test/WorkflowMessageTestTrait.php
+++ b/Civi/Test/WorkflowMessageTestTrait.php
@@ -28,8 +28,8 @@ public function getWorkflowName(): string {
*/
protected function findExamples(): \Civi\Api4\Generic\AbstractGetAction {
return \Civi\Api4\ExampleData::get(0)
- ->setSelect(['name', 'title', 'workflow', 'tags', 'data', 'asserts'])
- ->addWhere('workflow', '=', $this->getWorkflowName())
+ ->setSelect(['name', 'title', 'tags', 'data', 'asserts'])
+ ->addWhere('name', 'LIKE', 'workflow/' . $this->getWorkflowName() . '/%')
->addWhere('tags', 'CONTAINS', 'phpunit');
}
diff --git a/Civi/WorkflowMessage/GenericWorkflowMessage/alex.ex.php b/Civi/WorkflowMessage/GenericWorkflowMessage/Alex.ex.php
similarity index 85%
rename from Civi/WorkflowMessage/GenericWorkflowMessage/alex.ex.php
rename to Civi/WorkflowMessage/GenericWorkflowMessage/Alex.ex.php
index a2ccd3216782..5a7ba7d43762 100644
--- a/Civi/WorkflowMessage/GenericWorkflowMessage/alex.ex.php
+++ b/Civi/WorkflowMessage/GenericWorkflowMessage/Alex.ex.php
@@ -1,8 +1,24 @@
[],
- 'data' => function(\Civi\WorkflowMessage\Examples $examples) {
- return [
+
+namespace Civi\WorkflowMessage\GenericWorkflowMessage;
+
+class Alex extends \Civi\WorkflowMessage\WorkflowMessageExample {
+
+ /**
+ * @inheritDoc
+ */
+ public function getExamples(): iterable {
+ yield [
+ 'name' => "workflow/{$this->wfName}/{$this->exName}",
+ 'tags' => [],
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function build(array &$example): void {
+ $example['data'] = [
'modelProps' => [
'contact' => [
'contact_id' => '100',
@@ -75,5 +91,6 @@
],
],
];
- },
-];
+ }
+
+}
diff --git a/Civi/WorkflowMessage/WorkflowMessageExample.php b/Civi/WorkflowMessage/WorkflowMessageExample.php
new file mode 100644
index 000000000000..899c0a91653b
--- /dev/null
+++ b/Civi/WorkflowMessage/WorkflowMessageExample.php
@@ -0,0 +1,79 @@
+wfClass = $m[1];
+ $this->wfName = array_search($m[1], \Civi\WorkflowMessage\WorkflowMessage::getWorkflowNameClassMap());
+ $this->exName = $m[2];
+ }
+
+ /**
+ * Get an example, merge/extend it with more data, and return the extended
+ * variant.
+ *
+ * @param array $base
+ * Baseline data to build upon.
+ * @param array $overrides
+ * Additional data to recursively add.
+ *
+ * @return array
+ * The result of merging the original example with the $overrides.
+ */
+ public function extend($base, $overrides = []) {
+ \CRM_Utils_Array::extend($base, $overrides);
+ return $base;
+ }
+
+}
diff --git a/tests/phpunit/CRM/Case/WorkflowMessage/CaseActivityTest.php b/tests/phpunit/CRM/Case/WorkflowMessage/CaseActivityTest.php
index 3ca0fa32f413..c9ec0c824e74 100644
--- a/tests/phpunit/CRM/Case/WorkflowMessage/CaseActivityTest.php
+++ b/tests/phpunit/CRM/Case/WorkflowMessage/CaseActivityTest.php
@@ -23,12 +23,12 @@ public function getWorkflowClass(): string {
public function testAdhocClassEquiv() {
$examples = \Civi\Api4\ExampleData::get(0)
->setSelect(['name', 'data'])
- ->addWhere('name', 'IN', ['case_activity.adhoc_1', 'case_activity.class_1'])
+ ->addWhere('name', 'IN', ['workflow/case_activity/CaseAdhocExample', 'workflow/case_activity/CaseModelExample'])
->execute()
->indexBy('name')
->column('data');
- $byAdhoc = Civi\WorkflowMessage\WorkflowMessage::create('case_activity', $examples['case_activity.adhoc_1']);
- $byClass = new CRM_Case_WorkflowMessage_CaseActivity($examples['case_activity.class_1']);
+ $byAdhoc = Civi\WorkflowMessage\WorkflowMessage::create('case_activity', $examples['workflow/case_activity/CaseAdhocExample']);
+ $byClass = new CRM_Case_WorkflowMessage_CaseActivity($examples['workflow/case_activity/CaseModelExample']);
$this->assertSameWorkflowMessage($byClass, $byAdhoc, 'Compare byClass and byAdhoc: ');
}
@@ -52,9 +52,8 @@ public function testConstructorEquivalence() {
* @throws \Civi\API\Exception\UnauthorizedException
*/
public function testExampleGet() {
- $file = \Civi::paths()->getPath('[civicrm.root]/CRM/Case/WorkflowMessage/CaseActivity/class_1.ex.php');
- $workflow = 'case_activity';
- $name = 'case_activity.class_1';
+ $file = \Civi::paths()->getPath('[civicrm.root]/CRM/Case/WorkflowMessage/CaseActivity/CaseModelExample.ex.php');
+ $name = 'workflow/case_activity/CaseModelExample';
$this->assertTrue(file_exists($file), "Expect find canary file ($file)");
@@ -62,16 +61,16 @@ public function testExampleGet() {
->addWhere('name', '=', $name)
->execute()
->single();
- $this->assertEquals($workflow, $get['workflow']);
+ $this->assertEquals($name, $get['name']);
$this->assertTrue(!isset($get['data']));
$this->assertTrue(!isset($get['asserts']));
$get = \Civi\Api4\ExampleData::get()
->addWhere('name', '=', $name)
- ->addSelect('workflow', 'data')
+ ->addSelect('data')
->execute()
->single();
- $this->assertEquals($workflow, $get['workflow']);
+ $this->assertEquals($name, $get['name']);
$this->assertEquals(100, $get['data']['modelProps']['contact']['contact_id']);
$this->assertEquals('myrole', $get['data']['modelProps']['contact']['role']);
}
diff --git a/tests/phpunit/api/v4/Entity/ExampleDataTest.php b/tests/phpunit/api/v4/Entity/ExampleDataTest.php
index 88ace338ffb6..8f71f57240fe 100644
--- a/tests/phpunit/api/v4/Entity/ExampleDataTest.php
+++ b/tests/phpunit/api/v4/Entity/ExampleDataTest.php
@@ -34,7 +34,7 @@ class ExampleDataTest extends UnitTestCase {
*/
public function testGet() {
$file = \Civi::paths()->getPath('[civicrm.root]/Civi/WorkflowMessage/GenericWorkflowMessage/Alex.ex.php');
- $name = 'workflow/generic/alex';
+ $name = 'workflow/generic/Alex';
$this->assertTrue(file_exists($file), "Expect find canary file ($file)");
diff --git a/tests/phpunit/api/v4/Entity/WorkflowMessageTest.php b/tests/phpunit/api/v4/Entity/WorkflowMessageTest.php
index 0707c13ddacc..43cef7085b02 100644
--- a/tests/phpunit/api/v4/Entity/WorkflowMessageTest.php
+++ b/tests/phpunit/api/v4/Entity/WorkflowMessageTest.php
@@ -37,10 +37,10 @@ public function testGet() {
public function testRenderDefaultTemplate() {
$ex = \Civi\Api4\ExampleData::get(0)
- ->addWhere('name', '=', 'case_activity.class_1')
- ->addSelect('data', 'workflow')
+ ->addWhere('name', '=', 'workflow/case_activity/CaseModelExample')
+ ->addSelect('data')
->addChain('render', WorkflowMessage::render()
- ->setWorkflow('$workflow')
+ ->setWorkflow('$data.workflow')
->setValues('$data.modelProps'))
->execute()
->single();
@@ -50,7 +50,7 @@ public function testRenderDefaultTemplate() {
public function testRenderCustomTemplate() {
$ex = \Civi\Api4\ExampleData::get(0)
- ->addWhere('name', '=', 'case_activity.class_1')
+ ->addWhere('name', '=', 'workflow/case_activity/CaseModelExample')
->addSelect('data')
->execute()
->single();
@@ -68,14 +68,14 @@ public function testRenderCustomTemplate() {
public function testRenderExamples() {
$examples = \Civi\Api4\ExampleData::get(0)
->addWhere('tags', 'CONTAINS', 'phpunit')
- ->addSelect('name', 'workflow', 'data', 'asserts')
+ ->addSelect('name', 'data', 'asserts')
->execute();
$this->assertTrue($examples->rowCount >= 1);
foreach ($examples as $example) {
$this->assertTrue(!empty($example['data']['modelProps']), sprintf("Example (%s) is tagged phpunit. It should have modelProps.", $example['name']));
$this->assertTrue(!empty($example['asserts']['default']), sprintf("Example (%s) is tagged phpunit. It should have assertions.", $example['name']));
$result = \Civi\Api4\WorkflowMessage::render(0)
- ->setWorkflow($example['workflow'])
+ ->setWorkflow($example['data']['workflow'])
->setValues($example['data']['modelProps'])
->execute()
->single();
From 2548cc10e9591e8f6620d9452d674ea064887ed3 Mon Sep 17 00:00:00 2001
From: Tim Otten
Date: Mon, 13 Sep 2021 23:40:51 -0700
Subject: [PATCH 12/12] CaseActivity - Temporarily move to tests/phpunit
This is mostly to circumvent near-term questions on reviewing CaseActivity while still allowing it as an example-case.
---
.../phpunit/CRM}/Case/WorkflowMessage/CaseActivity.php | 0
.../Case/WorkflowMessage/CaseActivity/CaseAdhocExample.ex.php | 0
.../Case/WorkflowMessage/CaseActivity/CaseModelExample.ex.php | 0
tests/phpunit/CRM/Case/WorkflowMessage/CaseActivityTest.php | 2 +-
4 files changed, 1 insertion(+), 1 deletion(-)
rename {CRM => tests/phpunit/CRM}/Case/WorkflowMessage/CaseActivity.php (100%)
rename {CRM => tests/phpunit/CRM}/Case/WorkflowMessage/CaseActivity/CaseAdhocExample.ex.php (100%)
rename {CRM => tests/phpunit/CRM}/Case/WorkflowMessage/CaseActivity/CaseModelExample.ex.php (100%)
diff --git a/CRM/Case/WorkflowMessage/CaseActivity.php b/tests/phpunit/CRM/Case/WorkflowMessage/CaseActivity.php
similarity index 100%
rename from CRM/Case/WorkflowMessage/CaseActivity.php
rename to tests/phpunit/CRM/Case/WorkflowMessage/CaseActivity.php
diff --git a/CRM/Case/WorkflowMessage/CaseActivity/CaseAdhocExample.ex.php b/tests/phpunit/CRM/Case/WorkflowMessage/CaseActivity/CaseAdhocExample.ex.php
similarity index 100%
rename from CRM/Case/WorkflowMessage/CaseActivity/CaseAdhocExample.ex.php
rename to tests/phpunit/CRM/Case/WorkflowMessage/CaseActivity/CaseAdhocExample.ex.php
diff --git a/CRM/Case/WorkflowMessage/CaseActivity/CaseModelExample.ex.php b/tests/phpunit/CRM/Case/WorkflowMessage/CaseActivity/CaseModelExample.ex.php
similarity index 100%
rename from CRM/Case/WorkflowMessage/CaseActivity/CaseModelExample.ex.php
rename to tests/phpunit/CRM/Case/WorkflowMessage/CaseActivity/CaseModelExample.ex.php
diff --git a/tests/phpunit/CRM/Case/WorkflowMessage/CaseActivityTest.php b/tests/phpunit/CRM/Case/WorkflowMessage/CaseActivityTest.php
index c9ec0c824e74..8d29866cf640 100644
--- a/tests/phpunit/CRM/Case/WorkflowMessage/CaseActivityTest.php
+++ b/tests/phpunit/CRM/Case/WorkflowMessage/CaseActivityTest.php
@@ -52,7 +52,7 @@ public function testConstructorEquivalence() {
* @throws \Civi\API\Exception\UnauthorizedException
*/
public function testExampleGet() {
- $file = \Civi::paths()->getPath('[civicrm.root]/CRM/Case/WorkflowMessage/CaseActivity/CaseModelExample.ex.php');
+ $file = \Civi::paths()->getPath('[civicrm.root]/tests/phpunit/CRM/Case/WorkflowMessage/CaseActivity/CaseModelExample.ex.php');
$name = 'workflow/case_activity/CaseModelExample';
$this->assertTrue(file_exists($file), "Expect find canary file ($file)");