Afform - Provide `base_module` calculated field to APIv4 Afform.get
authorColeman Watts <coleman@civicrm.org>
Mon, 1 Nov 2021 17:27:51 +0000 (13:27 -0400)
committerColeman Watts <coleman@civicrm.org>
Tue, 2 Nov 2021 19:03:57 +0000 (15:03 -0400)
This extra Afform field mirrors the functionality of the field of the same name
from ManagedEntity APIv4 trait: it returns the name of the extension in which
an Afform is packaged.

CRM/Extension/Mapper.php
Civi/Api4/Generic/BasicGetAction.php
ext/afform/core/CRM/Afform/AfformScanner.php
ext/afform/core/Civi/Api4/Action/Afform/Get.php
ext/afform/core/Civi/Api4/Afform.php
ext/afform/core/tests/phpunit/Civi/Afform/AfformGetTest.php
ext/afform/mock/tests/phpunit/api/v4/AfformTest.php
tests/phpunit/api/v4/Action/BasicActionsTest.php
tests/phpunit/api/v4/Action/GetFromArrayTest.php

index 3e0c9dd14e4b7dc190becc1a83d8124a807aa9b9..dd989c8413ad4bf9b5c8ca6f19a4d2b13d577a8c 100644 (file)
@@ -480,8 +480,7 @@ class CRM_Extension_Mapper {
   /**
    * Get a list of all installed modules, including enabled and disabled ones
    *
-   * @return array
-   *   CRM_Core_Module
+   * @return CRM_Core_Module[]
    */
   public function getModules() {
     $result = [];
index 1f784cc6b90333d5b675cee80b15a41e46737ee7..5c615968cd397f0e9ff55508eb15b7fcf5fe3eae 100644 (file)
@@ -107,6 +107,7 @@ class BasicGetAction extends AbstractGetAction {
     $fields = $this->entityFields();
     foreach ($records as &$values) {
       foreach ($this->entityFields() as $field) {
+        $values += [$field['name'] => NULL];
         if (!empty($field['options'])) {
           foreach (FormattingUtil::$pseudoConstantSuffixes as $suffix) {
             $pseudofield = $field['name'] . ':' . $suffix;
index f7a7125f4beef75901dc525ee87d41a9aa5535f8..8d02738fe72e49698bcbec0ecf7a9e1162daf1e0 100644 (file)
@@ -49,10 +49,9 @@ class CRM_Afform_AfformScanner {
 
     $mapper = CRM_Extension_System::singleton()->getMapper();
     foreach ($mapper->getModules() as $module) {
-      /** @var $module CRM_Core_Module */
       try {
         if ($module->is_active) {
-          $this->appendFilePaths($paths, dirname($mapper->keyToPath($module->name)) . DIRECTORY_SEPARATOR . 'ang', 20);
+          $this->appendFilePaths($paths, dirname($mapper->keyToPath($module->name)) . DIRECTORY_SEPARATOR . 'ang', $module->name);
         }
       }
       catch (CRM_Extension_Exception_MissingException $e) {
@@ -60,7 +59,7 @@ class CRM_Afform_AfformScanner {
       }
     }
 
-    $this->appendFilePaths($paths, $this->getSiteLocalPath(), 10);
+    $this->appendFilePaths($paths, $this->getSiteLocalPath(), '');
 
     $this->cache->set('afformAllPaths', $paths);
     return $paths;
@@ -157,23 +156,19 @@ class CRM_Afform_AfformScanner {
   }
 
   /**
-   * Adds has_local & has_base to an afform metadata record
+   * Adds base_module, has_local & has_base to an afform metadata record
    *
    * @param array $record
    */
   public function addComputedFields(&$record) {
     $name = $record['name'];
-    // Ex: $allPaths['viewIndividual'][0] == '/var/www/foo/afform/view-individual'].
+    // Ex: $allPaths['viewIndividual']['org.civicrm.foo'] == '/var/www/foo/afform/view-individual'].
     $allPaths = $this->findFilePaths()[$name] ?? [];
-    // $activeLayoutPath = $this->findFilePath($name, self::LAYOUT_FILE);
-    // $activeMetaPath = $this->findFilePath($name, self::METADATA_FILE);
-    $localLayoutPath = $this->createSiteLocalPath($name, self::LAYOUT_FILE);
-    $localMetaPath = $this->createSiteLocalPath($name, self::METADATA_FILE);
-
-    $record['has_local'] = file_exists($localLayoutPath) || file_exists($localMetaPath);
+    // Empty string key refers to the site local path
+    $record['has_local'] = isset($allPaths['']);
     if (!isset($record['has_base'])) {
-      $record['has_base'] = ($record['has_local'] && count($allPaths) > 1)
-        || (!$record['has_local'] && count($allPaths) > 0);
+      $record['base_module'] = \CRM_Utils_Array::first(array_filter(array_keys($allPaths)));
+      $record['has_base'] = !empty($record['base_module']);
     }
   }
 
@@ -211,16 +206,17 @@ class CRM_Afform_AfformScanner {
    *   Ex: ['foo' => [0 => '/var/www/org.example.foobar/ang']]
    * @param string $parent
    *   Ex: '/var/www/org.example.foobar/afform/'
-   * @param int $priority
-   *   Lower priority files override higher priority files.
+   * @param string $module
+   *   Name of module or '' empty string for local files.
    */
-  private function appendFilePaths(&$formPaths, $parent, $priority) {
+  private function appendFilePaths(&$formPaths, $parent, $module) {
     $files = preg_grep(self::FILE_REGEXP, (array) glob("$parent/*"));
 
     foreach ($files as $file) {
       $fileBase = preg_replace(self::FILE_REGEXP, '', $file);
       $name = basename($fileBase);
-      $formPaths[$name][$priority] = $fileBase;
+      $formPaths[$name][$module] = $fileBase;
+      // Local files get top priority
       ksort($formPaths[$name]);
     }
   }
index 629083e655d51b9f4af2c6e517d8756d626d8d9e..cfd32556cfd088e2e94695dc1740727151e621a5 100644 (file)
@@ -17,7 +17,7 @@ class Get extends \Civi\Api4\Generic\BasicGetAction {
   public function getRecords() {
     /** @var \CRM_Afform_AfformScanner $scanner */
     $scanner = \Civi::service('afform_scanner');
-    $getComputed = $this->_isFieldSelected('has_local', 'has_base');
+    $getComputed = $this->_isFieldSelected('has_local', 'has_base', 'base_module');
     $getLayout = $this->_isFieldSelected('layout');
     $getSearchDisplays = $this->_isFieldSelected('search_displays');
     $values = [];
index 4e5f0c977f7085456137bbaa68c99f138b3f013d..c1f481e20dad70e673eb908f5181ac743f395f4d 100644 (file)
@@ -3,6 +3,7 @@
 namespace Civi\Api4;
 
 use Civi\Api4\Generic\BasicBatchAction;
+use Civi\Api4\Generic\BasicGetFieldsAction;
 
 /**
  * User-configurable forms.
@@ -138,7 +139,7 @@ class Afform extends Generic\AbstractEntity {
    * @return Generic\BasicGetFieldsAction
    */
   public static function getFields($checkPermissions = TRUE) {
-    return (new Generic\BasicGetFieldsAction('Afform', __FUNCTION__, function($self) {
+    return (new Generic\BasicGetFieldsAction('Afform', __FUNCTION__, function(BasicGetFieldsAction $self) {
       $fields = [
         [
           'name' => 'name',
@@ -222,14 +223,24 @@ class Afform extends Generic\AbstractEntity {
           'name' => 'has_local',
           'type' => 'Extra',
           'data_type' => 'Boolean',
+          'description' => 'Whether a local copy is saved on site',
           'readonly' => TRUE,
         ];
         $fields[] = [
           'name' => 'has_base',
           'type' => 'Extra',
           'data_type' => 'Boolean',
+          'description' => 'Is provided by an extension',
           'readonly' => TRUE,
         ];
+        $fields[] = [
+          'name' => 'base_module',
+          'type' => 'Extra',
+          'data_type' => 'String',
+          'description' => 'Name of extension which provides this form',
+          'readonly' => TRUE,
+          'options' => $self->getLoadOptions() ? \CRM_Core_PseudoConstant::getExtensions() : TRUE,
+        ];
         $fields[] = [
           'name' => 'search_displays',
           'type' => 'Extra',
index b8d0f9d8b08310035bcefa4a22d59a914eff522a..8be0dc3fb128d67f62df18e8983d8ad90b561b45 100644 (file)
@@ -52,6 +52,7 @@ class AfformGetTest extends \PHPUnit\Framework\TestCase implements HeadlessInter
     $this->assertEquals($this->formName, $result['name']);
     $this->assertFalse($result['has_base']);
     $this->assertArrayNotHasKey('has_local', $result);
+    $this->assertArrayNotHasKey('base_module', $result);
   }
 
   public function testGetSearchDisplays() {
index ffb62766f81c29d801d5b2399e500813372b7d32..af4854f74e3b2c3cc5625f1418d279c532726e68 100644 (file)
@@ -54,7 +54,7 @@ class api_v4_AfformTest extends api_v4_AfformTestCase {
 
     $message = 'The initial Afform.get should return default data';
     $result = Civi\Api4\Afform::get()
-      ->addSelect('*', 'has_base', 'has_local')
+      ->addSelect('*', 'has_base', 'has_local', 'base_module')
       ->addWhere('name', '=', $formName)->execute();
     $this->assertEquals($formName, $result[0]['name'], $message);
     $this->assertEquals($get($originalMetadata, 'title'), $get($result[0], 'title'), $message);
@@ -65,6 +65,7 @@ class api_v4_AfformTest extends api_v4_AfformTestCase {
     $this->assertTrue(is_array($result[0]['layout']), $message);
     $this->assertEquals(TRUE, $get($result[0], 'has_base'), $message);
     $this->assertEquals(FALSE, $get($result[0], 'has_local'), $message);
+    $this->assertEquals('org.civicrm.afform-mock', $get($result[0], 'base_module'), $message);
 
     $message = 'After updating with Afform.create, the revised data should be returned';
     $result = Civi\Api4\Afform::update()
@@ -78,7 +79,7 @@ class api_v4_AfformTest extends api_v4_AfformTestCase {
 
     $message = 'After updating, the Afform.get API should return blended data';
     $result = Civi\Api4\Afform::get()
-      ->addSelect('*', 'has_base', 'has_local')
+      ->addSelect('*', 'has_base', 'has_local', 'base_module')
       ->addWhere('name', '=', $formName)->execute();
     $this->assertEquals($formName, $result[0]['name'], $message);
     $this->assertEquals($get($originalMetadata, 'title'), $get($result[0], 'title'), $message);
@@ -89,11 +90,12 @@ class api_v4_AfformTest extends api_v4_AfformTestCase {
     $this->assertTrue(is_array($result[0]['layout']), $message);
     $this->assertEquals(TRUE, $get($result[0], 'has_base'), $message);
     $this->assertEquals(TRUE, $get($result[0], 'has_local'), $message);
+    $this->assertEquals('org.civicrm.afform-mock', $get($result[0], 'base_module'), $message);
 
     Civi\Api4\Afform::revert()->addWhere('name', '=', $formName)->execute();
     $message = 'After reverting, the final Afform.get should return default data';
     $result = Civi\Api4\Afform::get()
-      ->addSelect('*', 'has_base', 'has_local')
+      ->addSelect('*', 'has_base', 'has_local', 'base_module')
       ->addWhere('name', '=', $formName)->execute();
     $this->assertEquals($formName, $result[0]['name'], $message);
     $this->assertEquals($get($originalMetadata, 'title'), $get($result[0], 'title'), $message);
@@ -103,6 +105,7 @@ class api_v4_AfformTest extends api_v4_AfformTestCase {
     $this->assertTrue(is_array($result[0]['layout']), $message);
     $this->assertEquals(TRUE, $get($result[0], 'has_base'), $message);
     $this->assertEquals(FALSE, $get($result[0], 'has_local'), $message);
+    $this->assertEquals('org.civicrm.afform-mock', $get($result[0], 'base_module'), $message);
   }
 
   public function getFormatExamples() {
index 53edd99f5519327d870a8de84a234f47129a51fb..d67a61598c44e4c71c8e2cc6b8338060cd348069 100644 (file)
@@ -279,7 +279,7 @@ class BasicActionsTest extends UnitTestCase implements HookInterface {
 
     foreach (MockBasicEntity::get()->addSelect('*')->execute() as $result) {
       ksort($result);
-      $this->assertEquals(['color', 'group', 'identifier', 'shape', 'size', 'weight'], array_keys($result));
+      $this->assertEquals(['color', 'foo', 'fruit', 'group', 'identifier', 'shape', 'size', 'weight'], array_keys($result));
     }
 
     $result = MockBasicEntity::get()
index c41b100203633318824dfa758179f138bdc4f529..7b6f802a253f55d28c473815e626b6c4efc78cfb 100644 (file)
@@ -76,6 +76,7 @@ class GetFromArrayTest extends UnitTestCase {
       ],
       [
         'field1' => 3,
+        'field3' => NULL,
       ],
       [
         'field1' => 4,