Merge pull request #19498 from eileenmcnaughton/complete_order_test
authorSeamus Lee <seamuslee001@gmail.com>
Wed, 3 Feb 2021 20:21:57 +0000 (07:21 +1100)
committerGitHub <noreply@github.com>
Wed, 3 Feb 2021 20:21:57 +0000 (07:21 +1100)
[REF] Stop passing contributionPageID to isEmailReceipt

16 files changed:
CRM/Utils/PagerAToZ.php
Civi/API/Kernel.php
ext/afform/admin/CRM/AfformAdmin/Page/Base.php
ext/afform/admin/CRM/AfformAdmin/Upgrader.php
ext/afform/admin/Civi/AfformAdmin/AfformAdminMeta.php
ext/afform/admin/Civi/Api4/Action/Afform/LoadAdminData.php
ext/afform/admin/ang/afAdmin/afAdminList.controller.js
ext/afform/admin/ang/afAdmin/afAdminList.html
ext/afform/admin/ang/afGuiEditor/afGuiEditor.component.js
ext/afform/admin/ang/afGuiEditor/config-form.html
ext/afform/core/Civi/Api4/Action/Afform/Get.php
ext/afform/core/Civi/Api4/Action/Afform/Submit.php
ext/oauth-client/settings/OAuthClient.setting.php
ext/search/ang/crmSearchAdmin.module.js
ext/search/ang/crmSearchAdmin/crmSearchAdmin.component.js
ext/search/ang/crmSearchAdmin/searchList.html

index 354ff437020ece7fe4bb1862b929e743deb3a9f4..650dd715e35cc3175eea05658729bd4d27daa9a8 100644 (file)
@@ -156,7 +156,7 @@ class CRM_Utils_PagerAToZ {
           'force' => 1,
           'qfKey' => $qfKey,
         ];
