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