Skip to content

Commit

Permalink
Merge pull request #21338 from totten/master-msgtpl-api
Browse files Browse the repository at this point in the history
(dev/mail#83) Introduce WorkflowMessage APIs with CaseActivity example
  • Loading branch information
eileenmcnaughton authored Sep 14, 2021
2 parents 73156d5 + 2548cc1 commit 5bc6e53
Show file tree
Hide file tree
Showing 22 changed files with 1,511 additions and 24 deletions.
4 changes: 4 additions & 0 deletions CRM/Core/Permission.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
],
Expand Down
58 changes: 58 additions & 0 deletions Civi/Api4/Action/ExampleData/Get.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?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\ExampleData;

use Civi\Api4\Generic\BasicGetAction;
use Civi\Api4\Generic\Result;
use Civi\Test\ExampleDataLoader;

/**
* Get a list of example data-sets.
*
* Examples are generated by scanning `*.ex.php` files. The scanner caches
* metadata fields (`name`, `title`, `tags`, `file`) to avoid extraneous scanning, but
* substantive fields (`data`) are computed as-needed.
*
* FIXME: When we have an update for dev-docs, include a `@link` here.
*/
class Get extends BasicGetAction {

public function _run(Result $result) {
if ($this->select !== [] && !in_array('name', $this->select)) {
$this->select[] = 'name';
}
parent::_run($result);
}

protected function getRecords() {
return \Civi\Test::examples()->getMetas();
}

protected function selectArray($values) {
$result = parent::selectArray($values);

$heavyFields = array_intersect(
explode(',', ExampleDataLoader::HEAVY_FIELDS),
$this->select ?: []
);
if (!empty($heavyFields)) {
foreach ($result as &$item) {
$heavy = \Civi\Test::examples()->getFull($item['name']);
$item = array_merge($item, \CRM_Utils_Array::subset($heavy, $heavyFields));
}
}

return $result;
}

}
69 changes: 69 additions & 0 deletions Civi/Api4/Action/WorkflowMessage/GetTemplateFields.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace Civi\Api4\Action\WorkflowMessage;

/**
* Class GetTemplateFields
* @package Civi\Api4\Action\WorkflowMessage
*
* @method $this setWorkflow(string $workflow)
* @method string getWorkflow()
* @method $this setFormat(string $workflow)
* @method string getFormat()
*/
class GetTemplateFields extends \Civi\Api4\Generic\BasicGetAction {

/**
* @var string
* @required
*/
public $workflow;

/**
* Controls the return format.
* - 'metadata': Return the fields as an array of metadata
* - 'example': Return the fields as an example record (a basis for passing into Render::$values).
*
* @var string
* @options metadata,example
*/
protected $format = 'metadata';

protected function getRecords() {
$item = \Civi\WorkflowMessage\WorkflowMessage::create($this->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");
}
}

}
168 changes: 168 additions & 0 deletions Civi/Api4/Action/WorkflowMessage/Render.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<?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;
}

}
Loading

0 comments on commit 5bc6e53

Please sign in to comment.