Merge pull request #18725 from civicrm/5.31
authorcolemanw <coleman@civicrm.org>
Sat, 10 Oct 2020 18:11:24 +0000 (14:11 -0400)
committerGitHub <noreply@github.com>
Sat, 10 Oct 2020 18:11:24 +0000 (14:11 -0400)
5.31

17 files changed:
CRM/Contact/Form/Edit/TagsAndGroups.php
CRM/Contact/Form/Search/Basic.php
CRM/Contact/Form/Search/Criteria.php
CRM/Core/Form.php
CRM/Core/Form/Task/PDFLetterCommon.php
CRM/Event/Form/ManageEvent/Location.php
Civi/Api4/Action/Entity/Get.php
ext/search/CRM/Search/Page/Ang.php
ext/search/ang/search.module.js
ext/search/ang/search/crmSearch.component.js
ext/search/ang/search/crmSearch/controls.html
ext/search/ang/search/crmSearchActions.component.js
ext/search/ang/search/crmSearchActions.html
ext/search/ang/search/crmSearchActions/crmSearchActionUpdate.ctrl.js
ext/search/ang/search/crmSearchActions/crmSearchActionUpdate.html
templates/CRM/Contact/Form/Task/PDFLetterCommon.tpl
tests/phpunit/CRM/Event/Form/ManageEvent/LocationTest.php

