Merge pull request #19426 from colemanw/searchCount
authorEileen McNaughton <emcnaughton@wikimedia.org>
Fri, 22 Jan 2021 22:08:44 +0000 (11:08 +1300)
committerGitHub <noreply@github.com>
Fri, 22 Jan 2021 22:08:44 +0000 (11:08 +1300)
dev/core#2312 SearchKit - Improve results loading time in admin UI

36 files changed:
CRM/Activity/DAO/ActivityContact.php
CRM/Contact/DAO/RelationshipCache.php
CRM/Core/Payment/BaseIPN.php
CRM/Core/Payment/PayPalProIPN.php
CRM/Logging/Schema.php
CRM/Mailing/BAO/TrackableURL.php
Civi/Api4/ActivityContact.php
Civi/Api4/Address.php
Civi/Api4/Entity.php
Civi/Api4/RelationshipCache.php
Civi/Api4/Utils/ReflectionUtils.php
api/v3/examples/Setting/GetFields.ex.php
composer.json
composer.lock
css/civicrm.css
ext/afform/core/Civi/Api4/AfformPalette.php [deleted file]
ext/afform/core/Civi/Api4/AfformTag.php [deleted file]
ext/afform/mock/tests/phpunit/api/v4/AfformPaletteTest.php [deleted file]
ext/flexmailer/src/ClickTracker/HtmlClickTracker.php
ext/flexmailer/src/ClickTracker/TextClickTracker.php
ext/flexmailer/tests/phpunit/Civi/FlexMailer/ClickTrackerTest.php [new file with mode: 0644]
ext/flexmailer/tests/phpunit/Civi/FlexMailer/FlexMailerSystemTest.php
ext/greenwich/scss/_tweaks.scss
ext/search/Civi/Search/Admin.php
ext/search/ang/crmSearchAdmin.module.js
ext/search/ang/crmSearchAdmin/compose/criteria.html
ext/search/ang/crmSearchAdmin/crmSearchAdmin.component.js
ext/search/css/search.css
package-lock.json
settings/Case.setting.php
tests/phpunit/CRM/Core/Payment/BaseIPNTest.php
tests/phpunit/CRM/Logging/SchemaTest.php
tests/phpunit/CRM/Mailing/BaseMailingSystemTest.php
tests/phpunit/api/v4/Service/TestCreationParameterProvider.php
xml/schema/Activity/ActivityContact.xml
xml/schema/Contact/RelationshipCache.xml

index 7f1bfd317fba6da885a299f006d4664338d852d1..c789e4f0ed751f197251eceaa019e9e9f91256d1 100644 (file)
@@ -6,7 +6,7 @@
  *
  * Generated from xml/schema/CRM/Activity/ActivityContact.xml
  * DO NOT EDIT.  Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:3f147b2507b1e11a7df971be191161d1)
+ * (GenCodeChecksum:1263921d2a6832e26c5a7e34c684e35b)
  */
 
 /**
@@ -52,7 +52,7 @@ class CRM_Activity_DAO_ActivityContact extends CRM_Core_DAO {
   public $contact_id;
 
   /**
-   * Nature of this contact's role in the activity: 1 assignee, 2 creator, 3 focus or target.
+   * Determines the contact's role in the activity (source, target, or assignee).
    *
    * @var int
    */
@@ -146,8 +146,8 @@ class CRM_Activity_DAO_ActivityContact extends CRM_Core_DAO {
         'record_type_id' => [
           'name' => 'record_type_id',
           'type' => CRM_Utils_Type::T_INT,
-          'title' => ts('Record Type ID'),
-          'description' => ts('Nature of this contact\'s role in the activity: 1 assignee, 2 creator, 3 focus or target.'),
+          'title' => ts('Activity Contact Type'),
+          'description' => ts('Determines the contact\'s role in the activity (source, target, or assignee).'),
           'where' => 'civicrm_activity_contact.record_type_id',
           'table_name' => 'civicrm_activity_contact',
           'entity' => 'ActivityContact',
@@ -155,6 +155,7 @@ class CRM_Activity_DAO_ActivityContact extends CRM_Core_DAO {
           'localizable' => 0,
           'html' => [
             'type' => 'Select',
+            'label' => ts("Contact Role"),
           ],
           'pseudoconstant' => [
             'optionGroupName' => 'activity_contacts',
index 5701d5c3e67369f3bb6c9e897eda430d3203562b..c888435bfd5dd9094e08697b548f44e893473d38 100644 (file)
@@ -6,7 +6,7 @@
  *
  * Generated from xml/schema/CRM/Contact/RelationshipCache.xml
  * DO NOT EDIT.  Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:ba039fcadc13e48749f965343301ec1d)
+ * (GenCodeChecksum:ec899f1ccb7f617701d7108dc4282691)
  */
 
 /**
@@ -246,6 +246,9 @@ class CRM_Contact_DAO_RelationshipCache extends CRM_Core_DAO {
           'entity' => 'RelationshipCache',
           'bao' => 'CRM_Contact_BAO_RelationshipCache',
           'localizable' => 0,
+          'html' => [
+            'label' => ts("Relationship to contact"),
+          ],
           'pseudoconstant' => [
             'callback' => 'CRM_Core_PseudoConstant::relationshipTypeOptions',
           ],
@@ -280,6 +283,9 @@ class CRM_Contact_DAO_RelationshipCache extends CRM_Core_DAO {
           'entity' => 'RelationshipCache',
           'bao' => 'CRM_Contact_BAO_RelationshipCache',
           'localizable' => 0,
+          'html' => [
+            'label' => ts("Relationship from contact"),
+          ],
           'pseudoconstant' => [
             'callback' => 'CRM_Core_PseudoConstant::relationshipTypeOptions',
           ],
index 2d6a7e6f11ea0700dfed461b5a1bfad7e0f50092..a3c5a590607e8be1f0593124b0415d0e2e27133d 100644 (file)
@@ -140,6 +140,8 @@ class CRM_Core_Payment_BaseIPN {
   /**
    * Load objects related to contribution.
    *
+   * @deprecated
+   *
    * @input array information from Payment processor
    *
    * @param array $input
@@ -152,6 +154,7 @@ class CRM_Core_Payment_BaseIPN {
    * @throws \CRM_Core_Exception
    */
   public function loadObjects($input, &$ids, &$objects, $required, $paymentProcessorID) {
+    CRM_Core_Error::deprecatedFunctionWarning('use api methods in ipn');
     $contribution = &$objects['contribution'];
     $ids['paymentProcessor'] = $paymentProcessorID;
     $success = $contribution->loadRelatedObjects($input, $ids);
index 0ea9694d4e3345a0619d68a6bfb7dd7fb7d76cfc..6c7fbd7c5c8d83a962976258dbde73eb5e1e9a06 100644 (file)
@@ -482,9 +482,10 @@ INNER JOIN civicrm_membership_payment mp ON m.id = mp.membership_id AND mp.contr
         unset($ids['contributionPage']);
       }
 
-      if (!$this->loadObjects($input, $ids, $objects, TRUE, $paymentProcessorID)) {
-        return;
-      }
+      $contribution = &$objects['contribution'];
+      $ids['paymentProcessor'] = $paymentProcessorID;
+      $contribution->loadRelatedObjects($input, $ids);
+      $objects = array_merge($objects, $contribution->_relatedObjects);
 
       $input['payment_processor_id'] = $paymentProcessorID;
 
@@ -640,9 +641,11 @@ INNER JOIN civicrm_membership_payment mp ON m.id = mp.membership_id AND mp.contr
       unset($ids['contributionPage']);
     }
 
-    if (!$this->loadObjects($input, $ids, $objects, TRUE, $paymentProcessorID)) {
-      throw new CRM_Core_Exception('Data did not validate');
-    }
+    $contribution = &$objects['contribution'];
+    $ids['paymentProcessor'] = $paymentProcessorID;
+    $contribution->loadRelatedObjects($input, $ids);
+    $objects = array_merge($objects, $contribution->_relatedObjects);
+
     $this->recur($input, $ids, $objects['contributionRecur'], $objects['contribution'], $isFirst);
   }
 
index 6a75405d4c9d163833fed6e74b78dcb42727be21..cd7dfd5b8298f41f936fd6429d116b6e68e448ee 100644 (file)
@@ -948,6 +948,12 @@ COLS;
       }
       $columns = $this->columnsOf($table, $force);
 
