+<?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\Api4\Action\WorkflowMessage;
+
+use Civi\Api4\Event\ValidateValuesEvent;
+use Civi\WorkflowMessage\WorkflowMessage;
+
+/**
+ * Render a message.
+ *
+ * @method $this setValues(array $rows) Set the list of records to be rendered.
+ * @method array getValues()
+ * @method $this setMessageTemplate(array|null $fragments) Set of messages to be rendered.
+ * @method array|null getMessageTemplate()
+ * @method $this setMessageTemplateId(int|null $id) Set of messages to be rendered.
+ * @method int|null getMessageTemplateId()
+ * @method $this setWorkflow(string $workflow)
+ * @method string getWorkflow()
+ * @method $this setErrorLevel(string $workflow)
+ * @method string getErrorLevel()
+ */
+class Render extends \Civi\Api4\Generic\AbstractAction {
+
+ /**
+ * Abort if the validator finds any issues at this error level.
+ *
+ * @var string
+ * @options error,warning,info
+ */
+ protected $errorLevel = 'error';
+
+ /**
+ * Symbolic name of the workflow step for which we need a message.
+ * @var string
+ * @required
+ */
+ protected $workflow;
+
+ /**
+ * @var array
+ */
+ protected $values = [];
+
+ /**
+ * Load and render a specific message template (by ID).
+ *
+ * @var int|null
+ */
+ protected $messageTemplateId;
+
+ /**
+ * Use a draft message template.
+ *
+ * @var array|null
+ * - `subject`: Message template (eg `Hello {contact.first_name}!`)
+ * - `text`: Message template (eg `Hello {contact.first_name}!`)
+ * - `html`: Message template (eg `<p>Hello {contact.first_name}!</p>`)
+ */
+ 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;
+ }
+
+}