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