+      // Use utf8mb4_bin or utf8_bin, depending on what's in use.
+      $charset = 'utf8';
+      if (stripos(CRM_Core_BAO_SchemaHandler::getInUseCollation(), 'utf8mb4') !== FALSE) {
+        $charset = 'utf8mb4';
+      }
+
       // only do the change if any data has changed
       $cond = [];
       foreach ($columns as $column) {
@@ -958,7 +964,12 @@ COLS;
         $excludeColumn = in_array($column, $tableExceptions) ||
           in_array(str_replace('`', '', $column), $tableExceptions);
         if (!$excludeColumn) {
-          $cond[] = "IFNULL(OLD.$column,'') <> IFNULL(NEW.$column,'')";
+          // The empty string needs charset signalling to avoid errors.
+          // Note that it is not a cast/convert. It just tells mysql
+          // that there isn't a conflict when your system/connection defaults
+          // happen to be different from $charset.
+          // See https://dev.mysql.com/doc/refman/5.7/en/charset-literal.html
+          $cond[] = "IFNULL(OLD.$column,_{$charset}'') <> IFNULL(NEW.$column,_{$charset}'') COLLATE {$charset}_bin";
         }
       }
       $suppressLoggingCond = "@civicrm_disable_logging IS NULL OR @civicrm_disable_logging = 0";
index 0113e53bb9b7cbd063c3ff63ed184369f0455515..ea6d032db4d534b3308400fb853763c5c4bce7eb 100644 (file)
@@ -38,7 +38,15 @@ class CRM_Mailing_BAO_TrackableURL extends CRM_Mailing_DAO_TrackableURL {
    *   The redirect/tracking url
    */
   public static function getTrackerURL($url, $mailing_id, $queue_id) {
+    if (strpos($url, '{') !== FALSE) {
+      return self::getTrackerURLForUrlWithTokens($url, $mailing_id, $queue_id);
+    }
+    else {
+      return self::getBasicTrackerURL($url, $mailing_id, $queue_id);
+    }
+  }
 
+  private static function getBasicTrackerURL($url, $mailing_id, $queue_id) {
     static $urlCache = [];
 
     if (array_key_exists($mailing_id . $url, $urlCache)) {
@@ -87,6 +95,66 @@ class CRM_Mailing_BAO_TrackableURL extends CRM_Mailing_DAO_TrackableURL {
     return $returnUrl;
   }
 
+  /**
+   * Create a trackable URL for a URL with tokens.
+   *
+   * @param string $url
+   * @param int $mailing_id
+   * @param int|string $queue_id
+   *
+   * @return string
+   */
+  private static function getTrackerURLForUrlWithTokens($url, $mailing_id, $queue_id) {
+
+    // Parse the URL.
+    // (not using parse_url because it's messy to reassemble)
+    if (!preg_match('/^([^?#]+)([?][^#]*)?(#.*)?$/', $url, $parsed)) {
+      // Failed to parse it, give up and don't track it.
+      return $url;
+    }
+
+    // If we have a token in the URL + path section, we can't tokenise.
+    if (strpos($parsed[1], '{') !== FALSE) {
+      return $url;
+    }
+
+    $trackable_url = $parsed[1];
+
+    // Process the query parameters, if there are any.
+    $tokenised_params = [];
+    $static_params = [];
+    if (!empty($parsed[2])) {
+      $query_key_value_pairs = explode('&', substr($parsed[2], 1));
+
+      // Separate the tokenised from the static parts.
+      foreach ($query_key_value_pairs as $_) {
+        if (strpos($_, '{') === FALSE) {
+          $static_params[] = $_;
+        }
+        else {
+          $tokenised_params[] = $_;
+        }
+      }
+      // Add the static params to the trackable part.
+      if ($static_params) {
+        $trackable_url .= '?' . implode('&', $static_params);
+      }
+    }
+
+    // Get trackable URL.
+    $data = self::getBasicTrackerURL($trackable_url, $mailing_id, $queue_id);
+
+    // Append the tokenised bits and the fragment.
+    if ($tokenised_params) {
+      // We know the URL will already have the '?'
+      $data .= '&' . implode('&', $tokenised_params);
+    }
+    if (!empty($parsed[3])) {
+      $data .= $parsed[3];
+    }
+    return $data;
+  }
+
   /**
    * @param $url
    * @param $mailing_id
index 1af0de36ffa3b597b713b26bd3a3f8702f00d93d..39ec1b3b95df5db0679952e79f99230283011669 100644 (file)
 namespace Civi\Api4;
 
 /**
- * ActivityContact Entity.
+ * ActivityContact BridgeEntity.
  *
- * This entity adds a record which relate a contact to activity.
+ * This connects a contact to an activity.
  *
- * Creating a new ActivityContact requires at minimum a contact_id and activity_id.
+ * The record_type_id field determines the contact's role in the activity (source, target, or assignee).
+ * @ui_join_filters record_type_id
  *
  * @see \Civi\Api4\Activity
  * @package Civi\Api4
index 038e544ea71efb6d955b1306dc08891b1b7d0f19..242afbe0bdcbb46ef1c2d9b0640fecaac8fa71ca 100644 (file)
@@ -28,6 +28,8 @@ namespace Civi\Api4;
  * Creating a new address requires at minimum a contact's ID and location type ID
  *  and other attributes (although optional) like street address, city, country etc.
  *
+ * @ui_join_filters location_type_id
+ *
  * @package Civi\Api4
  */
 class Address extends Generic\DAOEntity {
index ac61fcf3892be89ddd6f20dcdcb7ed6de392423a..7bb80be3d1d71a29c70822509a2a938f04cd81a1 100644 (file)
@@ -103,6 +103,11 @@ class Entity extends Generic\AbstractEntity {
           'data_type' => 'Array',
           'description' => 'Connecting fields for EntityBridge types',
         ],
+        [
+          'name' => 'ui_join_filters',
+          'data_type' => 'Array',
+          'description' => 'When joining entities in the UI, which fields should be presented by default in the ON clause',
+        ],
       ];
     }))->setCheckPermissions($checkPermissions);
   }
index 4c6281c3a87792ef763b45c9d25cc3e277b4bbf7..8009477cc9c620d93f636600142cb9051306347e 100644 (file)
@@ -24,6 +24,7 @@ namespace Civi\Api4;
  *
  * @see \Civi\Api4\Relationship
  * @bridge near_contact_id far_contact_id