-        if ($query->_context === 'amtg') {
+        if (($query->_context ?? '') === 'amtg') {
           // See https://lab.civicrm.org/dev/core/-/issues/2333
           // Seems to be needed in add to group flow.
           $urlParams['_qf_Basic_display'] = 1;
index 91544231ae8ebd97053d0bd30021a7c070ce192d..f3cab8ed09c24d352702796fb58575711ba2bcef 100644 (file)
@@ -159,12 +159,15 @@ class Kernel {
    */
   public function boot($apiRequest) {
     require_once 'api/Exception.php';
+    // the create error function loads some functions from utils
+    // so this require is also needed for apiv4 until such time as
+    // we alter create error.
+    require_once 'api/v3/utils.php';
     switch ($apiRequest['version']) {
       case 3:
         if (!is_array($apiRequest['params'])) {
           throw new \API_Exception('Input variable `params` is not an array', 2000);
         }
-        require_once 'api/v3/utils.php';
         _civicrm_api3_initialize();
         break;
 
index 31338e5eaf532a1aac99be5dc2c3562365276799..fc22b35b94d052489cf3fa4e57e5ee1007d96510 100644 (file)
@@ -16,7 +16,7 @@ class CRM_AfformAdmin_Page_Base extends CRM_Core_Page {
 
   public function run() {
     $breadCrumb = [
-      'title' => ts('Forms'),
+      'title' => ts('Form Builder'),
       'url' => CRM_Utils_System::url('civicrm/admin/afform', NULL, FALSE, '/'),
     ];
     CRM_Utils_System::appendBreadCrumb([$breadCrumb]);
index f129852e728985421be1e0c99f80ac81572894b4..616e6305cc86059955e23a54977cdeb52d6ad42a 100644 (file)
@@ -23,12 +23,13 @@ class CRM_AfformAdmin_Upgrader extends CRM_AfformAdmin_Upgrader_Base {
       if (!$existing) {
         civicrm_api3('Navigation', 'create', [
           'parent_id' => 'Customize Data and Screens',
-          'label' => E::ts('Forms'),
+          'label' => E::ts('Form Builder'),
           'weight' => 1,
-          'name' => 'afform_gui',
+          'name' => 'afform_admin',
           'permission' => 'administer CiviCRM',
           'url' => 'civicrm/admin/afform',
           'is_active' => 1,
+          'icon' => 'crm-i fa-list-alt',
         ]);
       }
     }
@@ -48,4 +49,21 @@ class CRM_AfformAdmin_Upgrader extends CRM_AfformAdmin_Upgrader_Base {
     ]);
   }
 
+  /**
+   * Update menu item
+   *
+   * @return TRUE on success
+   * @throws Exception
+   */
+  public function upgrade_0001() {
+    $this->ctx->log->info('Applying update 0001');
+    \Civi\Api4\Navigation::update(FALSE)
+      ->addValue('icon', 'crm-i fa-list-alt')
+      ->addValue('label', E::ts('Form Builder'))
+      ->addValue('name', 'afform_admin')
+      ->addWhere('name', '=', 'afform_gui')
+      ->execute();
+    return TRUE;
+  }
+
 }
index 782a5fcc716ca96661db8cb6ab0978b2c6ae4f18..468d690bdbcdfe6a41f1f9a05f9eb5af749bf48d 100644 (file)
@@ -10,13 +10,24 @@ class AfformAdminMeta {
    * @return array
    */
   public static function getAdminSettings() {
+    $afformTypes = (array) \Civi\Api4\OptionValue::get(FALSE)
+      ->addSelect('name', 'label', 'icon')
+      ->addWhere('is_active', '=', TRUE)
+      ->addWhere('option_group_id:name', '=', 'afform_type')
+      ->addOrderBy('weight', 'ASC')
+      ->execute();
+    // Pluralize tabs (too bad option groups only store a single label)
+    $plurals = [
+      'form' => ts('Custom Forms'),
+      'search' => ts('Search Displays'),
+      'block' => ts('Field Blocks'),
+      'system' => ts('System Forms'),
+    ];
+    foreach ($afformTypes as $index => $type) {
+      $afformTypes[$index]['plural'] = $plurals[$type['name']] ?? \CRM_Utils_String::pluralize($type['label']);
+    }
     return [
-      'afform_type' => \Civi\Api4\OptionValue::get(FALSE)
-        ->addSelect('name', 'label', 'icon')
-        ->addWhere('is_active', '=', TRUE)
-        ->addWhere('option_group_id:name', '=', 'afform_type')
-        ->addOrderBy('weight', 'ASC')
-        ->execute(),
+      'afform_type' => $afformTypes,
     ];
   }
 
index db3dcb3b4d08e4b71ab5552a08fb7530dde4dae6..67ee5e0269c1a83edb03106fa2fca1a013d510fc 100644 (file)
@@ -135,7 +135,7 @@ class LoadAdminData extends \Civi\Api4\Generic\AbstractAction {
 
       // The full contents of blocks used on the form have been loaded. Get basic info about others relevant to these entities.
       $blockInfo = Afform::get($this->checkPermissions)
-        ->addSelect('name', 'title', 'block', 'join', 'directive_name')
+        ->addSelect('name', 'title', 'block', 'join', 'directive_name', 'repeat')
         ->addWhere('type', '=', 'block')
         ->addWhere('block', 'IN', $entities)
         ->addWhere('directive_name', 'NOT IN', array_keys($info['blocks']))
index f1d8003439593d062e5f94786d1a0eb0ab783a5f..716e7900dbdb9f291c2f5b2cd82caa3e58ab600c 100644 (file)
@@ -8,12 +8,12 @@
     $scope.crmUrl = CRM.url;
 
     this.tabs = CRM.afAdmin.afform_type;
-    $scope.tabs = _.indexBy(ctrl.tabs, 'name');
+    $scope.types = _.indexBy(ctrl.tabs, 'name');
     _.each(['form', 'block', 'search'], function(type) {
-      if ($scope.tabs[type]) {
-        $scope.tabs[type].options = [];
+      if ($scope.types[type]) {
+        $scope.types[type].options = [];
         if (type === 'form') {
-          $scope.tabs.form.default = '#create/form/Individual';
+          $scope.types.form.default = '#create/form/Individual';
         }
       }
     });
@@ -33,7 +33,7 @@
 
     this.createLinks = function() {
       ctrl.searchCreateLinks = '';
-      if ($scope.tabs[ctrl.tab].options.length) {
+      if ($scope.types[ctrl.tab].options.length) {
         return;
       }
       var links = [];
@@ -48,7 +48,7 @@
             });
           }
         });
-        $scope.tabs.form.options = _.sortBy(links, 'Label');
+        $scope.types.form.options = _.sortBy(links, 'Label');
       }
 
       if (ctrl.tab === 'block') {
@@ -61,7 +61,7 @@
             });
           }
         });
