Merge pull request #8482 from JKingsnorth/CRM-18739
[civicrm-core.git] / CRM / Contact / Form / Search / Custom / ActivitySearch.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2016 |
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-2016
32 */
33 class CRM_Contact_Form_Search_Custom_ActivitySearch extends CRM_Contact_Form_Search_Custom_Base implements CRM_Contact_Form_Search_Interface {
34
35 protected $_formValues;
36 protected $_aclFrom = NULL;
37 protected $_aclWhere = NULL;
38
39 /**
40 * Class constructor.
41 *
42 * @param array $formValues
43 */
44 public function __construct(&$formValues) {
45 $this->_formValues = $formValues;
46
47 /**
48 * Define the columns for search result rows
49 */
50 $this->_columns = array(
51 ts('Name') => 'sort_name',
52 ts('Status') => 'activity_status',
53 ts('Activity Type') => 'activity_type',
54 ts('Activity Subject') => 'activity_subject',
55 ts('Scheduled By') => 'source_contact',
56 ts('Scheduled Date') => 'activity_date',
57 ' ' => 'activity_id',
58 ' ' => 'activity_type_id',
59 ' ' => 'case_id',
60 ts('Location') => 'location',
61 ts('Duration') => 'duration',
62 ts('Details') => 'details',
63 ts('Assignee') => 'assignee',
64 );
65
66 $this->_groupId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup',
67 'activity_status',
68 'id',
69 'name'
70 );
71
72 //Add custom fields to columns array for inclusion in export
73 $groupTree = CRM_Core_BAO_CustomGroup::getTree('Activity', $form, NULL,
74 NULL, '', NULL
75 );
76
77 //use simplified formatted groupTree
78 $groupTree = CRM_Core_BAO_CustomGroup::formatGroupTree($groupTree, 1, $form);
79
80 //cycle through custom fields and assign to _columns array
81 foreach ($groupTree as $key) {
82 foreach ($key['fields'] as $field) {
83 $fieldlabel = $key['title'] . ": " . $field['label'];
84 $this->_columns[$fieldlabel] = $field['column_name'];
85 }
86 }
87 //end custom fields
88 }
89
90 /**
91 * @param CRM_Core_Form $form
92 */
93 public function buildForm(&$form) {
94
95 /**
96 * You can define a custom title for the search form
97 */
98 $this->setTitle('Find Latest Activities');
99
100 /**
101 * Define the search form fields here
102 */
103 // Allow user to choose which type of contact to limit search on
104 $form->add('select', 'contact_type', ts('Find...'), CRM_Core_SelectValues::contactType());
105
106 // Text box for Activity Subject
107 $form->add('text',
108 'activity_subject',
109 ts('Activity Subject')
110 );
111
112 // Select box for Activity Type
113 $activityType = array('' => ' - select activity - ') + CRM_Core_PseudoConstant::activityType();
114
115 $form->add('select', 'activity_type_id', ts('Activity Type'),
116 $activityType,
117 FALSE
118 );
119
120 // textbox for Activity Status
121 $activityStatus = array('' => ' - select status - ') + CRM_Core_PseudoConstant::activityStatus();
122
123 $form->add('select', 'activity_status_id', ts('Activity Status'),
124 $activityStatus,
125 FALSE
126 );
127
128 // Activity Date range
129 $form->addDate('start_date', ts('Activity Date From'), FALSE, array('formatType' => 'custom'));
130 $form->addDate('end_date', ts('...through'), FALSE, array('formatType' => 'custom'));
131
132 // Contact Name field
133 $form->add('text', 'sort_name', ts('Contact Name'));
134
135 /**
136 * If you are using the sample template, this array tells the template fields to render
137 * for the search form.
138 */
139 $form->assign('elements', array(
140 'contact_type',
141 'activity_subject',
142 'activity_type_id',
143 'activity_status_id',
144 'start_date',
145 'end_date',
146 'sort_name',
147 ));
148 }
149
150 /**
151 * Define the smarty template used to layout the search form and results listings.
152 */
153 public function templateFile() {
154 return 'CRM/Contact/Form/Search/Custom/ActivitySearch.tpl';
155 }
156
157 /**
158 * Construct the search query.
159 *
160 * @param int $offset
161 * @param int $rowcount
162 * @param null $sort
163 * @param bool $includeContactIDs
164 * @param bool $justIDs
165 *
166 * @return string
167 */
168 public function all(
169 $offset = 0, $rowcount = 0, $sort = NULL,
170 $includeContactIDs = FALSE, $justIDs = FALSE
171 ) {
172
173 // SELECT clause must include contact_id as an alias for civicrm_contact.id
174 if ($justIDs) {
175 $select = 'contact_a.id as contact_id';
176 }
177 else {
178 $select = '
179 contact_a.id as contact_id,
180 contact_a.sort_name as sort_name,
181 contact_a.contact_type as contact_type,
182 activity.id as activity_id,
183 activity.activity_type_id as activity_type_id,
184 contact_b.sort_name as source_contact,
185 ov1.label as activity_type,
186 activity.subject as activity_subject,
187 activity.activity_date_time as activity_date,
188 ov2.label as activity_status,
189 cca.case_id as case_id,
190 activity.location as location,
191 activity.duration as duration,
192 activity.details as details,
193 assignment.activity_id as assignment_activity,
194 contact_c.display_name as assignee
195 ';
196 }
197
198 $from = $this->from();
199
200 $where = $this->where($includeContactIDs);
201
202 if (!empty($where)) {
203 $where = "WHERE $where";
204 }
205
206 // add custom group fields to SELECT and FROM clause
207 $groupTree = CRM_Core_BAO_CustomGroup::getTree('Activity', $form, NULL, NULL, '', NULL);
208
209 foreach ($groupTree as $key) {
210 if (!empty($key['extends']) && $key['extends'] == 'Activity') {
211 $select .= ", " . $key['table_name'] . ".*";
212 $from .= " LEFT JOIN " . $key['table_name'] . " ON " . $key['table_name'] . ".entity_id = activity.id";
213 }
214 }
215 // end custom groups add
216
217 $sql = " SELECT $select FROM $from $where ";
218
219 //no need to add order when only contact Ids.
220 if (!$justIDs) {
221 // Define ORDER BY for query in $sort, with default value
222 if (!empty($sort)) {
223 if (is_string($sort)) {
224 $sort = CRM_Utils_Type::escape($sort, 'String');
225 $sql .= " ORDER BY $sort ";
226 }
227 else {
228 $sql .= ' ORDER BY ' . trim($sort->orderBy());
229 }
230 }
231 else {
232 $sql .= 'ORDER BY contact_a.sort_name, activity.activity_date_time DESC, activity.activity_type_id, activity.status_id, activity.subject';
233 }
234 }
235 else {
236 //CRM-14107, since there could be multiple activities against same contact,
237 //we need to provide GROUP BY on contact id to prevent duplicacy on prev/next entries
238 $sql .= 'GROUP BY contact_a.id
239 ORDER BY contact_a.sort_name';
240 }
241
242 if ($rowcount > 0 && $offset >= 0) {
243 $offset = CRM_Utils_Type::escape($offset, 'Int');
244 $rowcount = CRM_Utils_Type::escape($rowcount, 'Int');
245 $sql .= " LIMIT $offset, $rowcount ";
246 }
247 return $sql;
248 }
249
250 /**
251 * Alters the date display in the Activity Date Column. We do this after we already have
252 * the result so that sorting on the date column stays pertinent to the numeric date value
253 * @param $row
254 */
255 public function alterRow(&$row) {
256 $row['activity_date'] = CRM_Utils_Date::customFormat($row['activity_date'], '%B %E%f, %Y %l:%M %P');
257 }
258
259 /**
260 * Regular JOIN statements here to limit results to contacts who have activities.
261 * @return string
262 */
263 public function from() {
264 $this->buildACLClause('contact_a');
265 $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name');
266 $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts);
267 $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
268 $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts);
269
270 $from = "
271 civicrm_activity activity
272 LEFT JOIN civicrm_activity_contact target
273 ON activity.id = target.activity_id AND target.record_type_id = {$targetID}
274 JOIN civicrm_contact contact_a
275 ON contact_a.id = target.contact_id
276 JOIN civicrm_option_value ov1
277 ON activity.activity_type_id = ov1.value AND ov1.option_group_id = 2
278 JOIN civicrm_option_value ov2
279 ON activity.status_id = ov2.value AND ov2.option_group_id = {$this->_groupId}
280 LEFT JOIN civicrm_activity_contact sourceContact
281 ON activity.id = sourceContact.activity_id AND sourceContact.record_type_id = {$sourceID}
282 JOIN civicrm_contact contact_b
283 ON sourceContact.contact_id = contact_b.id
284 LEFT JOIN civicrm_case_activity cca
285 ON activity.id = cca.activity_id
286 LEFT JOIN civicrm_activity_contact assignment
287 ON activity.id = assignment.activity_id AND assignment.record_type_id = {$assigneeID}
288 LEFT JOIN civicrm_contact contact_c
289 ON assignment.contact_id = contact_c.id {$this->_aclFrom}";
290
291 return $from;
292 }
293
294 /**
295 * WHERE clause is an array built from any required JOINS plus conditional filters based on search criteria field values.
296 *
297 * @param bool $includeContactIDs
298 *
299 * @return string
300 */
301 public function where($includeContactIDs = FALSE) {
302 $clauses = array();
303
304 // add contact name search; search on primary name, source contact, assignee
305 $contactname = $this->_formValues['sort_name'];
306 if (!empty($contactname)) {
307 $dao = new CRM_Core_DAO();
308 $contactname = $dao->escape($contactname);
309 $clauses[] = "(contact_a.sort_name LIKE '%{$contactname}%' OR
310 contact_b.sort_name LIKE '%{$contactname}%' OR
311 contact_c.display_name LIKE '%{$contactname}%')";
312 }
313
314 $subject = $this->_formValues['activity_subject'];
315
316 if (!empty($this->_formValues['contact_type'])) {
317 $clauses[] = "contact_a.contact_type LIKE '%{$this->_formValues['contact_type']}%'";
318 }
319
320 if (!empty($subject)) {
321 $dao = new CRM_Core_DAO();
322 $subject = $dao->escape($subject);
323 $clauses[] = "activity.subject LIKE '%{$subject}%'";
324 }
325
326 if (!empty($this->_formValues['activity_status_id'])) {
327 $clauses[] = "activity.status_id = {$this->_formValues['activity_status_id']}";
328 }
329
330 if (!empty($this->_formValues['activity_type_id'])) {
331 $clauses[] = "activity.activity_type_id = {$this->_formValues['activity_type_id']}";
332 }
333
334 $startDate = $this->_formValues['start_date'];
335 if (!empty($startDate)) {
336 $startDate .= '00:00:00';
337 $startDateFormatted = CRM_Utils_Date::processDate($startDate);
338 if ($startDateFormatted) {
339 $clauses[] = "activity.activity_date_time >= $startDateFormatted";
340 }
341 }
342
343 $endDate = $this->_formValues['end_date'];
344 if (!empty($endDate)) {
345 $endDate .= '23:59:59';
346 $endDateFormatted = CRM_Utils_Date::processDate($endDate);
347 if ($endDateFormatted) {
348 $clauses[] = "activity.activity_date_time <= $endDateFormatted";
349 }
350 }
351
352 if ($includeContactIDs) {
353 $contactIDs = array();
354 foreach ($this->_formValues as $id => $value) {
355 if ($value &&
356 substr($id, 0, CRM_Core_Form::CB_PREFIX_LEN) == CRM_Core_Form::CB_PREFIX
357 ) {
358 $contactIDs[] = substr($id, CRM_Core_Form::CB_PREFIX_LEN);
359 }
360 }
361
362 if (!empty($contactIDs)) {
363 $contactIDs = implode(', ', $contactIDs);
364 $clauses[] = "contact_a.id IN ( $contactIDs )";
365 }
366 }
367
368 if ($this->_aclWhere) {
369 $clauses[] = " {$this->_aclWhere} ";
370 }
371 return implode(' AND ', $clauses);
372 }
373
374 /*
375 * Functions below generally don't need to be modified
376 */
377
378 /**
379 * @inheritDoc
380 */
381 public function count() {
382 $sql = $this->all();
383
384 $dao = CRM_Core_DAO::executeQuery($sql,
385 CRM_Core_DAO::$_nullArray
386 );
387 return $dao->N;
388 }
389
390 /**
391 * @param int $offset
392 * @param int $rowcount
393 * @param null $sort
394 * @param bool $returnSQL Not used; included for consistency with parent; SQL is always returned
395 *
396 * @return string
397 */
398 public function contactIDs($offset = 0, $rowcount = 0, $sort = NULL, $returnSQL = TRUE) {
399 return $this->all($offset, $rowcount, $sort, FALSE, TRUE);
400 }
401
402 /**
403 * @return array
404 */
405 public function &columns() {
406 return $this->_columns;
407 }
408
409 /**
410 * @param $title
411 */
412 public function setTitle($title) {
413 if ($title) {
414 CRM_Utils_System::setTitle($title);
415 }
416 else {
417 CRM_Utils_System::setTitle(ts('Search'));
418 }
419 }
420
421 /**
422 * @return null
423 */
424 public function summary() {
425 return NULL;
426 }
427
428 /**
429 * @param string $tableAlias
430 */
431 public function buildACLClause($tableAlias = 'contact') {
432 list($this->_aclFrom, $this->_aclWhere) = CRM_Contact_BAO_Contact_Permission::cacheClause($tableAlias);
433 }
434
435 }