dev/core#2850 update_email_resetdate consider opt out on hold
[civicrm-core.git] / CRM / Mailing / Event / BAO / Delivered.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17 class CRM_Mailing_Event_BAO_Delivered extends CRM_Mailing_Event_DAO_Delivered {
18
19 /**
20 * Class constructor.
21 */
22 public function __construct() {
23 parent::__construct();
24 }
25
26 /**
27 * Create a new delivery event.
28 *
29 * @param array $params
30 * Associative array of delivery event values.
31 *
32 * @return \CRM_Mailing_Event_BAO_Delivered
33 */
34 public static function &create(&$params) {
35 $q = &CRM_Mailing_Event_BAO_Queue::verify($params['job_id'],
36 $params['event_queue_id'],
37 $params['hash']
38 );
39
40 if (!$q) {
41 return NULL;
42 }
43
44 $delivered = new CRM_Mailing_Event_BAO_Delivered();
45 $delivered->time_stamp = date('YmdHis');
46 $delivered->copyValues($params);
47 $delivered->save();
48
49 $queue = new CRM_Mailing_Event_BAO_Queue();
50 $queue->id = $params['event_queue_id'];
51 $queue->find(TRUE);
52
53 while ($queue->fetch()) {
54 $email = new CRM_Core_BAO_Email();
55 $email->id = $queue->email_id;
56 $email->hold_date = '';
57 $email->reset_date = date('YmdHis');
58 $email->save();
59 }
60
61 return $delivered;
62 }
63
64 /**
65 * Get row count for the event selector.
66 *
67 * @param int $mailing_id
68 * ID of the mailing.
69 * @param int $job_id
70 * Optional ID of a job to filter on.
71 * @param bool $is_distinct
72 * Group by queue ID?.
73 * @param string $toDate
74 *
75 * @return int
76 * Number of rows in result set
77 */
78 public static function getTotalCount($mailing_id, $job_id = NULL, $is_distinct = FALSE, $toDate = NULL) {
79 $dao = new CRM_Core_DAO();
80
81 $delivered = self::getTableName();
82 $bounce = CRM_Mailing_Event_BAO_Bounce::getTableName();
83 $queue = CRM_Mailing_Event_BAO_Queue::getTableName();
84 $mailing = CRM_Mailing_BAO_Mailing::getTableName();
85 $job = CRM_Mailing_BAO_MailingJob::getTableName();
86
87 $query = "
88 SELECT COUNT($delivered.id) as delivered
89 FROM $delivered
90 INNER JOIN $queue
91 ON $delivered.event_queue_id = $queue.id
92 LEFT JOIN $bounce
93 ON $delivered.event_queue_id = $bounce.event_queue_id
94 INNER JOIN $job
95 ON $queue.job_id = $job.id
96 AND $job.is_test = 0
97 INNER JOIN $mailing
98 ON $job.mailing_id = $mailing.id
99 WHERE $bounce.id IS null
100 AND $mailing.id = " . CRM_Utils_Type::escape($mailing_id, 'Integer');
101
102 if (!empty($toDate)) {
103 $query .= " AND $delivered.time_stamp <= $toDate";
104 }
105
106 if (!empty($job_id)) {
107 $query .= " AND $job.id = " . CRM_Utils_Type::escape($job_id, 'Integer');
108 }
109
110 if ($is_distinct) {
111 $query .= " GROUP BY $queue.id ";
112 }
113
114 // query was missing
115 $dao->query($query);
116
117 if ($dao->fetch()) {
118 return $dao->delivered;
119 }
120
121 return NULL;
122 }
123
124 /**
125 * Get rows for the event browser.
126 *
127 * @param int $mailing_id
128 * ID of the mailing.
129 * @param int $job_id
130 * Optional ID of the job.
131 * @param bool $is_distinct
132 * Group by queue id?.
133 * @param int $offset
134 * Offset.
135 * @param int $rowCount
136 * Number of rows.
137 * @param array $sort
138 * Sort array.
139 *
140 * @param int $is_test
141 *
142 * @return array
143 * Result set
144 */
145 public static function &getRows(
146 $mailing_id, $job_id = NULL,
147 $is_distinct = FALSE, $offset = NULL, $rowCount = NULL, $sort = NULL, $is_test = 0
148 ) {
149
150 $dao = new CRM_Core_DAO();
151
152 $delivered = self::getTableName();
153 $bounce = CRM_Mailing_Event_BAO_Bounce::getTableName();
154 $queue = CRM_Mailing_Event_BAO_Queue::getTableName();
155 $mailing = CRM_Mailing_BAO_Mailing::getTableName();
156 $job = CRM_Mailing_BAO_MailingJob::getTableName();
157 $contact = CRM_Contact_BAO_Contact::getTableName();
158 $email = CRM_Core_BAO_Email::getTableName();
159
160 $query = "
161 SELECT $delivered.id as id,
162 $contact.display_name as display_name,
163 $contact.id as contact_id,
164 $email.email as email,
165 $delivered.time_stamp as date
166 FROM $contact
167 INNER JOIN $queue
168 ON $queue.contact_id = $contact.id
169 INNER JOIN $email
170 ON $queue.email_id = $email.id
171 INNER JOIN $delivered
172 ON $delivered.event_queue_id = $queue.id
173 LEFT JOIN $bounce
174 ON $bounce.event_queue_id = $queue.id
175 INNER JOIN $job
176 ON $queue.job_id = $job.id
177 AND $job.is_test = $is_test
178 INNER JOIN $mailing
179 ON $job.mailing_id = $mailing.id
180 WHERE $bounce.id IS null
181 AND $mailing.id = " . CRM_Utils_Type::escape($mailing_id, 'Integer');
182
183 if (!empty($job_id)) {
184 $query .= " AND $job.id = " . CRM_Utils_Type::escape($job_id, 'Integer');
185 }
186
187 if ($is_distinct) {
188 $query .= " GROUP BY $queue.id, $delivered.id";
189 }
190
191 $orderBy = "sort_name ASC, {$delivered}.time_stamp DESC";
192 if ($sort) {
193 if (is_string($sort)) {
194 $sort = CRM_Utils_Type::escape($sort, 'String');
195 $orderBy = $sort;
196 }
197 else {
198 $orderBy = trim($sort->orderBy());
199 }
200 }
201
202 $query .= " ORDER BY {$orderBy} ";
203
204 if ($offset || $rowCount) {
205 //Added "||$rowCount" to avoid displaying all records on first page
206 $query .= ' LIMIT ' . CRM_Utils_Type::escape($offset, 'Integer') . ', ' . CRM_Utils_Type::escape($rowCount, 'Integer');
207 }
208
209 $dao->query($query);
210
211 $results = [];
212
213 while ($dao->fetch()) {
214 $url = CRM_Utils_System::url('civicrm/contact/view',
215 "reset=1&cid={$dao->contact_id}"
216 );
217 $results[$dao->id] = [
218 'contact_id' => $dao->contact_id,
219 'name' => "<a href=\"$url\">{$dao->display_name}</a>",
220 'email' => $dao->email,
221 'date' => CRM_Utils_Date::customFormat($dao->date),
222 ];
223 }
224 return $results;
225 }
226
227 /**
228 * @param $eventQueueIDs
229 * @param null $time
230 */
231 public static function bulkCreate($eventQueueIDs, $time = NULL) {
232 if (!$time) {
233 $time = date('YmdHis');
234 }
235
236 // construct a bulk insert statement
237 $values = [];
238 foreach ($eventQueueIDs as $eqID) {
239 $values[] = "( $eqID, '{$time}' )";
240 }
241
242 while (!empty($values)) {
243 $input = array_splice($values, 0, CRM_Core_DAO::BULK_INSERT_COUNT);
244 $str = implode(',', $input);
245 $sql = "INSERT INTO civicrm_mailing_event_delivered ( event_queue_id, time_stamp ) VALUES $str;";
246 CRM_Core_DAO::executeQuery($sql);
247 }
248 }
249
250 /**
251 * Since we never know when a mailing really bounces (hard bounce == NOW, soft bounce == NOW to NOW + 3 days?)
252 * we cannot decide when an email address last got an email.
253 *
254 * We want to avoid putting on hold an email address which had a few bounces (mbox full) and then got quite a few
255 * successful deliveries before starting the bounce again. The current code does not set the resetDate and hence
256 * the above scenario results in the email being put on hold
257 *
258 * This function rectifies that by considering all non-test mailing jobs which have completed between $minDays and $maxDays
259 * and setting the resetDate to the date that an email was delivered
260 *
261 * @param int $minDays
262 * Consider mailings that were completed at least $minDays ago.
263 * @param int $maxDays
264 * Consider mailings that were completed not more than $maxDays ago.
265 */
266 public static function updateEmailResetDate(int $minDays = 3, int $maxDays = 7) {
267
268 if ($minDays < 0 || $maxDays < 0 || $maxDays <= $minDays) {
269 throw new \InvalidArgumentException("minDays and maxDays must be >=0 and maxDays > minDays");
270 }
271
272 $temporaryTable = CRM_Utils_SQL_TempTable::build()
273 ->setCategory('mailingemail')
274 ->setMemory()
275 ->createWithColumns('id int primary key, reset_date datetime');
276 $temporaryTableName = $temporaryTable->getName();
277
278 $query = "
279 INSERT INTO {$temporaryTableName} (id, reset_date)
280 SELECT civicrm_email.id as email_id,
281 max(civicrm_mailing_event_delivered.time_stamp) as reset_date
282 FROM civicrm_mailing_event_queue
283 INNER JOIN civicrm_email ON civicrm_mailing_event_queue.email_id = civicrm_email.id
284 INNER JOIN civicrm_mailing_event_delivered ON civicrm_mailing_event_delivered.event_queue_id = civicrm_mailing_event_queue.id
285 LEFT JOIN civicrm_mailing_event_bounce ON civicrm_mailing_event_bounce.event_queue_id = civicrm_mailing_event_queue.id
286 INNER JOIN civicrm_mailing_job ON civicrm_mailing_event_queue.job_id = civicrm_mailing_job.id AND civicrm_mailing_job.is_test = 0
287 WHERE civicrm_mailing_event_bounce.id IS NULL
288 AND civicrm_mailing_job.status = 'Complete'
289 AND civicrm_mailing_job.end_date BETWEEN DATE_SUB(NOW(), INTERVAL $maxDays day) AND DATE_SUB(NOW(), INTERVAL $minDays day)
290 AND (civicrm_email.reset_date IS NULL OR civicrm_email.reset_date < civicrm_mailing_job.start_date)
291 AND civicrm_email.on_hold != 2
292 GROUP BY civicrm_email.id
293 ";
294 CRM_Core_DAO::executeQuery($query);
295
296 $query = "
297 UPDATE civicrm_email e
298 INNER JOIN {$temporaryTableName} et ON e.id = et.id
299 SET e.on_hold = 0,
300 e.hold_date = NULL,
301 e.reset_date = et.reset_date
302 ";
303 CRM_Core_DAO::executeQuery($query);
304 $temporaryTable->drop();
305 }
306
307 }