Merge pull request #18430 from eileenmcnaughton/open
[civicrm-core.git] / Civi / Api4 / Query / Api4SelectQuery.php
index ea8cad5f3fe2e119dd7d102c9711dbf453388d9d..009095ebc91d3e150027b5c5e19a20041e98c75b 100644 (file)
@@ -26,8 +26,8 @@ use Civi\Api4\Utils\SelectUtil;
  * Leaf operators are one of:
  *
  * * '=', '<=', '>=', '>', '<', 'LIKE', "<>", "!=",
- * * "NOT LIKE", 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN',
- * * 'IS NOT NULL', or 'IS NULL'.
+ * * 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN',
+ * * 'IS NOT NULL', or 'IS NULL', 'CONTAINS'.
  */
 class Api4SelectQuery {
 
@@ -187,10 +187,25 @@ class Api4SelectQuery {
         $select = array_merge(['id'], $select);
       }
 
+      // Expand the superstar 'custom.*' to select all fields in all custom groups
+      $customStar = array_search('custom.*', array_values($select), TRUE);
+      if ($customStar !== FALSE) {
+        $customGroups = civicrm_api4($this->getEntity(), 'getFields', [
+          'checkPermissions' => FALSE,
+          'where' => [['custom_group', 'IS NOT NULL']],
+        ], ['custom_group' => 'custom_group']);
+        $customSelect = [];
+        foreach ($customGroups as $groupName) {
+          $customSelect[] = "$groupName.*";
+        }
+        array_splice($select, $customStar, 1, $customSelect);
+      }
+
       // Expand wildcards in joins (the api wrapper already expanded non-joined wildcards)
       $wildFields = array_filter($select, function($item) {
         return strpos($item, '*') !== FALSE && strpos($item, '.') !== FALSE && strpos($item, '(') === FALSE && strpos($item, ' ') === FALSE;
       });
+
       foreach ($wildFields as $item) {
         $pos = array_search($item, array_values($select));
         $this->autoJoinFK($item);
@@ -260,7 +275,7 @@ class Api4SelectQuery {
       $suffix = strstr($item, ':');
       if ($suffix && $expr->getType() === 'SqlField') {
         $field = $this->getField($item);
-        $options = FormattingUtil::getPseudoconstantList($field['entity'], $field['name'], substr($suffix, 1));
+        $options = FormattingUtil::getPseudoconstantList($field, substr($suffix, 1));
         if ($options) {
           asort($options);
           $column = "FIELD($column,'" . implode("','", array_keys($options)) . "')";
@@ -347,7 +362,7 @@ class Api4SelectQuery {
   protected function composeClause(array $clause, string $type) {
     // Pad array for unary operators
     list($expr, $operator, $value) = array_pad($clause, 3, NULL);
-    if (!in_array($operator, \CRM_Core_DAO::acceptedSQLOperators(), TRUE)) {
+    if (!in_array($operator, CoreUtil::getOperators(), TRUE)) {
       throw new \API_Exception('Illegal operator');
     }
 
@@ -364,7 +379,8 @@ class Api4SelectQuery {
         $fieldAlias = $expr;
         // Attempt to format if this is a real field
         if (isset($this->apiFieldSpec[$expr])) {
-          FormattingUtil::formatInputValue($value, $expr, $this->apiFieldSpec[$expr]);
+          $field = $this->getField($expr);
+          FormattingUtil::formatInputValue($value, $expr, $field);
         }
       }
       // Expr references a non-field expression like a function; convert to alias
@@ -377,7 +393,8 @@ class Api4SelectQuery {
         foreach ($this->selectAliases as $selectAlias => $selectExpr) {
           list($selectField) = explode(':', $selectAlias);
           if ($selectAlias === $selectExpr && $fieldName === $selectField && isset($this->apiFieldSpec[$fieldName])) {
-            FormattingUtil::formatInputValue($value, $expr, $this->apiFieldSpec[$fieldName]);
+            $field = $this->getField($fieldName);
+            FormattingUtil::formatInputValue($value, $expr, $field);
             $fieldAlias = $selectAlias;
             break;
           }
@@ -400,7 +417,29 @@ class Api4SelectQuery {
         return sprintf('%s %s %s', $fieldAlias, $operator, $valExpr->render($this->apiFieldSpec));
       }
       elseif ($fieldName) {
-        FormattingUtil::formatInputValue($value, $fieldName, $this->apiFieldSpec[$fieldName]);
+        $field = $this->getField($fieldName);
+        FormattingUtil::formatInputValue($value, $fieldName, $field);
+      }
+    }
+
+    if ($operator === 'CONTAINS') {
+      switch ($field['serialize'] ?? NULL) {
+        case \CRM_Core_DAO::SERIALIZE_JSON:
+          $operator = 'LIKE';
+          $value = '%"' . $value . '"%';
+          // FIXME: Use this instead of the above hack once MIN_INSTALL_MYSQL_VER is bumped to 5.7.
+          // return sprintf('JSON_SEARCH(%s, "one", "%s") IS NOT NULL', $fieldAlias, \CRM_Core_DAO::escapeString($value));
+          break;
+
+        case \CRM_Core_DAO::SERIALIZE_SEPARATOR_BOOKEND:
+          $operator = 'LIKE';
+          $value = '%' . \CRM_Core_DAO::VALUE_SEPARATOR . $value . \CRM_Core_DAO::VALUE_SEPARATOR . '%';
+          break;
+
+        default:
+          $operator = 'LIKE';
+          $value = '%' . $value . '%';
+          break;
       }
     }