Greeting handling - if email_greeting_custom (etc) isset & email_greeting_id is not, set to 'Customized'
$formValues['main_details'] = $this->_mainDetails;
$formValues['other_details'] = $this->_otherDetails;
+
+ // Check if any rel_tables checkboxes have been de-selected
+ $rowsElementsAndInfo = CRM_Dedupe_Merger::getRowsElementsAndInfo($this->_cid, $this->_oid);
+ // If rel_tables is not set then initialise with 0 value, required for the check which calls removeContactBelongings in moveAllBelongings
+ foreach (array_keys($rowsElementsAndInfo['rel_tables']) as $relTableElement) {
+ if (!array_key_exists($relTableElement, $formValues)) {
+ $formValues[$relTableElement] = '0';
+ }
+ }
$migrationData = ['migration_info' => $formValues];
+
CRM_Utils_Hook::merge('form', $migrationData, $this->_cid, $this->_oid);
CRM_Dedupe_Merger::moveAllBelongings($this->_cid, $this->_oid, $migrationData['migration_info']);
$formatted['updateBlankLocInfo'] = FALSE;
}
- [$data, $contactDetails] = CRM_Contact_BAO_Contact::formatProfileContactParams($formatted, $contactFields, $contactId, NULL, $formatted['contact_type']);
+ [$data, $contactDetails] = $this->formatProfileContactParams($formatted, $contactFields, $contactId, NULL, $formatted['contact_type']);
// manage is_opt_out
if (array_key_exists('is_opt_out', $contactFields) && array_key_exists('is_opt_out', $formatted)) {
return $newContact;
}
+ /**
+ * Legacy format profile contact parameters.
+ *
+ * This is a formerly shared function - most of the stuff in it probably does
+ * nothing but copied here to star unravelling that...
+ *
+ * @param array $params
+ * @param array $fields
+ * @param int|null $contactID
+ * @param int|null $ufGroupId
+ * @param string|null $ctype
+ * @param bool $skipCustom
+ *
+ * @return array
+ */
+ private function formatProfileContactParams(
+ &$params,
+ $fields,
+ $contactID = NULL,
+ $ufGroupId = NULL,
+ $ctype = NULL,
+ $skipCustom = FALSE
+ ) {
+
+ $data = $contactDetails = [];
+
+ // get the contact details (hier)
+ if ($contactID) {
+ $details = CRM_Contact_BAO_Contact::getHierContactDetails($contactID, $fields);
+
+ $contactDetails = $details[$contactID];
+ $data['contact_type'] = $contactDetails['contact_type'] ?? NULL;
+ $data['contact_sub_type'] = $contactDetails['contact_sub_type'] ?? NULL;
+ }
+ else {
+ //we should get contact type only if contact
+ if ($ufGroupId) {
+ $data['contact_type'] = CRM_Core_BAO_UFField::getProfileType($ufGroupId, TRUE, FALSE, TRUE);
+
+ //special case to handle profile with only contact fields
+ if ($data['contact_type'] == 'Contact') {
+ $data['contact_type'] = 'Individual';
+ }
+ elseif (CRM_Contact_BAO_ContactType::isaSubType($data['contact_type'])) {
+ $data['contact_type'] = CRM_Contact_BAO_ContactType::getBasicType($data['contact_type']);
+ }
+ }
+ elseif ($ctype) {
+ $data['contact_type'] = $ctype;
+ }
+ else {
+ $data['contact_type'] = 'Individual';
+ }
+ }
+
+ //fix contact sub type CRM-5125
+ if (array_key_exists('contact_sub_type', $params) &&
+ !empty($params['contact_sub_type'])
+ ) {
+ $data['contact_sub_type'] = CRM_Utils_Array::implodePadded($params['contact_sub_type']);
+ }
+ elseif (array_key_exists('contact_sub_type_hidden', $params) &&
+ !empty($params['contact_sub_type_hidden'])
+ ) {
+ // if profile was used, and had any subtype, we obtain it from there
+ //CRM-13596 - add to existing contact types, rather than overwriting
+ if (empty($data['contact_sub_type'])) {
+ // If we don't have a contact ID the $data['contact_sub_type'] will not be defined...
+ $data['contact_sub_type'] = CRM_Utils_Array::implodePadded($params['contact_sub_type_hidden']);
+ }
+ else {
+ $data_contact_sub_type_arr = CRM_Utils_Array::explodePadded($data['contact_sub_type']);
+ if (!in_array($params['contact_sub_type_hidden'], $data_contact_sub_type_arr)) {
+ //CRM-20517 - make sure contact_sub_type gets the correct delimiters
+ $data['contact_sub_type'] = trim($data['contact_sub_type'], CRM_Core_DAO::VALUE_SEPARATOR);
+ $data['contact_sub_type'] = CRM_Core_DAO::VALUE_SEPARATOR . $data['contact_sub_type'] . CRM_Utils_Array::implodePadded($params['contact_sub_type_hidden']);
+ }
+ }
+ }
+
+ if ($ctype == 'Organization') {
+ $data['organization_name'] = $contactDetails['organization_name'] ?? NULL;
+ }
+ elseif ($ctype == 'Household') {
+ $data['household_name'] = $contactDetails['household_name'] ?? NULL;
+ }
+
+ $locationType = [];
+ $count = 1;
+
+ if ($contactID) {
+ //add contact id
+ $data['contact_id'] = $contactID;
+ $primaryLocationType = CRM_Contact_BAO_Contact::getPrimaryLocationType($contactID);
+ }
+ else {
+ $defaultLocation = CRM_Core_BAO_LocationType::getDefault();
+ $defaultLocationId = $defaultLocation->id;
+ }
+
+ $billingLocationTypeId = CRM_Core_BAO_LocationType::getBilling();
+
+ $blocks = ['email', 'phone', 'im', 'openid'];
+
+ $multiplFields = ['url'];
+ // prevent overwritten of formatted array, reset all block from
+ // params if it is not in valid format (since import pass valid format)
+ foreach ($blocks as $blk) {
+ if (array_key_exists($blk, $params) &&
+ !is_array($params[$blk])
+ ) {
+ unset($params[$blk]);
+ }
+ }
+
+ $primaryPhoneLoc = NULL;
+ $session = CRM_Core_Session::singleton();
+ foreach ($params as $key => $value) {
+ [$fieldName, $locTypeId, $typeId] = CRM_Utils_System::explode('-', $key, 3);
+
+ if ($locTypeId == 'Primary') {
+ if ($contactID) {
+ if (in_array($fieldName, $blocks)) {
+ $locTypeId = CRM_Contact_BAO_Contact::getPrimaryLocationType($contactID, FALSE, $fieldName);
+ }
+ else {
+ $locTypeId = CRM_Contact_BAO_Contact::getPrimaryLocationType($contactID, FALSE, 'address');
+ }
+ $primaryLocationType = $locTypeId;
+ }
+ else {
+ $locTypeId = $defaultLocationId;
+ }
+ }
+
+ if (is_numeric($locTypeId) &&
+ !in_array($fieldName, $multiplFields) &&
+ substr($fieldName, 0, 7) != 'custom_'
+ ) {
+ $index = $locTypeId;
+
+ if (is_numeric($typeId)) {
+ $index .= '-' . $typeId;
+ }
+ if (!in_array($index, $locationType)) {
+ $locationType[$count] = $index;
+ $count++;
+ }
+
+ $loc = CRM_Utils_Array::key($index, $locationType);
+
+ $blockName = $this->getLocationEntityForKey($fieldName);
+
+ $data[$blockName][$loc]['location_type_id'] = $locTypeId;
+
+ //set is_billing true, for location type "Billing"
+ if ($locTypeId == $billingLocationTypeId) {
+ $data[$blockName][$loc]['is_billing'] = 1;
+ }
+
+ if ($contactID) {
+ //get the primary location type
+ if ($locTypeId == $primaryLocationType) {
+ $data[$blockName][$loc]['is_primary'] = 1;
+ }
+ }
+ elseif ($locTypeId == $defaultLocationId) {
+ $data[$blockName][$loc]['is_primary'] = 1;
+ }
+
+ if (in_array($fieldName, ['phone'])) {
+ if ($typeId) {
+ $data['phone'][$loc]['phone_type_id'] = $typeId;
+ }
+ else {
+ $data['phone'][$loc]['phone_type_id'] = '';
+ }
+ $data['phone'][$loc]['phone'] = $value;
+
+ //special case to handle primary phone with different phone types
+ // in this case we make first phone type as primary
+ if (isset($data['phone'][$loc]['is_primary']) && !$primaryPhoneLoc) {
+ $primaryPhoneLoc = $loc;
+ }
+
+ if ($loc != $primaryPhoneLoc) {
+ unset($data['phone'][$loc]['is_primary']);
+ }
+ }
+ elseif ($fieldName == 'email') {
+ $data['email'][$loc]['email'] = $value;
+ if (empty($contactID)) {
+ $data['email'][$loc]['is_primary'] = 1;
+ }
+ }
+ elseif ($fieldName == 'im') {
+ if (isset($params[$key . '-provider_id'])) {
+ $data['im'][$loc]['provider_id'] = $params[$key . '-provider_id'];
+ }
+ if (strpos($key, '-provider_id') !== FALSE) {
+ $data['im'][$loc]['provider_id'] = $params[$key];
+ }
+ else {
+ $data['im'][$loc]['name'] = $value;
+ }
+ }
+ elseif ($fieldName == 'openid') {
+ $data['openid'][$loc]['openid'] = $value;
+ }
+ else {
+ if ($fieldName === 'state_province') {
+ // CRM-3393
+ if (is_numeric($value) && ((int ) $value) >= 1000) {
+ $data['address'][$loc]['state_province_id'] = $value;
+ }
+ elseif (empty($value)) {
+ $data['address'][$loc]['state_province_id'] = '';
+ }
+ else {
+ $data['address'][$loc]['state_province'] = $value;
+ }
+ }
+ elseif ($fieldName === 'country') {
+ // CRM-3393
+ if (is_numeric($value) && ((int ) $value) >= 1000
+ ) {
+ $data['address'][$loc]['country_id'] = $value;
+ }
+ elseif (empty($value)) {
+ $data['address'][$loc]['country_id'] = '';
+ }
+ else {
+ $data['address'][$loc]['country'] = $value;
+ }
+ }
+ elseif ($fieldName === 'county') {
+ $data['address'][$loc]['county_id'] = $value;
+ }
+ elseif ($fieldName == 'address_name') {
+ $data['address'][$loc]['name'] = $value;
+ }
+ elseif (substr($fieldName, 0, 14) === 'address_custom') {
+ $data['address'][$loc][substr($fieldName, 8)] = $value;
+ }
+ else {
+ $data[$blockName][$loc][$fieldName] = $value;
+ }
+ }
+ }
+ else {
+ if (substr($key, 0, 4) === 'url-') {
+ $websiteField = explode('-', $key);
+ $data['website'][$websiteField[1]]['website_type_id'] = $websiteField[1];
+ $data['website'][$websiteField[1]]['url'] = $value;
+ }
+ elseif (in_array($key, CRM_Contact_BAO_Contact::$_greetingTypes, TRUE)) {
+ //save email/postal greeting and addressee values if any, CRM-4575
+ $data[$key . '_id'] = $value;
+ }
+ elseif (!$skipCustom && ($customFieldId = CRM_Core_BAO_CustomField::getKeyID($key))) {
+ // for autocomplete transfer hidden value instead of label
+ if ($params[$key] && isset($params[$key . '_id'])) {
+ $value = $params[$key . '_id'];
+ }
+
+ // we need to append time with date
+ if ($params[$key] && isset($params[$key . '_time'])) {
+ $value .= ' ' . $params[$key . '_time'];
+ }
+
+ // if auth source is not checksum / login && $value is blank, do not proceed - CRM-10128
+ if (($session->get('authSrc') & (CRM_Core_Permission::AUTH_SRC_CHECKSUM + CRM_Core_Permission::AUTH_SRC_LOGIN)) == 0 &&
+ ($value == '' || !isset($value))
+ ) {
+ continue;
+ }
+
+ $valueId = NULL;
+ if (!empty($params['customRecordValues'])) {
+ if (is_array($params['customRecordValues']) && !empty($params['customRecordValues'])) {
+ foreach ($params['customRecordValues'] as $recId => $customFields) {
+ if (is_array($customFields) && !empty($customFields)) {
+ foreach ($customFields as $customFieldName) {
+ if ($customFieldName == $key) {
+ $valueId = $recId;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //CRM-13596 - check for contact_sub_type_hidden first
+ if (array_key_exists('contact_sub_type_hidden', $params)) {
+ $type = $params['contact_sub_type_hidden'];
+ }
+ else {
+ $type = $data['contact_type'];
+ if (!empty($data['contact_sub_type'])) {
+ $type = CRM_Utils_Array::explodePadded($data['contact_sub_type']);
+ }
+ }
+
+ CRM_Core_BAO_CustomField::formatCustomField($customFieldId,
+ $data['custom'],
+ $value,
+ $type,
+ $valueId,
+ $contactID,
+ FALSE,
+ FALSE
+ );
+ }
+ elseif ($key === 'edit') {
+ continue;
+ }
+ else {
+ if ($key === 'location') {
+ foreach ($value as $locationTypeId => $field) {
+ foreach ($field as $block => $val) {
+ if ($block === 'address' && array_key_exists('address_name', $val)) {
+ $value[$locationTypeId][$block]['name'] = $value[$locationTypeId][$block]['address_name'];
+ }
+ }
+ }
+ }
+ if ($key === 'phone' && isset($params['phone_ext'])) {
+ $data[$key] = $value;
+ foreach ($value as $cnt => $phoneBlock) {
+ if ($params[$key][$cnt]['location_type_id'] == $params['phone_ext'][$cnt]['location_type_id']) {
+ $data[$key][$cnt]['phone_ext'] = CRM_Utils_Array::retrieveValueRecursive($params['phone_ext'][$cnt], 'phone_ext');
+ }
+ }
+ }
+ elseif (in_array($key, ['nick_name', 'job_title', 'middle_name', 'birth_date', 'gender_id', 'current_employer', 'prefix_id', 'suffix_id'])
+ && ($value == '' || !isset($value)) &&
+ ($session->get('authSrc') & (CRM_Core_Permission::AUTH_SRC_CHECKSUM + CRM_Core_Permission::AUTH_SRC_LOGIN)) == 0 ||
+ ($key === 'current_employer' && empty($params['current_employer']))) {
+ // CRM-10128: if auth source is not checksum / login && $value is blank, do not fill $data with empty value
+ // to avoid update with empty values
+ continue;
+ }
+ else {
+ $data[$key] = $value;
+ }
+ }
+ }
+ }
+
+ if (!isset($data['contact_type'])) {
+ $data['contact_type'] = 'Individual';
+ }
+
+ //set the values for checkboxes (do_not_email, do_not_mail, do_not_trade, do_not_phone)
+ $privacy = CRM_Core_SelectValues::privacy();
+ foreach ($privacy as $key => $value) {
+ if (array_key_exists($key, $fields)) {
+ // do not reset values for existing contacts, if fields are added to a profile
+ if (array_key_exists($key, $params)) {
+ $data[$key] = $params[$key];
+ if (empty($params[$key])) {
+ $data[$key] = 0;
+ }
+ }
+ elseif (!$contactID) {
+ $data[$key] = 0;
+ }
+ }
+ }
+
+ return [$data, $contactDetails];
+ }
+
+ /**
+ * Get the relevant location entity for the array key.
+ *
+ * Based on the field name we determine which location entity
+ * we are dealing with. Apart from a few specific ones they
+ * are mostly 'address' (the default).
+ *
+ * @param string $fieldName
+ *
+ * @return string
+ */
+ private static function getLocationEntityForKey($fieldName) {
+ if (in_array($fieldName, ['email', 'phone', 'im', 'openid'])) {
+ return $fieldName;
+ }
+ if ($fieldName === 'phone_ext') {
+ return 'phone';
+ }
+ return 'address';
+ }
+
/**
* Format params for update and fill mode.
*
$title[] = CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_Phone', 'phone_type_id', $mappedField['phone_type_id']);
}
if ($mappedField['im_provider_id']) {
- $title[] = CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_IM', 'provider_id', $mappedField['provider_id']);
+ $title[] = CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_IM', 'provider_id', $mappedField['im_provider_id']);
}
return implode(' - ', $title);
}
// Time to see if we can find an existing contact ID to make this an update
// not a create.
if ($extIDMatch || $this->isUpdateExistingContacts()) {
- return $this->getPossibleContactMatch($params, $extIDMatch, $this->getSubmittedValue('dedupe_rule_id'));
+ return $this->getPossibleContactMatch($params, $extIDMatch, $this->getSubmittedValue('dedupe_rule_id') ?: NULL);
}
return NULL;
}
*
* @param int $id
* @param array $overrides
- * Parameters that should be overriden. Add unit tests if using parameters other than total_amount & financial_type_id.
+ * Parameters that should be overridden. Add unit tests if using parameters other than total_amount & financial_type_id.
*
* @return array
*
// https://github.com/civicrm/civicrm-core/pull/17324
// and/or related get merged, then we should remove the REQUEST reference here.
$key = $_POST['qfKey'] ?? $_GET['qfKey'] ?? $_REQUEST['qfKey'] ?? NULL;
- // Allow POST if `$_GET['reset'] == 1` because standalone search actions require a
- // (potentially large) amount of data to the server and must make the page request using POST.
- // See https://lab.civicrm.org/dev/core/-/issues/3222
- if (!$key && (!empty($_GET['reset']) || in_array($_SERVER['REQUEST_METHOD'], ['GET', 'HEAD']))) {
+ if (!$key && in_array($_SERVER['REQUEST_METHOD'], ['GET', 'HEAD'])) {
// Generate a key if this is an initial request without one.
// We allow HEAD here because it is used by bots to validate URLs, so if
// we issue a 500 server error to them they may think the site is broken.
*
* Generated from xml/schema/CRM/Event/Event.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:8e47b6d674b9aa18013e67f27b4b355d)
+ * (GenCodeChecksum:ddad900cfc0f303d651fa7b935157992)
*/
/**
'is_show_location' => [
'name' => 'is_show_location',
'type' => CRM_Utils_Type::T_BOOLEAN,
- 'title' => ts('show location'),
+ 'title' => ts('Show Location'),
'description' => ts('If true, show event location.'),
'required' => TRUE,
'where' => 'civicrm_event.is_show_location',
throw new \API_Exception('Illegal expression');
}
$baoName = CoreUtil::getBAOFromApiName($this->getEntityName());
- $options = $baoName::buildOptions($fieldName, $context);
+ $options = $baoName::buildOptions($fieldName, $context) ?: [];
$this->values[$fieldName] = FormattingUtil::replacePseudoconstant($options, $this->values[$key], TRUE);
unset($this->values[$key]);
}
function civicrm_api3_contribution_repeattransaction($params) {
civicrm_api3_verify_one_mandatory($params, NULL, ['contribution_recur_id', 'original_contribution_id']);
if (empty($params['original_contribution_id'])) {
- // CRM-19873 call with test mode.
- $params['original_contribution_id'] = civicrm_api3('contribution', 'getvalue', [
- 'return' => 'id',
- 'contribution_status_id' => ['IN' => ['Completed']],
- 'contribution_recur_id' => $params['contribution_recur_id'],
- 'contribution_test' => CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_ContributionRecur', $params['contribution_recur_id'], 'is_test'),
- 'options' => ['limit' => 1, 'sort' => 'id DESC'],
- ]);
+ $templateContribution = CRM_Contribute_BAO_ContributionRecur::getTemplateContribution($params['contribution_recur_id']);
+ if (empty($templateContribution)) {
+ throw new CiviCRM_API3_Exception('Contribution.repeattransaction failed to get original_contribution_id for recur with ID: ' . $params['contribution_recur_id']);
+ }
+ $params['original_contribution_id'] = $templateContribution['id'];
}
$contribution = new CRM_Contribute_BAO_Contribution();
$contribution->id = $params['original_contribution_id'];
use Civi\Api4\Query\SqlField;
use Civi\Api4\SearchDisplay;
use Civi\Api4\Utils\CoreUtil;
+use Civi\Api4\Utils\FormattingUtil;
/**
* Base class for running a search.
}
/**
- * @param $column
- * @param $data
+ * @param array $column
+ * @param array $data
* @return array{entity: string, action: string, input_type: string, data_type: string, options: bool, serialize: bool, nullable: bool, fk_entity: string, value_key: string, record: array, value: mixed}|null
*/
private function formatEditableColumn($column, $data) {
$editable['action'] = 'update';
$editable['record'][$editable['id_key']] = $data[$editable['id_path']];
$editable['value'] = $data[$editable['value_path']];
+ // Ensure field is appropriate to this entity sub-type
+ $field = $this->getField($column['key']);
+ $entityValues = FormattingUtil::filterByPrefix($data, $editable['id_path'], $editable['id_key']);
+ if (!$this->fieldBelongsToEntity($editable['entity'], $field['name'], $entityValues)) {
+ return NULL;
+ }
}
// Generate params to create new record, if applicable
elseif ($editable['explicit_join'] && !$this->getJoin($editable['explicit_join'])['bridge']) {
'values' => $editable['record'],
], 0)['access'];
if ($access) {
- \CRM_Utils_Array::remove($editable, 'id_key', 'id_path', 'value_path', 'explicit_join');
+ // Remove info that's for internal use only
+ \CRM_Utils_Array::remove($editable, 'id_key', 'id_path', 'value_path', 'explicit_join', 'grouping_fields');
return $editable;
}
}
return NULL;
}
+ /**
+ * Check if a field is appropriate for this entity type or sub-type.
+ *
+ * For example, the 'first_name' field does not belong to Contacts of type Organization.
+ * And custom data is sometimes limited to specific contact types, event types, case types, etc.
+ *
+ * @param string $entityName
+ * @param string $fieldName
+ * @param array $entityValues
+ * @param bool $checkPermissions
+ * @return bool
+ */
+ private function fieldBelongsToEntity($entityName, $fieldName, $entityValues, $checkPermissions = TRUE) {
+ try {
+ return (bool) civicrm_api4($entityName, 'getFields', [
+ 'checkPermissions' => $checkPermissions,
+ 'where' => [['name', '=', $fieldName]],
+ 'values' => $entityValues,
+ ])->count();
+ }
+ catch (\API_Exception $e) {
+ return FALSE;
+ }
+ }
+
/**
* @param $key
- * @return array{entity: string, input_type: string, data_type: string, options: bool, serialize: bool, nullable: bool, fk_entity: string, value_key: string, value_path: string, id_key: string, id_path: string, explicit_join: string}|null
+ * @return array{entity: string, input_type: string, data_type: string, options: bool, serialize: bool, nullable: bool, fk_entity: string, value_key: string, value_path: string, id_key: string, id_path: string, explicit_join: string, grouping_fields: array}|null
*/
private function getEditableInfo($key) {
+ $result = NULL;
// Strip pseudoconstant suffix
[$key] = explode(':', $key);
$field = $this->getField($key);
}
if ($field) {
$idKey = CoreUtil::getIdFieldName($field['entity']);
- $idPath = ($field['explicit_join'] ? $field['explicit_join'] . '.' : '') . $idKey;
+ $path = ($field['explicit_join'] ? $field['explicit_join'] . '.' : '');
+ $idPath = $path . $idKey;
// Hack to support editing relationships
if ($field['entity'] === 'RelationshipCache') {
$field['entity'] = 'Relationship';
- $idPath = ($field['explicit_join'] ? $field['explicit_join'] . '.' : '') . 'relationship_id';
+ $idPath = $path . 'relationship_id';
}
- return [
+ $result = [
'entity' => $field['entity'],
'input_type' => $field['input_type'],
'data_type' => $field['data_type'],
'id_key' => $idKey,
'id_path' => $idPath,
'explicit_join' => $field['explicit_join'],
+ 'grouping_fields' => [],
];
+ // Grouping fields get added to the query so that contact sub-type and entity type (for custom fields)
+ // are available to filter fields specific to an entity sub-type. See self::fieldBelongsToEntity()
+ if ($field['type'] === 'Custom' || $field['entity'] === 'Contact') {
+ $customInfo = \Civi\Api4\Utils\CoreUtil::getCustomGroupExtends($field['entity']);
+ foreach ((array) ($customInfo['grouping'] ?? []) as $grouping) {
+ $result['grouping_fields'][] = $path . $grouping;
+ }
+ }
}
- return NULL;
+ return $result;
}
/**
if (!empty($column['editable'])) {
$editable = $this->getEditableInfo($column['key']);
if ($editable) {
- $additions[] = $editable['value_path'];
- $additions[] = $editable['id_path'];
+ $additions = array_merge($additions, $editable['grouping_fields'], [$editable['value_path'], $editable['id_path']]);
}
}
// Add style & icon conditions for the column
'icon' => 'fa-file-excel-o',
'crmPopup' => [
'path' => "'civicrm/export/standalone'",
- 'query' => "{reset: 1, entity: '{$entity['name']}'}",
- 'data' => "{id: ids.join(',')}",
+ 'query' => "{reset: 1, entity: '{$entity['name']}', id: ids.join(',')}",
],
];
}
'icon' => $task['icon'] ?? 'fa-gear',
'crmPopup' => [
'path' => "'{$task['url']}'",
- 'query' => "{reset: 1}",
- 'data' => "{cids: ids.join(',')}",
+ 'query' => "{reset: 1, cids: ids.join(',')}",
],
];
}
'icon' => $task['icon'] ?? 'fa-gear',
'crmPopup' => [
'path' => "'{$task['url']}'",
- 'data' => "{id: ids.join(',')}",
+ 'query' => "{id: ids.join(',')}",
],
];
}
if (action.crmPopup) {
var path = $scope.$eval(action.crmPopup.path, data),
query = action.crmPopup.query && $scope.$eval(action.crmPopup.query, data);
- CRM.loadForm(CRM.url(path, query), {post: action.crmPopup.data && $scope.$eval(action.crmPopup.data, data)})
+ CRM.loadForm(CRM.url(path, query))
.on('crmFormSuccess', ctrl.refresh);
}
// If action uses dialogService
$this->assertEquals([1, 2], $data);
}
+ public function testEditableContactFields() {
+ $source = uniqid(__FUNCTION__);
+ $sampleData = [
+ ['contact_type' => 'Individual', 'first_name' => 'One'],
+ ['contact_type' => 'Individual'],
+ ['contact_type' => 'Organization'],
+ ['contact_type' => 'Household'],
+ ];
+ $contact = Contact::save(FALSE)
+ ->addDefault('source', $source)
+ ->setRecords($sampleData)
+ ->execute();
+
+ $params = [
+ 'checkPermissions' => FALSE,
+ 'return' => 'page:1',
+ 'savedSearch' => [
+ 'api_entity' => 'Contact',
+ 'api_params' => [
+ 'version' => 4,
+ 'select' => ['first_name', 'organization_name', 'household_name'],
+ 'where' => [['source', '=', $source]],
+ ],
+ ],
+ 'display' => [
+ 'type' => 'table',
+ 'label' => '',
+ 'settings' => [
+ 'actions' => TRUE,
+ 'pager' => [],
+ 'columns' => [
+ [
+ 'key' => 'first_name',
+ 'label' => 'First',
+ 'dataType' => 'String',
+ 'type' => 'field',
+ 'editable' => TRUE,
+ ],
+ [
+ 'key' => 'organization_name',
+ 'label' => 'First',
+ 'dataType' => 'String',
+ 'type' => 'field',
+ 'editable' => TRUE,
+ ],
+ [
+ 'key' => 'household_name',
+ 'label' => 'First',
+ 'dataType' => 'String',
+ 'type' => 'field',
+ 'editable' => TRUE,
+ ],
+ ],
+ 'sort' => [
+ ['id', 'ASC'],
+ ],
+ ],
+ ],
+ 'afform' => NULL,
+ ];
+
+ $result = civicrm_api4('SearchDisplay', 'run', $params);
+ // First Individual
+ $expectedFirstNameEdit = [
+ 'entity' => 'Contact',
+ 'input_type' => 'Text',
+ 'data_type' => 'String',
+ 'options' => FALSE,
+ 'serialize' => FALSE,
+ 'nullable' => TRUE,
+ 'fk_entity' => NULL,
+ 'value_key' => 'first_name',
+ 'record' => ['id' => $contact[0]['id']],
+ 'action' => 'update',
+ 'value' => 'One',
+ ];
+ // Ensure first_name is editable but not organization_name or household_name
+ $this->assertEquals($expectedFirstNameEdit, $result[0]['columns'][0]['edit']);
+ $this->assertTrue(!isset($result[0]['columns'][1]['edit']));
+ $this->assertTrue(!isset($result[0]['columns'][2]['edit']));
+
+ // Second Individual
+ $expectedFirstNameEdit['record']['id'] = $contact[1]['id'];
+ $expectedFirstNameEdit['value'] = NULL;
+ $this->assertEquals($expectedFirstNameEdit, $result[1]['columns'][0]['edit']);
+ $this->assertTrue(!isset($result[1]['columns'][1]['edit']));
+ $this->assertTrue(!isset($result[1]['columns'][2]['edit']));
+
+ // Third contact: Organization
+ $expectedFirstNameEdit['record']['id'] = $contact[2]['id'];
+ $expectedFirstNameEdit['value_key'] = 'organization_name';
+ $this->assertTrue(!isset($result[2]['columns'][0]['edit']));
+ $this->assertEquals($expectedFirstNameEdit, $result[2]['columns'][1]['edit']);
+ $this->assertTrue(!isset($result[2]['columns'][2]['edit']));
+
+ // Third contact: Household
+ $expectedFirstNameEdit['record']['id'] = $contact[3]['id'];
+ $expectedFirstNameEdit['value_key'] = 'household_name';
+ $this->assertTrue(!isset($result[3]['columns'][0]['edit']));
+ $this->assertTrue(!isset($result[3]['columns'][1]['edit']));
+ $this->assertEquals($expectedFirstNameEdit, $result[3]['columns'][2]['edit']);
+ }
+
}
require_once 'tests/phpunit/api/v4/Custom/CustomTestBase.php';
use api\v4\Custom\CustomTestBase;
+use Civi\Api4\Activity;
+use Civi\Api4\Contact;
use Civi\Api4\CustomField;
use Civi\Api4\CustomGroup;
$this->assertEquals('abc', $result[0]['columns'][3]['val']);
$this->assertEquals($childRel, $result[0]['columns'][3]['edit']['record']['id']);
$this->assertNull($result[0]['columns'][4]['val']);
- // $this->assertArrayNotHasKey('edit', $result[0]['columns'][4]);
+ $this->assertArrayNotHasKey('edit', $result[0]['columns'][4]);
// Second contact has a spouse relation but not a child
$this->assertEquals('s', $result[1]['columns'][1]['val']);
$this->assertEquals('Married', $result[1]['columns'][2]['val']);
$this->assertEquals($spouseRel, $result[1]['columns'][2]['edit']['record']['id']);
$this->assertNull($result[1]['columns'][3]['val']);
- // $this->assertArrayNotHasKey('edit', $result[1]['columns'][3]);
+ $this->assertArrayNotHasKey('edit', $result[1]['columns'][3]);
$this->assertNull($result[1]['columns'][4]['val']);
$this->assertEquals($spouseRel, $result[1]['columns'][4]['edit']['record']['id']);
$this->assertArrayNotHasKey('edit', $result[2]['columns'][4]);
}
+ public function testEditableCustomFields() {
+ $subject = uniqid(__FUNCTION__);
+
+ $contact = Contact::create(FALSE)
+ ->execute()->single();
+
+ // CustomGroup based on Activity Type
+ CustomGroup::create(FALSE)
+ ->addValue('extends', 'Activity')
+ ->addValue('extends_entity_column_value:name', ['Meeting', 'Phone Call'])
+ ->addValue('title', 'meeting_phone')
+ ->addChain('field', CustomField::create()
+ ->addValue('custom_group_id', '$id')
+ ->addValue('label', 'sub_field')
+ ->addValue('html_type', 'Text')
+ )
+ ->execute();
+
+ $sampleData = [
+ ['activity_type_id:name' => 'Meeting', 'meeting_phone.sub_field' => 'Abc'],
+ ['activity_type_id:name' => 'Phone Call'],
+ ['activity_type_id:name' => 'Email'],
+ ];
+ $activity = $this->saveTestRecords('Activity', [
+ 'defaults' => ['subject' => $subject, 'source_contact_id', $contact['id']],
+ 'records' => $sampleData,
+ ]);
+
+ $activityTypes = array_column(
+ Activity::getFields(FALSE)->setLoadOptions(['id', 'name'])->addWhere('name', '=', 'activity_type_id')->execute()->single()['options'],
+ 'id',
+ 'name'
+ );
+
+ $params = [
+ 'checkPermissions' => FALSE,
+ 'return' => 'page:1',
+ 'savedSearch' => [
+ 'api_entity' => 'Activity',
+ 'api_params' => [
+ 'version' => 4,
+ 'select' => ['subject', 'meeting_phone.sub_field'],
+ 'where' => [['subject', '=', $subject]],
+ ],
+ ],
+ 'display' => [
+ 'type' => 'table',
+ 'label' => '',
+ 'settings' => [
+ 'actions' => TRUE,
+ 'pager' => [],
+ 'columns' => [
+ [
+ 'key' => 'subject',
+ 'label' => 'First',
+ 'dataType' => 'String',
+ 'type' => 'field',
+ 'editable' => TRUE,
+ ],
+ [
+ 'key' => 'meeting_phone.sub_field',
+ 'label' => 'First',
+ 'dataType' => 'String',
+ 'type' => 'field',
+ 'editable' => TRUE,
+ ],
+ ],
+ 'sort' => [
+ ['id', 'ASC'],
+ ],
+ ],
+ ],
+ 'afform' => NULL,
+ ];
+
+ $result = civicrm_api4('SearchDisplay', 'run', $params);
+ // Custom field editable
+ $expectedCustomFieldEdit = [
+ 'entity' => 'Activity',
+ 'input_type' => 'Text',
+ 'data_type' => 'String',
+ 'options' => FALSE,
+ 'serialize' => FALSE,
+ 'nullable' => TRUE,
+ 'fk_entity' => NULL,
+ 'value_key' => 'meeting_phone.sub_field',
+ 'record' => ['id' => $activity[0]['id']],
+ 'action' => 'update',
+ 'value' => 'Abc',
+ ];
+ $expectedSubjectEdit = ['value_key' => 'subject', 'value' => $subject] + $expectedCustomFieldEdit;
+
+ // First Activity
+ $this->assertEquals($expectedSubjectEdit, $result[0]['columns'][0]['edit']);
+ $this->assertEquals($expectedCustomFieldEdit, $result[0]['columns'][1]['edit']);
+ $this->assertEquals($activityTypes['Meeting'], $result[0]['data']['activity_type_id']);
+
+ // Second Activity
+ $expectedSubjectEdit['record']['id'] = $activity[1]['id'];
+ $expectedCustomFieldEdit['record']['id'] = $activity[1]['id'];
+ $expectedCustomFieldEdit['value'] = NULL;
+ $this->assertEquals($expectedSubjectEdit, $result[1]['columns'][0]['edit']);
+ $this->assertEquals($expectedCustomFieldEdit, $result[1]['columns'][1]['edit']);
+ $this->assertEquals($activityTypes['Phone Call'], $result[1]['data']['activity_type_id']);
+
+ // Third Activity
+ $expectedSubjectEdit['record']['id'] = $activity[2]['id'];
+ $this->assertEquals($expectedSubjectEdit, $result[2]['columns'][0]['edit']);
+ $this->assertTrue(!isset($result[2]['columns'][1]['edit']));
+ $this->assertEquals($activityTypes['Email'], $result[2]['data']['activity_type_id']);
+ }
+
}
options: {
url: null,
block: true,
- post: null,
crmForm: null
},
_originalContent: null,
return false;
});
},
- _ajax: function(url) {
- if (!this.options.post || !this.isOriginalUrl()) {
- return $.getJSON(url);
- }
- return $.post({
- url: url,
- dataType: 'json',
- data: this.options.post
- });
- },
refresh: function() {
var that = this;
var url = this._formatUrl(this.options.url, 'json');
if (this.options.crmForm) $('form', this.element).ajaxFormUnbind();
if (this.options.block) this.element.block();
- this._ajax(url).then(function(data) {
+ $.getJSON(url, function(data) {
if (data.status === 'redirect') {
that.options.url = data.userContext;
return that.refresh();
$('[name="'+formElement+'"]', that.element).crmError(msg);
});
}
- }, function(data, msg, status) {
+ }).fail(function(data, msg, status) {
that._onFailure(data, status);
});
},
--- /dev/null
+First Name,Last Name,Birth Date,Street Address,City,Postal Code,Country,State,Email,Signature Text,IM,Website,Phone,Phone Ext,Mum’s first name,Last Name,Mum-Street Address,Mum-City,Mum-Country,Mum-State,Mum-email,Mum-signature Test,Mum-IM,Mum-website,Mum-phone,Mum-phone-ext,Mum-home-phone,Mum-home-mobile,Sister-Street Address,Sister-City,Sister-Country,Sister-State,Sister-email,Sister-signature Test,Sister-IM,Sister-website,Sister-phone,Sister-phone-ext,Team,Team Website,Team email ,Team Reference,Team Address1,Team Address 2,Team address Validity Date,Team Backup Website
+Susie,Jones,2002-01-08,24 Adelaide Road,Sydney,90210,Australia,NSW,susie@example.com,Regards,susiej,https://susie.example.com,999-4445,123,Mum,Jones,The Green House,,,,mum@example.com,,Mum-IM,http://mum.example.com,911,1,88-999,99-888,,,New Zealand,,sis@example.com,,,,555-666,,Soccer Superstars,https://super.example.org,tt@example.org,T-882,PO Box 999,Marion Square,2022-03-04,http://super-t.example.org
use Civi\Api4\Address;
use Civi\Api4\Contact;
use Civi\Api4\ContactType;
+use Civi\Api4\LocationType;
use Civi\Api4\RelationshipType;
use Civi\Api4\UserJob;
];
}
+ /**
+ * Test location importing, including for related contacts.
+ *
+ * @throws \CRM_Core_Exception
+ * @throws \API_Exception
+ */
+ public function testImportLocations(): void {
+ $csv = 'individual_locations_with_related.csv';
+ $relationships = (array) RelationshipType::get()->addSelect('name_a_b', 'id')->addWhere('name_a_b', 'IN', [
+ 'Child of',
+ 'Sibling of',
+ 'Employee of',
+ ])->execute()->indexBy('name_a_b');
+
+ $childKey = $relationships['Child of']['id'] . '_a_b';
+ $siblingKey = $relationships['Sibling of']['id'] . '_a_b';
+ $employeeKey = $relationships['Employee of']['id'] . '_a_b';
+ $locations = LocationType::get()->execute()->indexBy('name');
+ $phoneTypeID = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Phone', 'phone_type_id', 'Phone');
+ $mobileTypeID = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Phone', 'phone_type_id', 'Mobile');
+ $skypeTypeID = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_IM', 'provider_id', 'Skype');
+ $mainWebsiteTypeID = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Website', 'website_type_id', 'Main');
+ $linkedInTypeID = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Website', 'website_type_id', 'LinkedIn');
+ $homeID = $locations['Home']['id'];
+ $workID = $locations['Work']['id'];
+ $mapper = [
+ ['first_name'],
+ ['last_name'],
+ ['birth_date'],
+ ['street_address', $homeID],
+ ['city', $homeID],
+ ['postal_code', $homeID],
+ ['country', $homeID],
+ ['state_province', $homeID],
+ // No location type ID means 'Primary'
+ ['email'],
+ ['signature_text'],
+ ['im', NULL, $skypeTypeID],
+ ['url', $mainWebsiteTypeID],
+ ['phone', $homeID, $phoneTypeID],
+ ['phone_ext', $homeID, $phoneTypeID],
+ [$childKey, 'first_name'],
+ [$childKey, 'last_name'],
+ [$childKey, 'street_address'],
+ [$childKey, 'city'],
+ [$childKey, 'country'],
+ [$childKey, 'state_province'],
+ [$childKey, 'email', $homeID],
+ [$childKey, 'signature_text', $homeID],
+ [$childKey, 'im', $homeID, $skypeTypeID],
+ [$childKey, 'url', $linkedInTypeID],
+ // Same location type, different phone typ in these phones
+ [$childKey, 'phone', $homeID, $phoneTypeID],
+ [$childKey, 'phone_ext', $homeID, $phoneTypeID],
+ [$childKey, 'phone', $homeID, $mobileTypeID],
+ [$childKey, 'phone_ext', $homeID, $mobileTypeID],
+ [$siblingKey, 'street_address', $homeID],
+ [$siblingKey, 'city', $homeID],
+ [$siblingKey, 'country', $homeID],
+ [$siblingKey, 'state_province', $homeID],
+ [$siblingKey, 'email', $homeID],
+ [$siblingKey, 'signature_text', $homeID],
+ [$childKey, 'im', $homeID, $skypeTypeID],
+ // The 2 is website_type_id (yes, small hard-coding cheat)
+ [$siblingKey, 'url', $linkedInTypeID],
+ [$siblingKey, 'phone', $workID, $phoneTypeID],
+ [$siblingKey, 'phone_ext', $workID, $phoneTypeID],
+ [$employeeKey, 'organization_name'],
+ [$employeeKey, 'url', $mainWebsiteTypeID],
+ [$employeeKey, 'email', $homeID],
+ [$employeeKey, 'do_not_import'],
+ [$employeeKey, 'street_address', $homeID],
+ [$employeeKey, 'supplemental_address_1', $homeID],
+ [$employeeKey, 'do_not_import'],
+ // Second website, different type.
+ [$employeeKey, 'url', $linkedInTypeID],
+ ];
+ $this->validateCSV($csv, $mapper);
+ }
+
/**
* Test that setting duplicate action to fill doesn't blow away data
* that exists, but does fill in where it's empty.
* Any submitted values overrides.
*
* @throws \API_Exception
- * @throws \CRM_Core_Exception
*/
- protected function validateCSV(string $csv, array $mapper, $submittedValues): void {
+ protected function validateCSV(string $csv, array $mapper, array $submittedValues = []): void {
[$dataSource, $parser] = $this->getDataSourceAndParser($csv, $mapper, $submittedValues);
$parser->validateValues(array_values($dataSource->getRow()));
}
'mapper' => $mapper,
'dataSource' => 'CRM_Import_DataSource_CSV',
'file' => ['name' => $csv],
+ 'dateFormats' => CRM_Core_Form_Date::DATE_yyyy_mm_dd,
'onDuplicate' => CRM_Import_Parser::DUPLICATE_UPDATE,
'groups' => [],
], $submittedValues);
+++ /dev/null
-,eileen,eileen-laptop,20.05.2022 17:00,file:///home/eileen/.config/libreoffice/4;
\ No newline at end of file
$this->assertEquals($contributionRecur['values'][1]['is_test'], $repeatedContribution['values'][2]['is_test']);
}
+ /**
+ * Test repeat contribution accepts recur_id instead of
+ * original_contribution_id.
+ *
+ * @throws \CRM_Core_Exception
+ */
+ public function testRepeatTransactionPreviousContributionRefunded(): void {
+ $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', [
+ 'contact_id' => $this->_individualId,
+ 'installments' => '12',
+ 'frequency_interval' => '1',
+ 'amount' => '100',
+ 'contribution_status_id' => 1,
+ 'start_date' => '2012-01-01 00:00:00',
+ 'currency' => 'USD',
+ 'frequency_unit' => 'month',
+ 'payment_processor_id' => $this->paymentProcessorID,
+ ]);
+ $this->callAPISuccess('contribution', 'create', array_merge(
+ $this->_params,
+ [
+ 'contribution_recur_id' => $contributionRecur['id'],
+ 'contribution_status_id' => 'Refunded',
+ ]
+ )
+ );
+
+ $this->callAPISuccess('contribution', 'repeattransaction', [
+ 'contribution_recur_id' => $contributionRecur['id'],
+ 'trxn_id' => 1234,
+ ]);
+ $contributions = $this->callAPISuccess('contribution', 'get', [
+ 'contribution_recur_id' => $contributionRecur['id'],
+ 'sequential' => 1,
+ ]);
+ // We should have contribution 0 in "Refunded" status and contribution 1 in "Pending" status
+ $this->assertEquals(2, $contributions['count']);
+ $this->assertEquals(7, $contributions['values'][0]['contribution_status_id']);
+ $this->assertEquals(2, $contributions['values'][1]['contribution_status_id']);
+ }
+
/**
* CRM-19945 Tests that Contribute.repeattransaction renews a membership when contribution status=Completed
*
<name>is_show_location</name>
<type>boolean</type>
<required>true</required>
- <title>show location</title>
+ <title>Show Location</title>
<default>1</default>
<comment>If true, show event location.</comment>
<add>1.7</add>