3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
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. |
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. |
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 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2014
35 class CRM_Contact_Form_Search_Custom_Group
36 extends CRM_Contact_Form_Search_Custom_Base
37 implements CRM_Contact_Form_Search_Interface
{
39 protected $_formValues;
41 protected $_tableName = NULL;
43 protected $_where = ' (1) ';
45 protected $_aclFrom = NULL;
46 protected $_aclWhere = NULL;
51 function __construct(&$formValues) {
52 $this->_formValues
= $formValues;
53 $this->_columns
= array(
54 ts('Contact Id') => 'contact_id',
55 ts('Contact Type') => 'contact_type',
56 ts('Name') => 'sort_name',
57 ts('Group Name') => 'gname',
58 ts('Tag Name') => 'tname',
61 $this->_includeGroups
= CRM_Utils_Array
::value('includeGroups', $this->_formValues
, array());
62 $this->_excludeGroups
= CRM_Utils_Array
::value('excludeGroups', $this->_formValues
, array());
63 $this->_includeTags
= CRM_Utils_Array
::value('includeTags', $this->_formValues
, array());
64 $this->_excludeTags
= CRM_Utils_Array
::value('excludeTags', $this->_formValues
, array());
67 $this->_allSearch
= FALSE;
68 $this->_groups
= FALSE;
70 $this->_andOr
= CRM_Utils_Array
::value('andOr', $this->_formValues
);
73 //make easy to check conditions for groups and tags are
74 //selected or it is empty search
75 if (empty($this->_includeGroups
) && empty($this->_excludeGroups
) &&
76 empty($this->_includeTags
) && empty($this->_excludeTags
)
79 $this->_allSearch
= TRUE;
82 $this->_groups
= (!empty($this->_includeGroups
) ||
!empty($this->_excludeGroups
));
84 $this->_tags
= (!empty($this->_includeTags
) ||
!empty($this->_excludeTags
));
87 function __destruct() {
88 // mysql drops the tables when connectiomn is terminated
89 // cannot drop tables here, since the search might be used
90 // in other parts after the object is destroyed
96 function buildForm(&$form) {
98 $this->setTitle(ts('Include / Exclude Search'));
100 $groups = CRM_Core_PseudoConstant
::group();
102 $tags = CRM_Core_PseudoConstant
::get('CRM_Core_DAO_EntityTag', 'tag_id', array('onlyActive' => FALSE));
103 if (count($groups) == 0 ||
count($tags) == 0) {
104 CRM_Core_Session
::setStatus(ts("At least one Group and Tag must be present for Custom Group / Tag search."), ts('Missing Group/Tag'));
105 $url = CRM_Utils_System
::url('civicrm/contact/search/custom/list', 'reset=1');
106 CRM_Utils_System
::redirect($url);
109 $inG = &$form->addElement('advmultiselect', 'includeGroups',
110 ts('Include Group(s)') . ' ', $groups,
113 'style' => 'width:240px',
114 'class' => 'advmultiselect',
118 $outG = &$form->addElement('advmultiselect', 'excludeGroups',
119 ts('Exclude Group(s)') . ' ', $groups,
122 'style' => 'width:240px',
123 'class' => 'advmultiselect',
128 '1' => ts('Show contacts that meet the Groups criteria AND the Tags criteria'),
129 '0' => ts('Show contacts that meet the Groups criteria OR the Tags criteria'),
131 $form->addRadio('andOr', ts('AND/OR'), $andOr, NULL, '<br />', TRUE);
133 $int = &$form->addElement('advmultiselect', 'includeTags',
134 ts('Include Tag(s)') . ' ', $tags,
137 'style' => 'width:240px',
138 'class' => 'advmultiselect',
142 $outt = &$form->addElement('advmultiselect', 'excludeTags',
143 ts('Exclude Tag(s)') . ' ', $tags,
146 'style' => 'width:240px',
147 'class' => 'advmultiselect',
151 //add/remove buttons for groups
152 $inG->setButtonAttributes('add', array('value' => ts('Add >>')));;
153 $outG->setButtonAttributes('add', array('value' => ts('Add >>')));;
154 $inG->setButtonAttributes('remove', array('value' => ts('<< Remove')));;
155 $outG->setButtonAttributes('remove', array('value' => ts('<< Remove')));;
157 //add/remove buttons for tags
158 $int->setButtonAttributes('add', array('value' => ts('Add >>')));;
159 $outt->setButtonAttributes('add', array('value' => ts('Add >>')));;
160 $int->setButtonAttributes('remove', array('value' => ts('<< Remove')));;
161 $outt->setButtonAttributes('remove', array('value' => ts('<< Remove')));;
164 * if you are using the standard template, this array tells the template what elements
165 * are part of the search criteria
167 $form->assign('elements', array('includeGroups', 'excludeGroups', 'andOr', 'includeTags', 'excludeTags'));
171 * Set search form field defaults here.
176 function setDefaultValues() {
177 $defaults = array( 'andOr' => '1' );
179 if (!empty($this->_formValues
)) {
180 $defaults['andOr'] = CRM_Utils_Array
::value('andOr', $this->_formValues
, '1');
182 $defaults['includeGroups'] = CRM_Utils_Array
::value('includeGroups', $this->_formValues
);
183 $defaults['excludeGroups'] = CRM_Utils_Array
::value('excludeGroups', $this->_formValues
);
191 * @param int $rowcount
193 * @param bool $includeContactIDs
194 * @param bool $justIDs
199 $offset = 0, $rowcount = 0, $sort = NULL,
200 $includeContactIDs = FALSE, $justIDs = FALSE
204 $selectClause = "contact_a.id as contact_id";
207 $selectClause = "contact_a.id as contact_id,
208 contact_a.contact_type as contact_type,
209 contact_a.sort_name as sort_name";
211 //distinguish column according to user selection
212 if (($this->_includeGroups
&& !$this->_includeTags
)) {
213 unset($this->_columns
['Tag Name']);
214 $selectClause .= ", GROUP_CONCAT(DISTINCT group_names ORDER BY group_names ASC ) as gname";
216 elseif ($this->_includeTags
&& (!$this->_includeGroups
)) {
217 unset($this->_columns
['Group Name']);
218 $selectClause .= ", GROUP_CONCAT(DISTINCT tag_names ORDER BY tag_names ASC ) as tname";
220 elseif (!empty($this->_includeTags
) && !empty($this->_includeGroups
)) {
221 $selectClause .= ", GROUP_CONCAT(DISTINCT group_names ORDER BY group_names ASC ) as gname , GROUP_CONCAT(DISTINCT tag_names ORDER BY tag_names ASC ) as tname";
224 unset($this->_columns
['Tag Name']);
225 unset($this->_columns
['Group Name']);
229 $from = $this->from();
231 $where = $this->where($includeContactIDs);
233 if (!$justIDs && !$this->_allSearch
) {
234 $groupBy = " GROUP BY contact_a.id";
238 // we do this since this if stmt is called by the smart group part of the code
239 // adding a groupBy clause and saving it as a smart group messes up the query and
241 // andrew hunt seemed to have rewritten this piece when he worked on this search
245 $sql = "SELECT $selectClause $from WHERE $where $groupBy";
247 // Define ORDER BY for query in $sort, with default value
250 if (is_string($sort)) {
251 $sort = CRM_Utils_Type
::escape($sort, 'String');
252 $sql .= " ORDER BY $sort ";
255 $sql .= " ORDER BY " . trim($sort->orderBy());
259 $sql .= " ORDER BY contact_id ASC";
263 $sql .= " ORDER BY contact_a.id ASC";
266 if ($offset >= 0 && $rowcount > 0) {
267 $sql .= " LIMIT $offset, $rowcount ";
279 $iGroups = $xGroups = $iTags = $xTags = 0;
282 $randomNum = md5(uniqid());
283 $this->_tableName
= "civicrm_temp_custom_{$randomNum}";
285 //block for Group search
286 $smartGroup = array();
287 if ($this->_groups ||
$this->_allSearch
) {
288 $group = new CRM_Contact_DAO_Group();
289 $group->is_active
= 1;
291 while ($group->fetch()) {
292 $allGroups[] = $group->id
;
293 if ($group->saved_search_id
) {
294 $smartGroup[$group->saved_search_id
] = $group->id
;
297 $includedGroups = implode(',', $allGroups);
299 if (!empty($this->_includeGroups
)) {
300 $iGroups = implode(',', $this->_includeGroups
);
303 //if no group selected search for all groups
306 if (is_array($this->_excludeGroups
)) {
307 $xGroups = implode(',', $this->_excludeGroups
);
313 $sql = "CREATE TEMPORARY TABLE Xg_{$this->_tableName} ( contact_id int primary key) ENGINE=MyISAM";
314 CRM_Core_DAO
::executeQuery($sql);
316 //used only when exclude group is selected
318 $excludeGroup = "INSERT INTO Xg_{$this->_tableName} ( contact_id )
319 SELECT DISTINCT civicrm_group_contact.contact_id
320 FROM civicrm_group_contact, civicrm_contact
322 civicrm_contact.id = civicrm_group_contact.contact_id AND
323 civicrm_group_contact.status = 'Added' AND
324 civicrm_group_contact.group_id IN( {$xGroups})";
326 CRM_Core_DAO
::executeQuery($excludeGroup);
328 //search for smart group contacts
329 foreach ($this->_excludeGroups
as $keys => $values) {
330 if (in_array($values, $smartGroup)) {
331 $ssGroup = new CRM_Contact_DAO_Group();
332 $ssGroup->id
= $values;
333 if (!$ssGroup->find(TRUE)) {
334 CRM_Core_Error
::fatal();
336 CRM_Contact_BAO_GroupContactCache
::load($ssGroup);
339 SELECT gcc.contact_id
340 FROM civicrm_group_contact_cache gcc
341 WHERE gcc.group_id = {$ssGroup->id}
343 $smartGroupQuery = " INSERT IGNORE INTO Xg_{$this->_tableName}(contact_id) $smartSql";
344 CRM_Core_DAO
::executeQuery($smartGroupQuery);
349 $sql = "CREATE TEMPORARY TABLE Ig_{$this->_tableName} ( id int PRIMARY KEY AUTO_INCREMENT,
351 group_names varchar(64)) ENGINE=MyISAM";
353 CRM_Core_DAO
::executeQuery($sql);
356 $includeGroup = "INSERT INTO Ig_{$this->_tableName} (contact_id, group_names)
357 SELECT civicrm_contact.id as contact_id, civicrm_group.title as group_name
359 INNER JOIN civicrm_group_contact
360 ON civicrm_group_contact.contact_id = civicrm_contact.id
361 LEFT JOIN civicrm_group
362 ON civicrm_group_contact.group_id = civicrm_group.id";
365 $includeGroup = "INSERT INTO Ig_{$this->_tableName} (contact_id, group_names)
366 SELECT civicrm_contact.id as contact_id, ''
367 FROM civicrm_contact";
371 //used only when exclude group is selected
373 $includeGroup .= " LEFT JOIN Xg_{$this->_tableName}
374 ON civicrm_contact.id = Xg_{$this->_tableName}.contact_id";
378 $includeGroup .= " WHERE
379 civicrm_group_contact.status = 'Added' AND
380 civicrm_group_contact.group_id IN($iGroups)";
383 $includeGroup .= " WHERE ( 1 ) ";
386 //used only when exclude group is selected
388 $includeGroup .= " AND Xg_{$this->_tableName}.contact_id IS null";
391 CRM_Core_DAO
::executeQuery($includeGroup);
393 //search for smart group contacts
395 foreach ($this->_includeGroups
as $keys => $values) {
396 if (in_array($values, $smartGroup)) {
397 $ssGroup = new CRM_Contact_DAO_Group();
398 $ssGroup->id
= $values;
399 if (!$ssGroup->find(TRUE)) {
400 CRM_Core_Error
::fatal();
402 CRM_Contact_BAO_GroupContactCache
::load($ssGroup);
405 SELECT gcc.contact_id
406 FROM civicrm_group_contact_cache gcc
407 WHERE gcc.group_id = {$ssGroup->id}
410 //used only when exclude group is selected
412 $smartSql .= " AND gcc.contact_id NOT IN (SELECT contact_id FROM Xg_{$this->_tableName})";
415 $smartGroupQuery = " INSERT IGNORE INTO Ig_{$this->_tableName}(contact_id)
418 CRM_Core_DAO
::executeQuery($smartGroupQuery);
419 $insertGroupNameQuery = "UPDATE IGNORE Ig_{$this->_tableName}
420 SET group_names = (SELECT title FROM civicrm_group
421 WHERE civicrm_group.id = $values)
422 WHERE Ig_{$this->_tableName}.contact_id IS NOT NULL
423 AND Ig_{$this->_tableName}.group_names IS NULL";
424 CRM_Core_DAO
::executeQuery($insertGroupNameQuery);
428 //group contact search end here;
430 //block for Tags search
431 if ($this->_tags ||
$this->_allSearch
) {
433 $tag = new CRM_Core_DAO_Tag();
436 while ($tag->fetch()) {
437 $allTags[] = $tag->id
;
439 $includedTags = implode(',', $allTags);
441 if (!empty($this->_includeTags
)) {
442 $iTags = implode(',', $this->_includeTags
);
445 //if no group selected search for all groups
448 if (is_array($this->_excludeTags
)) {
449 $xTags = implode(',', $this->_excludeTags
);
455 $sql = "CREATE TEMPORARY TABLE Xt_{$this->_tableName} ( contact_id int primary key) ENGINE=MyISAM";
456 CRM_Core_DAO
::executeQuery($sql);
458 //used only when exclude tag is selected
460 $excludeTag = "INSERT INTO Xt_{$this->_tableName} ( contact_id )
461 SELECT DISTINCT civicrm_entity_tag.entity_id
462 FROM civicrm_entity_tag, civicrm_contact
464 civicrm_entity_tag.entity_table = 'civicrm_contact' AND
465 civicrm_contact.id = civicrm_entity_tag.entity_id AND
466 civicrm_entity_tag.tag_id IN( {$xTags})";
468 CRM_Core_DAO
::executeQuery($excludeTag);
471 $sql = "CREATE TEMPORARY TABLE It_{$this->_tableName} ( id int PRIMARY KEY AUTO_INCREMENT,
473 tag_names varchar(64)) ENGINE=MyISAM";
475 CRM_Core_DAO
::executeQuery($sql);
478 $includeTag = "INSERT INTO It_{$this->_tableName} (contact_id, tag_names)
479 SELECT civicrm_contact.id as contact_id, civicrm_tag.name as tag_name
481 INNER JOIN civicrm_entity_tag
482 ON ( civicrm_entity_tag.entity_table = 'civicrm_contact' AND
483 civicrm_entity_tag.entity_id = civicrm_contact.id )
484 LEFT JOIN civicrm_tag
485 ON civicrm_entity_tag.tag_id = civicrm_tag.id";
488 $includeTag = "INSERT INTO It_{$this->_tableName} (contact_id, tag_names)
489 SELECT civicrm_contact.id as contact_id, ''
490 FROM civicrm_contact";
493 //used only when exclude tag is selected
495 $includeTag .= " LEFT JOIN Xt_{$this->_tableName}
496 ON civicrm_contact.id = Xt_{$this->_tableName}.contact_id";
499 $includeTag .= " WHERE civicrm_entity_tag.tag_id IN($iTags)";
502 $includeTag .= " WHERE ( 1 ) ";
505 //used only when exclude tag is selected
507 $includeTag .= " AND Xt_{$this->_tableName}.contact_id IS null";
510 CRM_Core_DAO
::executeQuery($includeTag);
513 $from = " FROM civicrm_contact contact_a";
516 * CRM-10850 / CRM-10848
517 * If we use include / exclude groups as smart groups for ACL's having the below causes
518 * a cycle which messes things up. Hence commenting out for now
519 * $this->buildACLClause('contact_a');
523 * check the situation and set booleans
525 $Ig = ($iGroups != 0);
527 $Xg = ($xGroups != 0);
531 if (!$this->_groups
&& !$this->_tags
) {
536 * Set from statement depending on array sel
538 $whereitems = array();
539 foreach (array('Ig', 'It') as $inc) {
540 if ($this->_andOr
== 1) {
542 $from .= " INNER JOIN {$inc}_{$this->_tableName} temptable$inc ON (contact_a.id = temptable$inc.contact_id)";
547 $from .= " LEFT JOIN {$inc}_{$this->_tableName} temptable$inc ON (contact_a.id = temptable$inc.contact_id)";
551 $whereitems[] = "temptable$inc.contact_id IS NOT NULL";
554 $this->_where
= $whereitems ?
"(" . implode(' OR ', $whereitems) . ')' : '(1)';
555 foreach (array('Xg', 'Xt') as $exc) {
557 $from .= " LEFT JOIN {$exc}_{$this->_tableName} temptable$exc ON (contact_a.id = temptable$exc.contact_id)";
558 $this->_where
.= " AND temptable$exc.contact_id IS NULL";
562 $from .= " LEFT JOIN civicrm_email ON ( contact_a.id = civicrm_email.contact_id AND ( civicrm_email.is_primary = 1 OR civicrm_email.is_bulkmail = 1 ) ) {$this->_aclFrom}";
564 if ($this->_aclWhere
) {
565 $this->_where
.= " AND {$this->_aclWhere} ";
568 // also exclude all contacts that are deleted
570 $this->_where
.= " AND (contact_a.is_deleted != 1) ";
576 * @param bool $includeContactIDs
580 function where($includeContactIDs = FALSE) {
581 if ($includeContactIDs) {
582 $contactIDs = array();
584 foreach ($this->_formValues
as $id => $value) {
586 substr($id, 0, CRM_Core_Form
::CB_PREFIX_LEN
) == CRM_Core_Form
::CB_PREFIX
588 $contactIDs[] = substr($id, CRM_Core_Form
::CB_PREFIX_LEN
);
592 if (!empty($contactIDs)) {
593 $contactIDs = implode(', ', $contactIDs);
594 $clauses[] = "contact_a.id IN ( $contactIDs )";
596 $where = "{$this->_where} AND " . implode(' AND ', $clauses);
599 $where = $this->_where
;
606 * Functions below generally don't need to be modified
614 $dao = CRM_Core_DAO
::executeQuery($sql);
620 * @param int $rowcount
622 * @param bool $returnSQL
626 function contactIDs($offset = 0, $rowcount = 0, $sort = NULL, $returnSQL = FALSE) {
627 return $this->all($offset, $rowcount, $sort, FALSE, TRUE);
633 function &columns() {
634 return $this->_columns
;
647 function templateFile() {
648 return 'CRM/Contact/Form/Search/Custom.tpl';
654 function setTitle($title) {
656 CRM_Utils_System
::setTitle($title);
659 CRM_Utils_System
::setTitle(ts('Search'));
664 * @param string $tableAlias
666 function buildACLClause($tableAlias = 'contact') {
667 list($this->_aclFrom
, $this->_aclWhere
) = CRM_Contact_BAO_Contact_Permission
::cacheClause($tableAlias);