+ * @ui_join_filters near_relation
  * @package Civi\Api4
  */
 class RelationshipCache extends Generic\AbstractEntity {
index ce3f709a2c352a1ae25ccc8cd832ae22f89c8f8c..781a87b127260fcad5395c283725000665d287ef 100644 (file)
@@ -88,9 +88,9 @@ class ReflectionUtils {
         elseif ($key == 'return') {
           $info['return'] = explode('|', $words[0]);
         }
-        elseif ($key == 'options') {
+        elseif ($key == 'options' || $key == 'ui_join_filters') {
           $val = str_replace(', ', ',', implode(' ', $words));
-          $info['options'] = explode(',', $val);
+          $info[$key] = explode(',', $val);
         }
         elseif ($key == 'throws' || $key == 'see') {
           $info[$key][] = implode(' ', $words);
index 936e6f6f77c047792792c53feaf0c21a92761e39..19b17e6e5aee3a793cc25350ca780df8ad411ce0 100644 (file)
@@ -206,7 +206,7 @@ function setting_getfields_expectedresult() {
         'default' => '',
         'html_type' => 'radio',
         'add' => '4.7',
-        'title' => 'Enable Embedded Activity Revisions',
+        'title' => 'Enable deprecated Embedded Activity Revisions',
         'is_domain' => 1,
         'is_contact' => 0,
         'description' => 'Enable tracking of activity revisions embedded within the \"civicrm_activity\" table. Alternatively, see \"Administer => System Settings => Misc => Logging\".',
index e6d47a9c30ed123685e5bbf0c75abc3cf89cbeaa..f50143f6733ee3095f7fc316330d41e3114790ae 100644 (file)
       },
       "monaco-editor": {
         "url": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.16.2.tgz",
-        "path": "bower_components/monaco-editor"
+        "path": "bower_components/monaco-editor",
+        "ignore": ["dev", "esm"]
       },
       "google-code-prettify": {
         "url": "https://github.com/tcollard/google-code-prettify/archive/v1.0.5.zip",
index 171510a6287f3cb73a81ca0e50ef72f9b1015730..e78998528c7e42e460df79ced1fb0f0f9691fde0 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "65312dbe20aaae9cb2a94b32937a24e0",
+    "content-hash": "a2171f9e6a9a0cffa1fcaebc3df8ea51",
     "packages": [
         {
             "name": "adrienrn/php-mimetyper",
     "platform-overrides": {
         "php": "7.2"
     },
-    "plugin-api-version": "2.0.0"
+    "plugin-api-version": "1.1.0"
 }
index b627f94813292ea680cb4b674689178ec972d11f..9a4ae97a4e6c79e57ac5cb80dd760a422baa118f 100644 (file)
@@ -2972,9 +2972,13 @@ tbody.scrollContent tr.alternateRow {
 .crm-container .select2-container-multi.crm-ajax-select .select2-choices:before {
   background-position: right -26px;
 }
+.crm-container .select2-container.select2-container-disabled .select2-choice .select2-arrow b {
+  visibility: hidden;
+}
 .crm-container .select2-container-multi.loading .select2-choices:before,
 .crm-container .select2-container.loading .select2-choice .select2-arrow b {
-  background: url('../i/loading.gif') no-repeat center center;
+  background: url('../i/loading.gif') no-repeat center center !important;
+  visibility: visible;
 }
 /* Reduce select2 size to match other inputs */
 .crm-container .select2-container-multi .select2-choices {
diff --git a/ext/afform/core/Civi/Api4/AfformPalette.php b/ext/afform/core/Civi/Api4/AfformPalette.php
deleted file mode 100644 (file)
index e803aa0..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-
-namespace Civi\Api4;
-
-/**
- * Class AfformPalette
- * @searchable false
- * @package Civi\Api4
- */
-class AfformPalette extends Generic\AbstractEntity {
-
-  /**
-   * @param bool $checkPermissions
-   * @return Generic\BasicGetAction
-   */
-  public static function get($checkPermissions = TRUE) {
-    return (new Generic\BasicGetAction('AfformPalette', __FUNCTION__, function() {
-      return [
-        [
-          'id' => 'Parent:afl-name',
-          'entity' => 'Parent',
-          'title' => 'Name',
-          'template' => '<afl-name contact-id="entities.parent.id" afl-label="Name"/>',
-        ],
-        [
-          'id' => 'Parent:afl-address',
-          'entity' => 'Parent',
-          'title' => 'Address',
-          'template' => '<afl-address contact-id="entities.parent.id" afl-label="Address"/>',
-        ],
-      ];
-    }))->setCheckPermissions($checkPermissions);
-  }
-
-  /**
-   * @param bool $checkPermissions
-   * @return Generic\BasicGetFieldsAction
-   */
-  public static function getFields($checkPermissions = TRUE) {
-    return (new Generic\BasicGetFieldsAction('AfformPalette', __FUNCTION__, function() {
-      return [
-        [
-          'name' => 'id',
-        ],
-        [
-          'name' => 'entity',
-        ],
-        [
-          'name' => 'title',
-        ],
-        [
-          'name' => 'template',
-        ],
-      ];
-    }))->setCheckPermissions($checkPermissions);
-  }
-
-  /**
-   * @return array
-   */
-  public static function permissions() {
-    return [
-      "meta" => ["access CiviCRM"],
-      "default" => ["administer CiviCRM"],
-    ];
-  }
-
-}
diff --git a/ext/afform/core/Civi/Api4/AfformTag.php b/ext/afform/core/Civi/Api4/AfformTag.php
deleted file mode 100644 (file)
index e2ca67f..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-<?php
-namespace Civi\Api4;
-
-/**
- * Class AfformTag
- * @searchable false
- * @package Civi\Api4
- */
-class AfformTag extends Generic\AbstractEntity {
-
-  /**
-   * @param bool $checkPermissions
-   * @return Generic\BasicGetAction
-   */
-  public static function get($checkPermissions = TRUE) {
-    return (new Generic\BasicGetAction('AfformTag', __FUNCTION__, function() {
-      return [
-        [
-          'name' => 'afl-entity',
-          'attrs' => ['entity-name', 'matching-rule', 'assigned-values'],
-        ],
-        [
-          'name' => 'afl-name',
-          'attrs' => ['contact-id', 'afl-label'],
-        ],
-        [
-          'name' => 'afl-contact-email',
-          'attrs' => ['contact-id', 'afl-label'],
-        ],
-      ];
-    }))->setCheckPermissions($checkPermissions);
-  }
-
-  /**
-   * @param bool $checkPermissions
-   * @return Generic\BasicGetFieldsAction
-   */
-  public static function getFields($checkPermissions = TRUE) {
-    return (new Generic\BasicGetFieldsAction('AfformTag', __FUNCTION__, function() {
-      return [
-        [
-          'name' => 'name',
-        ],
-        [
-          'name' => 'attrs',
-        ],
-      ];
-    }))->setCheckPermissions($checkPermissions);
-  }
-
-  /**
-   * @return array
-   */
-  public static function permissions() {
-    return [
-      "meta" => ["access CiviCRM"],
-      "default" => ["administer CiviCRM"],
-    ];
-  }
-
-}
diff --git a/ext/afform/mock/tests/phpunit/api/v4/AfformPaletteTest.php b/ext/afform/mock/tests/phpunit/api/v4/AfformPaletteTest.php
deleted file mode 100644 (file)
index 7f2ec85..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<?php
-
-/**
- * AfformPalette API Test
- * @group headless
- */
-class api_v4_AfformPaletteTest extends api_v4_AfformTestCase {
-
-  public function testGetPalette() {
-    $r = Civi\Api4\AfformPalette::get()
-      ->addWhere('id', '=', 'Parent:afl-name')
-      ->execute();
-    $this->assertEquals(1, $r->count());
-
-    $r = Civi\Api4\AfformPalette::get()
-      ->setLimit(10)
-      ->execute();
-    $this->assertTrue($r->count() > 1);
-  }
-
-}
index a511f90e06bebd8c8612ae60724ed1b4212bbbc9..5e8955d5168b3c5a324f0976404cb41347313b21 100644 (file)
@@ -15,11 +15,8 @@ class HtmlClickTracker implements ClickTrackerInterface {
   public function filterContent($msg, $mailing_id, $queue_id) {
     return self::replaceHrefUrls($msg,
       function ($url) use ($mailing_id, $queue_id) {
-        if (strpos($url, '{') !== FALSE) {
-          return $url;
-        }
         $data = \CRM_Mailing_BAO_TrackableURL::getTrackerURL(
-          $url, $mailing_id, $queue_id);
+          html_entity_decode($url), $mailing_id, $queue_id);
         $data = htmlentities($data, ENT_NOQUOTES);
         return $data;
       }
index 3f907e55e98e51af8aebd703b5520c3858bbf8f1..31cdd90c2c735ca20ac135c760b91607bfeb6997 100644 (file)
@@ -15,9 +15,6 @@ class TextClickTracker implements ClickTrackerInterface {
   public function filterContent($msg, $mailing_id, $queue_id) {
     return self::replaceTextUrls($msg,
       function ($url) use ($mailing_id, $queue_id) {
-        if (strpos($url, '{') !== FALSE) {
-          return $url;
-        }
         return \CRM_Mailing_BAO_TrackableURL::getTrackerURL($url, $mailing_id,
           $queue_id);
       }
diff --git a/ext/flexmailer/tests/phpunit/Civi/FlexMailer/ClickTrackerTest.php b/ext/flexmailer/tests/phpunit/Civi/FlexMailer/ClickTrackerTest.php
new file mode 100644 (file)
index 0000000..1f723c5
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+namespace Civi\FlexMailer;
+
+use Civi\Test\HeadlessInterface;
+use Civi\Test\HookInterface;
+use Civi\Test\TransactionalInterface;
+
+use Civi\FlexMailer\ClickTracker\TextClickTracker;
+use Civi\FlexMailer\ClickTracker\HtmlClickTracker;
+
+/**
+ * Tests that URLs are converted to tracked ones if at all possible.
+ *
+ * @group headless
+ */
+class ClickTrackerTest extends \PHPUnit\Framework\TestCase implements HeadlessInterface, HookInterface, TransactionalInterface {
+
+  protected $mailing_id;
+
+  public function setUpHeadless() {
+    // Civi\Test has many helpers, like install(), uninstall(), sql(), and sqlFile().
+    // See: https://docs.civicrm.org/dev/en/latest/testing/phpunit/#civitest
+    return \Civi\Test::headless()
+      ->installMe(__DIR__)
+      ->apply();
+  }
+
+  public function setUp() {
+    // Mock the getTrackerURL call; we don't need to test creating a row in a table.
+    // If you want this to work without runkit, then either (a) make the dummy rows or (b) switch this to a hook/event that is runtime-configurable.
+    require_once 'CRM/Mailing/BAO/TrackableURL.php';
+    runkit7_method_rename('\CRM_Mailing_BAO_TrackableURL', 'getBasicTrackerURL', 'orig_getBasicTrackerURL');
+    runkit7_method_add('\CRM_Mailing_BAO_TrackableURL', 'getBasicTrackerURL', '$a, $b, $c', 'return \'http://example.com/extern?u=1&qid=1\';', RUNKIT7_ACC_STATIC | RUNKIT7_ACC_PRIVATE);
+    parent::setUp();
+  }
+
+  public function tearDown() {
+    // Reset the class.
+    runkit7_method_remove('\CRM_Mailing_BAO_TrackableURL', 'getBasicTrackerURL');
+    runkit7_method_rename('\CRM_Mailing_BAO_TrackableURL', 'orig_getBasicTrackerURL', 'getBasicTrackerURL');
+    parent::tearDown();
+  }
+
+  /**
+   * Example: Test that a link without any tokens works.
+   */
+  public function testLinkWithoutTokens() {
+    $filter = new TextClickTracker();
+    $msg = 'See this: https://example.com/foo/bar?a=b&c=d#frag';
+    $result = $filter->filterContent($msg, 1, 1);
+    $this->assertEquals('See this: http://example.com/extern?u=1&qid=1', $result);
+  }
+
+  /**
+   * Example: Test that a link with tokens in the query works.
+   */
+  public function testLinkWithTokensInQueryWithStaticParams() {
+    $filter = new TextClickTracker();
+    $msg = 'See this: https://example.com/foo/bar?a=b&cid={contact.id}';
+    $result = $filter->filterContent($msg, 1, 1);
+    $this->assertEquals('See this: http://example.com/extern?u=1&qid=1&cid={contact.id}', $result);
+  }
+
+  /**
+   * Example: Test that a link with tokens in the query works.
+   */
+  public function testLinkWithTokensInQueryWithMultipleStaticParams() {
+    $filter = new TextClickTracker();
+    $msg = 'See this: https://example.com/foo/bar?cs={contact.checksum}&a=b&cid={contact.id}';
+    $result = $filter->filterContent($msg, 1, 1);
+    $this->assertEquals('See this: http://example.com/extern?u=1&qid=1&cs={contact.checksum}&cid={contact.id}', $result);
+  }
+
+  /**
+   * Example: Test that a link with tokens in the query works.
+   */
+  public function testLinkWithTokensInQueryWithMultipleStaticParamsHtml() {
+    $filter = new HtmlClickTracker();
+    $msg = '<a href="https://example.com/foo/bar?cs={contact.checksum}&amp;a=b&amp;cid={contact.id}">See this</a>';
+    $result = $filter->filterContent($msg, 1, 1);
+    $this->assertEquals('<a href="http://example.com/extern?u=1&amp;qid=1&amp;cs={contact.checksum}&amp;cid={contact.id}" rel=\'nofollow\'>See this</a>', $result);
+  }
+
+  /**
+   * Example: Test that a link with tokens in the query works.
+   */
+  public function testLinkWithTokensInQueryWithoutStaticParams() {
+    $filter = new TextClickTracker();
+    $msg = 'See this: https://example.com/foo/bar?cid={contact.id}';
+    $result = $filter->filterContent($msg, 1, 1);
+    $this->assertEquals('See this: http://example.com/extern?u=1&qid=1&cid={contact.id}', $result);
+  }
+
+  /**
+   * Example: Test that a link with tokens in the fragment works.
+   *
+   * Seems browsers maintain the fragment when they receive a redirect, so a
+   * token here might still work.
+   */
+  public function testLinkWithTokensInFragment() {
+    $filter = new TextClickTracker();
+    $msg = 'See this: https://example.com/foo/bar?a=b#cid={contact.id}';
+    $result = $filter->filterContent($msg, 1, 1);
+    $this->assertEquals('See this: http://example.com/extern?u=1&qid=1#cid={contact.id}', $result);
+  }
+
+  /**
+   * Example: Test that a link with tokens in the fragment works.
+   *
+   * Seems browsers maintain the fragment when they receive a redirect, so a
+   * token here might still work.
+   */
+  public function testLinkWithTokensInQueryAndFragment() {
+    $filter = new TextClickTracker();
+    $msg = 'See this: https://example.com/foo/bar?a=b&cid={contact.id}#cid={contact.id}';
+    $result = $filter->filterContent($msg, 1, 1);
+    $this->assertEquals('See this: http://example.com/extern?u=1&qid=1&cid={contact.id}#cid={contact.id}', $result);
+  }
+
+  /**
+   * We can't handle tokens in the domain so it should not be tracked.
+   */
+  public function testLinkWithTokensInDomainFails() {
+    $filter = new TextClickTracker();
+    $msg = 'See this: https://{some.domain}.com/foo/bar';
+    $result = $filter->filterContent($msg, 1, 1);
+    $this->assertEquals('See this: https://{some.domain}.com/foo/bar', $result);
+  }
+
+  /**
+   * We can't handle tokens in the path so it should not be tracked.
+   */
+  public function testLinkWithTokensInPathFails() {
+    $filter = new TextClickTracker();
+    $msg = 'See this: https://example.com/{some.path}';
+    $result = $filter->filterContent($msg, 1, 1);
+    $this->assertEquals('See this: https://example.com/{some.path}', $result);
+  }
+
+}
index d592787e8d0fc333ef716c52a124256d4542a597..a5b7d5c2b20f587f47c776558ba905df8f4d394b 100644 (file)
@@ -110,6 +110,28 @@ class FlexMailerSystemTest extends \CRM_Mailing_BaseMailingSystemTest {
     parent::testUrlTracking($inputHtml, $htmlUrlRegex, $textUrlRegex, $params);
   }
 
+  /**
+   *
+   * This takes CiviMail's own ones, but removes one that tested for a
+   * non-feature (i.e. that tokenised links are not handled).
+   *
+   * @return array
+   */
+  public function urlTrackingExamples() {
+    $cases = parent::urlTrackingExamples();
+
+    // When it comes to URLs with embedded tokens, support diverges - Flexmailer
+    // can track them, but BAO mailer cannot.
+    $cases[6] = [
+      '<p><a href="http://example.net/?id={contact.contact_id}">Foo</a></p>',
+      ';<p><a href=[\'"].*(extern/url.php|civicrm/mailing/url)(\?|&amp\\;)u=\d+.*&amp\\;id=\d+.*[\'"]>Foo</a></p>;',
+      ';\\[1\\] .*(extern/url.php|civicrm/mailing/url)[\?&]u=\d+.*&id=\d+.*;',
+      ['url_tracking' => 1],
+    ];
+
+    return $cases;
+  }
+
   public function testBasicHeaders() {
     parent::testBasicHeaders();
   }
index fe3a79d1cf7d5f553635b0d54ee7804e739b3e53..8c00681f153ee1359d24e2f254724dbbf8d5e8f8 100644 (file)
@@ -1,3 +1,9 @@
+/* Fix some cms themes which add margin to lists */
+ul, ol {
+  margin-left: 0;
+  margin-right: 0;
+}
+
 /* Supports adding form-control class to the checkbox-inline class to achieve an outer border around the checkbox&label */
 .form-control.checkbox-inline > label {
   margin-left: 9px;
index 6363b53c647b88008aa77c4de0185de89bbeee1a..f359c93fc2146a0b50133a3135a0719efeada5f2 100644 (file)
@@ -61,7 +61,7 @@ class Admin {
   public static function getSchema() {
     $schema = [];
     $entities = \Civi\Api4\Entity::get()
-      ->addSelect('name', 'title', 'type', 'title_plural', 'description', 'icon', 'paths', 'dao', 'bridge')
+      ->addSelect('name', 'title', 'type', 'title_plural', 'description', 'icon', 'paths', 'dao', 'bridge', 'ui_join_filters')
       ->addWhere('searchable', '=', TRUE)
       ->addOrderBy('title_plural')
       ->setChain([
@@ -142,6 +142,7 @@ class Admin {
           ) {
             continue;
           }
+          // For dynamic references getTargetEntities will return multiple targets; for normal joins this loop will only run once
           foreach ($reference->getTargetEntities() as $targetTable => $targetEntityName) {
             if (!isset($allowedEntities[$targetEntityName]) || $targetEntityName === $entity['name']) {
               continue;
@@ -158,6 +159,7 @@ class Admin {
                 'description' => $dynamicCol ? '' : $keyField['label'],
                 'entity' => $targetEntityName,
                 'conditions' => self::getJoinConditions($keyField['name'], $alias . '.' . $reference->getTargetKey(), $targetTable, $dynamicCol),
+                'defaults' => self::getJoinDefaults($alias, $targetEntity),
                 'alias' => $alias,
                 'multi' => FALSE,
               ];
@@ -168,6 +170,7 @@ class Admin {
                 'description' => $dynamicCol ? '' : $keyField['label'],
                 'entity' => $entity['name'],
                 'conditions' => self::getJoinConditions($reference->getTargetKey(), $alias . '.' . $keyField['name'], $targetTable, $dynamicCol ? $alias . '.' . $dynamicCol : NULL),
+                'defaults' => self::getJoinDefaults($alias, $entity),
                 'alias' => $alias,
                 'multi' => TRUE,
               ];
@@ -192,6 +195,7 @@ class Admin {
                   [$bridge],
                   self::getJoinConditions('id', $alias . '.' . $baseKey, NULL, NULL)
                 ),
+                'defaults' => self::getJoinDefaults($alias, $targetEntity, $entity),
                 'bridge' => $bridge,
                 'alias' => $alias,
                 'multi' => TRUE,
@@ -206,6 +210,7 @@ class Admin {
                     [$bridge],
                     self::getJoinConditions($reference->getTargetKey(), $alias . '.' . $keyField['name'], $targetTable, $dynamicCol ? $alias . '.' . $dynamicCol : NULL)
                   ),
+                  'defaults' => self::getJoinDefaults($alias, $baseEntity, $entity),
                   'bridge' => $bridge,
                   'alias' => $alias,
                   'multi' => TRUE,
@@ -246,4 +251,29 @@ class Admin {
     return $conditions;
   }
 
+  /**
+   * @param $alias
+   * @param array ...$entities
+   * @return array
+   */
+  private static function getJoinDefaults($alias, ...$entities):array {
+    $conditions = [];
+    foreach ($entities as $entity) {
+      foreach ($entity['ui_join_filters'] ?? [] as $fieldName) {
+        $field = civicrm_api4($entity['name'], 'getFields', [
+          'select' => ['options'],
+          'where' => [['name', '=', $fieldName]],
+          'loadOptions' => ['name'],
+        ])->first();
+        $value = isset($field['options'][0]) ? json_encode($field['options'][0]['name']) : '';
+        $conditions[] = [
+          $alias . '.' . $fieldName . ($value ? ':name' : ''),
+          '=',
+          $value,
+        ];
+      }
+    }
+    return $conditions;
+  }
+
 }
index 7e737d109667ce4f1cefade078762a2680608d1e..660611ffa2e0a939917689cf661d7f1e23706998 100644 (file)
         // Add the numbered suffix to the join conditions
         // If this is a deep join, also add the base entity prefix
         var prefix = alias.replace(new RegExp('_?' + join.alias + '_?\\d?\\d?$'), '');
-        _.each(result.conditions, function(condition) {
+        function replaceRefs(condition) {
           if (_.isArray(condition)) {
             _.each(condition, function(ref, side) {
-              if (side !== 1 && _.includes(ref, '.')) {
-                condition[side] = ref.replace(join.alias + '.', alias + '.');
-              } else if (side !== 1 && prefix.length && !_.includes(ref, '"') && !_.includes(ref, "'")) {
-                condition[side] = prefix + '.' + ref;
+              if (side !== 1 && typeof ref === 'string') {
+                if (_.includes(ref, '.')) {
+                  condition[side] = ref.replace(join.alias + '.', alias + '.');
+                } else if (prefix.length && !_.includes(ref, '"') && !_.includes(ref, "'")) {
+                  condition[side] = prefix + '.' + ref;
+                }
               }
             });
           }
-        });
+        }
+        _.each(result.conditions, replaceRefs);
+        _.each(result.defaults, replaceRefs);
         return result;
       }
       function getFieldAndJoin(fieldName, entityName) {
index 78c75a391a476b1eabf3761365aa40bad4659af4..eefa730caab63b2e69fcc5e2db783f8b32afb4f4 100644 (file)
@@ -4,8 +4,11 @@
       <fieldset ng-repeat="join in $ctrl.savedSearch.api_params.join">
         <div class="form-inline">
           <label for="crm-search-join-{{ $index }}">{{:: ts('With') }}</label>
-          <input id="crm-search-join-{{ $index }}" class="form-control huge" ng-model="join[0]" crm-ui-select="{placeholder: ' ', data: getJoinEntities}" ng-change="changeJoin($index)" />
+          <input id="crm-search-join-{{ $index }}" class="form-control huge" ng-model="join[0]" crm-ui-select="{placeholder: ' ', data: getJoinEntities}" disabled >
           <select class="form-control" ng-model="join[1]" ng-options="o.k as o.v for o in ::joinTypes" ></select>
+          <button class="btn btn-xs btn-danger-outline" ng-click="$ctrl.clearParam('join', $index)" title="{{:: ts('Remove join') }}">
+            <i class="crm-i fa-trash" aria-hidden="true"></i>
+          </button>
         </div>
         <fieldset class="api4-clause-fieldset">
           <crm-search-clause clauses="join" format="json" skip="2 + getJoin(join[0]).conditions.length" op="AND" label="{{ ts('If') }}" fields="fieldsForWhere" ></crm-search-clause>
index 80a444fd94ac49a7aed028ff19d13adddf969c61..7f82c23886ec7c0214cf3b313fb473fa47fff0db 100644 (file)
             _.each(_.cloneDeep(join.conditions), function(condition) {
               params.push(condition);
             });
+            _.each(_.cloneDeep(join.defaults), function(condition) {
+              params.push(condition);
+            });
             ctrl.savedSearch.api_params.join.push(params);
             loadFieldOptions();
           }
         });
       };
 
-      $scope.changeJoin = function(idx) {
-        if (ctrl.savedSearch.api_params.join[idx][0]) {
-          ctrl.savedSearch.api_params.join[idx].length = 2;
-          loadFieldOptions();
-        } else {
-          ctrl.clearParam('join', idx);
-        }
-      };
-
       $scope.changeGroupBy = function(idx) {
         if (!ctrl.savedSearch.api_params.groupBy[idx]) {
           ctrl.clearParam('groupBy', idx);
index 319fdb140002a744f4e3d5eee995f2dc04e5a06f..d6ac891b3cbcf15a733e7f4db9f2e8d85f2c48a9 100644 (file)
@@ -69,9 +69,6 @@
   right: 0;
   top: 0;
 }
-#bootstrap-theme.crm-search crm-search-clause > .btn-group .btn {
-  border: 0 none;
-}
 
 #bootstrap-theme.crm-search fieldset div.api4-input {
   margin-bottom: 10px;
index 48669434de475b26605feb2ea6cf715c89848f2d..335707b9c7f17b70b5808031b78c7e971f46f5ea 100644 (file)
       "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
       "dev": true
     },
-    "async-limiter": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
-      "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
-      "dev": true
-    },
     "asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
         "tweetnacl": "^0.14.3"
       }
     },
-    "better-assert": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
-      "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=",
-      "dev": true,
-      "requires": {
-        "callsite": "1.0.0"
-      }
-    },
     "binary-extensions": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
       "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
       "dev": true
     },
-    "callsite": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
-      "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=",
-      "dev": true
-    },
     "camelcase": {
       "version": "5.3.1",
       "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
       "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
       "dev": true
     },
-    "cookie": {
-      "version": "0.3.1",
-      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
-      "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=",
-      "dev": true
-    },
     "core-util-is": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
       "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
       "dev": true
     },
-    "engine.io": {
-      "version": "3.4.1",
-      "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.1.tgz",
-      "integrity": "sha512-8MfIfF1/IIfxuc2gv5K+XlFZczw/BpTvqBdl0E2fBLkYQp4miv4LuDTVtYt4yMyaIFLEr4vtaSgV4mjvll8Crw==",
-      "dev": true,
-      "requires": {
-        "accepts": "~1.3.4",
-        "base64id": "2.0.0",
-        "cookie": "0.3.1",
-        "debug": "~4.1.0",
-        "engine.io-parser": "~2.2.0",
-        "ws": "^7.1.2"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "4.1.1",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
-          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
-          "dev": true,
-          "requires": {
-            "ms": "^2.1.1"
-          }
-        },
-        "ms": {
-          "version": "2.1.2",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-          "dev": true
-        }
-      }
-    },
-    "engine.io-client": {
-      "version": "3.4.2",
-      "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.2.tgz",
-      "integrity": "sha512-AWjc1Xg06a6UPFOBAzJf48W1UR/qKYmv/ubgSCumo9GXgvL/xGIvo05dXoBL+2NTLMipDI7in8xK61C17L25xg==",
-      "dev": true,
-      "requires": {
-        "component-emitter": "~1.3.0",
-        "component-inherit": "0.0.3",
-        "debug": "~4.1.0",
-        "engine.io-parser": "~2.2.0",
-        "has-cors": "1.1.0",
-        "indexof": "0.0.1",
-        "parseqs": "0.0.5",
-        "parseuri": "0.0.5",
-        "ws": "~6.1.0",
-        "xmlhttprequest-ssl": "~1.5.4",
-        "yeast": "0.1.2"
-      },
-      "dependencies": {
-        "component-emitter": {
-          "version": "1.3.0",
-          "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
-          "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
-          "dev": true
-        },
-        "debug": {
-          "version": "4.1.1",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
-          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
-          "dev": true,
-          "requires": {
-            "ms": "^2.1.1"
-          }
-        },
-        "ms": {
-          "version": "2.1.2",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-          "dev": true
-        },
-        "ws": {
-          "version": "6.1.4",
-          "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz",
-          "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==",
-          "dev": true,
-          "requires": {
-            "async-limiter": "~1.0.0"
-          }
-        }
-      }
-    },
     "engine.io-parser": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz",
       "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
       "dev": true
     },
-    "object-component": {
-      "version": "0.0.3",
-      "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz",
-      "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=",
-      "dev": true
-    },
     "on-finished": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
       "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
       "dev": true
     },
-    "parseqs": {
-      "version": "0.0.5",
-      "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
-      "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
-      "dev": true,
-      "requires": {
-        "better-assert": "~1.0.0"
-      }
-    },
-    "parseuri": {
-      "version": "0.0.5",
-      "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
-      "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
-      "dev": true,
-      "requires": {
-        "better-assert": "~1.0.0"
-      }
-    },
     "parseurl": {
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
       "dev": true
     },
     "socket.io": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz",
-      "integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==",
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.1.tgz",
+      "integrity": "sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w==",
       "dev": true,
       "requires": {
         "debug": "~4.1.0",
-        "engine.io": "~3.4.0",
+        "engine.io": "~3.5.0",
         "has-binary2": "~1.0.2",
         "socket.io-adapter": "~1.1.0",
-        "socket.io-client": "2.3.0",
+        "socket.io-client": "2.4.0",
         "socket.io-parser": "~3.4.0"
       },
       "dependencies": {
+        "component-emitter": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
+          "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
+          "dev": true
+        },
+        "cookie": {
+          "version": "0.4.1",
+          "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
+          "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
+          "dev": true
+        },
         "debug": {
           "version": "4.1.1",
           "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
             "ms": "^2.1.1"
           }
         },
-        "ms": {
-          "version": "2.1.2",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-          "dev": true
-        }
-      }
-    },
-    "socket.io-adapter": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz",
-      "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==",
-      "dev": true
-    },
-    "socket.io-client": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz",
-      "integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==",
-      "dev": true,
-      "requires": {
-        "backo2": "1.0.2",
-        "base64-arraybuffer": "0.1.5",
-        "component-bind": "1.0.0",
-        "component-emitter": "1.2.1",
-        "debug": "~4.1.0",
-        "engine.io-client": "~3.4.0",
-        "has-binary2": "~1.0.2",
-        "has-cors": "1.1.0",
-        "indexof": "0.0.1",
-        "object-component": "0.0.3",
-        "parseqs": "0.0.5",
-        "parseuri": "0.0.5",
-        "socket.io-parser": "~3.3.0",
-        "to-array": "0.1.4"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "4.1.1",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
-          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+        "engine.io": {
+          "version": "3.5.0",
+          "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz",
+          "integrity": "sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==",
           "dev": true,
           "requires": {
-            "ms": "^2.1.1"
+            "accepts": "~1.3.4",
+            "base64id": "2.0.0",
+            "cookie": "~0.4.1",
+            "debug": "~4.1.0",
+            "engine.io-parser": "~2.2.0",
+            "ws": "~7.4.2"
+          }
+        },
+        "engine.io-client": {
+          "version": "3.5.0",
+          "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.0.tgz",
+          "integrity": "sha512-12wPRfMrugVw/DNyJk34GQ5vIVArEcVMXWugQGGuw2XxUSztFNmJggZmv8IZlLyEdnpO1QB9LkcjeWewO2vxtA==",
+          "dev": true,
+          "requires": {
+            "component-emitter": "~1.3.0",
+            "component-inherit": "0.0.3",
+            "debug": "~3.1.0",
+            "engine.io-parser": "~2.2.0",
+            "has-cors": "1.1.0",
+            "indexof": "0.0.1",
+            "parseqs": "0.0.6",
+            "parseuri": "0.0.6",
+            "ws": "~7.4.2",
+            "xmlhttprequest-ssl": "~1.5.4",
+            "yeast": "0.1.2"
+          },
+          "dependencies": {
+            "debug": {
+              "version": "3.1.0",
+              "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+              "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+              "dev": true,
+              "requires": {
+                "ms": "2.0.0"
+              }
+            },
+            "ms": {
+              "version": "2.0.0",
+              "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+              "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+              "dev": true
+            }
           }
         },
         "isarray": {
           "dev": true
         },
         "ms": {
-          "version": "2.1.2",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "version": "2.1.3",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+          "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+          "dev": true
+        },
+        "parseqs": {
+          "version": "0.0.6",
+          "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
+          "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==",
+          "dev": true
+        },
+        "parseuri": {
+          "version": "0.0.6",
+          "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz",
+          "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==",
           "dev": true
         },
-        "socket.io-parser": {
-          "version": "3.3.0",
-          "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz",
-          "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==",
+        "socket.io-client": {
+          "version": "2.4.0",
+          "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz",
+          "integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==",
           "dev": true,
           "requires": {
-            "component-emitter": "1.2.1",
+            "backo2": "1.0.2",
+            "component-bind": "1.0.0",
+            "component-emitter": "~1.3.0",
             "debug": "~3.1.0",
-            "isarray": "2.0.1"
+            "engine.io-client": "~3.5.0",
+            "has-binary2": "~1.0.2",
+            "indexof": "0.0.1",
+            "parseqs": "0.0.6",
+            "parseuri": "0.0.6",
+            "socket.io-parser": "~3.3.0",
+            "to-array": "0.1.4"
           },
           "dependencies": {
             "debug": {
               "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
               "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
               "dev": true
+            },
+            "socket.io-parser": {
+              "version": "3.3.2",
+              "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz",
+              "integrity": "sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==",
+              "dev": true,
+              "requires": {
+                "component-emitter": "~1.3.0",
+                "debug": "~3.1.0",
+                "isarray": "2.0.1"
+              }
             }
           }
+        },
+        "ws": {
+          "version": "7.4.2",
+          "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
+          "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==",
+          "dev": true
         }
       }
     },
+    "socket.io-adapter": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz",
+      "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==",
+      "dev": true
+    },
     "socket.io-parser": {
       "version": "3.4.1",
       "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz",
       "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
       "dev": true
     },
