[Import] Fix Contribution Import mapping fields to use labels
[civicrm-core.git] / CRM / Contact / Page / AJAX.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 */
17
18/**
19 * This class contains all contact related functions that are called using AJAX (jQuery)
20 */
21class CRM_Contact_Page_AJAX {
272081ca
TO
22 /**
23 * When a user chooses a username, CHECK_USERNAME_TTL
24 * is the time window in which they can check usernames
25 * (without reloading the overall form).
26 */
69078420
SL
27 // 3hr; 3*60*60
28 const CHECK_USERNAME_TTL = 10800;
272081ca 29
69078420
SL
30 // 6hr; 6*60*60
31 const AUTOCOMPLETE_TTL = 21600;
b1dba111 32
06508628 33 /**
06508628
CW
34 * Ajax callback for custom fields of type ContactReference
35 *
36 * Todo: Migrate contact reference fields to use EntityRef
37 */
00be9182 38 public static function contactReference() {
9c1bc317 39 $name = $_GET['term'] ?? NULL;
6a488035
TO
40 $name = CRM_Utils_Type::escape($name, 'String');
41 $cfID = CRM_Utils_Type::escape($_GET['id'], 'Positive');
42
43 // check that this is a valid, active custom field of Contact Reference type
be2fb01f
CW
44 $params = ['id' => $cfID];
45 $returnProperties = ['filter', 'data_type', 'is_active'];
46 $cf = [];
6a488035 47 CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_CustomField', $params, $cf, $returnProperties);
a4cce21a 48 if (!$cf['id'] || !$cf['is_active'] || $cf['data_type'] != 'ContactReference') {
d8cdb7e5 49 CRM_Utils_System::civiExit(1);
6a488035
TO
50 }
51
9624538c 52 if (!empty($cf['filter'])) {
be2fb01f 53 $filterParams = [];
6a488035
TO
54 parse_str($cf['filter'], $filterParams);
55
9c1bc317 56 $action = $filterParams['action'] ?? NULL;
be2fb01f 57 if (!empty($action) && !in_array($action, ['get', 'lookup'])) {
d8cdb7e5 58 CRM_Utils_System::civiExit(1);
6a488035 59 }
da0136df 60
61 if (!empty($filterParams['group'])) {
62 $filterParams['group'] = explode(',', $filterParams['group']);
63 }
6a488035
TO
64 }
65
66 $list = array_keys(CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
353ffa53
TO
67 'contact_reference_options'
68 ), '1');
6a488035 69
be2fb01f 70 $return = array_unique(array_merge(['sort_name'], $list));
6a488035 71
89595c92 72 $limit = Civi::settings()->get('search_autocomplete_count');
6a488035 73
be2fb01f 74 $params = ['offset' => 0, 'rowCount' => $limit, 'version' => 3];
6a488035
TO
75 foreach ($return as $fld) {
76 $params["return.{$fld}"] = 1;
77 }
78
79 if (!empty($action)) {
be2fb01f 80 $excludeGet = [
353ffa53
TO
81 'reset',
82 'key',
83 'className',
84 'fnName',
85 'json',
86 'reset',
87 'context',
88 'timestamp',
89 'limit',
90 'id',
91 's',
92 'q',
408b79bf 93 'action',
be2fb01f 94 ];
6a488035
TO
95 foreach ($_GET as $param => $val) {
96 if (empty($val) ||
97 in_array($param, $excludeGet) ||
98 strpos($param, 'return.') !== FALSE ||
99 strpos($param, 'api.') !== FALSE
100 ) {
101 continue;
102 }
103 $params[$param] = $val;
104 }
105 }
106
107 if ($name) {
108 $params['sort_name'] = $name;
109 }
110
111 $params['sort'] = 'sort_name';
112
113 // tell api to skip permission chk. dgg
114 $params['check_permissions'] = 0;
115
116 // add filter variable to params
117 if (!empty($filterParams)) {
118 $params = array_merge($params, $filterParams);
119 }
120
121 $contact = civicrm_api('Contact', 'Get', $params);
122
a7488080 123 if (!empty($contact['is_error'])) {
d8cdb7e5 124 CRM_Utils_System::civiExit(1);
6a488035
TO
125 }
126
be2fb01f 127 $contactList = [];
6a488035 128 foreach ($contact['values'] as $value) {
be2fb01f 129 $view = [];
6a488035 130 foreach ($return as $fld) {
a7488080 131 if (!empty($value[$fld])) {
6a488035
TO
132 $view[] = $value[$fld];
133 }
134 }
be2fb01f 135 $contactList[] = ['id' => $value['id'], 'text' => implode(' :: ', $view)];
6a488035
TO
136 }
137
da0136df 138 if (!empty($_GET['is_unit_test'])) {
139 return $contactList;
140 }
8be1a839 141 CRM_Utils_JSON::output($contactList);
6a488035
TO
142 }
143
144 /**
100fef9d 145 * Fetch PCP ID by PCP Supporter sort_name, also displays PCP title and associated Contribution Page title
6a488035 146 */
00be9182 147 public static function getPCPList() {
9c1bc317 148 $name = $_GET['term'] ?? NULL;
353ffa53 149 $name = CRM_Utils_Type::escape($name, 'String');
89595c92 150 $limit = $max = Civi::settings()->get('search_autocomplete_count');
6a488035
TO
151
152 $where = ' AND pcp.page_id = cp.id AND pcp.contact_id = cc.id';
153
154 $config = CRM_Core_Config::singleton();
155 if ($config->includeWildCardInName) {
156 $strSearch = "%$name%";
157 }
158 else {
159 $strSearch = "$name%";
160 }
161 $includeEmailFrom = $includeNickName = '';
162 if ($config->includeNickNameInName) {
163 $includeNickName = " OR nick_name LIKE '$strSearch'";
164 }
165 if ($config->includeEmailInName) {
166 $includeEmailFrom = "LEFT JOIN civicrm_email eml ON ( cc.id = eml.contact_id AND eml.is_primary = 1 )";
167 $whereClause = " WHERE ( email LIKE '$strSearch' OR sort_name LIKE '$strSearch' $includeNickName ) {$where} ";
168 }
169 else {
170 $whereClause = " WHERE ( sort_name LIKE '$strSearch' $includeNickName ) {$where} ";
171 }
172
8da35492 173 $offset = $count = 0;
4feb1cad
CW
174 if (!empty($_GET['page_num'])) {
175 $page = (int) $_GET['page_num'];
8da35492
CW
176 $offset = $limit * ($page - 1);
177 $limit++;
6a488035
TO
178 }
179
180 $select = 'cc.sort_name, pcp.title, cp.title';
181 $query = "
182 SELECT id, data
183 FROM (
184 SELECT pcp.id as id, CONCAT_WS( ' :: ', {$select} ) as data, sort_name
185 FROM civicrm_pcp pcp, civicrm_contribution_page cp, civicrm_contact cc
186 {$includeEmailFrom}
900e6829 187 {$whereClause} AND pcp.page_type = 'contribute'
188 UNION ALL
189 SELECT pcp.id as id, CONCAT_WS( ' :: ', {$select} ) as data, sort_name
190 FROM civicrm_pcp pcp, civicrm_event cp, civicrm_contact cc
191 {$includeEmailFrom}
192 {$whereClause} AND pcp.page_type = 'event'
6a488035
TO
193 ) t
194 ORDER BY sort_name
8da35492 195 LIMIT $offset, $limit
6a488035
TO
196 ";
197
198 $dao = CRM_Core_DAO::executeQuery($query);
be2fb01f 199 $output = ['results' => [], 'more' => FALSE];
6a488035 200 while ($dao->fetch()) {
8da35492
CW
201 if (++$count > $max) {
202 $output['more'] = TRUE;
203 }
204 else {
be2fb01f 205 $output['results'][] = ['id' => $dao->id, 'text' => $dao->data];
8da35492 206 }
6a488035 207 }
8da35492 208 CRM_Utils_JSON::output($output);
6a488035
TO
209 }
210
00be9182 211 public static function relationship() {
3b1c37fe 212 $relType = CRM_Utils_Request::retrieve('rel_type', 'String', CRM_Core_DAO::$_nullObject, TRUE);
c91df8b4 213 $relContactID = CRM_Utils_Request::retrieve('rel_contact', 'Positive', CRM_Core_DAO::$_nullObject, TRUE);
a3d827a7
CW
214 $originalCid = CRM_Utils_Request::retrieve('cid', 'Positive');
215 $relationshipID = CRM_Utils_Request::retrieve('rel_id', 'Positive');
c91df8b4 216 $caseID = CRM_Utils_Request::retrieve('case_id', 'Positive', CRM_Core_DAO::$_nullObject, TRUE);
6a488035 217
3b1c37fe
CW
218 if (!CRM_Case_BAO_Case::accessCase($caseID)) {
219 CRM_Utils_System::permissionDenied();
220 }
6a488035 221
be2fb01f 222 $ret = ['is_error' => 0];
c91df8b4 223
3b1c37fe 224 list($relTypeId, $b, $a) = explode('_', $relType);
14a679f1 225
3b1c37fe
CW
226 if ($relationshipID && $originalCid) {
227 CRM_Case_BAO_Case::endCaseRole($caseID, $a, $originalCid, $relTypeId);
228 }
14a679f1 229
3b1c37fe 230 $clientList = CRM_Case_BAO_Case::getCaseClients($caseID);
14a679f1 231
3b1c37fe
CW
232 // Loop through multiple case clients
233 foreach ($clientList as $i => $sourceContactID) {
bb76ee5a 234 try {
a0fdfb65 235 $params = [
bb76ee5a
FG
236 'case_id' => $caseID,
237 'relationship_type_id' => $relTypeId,
238 "contact_id_$a" => $relContactID,
239 "contact_id_$b" => $sourceContactID,
a0fdfb65
MD
240 'sequential' => TRUE,
241 ];
242 // first check if there is any existing relationship present with same parameters.
243 // If yes then update the relationship by setting active and start date to current time
244 $relationship = civicrm_api3('Relationship', 'get', $params)['values'];
245 $params = array_merge(CRM_Utils_Array::value(0, $relationship, $params), [
bb76ee5a 246 'start_date' => 'now',
a0fdfb65
MD
247 'is_active' => TRUE,
248 'end_date' => '',
249 ]);
250 $result = civicrm_api3('relationship', 'create', $params);
bb76ee5a
FG
251 }
252 catch (CiviCRM_API3_Exception $e) {
253 $ret['is_error'] = 1;
254 $ret['error_message'] = $e->getMessage();
255 }
3b1c37fe
CW
256 // Save activity only for the primary (first) client
257 if ($i == 0 && empty($result['is_error'])) {
3e4eb9aa 258 CRM_Case_BAO_Case::createCaseRoleActivity($caseID, $result['id'], $relContactID, $sourceContactID);
c91df8b4 259 }
6a488035 260 }
3e4eb9aa
JP
261 if (!empty($_REQUEST['is_unit_test'])) {
262 return $ret;
263 }
6a488035 264
ecdef330 265 CRM_Utils_JSON::output($ret);
6a488035
TO
266 }
267
268 /**
fe482240 269 * Fetch the custom field help.
6a488035 270 */
00be9182 271 public static function customField() {
353ffa53 272 $fieldId = CRM_Utils_Type::escape($_REQUEST['id'], 'Integer');
be2fb01f
CW
273 $params = ['id' => $fieldId];
274 $returnProperties = ['help_pre', 'help_post'];
275 $values = [];
6a488035
TO
276
277 CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_CustomField', $params, $values, $returnProperties);
ecdef330 278 CRM_Utils_JSON::output($values);
6a488035
TO
279 }
280
00be9182 281 public static function groupTree() {
d42a224c 282 CRM_Utils_System::setHttpHeader('Content-Type', 'application/json');
6a488035
TO
283 $gids = CRM_Utils_Type::escape($_GET['gids'], 'String');
284 echo CRM_Contact_BAO_GroupNestingCache::json($gids);
285 CRM_Utils_System::civiExit();
286 }
287
6a488035 288 /**
fe482240 289 * Delete custom value.
6a488035 290 */
00be9182 291 public static function deleteCustomValue() {
d42a224c 292 CRM_Utils_System::setHttpHeader('Content-Type', 'text/plain');
6a488035
TO
293 $customValueID = CRM_Utils_Type::escape($_REQUEST['valueID'], 'Positive');
294 $customGroupID = CRM_Utils_Type::escape($_REQUEST['groupID'], 'Positive');
a3d827a7 295 $contactId = CRM_Utils_Request::retrieve('contactId', 'Positive');
317103ab
CW
296 if (!CRM_Core_BAO_CustomGroup::checkGroupAccess($customGroupID, CRM_Core_Permission::EDIT) ||
297 !CRM_Contact_BAO_Contact_Permission::allow($contactId, CRM_Core_Permission::EDIT)
298 ) {
299 CRM_Utils_System::permissionDenied();
300 }
6a488035 301 CRM_Core_BAO_CustomValue::deleteCustomValue($customValueID, $customGroupID);
a4cce21a 302 if ($contactId) {
7fa9167d 303 echo CRM_Contact_BAO_Contact::getCountComponent('custom_' . $customGroupID, $contactId);
6a488035
TO
304 }
305
2b68a50c 306 CRM_Contact_BAO_GroupContactCache::opportunisticCacheFlush();
6a488035
TO
307 CRM_Utils_System::civiExit();
308 }
309
6a488035 310 /**
fe482240 311 * check the CMS username.
ce80b209 312 */
69078420 313 public static function checkUserName() {
be2fb01f 314 $signer = new CRM_Utils_Signer(CRM_Core_Key::privateKey(), ['for', 'ts']);
a3d827a7
CW
315 $sig = CRM_Utils_Request::retrieve('sig', 'String');
316 $for = CRM_Utils_Request::retrieve('for', 'String');
272081ca
TO
317 if (
318 CRM_Utils_Time::getTimeRaw() > $_REQUEST['ts'] + self::CHECK_USERNAME_TTL
7fa9167d 319 || $for != 'civicrm/ajax/cmsuser'
320 || !$signer->validate($sig, $_REQUEST)
272081ca 321 ) {
be2fb01f 322 $user = ['name' => 'error'];
8be1a839 323 CRM_Utils_JSON::output($user);
272081ca
TO
324 }
325
6a488035 326 $config = CRM_Core_Config::singleton();
4c68cf7b 327 $username = trim(CRM_Utils_Array::value('cms_name', $_REQUEST));
6a488035 328
be2fb01f 329 $params = ['name' => $username];
6a488035 330
be2fb01f 331 $errors = [];
6a488035
TO
332 $config->userSystem->checkUserNameEmailExists($params, $errors);
333
334 if (isset($errors['cms_name']) || isset($errors['name'])) {
b44e3f84 335 //user name is not available
be2fb01f 336 $user = ['name' => 'no'];
8be1a839 337 CRM_Utils_JSON::output($user);
6a488035
TO
338 }
339 else {
340 //user name is available
be2fb01f 341 $user = ['name' => 'yes'];
8be1a839 342 CRM_Utils_JSON::output($user);
6a488035 343 }
8be1a839
TO
344
345 // Not reachable: JSON::output() above exits.
6a488035
TO
346 CRM_Utils_System::civiExit();
347 }
348
349 /**
fe482240 350 * Function to get email address of a contact.
6a488035 351 */
00be9182 352 public static function getContactEmail() {
3c0dc533 353 $queryStrings = [];
9c1bc317 354 $name = $_GET['name'] ?? NULL;
3c0dc533 355 if ($name) {
356 $name = CRM_Utils_Type::escape($name, 'String');
357 $wildCard = Civi::settings()->get('includeWildCardInName') ? '%' : '';
358 foreach (['cc.sort_name', 'ce.email'] as $column) {
359 $queryStrings[] = "{$column} LIKE '{$wildCard}{$name}%'";
8c0ea1d7 360 }
3c0dc533 361 $result = [];
362 $rowCount = Civi::settings()->get('search_autocomplete_count');
effdc2d9 363
3c0dc533 364 // add acl clause here
365 list($aclFrom, $aclWhere) = CRM_Contact_BAO_Contact_Permission::cacheClause('cc');
366 if ($aclWhere) {
367 $aclWhere = "AND {$aclWhere}";
6a488035 368 }
3c0dc533 369 foreach ($queryStrings as &$queryString) {
370 $queryString = "(
6a488035
TO
371SELECT sort_name name, ce.email, cc.id
372FROM civicrm_email ce INNER JOIN civicrm_contact cc ON cc.id = ce.contact_id
373 {$aclFrom}
374WHERE ce.on_hold = 0 AND cc.is_deceased = 0 AND cc.do_not_email = 0 AND {$queryString}
375 {$aclWhere}
3c0dc533 376LIMIT {$rowCount}
377)";
378 }
379 $query = implode(' UNION ', $queryStrings) . " LIMIT {$rowCount}";
380
381 // send query to hook to be modified if needed
382 CRM_Utils_Hook::contactListQuery($query,
383 $name,
384 CRM_Utils_Request::retrieve('context', 'Alphanumeric'),
385 CRM_Utils_Request::retrieve('cid', 'Positive')
386 );
387
388 $dao = CRM_Core_DAO::executeQuery($query);
389
390 while ($dao->fetch()) {
391 //working here
392 $result[] = [
393 'text' => '"' . $dao->name . '" <' . $dao->email . '>',
394 'id' => (CRM_Utils_Array::value('id', $_GET)) ? "{$dao->id}::{$dao->email}" : '"' . $dao->name . '" <' . $dao->email . '>',
395 ];
6a488035 396 }
3c0dc533 397 CRM_Utils_JSON::output($result);
6a488035
TO
398 }
399 CRM_Utils_System::civiExit();
400 }
401
00be9182 402 public static function getContactPhone() {
6a488035
TO
403
404 $queryString = NULL;
3ae9c7d6 405 $sqlParmas = [];
6a488035
TO
406 //check for mobile type
407 $phoneTypes = CRM_Core_OptionGroup::values('phone_type', TRUE, FALSE, FALSE, NULL, 'name');
9c1bc317 408 $mobileType = $phoneTypes['Mobile'] ?? NULL;
6a488035 409
3ae9c7d6 410 $name = CRM_Utils_Request::retrieveValue('name', 'String', NULL, FALSE, 'GET');
a4cce21a 411 if ($name) {
3ae9c7d6
SL
412 $key = (int) count(array_keys($sqlParmas)) + 1;
413 $queryString = " ( cc.sort_name LIKE %{$key} OR cp.phone LIKE %{$key} ) ";
414 $sqlParams[$key] = ['%' . $name . '%', 'String'];
6a488035 415 }
a4cce21a 416 else {
3ae9c7d6 417 $cid = CRM_Utils_Request::retrieveValue('cid', 'CommaSeparatedIntegers', NULL, FALSE, 'GET');
d75f2f47 418 if ($cid) {
a4cce21a 419 $queryString = " cc.id IN ( $cid )";
6a488035 420 }
6a488035
TO
421 }
422
423 if ($queryString) {
be2fb01f 424 $result = [];
3ae9c7d6
SL
425 $offset = (int) CRM_Utils_Request::retrieveValue('offset', 'Integer', 0, FALSE, 'GET');
426 $rowCount = (int) CRM_Utils_Request::retrieveValue('rowcount', 'Integer', 20, FALSE, 'GET');
bf00d1b6 427
6a488035
TO
428 // add acl clause here
429 list($aclFrom, $aclWhere) = CRM_Contact_BAO_Contact_Permission::cacheClause('cc');
430 if ($aclWhere) {
431 $aclWhere = " AND $aclWhere";
432 }
433
434 $query = "
435SELECT sort_name name, cp.phone, cc.id
436FROM civicrm_phone cp INNER JOIN civicrm_contact cc ON cc.id = cp.contact_id
437 {$aclFrom}
438WHERE cc.is_deceased = 0 AND cc.do_not_sms = 0 AND cp.phone_type_id = {$mobileType} AND {$queryString}
439 {$aclWhere}
440LIMIT {$offset}, {$rowCount}
441";
442
443 // send query to hook to be modified if needed
444 CRM_Utils_Hook::contactListQuery($query,
445 $name,
edc80cda 446 CRM_Utils_Request::retrieve('context', 'Alphanumeric'),
a3d827a7 447 CRM_Utils_Request::retrieve('cid', 'Positive')
6a488035
TO
448 );
449
3ae9c7d6 450 $dao = CRM_Core_DAO::executeQuery($query, $sqlParams);
6a488035
TO
451
452 while ($dao->fetch()) {
be2fb01f 453 $result[] = [
b792e485 454 'text' => '"' . $dao->name . '" (' . $dao->phone . ')',
6a488035 455 'id' => (CRM_Utils_Array::value('id', $_GET)) ? "{$dao->id}::{$dao->phone}" : '"' . $dao->name . '" <' . $dao->phone . '>',
be2fb01f 456 ];
6a488035 457 }
8be1a839 458 CRM_Utils_JSON::output($result);
6a488035
TO
459 }
460 CRM_Utils_System::civiExit();
461 }
462
00be9182 463 public static function buildSubTypes() {
a3d827a7 464 $parent = CRM_Utils_Request::retrieve('parentId', 'Positive');
6a488035
TO
465
466 switch ($parent) {
467 case 1:
468 $contactType = 'Individual';
469 break;
470
471 case 2:
472 $contactType = 'Household';
473 break;
474
475 case 4:
476 $contactType = 'Organization';
477 break;
478 }
479
480 $subTypes = CRM_Contact_BAO_ContactType::subTypePairs($contactType, FALSE, NULL);
481 asort($subTypes);
ecdef330 482 CRM_Utils_JSON::output($subTypes);
6a488035
TO
483 }
484
00be9182 485 public static function buildDedupeRules() {
a3d827a7 486 $parent = CRM_Utils_Request::retrieve('parentId', 'Positive');
6a488035
TO
487
488 switch ($parent) {
489 case 1:
490 $contactType = 'Individual';
491 break;
492
493 case 2:
494 $contactType = 'Household';
495 break;
496
497 case 4:
498 $contactType = 'Organization';
499 break;
500 }
501
61194d45 502 $dedupeRules = CRM_Dedupe_BAO_DedupeRuleGroup::getByType($contactType);
6a488035 503
ecdef330 504 CRM_Utils_JSON::output($dedupeRules);
6a488035
TO
505 }
506
6a488035 507 /**
fe482240 508 * Retrieve signature based on email id.
6a488035 509 */
00be9182 510 public static function getSignature() {
6a488035 511 $emailID = CRM_Utils_Type::escape($_REQUEST['emailID'], 'Positive');
353ffa53
TO
512 $query = "SELECT signature_text, signature_html FROM civicrm_email WHERE id = {$emailID}";
513 $dao = CRM_Core_DAO::executeQuery($query);
6a488035 514
be2fb01f 515 $signatures = [];
6a488035 516 while ($dao->fetch()) {
be2fb01f 517 $signatures = [
6a488035
TO
518 'signature_text' => $dao->signature_text,
519 'signature_html' => $dao->signature_html,
be2fb01f 520 ];
6a488035
TO
521 }
522
ecdef330 523 CRM_Utils_JSON::output($signatures);
6a488035
TO
524 }
525
526 /**
100fef9d 527 * Process dupes.
6a488035 528 */
00be9182 529 public static function processDupes() {
6a488035 530 $oper = CRM_Utils_Type::escape($_REQUEST['op'], 'String');
353ffa53
TO
531 $cid = CRM_Utils_Type::escape($_REQUEST['cid'], 'Positive');
532 $oid = CRM_Utils_Type::escape($_REQUEST['oid'], 'Positive');
6a488035
TO
533
534 if (!$oper || !$cid || !$oid) {
535 return;
536 }
537
2d1fefa0 538 $status = self::markNonDuplicates($cid, $oid, $oper);
6a488035 539
be2fb01f 540 CRM_Utils_JSON::output(['status' => ($status) ? $oper : $status]);
6a488035
TO
541 }
542
183ec330 543 /**
544 * Retrieve list of duplicate pairs from cache table.
545 */
00be9182 546 public static function getDedupes() {
63ef778e 547 $offset = isset($_REQUEST['start']) ? CRM_Utils_Type::escape($_REQUEST['start'], 'Integer') : 0;
548 $rowCount = isset($_REQUEST['length']) ? CRM_Utils_Type::escape($_REQUEST['length'], 'Integer') : 25;
6a488035 549
dc6285d5 550 $gid = CRM_Utils_Request::retrieve('gid', 'Positive');
551 $rgid = CRM_Utils_Request::retrieve('rgid', 'Positive');
997a03fe 552 $limit = CRM_Utils_Request::retrieveValue('limit', 'Positive', 0);
9f54f049 553 $null = NULL;
554 $criteria = CRM_Utils_Request::retrieve('criteria', 'Json', $null, FALSE, '{}');
bb22928b 555 $selected = CRM_Utils_Request::retrieveValue('selected', 'Boolean');
63ef778e 556 if ($rowCount < 0) {
557 $rowCount = 0;
558 }
6a488035 559
1f51ef4e 560 $whereClause = $orderByClause = '';
997a03fe 561 $cacheKeyString = CRM_Dedupe_Merger::getMergeCacheKeyString($rgid, $gid, json_decode($criteria, TRUE), TRUE, $limit);
9f54f049 562
69078420 563 $searchRows = [];
6a488035 564
ed92673b 565 $searchParams = self::getSearchOptionsFromRequest();
be2fb01f 566 $queryParams = [];
ed92673b 567
63ef778e 568 $join = '';
be2fb01f 569 $where = [];
579ea9bc 570
ed92673b 571 $isOrQuery = self::isOrQuery();
572
573 $nextParamKey = 3;
be2fb01f 574 $mappings = [
e67dcaf8 575 'dst' => 'cc1.display_name',
576 'src' => 'cc2.display_name',
577 'dst_email' => 'ce1.email',
578 'src_email' => 'ce2.email',
579 'dst_postcode' => 'ca1.postal_code',
580 'src_postcode' => 'ca2.postal_code',
c8f4facc 581 'dst_street' => 'ca1.street_address',
582 'src_street' => 'ca2.street_address',
be2fb01f 583 ];
ed92673b 584
585 foreach ($mappings as $key => $dbName) {
586 if (!empty($searchParams[$key])) {
12af7add 587 // CRM-18694.
588 $wildcard = strstr($key, 'postcode') ? '' : '%';
be2fb01f 589 $queryParams[$nextParamKey] = [$wildcard . $searchParams[$key] . '%', 'String'];
ed92673b 590 $where[] = $dbName . " LIKE %{$nextParamKey} ";
591 $nextParamKey++;
592 }
63ef778e 593 }
ed92673b 594
595 if ($isOrQuery) {
69078420 596 $whereClause = ' ( ' . implode(' OR ', $where) . ' ) ';
63ef778e 597 }
598 else {
599 if (!empty($where)) {
69078420 600 $whereClause = implode(' AND ', $where);
63ef778e 601 }
f931b74c 602 }
63ef778e 603 $whereClause .= $whereClause ? ' AND de.id IS NULL' : ' de.id IS NULL';
6a488035 604
63ef778e 605 if ($selected) {
606 $whereClause .= ' AND pn.is_selected = 1';
607 }
2988f5c7 608 $join .= CRM_Dedupe_Merger::getJoinOnDedupeTable();
63ef778e 609
be2fb01f 610 $select = [
e67dcaf8 611 'cc1.contact_type' => 'dst_contact_type',
612 'cc1.display_name' => 'dst_display_name',
613 'cc1.contact_sub_type' => 'dst_contact_sub_type',
614 'cc2.contact_type' => 'src_contact_type',
615 'cc2.display_name' => 'src_display_name',
616 'cc2.contact_sub_type' => 'src_contact_sub_type',
617 'ce1.email' => 'dst_email',
618 'ce2.email' => 'src_email',
619 'ca1.postal_code' => 'dst_postcode',
620 'ca2.postal_code' => 'src_postcode',
621 'ca1.street_address' => 'dst_street',
622 'ca2.street_address' => 'src_street',
be2fb01f 623 ];
63ef778e 624
f931b74c 625 if ($select) {
63ef778e 626 $join .= " INNER JOIN civicrm_contact cc1 ON cc1.id = pn.entity_id1";
627 $join .= " INNER JOIN civicrm_contact cc2 ON cc2.id = pn.entity_id2";
628 $join .= " LEFT JOIN civicrm_email ce1 ON (ce1.contact_id = pn.entity_id1 AND ce1.is_primary = 1 )";
629 $join .= " LEFT JOIN civicrm_email ce2 ON (ce2.contact_id = pn.entity_id2 AND ce2.is_primary = 1 )";
630 $join .= " LEFT JOIN civicrm_address ca1 ON (ca1.contact_id = pn.entity_id1 AND ca1.is_primary = 1 )";
631 $join .= " LEFT JOIN civicrm_address ca2 ON (ca2.contact_id = pn.entity_id2 AND ca2.is_primary = 1 )";
632 }
ed92673b 633 $iTotal = CRM_Core_BAO_PrevNextCache::getCount($cacheKeyString, $join, $whereClause, '=', $queryParams);
1f51ef4e 634 if (!empty($_REQUEST['order'])) {
635 foreach ($_REQUEST['order'] as $orderInfo) {
636 if (!empty($orderInfo['column'])) {
637 $orderColumnNumber = $orderInfo['column'];
2c032aca 638 $dir = CRM_Utils_Type::escape($orderInfo['dir'], 'MysqlOrderByDirection', FALSE);
1f51ef4e 639 }
63ef778e 640 }
9c1bc317 641 $columnDetails = $_REQUEST['columns'][$orderColumnNumber] ?? NULL;
63ef778e 642 }
f931b74c 643 if (!empty($columnDetails)) {
63ef778e 644 switch ($columnDetails['data']) {
f931b74c 645 case 'src':
e67dcaf8 646 $orderByClause = " ORDER BY cc2.display_name {$dir}";
f931b74c 647 break;
648
649 case 'src_email':
e67dcaf8 650 $orderByClause = " ORDER BY ce2.email {$dir}";
f931b74c 651 break;
652
653 case 'src_street':
e67dcaf8 654 $orderByClause = " ORDER BY ca2.street_address {$dir}";
f931b74c 655 break;
656
657 case 'src_postcode':
e67dcaf8 658 $orderByClause = " ORDER BY ca2.postal_code {$dir}";
f931b74c 659 break;
660
661 case 'dst':
e67dcaf8 662 $orderByClause = " ORDER BY cc1.display_name {$dir}";
f931b74c 663 break;
664
665 case 'dst_email':
e67dcaf8 666 $orderByClause = " ORDER BY ce1.email {$dir}";
f931b74c 667 break;
668
669 case 'dst_street':
e67dcaf8 670 $orderByClause = " ORDER BY ca1.street_address {$dir}";
f931b74c 671 break;
672
673 case 'dst_postcode':
e67dcaf8 674 $orderByClause = " ORDER BY ca1.postal_code {$dir}";
f931b74c 675 break;
676
677 default:
063ffcb7 678 $orderByClause = " ORDER BY cc1.display_name ASC";
f931b74c 679 break;
63ef778e 680 }
681 }
6a488035 682
ed92673b 683 $dupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, $join, $whereClause, $offset, $rowCount, $select, $orderByClause, TRUE, $queryParams);
63ef778e 684 $iFilteredTotal = CRM_Core_DAO::singleValueQuery("SELECT FOUND_ROWS()");
6a488035 685
63ef778e 686 $count = 0;
687 foreach ($dupePairs as $key => $pairInfo) {
e67dcaf8 688 $pair = $pairInfo['data'];
9c1bc317
CW
689 $srcContactSubType = $pairInfo['src_contact_sub_type'] ?? NULL;
690 $dstContactSubType = $pairInfo['dst_contact_sub_type'] ?? NULL;
63ef778e 691 $srcTypeImage = CRM_Contact_BAO_Contact_Utils::getImage($srcContactSubType ?
692 $srcContactSubType : $pairInfo['src_contact_type'],
693 FALSE,
e67dcaf8 694 $pairInfo['entity_id2']
63ef778e 695 );
696 $dstTypeImage = CRM_Contact_BAO_Contact_Utils::getImage($dstContactSubType ?
697 $dstContactSubType : $pairInfo['dst_contact_type'],
698 FALSE,
e67dcaf8 699 $pairInfo['entity_id1']
63ef778e 700 );
6a488035 701
63ef778e 702 $searchRows[$count]['is_selected'] = $pairInfo['is_selected'];
703 $searchRows[$count]['is_selected_input'] = "<input type='checkbox' class='crm-dedupe-select' name='pnid_{$pairInfo['prevnext_id']}' value='{$pairInfo['is_selected']}' onclick='toggleDedupeSelect(this)'>";
704 $searchRows[$count]['src_image'] = $srcTypeImage;
e67dcaf8 705 $searchRows[$count]['src'] = CRM_Utils_System::href($pair['srcName'], 'civicrm/contact/view', "reset=1&cid={$pairInfo['entity_id2']}");
9c1bc317
CW
706 $searchRows[$count]['src_email'] = $pairInfo['src_email'] ?? NULL;
707 $searchRows[$count]['src_street'] = $pairInfo['src_street'] ?? NULL;
708 $searchRows[$count]['src_postcode'] = $pairInfo['src_postcode'] ?? NULL;
63ef778e 709 $searchRows[$count]['dst_image'] = $dstTypeImage;
e67dcaf8 710 $searchRows[$count]['dst'] = CRM_Utils_System::href($pair['dstName'], 'civicrm/contact/view', "reset=1&cid={$pairInfo['entity_id1']}");
9c1bc317
CW
711 $searchRows[$count]['dst_email'] = $pairInfo['dst_email'] ?? NULL;
712 $searchRows[$count]['dst_street'] = $pairInfo['dst_street'] ?? NULL;
713 $searchRows[$count]['dst_postcode'] = $pairInfo['dst_postcode'] ?? NULL;
da3afd4a 714 $searchRows[$count]['conflicts'] = str_replace("',", "',<br/>", CRM_Utils_Array::value('conflicts', $pair));
9c1bc317 715 $searchRows[$count]['weight'] = $pair['weight'] ?? NULL;
63ef778e 716
fd630ef9 717 if (!empty($pairInfo['data']['canMerge'])) {
c0cc2ad4 718 $mergeParams = [
719 'reset' => 1,
69078420
SL
720 'cid' => $pairInfo['entity_id1'],
721 'oid' => $pairInfo['entity_id2'],
722 'action' => 'update',
723 'rgid' => $rgid,
724 'criteria' => $criteria,
725 'limit' => CRM_Utils_Request::retrieve('limit', 'Integer'),
726 ];
6a488035 727 if ($gid) {
c0cc2ad4 728 $mergeParams['gid'] = $gid;
6a488035
TO
729 }
730
fd630ef9 731 $searchRows[$count]['actions'] = "<a class='crm-dedupe-flip' href='#' data-pnid={$pairInfo['prevnext_id']}>" . ts('flip') . "</a>&nbsp;|&nbsp;";
da3afd4a 732 $searchRows[$count]['actions'] .= CRM_Utils_System::href(ts('merge'), 'civicrm/contact/merge', $mergeParams);
fd630ef9 733 $searchRows[$count]['actions'] .= "&nbsp;|&nbsp;<a id='notDuplicate' href='#' onClick=\"processDupes( {$pairInfo['entity_id1']}, {$pairInfo['entity_id2']}, 'dupe-nondupe', 'dupe-listing'); return false;\">" . ts('not a duplicate') . "</a>";
6a488035
TO
734 }
735 else {
63ef778e 736 $searchRows[$count]['actions'] = '<em>' . ts('Insufficient access rights - cannot merge') . '</em>';
6a488035 737 }
63ef778e 738 $count++;
6a488035
TO
739 }
740
be2fb01f 741 $dupePairs = [
22b232f3 742 'data' => $searchRows,
743 'recordsTotal' => $iTotal,
744 'recordsFiltered' => $iFilteredTotal,
be2fb01f 745 ];
1f51ef4e 746 if (!empty($_REQUEST['is_unit_test'])) {
747 return $dupePairs;
748 }
22b232f3 749 CRM_Utils_JSON::output($dupePairs);
6a488035
TO
750 }
751
ed92673b 752 /**
753 * Get the searchable options from the request.
754 *
755 * @return array
756 */
757 public static function getSearchOptionsFromRequest() {
be2fb01f 758 $searchParams = [];
9c1bc317 759 $searchData = $_REQUEST['search'] ?? NULL;
32c3b33f 760 $searchData['value'] = CRM_Utils_Type::escape($searchData['value'] ?? NULL, 'String');
be2fb01f 761 $selectorElements = [
ed92673b 762 'is_selected',
763 'is_selected_input',
764 'src_image',
765 'src',
766 'src_email',
767 'src_street',
768 'src_postcode',
769 'dst_image',
770 'dst',
771 'dst_email',
772 'dst_street',
773 'dst_postcode',
774 'conflicts',
775 'weight',
776 'actions',
be2fb01f 777 ];
ed92673b 778 $columns = $_REQUEST['columns'];
779
780 foreach ($columns as $column) {
781 if (!empty($column['search']['value']) && in_array($column['data'], $selectorElements)) {
782 $searchParams[$column['data']] = CRM_Utils_Type::escape($column['search']['value'], 'String');
783 }
784 elseif (!empty($searchData['value'])) {
785 $searchParams[$column['data']] = $searchData['value'];
786 }
787 }
788 return $searchParams;
789 }
790
791 /**
792 * Is the query an OR query.
793 *
794 * If a generic search value is passed in - ie. $_REQUEST['search']['value'] = 'abc'
795 * then all fields are searched for this.
796 *
797 * It is unclear if there is any code that still passes this in or whether is is just legacy. It
798 * could cause a server-killing query on a large site so it probably is NOT in use if we haven't
799 * had complaints.
800 *
801 * @return bool
802 */
803 public static function isOrQuery() {
9c1bc317 804 $searchData = $_REQUEST['search'] ?? NULL;
ed92673b 805 return !empty($searchData['value']);
806 }
807
2d1fefa0 808 /**
809 * Mark not duplicates.
810 *
811 * Note this function would sensibly be replaced by an api-call but extracting here to add a test first.
812 *
813 * I would have like to make it private but test class accesses it & it doesn't warrant being a BAO class
814 * as it should feel very endangered.
815 *
816 * @param int $cid
817 * @param int $oid
aec7c57d
EM
818 * @param string $oper
819 * 'nondupe-dupe' or 'dupe-nondupe'
2d1fefa0 820 *
821 * @return \CRM_Core_DAO|mixed|null
822 */
823 public static function markNonDuplicates($cid, $oid, $oper) {
aec7c57d 824 if ($oper === 'dupe-nondupe') {
ac2751b3 825 try {
826 civicrm_api3('Exception', 'create', ['contact_id1' => $cid, 'contact_id2' => $oid]);
827 return TRUE;
828 }
829 catch (CiviCRM_API3_Exception $e) {
830 return FALSE;
831 }
832 }
833
61194d45 834 $exception = new CRM_Dedupe_DAO_DedupeException();
2d1fefa0 835 $exception->contact_id1 = $cid;
836 $exception->contact_id2 = $oid;
837 //make sure contact2 > contact1.
838 if ($cid > $oid) {
839 $exception->contact_id1 = $oid;
840 $exception->contact_id2 = $cid;
841 }
842 $exception->find(TRUE);
843 $status = NULL;
ac2751b3 844
aec7c57d 845 if ($oper === 'nondupe-dupe') {
2d1fefa0 846 $status = $exception->delete();
847 }
848 return $status;
849 }
850
6a488035 851 /**
fe482240 852 * Retrieve a PDF Page Format for the PDF Letter form.
6a488035 853 */
91aafe90 854 public static function pdfFormat() {
6a488035
TO
855 $formatId = CRM_Utils_Type::escape($_REQUEST['formatId'], 'Integer');
856
857 $pdfFormat = CRM_Core_BAO_PdfFormat::getById($formatId);
858
ecdef330 859 CRM_Utils_JSON::output($pdfFormat);
6a488035
TO
860 }
861
862 /**
fe482240 863 * Retrieve Paper Size dimensions.
6a488035 864 */
00be9182 865 public static function paperSize() {
6a488035
TO
866 $paperSizeName = CRM_Utils_Type::escape($_REQUEST['paperSizeName'], 'String');
867
868 $paperSize = CRM_Core_BAO_PaperSize::getByName($paperSizeName);
869
ecdef330 870 CRM_Utils_JSON::output($paperSize);
6a488035
TO
871 }
872
183ec330 873 /**
874 * Swap contacts in a dupe pair i.e main with duplicate contact.
ea3ddccf 875 *
876 * @param int $prevNextId
183ec330 877 */
f931b74c 878 public static function flipDupePairs($prevNextId = NULL) {
fd630ef9 879 if (!$prevNextId) {
808c05a9 880 // @todo figure out if this is always POST & specify that rather than inexact GET
6d13d1c4
ML
881
882 // We cannot use CRM_Utils_Request::retrieve() because it might be an array.
883 // It later gets validated in escapeAll below.
884 $prevNextId = $_REQUEST['pnid'];
fd630ef9 885 }
808c05a9 886
887 $onlySelected = FALSE;
fd630ef9 888 if (is_array($prevNextId) && !CRM_Utils_Array::crmIsEmptyArray($prevNextId)) {
808c05a9 889 $onlySelected = TRUE;
fd630ef9 890 }
808c05a9 891 $prevNextId = CRM_Utils_Type::escapeAll((array) $prevNextId, 'Positive');
892 CRM_Core_BAO_PrevNextCache::flipPair($prevNextId, $onlySelected);
558cd7c7 893 CRM_Utils_System::civiExit();
fd630ef9 894 }
895
aeb97cc1
CW
896 /**
897 * Used to store selected contacts across multiple pages in advanced search.
898 */
00be9182 899 public static function selectUnselectContacts() {
9c1bc317
CW
900 $name = $_REQUEST['name'] ?? NULL;
901 $cacheKey = $_REQUEST['qfKey'] ?? NULL;
353ffa53 902 $state = CRM_Utils_Array::value('state', $_REQUEST, 'checked');
6a488035
TO
903 $variableType = CRM_Utils_Array::value('variableType', $_REQUEST, 'single');
904
905 $actionToPerform = CRM_Utils_Array::value('action', $_REQUEST, 'select');
906
907 if ($variableType == 'multiple') {
908 // action post value only works with multiple type variable
909 if ($name) {
910 //multiple names like mark_x_1-mark_x_2 where 1,2 are cids
911 $elements = explode('-', $name);
912 foreach ($elements as $key => $element) {
913 $elements[$key] = self::_convertToId($element);
914 }
91209206 915 CRM_Utils_Type::escapeAll($elements, 'Integer');
0b8038a6 916 Civi::service('prevnext')->markSelection($cacheKey, $actionToPerform, $elements);
6a488035
TO
917 }
918 else {
0b8038a6 919 Civi::service('prevnext')->markSelection($cacheKey, $actionToPerform);
6a488035
TO
920 }
921 }
922 elseif ($variableType == 'single') {
923 $cId = self::_convertToId($name);
91209206 924 CRM_Utils_Type::escape($cId, 'Integer');
6a488035 925 $action = ($state == 'checked') ? 'select' : 'unselect';
0b8038a6 926 Civi::service('prevnext')->markSelection($cacheKey, $action, $cId);
6a488035 927 }
b7994703 928 $contactIds = Civi::service('prevnext')->getSelection($cacheKey);
6a488035
TO
929 $countSelectionCids = count($contactIds[$cacheKey]);
930
be2fb01f 931 $arrRet = ['getCount' => $countSelectionCids];
ecdef330 932 CRM_Utils_JSON::output($arrRet);
6a488035
TO
933 }
934
4319322b 935 /**
100fef9d 936 * @param string $name
4319322b
EM
937 *
938 * @return string
939 */
00be9182 940 public static function _convertToId($name) {
6a488035
TO
941 if (substr($name, 0, CRM_Core_Form::CB_PREFIX_LEN) == CRM_Core_Form::CB_PREFIX) {
942 $cId = substr($name, CRM_Core_Form::CB_PREFIX_LEN);
943 }
944 return $cId;
945 }
946
00be9182 947 public static function getAddressDisplay() {
a3d827a7 948 $contactId = CRM_Utils_Request::retrieve('contact_id', 'Positive');
6a488035
TO
949 if (!$contactId) {
950 $addressVal["error_message"] = "no contact id found";
951 }
952 else {
be2fb01f 953 $entityBlock = [
408b79bf 954 'contact_id' => $contactId,
955 'entity_id' => $contactId,
be2fb01f 956 ];
6a488035
TO
957 $addressVal = CRM_Core_BAO_Address::getValues($entityBlock);
958 }
959
ecdef330 960 CRM_Utils_JSON::output($addressVal);
6a488035 961 }
40458f6c 962
183ec330 963 /**
964 * Mark dupe pairs as selected from un-selected state or vice-versa, in dupe cache table.
965 */
f931b74c 966 public static function toggleDedupeSelect() {
63ef778e 967 $pnid = $_REQUEST['pnid'];
968 $isSelected = CRM_Utils_Type::escape($_REQUEST['is_selected'], 'Boolean');
9d2f6d53 969 $cacheKeyString = CRM_Utils_Request::retrieve('cacheKey', 'Alphanumeric', $null, FALSE);
63ef778e 970
be2fb01f
CW
971 $params = [
972 1 => [$isSelected, 'Boolean'],
69078420
SL
973 // using % to address rows with conflicts as well
974 3 => ["$cacheKeyString%", 'String'],
be2fb01f 975 ];
63ef778e 976
977 //check pnid is_array or integer
978 $whereClause = NULL;
979 if (is_array($pnid) && !CRM_Utils_Array::crmIsEmptyArray($pnid)) {
13c42e60 980 CRM_Utils_Type::escapeAll($pnid, 'Positive');
63ef778e 981 $pnid = implode(', ', $pnid);
63ef778e 982 $whereClause = " id IN ( {$pnid} ) ";
983 }
984 else {
985 $pnid = CRM_Utils_Type::escape($pnid, 'Integer');
986 $whereClause = " id = %2";
be2fb01f 987 $params[2] = [$pnid, 'Integer'];
63ef778e 988 }
989
783b4b21 990 $sql = "UPDATE civicrm_prevnext_cache SET is_selected = %1 WHERE {$whereClause} AND cachekey LIKE %3";
63ef778e 991 CRM_Core_DAO::executeQuery($sql, $params);
992
993 CRM_Utils_System::civiExit();
994 }
995
40458f6c 996 /**
fe482240 997 * Retrieve contact relationships.
40458f6c 998 */
999 public static function getContactRelationships() {
1000 $contactID = CRM_Utils_Type::escape($_GET['cid'], 'Integer');
edc80cda 1001 $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric');
7d12de7f 1002 $relationship_type_id = CRM_Utils_Type::escape(CRM_Utils_Array::value('relationship_type_id', $_GET), 'Integer', FALSE);
40458f6c 1003
b0266403
CW
1004 if (!CRM_Contact_BAO_Contact_Permission::allow($contactID)) {
1005 return CRM_Utils_System::permissionDenied();
1006 }
1007
00f11506 1008 $params = CRM_Core_Page_AJAX::defaultSortAndPagerParams();
40458f6c 1009
1010 $params['contact_id'] = $contactID;
1011 $params['context'] = $context;
4c5f31d0 1012 if ($relationship_type_id) {
cdee9432
TM
1013 $params['relationship_type_id'] = $relationship_type_id;
1014 }
40458f6c 1015
1016 // get the contact relationships
1017 $relationships = CRM_Contact_BAO_Relationship::getContactRelationshipSelector($params);
1018
7d12de7f 1019 CRM_Utils_JSON::output($relationships);
40458f6c 1020 }
96025800 1021
6a488035 1022}