Merge pull request #19296 from eileenmcnaughton/fbool
[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
6a488035 502 /**
fe482240 503 * Retrieve signature based on email id.
6a488035 504 */
00be9182 505 public static function getSignature() {
6a488035 506 $emailID = CRM_Utils_Type::escape($_REQUEST['emailID'], 'Positive');
353ffa53
TO
507 $query = "SELECT signature_text, signature_html FROM civicrm_email WHERE id = {$emailID}";
508 $dao = CRM_Core_DAO::executeQuery($query);
6a488035 509
be2fb01f 510 $signatures = [];
6a488035 511 while ($dao->fetch()) {
be2fb01f 512 $signatures = [
6a488035
TO
513 'signature_text' => $dao->signature_text,
514 'signature_html' => $dao->signature_html,
be2fb01f 515 ];
6a488035
TO
516 }
517
ecdef330 518 CRM_Utils_JSON::output($signatures);
6a488035
TO
519 }
520
521 /**
100fef9d 522 * Process dupes.
6a488035 523 */
00be9182 524 public static function processDupes() {
6a488035 525 $oper = CRM_Utils_Type::escape($_REQUEST['op'], 'String');
353ffa53
TO
526 $cid = CRM_Utils_Type::escape($_REQUEST['cid'], 'Positive');
527 $oid = CRM_Utils_Type::escape($_REQUEST['oid'], 'Positive');
6a488035
TO
528
529 if (!$oper || !$cid || !$oid) {
530 return;
531 }
532
2d1fefa0 533 $status = self::markNonDuplicates($cid, $oid, $oper);
6a488035 534
be2fb01f 535 CRM_Utils_JSON::output(['status' => ($status) ? $oper : $status]);
6a488035
TO
536 }
537
183ec330 538 /**
539 * Retrieve list of duplicate pairs from cache table.
540 */
00be9182 541 public static function getDedupes() {
63ef778e 542 $offset = isset($_REQUEST['start']) ? CRM_Utils_Type::escape($_REQUEST['start'], 'Integer') : 0;
543 $rowCount = isset($_REQUEST['length']) ? CRM_Utils_Type::escape($_REQUEST['length'], 'Integer') : 25;
6a488035 544
dc6285d5 545 $gid = CRM_Utils_Request::retrieve('gid', 'Positive');
546 $rgid = CRM_Utils_Request::retrieve('rgid', 'Positive');
997a03fe 547 $limit = CRM_Utils_Request::retrieveValue('limit', 'Positive', 0);
9f54f049 548 $null = NULL;
549 $criteria = CRM_Utils_Request::retrieve('criteria', 'Json', $null, FALSE, '{}');
bb22928b 550 $selected = CRM_Utils_Request::retrieveValue('selected', 'Boolean');
63ef778e 551 if ($rowCount < 0) {
552 $rowCount = 0;
553 }
6a488035 554
1f51ef4e 555 $whereClause = $orderByClause = '';
997a03fe 556 $cacheKeyString = CRM_Dedupe_Merger::getMergeCacheKeyString($rgid, $gid, json_decode($criteria, TRUE), TRUE, $limit);
9f54f049 557
69078420 558 $searchRows = [];
6a488035 559
ed92673b 560 $searchParams = self::getSearchOptionsFromRequest();
be2fb01f 561 $queryParams = [];
ed92673b 562
63ef778e 563 $join = '';
be2fb01f 564 $where = [];
579ea9bc 565
ed92673b 566 $isOrQuery = self::isOrQuery();
567
568 $nextParamKey = 3;
be2fb01f 569 $mappings = [
e67dcaf8 570 'dst' => 'cc1.display_name',
571 'src' => 'cc2.display_name',
572 'dst_email' => 'ce1.email',
573 'src_email' => 'ce2.email',
574 'dst_postcode' => 'ca1.postal_code',
575 'src_postcode' => 'ca2.postal_code',
576 'dst_street' => 'ca1.street',
577 'src_street' => 'ca2.street',
be2fb01f 578 ];
ed92673b 579
580 foreach ($mappings as $key => $dbName) {
581 if (!empty($searchParams[$key])) {
12af7add 582 // CRM-18694.
583 $wildcard = strstr($key, 'postcode') ? '' : '%';
be2fb01f 584 $queryParams[$nextParamKey] = [$wildcard . $searchParams[$key] . '%', 'String'];
ed92673b 585 $where[] = $dbName . " LIKE %{$nextParamKey} ";
586 $nextParamKey++;
587 }
63ef778e 588 }
ed92673b 589
590 if ($isOrQuery) {
69078420 591 $whereClause = ' ( ' . implode(' OR ', $where) . ' ) ';
63ef778e 592 }
593 else {
594 if (!empty($where)) {
69078420 595 $whereClause = implode(' AND ', $where);
63ef778e 596 }
f931b74c 597 }
63ef778e 598 $whereClause .= $whereClause ? ' AND de.id IS NULL' : ' de.id IS NULL';
6a488035 599
63ef778e 600 if ($selected) {
601 $whereClause .= ' AND pn.is_selected = 1';
602 }
2988f5c7 603 $join .= CRM_Dedupe_Merger::getJoinOnDedupeTable();
63ef778e 604
be2fb01f 605 $select = [
e67dcaf8 606 'cc1.contact_type' => 'dst_contact_type',
607 'cc1.display_name' => 'dst_display_name',
608 'cc1.contact_sub_type' => 'dst_contact_sub_type',
609 'cc2.contact_type' => 'src_contact_type',
610 'cc2.display_name' => 'src_display_name',
611 'cc2.contact_sub_type' => 'src_contact_sub_type',
612 'ce1.email' => 'dst_email',
613 'ce2.email' => 'src_email',
614 'ca1.postal_code' => 'dst_postcode',
615 'ca2.postal_code' => 'src_postcode',
616 'ca1.street_address' => 'dst_street',
617 'ca2.street_address' => 'src_street',
be2fb01f 618 ];
63ef778e 619
f931b74c 620 if ($select) {
63ef778e 621 $join .= " INNER JOIN civicrm_contact cc1 ON cc1.id = pn.entity_id1";
622 $join .= " INNER JOIN civicrm_contact cc2 ON cc2.id = pn.entity_id2";
623 $join .= " LEFT JOIN civicrm_email ce1 ON (ce1.contact_id = pn.entity_id1 AND ce1.is_primary = 1 )";
624 $join .= " LEFT JOIN civicrm_email ce2 ON (ce2.contact_id = pn.entity_id2 AND ce2.is_primary = 1 )";
625 $join .= " LEFT JOIN civicrm_address ca1 ON (ca1.contact_id = pn.entity_id1 AND ca1.is_primary = 1 )";
626 $join .= " LEFT JOIN civicrm_address ca2 ON (ca2.contact_id = pn.entity_id2 AND ca2.is_primary = 1 )";
627 }
ed92673b 628 $iTotal = CRM_Core_BAO_PrevNextCache::getCount($cacheKeyString, $join, $whereClause, '=', $queryParams);
1f51ef4e 629 if (!empty($_REQUEST['order'])) {
630 foreach ($_REQUEST['order'] as $orderInfo) {
631 if (!empty($orderInfo['column'])) {
632 $orderColumnNumber = $orderInfo['column'];
2c032aca 633 $dir = CRM_Utils_Type::escape($orderInfo['dir'], 'MysqlOrderByDirection', FALSE);
1f51ef4e 634 }
63ef778e 635 }
9c1bc317 636 $columnDetails = $_REQUEST['columns'][$orderColumnNumber] ?? NULL;
63ef778e 637 }
f931b74c 638 if (!empty($columnDetails)) {
63ef778e 639 switch ($columnDetails['data']) {
f931b74c 640 case 'src':
e67dcaf8 641 $orderByClause = " ORDER BY cc2.display_name {$dir}";
f931b74c 642 break;
643
644 case 'src_email':
e67dcaf8 645 $orderByClause = " ORDER BY ce2.email {$dir}";
f931b74c 646 break;
647
648 case 'src_street':
e67dcaf8 649 $orderByClause = " ORDER BY ca2.street_address {$dir}";
f931b74c 650 break;
651
652 case 'src_postcode':
e67dcaf8 653 $orderByClause = " ORDER BY ca2.postal_code {$dir}";
f931b74c 654 break;
655
656 case 'dst':
e67dcaf8 657 $orderByClause = " ORDER BY cc1.display_name {$dir}";
f931b74c 658 break;
659
660 case 'dst_email':
e67dcaf8 661 $orderByClause = " ORDER BY ce1.email {$dir}";
f931b74c 662 break;
663
664 case 'dst_street':
e67dcaf8 665 $orderByClause = " ORDER BY ca1.street_address {$dir}";
f931b74c 666 break;
667
668 case 'dst_postcode':
e67dcaf8 669 $orderByClause = " ORDER BY ca1.postal_code {$dir}";
f931b74c 670 break;
671
672 default:
063ffcb7 673 $orderByClause = " ORDER BY cc1.display_name ASC";
f931b74c 674 break;
63ef778e 675 }
676 }
6a488035 677
ed92673b 678 $dupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, $join, $whereClause, $offset, $rowCount, $select, $orderByClause, TRUE, $queryParams);
63ef778e 679 $iFilteredTotal = CRM_Core_DAO::singleValueQuery("SELECT FOUND_ROWS()");
6a488035 680
63ef778e 681 $count = 0;
682 foreach ($dupePairs as $key => $pairInfo) {
e67dcaf8 683 $pair = $pairInfo['data'];
9c1bc317
CW
684 $srcContactSubType = $pairInfo['src_contact_sub_type'] ?? NULL;
685 $dstContactSubType = $pairInfo['dst_contact_sub_type'] ?? NULL;
63ef778e 686 $srcTypeImage = CRM_Contact_BAO_Contact_Utils::getImage($srcContactSubType ?
687 $srcContactSubType : $pairInfo['src_contact_type'],
688 FALSE,
e67dcaf8 689 $pairInfo['entity_id2']
63ef778e 690 );
691 $dstTypeImage = CRM_Contact_BAO_Contact_Utils::getImage($dstContactSubType ?
692 $dstContactSubType : $pairInfo['dst_contact_type'],
693 FALSE,
e67dcaf8 694 $pairInfo['entity_id1']
63ef778e 695 );
6a488035 696
63ef778e 697 $searchRows[$count]['is_selected'] = $pairInfo['is_selected'];
698 $searchRows[$count]['is_selected_input'] = "<input type='checkbox' class='crm-dedupe-select' name='pnid_{$pairInfo['prevnext_id']}' value='{$pairInfo['is_selected']}' onclick='toggleDedupeSelect(this)'>";
699 $searchRows[$count]['src_image'] = $srcTypeImage;
e67dcaf8 700 $searchRows[$count]['src'] = CRM_Utils_System::href($pair['srcName'], 'civicrm/contact/view', "reset=1&cid={$pairInfo['entity_id2']}");
9c1bc317
CW
701 $searchRows[$count]['src_email'] = $pairInfo['src_email'] ?? NULL;
702 $searchRows[$count]['src_street'] = $pairInfo['src_street'] ?? NULL;
703 $searchRows[$count]['src_postcode'] = $pairInfo['src_postcode'] ?? NULL;
63ef778e 704 $searchRows[$count]['dst_image'] = $dstTypeImage;
e67dcaf8 705 $searchRows[$count]['dst'] = CRM_Utils_System::href($pair['dstName'], 'civicrm/contact/view', "reset=1&cid={$pairInfo['entity_id1']}");
9c1bc317
CW
706 $searchRows[$count]['dst_email'] = $pairInfo['dst_email'] ?? NULL;
707 $searchRows[$count]['dst_street'] = $pairInfo['dst_street'] ?? NULL;
708 $searchRows[$count]['dst_postcode'] = $pairInfo['dst_postcode'] ?? NULL;
da3afd4a 709 $searchRows[$count]['conflicts'] = str_replace("',", "',<br/>", CRM_Utils_Array::value('conflicts', $pair));
9c1bc317 710 $searchRows[$count]['weight'] = $pair['weight'] ?? NULL;
63ef778e 711
fd630ef9 712 if (!empty($pairInfo['data']['canMerge'])) {
c0cc2ad4 713 $mergeParams = [
714 'reset' => 1,
69078420
SL
715 'cid' => $pairInfo['entity_id1'],
716 'oid' => $pairInfo['entity_id2'],
717 'action' => 'update',
718 'rgid' => $rgid,
719 'criteria' => $criteria,
720 'limit' => CRM_Utils_Request::retrieve('limit', 'Integer'),
721 ];
6a488035 722 if ($gid) {
c0cc2ad4 723 $mergeParams['gid'] = $gid;
6a488035
TO
724 }
725
fd630ef9 726 $searchRows[$count]['actions'] = "<a class='crm-dedupe-flip' href='#' data-pnid={$pairInfo['prevnext_id']}>" . ts('flip') . "</a>&nbsp;|&nbsp;";
da3afd4a 727 $searchRows[$count]['actions'] .= CRM_Utils_System::href(ts('merge'), 'civicrm/contact/merge', $mergeParams);
fd630ef9 728 $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
729 }
730 else {
63ef778e 731 $searchRows[$count]['actions'] = '<em>' . ts('Insufficient access rights - cannot merge') . '</em>';
6a488035 732 }
63ef778e 733 $count++;
6a488035
TO
734 }
735
be2fb01f 736 $dupePairs = [
22b232f3 737 'data' => $searchRows,
738 'recordsTotal' => $iTotal,
739 'recordsFiltered' => $iFilteredTotal,
be2fb01f 740 ];
1f51ef4e 741 if (!empty($_REQUEST['is_unit_test'])) {
742 return $dupePairs;
743 }
22b232f3 744 CRM_Utils_JSON::output($dupePairs);
6a488035
TO
745 }
746
ed92673b 747 /**
748 * Get the searchable options from the request.
749 *
750 * @return array
751 */
752 public static function getSearchOptionsFromRequest() {
be2fb01f 753 $searchParams = [];
9c1bc317 754 $searchData = $_REQUEST['search'] ?? NULL;
32c3b33f 755 $searchData['value'] = CRM_Utils_Type::escape($searchData['value'] ?? NULL, 'String');
be2fb01f 756 $selectorElements = [
ed92673b 757 'is_selected',
758 'is_selected_input',
759 'src_image',
760 'src',
761 'src_email',
762 'src_street',
763 'src_postcode',
764 'dst_image',
765 'dst',
766 'dst_email',
767 'dst_street',
768 'dst_postcode',
769 'conflicts',
770 'weight',
771 'actions',
be2fb01f 772 ];
ed92673b 773 $columns = $_REQUEST['columns'];
774
775 foreach ($columns as $column) {
776 if (!empty($column['search']['value']) && in_array($column['data'], $selectorElements)) {
777 $searchParams[$column['data']] = CRM_Utils_Type::escape($column['search']['value'], 'String');
778 }
779 elseif (!empty($searchData['value'])) {
780 $searchParams[$column['data']] = $searchData['value'];
781 }
782 }
783 return $searchParams;
784 }
785
786 /**
787 * Is the query an OR query.
788 *
789 * If a generic search value is passed in - ie. $_REQUEST['search']['value'] = 'abc'
790 * then all fields are searched for this.
791 *
792 * It is unclear if there is any code that still passes this in or whether is is just legacy. It
793 * could cause a server-killing query on a large site so it probably is NOT in use if we haven't
794 * had complaints.
795 *
796 * @return bool
797 */
798 public static function isOrQuery() {
9c1bc317 799 $searchData = $_REQUEST['search'] ?? NULL;
ed92673b 800 return !empty($searchData['value']);
801 }
802
2d1fefa0 803 /**
804 * Mark not duplicates.
805 *
806 * Note this function would sensibly be replaced by an api-call but extracting here to add a test first.
807 *
808 * I would have like to make it private but test class accesses it & it doesn't warrant being a BAO class
809 * as it should feel very endangered.
810 *
811 * @param int $cid
812 * @param int $oid
041ecc95 813 * @param "dupe-nondupe|nondupe-dupe" $oper
2d1fefa0 814 *
815 * @return \CRM_Core_DAO|mixed|null
816 */
817 public static function markNonDuplicates($cid, $oid, $oper) {
ac2751b3 818 if ($oper == 'dupe-nondupe') {
819 try {
820 civicrm_api3('Exception', 'create', ['contact_id1' => $cid, 'contact_id2' => $oid]);
821 return TRUE;
822 }
823 catch (CiviCRM_API3_Exception $e) {
824 return FALSE;
825 }
826 }
827
2d1fefa0 828 $exception = new CRM_Dedupe_DAO_Exception();
829 $exception->contact_id1 = $cid;
830 $exception->contact_id2 = $oid;
831 //make sure contact2 > contact1.
832 if ($cid > $oid) {
833 $exception->contact_id1 = $oid;
834 $exception->contact_id2 = $cid;
835 }
836 $exception->find(TRUE);
837 $status = NULL;
ac2751b3 838
2d1fefa0 839 if ($oper == 'nondupe-dupe') {
840 $status = $exception->delete();
841 }
842 return $status;
843 }
844
6a488035 845 /**
fe482240 846 * Retrieve a PDF Page Format for the PDF Letter form.
6a488035 847 */
91aafe90 848 public static function pdfFormat() {
6a488035
TO
849 $formatId = CRM_Utils_Type::escape($_REQUEST['formatId'], 'Integer');
850
851 $pdfFormat = CRM_Core_BAO_PdfFormat::getById($formatId);
852
ecdef330 853 CRM_Utils_JSON::output($pdfFormat);
6a488035
TO
854 }
855
856 /**
fe482240 857 * Retrieve Paper Size dimensions.
6a488035 858 */
00be9182 859 public static function paperSize() {
6a488035
TO
860 $paperSizeName = CRM_Utils_Type::escape($_REQUEST['paperSizeName'], 'String');
861
862 $paperSize = CRM_Core_BAO_PaperSize::getByName($paperSizeName);
863
ecdef330 864 CRM_Utils_JSON::output($paperSize);
6a488035
TO
865 }
866
183ec330 867 /**
868 * Swap contacts in a dupe pair i.e main with duplicate contact.
ea3ddccf 869 *
870 * @param int $prevNextId
183ec330 871 */
f931b74c 872 public static function flipDupePairs($prevNextId = NULL) {
fd630ef9 873 if (!$prevNextId) {
808c05a9 874 // @todo figure out if this is always POST & specify that rather than inexact GET
6d13d1c4
ML
875
876 // We cannot use CRM_Utils_Request::retrieve() because it might be an array.
877 // It later gets validated in escapeAll below.
878 $prevNextId = $_REQUEST['pnid'];
fd630ef9 879 }
808c05a9 880
881 $onlySelected = FALSE;
fd630ef9 882 if (is_array($prevNextId) && !CRM_Utils_Array::crmIsEmptyArray($prevNextId)) {
808c05a9 883 $onlySelected = TRUE;
fd630ef9 884 }
808c05a9 885 $prevNextId = CRM_Utils_Type::escapeAll((array) $prevNextId, 'Positive');
886 CRM_Core_BAO_PrevNextCache::flipPair($prevNextId, $onlySelected);
558cd7c7 887 CRM_Utils_System::civiExit();
fd630ef9 888 }
889
aeb97cc1
CW
890 /**
891 * Used to store selected contacts across multiple pages in advanced search.
892 */
00be9182 893 public static function selectUnselectContacts() {
9c1bc317
CW
894 $name = $_REQUEST['name'] ?? NULL;
895 $cacheKey = $_REQUEST['qfKey'] ?? NULL;
353ffa53 896 $state = CRM_Utils_Array::value('state', $_REQUEST, 'checked');
6a488035
TO
897 $variableType = CRM_Utils_Array::value('variableType', $_REQUEST, 'single');
898
899 $actionToPerform = CRM_Utils_Array::value('action', $_REQUEST, 'select');
900
901 if ($variableType == 'multiple') {
902 // action post value only works with multiple type variable
903 if ($name) {
904 //multiple names like mark_x_1-mark_x_2 where 1,2 are cids
905 $elements = explode('-', $name);
906 foreach ($elements as $key => $element) {
907 $elements[$key] = self::_convertToId($element);
908 }
91209206 909 CRM_Utils_Type::escapeAll($elements, 'Integer');
0b8038a6 910 Civi::service('prevnext')->markSelection($cacheKey, $actionToPerform, $elements);
6a488035
TO
911 }
912 else {
0b8038a6 913 Civi::service('prevnext')->markSelection($cacheKey, $actionToPerform);
6a488035
TO
914 }
915 }
916 elseif ($variableType == 'single') {
917 $cId = self::_convertToId($name);
91209206 918 CRM_Utils_Type::escape($cId, 'Integer');
6a488035 919 $action = ($state == 'checked') ? 'select' : 'unselect';
0b8038a6 920 Civi::service('prevnext')->markSelection($cacheKey, $action, $cId);
6a488035 921 }
b7994703 922 $contactIds = Civi::service('prevnext')->getSelection($cacheKey);
6a488035
TO
923 $countSelectionCids = count($contactIds[$cacheKey]);
924
be2fb01f 925 $arrRet = ['getCount' => $countSelectionCids];
ecdef330 926 CRM_Utils_JSON::output($arrRet);
6a488035
TO
927 }
928
4319322b 929 /**
100fef9d 930 * @param string $name
4319322b
EM
931 *
932 * @return string
933 */
00be9182 934 public static function _convertToId($name) {
6a488035
TO
935 if (substr($name, 0, CRM_Core_Form::CB_PREFIX_LEN) == CRM_Core_Form::CB_PREFIX) {
936 $cId = substr($name, CRM_Core_Form::CB_PREFIX_LEN);
937 }
938 return $cId;
939 }
940
00be9182 941 public static function getAddressDisplay() {
a3d827a7 942 $contactId = CRM_Utils_Request::retrieve('contact_id', 'Positive');
6a488035
TO
943 if (!$contactId) {
944 $addressVal["error_message"] = "no contact id found";
945 }
946 else {
be2fb01f 947 $entityBlock = [
408b79bf 948 'contact_id' => $contactId,
949 'entity_id' => $contactId,
be2fb01f 950 ];
6a488035
TO
951 $addressVal = CRM_Core_BAO_Address::getValues($entityBlock);
952 }
953
ecdef330 954 CRM_Utils_JSON::output($addressVal);
6a488035 955 }
40458f6c 956
183ec330 957 /**
958 * Mark dupe pairs as selected from un-selected state or vice-versa, in dupe cache table.
959 */
f931b74c 960 public static function toggleDedupeSelect() {
63ef778e 961 $pnid = $_REQUEST['pnid'];
962 $isSelected = CRM_Utils_Type::escape($_REQUEST['is_selected'], 'Boolean');
9d2f6d53 963 $cacheKeyString = CRM_Utils_Request::retrieve('cacheKey', 'Alphanumeric', $null, FALSE);
63ef778e 964
be2fb01f
CW
965 $params = [
966 1 => [$isSelected, 'Boolean'],
69078420
SL
967 // using % to address rows with conflicts as well
968 3 => ["$cacheKeyString%", 'String'],
be2fb01f 969 ];
63ef778e 970
971 //check pnid is_array or integer
972 $whereClause = NULL;
973 if (is_array($pnid) && !CRM_Utils_Array::crmIsEmptyArray($pnid)) {
13c42e60 974 CRM_Utils_Type::escapeAll($pnid, 'Positive');
63ef778e 975 $pnid = implode(', ', $pnid);
63ef778e 976 $whereClause = " id IN ( {$pnid} ) ";
977 }
978 else {
979 $pnid = CRM_Utils_Type::escape($pnid, 'Integer');
980 $whereClause = " id = %2";
be2fb01f 981 $params[2] = [$pnid, 'Integer'];
63ef778e 982 }
983
783b4b21 984 $sql = "UPDATE civicrm_prevnext_cache SET is_selected = %1 WHERE {$whereClause} AND cachekey LIKE %3";
63ef778e 985 CRM_Core_DAO::executeQuery($sql, $params);
986
987 CRM_Utils_System::civiExit();
988 }
989
40458f6c 990 /**
fe482240 991 * Retrieve contact relationships.
40458f6c 992 */
993 public static function getContactRelationships() {
994 $contactID = CRM_Utils_Type::escape($_GET['cid'], 'Integer');
edc80cda 995 $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric');
7d12de7f 996 $relationship_type_id = CRM_Utils_Type::escape(CRM_Utils_Array::value('relationship_type_id', $_GET), 'Integer', FALSE);
40458f6c 997
b0266403
CW
998 if (!CRM_Contact_BAO_Contact_Permission::allow($contactID)) {
999 return CRM_Utils_System::permissionDenied();
1000 }
1001
00f11506 1002 $params = CRM_Core_Page_AJAX::defaultSortAndPagerParams();
40458f6c 1003
1004 $params['contact_id'] = $contactID;
1005 $params['context'] = $context;
4c5f31d0 1006 if ($relationship_type_id) {
cdee9432
TM
1007 $params['relationship_type_id'] = $relationship_type_id;
1008 }
40458f6c 1009
1010 // get the contact relationships
1011 $relationships = CRM_Contact_BAO_Relationship::getContactRelationshipSelector($params);
1012
7d12de7f 1013 CRM_Utils_JSON::output($relationships);
40458f6c 1014 }
96025800 1015
6a488035 1016}