-    "ws": {
-      "version": "7.3.0",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz",
-      "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==",
-      "dev": true
-    },
     "xmlhttprequest-ssl": {
       "version": "1.5.5",
       "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
index 7b26c26e8a8c3206c7430202761f3b3d939bd7c5..0b19250e61af2f4708bce5d6708c60767402a203 100644 (file)
@@ -91,10 +91,10 @@ return [
     'default' => FALSE,
     'html_type' => 'radio',
     'add' => '4.7',
-    'title' => ts('Enable Embedded Activity Revisions'),
+    'title' => ts('Enable deprecated Embedded Activity Revisions'),
     'is_domain' => 1,
     'is_contact' => 0,
-    'description' => ts('Enable tracking of activity revisions embedded within the "civicrm_activity" table. Alternatively, see "Administer => System Settings => Misc => Logging".'),
+    'description' => ts('Enable tracking of activity revisions embedded within the "civicrm_activity" table. This should not be enabled on new installs and will be unsupported in the future. You should enable "Administer => System Settings => Misc => Logging" instead.'),
     'help_text' => '',
   ],
   'civicaseShowCaseActivities' => [
index a6cafa18b68f93a8a7c3972ce963991eb2c57a79..930819c0c3dc0d4c3222616a362bfa2ced33384d 100644 (file)
@@ -98,43 +98,6 @@ class CRM_Core_Payment_BaseIPNTest extends CiviUnitTestCase {
     CRM_Member_PseudoConstant::membershipStatus(NULL, NULL, 'name', TRUE);
   }
 
-  /**
-   * Test the LoadObjects function with recurring membership data.
-   */
-  public function testLoadMembershipObjects() {
-    $this->_setUpMembershipObjects();
-    $this->_setUpRecurringContribution();
-    $this->IPN->loadObjects($this->input, $this->ids, $this->objects, FALSE, $this->_processorId);
-    $this->assertNotEmpty($this->objects['membership']);
-    $this->assertArrayHasKey($this->_membershipId . '_' . $this->_membershipTypeID, $this->objects['membership']);
-    $this->assertTrue(is_a($this->objects['membership'][$this->_membershipId . '_' . $this->_membershipTypeID], 'CRM_Member_BAO_Membership'));
-    $this->assertTrue(is_a($this->objects['financialType'], 'CRM_Financial_BAO_FinancialType'));
-    $this->assertNotEmpty($this->objects['contributionRecur']);
-    $this->assertNotEmpty($this->objects['paymentProcessor']);
-  }
-
-  /**
-   * Test the LoadObjects function with recurring membership data.
-   */
-  public function testLoadMembershipObjectsNoLeakage() {
-    $this->_setUpMembershipObjects();
-    $this->_setUpRecurringContribution();
-    $this->IPN->loadObjects($this->input, $this->ids, $this->objects, FALSE, $this->_processorId);
-    $this->assertEquals('Anthony', $this->objects['contact']->first_name);
-
-    $this->ids['contact'] = $this->_contactId = $this->individualCreate([
-      'first_name' => 'Donald',
-      'last_name' => 'Duck',
-      'email' => 'the-don@duckville.com',
-    ]);
-    $contribution = $this->callAPISuccess('contribution', 'create', array_merge($this->_contributionParams, ['invoice_id' => 'abc']));
-    $this->_contributionId = $contribution['id'];
-    $this->_setUpMembershipObjects();
-    $this->input['invoiceID'] = 'abc';
-    $this->IPN->loadObjects($this->input, $this->ids, $this->objects, FALSE, $this->_processorId);
-    $this->assertEquals('Donald', $this->objects['contact']->first_name);
-  }
-
   /**
    * Test the LoadObjects function with recurring membership data.
    */
@@ -189,8 +152,6 @@ class CRM_Core_Payment_BaseIPNTest extends CiviUnitTestCase {
     $this->_membershipTypeID = $this->membershipTypeCreate(['name' => 'Fowl']);
     $this->_setUpMembershipObjects();
     $this->input['invoiceID'] = 'abc';
-    $this->IPN->loadObjects($this->input, $this->ids, $this->objects, FALSE, $this->_processorId);
-    $this->assertEquals('Donald', $this->objects['contact']->first_name);
     $contribution = new CRM_Contribute_BAO_Contribution();
     $contribution->id = $this->_contributionId;
     $msg = $contribution->composeMessageArray($this->input, $this->ids, $values);
@@ -211,31 +172,14 @@ class CRM_Core_Payment_BaseIPNTest extends CiviUnitTestCase {
     $this->assertContains('Membership Type: General', $msg['body']);
   }
 
-  /**
-   * Test that loadObjects works with participant values.
-   */
-  public function testLoadParticipantObjects() {
-    $this->_setUpParticipantObjects();
-    $this->IPN->loadObjects($this->input, $this->ids, $this->objects, FALSE, $this->_processorId);
-    $this->assertNotEmpty($this->objects['participant']);
-    $this->assertTrue(is_a($this->objects['participant'], 'CRM_Event_BAO_Participant'));
-    $this->assertTrue(is_a($this->objects['financialType'], 'CRM_Financial_BAO_FinancialType'));
-    $this->assertNotEmpty($this->objects['event']);
-    $this->assertTrue(is_a($this->objects['event'], 'CRM_Event_BAO_Event'));
-    $this->assertTrue(is_a($this->objects['contribution'], 'CRM_Contribute_BAO_Contribution'));
-    $this->assertNotEmpty($this->objects['event']->id);
-  }
-
   /**
    * Test the LoadObjects function with a participant.
    */
   public function testComposeMailParticipant() {
     $this->_setUpParticipantObjects();
-    $this->IPN->loadObjects($this->input, $this->ids, $this->objects, FALSE, $this->_processorId);
-    $values = [];
-    $this->assertNotEmpty($this->objects['event']);
     $contribution = new CRM_Contribute_BAO_Contribution();
     $contribution->id = $this->_contributionId;
+    $contribution->loadRelatedObjects($this->input, $this->ids);
     $msg = $contribution->composeMessageArray($this->input, $this->ids, $values);
     $this->assertContains('registration has been received and your status has been updated to Attended.', $msg['body']);
     $this->assertContains('Annual CiviCRM meet', $msg['html']);
@@ -291,43 +235,6 @@ class CRM_Core_Payment_BaseIPNTest extends CiviUnitTestCase {
     $mut->stop();
   }
 
-  /**
-   * Test that loadObjects works with participant values.
-   */
-  public function testLoadPledgeObjects() {
-    $this->_setUpPledgeObjects();
-    $this->IPN->loadObjects($this->input, $this->ids, $this->objects, FALSE, $this->_processorId);
-    $this->assertNotEmpty($this->objects['pledge_payment'][0]);
-    $this->assertTrue(is_a($this->objects['financialType'], 'CRM_Financial_BAO_FinancialType'));
-    $this->assertTrue(is_a($this->objects['contribution'], 'CRM_Contribute_BAO_Contribution'));
-    $this->assertTrue(is_a($this->objects['pledge_payment'][0], 'CRM_Pledge_BAO_PledgePayment'));
-    $this->assertNotEmpty($this->objects['pledge_payment'][0]->id);
-    $this->assertEquals($this->_financialTypeId, $this->objects['financialType']->id);
-    $this->assertEquals($this->_processorId, $this->objects['paymentProcessor']['id']);
-    $this->assertEquals($this->_contributionId, $this->objects['contribution']->id);
-    $this->assertEquals($this->_contactId, $this->objects['contact']->id);
-    $this->assertEquals($this->_pledgeId, $this->objects['pledge_payment'][0]->pledge_id);
-  }
-
-  /**
-   * Test that loadObjects works with participant values.
-   */
-  public function testLoadPledgeObjectsInvalidPledgeID() {
-    $this->_setUpPledgeObjects();
-    $this->ids['pledge_payment'][0] = 0;
-
-    $this->IPN->loadObjects($this->input, $this->ids, $this->objects, TRUE, NULL);
-    $this->assertArrayNotHasKey('pledge_payment', $this->objects);
-
-    $this->ids['pledge_payment'][0] = 999;
-    try {
-      $this->IPN->loadObjects($this->input, $this->ids, $this->objects, TRUE, $this->_processorId);
-    }
-    catch (CRM_Core_Exception $e) {
-      $this->assertEquals('Could not find pledge payment record: 999', $e->getMessage());
-    }
-  }
-
   /**
    * Test the LoadObjects function with a pledge.
    */
@@ -339,27 +246,6 @@ class CRM_Core_Payment_BaseIPNTest extends CiviUnitTestCase {
     $this->assertContains('Contribution Information', $msg['html']);
   }
 
-  /**
-   * Test that an error is returned if required set & no contribution page.
-   */
-  public function testRequiredWithoutProcessorID() {
-    $this->_setUpPledgeObjects();
-    // error is only returned if $required set to True
-    $result = $this->IPN->loadObjects($this->input, $this->ids, $this->objects, FALSE, NULL);
-    $this->assertEquals(TRUE, $result);
-  }
-
-  /**
-   * Test that if part of $input the payment processor loads OK.
-   *
-   * It's preferable to pass it in as it cannot be correctly calculated.
-   */
-  public function testPaymentProcessorLoadsAsParam() {
-    $this->_setUpContributionObjects();
-    $this->input = array_merge($this->input, ['payment_processor_id' => $this->_processorId]);
-    $this->assertTrue($this->IPN->loadObjects($this->input, $this->ids, $this->objects, TRUE, NULL));
-  }
-
   public function testThatCancellingEventPaymentWillCancelAllAdditionalPendingParticipantsAndCreateCancellationActivities() {
     $this->_setUpParticipantObjects('Pending from incomplete transaction');
     $additionalParticipantId = $this->participantCreate([
index 5356c14e6d0160107cc3c0c3995199a05f6d68ef..b2f0bea21d29e647e8f265b533d1c940c7f0aa69 100644 (file)
@@ -375,6 +375,51 @@ class CRM_Logging_SchemaTest extends CiviUnitTestCase {
     $this->assertStringContainsString("`{$custom_field['column_name']}` varchar(768)", $dao->Create_Table);
   }
 
+  /**
+   * Test that logging records changes in upper/lower-case and accents.
+   * e.g. changing e to E or e to é
+   * @dataProvider loggingSensitivityProvider
+   *
+   * @param array $input
+   */
+  public function testLoggingSensitivity(array $input) {
+    $schema = new CRM_Logging_Schema();
+    $schema->enableLogging();
+
+    // create a contact with all lower case/no accents
+    $contact_id = $this->individualCreate(['first_name' => 'pierre']);
+
+    $query_params = [
+      1 => [$contact_id, 'Integer'],
+      2 => [$input['first_name'], 'String'],
+    ];
+
+    // Clear out anything that api did twice during initial creation so that
+    // spurious update records don't give false results.
+    CRM_Core_DAO::executeQuery("DELETE FROM log_civicrm_contact WHERE id = %1 AND log_action = 'update'", $query_params);
+
+    // Change the first name
+    CRM_Core_DAO::executeQuery("UPDATE civicrm_contact SET first_name = %2 WHERE id = %1", $query_params);
+    // check the log
+    $dao = CRM_Core_DAO::executeQuery("SELECT first_name FROM log_civicrm_contact WHERE id = %1 AND log_action = 'update'", $query_params);
+    $first_name = NULL;
+    if ($dao->fetch()) {
+      $first_name = $dao->first_name;
+    }
+    $this->assertSame($input['first_name'], $first_name);
+  }
+
+  /**
+   * Dataprovider for testLoggingSensitivity
+   * @return array
+   */
+  public function loggingSensitivityProvider():array {
+    return [
+      'upper' => [['first_name' => 'Pierre']],
+      'accent' => [['first_name' => 'pièrre']],
+    ];
+  }
+
   /**
    * Test creating a table with SchemaHandler::createTable when logging
    * is enabled.
index 3e3a9c5402771f5d744d094e2f18936baf1b182d..87670df9ff48505d3d7d8bee8493c0714f7fbaa3 100644 (file)
@@ -325,7 +325,6 @@ abstract class CRM_Mailing_BaseMailingSystemTest extends CiviUnitTestCase {
    * @throws \CRM_Core_Exception
    */
   public function testUrlTracking($inputHtml, $htmlUrlRegex, $textUrlRegex, $params) {
-    $caseName = print_r(['inputHtml' => $inputHtml, 'params' => $params], 1);
 
     $allMessages = $this->runMailingSuccess($params + [
       'subject' => 'Example Subject',
@@ -341,11 +340,13 @@ abstract class CRM_Mailing_BaseMailingSystemTest extends CiviUnitTestCase {
       list($textPart, $htmlPart) = $message->body->getParts();
 
       if ($htmlUrlRegex) {
+        $caseName = print_r(['inputHtml' => $inputHtml, 'params' => $params, 'htmlUrlRegex' => $htmlUrlRegex, 'htmlPart' => $htmlPart->text], 1);
         $this->assertEquals('html', $htmlPart->subType, "Should have HTML part in case: $caseName");
         $this->assertRegExp($htmlUrlRegex, $htmlPart->text, "Should have correct HTML in case: $caseName");
       }
 
       if ($textUrlRegex) {
+        $caseName = print_r(['inputHtml' => $inputHtml, 'params' => $params, 'textUrlRegex' => $textUrlRegex, 'textPart' => $textPart->text], 1);
         $this->assertEquals('plain', $textPart->subType, "Should have text part in case: $caseName");
         $this->assertRegExp($textUrlRegex, $textPart->text, "Should have correct text in case: $caseName");
       }
index ee0138a946d7967ca7afbd1ae6adf79d2a7d62c3..95284d83520e40d10332458426f08739123742ad 100644 (file)
@@ -107,7 +107,7 @@ class TestCreationParameterProvider {
    */
   private function getOption(FieldSpec $field) {
     $options = array_column($field->getOptions(), 'label', 'id');
-    return array_rand($options);
+    return key($options);
   }
 
   /**
index 351b22e047eed7e58226a013ca6c32c9e1561242..dc33ce3050717f2115403c2bb3411268e9b62cd2 100644 (file)
   <field>
     <name>record_type_id</name>
     <type>int unsigned</type>
-    <title>Record Type ID</title>
-    <comment>Nature of this contact's role in the activity: 1 assignee, 2 creator, 3 focus or target.</comment>
+    <title>Activity Contact Type</title>
+    <comment>Determines the contact's role in the activity (source, target, or assignee).</comment>
     <pseudoconstant>
       <optionGroupName>activity_contacts</optionGroupName>
     </pseudoconstant>
     <html>
       <type>Select</type>
+      <label>Contact Role</label>
     </html>
     <add>4.4</add>
   </field>
index 7de6ac766e4daa34c5bb6be15564b3fefc0750da..023e21e2b2110e87be68bbc8b222b2bc7b4b5641 100644 (file)
@@ -93,6 +93,9 @@
     <length>64</length>
     <comment>name for relationship of near_contact to far_contact.</comment>
     <add>5.29</add>
+    <html>
+      <label>Relationship to contact</label>
+    </html>
     <pseudoconstant>
       <callback>CRM_Core_PseudoConstant::relationshipTypeOptions</callback>
     </pseudoconstant>
     <length>64</length>
     <comment>name for relationship of far_contact to near_contact.</comment>
     <add>5.29</add>
+    <html>
+      <label>Relationship from contact</label>
+    </html>
     <pseudoconstant>
       <callback>CRM_Core_PseudoConstant::relationshipTypeOptions</callback>
     </pseudoconstant>