[NFC] Update flexmailer to be PHP7.4 compatible
[civicrm-core.git] / ext / flexmailer / src / FlexMailer.php
CommitLineData
bdf67e28
SL
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11namespace Civi\FlexMailer;
12
13use Civi\FlexMailer\Event\ComposeBatchEvent;
14use Civi\FlexMailer\Event\RunEvent;
15use Civi\FlexMailer\Event\SendBatchEvent;
16use Civi\FlexMailer\Event\WalkBatchesEvent;
17use Symfony\Component\EventDispatcher\EventDispatcherInterface;
18
19/**
20 * Class FlexMailer
21 * @package Civi\FlexMailer
22 *
23 * The FlexMailer is a mail-blaster which supports batching and events.
24 * Specifically, there are five key events:
25 * - WalkBatchesEvent: Examine the recipient list and pull out a subset
26 * for whom you want to send email.
27 * - ComposeBatchEvent: Given a batch of recipients, prepare an email message
28 * for each.
29 * - SendBatchEvent: Given a batch of recipients and their messages, send
30 * the messages out.
31 * - RunEvent: Execute the main-loop (with all of the above steps).
32 *
33 * The events are based on Symfony's EventDispatcher. You may register event
34 * listeners using hook_civicrm_container, e.g.
35 *
36 * function mymod_civicrm_container(ContainerBuilder $container) {
37 * $container
38 * ->setDefinition('mymod_subscriber', new Definition('MymodSubscriber', array()))
39 * ->addTag('kernel.event_subscriber');
40 * }
41 *
42 * FlexMailer includes default listeners for all of these events. They
43 * behaves in basically the same way as CiviMail's traditional BAO-based
44 * delivery system (respecting mailerJobSize, mailThrottleTime,
45 * mailing_backend, hook_civicrm_alterMailParams, etal). However, you
46 * can replace any of the major functions, e.g.
47 *
48 * - If you send large blasts across multiple servers, then you may
49 * prefer a different algorithm for splitting the recipient list.
50 * Listen for WalkBatchesEvent.
51 * - If you want to compose messages in a new way (e.g. a different
52 * templating language), then listen for ComposeBatchEvent.
53 * - If you want to deliver messages through a different medium
54 * (such as web-services or batched SMTP), listen for SendBatchEvent.
55 *
56 * In all cases, your function can listen to the event and then decide what
57 * to do. If your listener does the work required for the event, then
58 * you can disable the default listener by calling `$event->stopPropagation()`.
59 *
60 * @link http://symfony.com/doc/current/components/event_dispatcher.html
61 */
62class FlexMailer {
63
64 const WEIGHT_START = 2000;
65 const WEIGHT_PREPARE = 1000;
66 const WEIGHT_MAIN = 0;
67 const WEIGHT_ALTER = -1000;
68 const WEIGHT_END = -2000;
69
70 const EVENT_RUN = 'civi.flexmailer.run';
71 const EVENT_WALK = 'civi.flexmailer.walk';
72 const EVENT_COMPOSE = 'civi.flexmailer.compose';
73 const EVENT_SEND = 'civi.flexmailer.send';
74
75 /**
76 * @return array
77 * Array(string $event => string $class).
78 */
79 public static function getEventTypes() {
80 return array(
81 self::EVENT_RUN => 'Civi\\FlexMailer\\Event\\RunEvent',
82 self::EVENT_WALK => 'Civi\\FlexMailer\\Event\\WalkBatchesEvent',
83 self::EVENT_COMPOSE => 'Civi\\FlexMailer\\Event\\ComposeBatchEvent',
84 self::EVENT_SEND => 'Civi\\FlexMailer\\Event\\SendBatchEvent',
85 );
86 }
87
88 /**
89 * @var array
90 * An array which must define options:
91 * - mailing: \CRM_Mailing_BAO_Mailing
92 * - job: \CRM_Mailing_BAO_MailingJob
93 * - attachments: array
94 * - is_preview: bool
95 *
96 * Additional options may be passed. To avoid naming conflicts, use prefixing.
97 */
98 public $context;
99
100 /**
101 * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
102 */
103 private $dispatcher;
104
105 /**
106 * Create a new FlexMailer instance, using data available in the CiviMail runJobs().
107 *
108 * @param \CRM_Mailing_BAO_MailingJob $job
109 * @param object $deprecatedMessageMailer
110 * @param array $deprecatedTestParams
111 * @return bool
112 * TRUE if delivery completed.
113 */
114 public static function createAndRun($job, $deprecatedMessageMailer, $deprecatedTestParams) {
115 $flexMailer = new \Civi\FlexMailer\FlexMailer(array(
116 'mailing' => \CRM_Mailing_BAO_Mailing::findById($job->mailing_id),
117 'job' => $job,
118 'attachments' => \CRM_Core_BAO_File::getEntityFile('civicrm_mailing', $job->mailing_id),
119 'deprecatedMessageMailer' => $deprecatedMessageMailer,
120 'deprecatedTestParams' => $deprecatedTestParams,
121 ));
122 return $flexMailer->run();
123 }
124
125 /**
126 * FlexMailer constructor.
127 * @param array $context
128 * An array which must define options:
129 * - mailing: \CRM_Mailing_BAO_Mailing
130 * - job: \CRM_Mailing_BAO_MailingJob
131 * - attachments: array
132 * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
133 */
134 public function __construct($context = array(), EventDispatcherInterface $dispatcher = NULL) {
135 $this->context = $context;
136 $this->dispatcher = $dispatcher ? $dispatcher : \Civi::service('dispatcher');
137 }
138
139 /**
140 * @return bool
141 * TRUE if delivery completed.
142 * @throws \CRM_Core_Exception
143 */
144 public function run() {
145 // PHP 5.3
146 $flexMailer = $this;
147
148 if (count($this->validate()) > 0) {
149 throw new \CRM_Core_Exception("FlexMailer cannot execute: invalid context");
150 }
151
152 $run = $this->fireRun();
153 if ($run->isPropagationStopped()) {
154 return $run->getCompleted();
155 }
156
157 $walkBatches = $this->fireWalkBatches(function ($tasks) use ($flexMailer) {
158 $flexMailer->fireComposeBatch($tasks);
159 $sendBatch = $flexMailer->fireSendBatch($tasks);
160 return $sendBatch->getCompleted();
161 });
162
163 return $walkBatches->getCompleted();
164 }
165
166 /**
167 * @return array
168 * List of error messages
169 */
170 public function validate() {
171 $errors = array();
172 if (empty($this->context['mailing'])) {
173 $errors['mailing'] = 'Missing \"mailing\"';
174 }
175 if (empty($this->context['job'])) {
176 $errors['job'] = 'Missing \"job\"';
177 }
178 return $errors;
179 }
180
181 /**
182 * @return \Civi\FlexMailer\Event\RunEvent
183 */
184 public function fireRun() {
185 $event = new RunEvent($this->context);
186 $this->dispatcher->dispatch(self::EVENT_RUN, $event);
187 return $event;
188 }
189
190 /**
191 * @param callable $onVisitBatch
192 * @return \Civi\FlexMailer\Event\WalkBatchesEvent
193 */
194 public function fireWalkBatches($onVisitBatch) {
195 $event = new WalkBatchesEvent($this->context, $onVisitBatch);
196 $this->dispatcher->dispatch(self::EVENT_WALK, $event);
197 return $event;
198 }
199
200 /**
201 * @param array<FlexMailerTask> $tasks
202 * @return \Civi\FlexMailer\Event\ComposeBatchEvent
203 */
204 public function fireComposeBatch($tasks) {
205 // This isn't a great place for this, but it ensures consistent cleanup.
206 $mailing = $this->context['mailing'];
207 if (property_exists($mailing, 'language') && $mailing->language && $mailing->language != 'en_US') {
208 $swapLang = \CRM_Utils_AutoClean::swap('call://i18n/getLocale', 'call://i18n/setLocale', $mailing->language);
209 }
210
211 $event = new ComposeBatchEvent($this->context, $tasks);
212 $this->dispatcher->dispatch(self::EVENT_COMPOSE, $event);
213 return $event;
214 }
215
216 /**
217 * @param array<FlexMailerTask> $tasks
218 * @return \Civi\FlexMailer\Event\SendBatchEvent
219 */
220 public function fireSendBatch($tasks) {
221 $event = new SendBatchEvent($this->context, $tasks);
222 $this->dispatcher->dispatch(self::EVENT_SEND, $event);
223 return $event;
224 }
225
226}