Merge pull request #19165 from eileenmcnaughton/pdf
[civicrm-core.git] / Civi / Api4 / Query / SqlFunction.php
index dbc245f7bfc40dfd5e3ed0c5e6dbc74a47614b75..ef5d7747f259a5b5252f137a4e1dd08a1d808e3a 100644 (file)
@@ -18,8 +18,14 @@ namespace Civi\Api4\Query;
  */
 abstract class SqlFunction extends SqlExpression {
 
+  /**
+   * @var array
+   */
   protected static $params = [];
 
+  /**
+   * @var array[]
+   */
   protected $args = [];
 
   /**
@@ -40,17 +46,22 @@ abstract class SqlFunction extends SqlExpression {
    */
   protected function initialize() {
     $arg = trim(substr($this->expr, strpos($this->expr, '(') + 1, -1));
-    foreach ($this->getParams() as $param) {
+    foreach ($this->getParams() as $idx => $param) {
       $prefix = $this->captureKeyword($param['prefix'], $arg);
+      $this->args[$idx] = [
+        'prefix' => $prefix,
+        'expr' => [],
+        'suffix' => NULL,
+      ];
       if ($param['expr'] && isset($prefix) || in_array('', $param['prefix']) || !$param['optional']) {
-        $this->captureExpressions($arg, $param['expr'], $param['must_be'], $param['cant_be']);
-        $this->captureKeyword($param['suffix'], $arg);
+        $this->args[$idx]['expr'] = $this->captureExpressions($arg, $param['expr'], $param['must_be'], $param['cant_be']);
+        $this->args[$idx]['suffix'] = $this->captureKeyword($param['suffix'], $arg);
       }
     }
   }
 
   /**
-   * Shift a keyword off the beginning of the argument string and into the argument array.
+   * Shift a keyword off the beginning of the argument string and return it.
    *
    * @param array $keywords
    *   Whitelist of keywords
@@ -60,7 +71,6 @@ abstract class SqlFunction extends SqlExpression {
   private function captureKeyword($keywords, &$arg) {
     foreach (array_filter($keywords) as $key) {
       if (strpos($arg, $key . ' ') === 0) {
-        $this->args[] = $key;
         $arg = ltrim(substr($arg, strlen($key)));
         return $key;
       }
@@ -69,35 +79,34 @@ abstract class SqlFunction extends SqlExpression {
   }
 
   /**
-   * Shifts 0 or more expressions off the argument string and into the argument array
+   * Shifts 0 or more expressions off the argument string and returns them
    *
    * @param string $arg
    * @param int $limit
    * @param array $mustBe
    * @param array $cantBe
+   * @return array
    * @throws \API_Exception
    */
   private function captureExpressions(&$arg, $limit, $mustBe, $cantBe) {
-    $captured = 0;
+    $captured = [];
     $arg = ltrim($arg);
     while ($arg) {
       $item = $this->captureExpression($arg);
       $arg = ltrim(substr($arg, strlen($item)));
       $expr = SqlExpression::convert($item, FALSE, $mustBe, $cantBe);
       $this->fields = array_merge($this->fields, $expr->getFields());
-      if ($captured) {
-        $this->args[] = ',';
-      }
-      $this->args[] = $expr;
+      $captured[] = $expr;
       $captured++;
       // Keep going if we have a comma indicating another expression follows
-      if ($captured < $limit && substr($arg, 0, 1) === ',') {
+      if (count($captured) < $limit && substr($arg, 0, 1) === ',') {
         $arg = ltrim(substr($arg, 1));
       }
       else {
-        return;
+        break;
       }
     }
+    return $captured;
   }
 
   /**
@@ -147,20 +156,50 @@ abstract class SqlFunction extends SqlExpression {
     return $item;
   }
 
+  /**
+   * Render the expression for insertion into the sql query
+   *
+   * @param array $fieldList
+   * @return string
+   */
   public function render(array $fieldList): string {
-    $output = $this->getName() . '(';
+    $output = '';
+    $params = $this->getParams();
     foreach ($this->args as $index => $arg) {
-      if ($index && $arg !== ',') {
-        $output .= ' ';
-      }
-      if (is_object($arg)) {
-        $output .= $arg->render($fieldList);
+      $rendered = $this->renderArg($arg, $params[$index], $fieldList);
+      if (strlen($rendered)) {
+        $output .= (strlen($output) ? ' ' : '') . $rendered;
       }
-      else {
-        $output .= $arg;
+    }
+    return $this->getName() . '(' . $output . ')';
+  }
+
+  /**
+   * @param array $arg
+   * @param array $param
+   * @param array $fieldList
+   * @return string
+   */
+  private function renderArg($arg, $param, $fieldList): string {
+    // Supply api_default
+    if (!isset($arg['prefix']) && !isset($arg['suffix']) && empty($arg['expr']) && !empty($param['api_default'])) {
+      $arg = [
+        'prefix' => $param['api_default']['prefix'] ?? reset($param['prefix']),
+        'expr' => array_map([parent::class, 'convert'], $param['api_default']['expr'] ?? []),
+        'suffix' => $param['api_default']['suffix'] ?? reset($param['suffix']),
+      ];
+    }
+    $rendered = $arg['prefix'] ?? '';
+    foreach ($arg['expr'] ?? [] as $idx => $expr) {
+      if (strlen($rendered) || $idx) {
+        $rendered .= $idx ? ', ' : ' ';
       }
+      $rendered .= $expr->render($fieldList);
+    }
+    if (isset($arg['suffix'])) {
+      $rendered .= (strlen($rendered) ? ' ' : '') . $arg['suffix'];
     }
-    return $output . ')';
+    return $rendered;
   }
 
   /**
@@ -194,11 +233,20 @@ abstract class SqlFunction extends SqlExpression {
         'optional' => FALSE,
         'must_be' => [],
         'cant_be' => ['SqlWild'],
+        'api_default' => NULL,
       ];
     }
     return $params;
   }
 
+  /**
+   * Get the arguments passed to this sql function instance.
+   * @return array[]
+   */
+  public function getArgs(): array {
+    return $this->args;
+  }
+
   /**
    * @return string
    */