1
|
|
|
<?php |
2
|
|
|
use \EventEspresso\core\exceptions\SendMessageException; |
3
|
|
|
|
4
|
|
|
if (! defined('EVENT_ESPRESSO_VERSION')) { |
5
|
|
|
exit('No direct script access allowed'); |
6
|
|
|
} |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* This class is used for managing and interacting with the EE_messages Queue. An instance |
10
|
|
|
* of this object is used for interacting with a specific batch of EE_Message objects. |
11
|
|
|
* |
12
|
|
|
* @package Event Espresso |
13
|
|
|
* @subpackage messages |
14
|
|
|
* @author Darren Ethier |
15
|
|
|
* @since 4.9.0 |
16
|
|
|
*/ |
17
|
|
|
class EE_Messages_Queue |
18
|
|
|
{ |
19
|
|
|
|
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* @type string reference for sending action |
23
|
|
|
*/ |
24
|
|
|
const action_sending = 'sending'; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @type string reference for generation action |
28
|
|
|
*/ |
29
|
|
|
const action_generating = 'generation'; |
30
|
|
|
|
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @type EE_Message_Repository $_message_repository |
34
|
|
|
*/ |
35
|
|
|
protected $_message_repository; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Sets the limit of how many messages are generated per process. |
39
|
|
|
* |
40
|
|
|
* @type int |
41
|
|
|
*/ |
42
|
|
|
protected $_batch_count; |
43
|
|
|
|
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* This is an array of cached queue items being stored in this object. |
47
|
|
|
* The array keys will be the ID of the EE_Message in the db if saved. If the EE_Message |
48
|
|
|
* is not saved to the db then its key will be an increment of "UNS" (i.e. UNS1, UNS2 etc.) |
49
|
|
|
* |
50
|
|
|
* @type EE_Message[] |
51
|
|
|
*/ |
52
|
|
|
protected $_cached_queue_items; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Tracks the number of unsaved queue items. |
56
|
|
|
* |
57
|
|
|
* @type int |
58
|
|
|
*/ |
59
|
|
|
protected $_unsaved_count = 0; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* used to record if a do_messenger_hooks has already been called for a message type. This prevents multiple |
63
|
|
|
* hooks getting fired if users have setup their action/filter hooks to prevent duplicate calls. |
64
|
|
|
* |
65
|
|
|
* @type array |
66
|
|
|
*/ |
67
|
|
|
protected $_did_hook = array(); |
68
|
|
|
|
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Constructor. |
72
|
|
|
* Setup all the initial properties and load a EE_Message_Repository. |
73
|
|
|
* |
74
|
|
|
* @param \EE_Message_Repository $message_repository |
75
|
|
|
*/ |
76
|
|
|
public function __construct(EE_Message_Repository $message_repository) |
77
|
|
|
{ |
78
|
|
|
$this->_batch_count = apply_filters('FHEE__EE_Messages_Queue___batch_count', 50); |
79
|
|
|
$this->_message_repository = $message_repository; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Add a EE_Message object to the queue |
85
|
|
|
* |
86
|
|
|
* @param EE_Message $message |
87
|
|
|
* @param array $data This will be an array of data to attach to the object in the repository. If the |
88
|
|
|
* object is persisted, this data will be saved on an extra_meta object related to |
89
|
|
|
* EE_Message. |
90
|
|
|
* @param bool $preview Whether this EE_Message represents a preview or not. |
91
|
|
|
* @param bool $test_send This indicates whether to do a test send instead of actual send. A test send will |
92
|
|
|
* use the messenger send method but typically is based on preview data. |
93
|
|
|
* @return bool Whether the message was successfully added to the repository or not. |
94
|
|
|
*/ |
95
|
|
|
public function add(EE_Message $message, $data = array(), $preview = false, $test_send = false) |
96
|
|
|
{ |
97
|
|
|
$data['preview'] = $preview; |
98
|
|
|
$data['test_send'] = $test_send; |
99
|
|
|
return $this->_message_repository->add($message, $data); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Removes EE_Message from _queue that matches the given EE_Message if the pointer is on a matching EE_Message |
105
|
|
|
* |
106
|
|
|
* @param EE_Message $message The message to detach from the queue |
107
|
|
|
* @param bool $persist This flag indicates whether to attempt to delete the object from the db as well. |
108
|
|
|
* @return bool |
109
|
|
|
*/ |
110
|
|
|
public function remove(EE_Message $message, $persist = false) |
111
|
|
|
{ |
112
|
|
|
if ($persist && $this->_message_repository->current() !== $message) { |
113
|
|
|
//get pointer on right message |
114
|
|
|
if ($this->_message_repository->has($message)) { |
115
|
|
|
$this->_message_repository->rewind(); |
116
|
|
|
while ($this->_message_repository->valid()) { |
117
|
|
|
if ($this->_message_repository->current() === $message) { |
118
|
|
|
break; |
119
|
|
|
} |
120
|
|
|
$this->_message_repository->next(); |
121
|
|
|
} |
122
|
|
|
} else { |
123
|
|
|
return false; |
124
|
|
|
} |
125
|
|
|
} |
126
|
|
|
return $persist ? $this->_message_repository->delete() : $this->_message_repository->remove($message); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Persists all queued EE_Message objects to the db. |
132
|
|
|
* |
133
|
|
|
* @param bool $do_hooks_only @see EE_Message_Repository::saveAll |
134
|
|
|
* @return array @see EE_Messages_Repository::saveAll() for return values. |
135
|
|
|
*/ |
136
|
|
|
public function save($do_hooks_only = false) |
137
|
|
|
{ |
138
|
|
|
return $this->_message_repository->saveAll($do_hooks_only); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* @return EE_Message_Repository |
144
|
|
|
*/ |
145
|
|
|
public function get_message_repository() |
146
|
|
|
{ |
147
|
|
|
return $this->_message_repository; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* This does the following things: |
153
|
|
|
* 1. Checks if there is a lock on generation (prevents race conditions). If there is a lock then exits (return |
154
|
|
|
* false). |
155
|
|
|
* 2. If no lock, sets lock, then retrieves a batch of non-generated EE_Message objects and adds to queue |
156
|
|
|
* 3. Returns bool. True = batch ready. False = no batch ready (or nothing available for generation). |
157
|
|
|
* Note: Callers should make sure they release the lock otherwise batch generation will be prevented from |
158
|
|
|
* continuing. The lock is on a transient that is set to expire after one hour as a fallback in case locks are not |
159
|
|
|
* removed. |
160
|
|
|
* |
161
|
|
|
* @return bool true if successfully retrieved batch, false no batch ready. |
162
|
|
|
*/ |
163
|
|
|
public function get_batch_to_generate() |
164
|
|
|
{ |
165
|
|
|
if ($this->is_locked(EE_Messages_Queue::action_generating)) { |
166
|
|
|
return false; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
//lock batch generation to prevent race conditions. |
170
|
|
|
$this->lock_queue(EE_Messages_Queue::action_generating); |
171
|
|
|
|
172
|
|
|
$query_args = array( |
173
|
|
|
// key 0 = where conditions |
174
|
|
|
0 => array('STS_ID' => EEM_Message::status_incomplete), |
175
|
|
|
'order_by' => $this->_get_priority_orderby(), |
176
|
|
|
'limit' => $this->_batch_count, |
177
|
|
|
); |
178
|
|
|
$messages = EEM_Message::instance()->get_all($query_args); |
179
|
|
|
|
180
|
|
|
if ( ! $messages) { |
|
|
|
|
181
|
|
|
return false; //nothing to generate |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
foreach ($messages as $message) { |
185
|
|
|
if ($message instanceof EE_Message) { |
186
|
|
|
$data = $message->all_extra_meta_array(); |
187
|
|
|
$this->add($message, $data); |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
return true; |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* This does the following things: |
196
|
|
|
* 1. Checks if there is a lock on sending (prevents race conditions). If there is a lock then exits (return |
197
|
|
|
* false). |
198
|
|
|
* 2. Grabs the allowed number of messages to send for the rate_limit. If cannot send any more messages, then |
199
|
|
|
* return false. |
200
|
|
|
* 2. If no lock, sets lock, then retrieves a batch of EE_Message objects, adds to queue and triggers execution. |
201
|
|
|
* 3. On success or unsuccessful send, sets status appropriately. |
202
|
|
|
* 4. Saves messages via the queue |
203
|
|
|
* 5. Releases lock. |
204
|
|
|
* |
205
|
|
|
* @return bool true on success, false if something preventing sending (i.e. lock set). Note: true does not |
206
|
|
|
* necessarily mean that all messages were successfully sent. It just means that this method |
207
|
|
|
* successfully completed. On true, client may want to call $this->count_STS_in_queue( |
208
|
|
|
* EEM_Message::status_failed ) to see if any failed EE_Message objects. Each failed message object |
209
|
|
|
* will also have a saved error message on it to assist with notifying user. |
210
|
|
|
*/ |
211
|
|
|
public function get_to_send_batch_and_send() |
212
|
|
|
{ |
213
|
|
|
$rate_limit = $this->get_rate_limit(); |
214
|
|
|
if ($rate_limit < 1 || $this->is_locked(EE_Messages_Queue::action_sending)) { |
215
|
|
|
return false; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
$this->lock_queue(EE_Messages_Queue::action_sending); |
219
|
|
|
|
220
|
|
|
$batch = $this->_batch_count < $rate_limit ? $this->_batch_count : $rate_limit; |
221
|
|
|
|
222
|
|
|
$query_args = array( |
223
|
|
|
// key 0 = where conditions |
224
|
|
|
0 => array('STS_ID' => array('IN', EEM_Message::instance()->stati_indicating_to_send())), |
225
|
|
|
'order_by' => $this->_get_priority_orderby(), |
226
|
|
|
'limit' => $batch, |
227
|
|
|
); |
228
|
|
|
|
229
|
|
|
$messages_to_send = EEM_Message::instance()->get_all($query_args); |
230
|
|
|
|
231
|
|
|
|
232
|
|
|
//any to send? |
233
|
|
|
if ( ! $messages_to_send) { |
|
|
|
|
234
|
|
|
$this->unlock_queue(EE_Messages_Queue::action_sending); |
235
|
|
|
return false; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
$queue_count = 0; |
239
|
|
|
|
240
|
|
|
//add to queue. |
241
|
|
|
foreach ($messages_to_send as $message) { |
242
|
|
|
if ($message instanceof EE_Message) { |
243
|
|
|
$queue_count++; |
244
|
|
|
$this->add($message); |
245
|
|
|
} |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
//send messages (this also updates the rate limit) |
249
|
|
|
$this->execute(); |
250
|
|
|
|
251
|
|
|
//release lock |
252
|
|
|
$this->unlock_queue(EE_Messages_Queue::action_sending); |
253
|
|
|
//update rate limit |
254
|
|
|
$this->set_rate_limit($queue_count); |
255
|
|
|
return true; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* Locks the queue so that no other queues can call the "batch" methods. |
261
|
|
|
* |
262
|
|
|
* @param string $type The type of queue being locked. |
263
|
|
|
*/ |
264
|
|
|
public function lock_queue($type = EE_Messages_Queue::action_generating) |
265
|
|
|
{ |
266
|
|
|
update_option($this->_get_lock_key($type), $this->_get_lock_expiry($type)); |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
|
270
|
|
|
/** |
271
|
|
|
* Unlocks the queue so that batch methods can be used. |
272
|
|
|
* |
273
|
|
|
* @param string $type The type of queue being unlocked. |
274
|
|
|
*/ |
275
|
|
|
public function unlock_queue($type = EE_Messages_Queue::action_generating) |
276
|
|
|
{ |
277
|
|
|
delete_option($this->_get_lock_key($type)); |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
|
281
|
|
|
/** |
282
|
|
|
* Retrieve the key used for the lock transient. |
283
|
|
|
* |
284
|
|
|
* @param string $type The type of lock. |
285
|
|
|
* @return string |
286
|
|
|
*/ |
287
|
|
|
protected function _get_lock_key($type = EE_Messages_Queue::action_generating) |
288
|
|
|
{ |
289
|
|
|
return '_ee_lock_' . $type; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
|
293
|
|
|
/** |
294
|
|
|
* Retrieve the expiry time for the lock transient. |
295
|
|
|
* |
296
|
|
|
* @param string $type The type of lock |
297
|
|
|
* @return int time to expiry in seconds. |
298
|
|
|
*/ |
299
|
|
|
protected function _get_lock_expiry($type = EE_Messages_Queue::action_generating) |
300
|
|
|
{ |
301
|
|
|
return time() + (int) apply_filters('FHEE__EE_Messages_Queue__lock_expiry', HOUR_IN_SECONDS, $type); |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
|
305
|
|
|
/** |
306
|
|
|
* Returns the key used for rate limit transient. |
307
|
|
|
* |
308
|
|
|
* @return string |
309
|
|
|
*/ |
310
|
|
|
protected function _get_rate_limit_key() |
311
|
|
|
{ |
312
|
|
|
return '_ee_rate_limit'; |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* Returns the rate limit expiry time. |
318
|
|
|
* |
319
|
|
|
* @return int |
320
|
|
|
*/ |
321
|
|
|
protected function _get_rate_limit_expiry() |
322
|
|
|
{ |
323
|
|
|
return time() + (int) apply_filters('FHEE__EE_Messages_Queue__rate_limit_expiry', HOUR_IN_SECONDS); |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
|
327
|
|
|
/** |
328
|
|
|
* Returns the default rate limit for sending messages. |
329
|
|
|
* |
330
|
|
|
* @return int |
331
|
|
|
*/ |
332
|
|
|
protected function _default_rate_limit() |
333
|
|
|
{ |
334
|
|
|
return (int) apply_filters('FHEE__EE_Messages_Queue___rate_limit', 200); |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
|
338
|
|
|
/** |
339
|
|
|
* Return the orderby array for priority. |
340
|
|
|
* |
341
|
|
|
* @return array |
342
|
|
|
*/ |
343
|
|
|
protected function _get_priority_orderby() |
344
|
|
|
{ |
345
|
|
|
return array( |
346
|
|
|
'MSG_priority' => 'ASC', |
347
|
|
|
'MSG_modified' => 'DESC', |
348
|
|
|
); |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
|
352
|
|
|
/** |
353
|
|
|
* Returns whether batch methods are "locked" or not. |
354
|
|
|
* |
355
|
|
|
* @param string $type The type of lock being checked for. |
356
|
|
|
* @return bool |
357
|
|
|
*/ |
358
|
|
|
public function is_locked($type = EE_Messages_Queue::action_generating) |
359
|
|
|
{ |
360
|
|
|
$lock = (int) get_option($this->_get_lock_key($type), 0); |
361
|
|
|
/** |
362
|
|
|
* This filters the default is_locked behaviour. |
363
|
|
|
*/ |
364
|
|
|
$is_locked = filter_var( |
365
|
|
|
apply_filters( |
366
|
|
|
'FHEE__EE_Messages_Queue__is_locked', |
367
|
|
|
$lock > time(), |
368
|
|
|
$this |
369
|
|
|
), |
370
|
|
|
FILTER_VALIDATE_BOOLEAN |
371
|
|
|
); |
372
|
|
|
|
373
|
|
|
/** |
374
|
|
|
* @see usage of this filter in EE_Messages_Queue::initiate_request_by_priority() method. |
375
|
|
|
* Also implemented here because messages processed on the same request should not have any locks applied. |
376
|
|
|
*/ |
377
|
|
|
if ( |
378
|
|
|
apply_filters('FHEE__EE_Messages_Processor__initiate_request_by_priority__do_immediate_processing', false) |
379
|
|
|
|| EE_Registry::instance()->NET_CFG->core->do_messages_on_same_request |
380
|
|
|
) { |
381
|
|
|
$is_locked = false; |
382
|
|
|
} |
383
|
|
|
|
384
|
|
|
|
385
|
|
|
return $is_locked; |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
|
389
|
|
|
/** |
390
|
|
|
* Retrieves the rate limit that may be cached as a transient. |
391
|
|
|
* If the rate limit is not set, then this sets the default rate limit and expiry and returns it. |
392
|
|
|
* |
393
|
|
|
* @param bool $return_expiry If true then return the expiry time not the rate_limit. |
394
|
|
|
* @return int |
395
|
|
|
*/ |
396
|
|
|
protected function get_rate_limit($return_expiry = false) |
397
|
|
|
{ |
398
|
|
|
$stored_rate_info = get_option($this->_get_rate_limit_key(), array()); |
399
|
|
|
$rate_limit = isset($stored_rate_info[0]) |
400
|
|
|
? (int) $stored_rate_info[0] |
401
|
|
|
: 0; |
402
|
|
|
$expiry = isset($stored_rate_info[1]) |
403
|
|
|
? (int) $stored_rate_info[1] |
404
|
|
|
: 0; |
405
|
|
|
//set the default for tracking? |
406
|
|
|
if (empty($stored_rate_info) || time() > $expiry) { |
407
|
|
|
$expiry = $this->_get_rate_limit_expiry(); |
408
|
|
|
$rate_limit = $this->_default_rate_limit(); |
409
|
|
|
update_option($this->_get_rate_limit_key(), array($rate_limit, $expiry)); |
410
|
|
|
} |
411
|
|
|
return $return_expiry ? $expiry : $rate_limit; |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
|
415
|
|
|
/** |
416
|
|
|
* This updates existing rate limit with the new limit which is the old minus the batch. |
417
|
|
|
* |
418
|
|
|
* @param int $batch_completed This sets the new rate limit based on the given batch that was completed. |
419
|
|
|
*/ |
420
|
|
|
protected function set_rate_limit($batch_completed) |
421
|
|
|
{ |
422
|
|
|
//first get the most up to date rate limit (in case its expired and reset) |
423
|
|
|
$rate_limit = $this->get_rate_limit(); |
424
|
|
|
$expiry = $this->get_rate_limit(true); |
425
|
|
|
$new_limit = $rate_limit - $batch_completed; |
426
|
|
|
//updating the transient option directly to avoid resetting the expiry. |
427
|
|
|
|
428
|
|
|
update_option($this->_get_rate_limit_key(), array($new_limit, $expiry)); |
429
|
|
|
} |
430
|
|
|
|
431
|
|
|
|
432
|
|
|
/** |
433
|
|
|
* This method checks the queue for ANY EE_Message objects with a priority matching the given priority passed in. |
434
|
|
|
* If that exists, then we immediately initiate a non-blocking request to do the requested action type. |
435
|
|
|
* Note: Keep in mind that there is the possibility that the request will not execute if there is already another |
436
|
|
|
* request running on a queue for the given task. |
437
|
|
|
* |
438
|
|
|
* @param string $task This indicates what type of request is going to be initiated. |
439
|
|
|
* @param int $priority This indicates the priority that triggers initiating the request. |
440
|
|
|
*/ |
441
|
|
|
public function initiate_request_by_priority($task = 'generate', $priority = EEM_Message::priority_high) |
442
|
|
|
{ |
443
|
|
|
//determine what status is matched with the priority as part of the trigger conditions. |
444
|
|
|
$status = $task == 'generate' |
445
|
|
|
? EEM_Message::status_incomplete |
446
|
|
|
: EEM_Message::instance()->stati_indicating_to_send(); |
447
|
|
|
// always make sure we save because either this will get executed immediately on a separate request |
448
|
|
|
// or remains in the queue for the regularly scheduled queue batch. |
449
|
|
|
$this->save(); |
450
|
|
|
/** |
451
|
|
|
* This filter/option allows users to override processing of messages on separate requests and instead have everything |
452
|
|
|
* happen on the same request. If this is utilized remember: |
453
|
|
|
* - message priorities don't matter |
454
|
|
|
* - existing unprocessed messages in the queue will not get processed unless manually triggered. |
455
|
|
|
* - things will be perceived to take longer to happen for end users (i.e. registrations) because of the additional |
456
|
|
|
* processing happening on the same request. |
457
|
|
|
* - any race condition protection (locks) are removed because they don't apply when things are processed on |
458
|
|
|
* the same request. |
459
|
|
|
*/ |
460
|
|
|
if ( |
461
|
|
|
apply_filters('FHEE__EE_Messages_Processor__initiate_request_by_priority__do_immediate_processing', false) |
462
|
|
|
|| EE_Registry::instance()->NET_CFG->core->do_messages_on_same_request |
463
|
|
|
) { |
464
|
|
|
$messages_processor = EE_Registry::instance()->load_lib('Messages_Processor'); |
465
|
|
|
if ($messages_processor instanceof EE_Messages_Processor) { |
466
|
|
|
return $messages_processor->process_immediately_from_queue($this); |
467
|
|
|
} |
468
|
|
|
//if we get here then that means the messages processor couldn't be loaded so messages will just remain |
469
|
|
|
//queued for manual triggering by end user. |
470
|
|
|
} |
471
|
|
|
|
472
|
|
|
if ($this->_message_repository->count_by_priority_and_status($priority, $status)) { |
473
|
|
|
EE_Messages_Scheduler::initiate_scheduled_non_blocking_request($task); |
474
|
|
|
} |
475
|
|
|
} |
476
|
|
|
|
477
|
|
|
|
478
|
|
|
/** |
479
|
|
|
* Loops through the EE_Message objects in the _queue and calls the messenger send methods for each message. |
480
|
|
|
* |
481
|
|
|
* @param bool $save Used to indicate whether to save the message queue after sending |
482
|
|
|
* (default will save). |
483
|
|
|
* @param mixed $sending_messenger (optional) When the sending messenger is different than |
484
|
|
|
* what is on the EE_Message object in the queue. |
485
|
|
|
* For instance, showing the browser view of an email message, |
486
|
|
|
* or giving a pdf generated view of an html document. |
487
|
|
|
* This should be an instance of EE_messenger but if you call this |
488
|
|
|
* method |
489
|
|
|
* intending it to be a sending messenger but a valid one could not be |
490
|
|
|
* retrieved then send in an instance of EE_Error that contains the |
491
|
|
|
* related error message. |
492
|
|
|
* @param bool|int $by_priority When set, this indicates that only messages |
493
|
|
|
* matching the given priority should be executed. |
494
|
|
|
* @return int Number of messages sent. Note, 0 does not mean that no messages were processed. |
495
|
|
|
* Also, if the messenger is an request type messenger (or a preview), |
496
|
|
|
* its entirely possible that the messenger will exit before |
497
|
|
|
*/ |
498
|
|
|
public function execute($save = true, $sending_messenger = null, $by_priority = false) |
499
|
|
|
{ |
500
|
|
|
$messages_sent = 0; |
501
|
|
|
$this->_did_hook = array(); |
502
|
|
|
$this->_message_repository->rewind(); |
503
|
|
|
|
504
|
|
|
while ($this->_message_repository->valid()) { |
505
|
|
|
$error_messages = array(); |
506
|
|
|
/** @type EE_Message $message */ |
507
|
|
|
$message = $this->_message_repository->current(); |
508
|
|
|
//only process things that are queued for sending |
509
|
|
|
if (! in_array($message->STS_ID(), EEM_Message::instance()->stati_indicating_to_send())) { |
510
|
|
|
$this->_message_repository->next(); |
511
|
|
|
continue; |
512
|
|
|
} |
513
|
|
|
//if $by_priority is set and does not match then continue; |
514
|
|
|
if ($by_priority && $by_priority != $message->priority()) { |
515
|
|
|
$this->_message_repository->next(); |
516
|
|
|
continue; |
517
|
|
|
} |
518
|
|
|
//error checking |
519
|
|
|
if (! $message->valid_messenger()) { |
520
|
|
|
$error_messages[] = sprintf( |
521
|
|
|
__('The %s messenger is not active at time of sending.', 'event_espresso'), |
522
|
|
|
$message->messenger() |
523
|
|
|
); |
524
|
|
|
} |
525
|
|
|
if (! $message->valid_message_type()) { |
526
|
|
|
$error_messages[] = sprintf( |
527
|
|
|
__('The %s message type is not active at the time of sending.', 'event_espresso'), |
528
|
|
|
$message->message_type() |
529
|
|
|
); |
530
|
|
|
} |
531
|
|
|
// if there was supposed to be a sending messenger for this message, but it was invalid/inactive, |
532
|
|
|
// then it will instead be an EE_Error object, so let's check for that |
533
|
|
|
if ($sending_messenger instanceof EE_Error) { |
534
|
|
|
$error_messages[] = $sending_messenger->getMessage(); |
535
|
|
|
} |
536
|
|
|
// if there are no errors, then let's process the message |
537
|
|
|
if (empty($error_messages)) { |
538
|
|
|
if ($save) { |
539
|
|
|
$message->set_messenger_is_executing(); |
540
|
|
|
} |
541
|
|
|
if ($this->_process_message($message, $sending_messenger)) { |
542
|
|
|
$messages_sent++; |
543
|
|
|
} |
544
|
|
|
} |
545
|
|
|
$this->_set_error_message($message, $error_messages); |
546
|
|
|
//add modified time |
547
|
|
|
$message->set_modified(time()); |
548
|
|
|
//we save each message after its processed to make sure its status persists in case PHP times-out or runs |
549
|
|
|
//out of memory. @see https://events.codebasehq.com/projects/event-espresso/tickets/10281 |
550
|
|
|
if ($save) { |
551
|
|
|
$message->save(); |
552
|
|
|
} |
553
|
|
|
|
554
|
|
|
$this->_message_repository->next(); |
555
|
|
|
} |
556
|
|
|
if ($save) { |
557
|
|
|
$this->save(true); |
558
|
|
|
} |
559
|
|
|
return $messages_sent; |
560
|
|
|
} |
561
|
|
|
|
562
|
|
|
|
563
|
|
|
/** |
564
|
|
|
* _process_message |
565
|
|
|
* |
566
|
|
|
* @param EE_Message $message |
567
|
|
|
* @param mixed $sending_messenger (optional) |
568
|
|
|
* @return bool |
569
|
|
|
*/ |
570
|
|
|
protected function _process_message(EE_Message $message, $sending_messenger = null) |
571
|
|
|
{ |
572
|
|
|
// these *should* have been validated in the execute() method above |
573
|
|
|
$messenger = $message->messenger_object(); |
574
|
|
|
$message_type = $message->message_type_object(); |
575
|
|
|
//do actions for sending messenger if it differs from generating messenger and swap values. |
576
|
|
|
if ( |
577
|
|
|
$sending_messenger instanceof EE_messenger |
578
|
|
|
&& $messenger instanceof EE_messenger |
579
|
|
|
&& $sending_messenger->name != $messenger->name |
580
|
|
|
) { |
581
|
|
|
$messenger->do_secondary_messenger_hooks($sending_messenger->name); |
582
|
|
|
$messenger = $sending_messenger; |
583
|
|
|
} |
584
|
|
|
// send using messenger, but double check objects |
585
|
|
|
if ($messenger instanceof EE_messenger && $message_type instanceof EE_message_type) { |
586
|
|
|
//set hook for message type (but only if not using another messenger to send). |
587
|
|
|
if ( ! isset($this->_did_hook[$message_type->name])) { |
588
|
|
|
$message_type->do_messenger_hooks($messenger); |
589
|
|
|
$this->_did_hook[$message_type->name] = 1; |
590
|
|
|
} |
591
|
|
|
//if preview then use preview method |
592
|
|
|
return $this->_message_repository->is_preview() |
593
|
|
|
? $this->_do_preview($message, $messenger, $message_type, $this->_message_repository->is_test_send()) |
594
|
|
|
: $this->_do_send($message, $messenger, $message_type); |
595
|
|
|
} |
596
|
|
|
return false; |
597
|
|
|
} |
598
|
|
|
|
599
|
|
|
|
600
|
|
|
/** |
601
|
|
|
* The intention of this method is to count how many EE_Message objects |
602
|
|
|
* are in the queue with a given status. |
603
|
|
|
* Example usage: |
604
|
|
|
* After a caller calls the "EE_Message_Queue::execute()" method, the caller can check if there were any failed |
605
|
|
|
* sends by calling $queue->count_STS_in_queue( EEM_Message_Queue::status_failed ). |
606
|
|
|
* |
607
|
|
|
* @param array|string $status Stati to check for in queue |
608
|
|
|
* @return int Count of EE_Message's matching the given status. |
609
|
|
|
*/ |
610
|
|
|
public function count_STS_in_queue($status) |
611
|
|
|
{ |
612
|
|
|
$count = 0; |
613
|
|
|
$status = is_array($status) ? $status : array($status); |
614
|
|
|
$this->_message_repository->rewind(); |
615
|
|
|
foreach ($this->_message_repository as $message) { |
616
|
|
|
if (in_array($message->STS_ID(), $status)) { |
617
|
|
|
$count++; |
618
|
|
|
} |
619
|
|
|
} |
620
|
|
|
return $count; |
621
|
|
|
} |
622
|
|
|
|
623
|
|
|
|
624
|
|
|
/** |
625
|
|
|
* Executes the get_preview method on the provided messenger. |
626
|
|
|
* |
627
|
|
|
* @param EE_Message $message |
628
|
|
|
* @param EE_messenger $messenger |
629
|
|
|
* @param EE_message_type $message_type |
630
|
|
|
* @param $test_send |
631
|
|
|
* @return bool true means all went well, false means, not so much. |
632
|
|
|
*/ |
633
|
|
|
protected function _do_preview( |
634
|
|
|
EE_Message $message, |
635
|
|
|
EE_messenger $messenger, |
636
|
|
|
EE_message_type $message_type, |
637
|
|
|
$test_send |
638
|
|
|
) { |
639
|
|
|
if ($preview = $messenger->get_preview($message, $message_type, $test_send)) { |
640
|
|
|
if ( ! $test_send) { |
641
|
|
|
$message->set_content($preview); |
642
|
|
|
} |
643
|
|
|
$message->set_STS_ID(EEM_Message::status_sent); |
644
|
|
|
return true; |
645
|
|
|
} else { |
646
|
|
|
$message->set_STS_ID(EEM_Message::status_failed); |
647
|
|
|
return false; |
648
|
|
|
} |
649
|
|
|
} |
650
|
|
|
|
651
|
|
|
|
652
|
|
|
/** |
653
|
|
|
* Executes the send method on the provided messenger |
654
|
|
|
* EE_Messengers are expected to: |
655
|
|
|
* - return true if the send was successful. |
656
|
|
|
* - return false if the send was unsuccessful but can be tried again. |
657
|
|
|
* - throw an Exception if the send was unsuccessful and cannot be tried again. |
658
|
|
|
* |
659
|
|
|
* @param EE_Message $message |
660
|
|
|
* @param EE_messenger $messenger |
661
|
|
|
* @param EE_message_type $message_type |
662
|
|
|
* @return bool true means all went well, false means, not so much. |
663
|
|
|
*/ |
664
|
|
|
protected function _do_send(EE_Message $message, EE_messenger $messenger, EE_message_type $message_type) |
665
|
|
|
{ |
666
|
|
|
try { |
667
|
|
|
if ($messenger->send_message($message, $message_type)) { |
668
|
|
|
$message->set_STS_ID(EEM_Message::status_sent); |
669
|
|
|
return true; |
670
|
|
|
} else { |
671
|
|
|
$message->set_STS_ID(EEM_Message::status_retry); |
672
|
|
|
return false; |
673
|
|
|
} |
674
|
|
|
} catch (SendMessageException $e) { |
675
|
|
|
$message->set_STS_ID(EEM_Message::status_failed); |
676
|
|
|
$message->set_error_message($e->getMessage()); |
677
|
|
|
return false; |
678
|
|
|
} |
679
|
|
|
} |
680
|
|
|
|
681
|
|
|
|
682
|
|
|
/** |
683
|
|
|
* This sets any necessary error messages on the message object and its status to failed. |
684
|
|
|
* |
685
|
|
|
* @param EE_Message $message |
686
|
|
|
* @param array $error_messages the response from the messenger. |
687
|
|
|
*/ |
688
|
|
|
protected function _set_error_message(EE_Message $message, $error_messages) |
689
|
|
|
{ |
690
|
|
|
$error_messages = (array)$error_messages; |
691
|
|
|
if (in_array($message->STS_ID(), EEM_Message::instance()->stati_indicating_failed_sending())) { |
692
|
|
|
$notices = EE_Error::has_notices(); |
693
|
|
|
$error_messages[] = __( |
694
|
|
|
'Messenger and Message Type were valid and active, but the messenger send method failed.', |
695
|
|
|
'event_espresso' |
696
|
|
|
); |
697
|
|
|
if ($notices === 1) { |
698
|
|
|
$notices = EE_Error::get_vanilla_notices(); |
699
|
|
|
$notices['errors'] = isset($notices['errors']) ? $notices['errors'] : array(); |
700
|
|
|
$error_messages[] = implode("\n", $notices['errors']); |
701
|
|
|
} |
702
|
|
|
} |
703
|
|
|
if (count($error_messages) > 0) { |
704
|
|
|
$msg = __('Message was not executed successfully.', 'event_espresso'); |
705
|
|
|
$msg = $msg . "\n" . implode("\n", $error_messages); |
706
|
|
|
$message->set_error_message($msg); |
707
|
|
|
} |
708
|
|
|
} |
709
|
|
|
|
710
|
|
|
} //end EE_Messages_Queue class |
711
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.