-        $scope.tabs.block.options = _.sortBy(links, 'Label');
+        $scope.types.block.options = _.sortBy(links, 'Label');
       }
 
       if (ctrl.tab === 'search') {
@@ -75,7 +75,7 @@
               icon: searchDisplay['type:icon']
             });
           });
-          $scope.tabs.search.options = _.sortBy(links, 'Label');
+          $scope.types.search.options = _.sortBy(links, 'Label');
         });
       }
     };
index 430f0309858267267fd399216e79740c4ac40fd1..bebb59f91e8e9875dca645458db4d852fd87f84b 100644 (file)
@@ -1,29 +1,29 @@
 <div id="bootstrap-theme" class="afadmin-list">
-  <h1 crm-page-title>{{:: ts('Configurable Forms') }}</h1>
+  <h1 crm-page-title>{{:: ts('Form Builder') }}</h1>
 
   <ul class="nav nav-tabs">
     <li role="presentation" ng-repeat="tab in $ctrl.tabs" ng-class="{active: tab.name === $ctrl.tab}">
-      <a href ng-click="$ctrl.tab = tab.name; $ctrl.searchAfformList = ''"><i class="crm-i {{ tab.icon }}"></i> {{:: tab.label }}</a>
+      <a href ng-click="$ctrl.tab = tab.name; $ctrl.searchAfformList = ''"><i class="crm-i {{ tab.icon }}"></i> {{:: tab.plural }}</a>
     </li>
   </ul>
 
   <div class="form-inline">
     <label for="afform-list-filter">{{:: ts('Filter:') }}</label>
     <input class="form-control" type="search" id="afform-list-filter" ng-model="$ctrl.searchAfformList" placeholder="&#xf002">
-    <div class="btn-group pull-right" ng-if="tabs[$ctrl.tab].options">
-      <a ng-if="tabs[$ctrl.tab].default" href="{{ tabs[$ctrl.tab].default }}" class="btn btn-primary">
-        {{ ts('New %1', {1: tabs[$ctrl.tab].label }) }}
+    <div class="btn-group pull-right" ng-if="types[$ctrl.tab].options">
+      <a ng-if="types[$ctrl.tab].default" href="{{ types[$ctrl.tab].default }}" class="btn btn-primary">
+        {{ ts('New %1', {1: types[$ctrl.tab].label }) }}
       </a>
       <button ng-click="$ctrl.createLinks()" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-        <span ng-class="{'sr-only': tabs[$ctrl.tab].default}">{{ ts('New %1', {1: tabs[$ctrl.tab].label }) }}</span>
+        <span ng-class="{'sr-only': types[$ctrl.tab].default}">{{ ts('New %1', {1: types[$ctrl.tab].label }) }}</span>
         <span class="caret"></span>
       </button>
       <ul class="dropdown-menu">
         <li>
-          <input ng-if="tabs[$ctrl.tab].options.length" type="search" class="form-control" placeholder="&#xf002" ng-model="$ctrl.searchCreateLinks">
-          <a href ng-if="!tabs[$ctrl.tab].options.length"><i class="crm-i fa-spinner fa-spin"></i></a>
+          <input ng-if="types[$ctrl.tab].options.length" type="search" class="form-control" placeholder="&#xf002" ng-model="$ctrl.searchCreateLinks">
+          <a href ng-if="!types[$ctrl.tab].options.length"><i class="crm-i fa-spinner fa-spin"></i></a>
         </li>
-        <li ng-repeat="link in tabs[$ctrl.tab].options | filter:$ctrl.searchCreateLinks">
+        <li ng-repeat="link in types[$ctrl.tab].options | filter:$ctrl.searchCreateLinks">
           <a href="{{ link.url }}">
             <i class="crm-i {{ link.icon }}"></i>
             {{ link.label }}
