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