Merge pull request #18803 from eileenmcnaughton/canceltest
[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');
6a488035 296 CRM_Core_BAO_CustomValue::deleteCustomValue($customValueID, $customGroupID);
a4cce21a 297 if ($contactId) {
7fa9167d 298 echo CRM_Contact_BAO_Contact::getCountComponent('custom_' . $customGroupID, $contactId);
6a488035
TO
299 }
300
2b68a50c 301 CRM_Contact_BAO_GroupContactCache::opportunisticCacheFlush();
6a488035
TO
302 CRM_Utils_System::civiExit();
303 }
304
6a488035 305 /**
fe482240 306 * check the CMS username.
ce80b209 307 */
69078420 308 public static function checkUserName() {
be2fb01f 309 $signer = new CRM_Utils_Signer(CRM_Core_Key::privateKey(), ['for', 'ts']);
a3d827a7
CW
310 $sig = CRM_Utils_Request::retrieve('sig', 'String');
311 $for = CRM_Utils_Request::retrieve('for', 'String');
272081ca
TO
312 if (
313 CRM_Utils_Time::getTimeRaw() > $_REQUEST['ts'] + self::CHECK_USERNAME_TTL
7fa9167d 314 || $for != 'civicrm/ajax/cmsuser'
315 || !$signer->validate($sig, $_REQUEST)
272081ca 316 ) {
be2fb01f 317 $user = ['name' => 'error'];
8be1a839 318 CRM_Utils_JSON::output($user);
272081ca
TO
319 }
320
6a488035 321 $config = CRM_Core_Config::singleton();
4c68cf7b 322 $username = trim(CRM_Utils_Array::value('cms_name', $_REQUEST));
6a488035 323
be2fb01f 324 $params = ['name' => $username];
6a488035 325
be2fb01f 326 $errors = [];
6a488035
TO
327 $config->userSystem->checkUserNameEmailExists($params, $errors);
328
329 if (isset($errors['cms_name']) || isset($errors['name'])) {
b44e3f84 330 //user name is not available
be2fb01f 331 $user = ['name' => 'no'];
8be1a839 332 CRM_Utils_JSON::output($user);
6a488035
TO
333 }
334 else {
335 //user name is available
be2fb01f 336 $user = ['name' => 'yes'];
8be1a839 337 CRM_Utils_JSON::output($user);
6a488035 338 }
8be1a839
TO
339
340 // Not reachable: JSON::output() above exits.
6a488035
TO
341 CRM_Utils_System::civiExit();
342 }
343
344 /**
fe482240 345 * Function to get email address of a contact.
6a488035 346 */
00be9182 347 public static function getContactEmail() {
3c0dc533 348 $queryStrings = [];
9c1bc317 349 $name = $_GET['name'] ?? NULL;
3c0dc533 350 if ($name) {
351 $name = CRM_Utils_Type::escape($name, 'String');
352 $wildCard = Civi::settings()->get('includeWildCardInName') ? '%' : '';
353 foreach (['cc.sort_name', 'ce.email'] as $column) {
354 $queryStrings[] = "{$column} LIKE '{$wildCard}{$name}%'";
8c0ea1d7 355 }
3c0dc533 356 $result = [];
357 $rowCount = Civi::settings()->get('search_autocomplete_count');
effdc2d9 358
3c0dc533 359 // add acl clause here
360 list($aclFrom, $aclWhere) = CRM_Contact_BAO_Contact_Permission::cacheClause('cc');
361 if ($aclWhere) {
362 $aclWhere = "AND {$aclWhere}";
6a488035 363 }
3c0dc533 364 foreach ($queryStrings as &$queryString) {
365 $queryString = "(
6a488035
TO
366SELECT sort_name name, ce.email, cc.id
367FROM civicrm_email ce INNER JOIN civicrm_contact cc ON cc.id = ce.contact_id
368 {$aclFrom}
369WHERE ce.on_hold = 0 AND cc.is_deceased = 0 AND cc.do_not_email = 0 AND {$queryString}
370 {$aclWhere}
3c0dc533 371LIMIT {$rowCount}
372)";
373 }
374 $query = implode(' UNION ', $queryStrings) . " LIMIT {$rowCount}";
375
376 // send query to hook to be modified if needed
377 CRM_Utils_Hook::contactListQuery($query,
378 $name,
379 CRM_Utils_Request::retrieve('context', 'Alphanumeric'),
380 CRM_Utils_Request::retrieve('cid', 'Positive')
381 );
382
383 $dao = CRM_Core_DAO::executeQuery($query);
384
385 while ($dao->fetch()) {
386 //working here
387 $result[] = [
388 'text' => '"' . $dao->name . '" <' . $dao->email . '>',
389 'id' => (CRM_Utils_Array::value('id', $_GET)) ? "{$dao->id}::{$dao->email}" : '"' . $dao->name . '" <' . $dao->email . '>',
390 ];
6a488035 391 }
3c0dc533 392 CRM_Utils_JSON::output($result);
6a488035
TO
393 }
394 CRM_Utils_System::civiExit();
395 }
396
00be9182 397 public static function getContactPhone() {
6a488035
TO
398
399 $queryString = NULL;
3ae9c7d6 400 $sqlParmas = [];
6a488035
TO
401 //check for mobile type
402 $phoneTypes = CRM_Core_OptionGroup::values('phone_type', TRUE, FALSE, FALSE, NULL, 'name');
9c1bc317 403 $mobileType = $phoneTypes['Mobile'] ?? NULL;
6a488035 404
3ae9c7d6 405 $name = CRM_Utils_Request::retrieveValue('name', 'String', NULL, FALSE, 'GET');
a4cce21a 406 if ($name) {
3ae9c7d6
SL
407 $key = (int) count(array_keys($sqlParmas)) + 1;
408 $queryString = " ( cc.sort_name LIKE %{$key} OR cp.phone LIKE %{$key} ) ";
409 $sqlParams[$key] = ['%' . $name . '%', 'String'];
6a488035 410 }
a4cce21a 411 else {
3ae9c7d6 412 $cid = CRM_Utils_Request::retrieveValue('cid', 'CommaSeparatedIntegers', NULL, FALSE, 'GET');
d75f2f47 413 if ($cid) {
a4cce21a 414 $queryString = " cc.id IN ( $cid )";
6a488035 415 }
6a488035
TO
416 }
417
418 if ($queryString) {
be2fb01f 419 $result = [];
3ae9c7d6
SL
420 $offset = (int) CRM_Utils_Request::retrieveValue('offset', 'Integer', 0, FALSE, 'GET');
421 $rowCount = (int) CRM_Utils_Request::retrieveValue('rowcount', 'Integer', 20, FALSE, 'GET');
bf00d1b6 422
6a488035
TO
423 // add acl clause here
424 list($aclFrom, $aclWhere) = CRM_Contact_BAO_Contact_Permission::cacheClause('cc');
425 if ($aclWhere) {
426 $aclWhere = " AND $aclWhere";
427 }
428
429 $query = "
430SELECT sort_name name, cp.phone, cc.id
431FROM civicrm_phone cp INNER JOIN civicrm_contact cc ON cc.id = cp.contact_id
432 {$aclFrom}
433WHERE cc.is_deceased = 0 AND cc.do_not_sms = 0 AND cp.phone_type_id = {$mobileType} AND {$queryString}
434 {$aclWhere}
435LIMIT {$offset}, {$rowCount}
436";
437
438 // send query to hook to be modified if needed
439 CRM_Utils_Hook::contactListQuery($query,
440 $name,
edc80cda 441 CRM_Utils_Request::retrieve('context', 'Alphanumeric'),
a3d827a7 442 CRM_Utils_Request::retrieve('cid', 'Positive')
6a488035
TO
443 );
444
3ae9c7d6 445 $dao = CRM_Core_DAO::executeQuery($query, $sqlParams);
6a488035
TO
446
447 while ($dao->fetch()) {
be2fb01f 448 $result[] = [
b792e485 449 'text' => '"' . $dao->name . '" (' . $dao->phone . ')',
6a488035 450 'id' => (CRM_Utils_Array::value('id', $_GET)) ? "{$dao->id}::{$dao->phone}" : '"' . $dao->name . '" <' . $dao->phone . '>',
be2fb01f 451 ];
6a488035 452 }
8be1a839 453 CRM_Utils_JSON::output($result);
6a488035
TO
454 }
455 CRM_Utils_System::civiExit();
456 }
457
00be9182 458 public static function buildSubTypes() {
a3d827a7 459 $parent = CRM_Utils_Request::retrieve('parentId', 'Positive');
6a488035
TO
460
461 switch ($parent) {
462 case 1:
463 $contactType = 'Individual';
464 break;
465
466 case 2:
467 $contactType = 'Household';
468 break;
469
470 case 4:
471 $contactType = 'Organization';
472 break;
473 }
474
475 $subTypes = CRM_Contact_BAO_ContactType::subTypePairs($contactType, FALSE, NULL);
476 asort($subTypes);
ecdef330 477 CRM_Utils_JSON::output($subTypes);
6a488035
TO
478 }
479
00be9182 480 public static function buildDedupeRules() {
a3d827a7 481 $parent = CRM_Utils_Request::retrieve('parentId', 'Positive');
6a488035
TO
482
483 switch ($parent) {
484 case 1:
485 $contactType = 'Individual';
486 break;
487
488 case 2:
489 $contactType = 'Household';
490 break;
491
492 case 4:
493 $contactType = 'Organization';
494 break;
495 }
496
497 $dedupeRules = CRM_Dedupe_BAO_RuleGroup::getByType($contactType);
498
ecdef330 499 CRM_Utils_JSON::output($dedupeRules);
6a488035
TO
500 }
501
502 /**
fe482240 503 * Function used for CiviCRM dashboard operations.
6a488035 504 */
00be9182 505 public static function dashboard() {
ce2cc43e 506 switch ($_REQUEST['op']) {
6a488035 507 case 'save_columns':
26d9b052 508 CRM_Core_BAO_Dashboard::saveDashletChanges($_REQUEST['columns'] ?? NULL);
ce2cc43e
CW
509 break;
510
6a488035
TO
511 case 'delete_dashlet':
512 $dashletID = CRM_Utils_Type::escape($_REQUEST['dashlet_id'], 'Positive');
a279e546 513 CRM_Core_DAO_Dashboard::deleteRecord(['id' => $dashletID]);
6a488035
TO
514 }
515
ce2cc43e 516 CRM_Utils_System::civiExit();
6a488035
TO
517 }
518
519 /**
fe482240 520 * Retrieve signature based on email id.
6a488035 521 */
00be9182 522 public static function getSignature() {
6a488035 523 $emailID = CRM_Utils_Type::escape($_REQUEST['emailID'], 'Positive');
353ffa53
TO
524 $query = "SELECT signature_text, signature_html FROM civicrm_email WHERE id = {$emailID}";
525 $dao = CRM_Core_DAO::executeQuery($query);
6a488035 526
be2fb01f 527 $signatures = [];
6a488035 528 while ($dao->fetch()) {
be2fb01f 529 $signatures = [
6a488035
TO
530 'signature_text' => $dao->signature_text,
531 'signature_html' => $dao->signature_html,
be2fb01f 532 ];
6a488035
TO
533 }
534
ecdef330 535 CRM_Utils_JSON::output($signatures);
6a488035
TO
536 }
537
538 /**
100fef9d 539 * Process dupes.
6a488035 540 */
00be9182 541 public static function processDupes() {
6a488035 542 $oper = CRM_Utils_Type::escape($_REQUEST['op'], 'String');
353ffa53
TO
543 $cid = CRM_Utils_Type::escape($_REQUEST['cid'], 'Positive');
544 $oid = CRM_Utils_Type::escape($_REQUEST['oid'], 'Positive');
6a488035
TO
545
546 if (!$oper || !$cid || !$oid) {
547 return;
548 }
549
2d1fefa0 550 $status = self::markNonDuplicates($cid, $oid, $oper);
6a488035 551
be2fb01f 552 CRM_Utils_JSON::output(['status' => ($status) ? $oper : $status]);
6a488035
TO
553 }
554
183ec330 555 /**
556 * Retrieve list of duplicate pairs from cache table.
557 */
00be9182 558 public static function getDedupes() {
63ef778e 559 $offset = isset($_REQUEST['start']) ? CRM_Utils_Type::escape($_REQUEST['start'], 'Integer') : 0;
560 $rowCount = isset($_REQUEST['length']) ? CRM_Utils_Type::escape($_REQUEST['length'], 'Integer') : 25;
6a488035 561
dc6285d5 562 $gid = CRM_Utils_Request::retrieve('gid', 'Positive');
563 $rgid = CRM_Utils_Request::retrieve('rgid', 'Positive');
997a03fe 564 $limit = CRM_Utils_Request::retrieveValue('limit', 'Positive', 0);
9f54f049 565 $null = NULL;
566 $criteria = CRM_Utils_Request::retrieve('criteria', 'Json', $null, FALSE, '{}');
bb22928b 567 $selected = CRM_Utils_Request::retrieveValue('selected', 'Boolean');
63ef778e 568 if ($rowCount < 0) {
569 $rowCount = 0;
570 }
6a488035 571
1f51ef4e 572 $whereClause = $orderByClause = '';
997a03fe 573 $cacheKeyString = CRM_Dedupe_Merger::getMergeCacheKeyString($rgid, $gid, json_decode($criteria, TRUE), TRUE, $limit);
9f54f049 574
69078420 575 $searchRows = [];
6a488035 576
ed92673b 577 $searchParams = self::getSearchOptionsFromRequest();
be2fb01f 578 $queryParams = [];
ed92673b 579
63ef778e 580 $join = '';
be2fb01f 581 $where = [];
579ea9bc 582
ed92673b 583 $isOrQuery = self::isOrQuery();
584
585 $nextParamKey = 3;
be2fb01f 586 $mappings = [
e67dcaf8 587 'dst' => 'cc1.display_name',
588 'src' => 'cc2.display_name',
589 'dst_email' => 'ce1.email',
590 'src_email' => 'ce2.email',
591 'dst_postcode' => 'ca1.postal_code',
592 'src_postcode' => 'ca2.postal_code',
593 'dst_street' => 'ca1.street',
594 'src_street' => 'ca2.street',
be2fb01f 595 ];
ed92673b 596
597 foreach ($mappings as $key => $dbName) {
598 if (!empty($searchParams[$key])) {
12af7add 599 // CRM-18694.
600 $wildcard = strstr($key, 'postcode') ? '' : '%';
be2fb01f 601 $queryParams[$nextParamKey] = [$wildcard . $searchParams[$key] . '%', 'String'];
ed92673b 602 $where[] = $dbName . " LIKE %{$nextParamKey} ";
603 $nextParamKey++;
604 }
63ef778e 605 }
ed92673b 606
607 if ($isOrQuery) {
69078420 608 $whereClause = ' ( ' . implode(' OR ', $where) . ' ) ';
63ef778e 609 }
610 else {
611 if (!empty($where)) {
69078420 612 $whereClause = implode(' AND ', $where);
63ef778e 613 }
f931b74c 614 }
63ef778e 615 $whereClause .= $whereClause ? ' AND de.id IS NULL' : ' de.id IS NULL';
6a488035 616
63ef778e 617 if ($selected) {
618 $whereClause .= ' AND pn.is_selected = 1';
619 }
2988f5c7 620 $join .= CRM_Dedupe_Merger::getJoinOnDedupeTable();
63ef778e 621
be2fb01f 622 $select = [
e67dcaf8 623 'cc1.contact_type' => 'dst_contact_type',
624 'cc1.display_name' => 'dst_display_name',
625 'cc1.contact_sub_type' => 'dst_contact_sub_type',
626 'cc2.contact_type' => 'src_contact_type',
627 'cc2.display_name' => 'src_display_name',
628 'cc2.contact_sub_type' => 'src_contact_sub_type',
629 'ce1.email' => 'dst_email',
630 'ce2.email' => 'src_email',
631 'ca1.postal_code' => 'dst_postcode',
632 'ca2.postal_code' => 'src_postcode',
633 'ca1.street_address' => 'dst_street',
634 'ca2.street_address' => 'src_street',
be2fb01f 635 ];
63ef778e 636
f931b74c 637 if ($select) {
63ef778e 638 $join .= " INNER JOIN civicrm_contact cc1 ON cc1.id = pn.entity_id1";
639 $join .= " INNER JOIN civicrm_contact cc2 ON cc2.id = pn.entity_id2";
640 $join .= " LEFT JOIN civicrm_email ce1 ON (ce1.contact_id = pn.entity_id1 AND ce1.is_primary = 1 )";
641 $join .= " LEFT JOIN civicrm_email ce2 ON (ce2.contact_id = pn.entity_id2 AND ce2.is_primary = 1 )";
642 $join .= " LEFT JOIN civicrm_address ca1 ON (ca1.contact_id = pn.entity_id1 AND ca1.is_primary = 1 )";
643 $join .= " LEFT JOIN civicrm_address ca2 ON (ca2.contact_id = pn.entity_id2 AND ca2.is_primary = 1 )";
644 }
ed92673b 645 $iTotal = CRM_Core_BAO_PrevNextCache::getCount($cacheKeyString, $join, $whereClause, '=', $queryParams);
1f51ef4e 646 if (!empty($_REQUEST['order'])) {
647 foreach ($_REQUEST['order'] as $orderInfo) {
648 if (!empty($orderInfo['column'])) {
649 $orderColumnNumber = $orderInfo['column'];
2c032aca 650 $dir = CRM_Utils_Type::escape($orderInfo['dir'], 'MysqlOrderByDirection', FALSE);
1f51ef4e 651 }
63ef778e 652 }
9c1bc317 653 $columnDetails = $_REQUEST['columns'][$orderColumnNumber] ?? NULL;
63ef778e 654 }
f931b74c 655 if (!empty($columnDetails)) {
63ef778e 656 switch ($columnDetails['data']) {
f931b74c 657 case 'src':
e67dcaf8 658 $orderByClause = " ORDER BY cc2.display_name {$dir}";
f931b74c 659 break;
660
661 case 'src_email':
e67dcaf8 662 $orderByClause = " ORDER BY ce2.email {$dir}";
f931b74c 663 break;
664
665 case 'src_street':
e67dcaf8 666 $orderByClause = " ORDER BY ca2.street_address {$dir}";
f931b74c 667 break;
668
669 case 'src_postcode':
e67dcaf8 670 $orderByClause = " ORDER BY ca2.postal_code {$dir}";
f931b74c 671 break;
672
673 case 'dst':
e67dcaf8 674 $orderByClause = " ORDER BY cc1.display_name {$dir}";
f931b74c 675 break;
676
677 case 'dst_email':
e67dcaf8 678 $orderByClause = " ORDER BY ce1.email {$dir}";
f931b74c 679 break;
680
681 case 'dst_street':
e67dcaf8 682 $orderByClause = " ORDER BY ca1.street_address {$dir}";
f931b74c 683 break;
684
685 case 'dst_postcode':
e67dcaf8 686 $orderByClause = " ORDER BY ca1.postal_code {$dir}";
f931b74c 687 break;
688
689 default:
063ffcb7 690 $orderByClause = " ORDER BY cc1.display_name ASC";
f931b74c 691 break;
63ef778e 692 }
693 }
6a488035 694
ed92673b 695 $dupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, $join, $whereClause, $offset, $rowCount, $select, $orderByClause, TRUE, $queryParams);
63ef778e 696 $iFilteredTotal = CRM_Core_DAO::singleValueQuery("SELECT FOUND_ROWS()");
6a488035 697
63ef778e 698 $count = 0;
699 foreach ($dupePairs as $key => $pairInfo) {
e67dcaf8 700 $pair = $pairInfo['data'];
9c1bc317
CW
701 $srcContactSubType = $pairInfo['src_contact_sub_type'] ?? NULL;
702 $dstContactSubType = $pairInfo['dst_contact_sub_type'] ?? NULL;
63ef778e 703 $srcTypeImage = CRM_Contact_BAO_Contact_Utils::getImage($srcContactSubType ?
704 $srcContactSubType : $pairInfo['src_contact_type'],
705 FALSE,
e67dcaf8 706 $pairInfo['entity_id2']
63ef778e 707 );
708 $dstTypeImage = CRM_Contact_BAO_Contact_Utils::getImage($dstContactSubType ?
709 $dstContactSubType : $pairInfo['dst_contact_type'],
710 FALSE,
e67dcaf8 711 $pairInfo['entity_id1']
63ef778e 712 );
6a488035 713
63ef778e 714 $searchRows[$count]['is_selected'] = $pairInfo['is_selected'];
715 $searchRows[$count]['is_selected_input'] = "<input type='checkbox' class='crm-dedupe-select' name='pnid_{$pairInfo['prevnext_id']}' value='{$pairInfo['is_selected']}' onclick='toggleDedupeSelect(this)'>";
716 $searchRows[$count]['src_image'] = $srcTypeImage;
e67dcaf8 717 $searchRows[$count]['src'] = CRM_Utils_System::href($pair['srcName'], 'civicrm/contact/view', "reset=1&cid={$pairInfo['entity_id2']}");
9c1bc317
CW
718 $searchRows[$count]['src_email'] = $pairInfo['src_email'] ?? NULL;
719 $searchRows[$count]['src_street'] = $pairInfo['src_street'] ?? NULL;
720 $searchRows[$count]['src_postcode'] = $pairInfo['src_postcode'] ?? NULL;
63ef778e 721 $searchRows[$count]['dst_image'] = $dstTypeImage;
e67dcaf8 722 $searchRows[$count]['dst'] = CRM_Utils_System::href($pair['dstName'], 'civicrm/contact/view', "reset=1&cid={$pairInfo['entity_id1']}");
9c1bc317
CW
723 $searchRows[$count]['dst_email'] = $pairInfo['dst_email'] ?? NULL;
724 $searchRows[$count]['dst_street'] = $pairInfo['dst_street'] ?? NULL;
725 $searchRows[$count]['dst_postcode'] = $pairInfo['dst_postcode'] ?? NULL;
da3afd4a 726 $searchRows[$count]['conflicts'] = str_replace("',", "',<br/>", CRM_Utils_Array::value('conflicts', $pair));
9c1bc317 727 $searchRows[$count]['weight'] = $pair['weight'] ?? NULL;
63ef778e 728
fd630ef9 729 if (!empty($pairInfo['data']['canMerge'])) {
c0cc2ad4 730 $mergeParams = [
731 'reset' => 1,
69078420
SL
732 'cid' => $pairInfo['entity_id1'],
733 'oid' => $pairInfo['entity_id2'],
734 'action' => 'update',
735 'rgid' => $rgid,
736 'criteria' => $criteria,
737 'limit' => CRM_Utils_Request::retrieve('limit', 'Integer'),
738 ];
6a488035 739 if ($gid) {
c0cc2ad4 740 $mergeParams['gid'] = $gid;
6a488035
TO
741 }
742
fd630ef9 743 $searchRows[$count]['actions'] = "<a class='crm-dedupe-flip' href='#' data-pnid={$pairInfo['prevnext_id']}>" . ts('flip') . "</a>&nbsp;|&nbsp;";
da3afd4a 744 $searchRows[$count]['actions'] .= CRM_Utils_System::href(ts('merge'), 'civicrm/contact/merge', $mergeParams);
fd630ef9 745 $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
746 }
747 else {
63ef778e 748 $searchRows[$count]['actions'] = '<em>' . ts('Insufficient access rights - cannot merge') . '</em>';
6a488035 749 }
63ef778e 750 $count++;
6a488035
TO
751 }
752
be2fb01f 753 $dupePairs = [
22b232f3 754 'data' => $searchRows,
755 'recordsTotal' => $iTotal,
756 'recordsFiltered' => $iFilteredTotal,
be2fb01f 757 ];
1f51ef4e 758 if (!empty($_REQUEST['is_unit_test'])) {
759 return $dupePairs;
760 }
22b232f3 761 CRM_Utils_JSON::output($dupePairs);
6a488035
TO
762 }
763
ed92673b 764 /**
765 * Get the searchable options from the request.
766 *
767 * @return array
768 */
769 public static function getSearchOptionsFromRequest() {
be2fb01f 770 $searchParams = [];
9c1bc317 771 $searchData = $_REQUEST['search'] ?? NULL;
32c3b33f 772 $searchData['value'] = CRM_Utils_Type::escape($searchData['value'] ?? NULL, 'String');
be2fb01f 773 $selectorElements = [
ed92673b 774 'is_selected',
775 'is_selected_input',
776 'src_image',
777 'src',
778 'src_email',
779 'src_street',
780 'src_postcode',
781 'dst_image',
782 'dst',
783 'dst_email',
784 'dst_street',
785 'dst_postcode',
786 'conflicts',
787 'weight',
788 'actions',
be2fb01f 789 ];
ed92673b 790 $columns = $_REQUEST['columns'];
791
792 foreach ($columns as $column) {
793 if (!empty($column['search']['value']) && in_array($column['data'], $selectorElements)) {
794 $searchParams[$column['data']] = CRM_Utils_Type::escape($column['search']['value'], 'String');
795 }
796 elseif (!empty($searchData['value'])) {
797 $searchParams[$column['data']] = $searchData['value'];
798 }
799 }
800 return $searchParams;
801 }
802
803 /**
804 * Is the query an OR query.
805 *
806 * If a generic search value is passed in - ie. $_REQUEST['search']['value'] = 'abc'
807 * then all fields are searched for this.
808 *
809 * It is unclear if there is any code that still passes this in or whether is is just legacy. It
810 * could cause a server-killing query on a large site so it probably is NOT in use if we haven't
811 * had complaints.
812 *
813 * @return bool
814 */
815 public static function isOrQuery() {
9c1bc317 816 $searchData = $_REQUEST['search'] ?? NULL;
ed92673b 817 return !empty($searchData['value']);
818 }
819
2d1fefa0 820 /**
821 * Mark not duplicates.
822 *
823 * Note this function would sensibly be replaced by an api-call but extracting here to add a test first.
824 *
825 * I would have like to make it private but test class accesses it & it doesn't warrant being a BAO class
826 * as it should feel very endangered.
827 *
828 * @param int $cid
829 * @param int $oid
041ecc95 830 * @param "dupe-nondupe|nondupe-dupe" $oper
2d1fefa0 831 *
832 * @return \CRM_Core_DAO|mixed|null
833 */
834 public static function markNonDuplicates($cid, $oid, $oper) {
ac2751b3 835 if ($oper == 'dupe-nondupe') {
836 try {
837 civicrm_api3('Exception', 'create', ['contact_id1' => $cid, 'contact_id2' => $oid]);
838 return TRUE;
839 }
840 catch (CiviCRM_API3_Exception $e) {
841 return FALSE;
842 }
843 }
844
2d1fefa0 845 $exception = new CRM_Dedupe_DAO_Exception();
846 $exception->contact_id1 = $cid;
847 $exception->contact_id2 = $oid;
848 //make sure contact2 > contact1.
849 if ($cid > $oid) {
850 $exception->contact_id1 = $oid;
851 $exception->contact_id2 = $cid;
852 }
853 $exception->find(TRUE);
854 $status = NULL;
ac2751b3 855
2d1fefa0 856 if ($oper == 'nondupe-dupe') {
857 $status = $exception->delete();
858 }
859 return $status;
860 }
861
6a488035 862 /**
fe482240 863 * Retrieve a PDF Page Format for the PDF Letter form.
6a488035 864 */
91aafe90 865 public static function pdfFormat() {
6a488035
TO
866 $formatId = CRM_Utils_Type::escape($_REQUEST['formatId'], 'Integer');
867
868 $pdfFormat = CRM_Core_BAO_PdfFormat::getById($formatId);
869
ecdef330 870 CRM_Utils_JSON::output($pdfFormat);
6a488035
TO
871 }
872
873 /**
fe482240 874 * Retrieve Paper Size dimensions.
6a488035 875 */
00be9182 876 public static function paperSize() {
6a488035
TO
877 $paperSizeName = CRM_Utils_Type::escape($_REQUEST['paperSizeName'], 'String');
878
879 $paperSize = CRM_Core_BAO_PaperSize::getByName($paperSizeName);
880
ecdef330 881 CRM_Utils_JSON::output($paperSize);
6a488035
TO
882 }
883
183ec330 884 /**
885 * Swap contacts in a dupe pair i.e main with duplicate contact.
ea3ddccf 886 *
887 * @param int $prevNextId
183ec330 888 */
f931b74c 889 public static function flipDupePairs($prevNextId = NULL) {
fd630ef9 890 if (!$prevNextId) {
808c05a9 891 // @todo figure out if this is always POST & specify that rather than inexact GET
6d13d1c4
ML
892
893 // We cannot use CRM_Utils_Request::retrieve() because it might be an array.
894 // It later gets validated in escapeAll below.
895 $prevNextId = $_REQUEST['pnid'];
fd630ef9 896 }
808c05a9 897
898 $onlySelected = FALSE;
fd630ef9 899 if (is_array($prevNextId) && !CRM_Utils_Array::crmIsEmptyArray($prevNextId)) {
808c05a9 900 $onlySelected = TRUE;
fd630ef9 901 }
808c05a9 902 $prevNextId = CRM_Utils_Type::escapeAll((array) $prevNextId, 'Positive');
903 CRM_Core_BAO_PrevNextCache::flipPair($prevNextId, $onlySelected);
558cd7c7 904 CRM_Utils_System::civiExit();
fd630ef9 905 }
906
aeb97cc1
CW
907 /**
908 * Used to store selected contacts across multiple pages in advanced search.
909 */
00be9182 910 public static function selectUnselectContacts() {
9c1bc317
CW
911 $name = $_REQUEST['name'] ?? NULL;
912 $cacheKey = $_REQUEST['qfKey'] ?? NULL;
353ffa53 913 $state = CRM_Utils_Array::value('state', $_REQUEST, 'checked');
6a488035
TO
914 $variableType = CRM_Utils_Array::value('variableType', $_REQUEST, 'single');
915
916 $actionToPerform = CRM_Utils_Array::value('action', $_REQUEST, 'select');
917
918 if ($variableType == 'multiple') {
919 // action post value only works with multiple type variable
920 if ($name) {
921 //multiple names like mark_x_1-mark_x_2 where 1,2 are cids
922 $elements = explode('-', $name);
923 foreach ($elements as $key => $element) {
924 $elements[$key] = self::_convertToId($element);
925 }
91209206 926 CRM_Utils_Type::escapeAll($elements, 'Integer');
0b8038a6 927 Civi::service('prevnext')->markSelection($cacheKey, $actionToPerform, $elements);
6a488035
TO
928 }
929 else {
0b8038a6 930 Civi::service('prevnext')->markSelection($cacheKey, $actionToPerform);
6a488035
TO
931 }
932 }
933 elseif ($variableType == 'single') {
934 $cId = self::_convertToId($name);
91209206 935 CRM_Utils_Type::escape($cId, 'Integer');
6a488035 936 $action = ($state == 'checked') ? 'select' : 'unselect';
0b8038a6 937 Civi::service('prevnext')->markSelection($cacheKey, $action, $cId);
6a488035 938 }
b7994703 939 $contactIds = Civi::service('prevnext')->getSelection($cacheKey);
6a488035
TO
940 $countSelectionCids = count($contactIds[$cacheKey]);
941
be2fb01f 942 $arrRet = ['getCount' => $countSelectionCids];
ecdef330 943 CRM_Utils_JSON::output($arrRet);
6a488035
TO
944 }
945
4319322b 946 /**
100fef9d 947 * @param string $name
4319322b
EM
948 *
949 * @return string
950 */
00be9182 951 public static function _convertToId($name) {
6a488035
TO
952 if (substr($name, 0, CRM_Core_Form::CB_PREFIX_LEN) == CRM_Core_Form::CB_PREFIX) {
953 $cId = substr($name, CRM_Core_Form::CB_PREFIX_LEN);
954 }
955 return $cId;
956 }
957
00be9182 958 public static function getAddressDisplay() {
a3d827a7 959 $contactId = CRM_Utils_Request::retrieve('contact_id', 'Positive');
6a488035
TO
960 if (!$contactId) {
961 $addressVal["error_message"] = "no contact id found";
962 }
963 else {
be2fb01f 964 $entityBlock = [
408b79bf 965 'contact_id' => $contactId,
966 'entity_id' => $contactId,
be2fb01f 967 ];
6a488035
TO
968 $addressVal = CRM_Core_BAO_Address::getValues($entityBlock);
969 }
970
ecdef330 971 CRM_Utils_JSON::output($addressVal);
6a488035 972 }
40458f6c 973
183ec330 974 /**
975 * Mark dupe pairs as selected from un-selected state or vice-versa, in dupe cache table.
976 */
f931b74c 977 public static function toggleDedupeSelect() {
63ef778e 978 $pnid = $_REQUEST['pnid'];
979 $isSelected = CRM_Utils_Type::escape($_REQUEST['is_selected'], 'Boolean');
9d2f6d53 980 $cacheKeyString = CRM_Utils_Request::retrieve('cacheKey', 'Alphanumeric', $null, FALSE);
63ef778e 981
be2fb01f
CW
982 $params = [
983 1 => [$isSelected, 'Boolean'],
69078420
SL
984 // using % to address rows with conflicts as well
985 3 => ["$cacheKeyString%", 'String'],
be2fb01f 986 ];
63ef778e 987
988 //check pnid is_array or integer
989 $whereClause = NULL;
990 if (is_array($pnid) && !CRM_Utils_Array::crmIsEmptyArray($pnid)) {
13c42e60 991 CRM_Utils_Type::escapeAll($pnid, 'Positive');
63ef778e 992 $pnid = implode(', ', $pnid);
63ef778e 993 $whereClause = " id IN ( {$pnid} ) ";
994 }
995 else {
996 $pnid = CRM_Utils_Type::escape($pnid, 'Integer');
997 $whereClause = " id = %2";
be2fb01f 998 $params[2] = [$pnid, 'Integer'];
63ef778e 999 }
1000
783b4b21 1001 $sql = "UPDATE civicrm_prevnext_cache SET is_selected = %1 WHERE {$whereClause} AND cachekey LIKE %3";
63ef778e 1002 CRM_Core_DAO::executeQuery($sql, $params);
1003
1004 CRM_Utils_System::civiExit();
1005 }
1006
40458f6c 1007 /**
fe482240 1008 * Retrieve contact relationships.
40458f6c 1009 */
1010 public static function getContactRelationships() {
1011 $contactID = CRM_Utils_Type::escape($_GET['cid'], 'Integer');
edc80cda 1012 $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric');
7d12de7f 1013 $relationship_type_id = CRM_Utils_Type::escape(CRM_Utils_Array::value('relationship_type_id', $_GET), 'Integer', FALSE);
40458f6c 1014
b0266403
CW
1015 if (!CRM_Contact_BAO_Contact_Permission::allow($contactID)) {
1016 return CRM_Utils_System::permissionDenied();
1017 }
1018
00f11506 1019 $params = CRM_Core_Page_AJAX::defaultSortAndPagerParams();
40458f6c 1020
1021 $params['contact_id'] = $contactID;
1022 $params['context'] = $context;
4c5f31d0 1023 if ($relationship_type_id) {
cdee9432
TM
1024 $params['relationship_type_id'] = $relationship_type_id;
1025 }
40458f6c 1026
1027 // get the contact relationships
1028 $relationships = CRM_Contact_BAO_Relationship::getContactRelationshipSelector($params);
1029
7d12de7f 1030 CRM_Utils_JSON::output($relationships);
40458f6c 1031 }
96025800 1032
6a488035 1033}