-
-
Notifications
You must be signed in to change notification settings - Fork 825
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
APIv4 - When running validateValues(), fire an event
- Loading branch information
Showing
4 changed files
with
204 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
<?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 | | ||
+--------------------------------------------------------------------+ | ||
*/ | ||
|
||
/** | ||
* | ||
* @package CRM | ||
* @copyright CiviCRM LLC https://civicrm.org/licensing | ||
*/ | ||
|
||
namespace Civi\Api4\Event; | ||
|
||
use Symfony\Component\EventDispatcher\Event; | ||
|
||
/** | ||
* The ValidateValuesEvent ('civi.api4.validate') is emitted when creating or saving an entire record via APIv4. | ||
* It is emitted once for every record is updated. | ||
* | ||
* Example #1: Walk each record and validate some fields | ||
* | ||
* function(ValidateValuesEvent $e) { | ||
* if ($e->getEntity() !== 'Foozball') return; | ||
* foreach ($e->getRecords() as $r => $record) { | ||
* if (strtotime($record['start_time']) < CRM_Utils_Time::time()) { | ||
* $e->addError($r, 'start_time', 'past', ts('Start time has already passed.')); | ||
* } | ||
* if ($record['length'] * $record['width'] * $record['height'] > VOLUME_LIMIT) { | ||
* $e->addError($r, ['length', 'width', 'height'], 'excessive_volume', ts('The record is too big.')); | ||
* } | ||
* } | ||
* } | ||
* | ||
* Example #2: Prohibit recording `Contribution` records on `Student` contacts. | ||
* | ||
* function(ValidateValuesEvent $e) { | ||
* if ($e->getEntity() !== 'Contribution') return; | ||
* $contactSubTypes = CRM_Utils_SQL_Select::from('civicrm_contact') | ||
* ->where('id IN (#ids)', ['ids' => array_column($e->getRecords(), 'contact_id')]) | ||
* ->select('id, contact_sub_type') | ||
* ->execute()->fetchMap('id', 'contact_sub_type'); | ||
* foreach ($e->getRecords() as $r => $record) { | ||
* if ($contactSubTypes[$record['contact_id']] === 'Student') { | ||
* $e->addError($r, 'contact_id', 'student_prohibited', ts('Donations from student records are strictly prohibited.')); | ||
* } | ||
* } | ||
* } | ||
* | ||
*/ | ||
class ValidateValuesEvent extends Event { | ||
|
||
/** | ||
* Type of entity being validated. | ||
* | ||
* @var string | ||
* Ex: 'Membership' | ||
*/ | ||
protected $entity; | ||
|
||
/** | ||
* Key-value properties for a specific record. | ||
* | ||
* @var array | ||
*/ | ||
protected $records; | ||
|
||
/** | ||
* List of error messages. | ||
* | ||
* @var array | ||
* Array(string $errorName => string $errorMessage) | ||
* Note: | ||
*/ | ||
protected $errors = []; | ||
|
||
/** | ||
* ValidateValuesEvent constructor. | ||
* | ||
* @param string $entity | ||
* @param array $records | ||
* List of records to validate. | ||
*/ | ||
public function __construct(string $entity, array $records) { | ||
$this->entity = $entity; | ||
$this->records = $records; | ||
} | ||
|
||
/** | ||
* @return string | ||
* Ex: 'Contact', 'Contribution', 'Mailing' | ||
*/ | ||
public function getEntity(): string { | ||
return $this->entity; | ||
} | ||
|
||
/** | ||
* Get a list of records (with their new values). | ||
* | ||
* @return array | ||
*/ | ||
public function getRecords(): array { | ||
return $this->records; | ||
} | ||
|
||
// Tempting to add a 'getOldRecords()' and/or 'getEffectiveRecords()`. For updates, these would provide | ||
// lazy-loading (ie call '{$entity}.get' in the same security-context and cache the result). | ||
// Thus, most validations wouldn't require any extra I/O -- but if some did need I/O, then it could be minimized. | ||
|
||
/** | ||
* Add an error. | ||
* | ||
* @param string|int $recordKey | ||
* The validator may work with multiple records. This should identify the specific record. | ||
* Each record is identified by its offset (`getRecords()[$recordKey] === [...the record...]`). | ||
* @param string|array $field | ||
* The name of the field which has an error. | ||
* If the error is multi-field (e.g. mismatched password-confirmation), then use an array. | ||
* If the error is independent of any field, then use []. | ||
* @param string $name | ||
* @param string|NULL $message | ||
* @return $this | ||
*/ | ||
public function addError($recordKey, $field, string $name, string $message = NULL): self { | ||
$this->errors[] = [ | ||
'record' => $recordKey, | ||
'fields' => (array) $field, | ||
'name' => $name, | ||
'message' => $message ?: ts('Error code (%1)', [1 => $name]), | ||
]; | ||
return $this; | ||
} | ||
|
||
/** | ||
* @return array | ||
* Each error has the format: | ||
* - record: string|int | ||
* - fields: string[] | ||
* - name: string, symbolic name | ||
* - message: string, printable message | ||
*/ | ||
public function getErrors(): array { | ||
return $this->errors; | ||
} | ||
|
||
/** | ||
* Replace the list of errors. | ||
* | ||
* This is useful if you need to filter or rewrite the list. | ||
* | ||
* @param array $errors | ||
* @return self | ||
*/ | ||
public function setErrors(array $errors): self { | ||
$this->errors = $errors; | ||
return $this; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters