Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(dev/mail#83) Introduce WorkflowMessage APIs with CaseActivity example #21338

Merged
merged 12 commits into from
Sep 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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