index 288a4bb503e6dcbca6508315759015b68ceb29f8..2f4dceff97bc453957852ad6fae06fb766f6db42 100644 (file)
@@ -33,6 +33,8 @@
         }
         if (editor.mode === 'clone') {
           delete $scope.afform.name;
+          delete $scope.afform.server_route;
+          $scope.afform.is_dashlet = false;
           $scope.afform.title += ' ' + ts('(copy)');
         }
         $scope.canvasTab = 'layout';
       };
 
       $scope.save = function() {
+        var afform = JSON.parse(angular.toJson($scope.afform));
+        // This might be set to undefined by validation
+        afform.server_route = afform.server_route || '';
         $scope.saving = $scope.changesSaved = true;
-        crmApi4('Afform', 'save', {formatWhitespace: true, records: [JSON.parse(angular.toJson($scope.afform))]})
+        crmApi4('Afform', 'save', {formatWhitespace: true, records: [afform]})
           .then(function (data) {
             $scope.saving = false;
             $scope.afform.name = data[0].name;
index 58630edfa58d7569817db263b8f9385f93c9499d..a8689c2603f996e26c5439f6f1ac13306b47012c 100644 (file)
@@ -4,7 +4,7 @@
   <label for="af_config_form_title">
     {{:: ts('Title:') }} <span class="crm-marker">*</span>
   </label>
-  <input ng-model="afform.title" class="form-control" id="af_config_form_title" required />
+  <input ng-model="afform.title" class="form-control" id="af_config_form_title" required title="{{:: ts('Required') }}" />
 </div>
 
 <div class="form-group">
   <!-- "Semi-private": not generally public, but not audited for secrecy -->
 </div>
 
-<div class="form-group">
+<div class="form-group" ng-class="{'has-error': !!config_form.server_route.$error.pattern}">
   <label for="af_config_form_server_route">
-    {{:: ts('Path:') }}
+    {{:: ts('Page:') }}
   </label>
-  <input ng-model="afform.server_route" class="form-control" id="af_config_form_server_route" />
-  <p class="help-block">{{:: ts('Expose the form as a standalone page on the web site. (Example: "civicrm/my-form")') }}</p>
+  <input ng-model="afform.server_route" name="server_route" class="form-control" id="af_config_form_server_route" pattern="^civicrm\/[-0-9a-zA-z\/_]+$" onfocus="this.value = this.value || 'civicrm/'" onblur="if (this.value === 'civicrm/') this.value = ''" title="{{:: ts('Path must begin with &quot;civicrm/&quot;') }}">
+  <p class="help-block">{{:: ts('Expose the form as a standalone webpage. (Example: "civicrm/my-form")') }}</p>
 </div>
 
 <div class="form-group" ng-if="!!afform.server_route">
index 844c9aa2b951983bd61692c3562403ca8bc07a5a..180bc20c6bee6a9d9d249151a8806ab39cbc4221 100644 (file)
@@ -88,7 +88,7 @@ class Get extends \Civi\Api4\Generic\BasicGetAction {
     }
     $customApi = CustomGroup::get()
       ->setCheckPermissions(FALSE)
-      ->setSelect(['name', 'title', 'help_pre', 'help_post', 'extends'])
+      ->setSelect(['name', 'title', 'help_pre', 'help_post', 'extends', 'max_multiple'])
       ->addWhere('is_multiple', '=', 1)
       ->addWhere('is_active', '=', 1);
     if ($groupNames) {
@@ -120,7 +120,7 @@ class Get extends \Civi\Api4\Generic\BasicGetAction {
         'permission' => 'access CiviCRM',
         'join' => 'Custom_' . $custom['name'],
         'block' => $custom['extends'],
-        'repeat' => TRUE,
+        'repeat' => $custom['max_multiple'] ?: TRUE,
         'has_base' => TRUE,
       ];
       if ($getLayout) {
index 54a36f49d132ad2aaaff3add119f8a05e041fc4d..1f6e3e754c27a21dbcf0ff22f092d5eccb8d1948 100644 (file)
@@ -25,9 +25,9 @@ class Submit extends AbstractProcessor {
       foreach ($this->values[$entityName] ?? [] as $values) {
         $entityValues[$entity['type']][$entityName][] = $values + ['fields' => []];
         // Predetermined values override submitted values
-        if (!empty($entity['af-values'])) {
+        if (!empty($entity['data'])) {
           foreach ($entityValues[$entity['type']][$entityName] as $index => $vals) {
-            $entityValues[$entity['type']][$entityName][$index]['fields'] = $entity['af-values'] + $vals['fields'];
+            $entityValues[$entity['type']][$entityName][$index]['fields'] = $entity['data'] + $vals['fields'];
           }
         }
       }
index 9bcffb2f0cfb950535d53b5f0abe60ffe0ffd098..6b1e58bbcc77ae162a043f46ae3ec1a33c9c5b0c 100644 (file)
@@ -3,7 +3,7 @@ return [
   'oauthClientRedirectUrl' => [
     'group_name' => 'Developer Preferences',
     'group' => 'developer',
-    'name' => 'fatalErrorHandler',
+    'name' => 'oauthClientRedirectUrl',
     'type' => 'String',
     'quick_form_type' => 'Element',
     'html_type' => 'text',
index 824e68db3515c9dc0454fffe2130624b669bfa77..9d4b86bca606a472422e1431f5af4b25775e5f06 100644 (file)
           return;
         }
         var splitAs = expr.split(' AS '),
-          info = {fn: null, modifier: ''},
+          info = {fn: null, modifier: '', field: {}},
           fieldName = splitAs[0],
           bracketPos = splitAs[0].indexOf('(');
         if (bracketPos >= 0) {
index e1deda0be6358bcb58cf001c312c4ea9f5c5e575..de4b1bfed38d1321aa6686a58bdceb7ef6a77ccb 100644 (file)
 
       $scope.fieldsForHaving = function() {
         return {results: _.transform(ctrl.savedSearch.api_params.select, function(fields, name) {
-          fields.push({id: name, text: ctrl.getFieldLabel(name)});
+          var info = searchMeta.parseExpr(name);
+          fields.push({id: info.alias + info.suffix, text: ctrl.getFieldLabel(name)});
         })};
       };
 
index b5893b0587013b8ac7f80793c65397cdcab21593..06c8498ee3893af0190e1959cc110c79cf5610c3 100644 (file)
           </div>
         </td>
         <td>{{ search.groups.join(', ') }}</td>
-        <td ng-if="$ctrl.afformEnabled">
+        <td ng-if="::$ctrl.afformEnabled">
           <div class="btn-group">
             <button type="button" ng-click="$ctrl.loadAfforms()" ng-if="search.display_name" class="btn btn-xs dropdown-toggle btn-primary-outline" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-              {{:: ts('Forms') }} <span class="caret"></span>
+              {{ $ctrl.afforms ? ($ctrl.afforms[search.name] && $ctrl.afforms[search.name].length === 1 ? ts('1 Form') : ts('%1 Forms', {1: $ctrl.afforms[search.name] ? $ctrl.afforms[search.name].length : 0})) : ts('Forms...') }}
+              <span class="caret"></span>
             </button>
             <ul class="dropdown-menu">
               <li ng-repeat="display_name in search.display_name" ng-if="::$ctrl.afformAdminEnabled">
@@ -62,8 +63,9 @@
                   <em ng-if="$ctrl.afforms && !$ctrl.afforms[search.name]">{{:: ts('None Found') }}</em>
                 </a>
               </li>
-              <li ng-if="$ctrl.afforms" ng-repeat="afform in $ctrl.afforms[search.name]" ng-class="{disabled: !afform.url}">
+              <li ng-if="$ctrl.afforms" ng-repeat="afform in $ctrl.afforms[search.name]" ng-class="{disabled: !afform.url}" title="{{:: afform.url ? ts('Open form in new tab') : ts('This form does not have a page') }}">
                 <a href="{{:: afform.url }}" target="_blank">
+                  <i class="crm-i {{:: afform.url ? 'fa-external-link' : 'fa-list-alt' }}"></i>
                   {{:: afform.title }}
                 </a>
               </li>