index 9299246d9beb57312e3c9bd8172f874e589c74e8..de2084c0a2a2432c40ffa80156d560cb6192cb72 100644 (file)
@@ -88,7 +88,7 @@ class CRM_Contact_Form_Edit_TagsAndGroups {
       }
 
       if ($groupID || !empty($group)) {
-        $groups = CRM_Contact_BAO_Group::getGroupsHierarchy($ids);
+        $groups = CRM_Contact_BAO_Group::getGroupsHierarchy($ids, NULL, '- ');
 
         $attributes['skiplabel'] = TRUE;
         $elements = [];
index 6deec9e2a44dd3f4f396de3e6a80af0bdca2132b..537e19e0bdef1db6b52fcb814470e9d06bbc5772 100644 (file)
@@ -49,7 +49,7 @@ class CRM_Contact_Form_Search_Basic extends CRM_Contact_Form_Search {
 
     // add select for groups
     // Get hierarchical listing of groups, respecting ACLs for CRM-16836.
-    $groupHierarchy = CRM_Contact_BAO_Group::getGroupsHierarchy($this->_group, NULL, '&nbsp;&nbsp;');
+    $groupHierarchy = CRM_Contact_BAO_Group::getGroupsHierarchy($this->_group, NULL, '');
     if (!empty($searchOptions['groups'])) {
       $this->addField('group', [
         'entity' => 'group_contact',
index bd2d5829da5c6efc18760951f28fadd76716e4f4..6bcad01d86d63464a215fcf15a5a97d84ee7b42c 100644 (file)
@@ -42,7 +42,7 @@ class CRM_Contact_Form_Search_Criteria {
       // multiselect for groups
       if ($form->_group) {
         // Arrange groups into hierarchical listing (child groups follow their parents and have indentation spacing in title)
-        $groupHierarchy = CRM_Contact_BAO_Group::getGroupsHierarchy($form->_group, NULL, '&nbsp;&nbsp;', TRUE);
+        $groupHierarchy = CRM_Contact_BAO_Group::getGroupsHierarchy($form->_group, NULL, '', TRUE);
 
         $form->add('select', 'group', ts('Groups'), $groupHierarchy, FALSE,
          ['id' => 'group', 'multiple' => 'multiple', 'class' => 'crm-select2']
index 2cd2c226b5dbe675e14d2d6e25118ef9fd8662e4..9795790fa31a7d892d710160b158170af69663ac 100644 (file)
@@ -370,7 +370,7 @@ class CRM_Core_Form extends HTML_QuickForm_Page {
       CRM_Core_Error::deprecatedFunctionWarning('CRM_Core_Form::addRadio');
     }
 
-    if ($attributes && !is_array($attributes)) {
+    if ($type !== 'static' && $attributes && !is_array($attributes)) {
       // The $attributes param used to allow for strings and would default to an
       // empty string.  However, now that the variable is heavily manipulated,
       // we should expect it to always be an array.
index 49776534e041f81bab4c199583820ac95f0a538a..330e68c0c13b29f5faed98d294107356b33a7c88 100644 (file)
@@ -52,7 +52,6 @@ class CRM_Core_Form_Task_PDFLetterCommon {
       FALSE
     );
 
-    $form->add('static', 'pdf_format_header', NULL, ts('Page Format: %1', [1 => '<span class="pdf-format-header-label"></span>']));
     $form->addSelect('format_id', [
       'label' => ts('Select Format'),
       'placeholder' => ts('Default'),
@@ -68,7 +67,6 @@ class CRM_Core_Form_Task_PDFLetterCommon {
       FALSE,
       ['onChange' => "selectPaper( this.value ); showUpdateFormatChkBox();"]
     );
-    $form->add('static', 'paper_dimensions', NULL, ts('Width x Height'));
     $form->add(
       'select',
       'orientation',
index 3314d035eb0fbf43869d91b9308394b9ae820939..b0d045da488255d82dbf36c20dc3f13485cec007 100644 (file)
@@ -231,7 +231,7 @@ class CRM_Event_Form_ManageEvent_Location extends CRM_Event_Form_ManageEvent {
       CRM_Event_BAO_Event::deleteEventLocBlock($this->_oldLocBlockId, $this->_id);
     }
 
-    $isUpdateToExistingLocationBlock = !empty($params['loc_event_id']) && (int) $params['loc_event_id'] === $this->locationBlock['loc_block_id'];
+    $isUpdateToExistingLocationBlock = !$deleteOldBlock && !empty($params['loc_event_id']) && (int) $params['loc_event_id'] === $this->locationBlock['loc_block_id'];
     // It should be impossible for there to be no default location type. Consider removing this handling
     $defaultLocationTypeID = CRM_Core_BAO_LocationType::getDefault()->id ?? 1;
 
index 044105e4e5a639afd7395a160301dff0e1d3d31f..2d14f325be91559b8b8504a986cad93c7e9454ff 100644 (file)
@@ -20,6 +20,7 @@
 namespace Civi\Api4\Action\Entity;
 
 use Civi\Api4\CustomGroup;
+use Civi\Api4\Service\Schema\Joinable\CustomGroupJoinable;
 
 /**
  * Get the names & docblocks of all APIv4 entities.
@@ -91,10 +92,12 @@ class Get extends \Civi\Api4\Generic\BasicGetAction {
       ->execute();
     foreach ($customEntities as $customEntity) {
       $fieldName = 'Custom_' . $customEntity['name'];
+      $baseEntity = '\Civi\Api4\\' . CustomGroupJoinable::getEntityFromExtends($customEntity['extends']);
       $entities[$fieldName] = [
         'name' => $fieldName,
         'title' => $customEntity['title'],
-        'description' => 'Custom group - extends ' . $customEntity['extends'],
+        'titlePlural' => $customEntity['title'],
+        'description' => ts('Custom group for %1', [1 => $baseEntity::getInfo()['titlePlural']]),
         'see' => [
           'https://docs.civicrm.org/user/en/latest/organising-your-data/creating-custom-fields/#multiple-record-fieldsets',
           '\\Civi\\Api4\\CustomGroup',
index 0c9c228f21635b92fde815c23eeab4624191f656..22cab1dcfabc6e89bfecc4a471ed6c3f84353b74 100644 (file)
@@ -99,8 +99,6 @@ class CRM_Search_Page_Ang extends CRM_Core_Page {
         if ($loadOptions) {
           $entity['optionsLoaded'] = TRUE;
         }
-        // Because multivalue custom pseudo-entities don't have titlePlural
-        $entity['titlePlural'] = $entity['titlePlural'] ?? $entity['title'];
         $entity['fields'] = civicrm_api4($entity['name'], 'getFields', [
           'select' => $getFields,
           'where' => [['permission', 'IS NULL']],
index 1927554dd8b704b3c3017c5385b8388f7c7ca05b..291552c3db51c8577accb0501bbc38d1694d16bf 100644 (file)
     .factory('searchMeta', function() {
       function getEntity(entityName) {
         if (entityName) {
-          entityName = entityName === true ? searchEntity : entityName;
           return _.find(CRM.vars.search.schema, {name: entityName});
         }
       }
-      function getField(name) {
-        var dotSplit = name.split('.'),
+      function getField(fieldName, entityName) {
+        var dotSplit = fieldName.split('.'),
           joinEntity = dotSplit.length > 1 ? dotSplit[0] : null,
-          fieldName = _.last(dotSplit).split(':')[0],
-          entityName = searchEntity;
+          name = _.last(dotSplit).split(':')[0];
         // Custom fields contain a dot in their fieldname
         // If 3 segments, the first is the joinEntity and the last 2 are the custom field
         if (dotSplit.length === 3) {
-          fieldName = dotSplit[1] + '.' + fieldName;
+          name = dotSplit[1] + '.' + name;
         }
         // If 2 segments, it's ambiguous whether this is a custom field or joined field. Search the main entity first.
         if (dotSplit.length === 2) {
-          var field = _.find(getEntity(true).fields, {name: dotSplit[0] + '.' + fieldName});
+          var field = _.find(getEntity(entityName).fields, {name: dotSplit[0] + '.' + name});
           if (field) {
             return field;
           }
         if (joinEntity) {
           entityName = _.find(CRM.vars.search.links[entityName], {alias: joinEntity}).entity;
         }
-        return _.find(getEntity(entityName).fields, {name: fieldName});
+        return _.find(getEntity(entityName).fields, {name: name});
       }
       return {
         getEntity: getEntity,
             result.fn = _.find(CRM.vars.search.functions, {name: expr.substring(0, bracketPos)});
             result.modifier = _.trim(parsed[1]);
           }
-          result.field = getField(fieldName);
-          var split = fieldName.split(':'),
-            prefixPos = split[0].lastIndexOf(result.field.name);
-          result.path = split[0];
-          result.prefix = prefixPos > 0 ? result.path.substring(0, prefixPos) : '';
-          result.suffix = !split[1] ? '' : ':' + split[1];
+          result.field = expr ? getField(fieldName, searchEntity) : undefined;
+          if (result.field) {
+            var split = fieldName.split(':'),
+              prefixPos = split[0].lastIndexOf(result.field.name);
+            result.path = split[0];
+            result.prefix = prefixPos > 0 ? result.path.substring(0, prefixPos) : '';
+            result.suffix = !split[1] ? '' : ':' + split[1];
+          }
           return result;
         }
       };
index 9da0ffe8fabfb3e64ae0b9fb411f17b8a467d4f2..49c99081722035bf0d45069800d3fc71fa00ad74 100644 (file)
@@ -18,7 +18,7 @@
       this.page = 1;
       this.params = {};
       // After a search this.results is an object of result arrays keyed by page,
-      // Prior to searching it's an empty string because 1: falsey and 2: doesn't throw an error if you try to access undefined properties
+      // Initially this.results is an empty string because 1: it's falsey (unlike an empty object) and 2: it doesn't throw an error if you try to access undefined properties (unlike null)
       this.results = '';
       this.rowCount = false;
       // Have the filters (WHERE, HAVING, GROUP BY, JOIN) changed?
         return value;
       }
 
-      function getOption(field, value) {
-        return _.find(field.options, function(option) {
-          // Type coersion is intentional
-          return option.id == value;
-        });
-      }
-
       $scope.fieldsForGroupBy = function() {
         return {results: getAllFields('', function(key) {
             return _.contains(ctrl.params.groupBy, key);
       };
 
       function getDefaultSelect() {
-        return _.filter(['id', 'display_name', 'label', 'title', 'location_type_id:label'], searchMeta.getField);
+        return _.filter(['id', 'display_name', 'label', 'title', 'location_type_id:label'], function(field) {
+          return !!searchMeta.getField(field, ctrl.entity);
+        });
       }
 
       function getAllFields(suffix, disabledIf) {
index c17724ba312736ae0c2b3439c17d96723b7d5c21..ce1f843e0286aabcb0862131c6f206b7f60e1051 100644 (file)
@@ -10,9 +10,9 @@
       {{:: ts('Auto') }}
     </button>
   </div>
-  <crm-search-actions entity="$ctrl.entity" ids="$ctrl.selectedRows"></crm-search-actions>
+  <crm-search-actions entity="$ctrl.entity" ids="$ctrl.selectedRows" refresh="$ctrl.refreshPage()"></crm-search-actions>
   <div class="btn-group pull-right">
-    <button type="button" class="btn form-control dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+    <button type="button" class="btn btn-default form-control dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
       <i class="crm-i fa-save"></i> {{:: ts('Create')}}
       <span class="caret"></span>
     </button>
index 73ed61a0923dbb7281f453c5dfcc182533658dfd..212b8d914f85b8acf26638da53fe8501cc756f49 100644 (file)
@@ -4,11 +4,9 @@
   angular.module('search').component('crmSearchActions', {
     bindings: {
       entity: '<',
+      refresh: '&',
       ids: '<'
     },
-    require: {
-      search: '^crmSearch'
-    },
     templateUrl: '~/search/crmSearchActions.html',
     controller: function($scope, crmApi4, dialogService, searchMeta) {
       var ts = $scope.ts = CRM.ts(),
@@ -44,7 +42,7 @@
           var path = $scope.$eval(action.crmPopup.path, data),
             query = action.crmPopup.query && $scope.$eval(action.crmPopup.query, data);
           CRM.loadForm(CRM.url(path, query))
-            .on('crmFormSuccess', ctrl.search.refreshPage);
+            .on('crmFormSuccess', ctrl.refresh);
         }
         // If action uses dialogService
         else if (action.uiDialog) {
@@ -53,7 +51,7 @@
             title: action.title
           });
           dialogService.open('crmSearchAction', action.uiDialog.templateUrl, data, options)
-            .then(ctrl.search.refreshPage);
+            .then(ctrl.refresh);
         }
       };
     }
index dbeae2c84c5eda08603315db2b667d15531583d0..7442efbe09e46dfb30ee55e35d9e61790f0b23ea 100644 (file)
@@ -1,5 +1,5 @@
 <div class="btn-group" title="{{:: ts('Perform action on selected items.') }}">
-  <button type="button" ng-disabled="!$ctrl.ids.length" ng-click="$ctrl.init()" class="btn form-control dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+  <button type="button" ng-disabled="!$ctrl.ids.length" ng-click="$ctrl.init()" class="btn form-control dropdown-toggle btn-default" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
     {{:: ts('Action') }} <span class="caret"></span>
   </button>
   <ul class="dropdown-menu" ng-if=":: $ctrl.actions">
index 63394a2427f6746e338a7f0e4393c1636cc7dacd..a4fb759887a08965a7144565e86f9247cc62e0e8 100644 (file)
@@ -35,7 +35,7 @@
 
     this.availableFields = function() {
       var results = _.transform(ctrl.entity.fields, function(result, item) {
-        var formatted = {id: item.name, text: item.title, description: item.description};
+        var formatted = {id: item.name, text: item.label, description: item.description};
         if (fieldInUse(item.name)) {
           formatted.disabled = true;
         }
index 63178a0baed16f857e05bcda12abba5cf2ddc74a..fac9c10af20efc54027d5e1c7ccc7339480e31ff 100644 (file)
@@ -10,7 +10,7 @@
     <hr />
     <div class="buttons pull-right">
       <button type="button" ng-click="$ctrl.cancel()" class="btn btn-danger">{{:: ts('Cancel') }}</button>
-      <button ng-click="$ctrl.save()" class="btn btn-primary" ng-disabled="!$ctrl.values.length">{{:: ts('Update %1 %2', {1: model.ids.length, 2: $ctrl.entity.title}) }}</button>
+      <button ng-click="$ctrl.save()" class="btn btn-primary" ng-disabled="!$ctrl.values.length">{{:: ts('Update %1 %2', {1: model.ids.length, 2: (model.ids.length === 1 ? $ctrl.entity.title : $ctrl.entity.titlePlural)}) }}</button>
     </div>
   </div>
 </div>
index ec9c043705db312c460c11821816e61e27dca81c..864fedeb6daad783007fb2c6e149571acd48ca77 100644 (file)
@@ -34,7 +34,7 @@
 
 <div class="crm-accordion-wrapper collapsed crm-pdf-format-accordion">
     <div class="crm-accordion-header">
-        {$form.pdf_format_header.html}
+      {ts}Page Format:{/ts} <span class="pdf-format-header-label"></span>
     </div>
     <div class="crm-accordion-body">
       <div class="crm-block crm-form-block">
@@ -52,7 +52,7 @@
         <td colspan="2">&nbsp;</td>
       </tr>
       <tr>
-        <td>{$form.paper_dimensions.html}</td><td id="paper_dimensions">&nbsp;</td>
+        <td>{ts}Width x Height{/ts}</td><td id="paper_dimensions">&nbsp;</td>
         <td colspan="2">&nbsp;</td>
       </tr>
       <tr>
index 8ff04280db66cdd97e3779d353cab5c7623b9532..58bcb441f42630c743c70f1a8a4634302a64eb3b 100644 (file)
@@ -110,6 +110,76 @@ class CRM_Event_Form_ManageEvent_LocationTest extends CiviUnitTestCase {
     $this->eventDelete($eventID);
   }
 
+  /**
+   * Test updating a location block.
+   *
+   * @throws \API_Exception
+   * @throws \CRM_Core_Exception
+   * @throws \Civi\API\Exception\UnauthorizedException
+   */
+  public function testUpdateLocationBlock() {
+    $eventID = (int) $this->eventCreate()['id'];
+    $this->submitForm([
+      'address' => [
+        '1' => [
+          'street_address' => 'Old address',
+          'supplemental_address_1' => 'Hallmark Ct',
+          'supplemental_address_2' => 'Jersey Village',
+          'supplemental_address_3' => 'My Town',
+          'city' => 'Newark',
+          'postal_code' => '01903',
+          'country_id' => 1228,
+          'state_province_id' => 1029,
+          'geo_code_1' => '18.219023',
+          'geo_code_2' => '-105.00973',
+          'is_primary' => 1,
+          'location_type_id' => 1,
+        ],
+      ],
+    ], $eventID);
+
+    $this->submitForm([
+      'location_option' => 1,
+      'loc_event_id' => Event::get()->addWhere('id', '=', $eventID)->addSelect('loc_block_id')->execute()->first()['loc_block_id'],
+      'address' => [
+        '1' => [
+          'street_address' => 'New address',
+          'supplemental_address_1' => 'Hallmark Ct',
+          'supplemental_address_2' => 'Jersey Village',
+          'supplemental_address_3' => 'My Town',
+          'city' => 'Newark',
+          'postal_code' => '01903',
+          'country_id' => 1228,
+          'state_province_id' => 1029,
+          'geo_code_1' => '18.219023',
+          'geo_code_2' => '-105.00973',
+        ],
+      ],
+      'email' => [
+        '1' => [
+          'email' => '',
+        ],
+        '2' => [
+          'email' => '',
+        ],
+      ],
+      'phone' => [
+        '1' => [
+          'phone_type_id' => 1,
+          'phone' => '',
+          'phone_ext' => '',
+        ],
+        '2' => [
+          'phone_type_id' => 1,
+          'phone' => '',
+          'phone_ext' => '',
+        ],
+      ],
+    ], $eventID);
+    // Cleanup.
+    $this->eventDelete($eventID);
+  }
+
   /**
    * Get the values to submit for the form.
    *