Merge pull request #8645 from eileenmcnaughton/group_query
[civicrm-core.git] / CRM / Mailing / Selector / Browse.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
34 /**
35 * This class is used to browse past mailings.
36 */
37 class CRM_Mailing_Selector_Browse extends CRM_Core_Selector_Base implements CRM_Core_Selector_API {
38
39 /**
40 * Array of supported links, currently null
41 *
42 * @var array
43 */
44 static $_links = NULL;
45
46 /**
47 * We use desc to remind us what that column is, name is used in the tpl
48 *
49 * @var array
50 */
51 static $_columnHeaders;
52
53 protected $_parent;
54
55 /**
56 * Class constructor.
57 *
58 *
59 * @return \CRM_Mailing_Selector_Browse
60 */
61 public function __construct() {
62 }
63
64 /**
65 * This method returns the links that are given for each search row.
66 *
67 * @return array
68 */
69 public static function &links() {
70 return self::$_links;
71 }
72
73 /**
74 * Getter for array of the parameters required for creating pager.
75 *
76 * @param $action
77 * @param array $params
78 */
79 public function getPagerParams($action, &$params) {
80 $params['csvString'] = NULL;
81 $params['rowCount'] = CRM_Utils_Pager::ROWCOUNT;
82 $params['status'] = ts('Mailings %%StatusMessage%%');
83 $params['buttonTop'] = 'PagerTopButton';
84 $params['buttonBottom'] = 'PagerBottomButton';
85 }
86
87 /**
88 * Returns the column headers as an array of tuples:
89 * (name, sortName (key to the sort array))
90 *
91 * @param string $action
92 * The action being performed.
93 * @param string $output
94 * What should the result set include (web/email/csv).
95 *
96 * @return array
97 * the column headers that need to be displayed
98 */
99 public function &getColumnHeaders($action = NULL, $output = NULL) {
100 $mailing = CRM_Mailing_BAO_Mailing::getTableName();
101 $job = CRM_Mailing_BAO_MailingJob::getTableName();
102 if (!isset(self::$_columnHeaders)) {
103 $completedOrder = NULL;
104
105 // Set different default sort depending on type of mailings (CRM-7652)
106 $unscheduledOrder = $scheduledOrder = $archivedOrder = CRM_Utils_Sort::DONTCARE;
107 if ($this->_parent->get('unscheduled')) {
108 $unscheduledOrder = CRM_Utils_Sort::DESCENDING;
109 }
110 elseif ($this->_parent->get('scheduled')) {
111 $scheduledOrder = CRM_Utils_Sort::DESCENDING;
112 }
113 else {
114 // sort by completed date for archived and undefined get
115 $completedOrder = CRM_Utils_Sort::DESCENDING;
116 }
117 $nameHeaderLabel = ($this->_parent->get('sms')) ? ts('SMS Name') : ts('Mailing Name');
118
119 self::$_columnHeaders = array(
120 array(
121 'name' => $nameHeaderLabel,
122 'sort' => 'name',
123 'direction' => CRM_Utils_Sort::DONTCARE,
124 ),
125 array(
126 'name' => ts('Status'),
127 'sort' => 'status',
128 'direction' => CRM_Utils_Sort::DONTCARE,
129 ),
130 array(
131 'name' => ts('Created By'),
132 'sort' => 'created_by',
133 'direction' => CRM_Utils_Sort::DONTCARE,
134 ),
135 array(
136 'name' => ts('Created Date'),
137 'sort' => 'created_date',
138 'direction' => $unscheduledOrder,
139 ),
140 array(
141 'name' => ts('Sent By'),
142 'sort' => 'scheduled_by',
143 'direction' => CRM_Utils_Sort::DONTCARE,
144 ),
145 array(
146 'name' => ts('Scheduled'),
147 'sort' => 'scheduled_date',
148 'direction' => $scheduledOrder,
149 ),
150 array(
151 'name' => ts('Started'),
152 'sort' => 'start_date',
153 'direction' => CRM_Utils_Sort::DONTCARE,
154 ),
155 array(
156 'name' => ts('Completed'),
157 'sort' => 'end_date',
158 'direction' => $completedOrder,
159 ),
160 );
161
162 if (CRM_Campaign_BAO_Campaign::isCampaignEnable()) {
163 self::$_columnHeaders[] = array(
164 'name' => ts('Campaign'),
165 'sort' => 'campaign_id',
166 'direction' => CRM_Utils_Sort::DONTCARE,
167 );
168 }
169
170 if ($output != CRM_Core_Selector_Controller::EXPORT) {
171 self::$_columnHeaders[] = array('name' => ts('Action'));
172 }
173 }
174 return self::$_columnHeaders;
175 }
176
177 /**
178 * Returns total number of rows for the query.
179 *
180 * @param string $action
181 *
182 * @return int
183 * Total number of rows
184 */
185 public function getTotalCount($action) {
186 $job = CRM_Mailing_BAO_MailingJob::getTableName();
187 $mailing = CRM_Mailing_BAO_Mailing::getTableName();
188 $mailingACL = CRM_Mailing_BAO_Mailing::mailingACL();
189
190 // get the where clause.
191 $params = array();
192 $whereClause = "$mailingACL AND " . $this->whereClause($params);
193
194 // CRM-11919 added addition ON clauses to mailing_job to match getRows
195 $query = "
196 SELECT COUNT( DISTINCT $mailing.id ) as count
197 FROM $mailing
198 LEFT JOIN $job ON ( $mailing.id = $job.mailing_id AND civicrm_mailing_job.is_test = 0 AND civicrm_mailing_job.parent_id IS NULL )
199 LEFT JOIN civicrm_contact createdContact ON ( $mailing.created_id = createdContact.id )
200 LEFT JOIN civicrm_contact scheduledContact ON ( $mailing.scheduled_id = scheduledContact.id )
201 WHERE $whereClause";
202
203 return CRM_Core_DAO::singleValueQuery($query, $params);
204 }
205
206 /**
207 * Returns all the rows in the given offset and rowCount.
208 *
209 * @param string $action
210 * The action being performed.
211 * @param int $offset
212 * The row number to start from.
213 * @param int $rowCount
214 * The number of rows to return.
215 * @param string $sort
216 * The sql string that describes the sort order.
217 * @param string $output
218 * What should the result set include (web/email/csv).
219 *
220 * @return int
221 * the total number of rows for this action
222 */
223 public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) {
224 static $actionLinks = NULL;
225 if (empty($actionLinks)) {
226 $cancelExtra = ts('Are you sure you want to cancel this mailing?');
227 $deleteExtra = ts('Are you sure you want to delete this mailing?');
228 $archiveExtra = ts('Are you sure you want to archive this mailing?');
229
230 $actionLinks = array(
231 CRM_Core_Action::ENABLE => array(
232 'name' => ts('Approve/Reject'),
233 'url' => 'civicrm/mailing/approve',
234 'qs' => 'mid=%%mid%%&reset=1',
235 'title' => ts('Approve/Reject Mailing'),
236 ),
237 CRM_Core_Action::VIEW => array(
238 'name' => ts('Report'),
239 'url' => 'civicrm/mailing/report',
240 'qs' => 'mid=%%mid%%&reset=1',
241 'title' => ts('View Mailing Report'),
242 ),
243 CRM_Core_Action::UPDATE => array(
244 'name' => ts('Re-Use'),
245 'url' => 'civicrm/mailing/send',
246 'qs' => 'mid=%%mid%%&reset=1',
247 'title' => ts('Re-Send Mailing'),
248 ),
249 CRM_Core_Action::DISABLE => array(
250 'name' => ts('Cancel'),
251 'url' => 'civicrm/mailing/browse',
252 'qs' => 'action=disable&mid=%%mid%%&reset=1',
253 'extra' => 'onclick="if (confirm(\'' . $cancelExtra . '\')) this.href+=\'&amp;confirmed=1\'; else return false;"',
254 'title' => ts('Cancel Mailing'),
255 ),
256 CRM_Core_Action::PREVIEW => array(
257 'name' => ts('Continue'),
258 'url' => 'civicrm/mailing/send',
259 'qs' => 'mid=%%mid%%&continue=true&reset=1',
260 'title' => ts('Continue Mailing'),
261 ),
262 CRM_Core_Action::DELETE => array(
263 'name' => ts('Delete'),
264 'url' => 'civicrm/mailing/browse',
265 'qs' => 'action=delete&mid=%%mid%%&reset=1',
266 'extra' => 'onclick="if (confirm(\'' . $deleteExtra . '\')) this.href+=\'&amp;confirmed=1\'; else return false;"',
267 'title' => ts('Delete Mailing'),
268 ),
269 CRM_Core_Action::RENEW => array(
270 'name' => ts('Archive'),
271 'url' => 'civicrm/mailing/browse/archived',
272 'qs' => 'action=renew&mid=%%mid%%&reset=1',
273 'extra' => 'onclick="if (confirm(\'' . $archiveExtra . '\')) this.href+=\'&amp;confirmed=1\'; else return false;"',
274 'title' => ts('Archive Mailing'),
275 ),
276 );
277 }
278
279 $allAccess = TRUE;
280 $workFlow = $showApprovalLinks = $showScheduleLinks = $showCreateLinks = FALSE;
281 if (CRM_Mailing_Info::workflowEnabled()) {
282 $allAccess = FALSE;
283 $workFlow = TRUE;
284 // supercedes all permission
285 if (CRM_Core_Permission::check('access CiviMail')) {
286 $allAccess = TRUE;
287 }
288
289 if (CRM_Core_Permission::check('approve mailings')) {
290 $showApprovalLinks = TRUE;
291 }
292
293 if (CRM_Core_Permission::check('create mailings')) {
294 $showCreateLinks = TRUE;
295 }
296
297 if (CRM_Core_Permission::check('schedule mailings')) {
298 $showScheduleLinks = TRUE;
299 }
300 }
301 $mailing = new CRM_Mailing_BAO_Mailing();
302
303 $params = array();
304
305 $whereClause = ' AND ' . $this->whereClause($params);
306
307 if (empty($params)) {
308 $this->_parent->assign('isSearch', 0);
309 }
310 else {
311 $this->_parent->assign('isSearch', 1);
312 }
313 $rows = &$mailing->getRows($offset, $rowCount, $sort, $whereClause, $params);
314
315 // get the search base mailing Ids, CRM-3711.
316 $searchMailings = $mailing->searchMailingIDs();
317
318 // check for delete CRM-4418
319 $allowToDelete = CRM_Core_Permission::check('delete in CiviMail');
320
321 if ($output != CRM_Core_Selector_Controller::EXPORT) {
322
323 // create the appropriate $op to use for hook_civicrm_links
324 $pageTypes = array('view', 'mailing', 'browse');
325 if ($this->_parent->_unscheduled) {
326 $pageTypes[] = 'unscheduled';
327 }
328 if ($this->_parent->_scheduled) {
329 $pageTypes[] = 'scheduled';
330 }
331 if ($this->_parent->_archived) {
332 $pageTypes[] = 'archived';
333 }
334 $opString = implode('.', $pageTypes);
335
336 foreach ($rows as $key => $row) {
337 $actionMask = NULL;
338 if ($row['sms_provider_id']) {
339 $actionLinks[CRM_Core_Action::PREVIEW]['url'] = 'civicrm/sms/send';
340 }
341
342 if (!($row['status'] == 'Not scheduled') && !$row['sms_provider_id']) {
343 if ($allAccess || $showCreateLinks) {
344 $actionMask = CRM_Core_Action::VIEW;
345 }
346
347 if (!in_array($row['id'], $searchMailings)) {
348 if ($allAccess || $showCreateLinks) {
349 $actionMask |= CRM_Core_Action::UPDATE;
350 }
351 }
352 }
353 else {
354 if ($allAccess || ($showCreateLinks || $showScheduleLinks)) {
355 $actionMask = CRM_Core_Action::PREVIEW;
356 }
357 }
358 if (in_array($row['status'], array(
359 'Scheduled',
360 'Running',
361 'Paused',
362 ))) {
363 if ($allAccess ||
364 ($showApprovalLinks && $showCreateLinks && $showScheduleLinks)
365 ) {
366
367 $actionMask |= CRM_Core_Action::DISABLE;
368 }
369 if ($row['status'] == 'Scheduled' &&
370 empty($row['approval_status_id'])
371 ) {
372 if ($workFlow && ($allAccess || $showApprovalLinks)) {
373 $actionMask |= CRM_Core_Action::ENABLE;
374 }
375 }
376 }
377
378 if (in_array($row['status'], array('Complete', 'Canceled')) &&
379 !$row['archived']
380 ) {
381 if ($allAccess || $showCreateLinks) {
382 $actionMask |= CRM_Core_Action::RENEW;
383 }
384 }
385
386 // check for delete permission.
387 if ($allowToDelete) {
388 $actionMask |= CRM_Core_Action::DELETE;
389 }
390
391 if ($actionMask == NULL) {
392 $actionMask = CRM_Core_Action::ADD;
393 }
394 // get status strings as per locale settings CRM-4411.
395 $rows[$key]['status'] = CRM_Mailing_BAO_MailingJob::status($row['status']);
396
397 $rows[$key]['action'] = CRM_Core_Action::formLink($actionLinks,
398 $actionMask,
399 array('mid' => $row['id']),
400 "more",
401 FALSE,
402 $opString,
403 "Mailing",
404 $row['id']
405 );
406
407 // unset($rows[$key]['id']);
408 // if the scheduled date is 0, replace it with an empty string
409 if ($rows[$key]['scheduled_iso'] == '0000-00-00 00:00:00') {
410 $rows[$key]['scheduled'] = '';
411 }
412 unset($rows[$key]['scheduled_iso']);
413 }
414 }
415
416 // also initialize the AtoZ pager
417 $this->pagerAtoZ();
418 return $rows;
419 }
420
421 /**
422 * Name of export file.
423 *
424 * @param string $output
425 * Type of output.
426 *
427 * @return string
428 * name of the file
429 */
430 public function getExportFileName($output = 'csv') {
431 return ts('CiviMail Mailings');
432 }
433
434 /**
435 * @param $parent
436 */
437 public function setParent($parent) {
438 $this->_parent = $parent;
439 }
440
441 /**
442 * @param array $params
443 * @param bool $sortBy
444 *
445 * @return int|string
446 */
447 public function whereClause(&$params, $sortBy = TRUE) {
448 $values = $clauses = array();
449 $isFormSubmitted = $this->_parent->get('hidden_find_mailings');
450
451 $title = $this->_parent->get('mailing_name');
452 if ($title) {
453 $clauses[] = 'name LIKE %1';
454 if (strpos($title, '%') !== FALSE) {
455 $params[1] = array($title, 'String', FALSE);
456 }
457 else {
458 $params[1] = array($title, 'String', TRUE);
459 }
460 }
461
462 $dateClause1 = $dateClause2 = array();
463 $from = $this->_parent->get('mailing_from');
464 if (!CRM_Utils_System::isNull($from)) {
465 if ($this->_parent->get('unscheduled')) {
466 $dateClause1[] = 'civicrm_mailing.created_date >= %2';
467 }
468 else {
469 $dateClause1[] = 'civicrm_mailing_job.start_date >= %2';
470 $dateClause2[] = 'civicrm_mailing_job.scheduled_date >= %2';
471 }
472 $params[2] = array($from, 'String');
473 }
474
475 $to = $this->_parent->get('mailing_to');
476 if (!CRM_Utils_System::isNull($to)) {
477 if ($this->_parent->get('unscheduled')) {
478 $dateClause1[] = ' civicrm_mailing.created_date <= %3 ';
479 }
480 else {
481 $dateClause1[] = 'civicrm_mailing_job.start_date <= %3';
482 $dateClause2[] = 'civicrm_mailing_job.scheduled_date <= %3';
483 }
484 $params[3] = array($to, 'String');
485 }
486
487 $dateClauses = array();
488 if (!empty($dateClause1)) {
489 $dateClauses[] = implode(' AND ', $dateClause1);
490 }
491 if (!empty($dateClause2)) {
492 $dateClauses[] = implode(' AND ', $dateClause2);
493 }
494 $dateClauses = implode(' OR ', $dateClauses);
495 if (!empty($dateClauses)) {
496 $clauses[] = "({$dateClauses})";
497 }
498
499 if ($this->_parent->get('sms')) {
500 $clauses[] = "civicrm_mailing.sms_provider_id IS NOT NULL";
501 }
502 else {
503 $clauses[] = "civicrm_mailing.sms_provider_id IS NULL";
504 }
505
506 // get values submitted by form
507 $isDraft = $this->_parent->get('status_unscheduled');
508 $isArchived = $this->_parent->get('is_archived');
509 $mailingStatus = $this->_parent->get('mailing_status');
510
511 if (!$isFormSubmitted && $this->_parent->get('scheduled')) {
512 // mimic default behavior for scheduled screen
513 $isArchived = 0;
514 $mailingStatus = array('Scheduled' => 1, 'Complete' => 1, 'Running' => 1, 'Canceled' => 1);
515 }
516 if (!$isFormSubmitted && $this->_parent->get('archived')) {
517 // mimic default behavior for archived screen
518 $isArchived = 1;
519 }
520 if (!$isFormSubmitted && $this->_parent->get('unscheduled')) {
521 // mimic default behavior for draft screen
522 $isDraft = 1;
523 }
524
525 $statusClauses = array();
526 if ($isDraft) {
527 $statusClauses[] = "civicrm_mailing.scheduled_id IS NULL";
528 }
529 if (!empty($mailingStatus)) {
530 $statusClauses[] = "civicrm_mailing_job.status IN ('" . implode("', '", array_keys($mailingStatus)) . "')";
531 }
532 if (!empty($statusClauses)) {
533 $clauses[] = "(" . implode(' OR ', $statusClauses) . ")";
534 }
535
536 if (isset($isArchived)) {
537 if ($isArchived) {
538 $clauses[] = "civicrm_mailing.is_archived = 1";
539 }
540 else {
541 $clauses[] = "(civicrm_mailing.is_archived IS NULL OR civicrm_mailing.is_archived = 0)";
542 }
543 }
544
545 if ($sortBy &&
546 $this->_parent->_sortByCharacter !== NULL
547 ) {
548 $clauses[] = "name LIKE '" . strtolower(CRM_Core_DAO::escapeWildCardString($this->_parent->_sortByCharacter)) . "%'";
549 }
550
551 // dont do a the below assignement when doing a
552 // AtoZ pager clause
553 if ($sortBy) {
554 if (count($clauses) > 1) {
555 $this->_parent->assign('isSearch', 1);
556 }
557 else {
558 $this->_parent->assign('isSearch', 0);
559 }
560 }
561
562 $createOrSentBy = $this->_parent->get('sort_name');
563 if (!CRM_Utils_System::isNull($createOrSentBy)) {
564 $clauses[] = '(createdContact.sort_name LIKE %4 OR scheduledContact.sort_name LIKE %4)';
565 $params[4] = array('%' . $createOrSentBy . '%', 'String');
566 }
567
568 $createdId = $this->_parent->get('createdId');
569 if ($createdId) {
570 $clauses[] = "(created_id = {$createdId})";
571 $params[5] = array($createdId, 'Integer');
572 }
573
574 $campainIds = $this->_parent->get('campaign_id');
575 if (!CRM_Utils_System::isNull($campainIds)) {
576 if (!is_array($campainIds)) {
577 $campaignIds = array($campaignIds);
578 }
579 $clauses[] = '( campaign_id IN ( ' . implode(' , ', array_values($campainIds)) . ' ) )';
580 }
581
582 if (empty($clauses)) {
583 return 1;
584 }
585
586 return implode(' AND ', $clauses);
587 }
588
589 public function pagerAtoZ() {
590
591 $params = array();
592 $whereClause = $this->whereClause($params, FALSE);
593
594 $query = "
595 SELECT DISTINCT UPPER(LEFT(name, 1)) as sort_name
596 FROM civicrm_mailing
597 LEFT JOIN civicrm_mailing_job ON (civicrm_mailing_job.mailing_id = civicrm_mailing.id)
598 LEFT JOIN civicrm_contact createdContact ON ( civicrm_mailing.created_id = createdContact.id )
599 LEFT JOIN civicrm_contact scheduledContact ON ( civicrm_mailing.scheduled_id = scheduledContact.id )
600 WHERE $whereClause
601 ORDER BY UPPER(LEFT(name, 1))
602 ";
603
604 $dao = CRM_Core_DAO::executeQuery($query, $params);
605
606 $aToZBar = CRM_Utils_PagerAToZ::getAToZBar($dao, $this->_parent->_sortByCharacter, TRUE);
607 $this->_parent->assign('aToZ', $aToZBar);
608 }
609
610 }