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