class-internal-events.php 5.57 KB
Newer Older
1
2
<?php

3
namespace Automattic\WP\Cron_Control;
4

5
class Internal_Events extends Singleton {
6
7
8
9
10
11
12
13
14
15
16
17
18
	/**
	 * PLUGIN SETUP
	 */

	/**
	 * Class properties
	 */
	private $internal_jobs           = array();
	private $internal_jobs_schedules = array();

	/**
	 * Register hooks
	 */
19
	protected function class_init() {
20
		// Internal jobs variables
21
22
		$this->internal_jobs = array(
			array(
23
24
				'schedule' => 'a8c_cron_control_minute',
				'action'   => 'a8c_cron_control_force_publish_missed_schedules',
25
				'callback' => 'force_publish_missed_schedules',
26
27
			),
			array(
28
29
				'schedule' => 'a8c_cron_control_ten_minutes',
				'action'   => 'a8c_cron_control_confirm_scheduled_posts',
30
				'callback' => 'confirm_scheduled_posts',
31
			),
32
33
			array(
				'schedule' => 'daily',
34
				'action'   => 'a8c_cron_control_delete_cron_option',
35
				'callback' => 'delete_cron_option',
36
			),
37
38
39
40
41
			array(
				'schedule' => 'hourly',
				'action'   => 'a8c_cron_control_purge_completed_events',
				'callback' => 'purge_completed_events',
			),
42
43
44
		);

		$this->internal_jobs_schedules = array(
45
			'a8c_cron_control_minute' => array(
46
				'interval' => 1 * MINUTE_IN_SECONDS,
47
				'display' => __( 'Cron Control internal job - every minute', 'automattic-cron-control' ),
48
			),
49
			'a8c_cron_control_ten_minutes' => array(
50
				'interval' => 10 * MINUTE_IN_SECONDS,
51
				'display' => __( 'Cron Control internal job - every 10 minutes', 'automattic-cron-control' ),
52
53
			),
		);
54
55

		// Register hooks
56
57
		add_action( 'admin_init', array( $this, 'schedule_internal_events' ) );
		add_action( 'rest_api_init', array( $this, 'schedule_internal_events' ) );
58
59
60
61
62
		add_filter( 'cron_schedules', array( $this, 'register_internal_events_schedules' ) );

		foreach ( $this->internal_jobs as $internal_job ) {
			add_action( $internal_job['action'], array( $this, $internal_job['callback'] ) );
		}
63
64
65
66
67
68
69
70
71
72
73
74
75
	}

	/**
	 * Include custom schedules used for internal jobs
	 */
	public function register_internal_events_schedules( $schedules ) {
		return array_merge( $schedules, $this->internal_jobs_schedules );
	}

	/**
	 * Schedule internal jobs
	 */
	public function schedule_internal_events() {
76
		$when = strtotime( sprintf( '+%d seconds', JOB_QUEUE_WINDOW_IN_SECONDS ) );
77

78
79
		$schedules = wp_get_schedules();

80
81
		foreach ( $this->internal_jobs as $job_args ) {
			if ( ! wp_next_scheduled( $job_args['action'] ) ) {
82
83
84
85
86
87
88
89
90
				$interval = array_key_exists( $job_args['schedule'], $schedules ) ? $schedules[ $job_args['schedule'] ]['interval'] : 0;

				$args = array(
					'schedule' => $job_args['schedule'],
					'args'     => array(),
					'interval' => $interval,
				);

				Cron_Options_CPT::instance()->create_or_update_job( $when, $job_args['action'], $args );
91
92
93
94
95
			}
		}
	}

	/**
96
	 * PLUGIN UTILITIES
97
98
99
100
101
102
103
104
105
	 */

	/**
	 * Events that are always run, regardless of how many jobs are queued
	 */
	public function is_internal_event( $action ) {
		return in_array( $action, wp_list_pluck( $this->internal_jobs, 'action' ) );
	}

106
107
108
109
	/**
	 * EVENT CALLBACKS
	 */

110
111
112
113
114
115
116
117
118
119
	/**
	 * Published scheduled posts that miss their schedule
	 */
	public function force_publish_missed_schedules() {
		global $wpdb;

		$missed_posts = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_status = 'future' AND post_date <= %s LIMIT 100;", current_time( 'mysql', false ) ) );

		if ( ! empty( $missed_posts ) ) {
			foreach ( $missed_posts as $missed_post ) {
120
121
122
				$missed_post = absint( $missed_post );
				wp_publish_post( $missed_post );
				wp_clear_scheduled_hook( 'publish_future_post', array( $missed_post ) );
123

124
				do_action( 'a8c_cron_control_published_post_that_missed_schedule', $missed_post );
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
			}
		}
	}

	/**
	 * Ensure scheduled posts have a corresponding cron job to publish them
	 */
	public function confirm_scheduled_posts() {
		global $wpdb;

		$future_posts = $wpdb->get_results( $wpdb->prepare( "SELECT ID, post_date FROM {$wpdb->posts} WHERE post_status = 'future' AND post_date > %s LIMIT 100;", current_time( 'mysql', false ) ) );

		if ( ! empty( $future_posts ) ) {
			foreach ( $future_posts as $future_post ) {
				$future_post->ID = absint( $future_post->ID );
				$gmt_time        = strtotime( get_gmt_from_date( $future_post->post_date ) . ' GMT' );
				$timestamp       = wp_next_scheduled( 'publish_future_post', array( $future_post->ID ) );

				if ( false === $timestamp ) {
					wp_schedule_single_event( $gmt_time, 'publish_future_post', array( $future_post->ID ) );

146
					do_action( 'a8c_cron_control_publish_scheduled', $future_post->ID );
147
				} elseif ( (int) $timestamp !== $gmt_time ) {
148
					wp_clear_scheduled_hook( 'publish_future_post', array( $future_post->ID ) );
149
150
					wp_schedule_single_event( $gmt_time, 'publish_future_post', array( $future_post->ID ) );

151
					do_action( 'a8c_cron_control_publish_rescheduled', $future_post->ID );
152
153
154
155
				}
			}
		}
	}
156
157
158
159
160
161
162

	/**
	 * In case the cron option lingers, purge it
	 */
	public function delete_cron_option() {
		delete_option( 'cron' );
	}
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

	/**
	 * Delete event objects for events that have run
	 *
	 * Given volume of events that can be created, waiting for `wp_scheduled_delete()`, which defaults to a trailing 30-day delete, is unwise
	 */
	public function purge_completed_events() {
		$trashed_posts = get_posts( array(
			'post_type'        => Cron_Options_CPT::POST_TYPE,
			'post_status'      => Cron_Options_CPT::POST_STATUS_COMPLETED,
			'posts_per_page'   => 100,
			'fields'           => 'ids',
		) );

		if ( is_array( $trashed_posts ) && ! empty( $trashed_posts ) ) {
			foreach ( $trashed_posts as $trashed_post_id ) {
				wp_delete_post( $trashed_post_id, true );

				do_action( 'a8c_cron_control_purged_completed_event', $trashed_post_id );
			}
		}
	}
185
186
187
}

Internal_Events::instance();