Merge pull request #15590 from alifrumin/subjecthelp
[civicrm-core.git] / CRM / Campaign / BAO / Query.php
CommitLineData
6a488035 1<?php
6a488035
TO
2/*
3 +--------------------------------------------------------------------+
fee14197 4 | CiviCRM version 5 |
6a488035 5 +--------------------------------------------------------------------+
6b83d5bd 6 | Copyright CiviCRM LLC (c) 2004-2019 |
6a488035
TO
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035
TO
27
28/**
29 *
30 * @package CRM
6b83d5bd 31 * @copyright CiviCRM LLC (c) 2004-2019
6a488035
TO
32 */
33class CRM_Campaign_BAO_Query {
34 //since normal activity clause clause get collides.
7da04cde 35 const
6a488035
TO
36 CIVICRM_ACTIVITY = 'civicrm_survey_activity',
37 CIVICRM_ACTIVITY_TARGET = 'civicrm_survey_activity_target',
38 CIVICRM_ACTIVITY_ASSIGNMENT = 'civicrm_survey_activity_assignment';
39
40 /**
fe482240 41 * Static field for all the campaign fields.
6a488035
TO
42 *
43 * @var array
6a488035 44 */
f157740d 45 public static $_campaignFields = NULL;
6a488035 46
f157740d 47 public static $_applySurveyClause = FALSE;
6a488035
TO
48
49 /**
50 * Function get the fields for campaign.
51 *
a6c01b45
CW
52 * @return array
53 * self::$_campaignFields an associative array of campaign fields
6a488035 54 */
00be9182 55 public static function &getFields() {
6a488035 56 if (!isset(self::$_campaignFields)) {
be2fb01f 57 self::$_campaignFields = [];
6a488035
TO
58 }
59
60 return self::$_campaignFields;
61 }
62
63 /**
100fef9d 64 * If survey, campaign are involved, add the specific fields.
6a488035 65 *
ce064e4f 66 * @param CRM_Contact_BAO_Contact $query
6a488035 67 */
00be9182 68 public static function select(&$query) {
6a488035
TO
69 self::$_applySurveyClause = FALSE;
70 if (is_array($query->_params)) {
71 foreach ($query->_params as $values) {
72 if (!is_array($values) || count($values) != 5) {
73 continue;
74 }
75
76 list($name, $op, $value, $grouping, $wildcard) = $values;
77 if ($name == 'campaign_survey_id') {
78 self::$_applySurveyClause = TRUE;
79 break;
80 }
81 }
82 }
82a43d71 83 // CRM-13810 Translate campaign_id to label for search builder
4341a7a6 84 // CRM-14238 Only translate when we are in contact mode
85 // Other modes need the untranslated data for export and other functions
353ffa53 86 if (is_array($query->_select) && $query->_mode == CRM_Contact_BAO_Query::MODE_CONTACTS) {
22e263ad 87 foreach ($query->_select as $field => $queryString) {
82a43d71 88 if (substr($field, -11) == 'campaign_id') {
be2fb01f 89 $query->_pseudoConstantsSelect[$field] = [
82a43d71
CW
90 'pseudoField' => 'campaign_id',
91 'idCol' => $field,
92 'bao' => 'CRM_Activity_BAO_Activity',
be2fb01f 93 ];
82a43d71
CW
94 }
95 }
96 }
97
6a488035
TO
98 //get survey clause in force,
99 //only when we have survey id.
100 if (!self::$_applySurveyClause) {
101 return;
102 }
103
104 //all below tables are require to fetch result.
105
106 //1. get survey activity target table in.
e7a308ff 107 $query->_select['survey_activity_target_contact_id'] = 'civicrm_activity_target.contact_id as survey_activity_target_contact_id';
6a488035
TO
108 $query->_select['survey_activity_target_id'] = 'civicrm_activity_target.id as survey_activity_target_id';
109 $query->_element['survey_activity_target_id'] = 1;
110 $query->_element['survey_activity_target_contact_id'] = 1;
111 $query->_tables[self::CIVICRM_ACTIVITY_TARGET] = 1;
112 $query->_whereTables[self::CIVICRM_ACTIVITY_TARGET] = 1;
113
114 //2. get survey activity table in.
115 $query->_select['survey_activity_id'] = 'civicrm_activity.id as survey_activity_id';
116 $query->_element['survey_activity_id'] = 1;
117 $query->_tables[self::CIVICRM_ACTIVITY] = 1;
118 $query->_whereTables[self::CIVICRM_ACTIVITY] = 1;
119
120 //3. get the assignee table in.
121 $query->_select['survey_interviewer_id'] = 'civicrm_activity_assignment.id as survey_interviewer_id';
122 $query->_element['survey_interviewer_id'] = 1;
123 $query->_tables[self::CIVICRM_ACTIVITY_ASSIGNMENT] = 1;
124 $query->_whereTables[self::CIVICRM_ACTIVITY_ASSIGNMENT] = 1;
125
126 //4. get survey table.
127 $query->_select['campaign_survey_id'] = 'civicrm_survey.id as campaign_survey_id';
128 $query->_element['campaign_survey_id'] = 1;
129 $query->_tables['civicrm_survey'] = 1;
130 $query->_whereTables['civicrm_survey'] = 1;
131
132 //5. get campaign table.
133 $query->_select['campaign_id'] = 'civicrm_campaign.id as campaign_id';
134 $query->_element['campaign_id'] = 1;
135 $query->_tables['civicrm_campaign'] = 1;
136 $query->_whereTables['civicrm_campaign'] = 1;
137 }
138
30c4e065
EM
139 /**
140 * @param $query
141 */
00be9182 142 public static function where(&$query) {
6a488035
TO
143 //get survey clause in force,
144 //only when we have survey id.
145 if (!self::$_applySurveyClause) {
146 return;
147 }
148
149 $grouping = NULL;
150 foreach (array_keys($query->_params) as $id) {
395d8dc6 151 if ($query->_mode == CRM_Contact_BAO_Query::MODE_CONTACTS) {
6a488035
TO
152 $query->_useDistinct = TRUE;
153 }
154
155 self::whereClauseSingle($query->_params[$id], $query);
156 }
157 }
158
30c4e065
EM
159 /**
160 * @param $values
161 * @param $query
162 */
00be9182 163 public static function whereClauseSingle(&$values, &$query) {
6a488035
TO
164 //get survey clause in force,
165 //only when we have survey id.
166 if (!self::$_applySurveyClause) {
167 return;
168 }
169
170 list($name, $op, $value, $grouping, $wildcard) = $values;
171
172 switch ($name) {
173 case 'campaign_survey_id':
be2fb01f 174 $query->_qill[$grouping][] = ts('Survey - %1', [1 => CRM_Core_DAO::getFieldValue('CRM_Campaign_DAO_Survey', $value, 'title')]);
6a488035
TO
175
176 $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause('civicrm_activity.source_record_id',
e7a308ff 177 $op, $value, 'Integer'
6a488035
TO
178 );
179 $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause('civicrm_survey.id',
e7a308ff 180 $op, $value, 'Integer'
6a488035
TO
181 );
182 return;
183
184 case 'survey_status_id':
185 $activityStatus = CRM_Core_PseudoConstant::activityStatus();
186
be2fb01f 187 $query->_qill[$grouping][] = ts('Survey Status - %1', [1 => $activityStatus[$value]]);
6a488035 188 $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause('civicrm_activity.status_id',
e7a308ff 189 $op, $value, 'Integer'
6a488035
TO
190 );
191 return;
192
193 case 'campaign_search_voter_for':
be2fb01f 194 if (in_array($value, ['release', 'interview'])) {
6a488035
TO
195 $query->_where[$grouping][] = '(civicrm_activity.is_deleted = 0 OR civicrm_activity.is_deleted IS NULL)';
196 }
197 return;
198
199 case 'survey_interviewer_id':
56ecdf7b 200 $surveyInterviewerName = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $value, 'sort_name');
be2fb01f 201 $query->_qill[$grouping][] = ts('Survey Interviewer - %1', [1 => $surveyInterviewerName]);
e7a308ff 202 $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause('civicrm_activity_assignment.contact_id',
203 $op, $value, 'Integer'
6a488035
TO
204 );
205 return;
206 }
207 }
208
30c4e065 209 /**
100fef9d 210 * @param string $name
30c4e065
EM
211 * @param $mode
212 * @param $side
213 *
214 * @return null|string
215 */
00be9182 216 public static function from($name, $mode, $side) {
6a488035
TO
217 $from = NULL;
218 //get survey clause in force,
219 //only when we have survey id.
220 if (!self::$_applySurveyClause) {
221 return $from;
222 }
223
44f817d4 224 $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
9e74e3ce 225 $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts);
226 $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts);
227 $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
8ef12e64 228
6a488035
TO
229 switch ($name) {
230 case self::CIVICRM_ACTIVITY_TARGET:
e7a308ff 231 $from = " INNER JOIN civicrm_activity_contact civicrm_activity_target
9e74e3ce 232 ON ( civicrm_activity_target.contact_id = contact_a.id AND civicrm_activity_target.record_type_id = $targetID) ";
6a488035
TO
233 break;
234
235 case self::CIVICRM_ACTIVITY:
236 $surveyActivityTypes = CRM_Campaign_PseudoConstant::activityType();
353ffa53
TO
237 $surveyKeys = "(" . implode(',', array_keys($surveyActivityTypes)) . ")";
238 $from = " INNER JOIN civicrm_activity ON ( civicrm_activity.id = civicrm_activity_target.activity_id
6a488035
TO
239 AND civicrm_activity.activity_type_id IN $surveyKeys ) ";
240 break;
241
242 case self::CIVICRM_ACTIVITY_ASSIGNMENT:
243 $from = "
e7a308ff 244INNER JOIN civicrm_activity_contact civicrm_activity_assignment ON ( civicrm_activity.id = civicrm_activity_assignment.activity_id AND
9e74e3ce 245civicrm_activity_assignment.record_type_id = $assigneeID ) ";
6a488035
TO
246 break;
247
248 case 'civicrm_survey':
249 $from = " INNER JOIN civicrm_survey ON ( civicrm_survey.id = civicrm_activity.source_record_id ) ";
250 break;
251
252 case 'civicrm_campaign':
253 $from = " $side JOIN civicrm_campaign ON ( civicrm_campaign.id = civicrm_survey.campaign_id ) ";
254 break;
255 }
256
257 return $from;
258 }
259
30c4e065
EM
260 /**
261 * @param $mode
262 * @param bool $includeCustomFields
263 *
264 * @return array|null
265 */
408b79bf 266 public static function defaultReturnProperties(
5c2ea586 267 $mode,
6a488035
TO
268 $includeCustomFields = TRUE
269 ) {
270 $properties = NULL;
271 if ($mode & CRM_Contact_BAO_Query::MODE_CAMPAIGN) {
be2fb01f 272 $properties = [
6a488035
TO
273 'contact_id' => 1,
274 'contact_type' => 1,
275 'contact_sub_type' => 1,
276 'sort_name' => 1,
277 'display_name' => 1,
278 'street_unit' => 1,
279 'street_name' => 1,
280 'street_number' => 1,
281 'street_address' => 1,
282 'city' => 1,
283 'postal_code' => 1,
284 'state_province' => 1,
285 'country' => 1,
286 'email' => 1,
287 'phone' => 1,
288 'survey_activity_target_id' => 1,
289 'survey_activity_id' => 1,
290 'survey_status_id' => 1,
291 'campaign_survey_id' => 1,
292 'campaign_id' => 1,
293 'survey_interviewer_id' => 1,
294 'survey_activity_target_contact_id' => 1,
be2fb01f 295 ];
6a488035
TO
296 }
297
298 return $properties;
299 }
300
30c4e065
EM
301 /**
302 * @param $tables
303 */
5c2ea586
TO
304 public static function tableNames(&$tables) {
305 }
30c4e065
EM
306
307 /**
308 * @param $row
100fef9d 309 * @param int $id
30c4e065 310 */
5c2ea586
TO
311 public static function searchAction(&$row, $id) {
312 }
6a488035 313
30c4e065
EM
314 /**
315 * @param $tables
316 */
00be9182 317 public static function info(&$tables) {
6a488035
TO
318 //get survey clause in force,
319 //only when we have survey id.
320 if (!self::$_applySurveyClause) {
321 return;
322 }
323
324 $weight = end($tables);
325 $tables[self::CIVICRM_ACTIVITY_TARGET] = ++$weight;
326 $tables[self::CIVICRM_ACTIVITY] = ++$weight;
327 $tables[self::CIVICRM_ACTIVITY_ASSIGNMENT] = ++$weight;
328 $tables['civicrm_survey'] = ++$weight;
329 $tables['civicrm_campaign'] = ++$weight;
330 }
331
332 /**
100fef9d 333 * Add all the elements shared between,
6a488035
TO
334 * normal voter search and voter listing (GOTV form)
335 *
56ecdf7b 336 * @param CRM_Core_Form $form
6a488035 337 */
00be9182 338 public static function buildSearchForm(&$form) {
6a488035
TO
339
340 $attributes = CRM_Core_DAO::getAttribute('CRM_Core_DAO_Address');
341 $className = CRM_Utils_System::getClassName($form);
342
343 $form->add('text', 'sort_name', ts('Contact Name'),
344 CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Contact', 'sort_name')
345 );
346 $form->add('text', 'street_name', ts('Street Name'), $attributes['street_name']);
347 $form->add('text', 'street_number', ts('Street Number'), $attributes['street_number']);
348 $form->add('text', 'street_unit', ts('Street Unit'), $attributes['street_unit']);
349 $form->add('text', 'street_address', ts('Street Address'), $attributes['street_address']);
350 $form->add('text', 'city', ts('City'), $attributes['city']);
d79be26c 351 $form->add('text', 'postal_code', ts('Postal Code'), $attributes['postal_code']);
6a488035 352
b500fbea
EM
353 //@todo FIXME - using the CRM_Core_DAO::VALUE_SEPARATOR creates invalid html - if you can find the form
354 // this is loaded onto then replace with something like '__' & test
355 $separator = CRM_Core_DAO::VALUE_SEPARATOR;
356 $contactTypes = CRM_Contact_BAO_ContactType::getSelectElements(FALSE, TRUE, $separator);
6a488035 357 $form->add('select', 'contact_type', ts('Contact Type(s)'), $contactTypes, FALSE,
be2fb01f 358 ['id' => 'contact_type', 'multiple' => 'multiple', 'class' => 'crm-select2']
6a488035 359 );
24431f7b 360 $groups = CRM_Core_PseudoConstant::nestedGroup();
6a488035 361 $form->add('select', 'group', ts('Groups'), $groups, FALSE,
be2fb01f 362 ['multiple' => 'multiple', 'class' => 'crm-select2']
6a488035
TO
363 );
364
365 $showInterviewer = FALSE;
366 if (CRM_Core_Permission::check('administer CiviCampaign')) {
367 $showInterviewer = TRUE;
368 }
369 $form->assign('showInterviewer', $showInterviewer);
370
371 if ($showInterviewer ||
372 $className == 'CRM_Campaign_Form_Gotv'
373 ) {
6a488035 374
be2fb01f 375 $form->addEntityRef('survey_interviewer_id', ts('Interviewer'), ['class' => 'big']);
6a488035
TO
376
377 $userId = NULL;
378 if (isset($form->_interviewerId) && $form->_interviewerId) {
379 $userId = $form->_interviewerId;
380 }
381 if (!$userId) {
395d8dc6 382 $session = CRM_Core_Session::singleton();
6a488035
TO
383 $userId = $session->get('userID');
384 }
385 if ($userId) {
be2fb01f 386 $defaults = [];
6a488035 387 $defaults['survey_interviewer_id'] = $userId;
6a488035
TO
388 $form->setDefaults($defaults);
389 }
390 }
391
392 //build ward and precinct custom fields.
393 $query = '
394 SELECT fld.id, fld.label
395 FROM civicrm_custom_field fld
396INNER JOIN civicrm_custom_group grp on fld.custom_group_id = grp.id
397 WHERE grp.name = %1';
be2fb01f
CW
398 $dao = CRM_Core_DAO::executeQuery($query, [1 => ['Voter_Info', 'String']]);
399 $customSearchFields = [];
6a488035 400 while ($dao->fetch()) {
5d4fcf54 401 foreach (['ward', 'precinct'] as $name) {
6a488035
TO
402 if (stripos($name, $dao->label) !== FALSE) {
403 $fieldId = $dao->id;
404 $fieldName = 'custom_' . $dao->id;
405 $customSearchFields[$name] = $fieldName;
3a7773be 406 CRM_Core_BAO_CustomField::addQuickFormElement($form, $fieldName, $fieldId, FALSE);
6a488035
TO
407 break;
408 }
409 }
410 }
411 $form->assign('customSearchFields', $customSearchFields);
412
413 $surveys = CRM_Campaign_BAO_Survey::getSurveys();
414
415 if (empty($surveys) &&
416 ($className == 'CRM_Campaign_Form_Search')
417 ) {
418 CRM_Core_Error::statusBounce(ts('Could not find survey for %1 respondents.',
be2fb01f 419 [1 => $form->get('op')]
6a488035
TO
420 ),
421 CRM_Utils_System::url('civicrm/survey/add',
422 'reset=1&action=add'
423 )
424 );
425 }
426
427 //CRM-7406 --
428 //If survey had associated campaign and
429 //campaign has some contact groups, don't
430 //allow to search the contacts those are not
431 //in given campaign groups ( ie not in constituents )
be2fb01f 432 $props = ['class' => 'crm-select2'];
6a488035 433 if ($form->get('searchVoterFor') == 'reserve') {
4145f041 434 $props['onChange'] = "buildCampaignGroups( );return false;";
6a488035 435 }
4145f041 436 $form->add('select', 'campaign_survey_id', ts('Survey'), $surveys, TRUE, $props);
6a488035
TO
437 }
438
439 /*
440 * Retrieve all valid voter ids,
441 * and build respective clause to restrict search.
442 *
7aaf6db0
TO
443 * @param array $criteria
444 * An array.
6a488035 445 * @return $voterClause as a string
6a488035 446 */
f157740d 447
30c4e065 448 /**
c490a46a 449 * @param array $params
30c4e065
EM
450 *
451 * @return array
452 */
f157740d 453 public static function voterClause($params) {
be2fb01f 454 $voterClause = [];
6a488035
TO
455 $fromClause = $whereClause = NULL;
456 if (!is_array($params) || empty($params)) {
457 return $voterClause;
458 }
353ffa53 459 $surveyId = CRM_Utils_Array::value('campaign_survey_id', $params);
6a488035
TO
460 $searchVoterFor = CRM_Utils_Array::value('campaign_search_voter_for', $params);
461
462 //get the survey activities.
463 $activityStatus = CRM_Core_PseudoConstant::activityStatus('name');
be2fb01f 464 $status = ['Scheduled'];
6a488035
TO
465 if ($searchVoterFor == 'reserve') {
466 $status[] = 'Completed';
467 }
468
469 $completedStatusId = NULL;
470 foreach ($status as $name) {
471 if ($statusId = array_search($name, $activityStatus)) {
472 $statusIds[] = $statusId;
473 if ($name == 'Completed') {
474 $completedStatusId = $statusId;
475 }
476 }
477 }
478
479 $voterActValues = CRM_Campaign_BAO_Survey::getSurveyVoterInfo($surveyId, NULL, $statusIds);
480
481 if (!empty($voterActValues)) {
482 $operator = 'IN';
483 $voterIds = array_keys($voterActValues);
484 if ($searchVoterFor == 'reserve') {
485 $operator = 'NOT IN';
486 //filter out recontact survey contacts.
487 $recontactInterval = CRM_Core_DAO::getFieldValue('CRM_Campaign_DAO_Survey',
488 $surveyId, 'recontact_interval'
489 );
490 $recontactInterval = unserialize($recontactInterval);
491 if ($surveyId &&
492 is_array($recontactInterval) &&
493 !empty($recontactInterval)
494 ) {
be2fb01f 495 $voterIds = [];
6a488035
TO
496 foreach ($voterActValues as $values) {
497 $numOfDays = CRM_Utils_Array::value($values['result'], $recontactInterval);
498 if ($numOfDays &&
499 $values['status_id'] == $completedStatusId
500 ) {
501 $recontactIntSeconds = $numOfDays * 24 * 3600;
353ffa53
TO
502 $actDateTimeSeconds = CRM_Utils_Date::unixTime($values['activity_date_time']);
503 $totalSeconds = $recontactIntSeconds + $actDateTimeSeconds;
6a488035
TO
504 //don't consider completed survey activity
505 //unless it fulfill recontact interval criteria.
506 if ($totalSeconds <= time()) {
507 continue;
508 }
509 }
510 $voterIds[$values['voter_id']] = $values['voter_id'];
511 }
512 }
513 }
514
515 //lets dump these ids in tmp table and
516 //use appropriate join depend on operator.
517 if (!empty($voterIds)) {
518 $voterIdCount = count($voterIds);
519
520 //create temporary table to store voter ids.
521 $tempTableName = CRM_Core_DAO::createTempTableName('civicrm_survey_respondent');
893a39a2 522 CRM_Core_DAO::executeQuery("DROP TEMPORARY TABLE IF EXISTS {$tempTableName}");
6a488035
TO
523
524 $query = "
525 CREATE TEMPORARY TABLE {$tempTableName} (
526 id int unsigned NOT NULL AUTO_INCREMENT,
527 survey_contact_id int unsigned NOT NULL,
528 PRIMARY KEY ( id )
529);
530";
531 CRM_Core_DAO::executeQuery($query);
532
533 $batch = 100;
534 $insertedCount = 0;
535 do {
536 $processIds = $voterIds;
537 $insertIds = array_splice($processIds, $insertedCount, $batch);
538 if (!empty($insertIds)) {
539 $insertSQL = "INSERT IGNORE INTO {$tempTableName}( survey_contact_id )
540 VALUES (" . implode('),(', $insertIds) . ');';
541 CRM_Core_DAO::executeQuery($insertSQL);
542 }
543 $insertedCount += $batch;
544 } while ($insertedCount < $voterIdCount);
545
546 if ($operator == 'IN') {
547 $fromClause = " INNER JOIN {$tempTableName} ON ( {$tempTableName}.survey_contact_id = contact_a.id )";
548 }
549 else {
550 $fromClause = " LEFT JOIN {$tempTableName} ON ( {$tempTableName}.survey_contact_id = contact_a.id )";
551 $whereClause = "( {$tempTableName}.survey_contact_id IS NULL )";
552 }
553 }
554 }
be2fb01f 555 $voterClause = [
6a488035
TO
556 'fromClause' => $fromClause,
557 'whereClause' => $whereClause,
be2fb01f 558 ];
6a488035
TO
559
560 return $voterClause;
561 }
562
6a488035 563}