*
* Generated from xml/schema/CRM/Activity/ActivityContact.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:3f147b2507b1e11a7df971be191161d1)
+ * (GenCodeChecksum:1263921d2a6832e26c5a7e34c684e35b)
*/
/**
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
*/
'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',
'localizable' => 0,
'html' => [
'type' => 'Select',
+ 'label' => ts("Contact Role"),
],
'pseudoconstant' => [
'optionGroupName' => 'activity_contacts',
*
* Generated from xml/schema/CRM/Contact/RelationshipCache.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:ba039fcadc13e48749f965343301ec1d)
+ * (GenCodeChecksum:ec899f1ccb7f617701d7108dc4282691)
*/
/**
'entity' => 'RelationshipCache',
'bao' => 'CRM_Contact_BAO_RelationshipCache',
'localizable' => 0,
+ 'html' => [
+ 'label' => ts("Relationship to contact"),
+ ],
'pseudoconstant' => [
'callback' => 'CRM_Core_PseudoConstant::relationshipTypeOptions',
],
'entity' => 'RelationshipCache',
'bao' => 'CRM_Contact_BAO_RelationshipCache',
'localizable' => 0,
+ 'html' => [
+ 'label' => ts("Relationship from contact"),
+ ],
'pseudoconstant' => [
'callback' => 'CRM_Core_PseudoConstant::relationshipTypeOptions',
],
/**
* Load objects related to contribution.
*
+ * @deprecated
+ *
* @input array information from Payment processor
*
* @param array $input
* @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);
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;
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);
}
}
$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) {
$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";
* 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)) {
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
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
* 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 {
'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);
}
*
* @see \Civi\Api4\Relationship
* @bridge near_contact_id far_contact_id
+ * @ui_join_filters near_relation
* @package Civi\Api4
*/
class RelationshipCache extends Generic\AbstractEntity {
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);
'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\".',
},
"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",
"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"
}
.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 {
+++ /dev/null
-<?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"],
- ];
- }
-
-}
+++ /dev/null
-<?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"],
- ];
- }
-
-}
+++ /dev/null
-<?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);
- }
-
-}
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;
}
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);
}
--- /dev/null
+<?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}&a=b&cid={contact.id}">See this</a>';
+ $result = $filter->filterContent($msg, 1, 1);
+ $this->assertEquals('<a href="http://example.com/extern?u=1&qid=1&cs={contact.checksum}&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);
+ }
+
+}
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)(\?|&\\;)u=\d+.*&\\;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();
}
+/* 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;
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([
) {
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;
'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,
];
'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,
];
[$bridge],
self::getJoinConditions('id', $alias . '.' . $baseKey, NULL, NULL)
),
+ 'defaults' => self::getJoinDefaults($alias, $targetEntity, $entity),
'bridge' => $bridge,
'alias' => $alias,
'multi' => TRUE,
[$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,
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;
+ }
+
}
// 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) {
<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>
_.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);
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;
"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",
'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' => [
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.
*/
$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);
$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']);
$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.
*/
$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([
$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.
* @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',
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");
}
*/
private function getOption(FieldSpec $field) {
$options = array_column($field->getOptions(), 'label', 'id');
- return array_rand($options);
+ return key($options);
}
/**
<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>
<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>