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