Api improvements and test fix
[civicrm-core.git] / ext / afform / core / afform.php
index b1d41f5a84eae04c35f60aeb69c7c077aacf0a12..273ed37c83b8b2a09ff272818c9607d5bd470a34 100644 (file)
@@ -4,10 +4,6 @@ require_once 'afform.civix.php';
 use CRM_Afform_ExtensionUtil as E;
 use Civi\Api4\Action\Afform\Submit;
 
-function _afform_fields() {
-  return ['name', 'title', 'description', 'requires', 'layout', 'server_route', 'client_route', 'is_public'];
-}
-
 /**
  * Filter the content of $params to only have supported afform fields.
  *
@@ -16,15 +12,16 @@ function _afform_fields() {
  */
 function _afform_fields_filter($params) {
   $result = [];
-  foreach (_afform_fields() as $field) {
-    if (isset($params[$field])) {
-      $result[$field] = $params[$field];
+  $fields = \Civi\Api4\Afform::getfields()->setCheckPermissions(FALSE)->execute()->indexBy('name');
+  foreach ($fields as $fieldName => $field) {
+    if (isset($params[$fieldName])) {
+      $result[$fieldName] = $params[$fieldName];
     }
 
-    if (isset($result[$field])) {
-      switch ($field) {
+    if (isset($result[$fieldName])) {
+      switch ($fieldName) {
         case 'is_public':
-          $result[$field] = CRM_Utils_String::strtobool($result[$field]);
+          $result[$fieldName] = CRM_Utils_String::strtobool($result[$fieldName]);
           break;
 
       }
@@ -310,30 +307,45 @@ function afform_civicrm_alterAngular($angular) {
       foreach (pq('af-field', $doc) as $afField) {
         /** @var DOMElement $afField */
         $fieldName = $afField->getAttribute('name');
-        $entityName = pq($afField)->parent('[af-fieldset]')->attr('af-fieldset');
+        $entityName = pq($afField)->parents('[af-fieldset]')->attr('af-fieldset');
         if (!preg_match(';^[a-zA-Z0-9\_\-\. ]+$;', $entityName)) {
           throw new \CRM_Core_Exception("Cannot process $path: malformed entity name ($entityName)");
         }
         $entityType = $entities[$entityName]['type'];
         $getFields = civicrm_api4($entityType, 'getFields', [
+          'action' => 'create',
           'where' => [['name', '=', $fieldName]],
           'select' => ['title', 'input_type', 'input_attrs', 'options'],
           'loadOptions' => TRUE,
         ]);
         // Merge field definition data with whatever's already in the markup
-        foreach ($getFields as $field) {
+        $deep = ['input_attrs'];
+        foreach ($getFields as $fieldInfo) {
           $existingFieldDefn = trim(pq($afField)->attr('defn') ?: '');
           if ($existingFieldDefn && $existingFieldDefn[0] != '{') {
             // If it's not an object, don't mess with it.
             continue;
           }
-          foreach ($field as &$prop) {
-            $prop = json_encode($prop, JSON_UNESCAPED_SLASHES);
+          // TODO: Teach the api to return options in this format
+          if (!empty($fieldInfo['options'])) {
+            $fieldInfo['options'] = CRM_Utils_Array::makeNonAssociative($fieldInfo['options'], 'key', 'label');
           }
-          if ($existingFieldDefn) {
-            $field = array_merge($field, CRM_Utils_JS::getRawProps($existingFieldDefn));
+          // Default placeholder for select inputs
+          if ($fieldInfo['input_type'] === 'Select') {
+            $fieldInfo['input_attrs'] = ($fieldInfo['input_attrs'] ?? []) + ['placeholder' => ts('Select')];
+          }
+
+          $fieldDefn = $existingFieldDefn ? CRM_Utils_JS::getRawProps($existingFieldDefn) : [];
+          foreach ($fieldInfo as $name => $prop) {
+            // Merge array props 1 level deep
+            if (in_array($name, $deep) && !empty($fieldDefn[$name])) {
+              $fieldDefn[$name] = CRM_Utils_JS::writeObject(CRM_Utils_JS::getRawProps($fieldDefn[$name]) + array_map(['CRM_Utils_JS', 'encode'], $prop));
+            }
+            elseif (!isset($fieldDefn[$name])) {
+              $fieldDefn[$name] = CRM_Utils_JS::encode($prop);
+            }
           }
-          pq($afField)->attr('defn', CRM_Utils_JS::writeObject($field));
+          pq($afField)->attr('defn', htmlspecialchars(CRM_Utils_JS::writeObject($fieldDefn)));
         }
       }
     });
@@ -392,29 +404,37 @@ function afform_civicrm_buildAsset($asset, $params, &$mimeType, &$content) {
   }
 
   $name = $params['name'];
-  // Hmm?? $scanner = new CRM_Afform_AfformScanner();
-  // Hmm?? afform_scanner
+  /** @var \CRM_Afform_AfformScanner $scanner */
   $scanner = Civi::service('afform_scanner');
   $meta = $scanner->getMeta($name);
-  // Hmm?? $scanner = new CRM_Afform_AfformScanner();
-
-  $fileName = '~afform/' . _afform_angular_module_name($name, 'camel');
-  $htmls = [
-    $fileName => file_get_contents($scanner->findFilePath($name, 'aff.html')),
-  ];
-  $htmls = \Civi\Angular\ChangeSet::applyResourceFilters(Civi::service('angular')->getChangeSets(), 'partials', $htmls);
 
   $smarty = CRM_Core_Smarty::singleton();
   $smarty->assign('afform', [
     'camel' => _afform_angular_module_name($name, 'camel'),
     'meta' => $meta,
     'metaJson' => json_encode($meta),
-    'layout' => $htmls[$fileName],
+    'layout' => _afform_html_filter($name, $scanner->getLayout($name)),
   ]);
   $mimeType = 'text/javascript';
   $content = $smarty->fetch('afform/AfformAngularModule.tpl');
 }
 
+/**
+ * Apply any filters to an HTML partial.
+ *
+ * @param string $formName
+ * @param string $html
+ *   Original HTML.
+ * @return string
+ *   Modified HTML.
+ */
+function _afform_html_filter($formName, $html) {
+  $fileName = '~afform/' . _afform_angular_module_name($formName, 'camel');
+  $htmls = [$fileName => $html];
+  $htmls = \Civi\Angular\ChangeSet::applyResourceFilters(Civi::service('angular')->getChangeSets(), 'partials', $htmls);
+  return $htmls[$fileName];
+}
+
 /**
  * Implements hook_civicrm_alterMenu().
  */