Merge pull request #8980 from ergonlogic/dev/CRM-19308
[civicrm-core.git] / CRM / Mailing / Selector / Browse.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2017 |
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-2017
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 );
126
127 if (CRM_Core_I18n::isMultilingual()) {
128 self::$_columnHeaders = array_merge(
129 self::$_columnHeaders,
130 array(
131 array(
132 'name' => ts('Language'),
133 'sort' => 'language',
134 'direction' => CRM_Utils_Sort::DONTCARE,
135 ),
136 )
137 );
138 }
139
140 self::$_columnHeaders = array_merge(
141 self::$_columnHeaders,
142 array(
143 array(
144 'name' => ts('Status'),
145 'sort' => 'status',
146 'direction' => CRM_Utils_Sort::DONTCARE,
147 ),
148 array(
149 'name' => ts('Created By'),
150 'sort' => 'created_by',
151 'direction' => CRM_Utils_Sort::DONTCARE,
152 ),
153 array(
154 'name' => ts('Created Date'),
155 'sort' => 'created_date',
156 'direction' => $unscheduledOrder,
157 ),
158 array(
159 'name' => ts('Sent By'),
160 'sort' => 'scheduled_by',
161 'direction' => CRM_Utils_Sort::DONTCARE,
162 ),
163 array(
164 'name' => ts('Scheduled'),
165 'sort' => 'scheduled_date',
166 'direction' => $scheduledOrder,
167 ),
168 array(
169 'name' => ts('Started'),
170 'sort' => 'start_date',
171 'direction' => CRM_Utils_Sort::DONTCARE,
172 ),
173 array(
174 'name' => ts('Completed'),
175 'sort' => 'end_date',
176 'direction' => $completedOrder,
177 ),
178 )
179 );
180
181 if (CRM_Campaign_BAO_Campaign::isCampaignEnable()) {
182 self::$_columnHeaders[] = array(
183 'name' => ts('Campaign'),
184 'sort' => 'campaign_id',
185 'direction' => CRM_Utils_Sort::DONTCARE,
186 );
187 }
188
189 if ($output != CRM_Core_Selector_Controller::EXPORT) {
190 self::$_columnHeaders[] = array('name' => ts('Action'));
191 }
192 }
193
194 CRM_Core_Smarty::singleton()->assign('multilingual', CRM_Core_I18n::isMultilingual());
195 return self::$_columnHeaders;
196 }
197
198 /**
199 * Returns total number of rows for the query.
200 *
201 * @param string $action
202 *
203 * @return int
204 * Total number of rows
205 */
206 public function getTotalCount($action) {
207 $job = CRM_Mailing_BAO_MailingJob::getTableName();
208 $mailing = CRM_Mailing_BAO_Mailing::getTableName();
209 $mailingACL = CRM_Mailing_BAO_Mailing::mailingACL();
210
211 // get the where clause.
212 $params = array();
213 $whereClause = "$mailingACL AND " . $this->whereClause($params);
214
215 // CRM-11919 added addition ON clauses to mailing_job to match getRows
216 $query = "
217 SELECT COUNT( DISTINCT $mailing.id ) as count
218 FROM $mailing
219 LEFT JOIN $job ON ( $mailing.id = $job.mailing_id AND civicrm_mailing_job.is_test = 0 AND civicrm_mailing_job.parent_id IS NULL )
220 LEFT JOIN civicrm_contact createdContact ON ( $mailing.created_id = createdContact.id )
221 LEFT JOIN civicrm_contact scheduledContact ON ( $mailing.scheduled_id = scheduledContact.id )
222 WHERE $whereClause";
223
224 return CRM_Core_DAO::singleValueQuery($query, $params);
225 }
226
227 /**
228 * Returns all the rows in the given offset and rowCount.
229 *
230 * @param string $action
231 * The action being performed.
232 * @param int $offset
233 * The row number to start from.
234 * @param int $rowCount
235 * The number of rows to return.
236 * @param string $sort
237 * The sql string that describes the sort order.
238 * @param string $output
239 * What should the result set include (web/email/csv).
240 *
241 * @return int
242 * the total number of rows for this action
243 */
244 public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) {
245 static $actionLinks = NULL;
246 if (empty($actionLinks)) {
247 $cancelExtra = ts('Are you sure you want to cancel this mailing?');
248 $deleteExtra = ts('Are you sure you want to delete this mailing?');
249 $archiveExtra = ts('Are you sure you want to archive this mailing?');
250
251 $actionLinks = array(
252 CRM_Core_Action::ENABLE => array(
253 'name' => ts('Approve/Reject'),
254 'url' => 'civicrm/mailing/approve',
255 'qs' => 'mid=%%mid%%&reset=1',
256 'title' => ts('Approve/Reject Mailing'),
257 ),
258 CRM_Core_Action::VIEW => array(
259 'name' => ts('Report'),
260 'url' => 'civicrm/mailing/report',
261 'qs' => 'mid=%%mid%%&reset=1',
262 'title' => ts('View Mailing Report'),
263 ),
264 CRM_Core_Action::UPDATE => array(
265 'name' => ts('Re-Use'),
266 'url' => 'civicrm/mailing/send',
267 'qs' => 'mid=%%mid%%&reset=1',
268 'title' => ts('Re-Send Mailing'),
269 ),
270 CRM_Core_Action::DISABLE => array(
271 'name' => ts('Cancel'),
272 'url' => 'civicrm/mailing/browse',
273 'qs' => 'action=disable&mid=%%mid%%&reset=1',
274 'extra' => 'onclick="if (confirm(\'' . $cancelExtra . '\')) this.href+=\'&amp;confirmed=1\'; else return false;"',
275 'title' => ts('Cancel Mailing'),
276 ),
277 CRM_Core_Action::PREVIEW => array(
278 'name' => ts('Continue'),
279 'url' => 'civicrm/mailing/send',
280 'qs' => 'mid=%%mid%%&continue=true&reset=1',
281 'title' => ts('Continue Mailing'),
282 ),
283 CRM_Core_Action::DELETE => array(
284 'name' => ts('Delete'),
285 'url' => 'civicrm/mailing/browse',
286 'qs' => 'action=delete&mid=%%mid%%&reset=1',
287 'extra' => 'onclick="if (confirm(\'' . $deleteExtra . '\')) this.href+=\'&amp;confirmed=1\'; else return false;"',
288 'title' => ts('Delete Mailing'),
289 ),
290 CRM_Core_Action::RENEW => array(
291 'name' => ts('Archive'),
292 'url' => 'civicrm/mailing/browse/archived',
293 'qs' => 'action=renew&mid=%%mid%%&reset=1',
294 'extra' => 'onclick="if (confirm(\'' . $archiveExtra . '\')) this.href+=\'&amp;confirmed=1\'; else return false;"',
295 'title' => ts('Archive Mailing'),
296 ),
297 );
298 }
299
300 $allAccess = TRUE;
301 $workFlow = $showApprovalLinks = $showScheduleLinks = $showCreateLinks = FALSE;
302 if (CRM_Mailing_Info::workflowEnabled()) {
303 $allAccess = FALSE;
304 $workFlow = TRUE;
305 // supercedes all permission
306 if (CRM_Core_Permission::check('access CiviMail')) {
307 $allAccess = TRUE;
308 }
309
310 if (CRM_Core_Permission::check('approve mailings')) {
311 $showApprovalLinks = TRUE;
312 }
313
314 if (CRM_Core_Permission::check('create mailings')) {
315 $showCreateLinks = TRUE;
316 }
317
318 if (CRM_Core_Permission::check('schedule mailings')) {
319 $showScheduleLinks = TRUE;
320 }
321 }
322 $mailing = new CRM_Mailing_BAO_Mailing();
323
324 $params = array();
325
326 $whereClause = ' AND ' . $this->whereClause($params);
327
328 if (empty($params)) {
329 $this->_parent->assign('isSearch', 0);
330 }
331 else {
332 $this->_parent->assign('isSearch', 1);
333 }
334 $rows = &$mailing->getRows($offset, $rowCount, $sort, $whereClause, $params);
335
336 // get the search base mailing Ids, CRM-3711.
337 $searchMailings = $mailing->searchMailingIDs();
338
339 // check for delete CRM-4418
340 $allowToDelete = CRM_Core_Permission::check('delete in CiviMail');
341
342 if ($output != CRM_Core_Selector_Controller::EXPORT) {
343
344 // create the appropriate $op to use for hook_civicrm_links
345 $pageTypes = array('view', 'mailing', 'browse');
346 if ($this->_parent->_unscheduled) {
347 $pageTypes[] = 'unscheduled';
348 }
349 if ($this->_parent->_scheduled) {
350 $pageTypes[] = 'scheduled';
351 }
352 if ($this->_parent->_archived) {
353 $pageTypes[] = 'archived';
354 }
355 $opString = implode('.', $pageTypes);
356
357 // get languages for later conversion
358 $languages = CRM_Core_I18n::languages();
359
360 foreach ($rows as $key => $row) {
361 $actionMask = NULL;
362 if ($row['sms_provider_id']) {
363 $actionLinks[CRM_Core_Action::PREVIEW]['url'] = 'civicrm/sms/send';
364 }
365
366 if (!($row['status'] == 'Not scheduled') && !$row['sms_provider_id']) {
367 if ($allAccess || $showCreateLinks) {
368 $actionMask = CRM_Core_Action::VIEW;
369 }
370
371 if (!in_array($row['id'], $searchMailings)) {
372 if ($allAccess || $showCreateLinks) {
373 $actionMask |= CRM_Core_Action::UPDATE;
374 }
375 }
376 }
377 else {
378 if ($allAccess || ($showCreateLinks || $showScheduleLinks)) {
379 $actionMask = CRM_Core_Action::PREVIEW;
380 }
381 }
382 if (in_array($row['status'], array(
383 'Scheduled',
384 'Running',
385 'Paused',
386 ))) {
387 if ($allAccess ||
388 ($showApprovalLinks && $showCreateLinks && $showScheduleLinks)
389 ) {
390
391 $actionMask |= CRM_Core_Action::DISABLE;
392 }
393 if ($row['status'] == 'Scheduled' &&
394 empty($row['approval_status_id'])
395 ) {
396 if ($workFlow && ($allAccess || $showApprovalLinks)) {
397 $actionMask |= CRM_Core_Action::ENABLE;
398 }
399 }
400 }
401
402 if (in_array($row['status'], array('Complete', 'Canceled')) &&
403 !$row['archived']
404 ) {
405 if ($allAccess || $showCreateLinks) {
406 $actionMask |= CRM_Core_Action::RENEW;
407 }
408 }
409
410 // check for delete permission.
411 if ($allowToDelete) {
412 $actionMask |= CRM_Core_Action::DELETE;
413 }
414
415 if ($actionMask == NULL) {
416 $actionMask = CRM_Core_Action::ADD;
417 }
418 // get status strings as per locale settings CRM-4411.
419 $rows[$key]['status'] = CRM_Mailing_BAO_MailingJob::status($row['status']);
420
421 // get language string
422 $rows[$key]['language'] = (isset($row['language']) ? $languages[$row['language']] : NULL);
423
424 $validLinks = $actionLinks;
425 if (($mailingUrl = CRM_Mailing_BAO_Mailing::getPublicViewUrl($row['id'])) != FALSE) {
426 $validLinks[] = array(
427 'name' => ts('Public View'),
428 'url' => 'civicrm/mailing/view',
429 'qs' => 'id=%%mid%%&reset=1',
430 'title' => ts('Public View'),
431 'fe' => TRUE,
432 );
433 }
434
435 $rows[$key]['action'] = CRM_Core_Action::formLink(
436 $validLinks,
437 $actionMask,
438 array('mid' => $row['id']),
439 "more",
440 FALSE,
441 $opString,
442 "Mailing",
443 $row['id']
444 );
445
446 // unset($rows[$key]['id']);
447 // if the scheduled date is 0, replace it with an empty string
448 if ($rows[$key]['scheduled_iso'] == '0000-00-00 00:00:00') {
449 $rows[$key]['scheduled'] = '';
450 }
451 unset($rows[$key]['scheduled_iso']);
452 }
453 }
454
455 // also initialize the AtoZ pager
456 $this->pagerAtoZ();
457 return $rows;
458 }
459
460 /**
461 * Name of export file.
462 *
463 * @param string $output
464 * Type of output.
465 *
466 * @return string
467 * name of the file
468 */
469 public function getExportFileName($output = 'csv') {
470 return ts('CiviMail Mailings');
471 }
472
473 /**
474 * @param $parent
475 */
476 public function setParent($parent) {
477 $this->_parent = $parent;
478 }
479
480 /**
481 * @param array $params
482 * @param bool $sortBy
483 *
484 * @return int|string
485 */
486 public function whereClause(&$params, $sortBy = TRUE) {
487 $values = $clauses = array();
488 $isFormSubmitted = $this->_parent->get('hidden_find_mailings');
489
490 $title = $this->_parent->get('mailing_name');
491 if ($title) {
492 $clauses[] = 'name LIKE %1';
493 if (strpos($title, '%') !== FALSE) {
494 $params[1] = array($title, 'String', FALSE);
495 }
496 else {
497 $params[1] = array($title, 'String', TRUE);
498 }
499 }
500
501 $dateClause1 = $dateClause2 = array();
502 $from = $this->_parent->get('mailing_from');
503 if (!CRM_Utils_System::isNull($from)) {
504 if ($this->_parent->get('unscheduled')) {
505 $dateClause1[] = 'civicrm_mailing.created_date >= %2';
506 }
507 else {
508 $dateClause1[] = 'civicrm_mailing_job.start_date >= %2';
509 $dateClause2[] = 'civicrm_mailing_job.scheduled_date >= %2';
510 }
511 $params[2] = array($from, 'String');
512 }
513
514 $to = $this->_parent->get('mailing_to');
515 if (!CRM_Utils_System::isNull($to)) {
516 if ($this->_parent->get('unscheduled')) {
517 $dateClause1[] = ' civicrm_mailing.created_date <= %3 ';
518 }
519 else {
520 $dateClause1[] = 'civicrm_mailing_job.start_date <= %3';
521 $dateClause2[] = 'civicrm_mailing_job.scheduled_date <= %3';
522 }
523 $params[3] = array($to, 'String');
524 }
525
526 $dateClauses = array();
527 if (!empty($dateClause1)) {
528 $dateClauses[] = implode(' AND ', $dateClause1);
529 }
530 if (!empty($dateClause2)) {
531 $dateClauses[] = implode(' AND ', $dateClause2);
532 }
533 $dateClauses = implode(' OR ', $dateClauses);
534 if (!empty($dateClauses)) {
535 $clauses[] = "({$dateClauses})";
536 }
537
538 if ($this->_parent->get('sms')) {
539 $clauses[] = "civicrm_mailing.sms_provider_id IS NOT NULL";
540 }
541 else {
542 $clauses[] = "civicrm_mailing.sms_provider_id IS NULL";
543 }
544
545 // get values submitted by form
546 $isDraft = $this->_parent->get('status_unscheduled');
547 $isArchived = $this->_parent->get('is_archived');
548 $mailingStatus = $this->_parent->get('mailing_status');
549
550 if (!$isFormSubmitted && $this->_parent->get('scheduled')) {
551 // mimic default behavior for scheduled screen
552 $isArchived = 0;
553 $mailingStatus = array('Scheduled' => 1, 'Complete' => 1, 'Running' => 1, 'Canceled' => 1);
554 }
555 if (!$isFormSubmitted && $this->_parent->get('archived')) {
556 // mimic default behavior for archived screen
557 $isArchived = 1;
558 }
559 if (!$isFormSubmitted && $this->_parent->get('unscheduled')) {
560 // mimic default behavior for draft screen
561 $isDraft = 1;
562 }
563
564 $statusClauses = array();
565 if ($isDraft) {
566 $statusClauses[] = "civicrm_mailing.scheduled_id IS NULL";
567 }
568 if (!empty($mailingStatus)) {
569 $statusClauses[] = "civicrm_mailing_job.status IN ('" . implode("', '", array_keys($mailingStatus)) . "')";
570 }
571 if (!empty($statusClauses)) {
572 $clauses[] = "(" . implode(' OR ', $statusClauses) . ")";
573 }
574
575 if (isset($isArchived)) {
576 if ($isArchived) {
577 $clauses[] = "civicrm_mailing.is_archived = 1";
578 }
579 else {
580 $clauses[] = "(civicrm_mailing.is_archived IS NULL OR civicrm_mailing.is_archived = 0)";
581 }
582 }
583
584 if ($sortBy &&
585 $this->_parent->_sortByCharacter !== NULL
586 ) {
587 $clauses[] = "name LIKE '" . strtolower(CRM_Core_DAO::escapeWildCardString($this->_parent->_sortByCharacter)) . "%'";
588 }
589
590 // dont do a the below assignement when doing a
591 // AtoZ pager clause
592 if ($sortBy) {
593 if (count($clauses) > 1) {
594 $this->_parent->assign('isSearch', 1);
595 }
596 else {
597 $this->_parent->assign('isSearch', 0);
598 }
599 }
600
601 $createOrSentBy = $this->_parent->get('sort_name');
602 if (!CRM_Utils_System::isNull($createOrSentBy)) {
603 $clauses[] = '(createdContact.sort_name LIKE %4 OR scheduledContact.sort_name LIKE %4)';
604 $params[4] = array('%' . $createOrSentBy . '%', 'String');
605 }
606
607 $createdId = $this->_parent->get('createdId');
608 if ($createdId) {
609 $clauses[] = "(created_id = {$createdId})";
610 $params[5] = array($createdId, 'Integer');
611 }
612
613 $campainIds = $this->_parent->get('campaign_id');
614 if (!CRM_Utils_System::isNull($campainIds)) {
615 if (!is_array($campainIds)) {
616 $campaignIds = array($campaignIds);
617 }
618 $clauses[] = '( campaign_id IN ( ' . implode(' , ', array_values($campainIds)) . ' ) )';
619 }
620
621 if ($language = $this->_parent->get('language')) {
622 $clauses[] = "civicrm_mailing.language = %6";
623 $params[6] = array($language, 'String');
624 }
625
626 if (empty($clauses)) {
627 return 1;
628 }
629
630 return implode(' AND ', $clauses);
631 }
632
633 public function pagerAtoZ() {
634
635 $params = array();
636 $whereClause = $this->whereClause($params, FALSE);
637
638 $query = "
639 SELECT DISTINCT UPPER(LEFT(name, 1)) as sort_name
640 FROM civicrm_mailing
641 LEFT JOIN civicrm_mailing_job ON (civicrm_mailing_job.mailing_id = civicrm_mailing.id)
642 LEFT JOIN civicrm_contact createdContact ON ( civicrm_mailing.created_id = createdContact.id )
643 LEFT JOIN civicrm_contact scheduledContact ON ( civicrm_mailing.scheduled_id = scheduledContact.id )
644 WHERE $whereClause
645 ORDER BY UPPER(LEFT(name, 1))
646 ";
647
648 $dao = CRM_Core_DAO::executeQuery($query, $params);
649
650 $aToZBar = CRM_Utils_PagerAToZ::getAToZBar($dao, $this->_parent->_sortByCharacter, TRUE);
651 $this->_parent->assign('aToZ', $aToZBar);
652 }
653
654 }