APIv4 - Support output_formatters with aggregated columns
authorcolemanw <coleman@civicrm.org>
Sun, 10 Sep 2023 22:39:31 +0000 (18:39 -0400)
committercolemanw <coleman@civicrm.org>
Tue, 12 Sep 2023 01:52:46 +0000 (21:52 -0400)
Civi/Api4/Utils/FormattingUtil.php
tests/phpunit/api/v4/Action/EntityFileTest.php

index 4a63e4db4192c1bea7603d9a9a853c02685b1b6b..0376bb044efb6d98649abdc83ed8332c8e0cacee 100644 (file)
@@ -237,17 +237,17 @@ class FormattingUtil {
         continue;
       }
       $fieldExpr = SqlExpression::convert($selectAliases[$key] ?? $key);
-      $fieldName = \CRM_Utils_Array::first($fieldExpr->getFields() ?? '');
+      $fieldName = \CRM_Utils_Array::first($fieldExpr->getFields());
       $baseName = $fieldName ? \CRM_Utils_Array::first(explode(':', $fieldName)) : NULL;
       $field = $fields[$fieldName] ?? $fields[$baseName] ?? NULL;
       $dataType = $field['data_type'] ?? ($fieldName == 'id' ? 'Integer' : NULL);
-      // Allow Sql Functions to do alter the value and/or $dataType
+      // Allow Sql Functions to alter the value and/or $dataType
       if (method_exists($fieldExpr, 'formatOutputValue') && is_string($value)) {
         $fieldExpr->formatOutputValue($dataType, $result, $key);
         $value = $result[$key];
       }
       if (!empty($field['output_formatters'])) {
-        self::applyFormatters($result, $fieldName, $field, $value);
+        self::applyFormatters($result, $fieldExpr, $field, $value);
         $dataType = NULL;
       }
       // Evaluate pseudoconstant suffixes
@@ -345,15 +345,34 @@ class FormattingUtil {
    * Apply a field's output_formatters callback functions
    *
    * @param array $result
-   * @param string $fieldPath
-   * @param array $field
+   * @param \Civi\Api4\Query\SqlExpression $fieldExpr
+   * @param array $fieldDefn
    * @param mixed $value
    */
-  private static function applyFormatters(array $result, string $fieldPath, array $field, &$value) {
-    $row = self::filterByPath($result, $fieldPath, $field['name']);
+  private static function applyFormatters(array $result, SqlExpression $fieldExpr, array $fieldDefn, &$value): void {
+    $fieldPath = \CRM_Utils_Array::first($fieldExpr->getFields());
+    $row = self::filterByPath($result, $fieldPath, $fieldDefn['name']);
+
+    // For aggregated array data, apply the formatter to each item
+    if (is_array($value) && $fieldExpr->getType() === 'SqlFunction' && $fieldExpr::getCategory() === 'aggregate') {
+      foreach ($value as $index => &$val) {
+        $subRow = $row;
+        foreach ($row as $rowKey => $rowValue) {
+          if (is_array($rowValue) && array_key_exists($index, $rowValue)) {
+            $subRow[$rowKey] = $rowValue[$index];
+          }
+        }
+        self::applyFormatter($fieldDefn, $subRow, $val);
+      }
+    }
+    else {
+      self::applyFormatter($fieldDefn, $row, $value);
+    }
+  }
 
-    foreach ($field['output_formatters'] as $formatter) {
-      $formatter($value, $row, $field);
+  private static function applyFormatter(array $fieldDefn, array $row, &$value): void {
+    foreach ($fieldDefn['output_formatters'] as $formatter) {
+      $formatter($value, $row, $fieldDefn);
     }
   }
 
index 54777e980295810fbc09f7d69c833a90d13c4aee..9df5d3b11ef67b1572f7f6577c29907072a8c224 100644 (file)
@@ -20,6 +20,7 @@
 namespace api\v4\Action;
 
 use api\v4\Api4TestBase;
+use Civi\Api4\Activity;
 use Civi\Api4\EntityFile;
 use Civi\Api4\File;
 use Civi\Api4\Note;
@@ -98,4 +99,33 @@ class EntityFileTest extends Api4TestBase implements TransactionalInterface, Hoo
     $this->assertStringContainsString("id=$file[3]&eid=$note[3]&fcs=", $allowedNotes[$note[3]]['file.url']);
   }
 
+  public function testGetAggregateFileFields() {
+    $activity = $this->createTestRecord('Activity');
+
+    foreach (['text/plain' => 'txt', 'image/png' => 'png', 'image/jpg' => 'jpg'] as $mimeType => $ext) {
+      // FIXME: Use api4 when available
+      civicrm_api3('Attachment', 'create', [
+        'entity_table' => 'civicrm_activity',
+        'entity_id' => $activity['id'],
+        'name' => 'test_file.' . $ext,
+        'mime_type' => $mimeType,
+        'content' => 'hello',
+      ])['id'];
+    }
+
+    $get = Activity::get(FALSE)
+      ->addWhere('id', '=', $activity['id'])
+      ->addJoin('File AS file', 'LEFT', 'EntityFile', ['file.entity_id', '=', 'id'], ['file.entity_table', '=', '"civicrm_activity"'])
+      ->addGroupBy('id')
+      ->addSelect('GROUP_CONCAT(UNIQUE file.file_name) AS aggregate_file_name')
+      ->addSelect('GROUP_CONCAT(UNIQUE file.url) AS aggregate_url')
+      ->addSelect('GROUP_CONCAT(UNIQUE file.icon) AS aggregate_icon')
+      ->execute()->single();
+
+    $this->assertCount(3, $get['aggregate_url']);
+    $this->assertCount(3, $get['aggregate_icon']);
+    $this->assertEquals(['test_file.txt', 'test_file.png', 'test_file.jpg'], $get['aggregate_file_name']);
+    $this->assertEquals(['fa-file-text-o', 'fa-file-image-o', 'fa-file-image-o'], $get['aggregate_icon']);
+  }
+
 }