-
Notifications
You must be signed in to change notification settings - Fork 382
/
CronBasedBackgroundTask.php
204 lines (175 loc) · 5.55 KB
/
CronBasedBackgroundTask.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
<?php
/**
* Abstract class CronBasedBackgroundTask.
*
* @package AmpProject\AmpWP
*/
namespace AmpProject\AmpWP\BackgroundTask;
use AmpProject\AmpWP\Exception\FailedToRegisterBackgroundTask;
use AmpProject\AmpWP\Exception\InvalidInterval;
/**
* Abstract base class for using cron to execute a background task.
*
* @package AmpProject\AmpWP
*/
abstract class CronBasedBackgroundTask {
const DEFAULT_INTERVAL_HOURLY = 'hourly';
const DEFAULT_INTERVAL_TWICE_DAILY = 'twicedaily';
const DEFAULT_INTERVAL_DAILY = 'daily';
/**
* List of default interval names that are shipped with WordPress.
*
* @var string[]
*/
const DEFAULT_INTERVAL_NAMES = [
self::DEFAULT_INTERVAL_HOURLY,
self::DEFAULT_INTERVAL_TWICE_DAILY,
self::DEFAULT_INTERVAL_DAILY,
];
/**
* Register the background task with the system.
*
* @return void
* @throws FailedToRegisterBackgroundTask If the background task could not be registered with WordPress.
*/
public function register() {
$interval = $this->validate_interval( $this->get_interval() );
$interval_name = $this->maybe_register_interval( $interval );
$this->register_hook();
$timestamp = $this->schedule_event( $interval_name );
if ( ! $timestamp ) {
throw FailedToRegisterBackgroundTask::for_scheduled_event( $this->get_event_name() );
}
$this->register_deactivation( $timestamp );
}
/**
* Maybe register a custom interval if a custom duration was provided.
*
* @param string|int $interval Either an existing interval name, or a new interval duration in seconds.
* @return string Name of the interval to use.
*/
protected function maybe_register_interval( $interval ) {
if ( is_string( $interval ) ) {
// A string was already validated to be one of the default interval names.
return $interval;
}
$interval_name = $this->get_interval_name();
// The interval is a duration, so we add a new cron_schedule for this duration.
add_filter(
'cron_schedules', // phpcs:ignore WordPress.WP.CronInterval.ChangeDetected
function ( $schedules ) use ( $interval_name, $interval ) {
$schedules[ $interval_name ] = [
'interval' => $interval,
'display' => esc_attr( $this->get_interval_display_name() ),
];
return $schedules;
}
);
return $interval_name;
}
/**
* Register the hook that triggers the processing.
*/
protected function register_hook() {
add_action( $this->get_event_name(), [ $this, 'process' ] );
}
/**
* Schedule the event.
*
* @param string $interval_name Name of the interval to use for scheduling.
* @return string|false Timestamp of the next recurrence or false if failed.
*/
protected function schedule_event( $interval_name ) {
$event_name = $this->get_event_name();
$timestamp = wp_next_scheduled( $event_name );
if ( $timestamp ) {
return $timestamp;
}
if ( ! wp_schedule_event( time(), $interval_name, $event_name ) ) {
return false;
}
return wp_next_scheduled( $event_name );
}
/**
* Register the deactivation hook.
*
* @param string $timestamp Timestamp to register the deactivation hook for.
*/
protected function register_deactivation( $timestamp ) {
register_deactivation_hook(
dirname( dirname( __DIR__ ) ) . '/amp.php',
function () use ( $timestamp ) {
wp_unschedule_event( $timestamp, $this->get_event_name() );
}
);
}
/**
* Get the name for the event's interval.
*
* @return string Name of the interval.
*/
protected function get_interval_name() {
return "{$this->get_event_name()}_interval";
}
/**
* Get the display name for the event's interval.
*
* @return string Display name of the interval.
*/
protected function get_interval_display_name() {
return sprintf(
/* translators: %s => name of the registered event */
__( 'Interval for the "%s" event', 'amp' ),
$this->get_event_name()
);
}
/**
* Validate the given interval value.
*
* The $interval needs to be either a string matching one of the default intervals shipped with WordPress (i.e.
* 'hourly', 'twicedaily' or 'daily'), or a positive integer representing the duration in seconds of a new interval
* to be registered.
*
* @param mixed $interval Interval value to validate.
* @return string|int Validated interval.
* @throws InvalidInterval If the interval was neither an existing interval name nor a valid duration.
*/
protected function validate_interval( $interval ) {
if ( ! is_int( $interval ) && ! is_string( $interval ) ) {
throw InvalidInterval::for_invalid_type( $interval );
}
if ( is_int( $interval ) && $interval <= 0 ) {
throw InvalidInterval::for_invalid_duration( $interval );
}
if ( is_string( $interval ) && ! in_array( $interval, self::DEFAULT_INTERVAL_NAMES, true ) ) {
throw InvalidInterval::for_unknown_name( $interval );
}
return $interval;
}
/**
* Get the interval to use for the event.
*
* If the passed-in interval is a string that matches an existing interval, that interval is used as-is.
*
* If the passed-in interval is an integer, that value is used as a duration in seconds to create a new interval.
*
* @return string|int Either an existing interval name, or a new interval duration in seconds.
*/
abstract protected function get_interval();
/**
* Get the event name.
*
* This is the "slug" of the event, not the display name.
*
* Note: the event name should be prefixed to prevent naming collisions.
*
* @return string Name of the event.
*/
abstract protected function get_event_name();
/**
* Process a single cron tick.
*
* @return void
*/
abstract public function process();
}