--- /dev/null
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved. |
+ | |
+ | This work is published under the GNU AGPLv3 license with some |
+ | permitted exceptions and without any warranty. For full license |
+ | and copyright information, see https://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * When an activity is created in a case, the "case_activity" email is sent.
+ * Generally, the email is sent to the assignee, although (depending on
+ * the configuration/add-ons) additional copies may be sent.
+ *
+ * @see CRM_Case_BAO_Case::sendActivityCopy
+ */
+class CRM_Case_WorkflowMessage_CaseActivity extends Civi\WorkflowMessage\GenericWorkflowMessage {
+
+ const GROUP = 'msg_tpl_workflow_case';
+ const WORKFLOW = 'case_activity';
+
+ /**
+ * The recipient.
+ *
+ * Example: ['contact_id' => 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 = [];
+
+}
--- /dev/null
+<?php
+return [
+ 'title' => 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',
+ ],
+ ],
+ ],
+ ],
+ ];
+ },
+];
--- /dev/null
+<?php
+return [
+ 'title' => 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/'],
+ ],
+ ],
+];
--- /dev/null
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved. |
+ | |
+ | This work is published under the GNU AGPLv3 license with some |
+ | permitted exceptions and without any warranty. For full license |
+ | and copyright information, see https://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+namespace Civi\Test;
+
+use Civi\WorkflowMessage\WorkflowMessage;
+
+trait WorkflowMessageTestTrait {
+
+ abstract public function getWorkflowClass(): string;
+
+ public function getWorkflowName(): string {
+ $class = $this->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()");
+ }
+
+}
$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) {
--- /dev/null
+<?php
+
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved. |
+ | |
+ | This work is published under the GNU AGPLv3 license with some |
+ | permitted exceptions and without any warranty. For full license |
+ | and copyright information, see https://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * Class CRM_Case_WorkflowMessage_CaseActivityTest
+ */
+class CRM_Case_WorkflowMessage_CaseActivityTest extends CiviUnitTestCase {
+ use \Civi\Test\WorkflowMessageTestTrait;
+
+ public function getWorkflowClass(): string {
+ return CRM_Case_WorkflowMessage_CaseActivity::class;
+ }
+
+ public function testAdhocClassEquiv() {
+ $examples = \Civi\Api4\WorkflowMessageExample::get(0)
+ ->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']);
+ }
+
+}
$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);
}