APIv4 - Support pseudoconstant lookups
[civicrm-core.git] / Civi / Api4 / Generic / AbstractAction.php
index 310df486e5c4ca14b8573e2d0f0918677167672b..905c9a1911d69acd5199726267ae8e221c82f0d1 100644 (file)
@@ -20,8 +20,8 @@
 
 namespace Civi\Api4\Generic;
 
+use Civi\Api4\Utils\FormattingUtil;
 use Civi\Api4\Utils\ReflectionUtils;
-use Civi\Api4\Utils\ActionUtil;
 
 /**
  * Base class for all api actions.
@@ -56,16 +56,21 @@ abstract class AbstractAction implements \ArrayAccess {
    *
    * Keys can be any string - this will be the name given to the output.
    *
-   * You can reference other values in the api results in this call by prefixing them with $
+   * You can reference other values in the api results in this call by prefixing them with `$`.
    *
    * For example, you could create a contact and place them in a group by chaining the
-   * GroupContact api to the Contact api:
+   * `GroupContact` api to the `Contact` api:
    *
+   * ```php
    * Contact::create()
    *   ->setValue('first_name', 'Hello')
-   *   ->addChain('add_to_a_group', GroupContact::create()->setValue('contact_id', '$id')->setValue('group_id', 123))
+   *   ->addChain('add_a_group', GroupContact::create()
+   *     ->setValue('contact_id', '$id')
+   *     ->setValue('group_id', 123)
+   *   )
+   * ```
    *
-   * This will substitute the id of the newly created contact with $id.
+   * This will substitute the id of the newly created contact with `$id`.
    *
    * @var array
    */
@@ -84,10 +89,10 @@ abstract class AbstractAction implements \ArrayAccess {
   /**
    * Add debugging info to the api result.
    *
-   * When enabled, the $result->debug will be populated with information about the api call,
+   * When enabled, `$result->debug` will be populated with information about the api call,
    * including sql queries executed.
    *
-   * Note: with checkPermissions enabled, debug info will only be returned if the user has "view debug output" permission.
+   * **Note:** with checkPermissions enabled, debug info will only be returned if the user has "view debug output" permission.
    *
    * @var bool
    */
@@ -175,9 +180,8 @@ abstract class AbstractAction implements \ArrayAccess {
    * @param string $name
    *   Unique name for this chained request
    * @param \Civi\Api4\Generic\AbstractAction $apiRequest
-   * @param string|int $index
-   *   Either a string for how the results should be indexed e.g. 'name'
-   *   or the index of a single result to return e.g. 0 for the first result.
+   * @param string|int|array $index
+   *   See `civicrm_api4()` for documentation of `$index` param
    * @return $this
    */
   public function addChain($name, AbstractAction $apiRequest, $index = NULL) {
@@ -266,10 +270,19 @@ abstract class AbstractAction implements \ArrayAccess {
   public function getParamInfo($param = NULL) {
     if (!isset($this->_paramInfo)) {
       $defaults = $this->getParamDefaults();
+      $vars = [
+        'entity' => $this->getEntityName(),
+        'action' => $this->getActionName(),
+      ];
+      // For actions like "getFields" and "getActions" they are not getting the entity itself.
+      // So generic docs will make more sense like this:
+      if (substr($vars['action'], 0, 3) === 'get' && substr($vars['action'], -1) === 's') {
+        $vars['entity'] = lcfirst(substr($vars['action'], 3, -1));
+      }
       foreach ($this->reflect()->getProperties(\ReflectionProperty::IS_PROTECTED) as $property) {
         $name = $property->getName();
         if ($name != 'version' && $name[0] != '_') {
-          $this->_paramInfo[$name] = ReflectionUtils::getCodeDocs($property, 'Property');
+          $this->_paramInfo[$name] = ReflectionUtils::getCodeDocs($property, 'Property', $vars);
           $this->_paramInfo[$name]['default'] = $defaults[$name];
         }
       }
@@ -411,15 +424,14 @@ abstract class AbstractAction implements \ArrayAccess {
    */
   public function entityFields() {
     if (!$this->_entityFields) {
-      $getFields = ActionUtil::getAction($this->getEntityName(), 'getFields');
+      $getFields = \Civi\API\Request::create($this->getEntityName(), 'getFields', [
+        'version' => 4,
+        'checkPermissions' => $this->checkPermissions,
+        'action' => $this->getActionName(),
+        'includeCustom' => FALSE,
+      ]);
       $result = new Result();
-      if (method_exists($this, 'getBaoName')) {
-        $getFields->setIncludeCustom(FALSE);
-      }
-      $getFields
-        ->setCheckPermissions($this->checkPermissions)
-        ->setAction($this->getActionName())
-        ->_run($result);
+      $getFields->_run($result);
       $this->_entityFields = (array) $result->indexBy('name');
     }
     return $this->_entityFields;
@@ -459,6 +471,42 @@ abstract class AbstractAction implements \ArrayAccess {
     return $unmatched;
   }
 
+  /**
+   * Replaces pseudoconstants in input values
+   *
+   * @param array $record
+   * @throws \API_Exception
+   */
+  protected function formatWriteValues(&$record) {
+    $optionFields = [];
+    // Collect fieldnames with a :pseudoconstant suffix & remove them from $record array
+    foreach (array_keys($record) as $expr) {
+      $suffix = strrpos($expr, ':');
+      if ($suffix) {
+        $fieldName = substr($expr, 0, $suffix);
+        $field = $this->entityFields()[$fieldName] ?? NULL;
+        if ($field) {
+          $optionFields[$fieldName] = [
+            'val' => $record[$expr],
+            'name' => empty($field['custom_field_id']) ? $field['name'] : 'custom_' . $field['custom_field_id'],
+            'suffix' => substr($expr, $suffix + 1),
+            'depends' => $field['input_attrs']['controlField'] ?? NULL,
+          ];
+          unset($record[$expr]);
+        }
+      }
+    }
+    // Sort option lookups by dependency, so e.g. country_id is processed first, then state_province_id, then county_id
+    uasort($optionFields, function ($a, $b) {
+      return $a['name'] === $b['depends'] ? -1 : 1;
+    });
+    // Replace pseudoconstants. Note this is a reverse lookup as we are evaluating input not output.
+    foreach ($optionFields as $fieldName => $info) {
+      $options = FormattingUtil::getPseudoconstantList($this->_entityName, $info['name'], $info['suffix'], $record, 'create');
+      $record[$fieldName] = FormattingUtil::replacePseudoconstant($options, $info['val'], TRUE);
+    }
+  }
+
   /**
    * This function is used internally for evaluating field annotations.
    *