Merge pull request #5076 from colemanw/Attachment
[civicrm-core.git] / CRM / Mailing / BAO / MailingJob.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
39de6fd5 4 | CiviCRM version 4.6 |
6a488035 5 +--------------------------------------------------------------------+
06b69b18 6 | Copyright CiviCRM LLC (c) 2004-2014 |
6a488035
TO
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 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035
TO
27
28/**
29 *
30 * @package CRM
06b69b18 31 * @copyright CiviCRM LLC (c) 2004-2014
6a488035
TO
32 * $Id$
33 *
34 */
35
36require_once 'Mail.php';
4c6ce474
EM
37
38/**
39 * Class CRM_Mailing_BAO_MailingJob
40 */
9da8dc8c 41class CRM_Mailing_BAO_MailingJob extends CRM_Mailing_DAO_MailingJob {
7da04cde 42 const MAX_CONTACTS_TO_PROCESS = 1000;
6a488035 43
97b7d4a0 44 /**
a335f6b2 45 * (Dear God Why) Keep a global count of mails processed within the current
97b7d4a0
TO
46 * request.
47 *
97b7d4a0
TO
48 * @var int
49 */
50 static $mailsProcessed = 0;
51
6a488035 52 /**
100fef9d 53 * Class constructor
6a488035 54 */
00be9182 55 public function __construct() {
6a488035
TO
56 parent::__construct();
57 }
58
af4c09bc 59 /**
c490a46a 60 * @param array $params
af4c09bc
EM
61 *
62 * @return CRM_Mailing_BAO_MailingJob
63 */
84dd5db6 64 static public function create($params) {
9da8dc8c 65 $job = new CRM_Mailing_BAO_MailingJob();
6a488035
TO
66 $job->mailing_id = $params['mailing_id'];
67 $job->status = $params['status'];
68 $job->scheduled_date = $params['scheduled_date'];
69 $job->is_test = $params['is_test'];
70 $job->save();
71 $mailing = new CRM_Mailing_BAO_Mailing();
84dd5db6 72 $mailing->getRecipients($job->id, $params['mailing_id'], NULL, NULL, TRUE, FALSE);
6a488035
TO
73 return $job;
74 }
af4c09bc 75
6a488035
TO
76 /**
77 * Initiate all pending/ready jobs
78 *
100fef9d 79 * @param array $testParams
af4c09bc
EM
80 * @param null $mode
81 *
6a488035 82 * @return void
6a488035
TO
83 */
84 public static function runJobs($testParams = NULL, $mode = NULL) {
9da8dc8c 85 $job = new CRM_Mailing_BAO_MailingJob();
6a488035 86
353ffa53
TO
87 $config = CRM_Core_Config::singleton();
88 $jobTable = CRM_Mailing_DAO_MailingJob::getTableName();
6a488035
TO
89 $mailingTable = CRM_Mailing_DAO_Mailing::getTableName();
90
91 if (!empty($testParams)) {
92 $query = "
93 SELECT *
94 FROM $jobTable
95 WHERE id = {$testParams['job_id']}";
96 $job->query($query);
97 }
98 else {
99 $currentTime = date('YmdHis');
353ffa53
TO
100 $mailingACL = CRM_Mailing_BAO_Mailing::mailingACL('m');
101 $domainID = CRM_Core_Config::domainID();
6a488035
TO
102
103 $modeClause = 'AND m.sms_provider_id IS NULL';
104 if ($mode == 'sms') {
105 $modeClause = 'AND m.sms_provider_id IS NOT NULL';
106 }
107
108 // Select the first child job that is scheduled
109 // CRM-6835
110 $query = "
111 SELECT j.*
112 FROM $jobTable j,
113 $mailingTable m
114 WHERE m.id = j.mailing_id AND m.domain_id = {$domainID}
115 {$modeClause}
116 AND j.is_test = 0
117 AND ( ( j.start_date IS null
118 AND j.scheduled_date <= $currentTime
119 AND j.status = 'Scheduled' )
120 OR ( j.status = 'Running'
121 AND j.end_date IS null ) )
122 AND (j.job_type = 'child')
123 AND {$mailingACL}
124 ORDER BY j.mailing_id,
125 j.id
126 ";
127
128 $job->query($query);
129 }
130
6a488035
TO
131 while ($job->fetch()) {
132 // still use job level lock for each child job
133 $lockName = "civimail.job.{$job->id}";
134
135 $lock = new CRM_Core_Lock($lockName);
136 if (!$lock->isAcquired()) {
137 continue;
138 }
139
140 // for test jobs we do not change anything, since its on a short-circuit path
141 if (empty($testParams)) {
142 // we've got the lock, but while we were waiting and processing
143 // other emails, this job might have changed under us
144 // lets get the job status again and check
1a5727bd 145 $job->status = CRM_Core_DAO::getFieldValue(
cbe30f5a 146 'CRM_Mailing_DAO_MailingJob',
6a488035 147 $job->id,
6606b2e1
DL
148 'status',
149 'id',
150 TRUE
6a488035
TO
151 );
152
1a5727bd
DL
153 if (
154 $job->status != 'Running' &&
6a488035
TO
155 $job->status != 'Scheduled'
156 ) {
157 // this includes Cancelled and other statuses, CRM-4246
158 $lock->release();
159 continue;
160 }
161 }
162
163 /* Queue up recipients for the child job being launched */
164
165 if ($job->status != 'Running') {
166 $transaction = new CRM_Core_Transaction();
167
168 // have to queue it up based on the offset and limits
169 // get the parent ID, and limit and offset
170 $job->queue($testParams);
171
172 // Mark up the starting time
353ffa53
TO
173 $saveJob = new CRM_Mailing_DAO_MailingJob();
174 $saveJob->id = $job->id;
6a488035 175 $saveJob->start_date = date('YmdHis');
353ffa53 176 $saveJob->status = 'Running';
6a488035
TO
177 $saveJob->save();
178
179 $transaction->commit();
180 }
181
182 // Get the mailer
183 // make it a persistent connection, CRM-9349
184 if ($mode === NULL) {
185 $mailer = $config->getMailer(TRUE);
186 }
187 elseif ($mode == 'sms') {
188 $mailer = CRM_SMS_Provider::singleton(array('mailing_id' => $job->mailing_id));
189 }
190
191 // Compose and deliver each child job
192 $isComplete = $job->deliver($mailer, $testParams);
193
194 CRM_Utils_Hook::post('create', 'CRM_Mailing_DAO_Spool', $job->id, $isComplete);
195
196 // Mark the child complete
197 if ($isComplete) {
198 /* Finish the job */
199
200 $transaction = new CRM_Core_Transaction();
201
353ffa53
TO
202 $saveJob = new CRM_Mailing_DAO_MailingJob();
203 $saveJob->id = $job->id;
6a488035 204 $saveJob->end_date = date('YmdHis');
353ffa53 205 $saveJob->status = 'Complete';
6a488035
TO
206 $saveJob->save();
207
208 $transaction->commit();
209
210 // don't mark the mailing as complete
211 }
212
213 // Release the child joblock
214 $lock->release();
215
216 if ($testParams) {
217 return $isComplete;
218 }
219 }
220 }
221
e0ef6999 222 /**
4f1f1f2a
CW
223 * post process to determine if the parent job
224 * as well as the mailing is complete after the run
e0ef6999
EM
225 * @param null $mode
226 */
6a488035
TO
227 public static function runJobs_post($mode = NULL) {
228
9da8dc8c 229 $job = new CRM_Mailing_BAO_MailingJob();
6a488035
TO
230
231 $mailing = new CRM_Mailing_BAO_Mailing();
232
353ffa53
TO
233 $config = CRM_Core_Config::singleton();
234 $jobTable = CRM_Mailing_DAO_MailingJob::getTableName();
6a488035
TO
235 $mailingTable = CRM_Mailing_DAO_Mailing::getTableName();
236
237 $currentTime = date('YmdHis');
353ffa53
TO
238 $mailingACL = CRM_Mailing_BAO_Mailing::mailingACL('m');
239 $domainID = CRM_Core_Config::domainID();
6a488035
TO
240
241 $query = "
242 SELECT j.*
243 FROM $jobTable j,
244 $mailingTable m
245 WHERE m.id = j.mailing_id AND m.domain_id = {$domainID}
246 AND j.is_test = 0
247 AND j.scheduled_date <= $currentTime
248 AND j.status = 'Running'
249 AND j.end_date IS null
250 AND (j.job_type != 'child' OR j.job_type is NULL)
251 ORDER BY j.scheduled_date,
252 j.start_date";
253
254 $job->query($query);
255
256 // For each parent job that is running, let's look at their child jobs
257 while ($job->fetch()) {
258
9da8dc8c 259 $child_job = new CRM_Mailing_BAO_MailingJob();
6a488035
TO
260
261 $child_job_sql = "
262 SELECT count(j.id)
263 FROM civicrm_mailing_job j, civicrm_mailing m
264 WHERE m.id = j.mailing_id
265 AND j.job_type = 'child'
266 AND j.parent_id = %1
267 AND j.status <> 'Complete'";
268 $params = array(1 => array($job->id, 'Integer'));
269
270 $anyChildLeft = CRM_Core_DAO::singleValueQuery($child_job_sql, $params);
271
272 // all of the child jobs are complete, update
273 // the parent job as well as the mailing status
274 if (!$anyChildLeft) {
275
276 $transaction = new CRM_Core_Transaction();
277
353ffa53
TO
278 $saveJob = new CRM_Mailing_DAO_MailingJob();
279 $saveJob->id = $job->id;
6a488035 280 $saveJob->end_date = date('YmdHis');
353ffa53 281 $saveJob->status = 'Complete';
6a488035
TO
282 $saveJob->save();
283
284 $mailing->reset();
285 $mailing->id = $job->mailing_id;
286 $mailing->is_completed = TRUE;
287 $mailing->save();
288 $transaction->commit();
289 }
290 }
291 }
292
293
e0ef6999 294 /**
4f1f1f2a 295 * before we run jobs, we need to split the jobs
e0ef6999
EM
296 * @param int $offset
297 * @param null $mode
298 */
6a488035 299 public static function runJobs_pre($offset = 200, $mode = NULL) {
9da8dc8c 300 $job = new CRM_Mailing_BAO_MailingJob();
6a488035 301
353ffa53 302 $jobTable = CRM_Mailing_DAO_MailingJob::getTableName();
6a488035
TO
303 $mailingTable = CRM_Mailing_DAO_Mailing::getTableName();
304
305 $currentTime = date('YmdHis');
306 $mailingACL = CRM_Mailing_BAO_Mailing::mailingACL('m');
307
9da8dc8c 308 $workflowClause = CRM_Mailing_BAO_MailingJob::workflowClause();
6a488035
TO
309
310 $domainID = CRM_Core_Config::domainID();
311
312 $modeClause = 'AND m.sms_provider_id IS NULL';
313 if ($mode == 'sms') {
314 $modeClause = 'AND m.sms_provider_id IS NOT NULL';
315 }
316
317 // Select all the mailing jobs that are created from
318 // when the mailing is submitted or scheduled.
319 $query = "
320 SELECT j.*
321 FROM $jobTable j,
322 $mailingTable m
323 WHERE m.id = j.mailing_id AND m.domain_id = {$domainID}
324 $workflowClause
325 $modeClause
326 AND j.is_test = 0
327 AND ( ( j.start_date IS null
328 AND j.scheduled_date <= $currentTime
329 AND j.status = 'Scheduled'
330 AND j.end_date IS null ) )
331 AND ((j.job_type is NULL) OR (j.job_type <> 'child'))
332 ORDER BY j.scheduled_date,
333 j.start_date";
334
6a488035
TO
335 $job->query($query);
336
6a488035
TO
337 // For each of the "Parent Jobs" we find, we split them into
338 // X Number of child jobs
339 while ($job->fetch()) {
340 // still use job level lock for each child job
341 $lockName = "civimail.job.{$job->id}";
342
343 $lock = new CRM_Core_Lock($lockName);
344 if (!$lock->isAcquired()) {
345 continue;
346 }
347
348 // Re-fetch the job status in case things
349 // changed between the first query and now
350 // to avoid race conditions
6606b2e1 351 $job->status = CRM_Core_DAO::getFieldValue(
cbe30f5a 352 'CRM_Mailing_DAO_MailingJob',
6a488035 353 $job->id,
6606b2e1
DL
354 'status',
355 'id',
356 TRUE
6a488035
TO
357 );
358 if ($job->status != 'Scheduled') {
359 $lock->release();
360 continue;
361 }
362
363 $job->split_job($offset);
364
365 // update the status of the parent job
366 $transaction = new CRM_Core_Transaction();
367
353ffa53
TO
368 $saveJob = new CRM_Mailing_DAO_MailingJob();
369 $saveJob->id = $job->id;
6a488035 370 $saveJob->start_date = date('YmdHis');
353ffa53 371 $saveJob->status = 'Running';
6a488035
TO
372 $saveJob->save();
373
374 $transaction->commit();
375
376 // Release the job lock
377 $lock->release();
378 }
379 }
380
e0ef6999 381 /**
4f1f1f2a
CW
382 * Split the parent job into n number of child job based on an offset
383 * If null or 0 , we create only one child job
e0ef6999
EM
384 * @param int $offset
385 */
6a488035
TO
386 public function split_job($offset = 200) {
387 $recipient_count = CRM_Mailing_BAO_Recipients::mailingSize($this->mailing_id);
388
9da8dc8c 389 $jobTable = CRM_Mailing_DAO_MailingJob::getTableName();
6a488035 390
6a488035
TO
391 $dao = new CRM_Core_DAO();
392
393 $sql = "
394INSERT INTO civicrm_mailing_job
395(`mailing_id`, `scheduled_date`, `status`, `job_type`, `parent_id`, `job_offset`, `job_limit`)
396VALUES (%1, %2, %3, %4, %5, %6, %7)
397";
35f7561f 398 $params = array(
353ffa53 399 1 => array($this->mailing_id, 'Integer'),
6a488035
TO
400 2 => array($this->scheduled_date, 'String'),
401 3 => array('Scheduled', 'String'),
402 4 => array('child', 'String'),
403 5 => array($this->id, 'Integer'),
404 6 => array(0, 'Integer'),
405 7 => array($recipient_count, 'Integer'),
406 );
407
408 // create one child job if the mailing size is less than the offset
cbe30f5a 409 // probably use a CRM_Mailing_DAO_MailingJob( );
6a488035
TO
410 if (empty($offset) ||
411 $recipient_count <= $offset
412 ) {
413 CRM_Core_DAO::executeQuery($sql, $params);
414 }
415 else {
416 // Creating 'child jobs'
417 for ($i = 0; $i < $recipient_count; $i = $i + $offset) {
418 $params[6][0] = $i;
419 $params[7][0] = $offset;
420 CRM_Core_DAO::executeQuery($sql, $params);
421 }
422 }
423 }
424
e0ef6999 425 /**
100fef9d 426 * @param array $testParams
e0ef6999 427 */
6a488035
TO
428 public function queue($testParams = NULL) {
429 $mailing = new CRM_Mailing_BAO_Mailing();
430 $mailing->id = $this->mailing_id;
431 if (!empty($testParams)) {
432 $mailing->getTestRecipients($testParams);
433 }
434 else {
435 // We are still getting all the recipients from the parent job
436 // so we don't mess with the include/exclude logic.
437 $recipients = CRM_Mailing_BAO_Recipients::mailingQuery($this->mailing_id, $this->job_offset, $this->job_limit);
438
439 // FIXME: this is not very smart, we should move this to one DB call
440 // INSERT INTO ... SELECT FROM ..
441 // the thing we need to figure out is how to generate the hash automatically
353ffa53 442 $now = time();
6a488035 443 $params = array();
353ffa53 444 $count = 0;
6a488035
TO
445 while ($recipients->fetch()) {
446 if ($recipients->phone_id) {
447 $recipients->email_id = "null";
448 }
449 else {
450 $recipients->phone_id = "null";
451 }
452
453 $params[] = array(
454 $this->id,
455 $recipients->email_id,
456 $recipients->contact_id,
457 $recipients->phone_id,
458 );
459 $count++;
460 if ($count % CRM_Core_DAO::BULK_MAIL_INSERT_COUNT == 0) {
461 CRM_Mailing_Event_BAO_Queue::bulkCreate($params, $now);
462 $count = 0;
463 $params = array();
464 }
465 }
466
467 if (!empty($params)) {
468 CRM_Mailing_Event_BAO_Queue::bulkCreate($params, $now);
469 }
470 }
471 }
472
473 /**
474 * Send the mailing
475 *
90c8230e
TO
476 * @param object $mailer
477 * A Mail object to send the messages.
2a6da8d7 478 *
100fef9d 479 * @param array $testParams
6a488035
TO
480 *
481 * @return void
6a488035
TO
482 */
483 public function deliver(&$mailer, $testParams = NULL) {
484 $mailing = new CRM_Mailing_BAO_Mailing();
485 $mailing->id = $this->mailing_id;
486 $mailing->find(TRUE);
487 $mailing->free();
488
353ffa53
TO
489 $eq = new CRM_Mailing_Event_BAO_Queue();
490 $eqTable = CRM_Mailing_Event_BAO_Queue::getTableName();
491 $emailTable = CRM_Core_BAO_Email::getTableName();
492 $phoneTable = CRM_Core_DAO_Phone::getTableName();
6a488035 493 $contactTable = CRM_Contact_BAO_Contact::getTableName();
353ffa53
TO
494 $edTable = CRM_Mailing_Event_BAO_Delivered::getTableName();
495 $ebTable = CRM_Mailing_Event_BAO_Bounce::getTableName();
6a488035
TO
496
497 $query = " SELECT $eqTable.id,
498 $emailTable.email as email,
499 $eqTable.contact_id,
500 $eqTable.hash,
501 NULL as phone
502 FROM $eqTable
503 INNER JOIN $emailTable
504 ON $eqTable.email_id = $emailTable.id
505 INNER JOIN $contactTable
506 ON $contactTable.id = $emailTable.contact_id
507 LEFT JOIN $edTable
508 ON $eqTable.id = $edTable.event_queue_id
509 LEFT JOIN $ebTable
510 ON $eqTable.id = $ebTable.event_queue_id
511 WHERE $eqTable.job_id = " . $this->id . "
512 AND $edTable.id IS null
513 AND $ebTable.id IS null
514 AND $contactTable.is_opt_out = 0";
515
516 if ($mailing->sms_provider_id) {
517 $query = "
518 SELECT $eqTable.id,
519 $phoneTable.phone as phone,
520 $eqTable.contact_id,
521 $eqTable.hash,
522 NULL as email
523 FROM $eqTable
524 INNER JOIN $phoneTable
525 ON $eqTable.phone_id = $phoneTable.id
526 INNER JOIN $contactTable
527 ON $contactTable.id = $phoneTable.contact_id
528 LEFT JOIN $edTable
529 ON $eqTable.id = $edTable.event_queue_id
530 LEFT JOIN $ebTable
531 ON $eqTable.id = $ebTable.event_queue_id
532 WHERE $eqTable.job_id = " . $this->id . "
533 AND $edTable.id IS null
534 AND $ebTable.id IS null
7a9646c1 535 AND ( $contactTable.is_opt_out = 0
536 OR $contactTable.do_not_sms = 0 )";
6a488035
TO
537 }
538 $eq->query($query);
539
97b7d4a0 540 $config = NULL;
6a488035
TO
541
542 if ($config == NULL) {
543 $config = CRM_Core_Config::singleton();
544 }
545
546 $job_date = CRM_Utils_Date::isoToMysql($this->scheduled_date);
547 $fields = array();
548
549 if (!empty($testParams)) {
a5db8e6f 550 $mailing->subject = ts('[CiviMail Draft]') . ' ' . $mailing->subject;
6a488035
TO
551 }
552
553 CRM_Mailing_BAO_Mailing::tokenReplace($mailing);
554
555 // get and format attachments
556 $attachments = CRM_Core_BAO_File::getEntityFile('civicrm_mailing', $mailing->id);
557
558 if (defined('CIVICRM_MAIL_SMARTY') && CIVICRM_MAIL_SMARTY) {
559 CRM_Core_Smarty::registerStringResource();
560 }
561
df7f4ae1
DL
562 // CRM-12376
563 // This handles the edge case scenario where all the mails
564 // have been delivered in prior jobs
565 $isDelivered = TRUE;
6a488035
TO
566
567 // make sure that there's no more than $config->mailerBatchLimit mails processed in a run
568 while ($eq->fetch()) {
569 // if ( ( $mailsProcessed % 100 ) == 0 ) {
570 // CRM_Utils_System::xMemory( "$mailsProcessed: " );
571 // }
572
df7f4ae1
DL
573 if (
574 $config->mailerBatchLimit > 0 &&
97b7d4a0 575 self::$mailsProcessed >= $config->mailerBatchLimit
6a488035
TO
576 ) {
577 if (!empty($fields)) {
578 $this->deliverGroup($fields, $mailing, $mailer, $job_date, $attachments);
579 }
580 $eq->free();
581 return FALSE;
582 }
97b7d4a0 583 self::$mailsProcessed++;
6a488035
TO
584
585 $fields[] = array(
586 'id' => $eq->id,
587 'hash' => $eq->hash,
588 'contact_id' => $eq->contact_id,
589 'email' => $eq->email,
590 'phone' => $eq->phone,
591 );
592 if (count($fields) == self::MAX_CONTACTS_TO_PROCESS) {
593 $isDelivered = $this->deliverGroup($fields, $mailing, $mailer, $job_date, $attachments);
594 if (!$isDelivered) {
595 $eq->free();
596 return $isDelivered;
597 }
598 $fields = array();
599 }
600 }
601
602 $eq->free();
603
604 if (!empty($fields)) {
605 $isDelivered = $this->deliverGroup($fields, $mailing, $mailer, $job_date, $attachments);
606 }
607 return $isDelivered;
608 }
609
e0ef6999
EM
610 /**
611 * @param $fields
612 * @param $mailing
613 * @param $mailer
614 * @param $job_date
615 * @param $attachments
616 *
617 * @return bool|null
618 * @throws Exception
619 */
6a488035
TO
620 public function deliverGroup(&$fields, &$mailing, &$mailer, &$job_date, &$attachments) {
621 static $smtpConnectionErrors = 0;
622
1a5727bd 623 if (!is_object($mailer) || empty($fields)) {
6a488035
TO
624 CRM_Core_Error::fatal();
625 }
626
627 // get the return properties
628 $returnProperties = $mailing->getReturnProperties();
353ffa53
TO
629 $params = $targetParams = $deliveredParams = array();
630 $count = 0;
7a9646c1 631
e36508bf
JJ
632 /**
633 * CRM-15702: Sending bulk sms to contacts without e-mail addres fails.
634 * Solution is to skip checking for on hold
635 */
35f7561f 636 $skipOnHold = TRUE; //do include a statement to check wether e-mail address is on hold
e36508bf 637 if ($mailing->sms_provider_id) {
35f7561f 638 $skipOnHold = FALSE; //do not include a statement to check wether e-mail address is on hold
e36508bf 639 }
6a488035
TO
640
641 foreach ($fields as $key => $field) {
642 $params[] = $field['contact_id'];
643 }
644
645 $details = CRM_Utils_Token::getTokenDetails(
646 $params,
647 $returnProperties,
e36508bf 648 $skipOnHold, TRUE, NULL,
6a488035
TO
649 $mailing->getFlattenedTokens(),
650 get_class($this),
651 $this->id
652 );
653
654 $config = CRM_Core_Config::singleton();
655 foreach ($fields as $key => $field) {
656 $contactID = $field['contact_id'];
657 if (!array_key_exists($contactID, $details[0])) {
658 $details[0][$contactID] = array();
659 }
6a488035 660
1a5727bd 661 /* Compose the mailing */
6a488035
TO
662 $recipient = $replyToEmail = NULL;
663 $replyValue = strcmp($mailing->replyto_email, $mailing->from_email);
664 if ($replyValue) {
665 $replyToEmail = $mailing->replyto_email;
666 }
667
1a5727bd
DL
668 $message = &$mailing->compose(
669 $this->id, $field['id'], $field['hash'],
6a488035
TO
670 $field['contact_id'], $field['email'],
671 $recipient, FALSE, $details[0][$contactID], $attachments,
672 FALSE, NULL, $replyToEmail
673 );
674 if (empty($message)) {
675 // lets keep the message in the queue
676 // most likely a permissions related issue with smarty templates
677 // or a bad contact id? CRM-9833
678 continue;
679 }
680
681 /* Send the mailing */
682
683 $body = &$message->get();
684 $headers = &$message->headers();
685
686 if ($mailing->sms_provider_id) {
687 $provider = CRM_SMS_Provider::singleton(array('mailing_id' => $mailing->id));
353ffa53
TO
688 $body = $provider->getMessage($message, $field['contact_id'], $details[0][$contactID]);
689 $headers = $provider->getRecipientDetails($field, $details[0][$contactID]);
6a488035
TO
690 }
691
692 // make $recipient actually be the *encoded* header, so as not to baffle Mail_RFC822, CRM-5743
693 $recipient = $headers['To'];
694 $result = NULL;
695
696 // disable error reporting on real mailings (but leave error reporting for tests), CRM-5744
697 if ($job_date) {
6a4257d4 698 $errorScope = CRM_Core_TemporaryErrorScope::ignoreException();
6a488035
TO
699 }
700
701 $result = $mailer->send($recipient, $headers, $body, $this->id);
702
703 if ($job_date) {
6a4257d4 704 unset($errorScope);
6a488035
TO
705 }
706
c5a6413b 707 if (is_a($result, 'PEAR_Error') && !$mailing->sms_provider_id) {
6a488035
TO
708 // CRM-9191
709 $message = $result->getMessage();
18810158
DL
710 if (
711 strpos($message, 'Failed to write to socket') !== FALSE ||
712 strpos($message, 'Failed to set sender') !== FALSE
713 ) {
6a488035
TO
714 // lets log this message and code
715 $code = $result->getCode();
18810158 716 CRM_Core_Error::debug_log_message("SMTP Socket Error or failed to set sender error. Message: $message, Code: $code");
6a488035
TO
717
718 // these are socket write errors which most likely means smtp connection errors
719 // lets skip them
720 $smtpConnectionErrors++;
721 if ($smtpConnectionErrors <= 5) {
722 continue;
723 }
724
6a488035
TO
725 // seems like we have too many of them in a row, we should
726 // write stuff to disk and abort the cron job
2ede60ec
DL
727 $this->writeToDB(
728 $deliveredParams,
6a488035
TO
729 $targetParams,
730 $mailing,
731 $job_date
732 );
733
734 CRM_Core_Error::debug_log_message("Too many SMTP Socket Errors. Exiting");
735 CRM_Utils_System::civiExit();
736 }
737
738 /* Register the bounce event */
739
740 $params = array(
741 'event_queue_id' => $field['id'],
742 'job_id' => $this->id,
743 'hash' => $field['hash'],
744 );
745 $params = array_merge($params,
746 CRM_Mailing_BAO_BouncePattern::match($result->getMessage())
747 );
748 CRM_Mailing_Event_BAO_Bounce::create($params);
749 }
39373d13
JM
750 elseif (is_a($result, 'PEAR_Error') && $mailing->sms_provider_id) {
751 // Handle SMS errors: CRM-15426
752 $job_id = intval($this->id);
753 $mailing_id = intval($mailing->id);
754 CRM_Core_Error::debug_log_message("Failed to send SMS message. Vars: mailing_id: ${mailing_id}, job_id: ${job_id}. Error message follows.");
755 CRM_Core_Error::debug_log_message($result->getMessage());
756 }
6a488035
TO
757 else {
758 /* Register the delivery event */
6a488035
TO
759 $deliveredParams[] = $field['id'];
760 $targetParams[] = $field['contact_id'];
761
762 $count++;
763 if ($count % CRM_Core_DAO::BULK_MAIL_INSERT_COUNT == 0) {
2ede60ec
DL
764 $this->writeToDB(
765 $deliveredParams,
6a488035
TO
766 $targetParams,
767 $mailing,
768 $job_date
769 );
770 $count = 0;
771
772 // hack to stop mailing job at run time, CRM-4246.
773 // to avoid making too many DB calls for this rare case
774 // lets do it when we snapshot
1a5727bd 775 $status = CRM_Core_DAO::getFieldValue(
cbe30f5a 776 'CRM_Mailing_DAO_MailingJob',
6a488035 777 $this->id,
1a5727bd
DL
778 'status',
779 'id',
780 TRUE
6a488035 781 );
1a5727bd 782
6a488035
TO
783 if ($status != 'Running') {
784 return FALSE;
785 }
786 }
787 }
788
789 unset($result);
790
791 // seems like a successful delivery or bounce, lets decrement error count
792 // only if we have smtp connection errors
793 if ($smtpConnectionErrors > 0) {
794 $smtpConnectionErrors--;
795 }
796
797 // If we have enabled the Throttle option, this is the time to enforce it.
798 if (isset($config->mailThrottleTime) && $config->mailThrottleTime > 0) {
799 usleep((int ) $config->mailThrottleTime);
800 }
801 }
802
2ede60ec
DL
803 $result = $this->writeToDB(
804 $deliveredParams,
6a488035
TO
805 $targetParams,
806 $mailing,
807 $job_date
808 );
809
810 return $result;
811 }
812
813 /**
100fef9d 814 * Cancel a mailing
6a488035 815 *
90c8230e
TO
816 * @param int $mailingId
817 * The id of the mailing to be canceled.
6a488035
TO
818 */
819 public static function cancel($mailingId) {
820 $sql = "
821SELECT *
822FROM civicrm_mailing_job
823WHERE mailing_id = %1
824AND is_test = 0
825AND ( ( job_type IS NULL ) OR
826 job_type <> 'child' )
827";
828 $params = array(1 => array($mailingId, 'Integer'));
829 $job = CRM_Core_DAO::executeQuery($sql, $params);
830 if ($job->fetch() &&
831 in_array($job->status, array('Scheduled', 'Running', 'Paused'))
832 ) {
833
353ffa53
TO
834 $newJob = new CRM_Mailing_BAO_MailingJob();
835 $newJob->id = $job->id;
6a488035 836 $newJob->end_date = date('YmdHis');
353ffa53 837 $newJob->status = 'Canceled';
6a488035
TO
838 $newJob->save();
839
840 // also cancel all child jobs
841 $sql = "
842UPDATE civicrm_mailing_job
843SET status = 'Canceled',
844 end_date = %2
845WHERE parent_id = %1
846AND is_test = 0
847AND job_type = 'child'
848AND status IN ( 'Scheduled', 'Running', 'Paused' )
849";
35f7561f 850 $params = array(
353ffa53 851 1 => array($job->id, 'Integer'),
6a488035
TO
852 2 => array(date('YmdHis'), 'Timestamp'),
853 );
854 CRM_Core_DAO::executeQuery($sql, $params);
855
856 CRM_Core_Session::setStatus(ts('The mailing has been canceled.'), ts('Canceled'), 'success');
857 }
858 }
859
860 /**
861 * Return a translated status enum string
862 *
90c8230e
TO
863 * @param string $status
864 * The status enum.
6a488035 865 *
a6c01b45
CW
866 * @return string
867 * The translated version
6a488035
TO
868 */
869 public static function status($status) {
870 static $translation = NULL;
871
872 if (empty($translation)) {
873 $translation = array(
874 'Scheduled' => ts('Scheduled'),
875 'Running' => ts('Running'),
876 'Complete' => ts('Complete'),
877 'Paused' => ts('Paused'),
878 'Canceled' => ts('Canceled'),
879 );
880 }
881 return CRM_Utils_Array::value($status, $translation, ts('Not scheduled'));
882 }
883
884 /**
885 * Return a workflow clause for use in SQL queries,
886 * to only process jobs that are approved.
887 *
a6c01b45
CW
888 * @return string
889 * For use in a WHERE clause
6a488035
TO
890 */
891 public static function workflowClause() {
892 // add an additional check and only process
893 // jobs that are approved
894 if (CRM_Mailing_Info::workflowEnabled()) {
895 $approveOptionID = CRM_Core_OptionGroup::getValue('mail_approval_status',
896 'Approved',
897 'name'
898 );
899 if ($approveOptionID) {
900 return " AND m.approval_status_id = $approveOptionID ";
901 }
902 }
903 return '';
904 }
905
e0ef6999 906 /**
100fef9d
CW
907 * @param array $deliveredParams
908 * @param array $targetParams
e0ef6999
EM
909 * @param $mailing
910 * @param $job_date
911 *
912 * @return bool
913 * @throws CRM_Core_Exception
914 * @throws Exception
915 */
2ede60ec
DL
916 public function writeToDB(
917 &$deliveredParams,
6a488035
TO
918 &$targetParams,
919 &$mailing,
920 $job_date
921 ) {
922 static $activityTypeID = NULL;
2ede60ec 923 static $writeActivity = NULL;
6a488035
TO
924
925 if (!empty($deliveredParams)) {
926 CRM_Mailing_Event_BAO_Delivered::bulkCreate($deliveredParams);
927 $deliveredParams = array();
928 }
929
2ede60ec
DL
930 if ($writeActivity === NULL) {
931 $writeActivity = CRM_Core_BAO_Setting::getItem(
932 CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME,
933 'write_activity_record',
934 NULL,
935 TRUE
936 );
937 }
6a488035 938
2ede60ec
DL
939 if (!$writeActivity) {
940 return TRUE;
941 }
942
943 $result = TRUE;
944 if (!empty($targetParams) && !empty($mailing->scheduled_id)) {
6a488035 945 if (!$activityTypeID) {
6a488035
TO
946 if ($mailing->sms_provider_id) {
947 $mailing->subject = $mailing->name;
2ede60ec
DL
948 $activityTypeID = CRM_Core_OptionGroup::getValue(
949 'activity_type',
6a488035
TO
950 'Mass SMS',
951 'name'
952 );
953 }
2ede60ec
DL
954 else {
955 $activityTypeID = CRM_Core_OptionGroup::getValue(
956 'activity_type',
957 'Bulk Email',
958 'name'
959 );
960 }
6a488035
TO
961 if (!$activityTypeID) {
962 CRM_Core_Error::fatal();
963 }
964 }
965
6a488035
TO
966 $activity = array(
967 'source_contact_id' => $mailing->scheduled_id,
968 // CRM-9519
969 'target_contact_id' => array_unique($targetParams),
6a488035
TO
970 'activity_type_id' => $activityTypeID,
971 'source_record_id' => $this->mailing_id,
972 'activity_date_time' => $job_date,
973 'subject' => $mailing->subject,
974 'status_id' => 2,
975 'deleteActivityTarget' => FALSE,
976 'campaign_id' => $mailing->campaign_id,
977 );
978
979 //check whether activity is already created for this mailing.
980 //if yes then create only target contact record.
981 $query = "
982SELECT id
983FROM civicrm_activity
984WHERE civicrm_activity.activity_type_id = %1
2ede60ec
DL
985AND civicrm_activity.source_record_id = %2
986";
6a488035 987
2ede60ec
DL
988 $queryParams = array(
989 1 => array($activityTypeID, 'Integer'),
6a488035
TO
990 2 => array($this->mailing_id, 'Integer'),
991 );
2ede60ec 992 $activityID = CRM_Core_DAO::singleValueQuery($query, $queryParams);
6a488035
TO
993
994 if ($activityID) {
995 $activity['id'] = $activityID;
996
997 // CRM-9519
998 if (CRM_Core_BAO_Email::isMultipleBulkMail()) {
b561230a
DL
999 static $targetRecordID = NULL;
1000 if (!$targetRecordID) {
1001 $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name');
1002 $targetRecordID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
1003 }
1004
6a488035
TO
1005 // make sure we don't attempt to duplicate the target activity
1006 foreach ($activity['target_contact_id'] as $key => $targetID) {
b561230a
DL
1007 $sql = "
1008SELECT id
1009FROM civicrm_activity_contact
1010WHERE activity_id = $activityID
1011AND contact_id = $targetID
1012AND record_type_id = $targetRecordID
1013";
6a488035
TO
1014 if (CRM_Core_DAO::singleValueQuery($sql)) {
1015 unset($activity['target_contact_id'][$key]);
1016 }
1017 }
1018 }
1019 }
1020
2ede60ec 1021 if (is_a(CRM_Activity_BAO_Activity::create($activity), 'CRM_Core_Error')) {
6a488035
TO
1022 $result = FALSE;
1023 }
1024
1025 $targetParams = array();
1026 }
1027
1028 return $result;
1029 }
96025800 1030
6a488035 1031}