Merge pull request #12492 from eileenmcnaughton/msg_fix
[civicrm-core.git] / CRM / Contact / Form / Task.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2018 |
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 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2018
32 */
33
34 /**
35 * This class generates form components for search-result tasks.
36 */
37 class CRM_Contact_Form_Task extends CRM_Core_Form_Task {
38
39 /**
40 * The task being performed
41 *
42 * @var int
43 */
44 protected $_task;
45
46 /**
47 * The array that holds all the contact ids
48 *
49 * @var array
50 */
51 public $_contactIds;
52
53 /**
54 * The array that holds all the contact types
55 *
56 * @var array
57 */
58 public $_contactTypes;
59
60 /**
61 * The additional clause that we restrict the search with
62 *
63 * @var string
64 */
65 protected $_componentClause = NULL;
66
67 /**
68 * The name of the temp table where we store the contact IDs
69 *
70 * @var string
71 */
72 protected $_componentTable = NULL;
73
74 /**
75 * The array that holds all the component ids
76 *
77 * @var array
78 */
79 protected $_componentIds;
80
81 /**
82 * This includes the submitted values of the search form
83 */
84 static protected $_searchFormValues;
85
86 /**
87 * Build all the data structures needed to build the form.
88 */
89 public function preProcess() {
90 self::preProcessCommon($this);
91 }
92
93 /**
94 * Common pre-processing function.
95 *
96 * @param CRM_Core_Form $form
97 */
98 public static function preProcessCommon(&$form) {
99 $form->_contactIds = array();
100 $form->_contactTypes = array();
101
102 $useTable = (CRM_Utils_System::getClassName($form->controller->getStateMachine()) == 'CRM_Export_StateMachine_Standalone');
103
104 $isStandAlone = in_array('task', $form->urlPath) || in_array('standalone', $form->urlPath);
105 if ($isStandAlone) {
106 list($form->_task, $title) = CRM_Contact_Task::getTaskAndTitleByClass(get_class($form));
107 if (!array_key_exists($form->_task, CRM_Contact_Task::permissionedTaskTitles(CRM_Core_Permission::getPermission()))) {
108 CRM_Core_Error::statusBounce(ts('You do not have permission to access this page.'));
109 }
110 $form->_contactIds = explode(',', CRM_Utils_Request::retrieve('cids', 'CommaSeparatedIntegers', $form, TRUE));
111 if (empty($form->_contactIds)) {
112 CRM_Core_Error::statusBounce(ts('No Contacts Selected'));
113 }
114 $form->setTitle($title);
115 }
116
117 // get the submitted values of the search form
118 // we'll need to get fv from either search or adv search in the future
119 $fragment = 'search';
120 if ($form->_action == CRM_Core_Action::ADVANCED) {
121 self::$_searchFormValues = $form->controller->exportValues('Advanced');
122 $fragment .= '/advanced';
123 }
124 elseif ($form->_action == CRM_Core_Action::PROFILE) {
125 self::$_searchFormValues = $form->controller->exportValues('Builder');
126 $fragment .= '/builder';
127 }
128 elseif ($form->_action == CRM_Core_Action::COPY) {
129 self::$_searchFormValues = $form->controller->exportValues('Custom');
130 $fragment .= '/custom';
131 }
132 elseif (!$isStandAlone) {
133 self::$_searchFormValues = $form->controller->exportValues('Basic');
134 }
135
136 //set the user context for redirection of task actions
137 $qfKey = CRM_Utils_Request::retrieve('qfKey', 'String', $form);
138 $urlParams = 'force=1';
139 if (CRM_Utils_Rule::qfKey($qfKey)) {
140 $urlParams .= "&qfKey=$qfKey";
141 }
142
143 $cacheKey = "civicrm search {$qfKey}";
144
145 $url = CRM_Utils_System::url('civicrm/contact/' . $fragment, $urlParams);
146 $session = CRM_Core_Session::singleton();
147 $session->replaceUserContext($url);
148
149 $form->_task = CRM_Utils_Array::value('task', self::$_searchFormValues);
150 $crmContactTaskTasks = CRM_Contact_Task::taskTitles();
151 $form->assign('taskName', CRM_Utils_Array::value($form->_task, $crmContactTaskTasks));
152
153 if ($useTable) {
154 $form->_componentTable = CRM_Utils_SQL_TempTable::build()->setCategory('tskact')->setDurable()->setId($qfKey)->getName();
155 $sql = " DROP TABLE IF EXISTS {$form->_componentTable}";
156 CRM_Core_DAO::executeQuery($sql);
157
158 $sql = "CREATE TABLE {$form->_componentTable} ( contact_id int primary key) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci";
159 CRM_Core_DAO::executeQuery($sql);
160 }
161
162 // all contacts or action = save a search
163 if ((CRM_Utils_Array::value('radio_ts', self::$_searchFormValues) == 'ts_all') ||
164 ($form->_task == CRM_Contact_Task::SAVE_SEARCH)
165 ) {
166 $sortByCharacter = $form->get('sortByCharacter');
167 $cacheKey = ($sortByCharacter && $sortByCharacter != 'all') ? "{$cacheKey}_alphabet" : $cacheKey;
168
169 // since we don't store all contacts in prevnextcache, when user selects "all" use query to retrieve contacts
170 // rather than prevnext cache table for most of the task actions except export where we rebuild query to fetch
171 // final result set
172 if ($useTable) {
173 $allCids = CRM_Core_BAO_PrevNextCache::getSelection($cacheKey, "getall");
174 }
175 else {
176 $allCids[$cacheKey] = self::getContactIds($form);
177 }
178
179 $form->_contactIds = array();
180 if ($useTable) {
181 $count = 0;
182 $insertString = array();
183 foreach ($allCids[$cacheKey] as $cid => $ignore) {
184 $count++;
185 $insertString[] = " ( {$cid} ) ";
186 if ($count % 200 == 0) {
187 $string = implode(',', $insertString);
188 $sql = "REPLACE INTO {$form->_componentTable} ( contact_id ) VALUES $string";
189 CRM_Core_DAO::executeQuery($sql);
190 $insertString = array();
191 }
192 }
193 if (!empty($insertString)) {
194 $string = implode(',', $insertString);
195 $sql = "REPLACE INTO {$form->_componentTable} ( contact_id ) VALUES $string";
196 CRM_Core_DAO::executeQuery($sql);
197 }
198 }
199 elseif (empty($form->_contactIds)) {
200 // filter duplicates here
201 // CRM-7058
202 // might be better to do this in the query, but that logic is a bit complex
203 // and it decides when to use distinct based on input criteria, which needs
204 // to be fixed and optimized.
205
206 foreach ($allCids[$cacheKey] as $cid => $ignore) {
207 $form->_contactIds[] = $cid;
208 }
209 }
210 }
211 elseif (CRM_Utils_Array::value('radio_ts', self::$_searchFormValues) == 'ts_sel') {
212 // selected contacts only
213 // need to perform action on only selected contacts
214 $insertString = array();
215
216 // refire sql in case of custom search
217 if ($form->_action == CRM_Core_Action::COPY) {
218 // selected contacts only
219 // need to perform action on only selected contacts
220 foreach (self::$_searchFormValues as $name => $value) {
221 if (substr($name, 0, CRM_Core_Form::CB_PREFIX_LEN) == CRM_Core_Form::CB_PREFIX) {
222 $contactID = substr($name, CRM_Core_Form::CB_PREFIX_LEN);
223 if ($useTable) {
224 $insertString[] = " ( {$contactID} ) ";
225 }
226 else {
227 $form->_contactIds[] = substr($name, CRM_Core_Form::CB_PREFIX_LEN);
228 }
229 }
230 }
231 }
232 else {
233 // fetching selected contact ids of passed cache key
234 $selectedCids = CRM_Core_BAO_PrevNextCache::getSelection($cacheKey);
235 foreach ($selectedCids[$cacheKey] as $selectedCid => $ignore) {
236 if ($useTable) {
237 $insertString[] = " ( {$selectedCid} ) ";
238 }
239 else {
240 $form->_contactIds[] = $selectedCid;
241 }
242 }
243 }
244
245 if (!empty($insertString)) {
246 $string = implode(',', $insertString);
247 $sql = "REPLACE INTO {$form->_componentTable} ( contact_id ) VALUES $string";
248 CRM_Core_DAO::executeQuery($sql);
249 }
250 }
251
252 //contact type for pick up profiles as per selected contact types with subtypes
253 //CRM-5521
254 if ($selectedTypes = CRM_Utils_Array::value('contact_type', self::$_searchFormValues)) {
255 if (!is_array($selectedTypes)) {
256 $selectedTypes = explode(' ', $selectedTypes);
257 }
258 foreach ($selectedTypes as $ct => $dontcare) {
259 if (strpos($ct, CRM_Core_DAO::VALUE_SEPARATOR) === FALSE) {
260 $form->_contactTypes[] = $ct;
261 }
262 else {
263 $separator = strpos($ct, CRM_Core_DAO::VALUE_SEPARATOR);
264 $form->_contactTypes[] = substr($ct, $separator + 1);
265 }
266 }
267 }
268
269 if (CRM_Utils_Array::value('radio_ts', self::$_searchFormValues) == 'ts_sel'
270 && ($form->_action != CRM_Core_Action::COPY)
271 ) {
272 $sel = CRM_Utils_Array::value('radio_ts', self::$_searchFormValues);
273 $form->assign('searchtype', $sel);
274 $result = CRM_Core_BAO_PrevNextCache::getSelectedContacts();
275 $form->assign("value", $result);
276 }
277
278 if (!empty($form->_contactIds)) {
279 $form->_componentClause = ' contact_a.id IN ( ' . implode(',', $form->_contactIds) . ' ) ';
280 $form->assign('totalSelectedContacts', count($form->_contactIds));
281
282 $form->_componentIds = $form->_contactIds;
283 }
284 }
285
286 /**
287 * Get the contact ids for:
288 * - "Select Records: All xx records"
289 * - custom search (FIXME: does this still apply to custom search?).
290 * When we call this function we are not using the prev/next cache
291 *
292 * @param $form CRM_Core_Form
293 *
294 * @return array $contactIds
295 */
296 public static function getContactIds($form) {
297 // need to perform action on all contacts
298 // fire the query again and get the contact id's + display name
299 $sortID = NULL;
300 if ($form->get(CRM_Utils_Sort::SORT_ID)) {
301 $sortID = CRM_Utils_Sort::sortIDValue($form->get(CRM_Utils_Sort::SORT_ID),
302 $form->get(CRM_Utils_Sort::SORT_DIRECTION)
303 );
304 }
305
306 $selectorName = $form->controller->selectorName();
307
308 $fv = $form->get('formValues');
309 $customClass = $form->get('customSearchClass');
310 $returnProperties = CRM_Core_BAO_Mapping::returnProperties(self::$_searchFormValues);
311
312 $selector = new $selectorName($customClass, $fv, NULL, $returnProperties);
313
314 $params = $form->get('queryParams');
315
316 // fix for CRM-5165
317 $sortByCharacter = $form->get('sortByCharacter');
318 if ($sortByCharacter && $sortByCharacter != 1) {
319 $params[] = array('sortByCharacter', '=', $sortByCharacter, 0, 0);
320 }
321 $queryOperator = $form->get('queryOperator');
322 if (!$queryOperator) {
323 $queryOperator = 'AND';
324 }
325 $dao = $selector->contactIDQuery($params, $sortID,
326 CRM_Utils_Array::value('display_relationship_type', $fv),
327 $queryOperator
328 );
329
330 $contactIds = array();
331 while ($dao->fetch()) {
332 $contactIds[$dao->contact_id] = $dao->contact_id;
333 }
334
335 return $contactIds;
336 }
337
338
339 /**
340 * Set default values for the form. Relationship that in edit/view action.
341 *
342 * The default values are retrieved from the database.
343 *
344 * @return array
345 */
346 public function setDefaultValues() {
347 $defaults = array();
348 return $defaults;
349 }
350
351 /**
352 * Add the rules for form.
353 */
354 public function addRules() {
355 }
356
357 /**
358 * Build the form object.
359 */
360 public function buildQuickForm() {
361 $this->addDefaultButtons(ts('Confirm Action'));
362 }
363
364 /**
365 * Process the form after the input has been submitted and validated.
366 */
367 public function postProcess() {
368 }
369
370 /**
371 * Simple shell that derived classes can call to add form buttons.
372 *
373 * Allows customized title for the main Submit
374 *
375 * @param string $title
376 * Title of the main button.
377 * @param string $nextType
378 * Button type for the form after processing.
379 * @param string $backType
380 * @param bool $submitOnce
381 */
382 public function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) {
383 $this->addButtons(array(
384 array(
385 'type' => $nextType,
386 'name' => $title,
387 'isDefault' => TRUE,
388 ),
389 array(
390 'type' => $backType,
391 'name' => ts('Cancel'),
392 'icon' => 'fa-times',
393 ),
394 )
395 );
396 }
397
398 /**
399 * Replace ids of household members in $this->_contactIds with the id of their household.
400 *
401 * CRM-8338
402 */
403 public function mergeContactIdsByHousehold() {
404 if (empty($this->_contactIds)) {
405 return;
406 }
407
408 $contactRelationshipTypes = CRM_Contact_BAO_Relationship::getContactRelationshipType(
409 NULL,
410 NULL,
411 NULL,
412 NULL,
413 TRUE,
414 'name',
415 FALSE
416 );
417
418 // Get Head of Household & Household Member relationships
419 $relationKeyMOH = CRM_Utils_Array::key('Household Member of', $contactRelationshipTypes);
420 $relationKeyHOH = CRM_Utils_Array::key('Head of Household for', $contactRelationshipTypes);
421 $householdRelationshipTypes = array(
422 $relationKeyMOH => $contactRelationshipTypes[$relationKeyMOH],
423 $relationKeyHOH => $contactRelationshipTypes[$relationKeyHOH],
424 );
425
426 $relID = implode(',', $this->_contactIds);
427
428 foreach ($householdRelationshipTypes as $rel => $dnt) {
429 list($id, $direction) = explode('_', $rel, 2);
430 // identify the relationship direction
431 $contactA = 'contact_id_a';
432 $contactB = 'contact_id_b';
433 if ($direction == 'b_a') {
434 $contactA = 'contact_id_b';
435 $contactB = 'contact_id_a';
436 }
437
438 // Find related households.
439 $relationSelect = "SELECT contact_household.id as household_id, {$contactA} as refContact ";
440 $relationFrom = " FROM civicrm_contact contact_household
441 INNER JOIN civicrm_relationship crel ON crel.{$contactB} = contact_household.id AND crel.relationship_type_id = {$id} ";
442
443 // Check for active relationship status only.
444 $today = date('Ymd');
445 $relationActive = " AND (crel.is_active = 1 AND ( crel.end_date is NULL OR crel.end_date >= {$today} ) )";
446 $relationWhere = " WHERE contact_household.is_deleted = 0 AND crel.{$contactA} IN ( {$relID} ) {$relationActive}";
447 $relationGroupBy = " GROUP BY crel.{$contactA}, contact_household.id";
448 $relationQueryString = "$relationSelect $relationFrom $relationWhere $relationGroupBy";
449
450 $householdsDAO = CRM_Core_DAO::executeQuery($relationQueryString);
451 while ($householdsDAO->fetch()) {
452 // Remove contact's id from $this->_contactIds and replace with their household's id.
453 foreach (array_keys($this->_contactIds, $householdsDAO->refContact) as $idKey) {
454 unset($this->_contactIds[$idKey]);
455 }
456 if (!in_array($householdsDAO->household_id, $this->_contactIds)) {
457 $this->_contactIds[] = $householdsDAO->household_id;
458 }
459 }
460 $householdsDAO->free();
461 }
462
463 // If contact list has changed, households will probably be at the end of
464 // the list. Sort it again by sort_name.
465 if (implode(',', $this->_contactIds) != $relID) {
466 $result = civicrm_api3('Contact', 'get', array(
467 'return' => array('id'),
468 'id' => array('IN' => $this->_contactIds),
469 'options' => array(
470 'limit' => 0,
471 'sort' => "sort_name",
472 ),
473 ));
474 $this->_contactIds = array_keys($result['values']);
475 }
476 }
477
478 /**
479 * Given this task's list of targets, produce a hidden group.
480 *
481 * @return array
482 * Array(0 => int $groupID, 1 => int|NULL $ssID).
483 * @throws Exception
484 */
485 public function createHiddenGroup() {
486 // Did the user select "All" matches or cherry-pick a few records?
487 $searchParams = $this->controller->exportValues();
488 if ($searchParams['radio_ts'] == 'ts_sel') {
489 // Create a static group.
490 $randID = md5(time() . rand(1, 1000)); // groups require a unique name
491 $grpTitle = "Hidden Group {$randID}";
492 $grpID = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Group', $grpTitle, 'id', 'title');
493
494 if (!$grpID) {
495 $groupParams = array(
496 'title' => $grpTitle,
497 'is_active' => 1,
498 'is_hidden' => 1,
499 'group_type' => array('2' => 1),
500 );
501
502 $group = CRM_Contact_BAO_Group::create($groupParams);
503 $grpID = $group->id;
504
505 CRM_Contact_BAO_GroupContact::addContactsToGroup($this->_contactIds, $group->id);
506
507 $newGroupTitle = "Hidden Group {$grpID}";
508 $groupParams = array(
509 'id' => $grpID,
510 'name' => CRM_Utils_String::titleToVar($newGroupTitle),
511 'title' => $newGroupTitle,
512 'group_type' => array('2' => 1),
513 );
514 CRM_Contact_BAO_Group::create($groupParams);
515 }
516
517 // note at this point its a static group
518 return array($grpID, NULL);
519 }
520 else {
521 // Create a smart group.
522 $ssId = $this->get('ssID');
523 $hiddenSmartParams = array(
524 'group_type' => array('2' => 1),
525 'form_values' => $this->get('formValues'),
526 'saved_search_id' => $ssId,
527 'search_custom_id' => $this->get('customSearchID'),
528 'search_context' => $this->get('context'),
529 );
530
531 list($smartGroupId, $savedSearchId) = CRM_Contact_BAO_Group::createHiddenSmartGroup($hiddenSmartParams);
532 return array($smartGroupId, $savedSearchId);
533 }
534
535 }
536
537 }