Passed
Pull Request — master (#281)
by Kiran
04:07
created
action-scheduler/classes/data-stores/ActionScheduler_wpCommentLogger.php 1 patch
Indentation   +232 added lines, -232 removed lines patch added patch discarded remove patch
@@ -4,237 +4,237 @@
 block discarded – undo
4 4
  * Class ActionScheduler_wpCommentLogger
5 5
  */
6 6
 class ActionScheduler_wpCommentLogger extends ActionScheduler_Logger {
7
-	const AGENT = 'ActionScheduler';
8
-	const TYPE = 'action_log';
9
-
10
-	/**
11
-	 * @param string $action_id
12
-	 * @param string $message
13
-	 * @param DateTime $date
14
-	 *
15
-	 * @return string The log entry ID
16
-	 */
17
-	public function log( $action_id, $message, DateTime $date = NULL ) {
18
-		if ( empty($date) ) {
19
-			$date = as_get_datetime_object();
20
-		} else {
21
-			$date = as_get_datetime_object( clone $date );
22
-		}
23
-		$comment_id = $this->create_wp_comment( $action_id, $message, $date );
24
-		return $comment_id;
25
-	}
26
-
27
-	protected function create_wp_comment( $action_id, $message, DateTime $date ) {
28
-
29
-		$comment_date_gmt = $date->format('Y-m-d H:i:s');
30
-		ActionScheduler_TimezoneHelper::set_local_timezone( $date );
31
-		$comment_data = array(
32
-			'comment_post_ID' => $action_id,
33
-			'comment_date' => $date->format('Y-m-d H:i:s'),
34
-			'comment_date_gmt' => $comment_date_gmt,
35
-			'comment_author' => self::AGENT,
36
-			'comment_content' => $message,
37
-			'comment_agent' => self::AGENT,
38
-			'comment_type' => self::TYPE,
39
-		);
40
-		return wp_insert_comment($comment_data);
41
-	}
42
-
43
-	/**
44
-	 * @param string $entry_id
45
-	 *
46
-	 * @return ActionScheduler_LogEntry
47
-	 */
48
-	public function get_entry( $entry_id ) {
49
-		$comment = $this->get_comment( $entry_id );
50
-		if ( empty($comment) || $comment->comment_type != self::TYPE ) {
51
-			return new ActionScheduler_NullLogEntry();
52
-		}
53
-
54
-		$date = as_get_datetime_object( $comment->comment_date_gmt );
55
-		ActionScheduler_TimezoneHelper::set_local_timezone( $date );
56
-		return new ActionScheduler_LogEntry( $comment->comment_post_ID, $comment->comment_content, $date );
57
-	}
58
-
59
-	/**
60
-	 * @param string $action_id
61
-	 *
62
-	 * @return ActionScheduler_LogEntry[]
63
-	 */
64
-	public function get_logs( $action_id ) {
65
-		$status = 'all';
66
-		if ( get_post_status($action_id) == 'trash' ) {
67
-			$status = 'post-trashed';
68
-		}
69
-		$comments = get_comments(array(
70
-			'post_id' => $action_id,
71
-			'orderby' => 'comment_date_gmt',
72
-			'order' => 'ASC',
73
-			'type' => self::TYPE,
74
-			'status' => $status,
75
-		));
76
-		$logs = array();
77
-		foreach ( $comments as $c ) {
78
-			$entry = $this->get_entry( $c );
79
-			if ( !empty($entry) ) {
80
-				$logs[] = $entry;
81
-			}
82
-		}
83
-		return $logs;
84
-	}
85
-
86
-	protected function get_comment( $comment_id ) {
87
-		return get_comment( $comment_id );
88
-	}
89
-
90
-
91
-
92
-	/**
93
-	 * @param WP_Comment_Query $query
94
-	 */
95
-	public function filter_comment_queries( $query ) {
96
-		foreach ( array('ID', 'parent', 'post_author', 'post_name', 'post_parent', 'type', 'post_type', 'post_id', 'post_ID') as $key ) {
97
-			if ( !empty($query->query_vars[$key]) ) {
98
-				return; // don't slow down queries that wouldn't include action_log comments anyway
99
-			}
100
-		}
101
-		$query->query_vars['action_log_filter'] = TRUE;
102
-		add_filter( 'comments_clauses', array( $this, 'filter_comment_query_clauses' ), 10, 2 );
103
-	}
104
-
105
-	/**
106
-	 * @param array $clauses
107
-	 * @param WP_Comment_Query $query
108
-	 *
109
-	 * @return array
110
-	 */
111
-	public function filter_comment_query_clauses( $clauses, $query ) {
112
-		if ( !empty($query->query_vars['action_log_filter']) ) {
113
-			$clauses['where'] .= $this->get_where_clause();
114
-		}
115
-		return $clauses;
116
-	}
117
-
118
-	/**
119
-	 * Make sure Action Scheduler logs are excluded from comment feeds, which use WP_Query, not
120
-	 * the WP_Comment_Query class handled by @see self::filter_comment_queries().
121
-	 *
122
-	 * @param string $where
123
-	 * @param WP_Query $query
124
-	 *
125
-	 * @return string
126
-	 */
127
-	public function filter_comment_feed( $where, $query ) {
128
-		if ( is_comment_feed() ) {
129
-			$where .= $this->get_where_clause();
130
-		}
131
-		return $where;
132
-	}
133
-
134
-	/**
135
-	 * Return a SQL clause to exclude Action Scheduler comments.
136
-	 *
137
-	 * @return string
138
-	 */
139
-	protected function get_where_clause() {
140
-		global $wpdb;
141
-		return sprintf( " AND {$wpdb->comments}.comment_type != '%s'", self::TYPE );
142
-	}
143
-
144
-	/**
145
-	 * Remove action log entries from wp_count_comments()
146
-	 *
147
-	 * @param array $stats
148
-	 * @param int $post_id
149
-	 *
150
-	 * @return object
151
-	 */
152
-	public function filter_comment_count( $stats, $post_id ) {
153
-		global $wpdb;
154
-
155
-		if ( 0 === $post_id ) {
156
-			$stats = $this->get_comment_count();
157
-		}
158
-
159
-		return $stats;
160
-	}
161
-
162
-	/**
163
-	 * Retrieve the comment counts from our cache, or the database if the cached version isn't set.
164
-	 *
165
-	 * @return object
166
-	 */
167
-	protected function get_comment_count() {
168
-		global $wpdb;
169
-
170
-		$stats = get_transient( 'as_comment_count' );
171
-
172
-		if ( ! $stats ) {
173
-			$stats = array();
174
-
175
-			$count = $wpdb->get_results( "SELECT comment_approved, COUNT( * ) AS num_comments FROM {$wpdb->comments} WHERE comment_type NOT IN('order_note','action_log') GROUP BY comment_approved", ARRAY_A );
176
-
177
-			$total = 0;
178
-			$stats = array();
179
-			$approved = array( '0' => 'moderated', '1' => 'approved', 'spam' => 'spam', 'trash' => 'trash', 'post-trashed' => 'post-trashed' );
180
-
181
-			foreach ( (array) $count as $row ) {
182
-				// Don't count post-trashed toward totals
183
-				if ( 'post-trashed' != $row['comment_approved'] && 'trash' != $row['comment_approved'] ) {
184
-					$total += $row['num_comments'];
185
-				}
186
-				if ( isset( $approved[ $row['comment_approved'] ] ) ) {
187
-					$stats[ $approved[ $row['comment_approved'] ] ] = $row['num_comments'];
188
-				}
189
-			}
190
-
191
-			$stats['total_comments'] = $total;
192
-			$stats['all']            = $total;
193
-
194
-			foreach ( $approved as $key ) {
195
-				if ( empty( $stats[ $key ] ) ) {
196
-					$stats[ $key ] = 0;
197
-				}
198
-			}
199
-
200
-			$stats = (object) $stats;
201
-			set_transient( 'as_comment_count', $stats );
202
-		}
203
-
204
-		return $stats;
205
-	}
206
-
207
-	/**
208
-	 * Delete comment count cache whenever there is new comment or the status of a comment changes. Cache
209
-	 * will be regenerated next time ActionScheduler_wpCommentLogger::filter_comment_count() is called.
210
-	 */
211
-	public function delete_comment_count_cache() {
212
-		delete_transient( 'as_comment_count' );
213
-	}
214
-
215
-	/**
216
-	 * @codeCoverageIgnore
217
-	 */
218
-	public function init() {
219
-		add_action( 'action_scheduler_before_process_queue', array( $this, 'disable_comment_counting' ), 10, 0 );
220
-		add_action( 'action_scheduler_after_process_queue', array( $this, 'enable_comment_counting' ), 10, 0 );
221
-
222
-		parent::init();
223
-
224
-		add_action( 'pre_get_comments', array( $this, 'filter_comment_queries' ), 10, 1 );
225
-		add_action( 'wp_count_comments', array( $this, 'filter_comment_count' ), 20, 2 ); // run after WC_Comments::wp_count_comments() to make sure we exclude order notes and action logs
226
-		add_action( 'comment_feed_where', array( $this, 'filter_comment_feed' ), 10, 2 );
227
-
228
-		// Delete comments count cache whenever there is a new comment or a comment status changes
229
-		add_action( 'wp_insert_comment', array( $this, 'delete_comment_count_cache' ) );
230
-		add_action( 'wp_set_comment_status', array( $this, 'delete_comment_count_cache' ) );
231
-	}
232
-
233
-	public function disable_comment_counting() {
234
-		wp_defer_comment_counting(true);
235
-	}
236
-	public function enable_comment_counting() {
237
-		wp_defer_comment_counting(false);
238
-	}
7
+    const AGENT = 'ActionScheduler';
8
+    const TYPE = 'action_log';
9
+
10
+    /**
11
+     * @param string $action_id
12
+     * @param string $message
13
+     * @param DateTime $date
14
+     *
15
+     * @return string The log entry ID
16
+     */
17
+    public function log( $action_id, $message, DateTime $date = NULL ) {
18
+        if ( empty($date) ) {
19
+            $date = as_get_datetime_object();
20
+        } else {
21
+            $date = as_get_datetime_object( clone $date );
22
+        }
23
+        $comment_id = $this->create_wp_comment( $action_id, $message, $date );
24
+        return $comment_id;
25
+    }
26
+
27
+    protected function create_wp_comment( $action_id, $message, DateTime $date ) {
28
+
29
+        $comment_date_gmt = $date->format('Y-m-d H:i:s');
30
+        ActionScheduler_TimezoneHelper::set_local_timezone( $date );
31
+        $comment_data = array(
32
+            'comment_post_ID' => $action_id,
33
+            'comment_date' => $date->format('Y-m-d H:i:s'),
34
+            'comment_date_gmt' => $comment_date_gmt,
35
+            'comment_author' => self::AGENT,
36
+            'comment_content' => $message,
37
+            'comment_agent' => self::AGENT,
38
+            'comment_type' => self::TYPE,
39
+        );
40
+        return wp_insert_comment($comment_data);
41
+    }
42
+
43
+    /**
44
+     * @param string $entry_id
45
+     *
46
+     * @return ActionScheduler_LogEntry
47
+     */
48
+    public function get_entry( $entry_id ) {
49
+        $comment = $this->get_comment( $entry_id );
50
+        if ( empty($comment) || $comment->comment_type != self::TYPE ) {
51
+            return new ActionScheduler_NullLogEntry();
52
+        }
53
+
54
+        $date = as_get_datetime_object( $comment->comment_date_gmt );
55
+        ActionScheduler_TimezoneHelper::set_local_timezone( $date );
56
+        return new ActionScheduler_LogEntry( $comment->comment_post_ID, $comment->comment_content, $date );
57
+    }
58
+
59
+    /**
60
+     * @param string $action_id
61
+     *
62
+     * @return ActionScheduler_LogEntry[]
63
+     */
64
+    public function get_logs( $action_id ) {
65
+        $status = 'all';
66
+        if ( get_post_status($action_id) == 'trash' ) {
67
+            $status = 'post-trashed';
68
+        }
69
+        $comments = get_comments(array(
70
+            'post_id' => $action_id,
71
+            'orderby' => 'comment_date_gmt',
72
+            'order' => 'ASC',
73
+            'type' => self::TYPE,
74
+            'status' => $status,
75
+        ));
76
+        $logs = array();
77
+        foreach ( $comments as $c ) {
78
+            $entry = $this->get_entry( $c );
79
+            if ( !empty($entry) ) {
80
+                $logs[] = $entry;
81
+            }
82
+        }
83
+        return $logs;
84
+    }
85
+
86
+    protected function get_comment( $comment_id ) {
87
+        return get_comment( $comment_id );
88
+    }
89
+
90
+
91
+
92
+    /**
93
+     * @param WP_Comment_Query $query
94
+     */
95
+    public function filter_comment_queries( $query ) {
96
+        foreach ( array('ID', 'parent', 'post_author', 'post_name', 'post_parent', 'type', 'post_type', 'post_id', 'post_ID') as $key ) {
97
+            if ( !empty($query->query_vars[$key]) ) {
98
+                return; // don't slow down queries that wouldn't include action_log comments anyway
99
+            }
100
+        }
101
+        $query->query_vars['action_log_filter'] = TRUE;
102
+        add_filter( 'comments_clauses', array( $this, 'filter_comment_query_clauses' ), 10, 2 );
103
+    }
104
+
105
+    /**
106
+     * @param array $clauses
107
+     * @param WP_Comment_Query $query
108
+     *
109
+     * @return array
110
+     */
111
+    public function filter_comment_query_clauses( $clauses, $query ) {
112
+        if ( !empty($query->query_vars['action_log_filter']) ) {
113
+            $clauses['where'] .= $this->get_where_clause();
114
+        }
115
+        return $clauses;
116
+    }
117
+
118
+    /**
119
+     * Make sure Action Scheduler logs are excluded from comment feeds, which use WP_Query, not
120
+     * the WP_Comment_Query class handled by @see self::filter_comment_queries().
121
+     *
122
+     * @param string $where
123
+     * @param WP_Query $query
124
+     *
125
+     * @return string
126
+     */
127
+    public function filter_comment_feed( $where, $query ) {
128
+        if ( is_comment_feed() ) {
129
+            $where .= $this->get_where_clause();
130
+        }
131
+        return $where;
132
+    }
133
+
134
+    /**
135
+     * Return a SQL clause to exclude Action Scheduler comments.
136
+     *
137
+     * @return string
138
+     */
139
+    protected function get_where_clause() {
140
+        global $wpdb;
141
+        return sprintf( " AND {$wpdb->comments}.comment_type != '%s'", self::TYPE );
142
+    }
143
+
144
+    /**
145
+     * Remove action log entries from wp_count_comments()
146
+     *
147
+     * @param array $stats
148
+     * @param int $post_id
149
+     *
150
+     * @return object
151
+     */
152
+    public function filter_comment_count( $stats, $post_id ) {
153
+        global $wpdb;
154
+
155
+        if ( 0 === $post_id ) {
156
+            $stats = $this->get_comment_count();
157
+        }
158
+
159
+        return $stats;
160
+    }
161
+
162
+    /**
163
+     * Retrieve the comment counts from our cache, or the database if the cached version isn't set.
164
+     *
165
+     * @return object
166
+     */
167
+    protected function get_comment_count() {
168
+        global $wpdb;
169
+
170
+        $stats = get_transient( 'as_comment_count' );
171
+
172
+        if ( ! $stats ) {
173
+            $stats = array();
174
+
175
+            $count = $wpdb->get_results( "SELECT comment_approved, COUNT( * ) AS num_comments FROM {$wpdb->comments} WHERE comment_type NOT IN('order_note','action_log') GROUP BY comment_approved", ARRAY_A );
176
+
177
+            $total = 0;
178
+            $stats = array();
179
+            $approved = array( '0' => 'moderated', '1' => 'approved', 'spam' => 'spam', 'trash' => 'trash', 'post-trashed' => 'post-trashed' );
180
+
181
+            foreach ( (array) $count as $row ) {
182
+                // Don't count post-trashed toward totals
183
+                if ( 'post-trashed' != $row['comment_approved'] && 'trash' != $row['comment_approved'] ) {
184
+                    $total += $row['num_comments'];
185
+                }
186
+                if ( isset( $approved[ $row['comment_approved'] ] ) ) {
187
+                    $stats[ $approved[ $row['comment_approved'] ] ] = $row['num_comments'];
188
+                }
189
+            }
190
+
191
+            $stats['total_comments'] = $total;
192
+            $stats['all']            = $total;
193
+
194
+            foreach ( $approved as $key ) {
195
+                if ( empty( $stats[ $key ] ) ) {
196
+                    $stats[ $key ] = 0;
197
+                }
198
+            }
199
+
200
+            $stats = (object) $stats;
201
+            set_transient( 'as_comment_count', $stats );
202
+        }
203
+
204
+        return $stats;
205
+    }
206
+
207
+    /**
208
+     * Delete comment count cache whenever there is new comment or the status of a comment changes. Cache
209
+     * will be regenerated next time ActionScheduler_wpCommentLogger::filter_comment_count() is called.
210
+     */
211
+    public function delete_comment_count_cache() {
212
+        delete_transient( 'as_comment_count' );
213
+    }
214
+
215
+    /**
216
+     * @codeCoverageIgnore
217
+     */
218
+    public function init() {
219
+        add_action( 'action_scheduler_before_process_queue', array( $this, 'disable_comment_counting' ), 10, 0 );
220
+        add_action( 'action_scheduler_after_process_queue', array( $this, 'enable_comment_counting' ), 10, 0 );
221
+
222
+        parent::init();
223
+
224
+        add_action( 'pre_get_comments', array( $this, 'filter_comment_queries' ), 10, 1 );
225
+        add_action( 'wp_count_comments', array( $this, 'filter_comment_count' ), 20, 2 ); // run after WC_Comments::wp_count_comments() to make sure we exclude order notes and action logs
226
+        add_action( 'comment_feed_where', array( $this, 'filter_comment_feed' ), 10, 2 );
227
+
228
+        // Delete comments count cache whenever there is a new comment or a comment status changes
229
+        add_action( 'wp_insert_comment', array( $this, 'delete_comment_count_cache' ) );
230
+        add_action( 'wp_set_comment_status', array( $this, 'delete_comment_count_cache' ) );
231
+    }
232
+
233
+    public function disable_comment_counting() {
234
+        wp_defer_comment_counting(true);
235
+    }
236
+    public function enable_comment_counting() {
237
+        wp_defer_comment_counting(false);
238
+    }
239 239
 
240 240
 }
Please login to merge, or discard this patch.
action-scheduler/classes/data-stores/ActionScheduler_wpPostStore.php 1 patch
Indentation   +844 added lines, -844 removed lines patch added patch discarded remove patch
@@ -4,857 +4,857 @@
 block discarded – undo
4 4
  * Class ActionScheduler_wpPostStore
5 5
  */
6 6
 class ActionScheduler_wpPostStore extends ActionScheduler_Store {
7
-	const POST_TYPE = 'scheduled-action';
8
-	const GROUP_TAXONOMY = 'action-group';
9
-	const SCHEDULE_META_KEY = '_action_manager_schedule';
10
-	const DEPENDENCIES_MET = 'as-post-store-dependencies-met';
11
-
12
-	/** @var DateTimeZone */
13
-	protected $local_timezone = NULL;
14
-
15
-	public function save_action( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ){
16
-		try {
17
-			$this->validate_action( $action );
18
-			$post_array = $this->create_post_array( $action, $scheduled_date );
19
-			$post_id = $this->save_post_array( $post_array );
20
-			$this->save_post_schedule( $post_id, $action->get_schedule() );
21
-			$this->save_action_group( $post_id, $action->get_group() );
22
-			do_action( 'action_scheduler_stored_action', $post_id );
23
-			return $post_id;
24
-		} catch ( Exception $e ) {
25
-			throw new RuntimeException( sprintf( __( 'Error saving action: %s', 'action-scheduler' ), $e->getMessage() ), 0 );
26
-		}
27
-	}
28
-
29
-	protected function create_post_array( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) {
30
-		$post = array(
31
-			'post_type' => self::POST_TYPE,
32
-			'post_title' => $action->get_hook(),
33
-			'post_content' => json_encode($action->get_args()),
34
-			'post_status' => ( $action->is_finished() ? 'publish' : 'pending' ),
35
-			'post_date_gmt' => $this->get_scheduled_date_string( $action, $scheduled_date ),
36
-			'post_date'     => $this->get_scheduled_date_string_local( $action, $scheduled_date ),
37
-		);
38
-		return $post;
39
-	}
40
-
41
-	protected function save_post_array( $post_array ) {
42
-		add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
43
-		add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
44
-
45
-		$has_kses = false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' );
46
-
47
-		if ( $has_kses ) {
48
-			// Prevent KSES from corrupting JSON in post_content.
49
-			kses_remove_filters();
50
-		}
51
-
52
-		$post_id = wp_insert_post($post_array);
53
-
54
-		if ( $has_kses ) {
55
-			kses_init_filters();
56
-		}
57
-
58
-		remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 );
59
-		remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
60
-
61
-		if ( is_wp_error($post_id) || empty($post_id) ) {
62
-			throw new RuntimeException( __( 'Unable to save action.', 'action-scheduler' ) );
63
-		}
64
-		return $post_id;
65
-	}
66
-
67
-	public function filter_insert_post_data( $postdata ) {
68
-		if ( $postdata['post_type'] == self::POST_TYPE ) {
69
-			$postdata['post_author'] = 0;
70
-			if ( $postdata['post_status'] == 'future' ) {
71
-				$postdata['post_status'] = 'publish';
72
-			}
73
-		}
74
-		return $postdata;
75
-	}
76
-
77
-	/**
78
-	 * Create a (probably unique) post name for scheduled actions in a more performant manner than wp_unique_post_slug().
79
-	 *
80
-	 * When an action's post status is transitioned to something other than 'draft', 'pending' or 'auto-draft, like 'publish'
81
-	 * or 'failed' or 'trash', WordPress will find a unique slug (stored in post_name column) using the wp_unique_post_slug()
82
-	 * function. This is done to ensure URL uniqueness. The approach taken by wp_unique_post_slug() is to iterate over existing
83
-	 * post_name values that match, and append a number 1 greater than the largest. This makes sense when manually creating a
84
-	 * post from the Edit Post screen. It becomes a bottleneck when automatically processing thousands of actions, with a
85
-	 * database containing thousands of related post_name values.
86
-	 *
87
-	 * WordPress 5.1 introduces the 'pre_wp_unique_post_slug' filter for plugins to address this issue.
88
-	 *
89
-	 * We can short-circuit WordPress's wp_unique_post_slug() approach using the 'pre_wp_unique_post_slug' filter. This
90
-	 * method is available to be used as a callback on that filter. It provides a more scalable approach to generating a
91
-	 * post_name/slug that is probably unique. Because Action Scheduler never actually uses the post_name field, or an
92
-	 * action's slug, being probably unique is good enough.
93
-	 *
94
-	 * For more backstory on this issue, see:
95
-	 * - https://github.com/woocommerce/action-scheduler/issues/44 and
96
-	 * - https://core.trac.wordpress.org/ticket/21112
97
-	 *
98
-	 * @param string $override_slug Short-circuit return value.
99
-	 * @param string $slug          The desired slug (post_name).
100
-	 * @param int    $post_ID       Post ID.
101
-	 * @param string $post_status   The post status.
102
-	 * @param string $post_type     Post type.
103
-	 * @return string
104
-	 */
105
-	public function set_unique_post_slug( $override_slug, $slug, $post_ID, $post_status, $post_type ) {
106
-		if ( self::POST_TYPE == $post_type ) {
107
-			$override_slug = uniqid( self::POST_TYPE . '-', true ) . '-' . wp_generate_password( 32, false );
108
-		}
109
-		return $override_slug;
110
-	}
111
-
112
-	protected function save_post_schedule( $post_id, $schedule ) {
113
-		update_post_meta( $post_id, self::SCHEDULE_META_KEY, $schedule );
114
-	}
115
-
116
-	protected function save_action_group( $post_id, $group ) {
117
-		if ( empty($group) ) {
118
-			wp_set_object_terms( $post_id, array(), self::GROUP_TAXONOMY, FALSE );
119
-		} else {
120
-			wp_set_object_terms( $post_id, array($group), self::GROUP_TAXONOMY, FALSE );
121
-		}
122
-	}
123
-
124
-	public function fetch_action( $action_id ) {
125
-		$post = $this->get_post( $action_id );
126
-		if ( empty($post) || $post->post_type != self::POST_TYPE ) {
127
-			return $this->get_null_action();
128
-		}
129
-
130
-		try {
131
-			$action = $this->make_action_from_post( $post );
132
-		} catch ( ActionScheduler_InvalidActionException $exception ) {
133
-			do_action( 'action_scheduler_failed_fetch_action', $post->ID, $exception );
134
-			return $this->get_null_action();
135
-		}
136
-
137
-		return $action;
138
-	}
139
-
140
-	protected function get_post( $action_id ) {
141
-		if ( empty($action_id) ) {
142
-			return NULL;
143
-		}
144
-		return get_post($action_id);
145
-	}
146
-
147
-	protected function get_null_action() {
148
-		return new ActionScheduler_NullAction();
149
-	}
150
-
151
-	protected function make_action_from_post( $post ) {
152
-		$hook = $post->post_title;
153
-
154
-		$args = json_decode( $post->post_content, true );
155
-		$this->validate_args( $args, $post->ID );
156
-
157
-		$schedule = get_post_meta( $post->ID, self::SCHEDULE_META_KEY, true );
158
-		$this->validate_schedule( $schedule, $post->ID );
159
-
160
-		$group = wp_get_object_terms( $post->ID, self::GROUP_TAXONOMY, array('fields' => 'names') );
161
-		$group = empty( $group ) ? '' : reset($group);
162
-
163
-		return ActionScheduler::factory()->get_stored_action( $this->get_action_status_by_post_status( $post->post_status ), $hook, $args, $schedule, $group );
164
-	}
165
-
166
-	/**
167
-	 * @param string $post_status
168
-	 *
169
-	 * @throws InvalidArgumentException if $post_status not in known status fields returned by $this->get_status_labels()
170
-	 * @return string
171
-	 */
172
-	protected function get_action_status_by_post_status( $post_status ) {
173
-
174
-		switch ( $post_status ) {
175
-			case 'publish' :
176
-				$action_status = self::STATUS_COMPLETE;
177
-				break;
178
-			case 'trash' :
179
-				$action_status = self::STATUS_CANCELED;
180
-				break;
181
-			default :
182
-				if ( ! array_key_exists( $post_status, $this->get_status_labels() ) ) {
183
-					throw new InvalidArgumentException( sprintf( 'Invalid post status: "%s". No matching action status available.', $post_status ) );
184
-				}
185
-				$action_status = $post_status;
186
-				break;
187
-		}
188
-
189
-		return $action_status;
190
-	}
191
-
192
-	/**
193
-	 * @param string $action_status
194
-	 * @throws InvalidArgumentException if $post_status not in known status fields returned by $this->get_status_labels()
195
-	 * @return string
196
-	 */
197
-	protected function get_post_status_by_action_status( $action_status ) {
198
-
199
-		switch ( $action_status ) {
200
-			case self::STATUS_COMPLETE :
201
-				$post_status = 'publish';
202
-				break;
203
-			case self::STATUS_CANCELED :
204
-				$post_status = 'trash';
205
-				break;
206
-			default :
207
-				if ( ! array_key_exists( $action_status, $this->get_status_labels() ) ) {
208
-					throw new InvalidArgumentException( sprintf( 'Invalid action status: "%s".', $action_status ) );
209
-				}
210
-				$post_status = $action_status;
211
-				break;
212
-		}
213
-
214
-		return $post_status;
215
-	}
216
-
217
-	/**
218
-	 * @param string $hook
219
-	 * @param array $params
220
-	 *
221
-	 * @return string ID of the next action matching the criteria or NULL if not found
222
-	 */
223
-	public function find_action( $hook, $params = array() ) {
224
-		$params = wp_parse_args( $params, array(
225
-			'args' => NULL,
226
-			'status' => ActionScheduler_Store::STATUS_PENDING,
227
-			'group' => '',
228
-		));
229
-		/** @var wpdb $wpdb */
230
-		global $wpdb;
231
-		$query = "SELECT p.ID FROM {$wpdb->posts} p";
232
-		$args = array();
233
-		if ( !empty($params['group']) ) {
234
-			$query .= " INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
235
-			$query .= " INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
236
-			$query .= " INNER JOIN {$wpdb->terms} t ON tt.term_id=t.term_id AND t.slug=%s";
237
-			$args[] = $params['group'];
238
-		}
239
-		$query .= " WHERE p.post_title=%s";
240
-		$args[] = $hook;
241
-		$query .= " AND p.post_type=%s";
242
-		$args[] = self::POST_TYPE;
243
-		if ( !is_null($params['args']) ) {
244
-			$query .= " AND p.post_content=%s";
245
-			$args[] = json_encode($params['args']);
246
-		}
247
-
248
-		if ( ! empty( $params['status'] ) ) {
249
-			$query .= " AND p.post_status=%s";
250
-			$args[] = $this->get_post_status_by_action_status( $params['status'] );
251
-		}
252
-
253
-		switch ( $params['status'] ) {
254
-			case self::STATUS_COMPLETE:
255
-			case self::STATUS_RUNNING:
256
-			case self::STATUS_FAILED:
257
-				$order = 'DESC'; // Find the most recent action that matches
258
-				break;
259
-			case self::STATUS_PENDING:
260
-			default:
261
-				$order = 'ASC'; // Find the next action that matches
262
-				break;
263
-		}
264
-		$query .= " ORDER BY post_date_gmt $order LIMIT 1";
265
-
266
-		$query = $wpdb->prepare( $query, $args );
267
-
268
-		$id = $wpdb->get_var($query);
269
-		return $id;
270
-	}
271
-
272
-	/**
273
-	 * Returns the SQL statement to query (or count) actions.
274
-	 *
275
-	 * @param array $query Filtering options
276
-	 * @param string $select_or_count  Whether the SQL should select and return the IDs or just the row count
277
-	 * @throws InvalidArgumentException if $select_or_count not count or select
278
-	 * @return string SQL statement. The returned SQL is already properly escaped.
279
-	 */
280
-	protected function get_query_actions_sql( array $query, $select_or_count = 'select' ) {
281
-
282
-		if ( ! in_array( $select_or_count, array( 'select', 'count' ) ) ) {
283
-			throw new InvalidArgumentException( __( 'Invalid schedule. Cannot save action.', 'action-scheduler' ) );
284
-		}
285
-
286
-		$query = wp_parse_args( $query, array(
287
-			'hook' => '',
288
-			'args' => NULL,
289
-			'date' => NULL,
290
-			'date_compare' => '<=',
291
-			'modified' => NULL,
292
-			'modified_compare' => '<=',
293
-			'group' => '',
294
-			'status' => '',
295
-			'claimed' => NULL,
296
-			'per_page' => 5,
297
-			'offset' => 0,
298
-			'orderby' => 'date',
299
-			'order' => 'ASC',
300
-			'search' => '',
301
-		) );
302
-
303
-		/** @var wpdb $wpdb */
304
-		global $wpdb;
305
-		$sql  = ( 'count' === $select_or_count ) ? 'SELECT count(p.ID)' : 'SELECT p.ID ';
306
-		$sql .= "FROM {$wpdb->posts} p";
307
-		$sql_params = array();
308
-		if ( empty( $query['group'] ) && 'group' === $query['orderby'] ) {
309
-			$sql .= " LEFT JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
310
-			$sql .= " LEFT JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
311
-			$sql .= " LEFT JOIN {$wpdb->terms} t ON tt.term_id=t.term_id";
312
-		} elseif ( ! empty( $query['group'] ) ) {
313
-			$sql .= " INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
314
-			$sql .= " INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
315
-			$sql .= " INNER JOIN {$wpdb->terms} t ON tt.term_id=t.term_id";
316
-			$sql .= " AND t.slug=%s";
317
-			$sql_params[] = $query['group'];
318
-		}
319
-		$sql .= " WHERE post_type=%s";
320
-		$sql_params[] = self::POST_TYPE;
321
-		if ( $query['hook'] ) {
322
-			$sql .= " AND p.post_title=%s";
323
-			$sql_params[] = $query['hook'];
324
-		}
325
-		if ( !is_null($query['args']) ) {
326
-			$sql .= " AND p.post_content=%s";
327
-			$sql_params[] = json_encode($query['args']);
328
-		}
329
-
330
-		if ( ! empty( $query['status'] ) ) {
331
-			$sql .= " AND p.post_status=%s";
332
-			$sql_params[] = $this->get_post_status_by_action_status( $query['status'] );
333
-		}
334
-
335
-		if ( $query['date'] instanceof DateTime ) {
336
-			$date = clone $query['date'];
337
-			$date->setTimezone( new DateTimeZone('UTC') );
338
-			$date_string = $date->format('Y-m-d H:i:s');
339
-			$comparator = $this->validate_sql_comparator($query['date_compare']);
340
-			$sql .= " AND p.post_date_gmt $comparator %s";
341
-			$sql_params[] = $date_string;
342
-		}
343
-
344
-		if ( $query['modified'] instanceof DateTime ) {
345
-			$modified = clone $query['modified'];
346
-			$modified->setTimezone( new DateTimeZone('UTC') );
347
-			$date_string = $modified->format('Y-m-d H:i:s');
348
-			$comparator = $this->validate_sql_comparator($query['modified_compare']);
349
-			$sql .= " AND p.post_modified_gmt $comparator %s";
350
-			$sql_params[] = $date_string;
351
-		}
352
-
353
-		if ( $query['claimed'] === TRUE ) {
354
-			$sql .= " AND p.post_password != ''";
355
-		} elseif ( $query['claimed'] === FALSE ) {
356
-			$sql .= " AND p.post_password = ''";
357
-		} elseif ( !is_null($query['claimed']) ) {
358
-			$sql .= " AND p.post_password = %s";
359
-			$sql_params[] = $query['claimed'];
360
-		}
361
-
362
-		if ( ! empty( $query['search'] ) ) {
363
-			$sql .= " AND (p.post_title LIKE %s OR p.post_content LIKE %s OR p.post_password LIKE %s)";
364
-			for( $i = 0; $i < 3; $i++ ) {
365
-				$sql_params[] = sprintf( '%%%s%%', $query['search'] );
366
-			}
367
-		}
368
-
369
-		if ( 'select' === $select_or_count ) {
370
-			switch ( $query['orderby'] ) {
371
-				case 'hook':
372
-					$orderby = 'p.post_title';
373
-					break;
374
-				case 'group':
375
-					$orderby = 't.name';
376
-					break;
377
-				case 'status':
378
-					$orderby = 'p.post_status';
379
-					break;
380
-				case 'modified':
381
-					$orderby = 'p.post_modified';
382
-					break;
383
-				case 'claim_id':
384
-					$orderby = 'p.post_password';
385
-					break;
386
-				case 'schedule':
387
-				case 'date':
388
-				default:
389
-					$orderby = 'p.post_date_gmt';
390
-					break;
391
-			}
392
-			if ( 'ASC' === strtoupper( $query['order'] ) ) {
393
-				$order = 'ASC';
394
-			} else {
395
-				$order = 'DESC';
396
-			}
397
-			$sql .= " ORDER BY $orderby $order";
398
-			if ( $query['per_page'] > 0 ) {
399
-				$sql .= " LIMIT %d, %d";
400
-				$sql_params[] = $query['offset'];
401
-				$sql_params[] = $query['per_page'];
402
-			}
403
-		}
404
-
405
-		return $wpdb->prepare( $sql, $sql_params );
406
-	}
407
-
408
-	/**
409
-	 * @param array $query
410
-	 * @param string $query_type Whether to select or count the results. Default, select.
411
-	 * @return string|array The IDs of actions matching the query
412
-	 */
413
-	public function query_actions( $query = array(), $query_type = 'select' ) {
414
-		/** @var wpdb $wpdb */
415
-		global $wpdb;
416
-
417
-		$sql = $this->get_query_actions_sql( $query, $query_type );
418
-
419
-		return ( 'count' === $query_type ) ? $wpdb->get_var( $sql ) : $wpdb->get_col( $sql );
420
-	}
421
-
422
-	/**
423
-	 * Get a count of all actions in the store, grouped by status
424
-	 *
425
-	 * @return array
426
-	 */
427
-	public function action_counts() {
428
-
429
-		$action_counts_by_status = array();
430
-		$action_stati_and_labels = $this->get_status_labels();
431
-		$posts_count_by_status   = (array) wp_count_posts( self::POST_TYPE, 'readable' );
432
-
433
-		foreach ( $posts_count_by_status as $post_status_name => $count ) {
434
-
435
-			try {
436
-				$action_status_name = $this->get_action_status_by_post_status( $post_status_name );
437
-			} catch ( Exception $e ) {
438
-				// Ignore any post statuses that aren't for actions
439
-				continue;
440
-			}
441
-			if ( array_key_exists( $action_status_name, $action_stati_and_labels ) ) {
442
-				$action_counts_by_status[ $action_status_name ] = $count;
443
-			}
444
-		}
445
-
446
-		return $action_counts_by_status;
447
-	}
448
-
449
-	/**
450
-	 * @param string $action_id
451
-	 *
452
-	 * @throws InvalidArgumentException
453
-	 */
454
-	public function cancel_action( $action_id ) {
455
-		$post = get_post( $action_id );
456
-		if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
457
-			throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
458
-		}
459
-		do_action( 'action_scheduler_canceled_action', $action_id );
460
-		add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
461
-		wp_trash_post( $action_id );
462
-		remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
463
-	}
464
-
465
-	public function delete_action( $action_id ) {
466
-		$post = get_post( $action_id );
467
-		if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
468
-			throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
469
-		}
470
-		do_action( 'action_scheduler_deleted_action', $action_id );
471
-
472
-		wp_delete_post( $action_id, TRUE );
473
-	}
474
-
475
-	/**
476
-	 * @param string $action_id
477
-	 *
478
-	 * @throws InvalidArgumentException
479
-	 * @return ActionScheduler_DateTime The date the action is schedule to run, or the date that it ran.
480
-	 */
481
-	public function get_date( $action_id ) {
482
-		$next = $this->get_date_gmt( $action_id );
483
-		return ActionScheduler_TimezoneHelper::set_local_timezone( $next );
484
-	}
485
-
486
-	/**
487
-	 * @param string $action_id
488
-	 *
489
-	 * @throws InvalidArgumentException
490
-	 * @return ActionScheduler_DateTime The date the action is schedule to run, or the date that it ran.
491
-	 */
492
-	public function get_date_gmt( $action_id ) {
493
-		$post = get_post( $action_id );
494
-		if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
495
-			throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
496
-		}
497
-		if ( $post->post_status == 'publish' ) {
498
-			return as_get_datetime_object( $post->post_modified_gmt );
499
-		} else {
500
-			return as_get_datetime_object( $post->post_date_gmt );
501
-		}
502
-	}
503
-
504
-	/**
505
-	 * @param int      $max_actions
506
-	 * @param DateTime $before_date Jobs must be schedule before this date. Defaults to now.
507
-	 * @param array    $hooks       Claim only actions with a hook or hooks.
508
-	 * @param string   $group       Claim only actions in the given group.
509
-	 *
510
-	 * @return ActionScheduler_ActionClaim
511
-	 * @throws RuntimeException When there is an error staking a claim.
512
-	 * @throws InvalidArgumentException When the given group is not valid.
513
-	 */
514
-	public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' ) {
515
-		$claim_id = $this->generate_claim_id();
516
-		$this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group );
517
-		$action_ids = $this->find_actions_by_claim_id( $claim_id );
518
-
519
-		return new ActionScheduler_ActionClaim( $claim_id, $action_ids );
520
-	}
521
-
522
-	/**
523
-	 * @return int
524
-	 */
525
-	public function get_claim_count(){
526
-		global $wpdb;
527
-
528
-		$sql = "SELECT COUNT(DISTINCT post_password) FROM {$wpdb->posts} WHERE post_password != '' AND post_type = %s AND post_status IN ('in-progress','pending')";
529
-		$sql = $wpdb->prepare( $sql, array( self::POST_TYPE ) );
530
-
531
-		return $wpdb->get_var( $sql );
532
-	}
533
-
534
-	protected function generate_claim_id() {
535
-		$claim_id = md5(microtime(true) . rand(0,1000));
536
-		return substr($claim_id, 0, 20); // to fit in db field with 20 char limit
537
-	}
538
-
539
-	/**
540
-	 * @param string   $claim_id
541
-	 * @param int      $limit
542
-	 * @param DateTime $before_date Should use UTC timezone.
543
-	 * @param array    $hooks       Claim only actions with a hook or hooks.
544
-	 * @param string   $group       Claim only actions in the given group.
545
-	 *
546
-	 * @return int The number of actions that were claimed
547
-	 * @throws RuntimeException When there is a database error.
548
-	 * @throws InvalidArgumentException When the group is invalid.
549
-	 */
550
-	protected function claim_actions( $claim_id, $limit, DateTime $before_date = null, $hooks = array(), $group = '' ) {
551
-		// Set up initial variables.
552
-		$date      = null === $before_date ? as_get_datetime_object() : clone $before_date;
553
-		$limit_ids = ! empty( $group );
554
-		$ids       = $limit_ids ? $this->get_actions_by_group( $group, $limit, $date ) : array();
555
-
556
-		// If limiting by IDs and no posts found, then return early since we have nothing to update.
557
-		if ( $limit_ids && 0 === count( $ids ) ) {
558
-			return 0;
559
-		}
560
-
561
-		/** @var wpdb $wpdb */
562
-		global $wpdb;
563
-
564
-		/*
7
+    const POST_TYPE = 'scheduled-action';
8
+    const GROUP_TAXONOMY = 'action-group';
9
+    const SCHEDULE_META_KEY = '_action_manager_schedule';
10
+    const DEPENDENCIES_MET = 'as-post-store-dependencies-met';
11
+
12
+    /** @var DateTimeZone */
13
+    protected $local_timezone = NULL;
14
+
15
+    public function save_action( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ){
16
+        try {
17
+            $this->validate_action( $action );
18
+            $post_array = $this->create_post_array( $action, $scheduled_date );
19
+            $post_id = $this->save_post_array( $post_array );
20
+            $this->save_post_schedule( $post_id, $action->get_schedule() );
21
+            $this->save_action_group( $post_id, $action->get_group() );
22
+            do_action( 'action_scheduler_stored_action', $post_id );
23
+            return $post_id;
24
+        } catch ( Exception $e ) {
25
+            throw new RuntimeException( sprintf( __( 'Error saving action: %s', 'action-scheduler' ), $e->getMessage() ), 0 );
26
+        }
27
+    }
28
+
29
+    protected function create_post_array( ActionScheduler_Action $action, DateTime $scheduled_date = NULL ) {
30
+        $post = array(
31
+            'post_type' => self::POST_TYPE,
32
+            'post_title' => $action->get_hook(),
33
+            'post_content' => json_encode($action->get_args()),
34
+            'post_status' => ( $action->is_finished() ? 'publish' : 'pending' ),
35
+            'post_date_gmt' => $this->get_scheduled_date_string( $action, $scheduled_date ),
36
+            'post_date'     => $this->get_scheduled_date_string_local( $action, $scheduled_date ),
37
+        );
38
+        return $post;
39
+    }
40
+
41
+    protected function save_post_array( $post_array ) {
42
+        add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
43
+        add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
44
+
45
+        $has_kses = false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' );
46
+
47
+        if ( $has_kses ) {
48
+            // Prevent KSES from corrupting JSON in post_content.
49
+            kses_remove_filters();
50
+        }
51
+
52
+        $post_id = wp_insert_post($post_array);
53
+
54
+        if ( $has_kses ) {
55
+            kses_init_filters();
56
+        }
57
+
58
+        remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 );
59
+        remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
60
+
61
+        if ( is_wp_error($post_id) || empty($post_id) ) {
62
+            throw new RuntimeException( __( 'Unable to save action.', 'action-scheduler' ) );
63
+        }
64
+        return $post_id;
65
+    }
66
+
67
+    public function filter_insert_post_data( $postdata ) {
68
+        if ( $postdata['post_type'] == self::POST_TYPE ) {
69
+            $postdata['post_author'] = 0;
70
+            if ( $postdata['post_status'] == 'future' ) {
71
+                $postdata['post_status'] = 'publish';
72
+            }
73
+        }
74
+        return $postdata;
75
+    }
76
+
77
+    /**
78
+     * Create a (probably unique) post name for scheduled actions in a more performant manner than wp_unique_post_slug().
79
+     *
80
+     * When an action's post status is transitioned to something other than 'draft', 'pending' or 'auto-draft, like 'publish'
81
+     * or 'failed' or 'trash', WordPress will find a unique slug (stored in post_name column) using the wp_unique_post_slug()
82
+     * function. This is done to ensure URL uniqueness. The approach taken by wp_unique_post_slug() is to iterate over existing
83
+     * post_name values that match, and append a number 1 greater than the largest. This makes sense when manually creating a
84
+     * post from the Edit Post screen. It becomes a bottleneck when automatically processing thousands of actions, with a
85
+     * database containing thousands of related post_name values.
86
+     *
87
+     * WordPress 5.1 introduces the 'pre_wp_unique_post_slug' filter for plugins to address this issue.
88
+     *
89
+     * We can short-circuit WordPress's wp_unique_post_slug() approach using the 'pre_wp_unique_post_slug' filter. This
90
+     * method is available to be used as a callback on that filter. It provides a more scalable approach to generating a
91
+     * post_name/slug that is probably unique. Because Action Scheduler never actually uses the post_name field, or an
92
+     * action's slug, being probably unique is good enough.
93
+     *
94
+     * For more backstory on this issue, see:
95
+     * - https://github.com/woocommerce/action-scheduler/issues/44 and
96
+     * - https://core.trac.wordpress.org/ticket/21112
97
+     *
98
+     * @param string $override_slug Short-circuit return value.
99
+     * @param string $slug          The desired slug (post_name).
100
+     * @param int    $post_ID       Post ID.
101
+     * @param string $post_status   The post status.
102
+     * @param string $post_type     Post type.
103
+     * @return string
104
+     */
105
+    public function set_unique_post_slug( $override_slug, $slug, $post_ID, $post_status, $post_type ) {
106
+        if ( self::POST_TYPE == $post_type ) {
107
+            $override_slug = uniqid( self::POST_TYPE . '-', true ) . '-' . wp_generate_password( 32, false );
108
+        }
109
+        return $override_slug;
110
+    }
111
+
112
+    protected function save_post_schedule( $post_id, $schedule ) {
113
+        update_post_meta( $post_id, self::SCHEDULE_META_KEY, $schedule );
114
+    }
115
+
116
+    protected function save_action_group( $post_id, $group ) {
117
+        if ( empty($group) ) {
118
+            wp_set_object_terms( $post_id, array(), self::GROUP_TAXONOMY, FALSE );
119
+        } else {
120
+            wp_set_object_terms( $post_id, array($group), self::GROUP_TAXONOMY, FALSE );
121
+        }
122
+    }
123
+
124
+    public function fetch_action( $action_id ) {
125
+        $post = $this->get_post( $action_id );
126
+        if ( empty($post) || $post->post_type != self::POST_TYPE ) {
127
+            return $this->get_null_action();
128
+        }
129
+
130
+        try {
131
+            $action = $this->make_action_from_post( $post );
132
+        } catch ( ActionScheduler_InvalidActionException $exception ) {
133
+            do_action( 'action_scheduler_failed_fetch_action', $post->ID, $exception );
134
+            return $this->get_null_action();
135
+        }
136
+
137
+        return $action;
138
+    }
139
+
140
+    protected function get_post( $action_id ) {
141
+        if ( empty($action_id) ) {
142
+            return NULL;
143
+        }
144
+        return get_post($action_id);
145
+    }
146
+
147
+    protected function get_null_action() {
148
+        return new ActionScheduler_NullAction();
149
+    }
150
+
151
+    protected function make_action_from_post( $post ) {
152
+        $hook = $post->post_title;
153
+
154
+        $args = json_decode( $post->post_content, true );
155
+        $this->validate_args( $args, $post->ID );
156
+
157
+        $schedule = get_post_meta( $post->ID, self::SCHEDULE_META_KEY, true );
158
+        $this->validate_schedule( $schedule, $post->ID );
159
+
160
+        $group = wp_get_object_terms( $post->ID, self::GROUP_TAXONOMY, array('fields' => 'names') );
161
+        $group = empty( $group ) ? '' : reset($group);
162
+
163
+        return ActionScheduler::factory()->get_stored_action( $this->get_action_status_by_post_status( $post->post_status ), $hook, $args, $schedule, $group );
164
+    }
165
+
166
+    /**
167
+     * @param string $post_status
168
+     *
169
+     * @throws InvalidArgumentException if $post_status not in known status fields returned by $this->get_status_labels()
170
+     * @return string
171
+     */
172
+    protected function get_action_status_by_post_status( $post_status ) {
173
+
174
+        switch ( $post_status ) {
175
+            case 'publish' :
176
+                $action_status = self::STATUS_COMPLETE;
177
+                break;
178
+            case 'trash' :
179
+                $action_status = self::STATUS_CANCELED;
180
+                break;
181
+            default :
182
+                if ( ! array_key_exists( $post_status, $this->get_status_labels() ) ) {
183
+                    throw new InvalidArgumentException( sprintf( 'Invalid post status: "%s". No matching action status available.', $post_status ) );
184
+                }
185
+                $action_status = $post_status;
186
+                break;
187
+        }
188
+
189
+        return $action_status;
190
+    }
191
+
192
+    /**
193
+     * @param string $action_status
194
+     * @throws InvalidArgumentException if $post_status not in known status fields returned by $this->get_status_labels()
195
+     * @return string
196
+     */
197
+    protected function get_post_status_by_action_status( $action_status ) {
198
+
199
+        switch ( $action_status ) {
200
+            case self::STATUS_COMPLETE :
201
+                $post_status = 'publish';
202
+                break;
203
+            case self::STATUS_CANCELED :
204
+                $post_status = 'trash';
205
+                break;
206
+            default :
207
+                if ( ! array_key_exists( $action_status, $this->get_status_labels() ) ) {
208
+                    throw new InvalidArgumentException( sprintf( 'Invalid action status: "%s".', $action_status ) );
209
+                }
210
+                $post_status = $action_status;
211
+                break;
212
+        }
213
+
214
+        return $post_status;
215
+    }
216
+
217
+    /**
218
+     * @param string $hook
219
+     * @param array $params
220
+     *
221
+     * @return string ID of the next action matching the criteria or NULL if not found
222
+     */
223
+    public function find_action( $hook, $params = array() ) {
224
+        $params = wp_parse_args( $params, array(
225
+            'args' => NULL,
226
+            'status' => ActionScheduler_Store::STATUS_PENDING,
227
+            'group' => '',
228
+        ));
229
+        /** @var wpdb $wpdb */
230
+        global $wpdb;
231
+        $query = "SELECT p.ID FROM {$wpdb->posts} p";
232
+        $args = array();
233
+        if ( !empty($params['group']) ) {
234
+            $query .= " INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
235
+            $query .= " INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
236
+            $query .= " INNER JOIN {$wpdb->terms} t ON tt.term_id=t.term_id AND t.slug=%s";
237
+            $args[] = $params['group'];
238
+        }
239
+        $query .= " WHERE p.post_title=%s";
240
+        $args[] = $hook;
241
+        $query .= " AND p.post_type=%s";
242
+        $args[] = self::POST_TYPE;
243
+        if ( !is_null($params['args']) ) {
244
+            $query .= " AND p.post_content=%s";
245
+            $args[] = json_encode($params['args']);
246
+        }
247
+
248
+        if ( ! empty( $params['status'] ) ) {
249
+            $query .= " AND p.post_status=%s";
250
+            $args[] = $this->get_post_status_by_action_status( $params['status'] );
251
+        }
252
+
253
+        switch ( $params['status'] ) {
254
+            case self::STATUS_COMPLETE:
255
+            case self::STATUS_RUNNING:
256
+            case self::STATUS_FAILED:
257
+                $order = 'DESC'; // Find the most recent action that matches
258
+                break;
259
+            case self::STATUS_PENDING:
260
+            default:
261
+                $order = 'ASC'; // Find the next action that matches
262
+                break;
263
+        }
264
+        $query .= " ORDER BY post_date_gmt $order LIMIT 1";
265
+
266
+        $query = $wpdb->prepare( $query, $args );
267
+
268
+        $id = $wpdb->get_var($query);
269
+        return $id;
270
+    }
271
+
272
+    /**
273
+     * Returns the SQL statement to query (or count) actions.
274
+     *
275
+     * @param array $query Filtering options
276
+     * @param string $select_or_count  Whether the SQL should select and return the IDs or just the row count
277
+     * @throws InvalidArgumentException if $select_or_count not count or select
278
+     * @return string SQL statement. The returned SQL is already properly escaped.
279
+     */
280
+    protected function get_query_actions_sql( array $query, $select_or_count = 'select' ) {
281
+
282
+        if ( ! in_array( $select_or_count, array( 'select', 'count' ) ) ) {
283
+            throw new InvalidArgumentException( __( 'Invalid schedule. Cannot save action.', 'action-scheduler' ) );
284
+        }
285
+
286
+        $query = wp_parse_args( $query, array(
287
+            'hook' => '',
288
+            'args' => NULL,
289
+            'date' => NULL,
290
+            'date_compare' => '<=',
291
+            'modified' => NULL,
292
+            'modified_compare' => '<=',
293
+            'group' => '',
294
+            'status' => '',
295
+            'claimed' => NULL,
296
+            'per_page' => 5,
297
+            'offset' => 0,
298
+            'orderby' => 'date',
299
+            'order' => 'ASC',
300
+            'search' => '',
301
+        ) );
302
+
303
+        /** @var wpdb $wpdb */
304
+        global $wpdb;
305
+        $sql  = ( 'count' === $select_or_count ) ? 'SELECT count(p.ID)' : 'SELECT p.ID ';
306
+        $sql .= "FROM {$wpdb->posts} p";
307
+        $sql_params = array();
308
+        if ( empty( $query['group'] ) && 'group' === $query['orderby'] ) {
309
+            $sql .= " LEFT JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
310
+            $sql .= " LEFT JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
311
+            $sql .= " LEFT JOIN {$wpdb->terms} t ON tt.term_id=t.term_id";
312
+        } elseif ( ! empty( $query['group'] ) ) {
313
+            $sql .= " INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
314
+            $sql .= " INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
315
+            $sql .= " INNER JOIN {$wpdb->terms} t ON tt.term_id=t.term_id";
316
+            $sql .= " AND t.slug=%s";
317
+            $sql_params[] = $query['group'];
318
+        }
319
+        $sql .= " WHERE post_type=%s";
320
+        $sql_params[] = self::POST_TYPE;
321
+        if ( $query['hook'] ) {
322
+            $sql .= " AND p.post_title=%s";
323
+            $sql_params[] = $query['hook'];
324
+        }
325
+        if ( !is_null($query['args']) ) {
326
+            $sql .= " AND p.post_content=%s";
327
+            $sql_params[] = json_encode($query['args']);
328
+        }
329
+
330
+        if ( ! empty( $query['status'] ) ) {
331
+            $sql .= " AND p.post_status=%s";
332
+            $sql_params[] = $this->get_post_status_by_action_status( $query['status'] );
333
+        }
334
+
335
+        if ( $query['date'] instanceof DateTime ) {
336
+            $date = clone $query['date'];
337
+            $date->setTimezone( new DateTimeZone('UTC') );
338
+            $date_string = $date->format('Y-m-d H:i:s');
339
+            $comparator = $this->validate_sql_comparator($query['date_compare']);
340
+            $sql .= " AND p.post_date_gmt $comparator %s";
341
+            $sql_params[] = $date_string;
342
+        }
343
+
344
+        if ( $query['modified'] instanceof DateTime ) {
345
+            $modified = clone $query['modified'];
346
+            $modified->setTimezone( new DateTimeZone('UTC') );
347
+            $date_string = $modified->format('Y-m-d H:i:s');
348
+            $comparator = $this->validate_sql_comparator($query['modified_compare']);
349
+            $sql .= " AND p.post_modified_gmt $comparator %s";
350
+            $sql_params[] = $date_string;
351
+        }
352
+
353
+        if ( $query['claimed'] === TRUE ) {
354
+            $sql .= " AND p.post_password != ''";
355
+        } elseif ( $query['claimed'] === FALSE ) {
356
+            $sql .= " AND p.post_password = ''";
357
+        } elseif ( !is_null($query['claimed']) ) {
358
+            $sql .= " AND p.post_password = %s";
359
+            $sql_params[] = $query['claimed'];
360
+        }
361
+
362
+        if ( ! empty( $query['search'] ) ) {
363
+            $sql .= " AND (p.post_title LIKE %s OR p.post_content LIKE %s OR p.post_password LIKE %s)";
364
+            for( $i = 0; $i < 3; $i++ ) {
365
+                $sql_params[] = sprintf( '%%%s%%', $query['search'] );
366
+            }
367
+        }
368
+
369
+        if ( 'select' === $select_or_count ) {
370
+            switch ( $query['orderby'] ) {
371
+                case 'hook':
372
+                    $orderby = 'p.post_title';
373
+                    break;
374
+                case 'group':
375
+                    $orderby = 't.name';
376
+                    break;
377
+                case 'status':
378
+                    $orderby = 'p.post_status';
379
+                    break;
380
+                case 'modified':
381
+                    $orderby = 'p.post_modified';
382
+                    break;
383
+                case 'claim_id':
384
+                    $orderby = 'p.post_password';
385
+                    break;
386
+                case 'schedule':
387
+                case 'date':
388
+                default:
389
+                    $orderby = 'p.post_date_gmt';
390
+                    break;
391
+            }
392
+            if ( 'ASC' === strtoupper( $query['order'] ) ) {
393
+                $order = 'ASC';
394
+            } else {
395
+                $order = 'DESC';
396
+            }
397
+            $sql .= " ORDER BY $orderby $order";
398
+            if ( $query['per_page'] > 0 ) {
399
+                $sql .= " LIMIT %d, %d";
400
+                $sql_params[] = $query['offset'];
401
+                $sql_params[] = $query['per_page'];
402
+            }
403
+        }
404
+
405
+        return $wpdb->prepare( $sql, $sql_params );
406
+    }
407
+
408
+    /**
409
+     * @param array $query
410
+     * @param string $query_type Whether to select or count the results. Default, select.
411
+     * @return string|array The IDs of actions matching the query
412
+     */
413
+    public function query_actions( $query = array(), $query_type = 'select' ) {
414
+        /** @var wpdb $wpdb */
415
+        global $wpdb;
416
+
417
+        $sql = $this->get_query_actions_sql( $query, $query_type );
418
+
419
+        return ( 'count' === $query_type ) ? $wpdb->get_var( $sql ) : $wpdb->get_col( $sql );
420
+    }
421
+
422
+    /**
423
+     * Get a count of all actions in the store, grouped by status
424
+     *
425
+     * @return array
426
+     */
427
+    public function action_counts() {
428
+
429
+        $action_counts_by_status = array();
430
+        $action_stati_and_labels = $this->get_status_labels();
431
+        $posts_count_by_status   = (array) wp_count_posts( self::POST_TYPE, 'readable' );
432
+
433
+        foreach ( $posts_count_by_status as $post_status_name => $count ) {
434
+
435
+            try {
436
+                $action_status_name = $this->get_action_status_by_post_status( $post_status_name );
437
+            } catch ( Exception $e ) {
438
+                // Ignore any post statuses that aren't for actions
439
+                continue;
440
+            }
441
+            if ( array_key_exists( $action_status_name, $action_stati_and_labels ) ) {
442
+                $action_counts_by_status[ $action_status_name ] = $count;
443
+            }
444
+        }
445
+
446
+        return $action_counts_by_status;
447
+    }
448
+
449
+    /**
450
+     * @param string $action_id
451
+     *
452
+     * @throws InvalidArgumentException
453
+     */
454
+    public function cancel_action( $action_id ) {
455
+        $post = get_post( $action_id );
456
+        if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
457
+            throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
458
+        }
459
+        do_action( 'action_scheduler_canceled_action', $action_id );
460
+        add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
461
+        wp_trash_post( $action_id );
462
+        remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
463
+    }
464
+
465
+    public function delete_action( $action_id ) {
466
+        $post = get_post( $action_id );
467
+        if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
468
+            throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
469
+        }
470
+        do_action( 'action_scheduler_deleted_action', $action_id );
471
+
472
+        wp_delete_post( $action_id, TRUE );
473
+    }
474
+
475
+    /**
476
+     * @param string $action_id
477
+     *
478
+     * @throws InvalidArgumentException
479
+     * @return ActionScheduler_DateTime The date the action is schedule to run, or the date that it ran.
480
+     */
481
+    public function get_date( $action_id ) {
482
+        $next = $this->get_date_gmt( $action_id );
483
+        return ActionScheduler_TimezoneHelper::set_local_timezone( $next );
484
+    }
485
+
486
+    /**
487
+     * @param string $action_id
488
+     *
489
+     * @throws InvalidArgumentException
490
+     * @return ActionScheduler_DateTime The date the action is schedule to run, or the date that it ran.
491
+     */
492
+    public function get_date_gmt( $action_id ) {
493
+        $post = get_post( $action_id );
494
+        if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
495
+            throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
496
+        }
497
+        if ( $post->post_status == 'publish' ) {
498
+            return as_get_datetime_object( $post->post_modified_gmt );
499
+        } else {
500
+            return as_get_datetime_object( $post->post_date_gmt );
501
+        }
502
+    }
503
+
504
+    /**
505
+     * @param int      $max_actions
506
+     * @param DateTime $before_date Jobs must be schedule before this date. Defaults to now.
507
+     * @param array    $hooks       Claim only actions with a hook or hooks.
508
+     * @param string   $group       Claim only actions in the given group.
509
+     *
510
+     * @return ActionScheduler_ActionClaim
511
+     * @throws RuntimeException When there is an error staking a claim.
512
+     * @throws InvalidArgumentException When the given group is not valid.
513
+     */
514
+    public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' ) {
515
+        $claim_id = $this->generate_claim_id();
516
+        $this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group );
517
+        $action_ids = $this->find_actions_by_claim_id( $claim_id );
518
+
519
+        return new ActionScheduler_ActionClaim( $claim_id, $action_ids );
520
+    }
521
+
522
+    /**
523
+     * @return int
524
+     */
525
+    public function get_claim_count(){
526
+        global $wpdb;
527
+
528
+        $sql = "SELECT COUNT(DISTINCT post_password) FROM {$wpdb->posts} WHERE post_password != '' AND post_type = %s AND post_status IN ('in-progress','pending')";
529
+        $sql = $wpdb->prepare( $sql, array( self::POST_TYPE ) );
530
+
531
+        return $wpdb->get_var( $sql );
532
+    }
533
+
534
+    protected function generate_claim_id() {
535
+        $claim_id = md5(microtime(true) . rand(0,1000));
536
+        return substr($claim_id, 0, 20); // to fit in db field with 20 char limit
537
+    }
538
+
539
+    /**
540
+     * @param string   $claim_id
541
+     * @param int      $limit
542
+     * @param DateTime $before_date Should use UTC timezone.
543
+     * @param array    $hooks       Claim only actions with a hook or hooks.
544
+     * @param string   $group       Claim only actions in the given group.
545
+     *
546
+     * @return int The number of actions that were claimed
547
+     * @throws RuntimeException When there is a database error.
548
+     * @throws InvalidArgumentException When the group is invalid.
549
+     */
550
+    protected function claim_actions( $claim_id, $limit, DateTime $before_date = null, $hooks = array(), $group = '' ) {
551
+        // Set up initial variables.
552
+        $date      = null === $before_date ? as_get_datetime_object() : clone $before_date;
553
+        $limit_ids = ! empty( $group );
554
+        $ids       = $limit_ids ? $this->get_actions_by_group( $group, $limit, $date ) : array();
555
+
556
+        // If limiting by IDs and no posts found, then return early since we have nothing to update.
557
+        if ( $limit_ids && 0 === count( $ids ) ) {
558
+            return 0;
559
+        }
560
+
561
+        /** @var wpdb $wpdb */
562
+        global $wpdb;
563
+
564
+        /*
565 565
 		 * Build up custom query to update the affected posts. Parameters are built as a separate array
566 566
 		 * to make it easier to identify where they are in the query.
567 567
 		 *
568 568
 		 * We can't use $wpdb->update() here because of the "ID IN ..." clause.
569 569
 		 */
570
-		$update = "UPDATE {$wpdb->posts} SET post_password = %s, post_modified_gmt = %s, post_modified = %s";
571
-		$params = array(
572
-			$claim_id,
573
-			current_time( 'mysql', true ),
574
-			current_time( 'mysql' ),
575
-		);
576
-
577
-		// Build initial WHERE clause.
578
-		$where    = "WHERE post_type = %s AND post_status = %s AND post_password = ''";
579
-		$params[] = self::POST_TYPE;
580
-		$params[] = ActionScheduler_Store::STATUS_PENDING;
581
-
582
-		if ( ! empty( $hooks ) ) {
583
-			$placeholders = array_fill( 0, count( $hooks ), '%s' );
584
-			$where       .= ' AND post_title IN (' . join( ', ', $placeholders ) . ')';
585
-			$params       = array_merge( $params, array_values( $hooks ) );
586
-		}
587
-
588
-		/*
570
+        $update = "UPDATE {$wpdb->posts} SET post_password = %s, post_modified_gmt = %s, post_modified = %s";
571
+        $params = array(
572
+            $claim_id,
573
+            current_time( 'mysql', true ),
574
+            current_time( 'mysql' ),
575
+        );
576
+
577
+        // Build initial WHERE clause.
578
+        $where    = "WHERE post_type = %s AND post_status = %s AND post_password = ''";
579
+        $params[] = self::POST_TYPE;
580
+        $params[] = ActionScheduler_Store::STATUS_PENDING;
581
+
582
+        if ( ! empty( $hooks ) ) {
583
+            $placeholders = array_fill( 0, count( $hooks ), '%s' );
584
+            $where       .= ' AND post_title IN (' . join( ', ', $placeholders ) . ')';
585
+            $params       = array_merge( $params, array_values( $hooks ) );
586
+        }
587
+
588
+        /*
589 589
 		 * Add the IDs to the WHERE clause. IDs not escaped because they came directly from a prior DB query.
590 590
 		 *
591 591
 		 * If we're not limiting by IDs, then include the post_date_gmt clause.
592 592
 		 */
593
-		if ( $limit_ids ) {
594
-			$where .= ' AND ID IN (' . join( ',', $ids ) . ')';
595
-		} else {
596
-			$where .= ' AND post_date_gmt <= %s';
597
-			$params[] = $date->format( 'Y-m-d H:i:s' );
598
-		}
599
-
600
-		// Add the ORDER BY clause and,ms limit.
601
-		$order    = 'ORDER BY menu_order ASC, post_date_gmt ASC, ID ASC LIMIT %d';
602
-		$params[] = $limit;
603
-
604
-		// Run the query and gather results.
605
-		$rows_affected = $wpdb->query( $wpdb->prepare( "{$update} {$where} {$order}", $params ) );
606
-		if ( $rows_affected === false ) {
607
-			throw new RuntimeException( __( 'Unable to claim actions. Database error.', 'action-scheduler' ) );
608
-		}
609
-
610
-		return (int) $rows_affected;
611
-	}
612
-
613
-	/**
614
-	 * Get IDs of actions within a certain group and up to a certain date/time.
615
-	 *
616
-	 * @param string   $group The group to use in finding actions.
617
-	 * @param int      $limit The number of actions to retrieve.
618
-	 * @param DateTime $date  DateTime object representing cutoff time for actions. Actions retrieved will be
619
-	 *                        up to and including this DateTime.
620
-	 *
621
-	 * @return array IDs of actions in the appropriate group and before the appropriate time.
622
-	 * @throws InvalidArgumentException When the group does not exist.
623
-	 */
624
-	protected function get_actions_by_group( $group, $limit, DateTime $date ) {
625
-		// Ensure the group exists before continuing.
626
-		if ( ! term_exists( $group, self::GROUP_TAXONOMY )) {
627
-			throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'action-scheduler' ), $group ) );
628
-		}
629
-
630
-		// Set up a query for post IDs to use later.
631
-		$query      = new WP_Query();
632
-		$query_args = array(
633
-			'fields'           => 'ids',
634
-			'post_type'        => self::POST_TYPE,
635
-			'post_status'      => ActionScheduler_Store::STATUS_PENDING,
636
-			'has_password'     => false,
637
-			'posts_per_page'   => $limit * 3,
638
-			'suppress_filters' => true,
639
-			'no_found_rows'    => true,
640
-			'orderby'          => array(
641
-				'menu_order' => 'ASC',
642
-				'date'       => 'ASC',
643
-				'ID'         => 'ASC',
644
-			),
645
-			'date_query'       => array(
646
-				'column' => 'post_date_gmt',
647
-				'before' => $date->format( 'Y-m-d H:i' ),
648
-				'inclusive' => true,
649
-			),
650
-			'tax_query' => array(
651
-				array(
652
-					'taxonomy'         => self::GROUP_TAXONOMY,
653
-					'field'            => 'slug',
654
-					'terms'            => $group,
655
-					'include_children' => false,
656
-				),
657
-			),
658
-		);
659
-
660
-		return $query->query( $query_args );
661
-	}
662
-
663
-	/**
664
-	 * @param string $claim_id
665
-	 * @return array
666
-	 */
667
-	public function find_actions_by_claim_id( $claim_id ) {
668
-		/** @var wpdb $wpdb */
669
-		global $wpdb;
670
-		$sql = "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_password = %s";
671
-		$sql = $wpdb->prepare( $sql, array( self::POST_TYPE, $claim_id ) );
672
-		$action_ids = $wpdb->get_col( $sql );
673
-		return $action_ids;
674
-	}
675
-
676
-	public function release_claim( ActionScheduler_ActionClaim $claim ) {
677
-		$action_ids = $this->find_actions_by_claim_id( $claim->get_id() );
678
-		if ( empty( $action_ids ) ) {
679
-			return; // nothing to do
680
-		}
681
-		$action_id_string = implode( ',', array_map( 'intval', $action_ids ) );
682
-		/** @var wpdb $wpdb */
683
-		global $wpdb;
684
-		$sql = "UPDATE {$wpdb->posts} SET post_password = '' WHERE ID IN ($action_id_string) AND post_password = %s";
685
-		$sql = $wpdb->prepare( $sql, array( $claim->get_id() ) );
686
-		$result = $wpdb->query( $sql );
687
-		if ( $result === false ) {
688
-			/* translators: %s: claim ID */
689
-			throw new RuntimeException( sprintf( __( 'Unable to unlock claim %s. Database error.', 'action-scheduler' ), $claim->get_id() ) );
690
-		}
691
-	}
692
-
693
-	/**
694
-	 * @param string $action_id
695
-	 */
696
-	public function unclaim_action( $action_id ) {
697
-		/** @var wpdb $wpdb */
698
-		global $wpdb;
699
-		$sql = "UPDATE {$wpdb->posts} SET post_password = '' WHERE ID = %d AND post_type = %s";
700
-		$sql = $wpdb->prepare( $sql, $action_id, self::POST_TYPE );
701
-		$result = $wpdb->query( $sql );
702
-		if ( $result === false ) {
703
-			/* translators: %s: action ID */
704
-			throw new RuntimeException( sprintf( __( 'Unable to unlock claim on action %s. Database error.', 'action-scheduler' ), $action_id ) );
705
-		}
706
-	}
707
-
708
-	public function mark_failure( $action_id ) {
709
-		/** @var wpdb $wpdb */
710
-		global $wpdb;
711
-		$sql = "UPDATE {$wpdb->posts} SET post_status = %s WHERE ID = %d AND post_type = %s";
712
-		$sql = $wpdb->prepare( $sql, self::STATUS_FAILED, $action_id, self::POST_TYPE );
713
-		$result = $wpdb->query( $sql );
714
-		if ( $result === false ) {
715
-			/* translators: %s: action ID */
716
-			throw new RuntimeException( sprintf( __( 'Unable to mark failure on action %s. Database error.', 'action-scheduler' ), $action_id ) );
717
-		}
718
-	}
719
-
720
-	/**
721
-	 * Return an action's claim ID, as stored in the post password column
722
-	 *
723
-	 * @param string $action_id
724
-	 * @return mixed
725
-	 */
726
-	public function get_claim_id( $action_id ) {
727
-		return $this->get_post_column( $action_id, 'post_password' );
728
-	}
729
-
730
-	/**
731
-	 * Return an action's status, as stored in the post status column
732
-	 *
733
-	 * @param string $action_id
734
-	 * @return mixed
735
-	 */
736
-	public function get_status( $action_id ) {
737
-		$status = $this->get_post_column( $action_id, 'post_status' );
738
-
739
-		if ( $status === null ) {
740
-			throw new InvalidArgumentException( __( 'Invalid action ID. No status found.', 'action-scheduler' ) );
741
-		}
742
-
743
-		return $this->get_action_status_by_post_status( $status );
744
-	}
745
-
746
-	private function get_post_column( $action_id, $column_name ) {
747
-		/** @var \wpdb $wpdb */
748
-		global $wpdb;
749
-		return $wpdb->get_var( $wpdb->prepare( "SELECT {$column_name} FROM {$wpdb->posts} WHERE ID=%d AND post_type=%s", $action_id, self::POST_TYPE ) );
750
-	}
751
-
752
-	/**
753
-	 * @param string $action_id
754
-	 */
755
-	public function log_execution( $action_id ) {
756
-		/** @var wpdb $wpdb */
757
-		global $wpdb;
758
-
759
-		$sql = "UPDATE {$wpdb->posts} SET menu_order = menu_order+1, post_status=%s, post_modified_gmt = %s, post_modified = %s WHERE ID = %d AND post_type = %s";
760
-		$sql = $wpdb->prepare( $sql, self::STATUS_RUNNING, current_time('mysql', true), current_time('mysql'), $action_id, self::POST_TYPE );
761
-		$wpdb->query($sql);
762
-	}
763
-
764
-	/**
765
-	 * Record that an action was completed.
766
-	 *
767
-	 * @param int $action_id ID of the completed action.
768
-	 * @throws InvalidArgumentException|RuntimeException
769
-	 */
770
-	public function mark_complete( $action_id ) {
771
-		$post = get_post( $action_id );
772
-		if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
773
-			throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
774
-		}
775
-		add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
776
-		add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
777
-		$result = wp_update_post(array(
778
-			'ID' => $action_id,
779
-			'post_status' => 'publish',
780
-		), TRUE);
781
-		remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 );
782
-		remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
783
-		if ( is_wp_error( $result ) ) {
784
-			throw new RuntimeException( $result->get_error_message() );
785
-		}
786
-	}
787
-
788
-	/**
789
-	 * Mark action as migrated when there is an error deleting the action.
790
-	 *
791
-	 * @param int $action_id Action ID.
792
-	 */
793
-	public function mark_migrated( $action_id ) {
794
-		wp_update_post(
795
-			array(
796
-				'ID'          => $action_id,
797
-				'post_status' => 'migrated'
798
-			)
799
-		);
800
-	}
801
-
802
-	/**
803
-	 * Determine whether the post store can be migrated.
804
-	 *
805
-	 * @return bool
806
-	 */
807
-	public function migration_dependencies_met( $setting ) {
808
-		global $wpdb;
809
-
810
-		$dependencies_met = get_transient( self::DEPENDENCIES_MET );
811
-		if ( empty( $dependencies_met ) ) {
812
-			$maximum_args_length = apply_filters( 'action_scheduler_maximum_args_length', 191 );
813
-			$found_action        = $wpdb->get_var(
814
-				$wpdb->prepare(
815
-					"SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND CHAR_LENGTH(post_content) > %d LIMIT 1",
816
-					$maximum_args_length,
817
-					self::POST_TYPE
818
-				)
819
-			);
820
-			$dependencies_met = $found_action ? 'no' : 'yes';
821
-			set_transient( self::DEPENDENCIES_MET, $dependencies_met, DAY_IN_SECONDS );
822
-		}
823
-
824
-		return 'yes' == $dependencies_met ? $setting : false;
825
-	}
826
-
827
-	/**
828
-	 * InnoDB indexes have a maximum size of 767 bytes by default, which is only 191 characters with utf8mb4.
829
-	 *
830
-	 * Previously, AS wasn't concerned about args length, as we used the (unindex) post_content column. However,
831
-	 * as we prepare to move to custom tables, and can use an indexed VARCHAR column instead, we want to warn
832
-	 * developers of this impending requirement.
833
-	 *
834
-	 * @param ActionScheduler_Action $action
835
-	 */
836
-	protected function validate_action( ActionScheduler_Action $action ) {
837
-		try {
838
-			parent::validate_action( $action );
839
-		} catch ( Exception $e ) {
840
-			$message = sprintf( __( '%s Support for strings longer than this will be removed in a future version.', 'action-scheduler' ), $e->getMessage() );
841
-			_doing_it_wrong( 'ActionScheduler_Action::$args', $message, '2.1.0' );
842
-		}
843
-	}
844
-
845
-	/**
846
-	 * @codeCoverageIgnore
847
-	 */
848
-	public function init() {
849
-		add_filter( 'action_scheduler_migration_dependencies_met', array( $this, 'migration_dependencies_met' ) );
850
-
851
-		$post_type_registrar = new ActionScheduler_wpPostStore_PostTypeRegistrar();
852
-		$post_type_registrar->register();
853
-
854
-		$post_status_registrar = new ActionScheduler_wpPostStore_PostStatusRegistrar();
855
-		$post_status_registrar->register();
856
-
857
-		$taxonomy_registrar = new ActionScheduler_wpPostStore_TaxonomyRegistrar();
858
-		$taxonomy_registrar->register();
859
-	}
593
+        if ( $limit_ids ) {
594
+            $where .= ' AND ID IN (' . join( ',', $ids ) . ')';
595
+        } else {
596
+            $where .= ' AND post_date_gmt <= %s';
597
+            $params[] = $date->format( 'Y-m-d H:i:s' );
598
+        }
599
+
600
+        // Add the ORDER BY clause and,ms limit.
601
+        $order    = 'ORDER BY menu_order ASC, post_date_gmt ASC, ID ASC LIMIT %d';
602
+        $params[] = $limit;
603
+
604
+        // Run the query and gather results.
605
+        $rows_affected = $wpdb->query( $wpdb->prepare( "{$update} {$where} {$order}", $params ) );
606
+        if ( $rows_affected === false ) {
607
+            throw new RuntimeException( __( 'Unable to claim actions. Database error.', 'action-scheduler' ) );
608
+        }
609
+
610
+        return (int) $rows_affected;
611
+    }
612
+
613
+    /**
614
+     * Get IDs of actions within a certain group and up to a certain date/time.
615
+     *
616
+     * @param string   $group The group to use in finding actions.
617
+     * @param int      $limit The number of actions to retrieve.
618
+     * @param DateTime $date  DateTime object representing cutoff time for actions. Actions retrieved will be
619
+     *                        up to and including this DateTime.
620
+     *
621
+     * @return array IDs of actions in the appropriate group and before the appropriate time.
622
+     * @throws InvalidArgumentException When the group does not exist.
623
+     */
624
+    protected function get_actions_by_group( $group, $limit, DateTime $date ) {
625
+        // Ensure the group exists before continuing.
626
+        if ( ! term_exists( $group, self::GROUP_TAXONOMY )) {
627
+            throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'action-scheduler' ), $group ) );
628
+        }
629
+
630
+        // Set up a query for post IDs to use later.
631
+        $query      = new WP_Query();
632
+        $query_args = array(
633
+            'fields'           => 'ids',
634
+            'post_type'        => self::POST_TYPE,
635
+            'post_status'      => ActionScheduler_Store::STATUS_PENDING,
636
+            'has_password'     => false,
637
+            'posts_per_page'   => $limit * 3,
638
+            'suppress_filters' => true,
639
+            'no_found_rows'    => true,
640
+            'orderby'          => array(
641
+                'menu_order' => 'ASC',
642
+                'date'       => 'ASC',
643
+                'ID'         => 'ASC',
644
+            ),
645
+            'date_query'       => array(
646
+                'column' => 'post_date_gmt',
647
+                'before' => $date->format( 'Y-m-d H:i' ),
648
+                'inclusive' => true,
649
+            ),
650
+            'tax_query' => array(
651
+                array(
652
+                    'taxonomy'         => self::GROUP_TAXONOMY,
653
+                    'field'            => 'slug',
654
+                    'terms'            => $group,
655
+                    'include_children' => false,
656
+                ),
657
+            ),
658
+        );
659
+
660
+        return $query->query( $query_args );
661
+    }
662
+
663
+    /**
664
+     * @param string $claim_id
665
+     * @return array
666
+     */
667
+    public function find_actions_by_claim_id( $claim_id ) {
668
+        /** @var wpdb $wpdb */
669
+        global $wpdb;
670
+        $sql = "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_password = %s";
671
+        $sql = $wpdb->prepare( $sql, array( self::POST_TYPE, $claim_id ) );
672
+        $action_ids = $wpdb->get_col( $sql );
673
+        return $action_ids;
674
+    }
675
+
676
+    public function release_claim( ActionScheduler_ActionClaim $claim ) {
677
+        $action_ids = $this->find_actions_by_claim_id( $claim->get_id() );
678
+        if ( empty( $action_ids ) ) {
679
+            return; // nothing to do
680
+        }
681
+        $action_id_string = implode( ',', array_map( 'intval', $action_ids ) );
682
+        /** @var wpdb $wpdb */
683
+        global $wpdb;
684
+        $sql = "UPDATE {$wpdb->posts} SET post_password = '' WHERE ID IN ($action_id_string) AND post_password = %s";
685
+        $sql = $wpdb->prepare( $sql, array( $claim->get_id() ) );
686
+        $result = $wpdb->query( $sql );
687
+        if ( $result === false ) {
688
+            /* translators: %s: claim ID */
689
+            throw new RuntimeException( sprintf( __( 'Unable to unlock claim %s. Database error.', 'action-scheduler' ), $claim->get_id() ) );
690
+        }
691
+    }
692
+
693
+    /**
694
+     * @param string $action_id
695
+     */
696
+    public function unclaim_action( $action_id ) {
697
+        /** @var wpdb $wpdb */
698
+        global $wpdb;
699
+        $sql = "UPDATE {$wpdb->posts} SET post_password = '' WHERE ID = %d AND post_type = %s";
700
+        $sql = $wpdb->prepare( $sql, $action_id, self::POST_TYPE );
701
+        $result = $wpdb->query( $sql );
702
+        if ( $result === false ) {
703
+            /* translators: %s: action ID */
704
+            throw new RuntimeException( sprintf( __( 'Unable to unlock claim on action %s. Database error.', 'action-scheduler' ), $action_id ) );
705
+        }
706
+    }
707
+
708
+    public function mark_failure( $action_id ) {
709
+        /** @var wpdb $wpdb */
710
+        global $wpdb;
711
+        $sql = "UPDATE {$wpdb->posts} SET post_status = %s WHERE ID = %d AND post_type = %s";
712
+        $sql = $wpdb->prepare( $sql, self::STATUS_FAILED, $action_id, self::POST_TYPE );
713
+        $result = $wpdb->query( $sql );
714
+        if ( $result === false ) {
715
+            /* translators: %s: action ID */
716
+            throw new RuntimeException( sprintf( __( 'Unable to mark failure on action %s. Database error.', 'action-scheduler' ), $action_id ) );
717
+        }
718
+    }
719
+
720
+    /**
721
+     * Return an action's claim ID, as stored in the post password column
722
+     *
723
+     * @param string $action_id
724
+     * @return mixed
725
+     */
726
+    public function get_claim_id( $action_id ) {
727
+        return $this->get_post_column( $action_id, 'post_password' );
728
+    }
729
+
730
+    /**
731
+     * Return an action's status, as stored in the post status column
732
+     *
733
+     * @param string $action_id
734
+     * @return mixed
735
+     */
736
+    public function get_status( $action_id ) {
737
+        $status = $this->get_post_column( $action_id, 'post_status' );
738
+
739
+        if ( $status === null ) {
740
+            throw new InvalidArgumentException( __( 'Invalid action ID. No status found.', 'action-scheduler' ) );
741
+        }
742
+
743
+        return $this->get_action_status_by_post_status( $status );
744
+    }
745
+
746
+    private function get_post_column( $action_id, $column_name ) {
747
+        /** @var \wpdb $wpdb */
748
+        global $wpdb;
749
+        return $wpdb->get_var( $wpdb->prepare( "SELECT {$column_name} FROM {$wpdb->posts} WHERE ID=%d AND post_type=%s", $action_id, self::POST_TYPE ) );
750
+    }
751
+
752
+    /**
753
+     * @param string $action_id
754
+     */
755
+    public function log_execution( $action_id ) {
756
+        /** @var wpdb $wpdb */
757
+        global $wpdb;
758
+
759
+        $sql = "UPDATE {$wpdb->posts} SET menu_order = menu_order+1, post_status=%s, post_modified_gmt = %s, post_modified = %s WHERE ID = %d AND post_type = %s";
760
+        $sql = $wpdb->prepare( $sql, self::STATUS_RUNNING, current_time('mysql', true), current_time('mysql'), $action_id, self::POST_TYPE );
761
+        $wpdb->query($sql);
762
+    }
763
+
764
+    /**
765
+     * Record that an action was completed.
766
+     *
767
+     * @param int $action_id ID of the completed action.
768
+     * @throws InvalidArgumentException|RuntimeException
769
+     */
770
+    public function mark_complete( $action_id ) {
771
+        $post = get_post( $action_id );
772
+        if ( empty( $post ) || ( $post->post_type != self::POST_TYPE ) ) {
773
+            throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'action-scheduler' ), $action_id ) );
774
+        }
775
+        add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
776
+        add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
777
+        $result = wp_update_post(array(
778
+            'ID' => $action_id,
779
+            'post_status' => 'publish',
780
+        ), TRUE);
781
+        remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 );
782
+        remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
783
+        if ( is_wp_error( $result ) ) {
784
+            throw new RuntimeException( $result->get_error_message() );
785
+        }
786
+    }
787
+
788
+    /**
789
+     * Mark action as migrated when there is an error deleting the action.
790
+     *
791
+     * @param int $action_id Action ID.
792
+     */
793
+    public function mark_migrated( $action_id ) {
794
+        wp_update_post(
795
+            array(
796
+                'ID'          => $action_id,
797
+                'post_status' => 'migrated'
798
+            )
799
+        );
800
+    }
801
+
802
+    /**
803
+     * Determine whether the post store can be migrated.
804
+     *
805
+     * @return bool
806
+     */
807
+    public function migration_dependencies_met( $setting ) {
808
+        global $wpdb;
809
+
810
+        $dependencies_met = get_transient( self::DEPENDENCIES_MET );
811
+        if ( empty( $dependencies_met ) ) {
812
+            $maximum_args_length = apply_filters( 'action_scheduler_maximum_args_length', 191 );
813
+            $found_action        = $wpdb->get_var(
814
+                $wpdb->prepare(
815
+                    "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND CHAR_LENGTH(post_content) > %d LIMIT 1",
816
+                    $maximum_args_length,
817
+                    self::POST_TYPE
818
+                )
819
+            );
820
+            $dependencies_met = $found_action ? 'no' : 'yes';
821
+            set_transient( self::DEPENDENCIES_MET, $dependencies_met, DAY_IN_SECONDS );
822
+        }
823
+
824
+        return 'yes' == $dependencies_met ? $setting : false;
825
+    }
826
+
827
+    /**
828
+     * InnoDB indexes have a maximum size of 767 bytes by default, which is only 191 characters with utf8mb4.
829
+     *
830
+     * Previously, AS wasn't concerned about args length, as we used the (unindex) post_content column. However,
831
+     * as we prepare to move to custom tables, and can use an indexed VARCHAR column instead, we want to warn
832
+     * developers of this impending requirement.
833
+     *
834
+     * @param ActionScheduler_Action $action
835
+     */
836
+    protected function validate_action( ActionScheduler_Action $action ) {
837
+        try {
838
+            parent::validate_action( $action );
839
+        } catch ( Exception $e ) {
840
+            $message = sprintf( __( '%s Support for strings longer than this will be removed in a future version.', 'action-scheduler' ), $e->getMessage() );
841
+            _doing_it_wrong( 'ActionScheduler_Action::$args', $message, '2.1.0' );
842
+        }
843
+    }
844
+
845
+    /**
846
+     * @codeCoverageIgnore
847
+     */
848
+    public function init() {
849
+        add_filter( 'action_scheduler_migration_dependencies_met', array( $this, 'migration_dependencies_met' ) );
850
+
851
+        $post_type_registrar = new ActionScheduler_wpPostStore_PostTypeRegistrar();
852
+        $post_type_registrar->register();
853
+
854
+        $post_status_registrar = new ActionScheduler_wpPostStore_PostStatusRegistrar();
855
+        $post_status_registrar->register();
856
+
857
+        $taxonomy_registrar = new ActionScheduler_wpPostStore_TaxonomyRegistrar();
858
+        $taxonomy_registrar->register();
859
+    }
860 860
 }
Please login to merge, or discard this patch.
libraries/action-scheduler/classes/schema/ActionScheduler_LoggerSchema.php 1 patch
Indentation   +21 added lines, -21 removed lines patch added patch discarded remove patch
@@ -8,28 +8,28 @@  discard block
 block discarded – undo
8 8
  * Creates a custom table for storing action logs
9 9
  */
10 10
 class ActionScheduler_LoggerSchema extends ActionScheduler_Abstract_Schema {
11
-	const LOG_TABLE = 'actionscheduler_logs';
11
+    const LOG_TABLE = 'actionscheduler_logs';
12 12
 
13
-	/**
14
-	 * @var int Increment this value to trigger a schema update.
15
-	 */
16
-	protected $schema_version = 2;
13
+    /**
14
+     * @var int Increment this value to trigger a schema update.
15
+     */
16
+    protected $schema_version = 2;
17 17
 
18
-	public function __construct() {
19
-		$this->tables = [
20
-			self::LOG_TABLE,
21
-		];
22
-	}
18
+    public function __construct() {
19
+        $this->tables = [
20
+            self::LOG_TABLE,
21
+        ];
22
+    }
23 23
 
24
-	protected function get_table_definition( $table ) {
25
-		global $wpdb;
26
-		$table_name       = $wpdb->$table;
27
-		$charset_collate  = $wpdb->get_charset_collate();
28
-		switch ( $table ) {
24
+    protected function get_table_definition( $table ) {
25
+        global $wpdb;
26
+        $table_name       = $wpdb->$table;
27
+        $charset_collate  = $wpdb->get_charset_collate();
28
+        switch ( $table ) {
29 29
 
30
-			case self::LOG_TABLE:
30
+            case self::LOG_TABLE:
31 31
 
32
-				return "CREATE TABLE {$table_name} (
32
+                return "CREATE TABLE {$table_name} (
33 33
 				        log_id bigint(20) unsigned NOT NULL auto_increment,
34 34
 				        action_id bigint(20) unsigned NOT NULL,
35 35
 				        message text NOT NULL,
@@ -40,8 +40,8 @@  discard block
 block discarded – undo
40 40
 				        KEY log_date_gmt (log_date_gmt)
41 41
 				        ) $charset_collate";
42 42
 
43
-			default:
44
-				return '';
45
-		}
46
-	}
43
+            default:
44
+                return '';
45
+        }
46
+    }
47 47
 }
Please login to merge, or discard this patch.
libraries/action-scheduler/classes/schema/ActionScheduler_StoreSchema.php 1 patch
Indentation   +30 added lines, -30 removed lines patch added patch discarded remove patch
@@ -8,33 +8,33 @@  discard block
 block discarded – undo
8 8
  * Creates custom tables for storing scheduled actions
9 9
  */
10 10
 class ActionScheduler_StoreSchema extends ActionScheduler_Abstract_Schema {
11
-	const ACTIONS_TABLE = 'actionscheduler_actions';
12
-	const CLAIMS_TABLE  = 'actionscheduler_claims';
13
-	const GROUPS_TABLE  = 'actionscheduler_groups';
11
+    const ACTIONS_TABLE = 'actionscheduler_actions';
12
+    const CLAIMS_TABLE  = 'actionscheduler_claims';
13
+    const GROUPS_TABLE  = 'actionscheduler_groups';
14 14
 
15
-	/**
16
-	 * @var int Increment this value to trigger a schema update.
17
-	 */
18
-	protected $schema_version = 3;
15
+    /**
16
+     * @var int Increment this value to trigger a schema update.
17
+     */
18
+    protected $schema_version = 3;
19 19
 
20
-	public function __construct() {
21
-		$this->tables = [
22
-			self::ACTIONS_TABLE,
23
-			self::CLAIMS_TABLE,
24
-			self::GROUPS_TABLE,
25
-		];
26
-	}
20
+    public function __construct() {
21
+        $this->tables = [
22
+            self::ACTIONS_TABLE,
23
+            self::CLAIMS_TABLE,
24
+            self::GROUPS_TABLE,
25
+        ];
26
+    }
27 27
 
28
-	protected function get_table_definition( $table ) {
29
-		global $wpdb;
30
-		$table_name       = $wpdb->$table;
31
-		$charset_collate  = $wpdb->get_charset_collate();
32
-		$max_index_length = 191; // @see wp_get_db_schema()
33
-		switch ( $table ) {
28
+    protected function get_table_definition( $table ) {
29
+        global $wpdb;
30
+        $table_name       = $wpdb->$table;
31
+        $charset_collate  = $wpdb->get_charset_collate();
32
+        $max_index_length = 191; // @see wp_get_db_schema()
33
+        switch ( $table ) {
34 34
 
35
-			case self::ACTIONS_TABLE:
35
+            case self::ACTIONS_TABLE:
36 36
 
37
-				return "CREATE TABLE {$table_name} (
37
+                return "CREATE TABLE {$table_name} (
38 38
 				        action_id bigint(20) unsigned NOT NULL auto_increment,
39 39
 				        hook varchar(191) NOT NULL,
40 40
 				        status varchar(20) NOT NULL,
@@ -58,26 +58,26 @@  discard block
 block discarded – undo
58 58
 				        KEY claim_id (claim_id)
59 59
 				        ) $charset_collate";
60 60
 
61
-			case self::CLAIMS_TABLE:
61
+            case self::CLAIMS_TABLE:
62 62
 
63
-				return "CREATE TABLE {$table_name} (
63
+                return "CREATE TABLE {$table_name} (
64 64
 				        claim_id bigint(20) unsigned NOT NULL auto_increment,
65 65
 				        date_created_gmt datetime NOT NULL default '0000-00-00 00:00:00',
66 66
 				        PRIMARY KEY  (claim_id),
67 67
 				        KEY date_created_gmt (date_created_gmt)
68 68
 				        ) $charset_collate";
69 69
 
70
-			case self::GROUPS_TABLE:
70
+            case self::GROUPS_TABLE:
71 71
 
72
-				return "CREATE TABLE {$table_name} (
72
+                return "CREATE TABLE {$table_name} (
73 73
 				        group_id bigint(20) unsigned NOT NULL auto_increment,
74 74
 				        slug varchar(255) NOT NULL,
75 75
 				        PRIMARY KEY  (group_id),
76 76
 				        KEY slug (slug($max_index_length))
77 77
 				        ) $charset_collate";
78 78
 
79
-			default:
80
-				return '';
81
-		}
82
-	}
79
+            default:
80
+                return '';
81
+        }
82
+    }
83 83
 }
Please login to merge, or discard this patch.
libraries/action-scheduler/classes/ActionScheduler_NullLogEntry.php 1 patch
Indentation   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -4,8 +4,8 @@
 block discarded – undo
4 4
  * Class ActionScheduler_NullLogEntry
5 5
  */
6 6
 class ActionScheduler_NullLogEntry extends ActionScheduler_LogEntry {
7
-	public function __construct( $action_id = '', $message = '' ) {
8
-		// nothing to see here
9
-	}
7
+    public function __construct( $action_id = '', $message = '' ) {
8
+        // nothing to see here
9
+    }
10 10
 }
11
- 
12 11
\ No newline at end of file
12
+    
13 13
\ No newline at end of file
Please login to merge, or discard this patch.
libraries/action-scheduler/classes/ActionScheduler_QueueCleaner.php 1 patch
Indentation   +147 added lines, -147 removed lines patch added patch discarded remove patch
@@ -5,151 +5,151 @@
 block discarded – undo
5 5
  */
6 6
 class ActionScheduler_QueueCleaner {
7 7
 
8
-	/** @var int */
9
-	protected $batch_size;
10
-
11
-	/** @var ActionScheduler_Store */
12
-	private $store = null;
13
-
14
-	/**
15
-	 * 31 days in seconds.
16
-	 *
17
-	 * @var int
18
-	 */
19
-	private $month_in_seconds = 2678400;
20
-
21
-	/**
22
-	 * ActionScheduler_QueueCleaner constructor.
23
-	 *
24
-	 * @param ActionScheduler_Store $store      The store instance.
25
-	 * @param int                   $batch_size The batch size.
26
-	 */
27
-	public function __construct( ActionScheduler_Store $store = null, $batch_size = 20 ) {
28
-		$this->store = $store ? $store : ActionScheduler_Store::instance();
29
-		$this->batch_size = $batch_size;
30
-	}
31
-
32
-	public function delete_old_actions() {
33
-		$lifespan = apply_filters( 'action_scheduler_retention_period', $this->month_in_seconds );
34
-		$cutoff = as_get_datetime_object($lifespan.' seconds ago');
35
-
36
-		$statuses_to_purge = array(
37
-			ActionScheduler_Store::STATUS_COMPLETE,
38
-			ActionScheduler_Store::STATUS_CANCELED,
39
-		);
40
-
41
-		foreach ( $statuses_to_purge as $status ) {
42
-			$actions_to_delete = $this->store->query_actions( array(
43
-				'status'           => $status,
44
-				'modified'         => $cutoff,
45
-				'modified_compare' => '<=',
46
-				'per_page'         => $this->get_batch_size(),
47
-			) );
48
-
49
-			foreach ( $actions_to_delete as $action_id ) {
50
-				try {
51
-					$this->store->delete_action( $action_id );
52
-				} catch ( Exception $e ) {
53
-
54
-					/**
55
-					 * Notify 3rd party code of exceptions when deleting a completed action older than the retention period
56
-					 *
57
-					 * This hook provides a way for 3rd party code to log or otherwise handle exceptions relating to their
58
-					 * actions.
59
-					 *
60
-					 * @since 2.0.0
61
-					 *
62
-					 * @param int $action_id The scheduled actions ID in the data store
63
-					 * @param Exception $e The exception thrown when attempting to delete the action from the data store
64
-					 * @param int $lifespan The retention period, in seconds, for old actions
65
-					 * @param int $count_of_actions_to_delete The number of old actions being deleted in this batch
66
-					 */
67
-					do_action( 'action_scheduler_failed_old_action_deletion', $action_id, $e, $lifespan, count( $actions_to_delete ) );
68
-				}
69
-			}
70
-		}
71
-	}
72
-
73
-	/**
74
-	 * Unclaim pending actions that have not been run within a given time limit.
75
-	 *
76
-	 * When called by ActionScheduler_Abstract_QueueRunner::run_cleanup(), the time limit passed
77
-	 * as a parameter is 10x the time limit used for queue processing.
78
-	 *
79
-	 * @param int $time_limit The number of seconds to allow a queue to run before unclaiming its pending actions. Default 300 (5 minutes).
80
-	 */
81
-	public function reset_timeouts( $time_limit = 300 ) {
82
-		$timeout = apply_filters( 'action_scheduler_timeout_period', $time_limit );
83
-		if ( $timeout < 0 ) {
84
-			return;
85
-		}
86
-		$cutoff = as_get_datetime_object($timeout.' seconds ago');
87
-		$actions_to_reset = $this->store->query_actions( array(
88
-			'status'           => ActionScheduler_Store::STATUS_PENDING,
89
-			'modified'         => $cutoff,
90
-			'modified_compare' => '<=',
91
-			'claimed'          => true,
92
-			'per_page'         => $this->get_batch_size(),
93
-		) );
94
-
95
-		foreach ( $actions_to_reset as $action_id ) {
96
-			$this->store->unclaim_action( $action_id );
97
-			do_action( 'action_scheduler_reset_action', $action_id );
98
-		}
99
-	}
100
-
101
-	/**
102
-	 * Mark actions that have been running for more than a given time limit as failed, based on
103
-	 * the assumption some uncatachable and unloggable fatal error occurred during processing.
104
-	 *
105
-	 * When called by ActionScheduler_Abstract_QueueRunner::run_cleanup(), the time limit passed
106
-	 * as a parameter is 10x the time limit used for queue processing.
107
-	 *
108
-	 * @param int $time_limit The number of seconds to allow an action to run before it is considered to have failed. Default 300 (5 minutes).
109
-	 */
110
-	public function mark_failures( $time_limit = 300 ) {
111
-		$timeout = apply_filters( 'action_scheduler_failure_period', $time_limit );
112
-		if ( $timeout < 0 ) {
113
-			return;
114
-		}
115
-		$cutoff = as_get_datetime_object($timeout.' seconds ago');
116
-		$actions_to_reset = $this->store->query_actions( array(
117
-			'status'           => ActionScheduler_Store::STATUS_RUNNING,
118
-			'modified'         => $cutoff,
119
-			'modified_compare' => '<=',
120
-			'per_page'         => $this->get_batch_size(),
121
-		) );
122
-
123
-		foreach ( $actions_to_reset as $action_id ) {
124
-			$this->store->mark_failure( $action_id );
125
-			do_action( 'action_scheduler_failed_action', $action_id, $timeout );
126
-		}
127
-	}
128
-
129
-	/**
130
-	 * Do all of the cleaning actions.
131
-	 *
132
-	 * @param int $time_limit The number of seconds to use as the timeout and failure period. Default 300 (5 minutes).
133
-	 * @author Jeremy Pry
134
-	 */
135
-	public function clean( $time_limit = 300 ) {
136
-		$this->delete_old_actions();
137
-		$this->reset_timeouts( $time_limit );
138
-		$this->mark_failures( $time_limit );
139
-	}
140
-
141
-	/**
142
-	 * Get the batch size for cleaning the queue.
143
-	 *
144
-	 * @author Jeremy Pry
145
-	 * @return int
146
-	 */
147
-	protected function get_batch_size() {
148
-		/**
149
-		 * Filter the batch size when cleaning the queue.
150
-		 *
151
-		 * @param int $batch_size The number of actions to clean in one batch.
152
-		 */
153
-		return absint( apply_filters( 'action_scheduler_cleanup_batch_size', $this->batch_size ) );
154
-	}
8
+    /** @var int */
9
+    protected $batch_size;
10
+
11
+    /** @var ActionScheduler_Store */
12
+    private $store = null;
13
+
14
+    /**
15
+     * 31 days in seconds.
16
+     *
17
+     * @var int
18
+     */
19
+    private $month_in_seconds = 2678400;
20
+
21
+    /**
22
+     * ActionScheduler_QueueCleaner constructor.
23
+     *
24
+     * @param ActionScheduler_Store $store      The store instance.
25
+     * @param int                   $batch_size The batch size.
26
+     */
27
+    public function __construct( ActionScheduler_Store $store = null, $batch_size = 20 ) {
28
+        $this->store = $store ? $store : ActionScheduler_Store::instance();
29
+        $this->batch_size = $batch_size;
30
+    }
31
+
32
+    public function delete_old_actions() {
33
+        $lifespan = apply_filters( 'action_scheduler_retention_period', $this->month_in_seconds );
34
+        $cutoff = as_get_datetime_object($lifespan.' seconds ago');
35
+
36
+        $statuses_to_purge = array(
37
+            ActionScheduler_Store::STATUS_COMPLETE,
38
+            ActionScheduler_Store::STATUS_CANCELED,
39
+        );
40
+
41
+        foreach ( $statuses_to_purge as $status ) {
42
+            $actions_to_delete = $this->store->query_actions( array(
43
+                'status'           => $status,
44
+                'modified'         => $cutoff,
45
+                'modified_compare' => '<=',
46
+                'per_page'         => $this->get_batch_size(),
47
+            ) );
48
+
49
+            foreach ( $actions_to_delete as $action_id ) {
50
+                try {
51
+                    $this->store->delete_action( $action_id );
52
+                } catch ( Exception $e ) {
53
+
54
+                    /**
55
+                     * Notify 3rd party code of exceptions when deleting a completed action older than the retention period
56
+                     *
57
+                     * This hook provides a way for 3rd party code to log or otherwise handle exceptions relating to their
58
+                     * actions.
59
+                     *
60
+                     * @since 2.0.0
61
+                     *
62
+                     * @param int $action_id The scheduled actions ID in the data store
63
+                     * @param Exception $e The exception thrown when attempting to delete the action from the data store
64
+                     * @param int $lifespan The retention period, in seconds, for old actions
65
+                     * @param int $count_of_actions_to_delete The number of old actions being deleted in this batch
66
+                     */
67
+                    do_action( 'action_scheduler_failed_old_action_deletion', $action_id, $e, $lifespan, count( $actions_to_delete ) );
68
+                }
69
+            }
70
+        }
71
+    }
72
+
73
+    /**
74
+     * Unclaim pending actions that have not been run within a given time limit.
75
+     *
76
+     * When called by ActionScheduler_Abstract_QueueRunner::run_cleanup(), the time limit passed
77
+     * as a parameter is 10x the time limit used for queue processing.
78
+     *
79
+     * @param int $time_limit The number of seconds to allow a queue to run before unclaiming its pending actions. Default 300 (5 minutes).
80
+     */
81
+    public function reset_timeouts( $time_limit = 300 ) {
82
+        $timeout = apply_filters( 'action_scheduler_timeout_period', $time_limit );
83
+        if ( $timeout < 0 ) {
84
+            return;
85
+        }
86
+        $cutoff = as_get_datetime_object($timeout.' seconds ago');
87
+        $actions_to_reset = $this->store->query_actions( array(
88
+            'status'           => ActionScheduler_Store::STATUS_PENDING,
89
+            'modified'         => $cutoff,
90
+            'modified_compare' => '<=',
91
+            'claimed'          => true,
92
+            'per_page'         => $this->get_batch_size(),
93
+        ) );
94
+
95
+        foreach ( $actions_to_reset as $action_id ) {
96
+            $this->store->unclaim_action( $action_id );
97
+            do_action( 'action_scheduler_reset_action', $action_id );
98
+        }
99
+    }
100
+
101
+    /**
102
+     * Mark actions that have been running for more than a given time limit as failed, based on
103
+     * the assumption some uncatachable and unloggable fatal error occurred during processing.
104
+     *
105
+     * When called by ActionScheduler_Abstract_QueueRunner::run_cleanup(), the time limit passed
106
+     * as a parameter is 10x the time limit used for queue processing.
107
+     *
108
+     * @param int $time_limit The number of seconds to allow an action to run before it is considered to have failed. Default 300 (5 minutes).
109
+     */
110
+    public function mark_failures( $time_limit = 300 ) {
111
+        $timeout = apply_filters( 'action_scheduler_failure_period', $time_limit );
112
+        if ( $timeout < 0 ) {
113
+            return;
114
+        }
115
+        $cutoff = as_get_datetime_object($timeout.' seconds ago');
116
+        $actions_to_reset = $this->store->query_actions( array(
117
+            'status'           => ActionScheduler_Store::STATUS_RUNNING,
118
+            'modified'         => $cutoff,
119
+            'modified_compare' => '<=',
120
+            'per_page'         => $this->get_batch_size(),
121
+        ) );
122
+
123
+        foreach ( $actions_to_reset as $action_id ) {
124
+            $this->store->mark_failure( $action_id );
125
+            do_action( 'action_scheduler_failed_action', $action_id, $timeout );
126
+        }
127
+    }
128
+
129
+    /**
130
+     * Do all of the cleaning actions.
131
+     *
132
+     * @param int $time_limit The number of seconds to use as the timeout and failure period. Default 300 (5 minutes).
133
+     * @author Jeremy Pry
134
+     */
135
+    public function clean( $time_limit = 300 ) {
136
+        $this->delete_old_actions();
137
+        $this->reset_timeouts( $time_limit );
138
+        $this->mark_failures( $time_limit );
139
+    }
140
+
141
+    /**
142
+     * Get the batch size for cleaning the queue.
143
+     *
144
+     * @author Jeremy Pry
145
+     * @return int
146
+     */
147
+    protected function get_batch_size() {
148
+        /**
149
+         * Filter the batch size when cleaning the queue.
150
+         *
151
+         * @param int $batch_size The number of actions to clean in one batch.
152
+         */
153
+        return absint( apply_filters( 'action_scheduler_cleanup_batch_size', $this->batch_size ) );
154
+    }
155 155
 }
Please login to merge, or discard this patch.
includes/libraries/action-scheduler/classes/ActionScheduler_OptionLock.php 1 patch
Indentation   +35 added lines, -35 removed lines patch added patch discarded remove patch
@@ -9,41 +9,41 @@
 block discarded – undo
9 9
  */
10 10
 class ActionScheduler_OptionLock extends ActionScheduler_Lock {
11 11
 
12
-	/**
13
-	 * Set a lock using options for a given amount of time (60 seconds by default).
14
-	 *
15
-	 * Using an autoloaded option avoids running database queries or other resource intensive tasks
16
-	 * on frequently triggered hooks, like 'init' or 'shutdown'.
17
-	 *
18
-	 * For example, ActionScheduler_QueueRunner->maybe_dispatch_async_request() uses a lock to avoid
19
-	 * calling ActionScheduler_QueueRunner->has_maximum_concurrent_batches() every time the 'shutdown',
20
-	 * hook is triggered, because that method calls ActionScheduler_QueueRunner->store->get_claim_count()
21
-	 * to find the current number of claims in the database.
22
-	 *
23
-	 * @param string $lock_type A string to identify different lock types.
24
-	 * @bool True if lock value has changed, false if not or if set failed.
25
-	 */
26
-	public function set( $lock_type ) {
27
-		return update_option( $this->get_key( $lock_type ), time() + $this->get_duration( $lock_type ) );
28
-	}
12
+    /**
13
+     * Set a lock using options for a given amount of time (60 seconds by default).
14
+     *
15
+     * Using an autoloaded option avoids running database queries or other resource intensive tasks
16
+     * on frequently triggered hooks, like 'init' or 'shutdown'.
17
+     *
18
+     * For example, ActionScheduler_QueueRunner->maybe_dispatch_async_request() uses a lock to avoid
19
+     * calling ActionScheduler_QueueRunner->has_maximum_concurrent_batches() every time the 'shutdown',
20
+     * hook is triggered, because that method calls ActionScheduler_QueueRunner->store->get_claim_count()
21
+     * to find the current number of claims in the database.
22
+     *
23
+     * @param string $lock_type A string to identify different lock types.
24
+     * @bool True if lock value has changed, false if not or if set failed.
25
+     */
26
+    public function set( $lock_type ) {
27
+        return update_option( $this->get_key( $lock_type ), time() + $this->get_duration( $lock_type ) );
28
+    }
29 29
 
30
-	/**
31
-	 * If a lock is set, return the timestamp it was set to expiry.
32
-	 *
33
-	 * @param string $lock_type A string to identify different lock types.
34
-	 * @return bool|int False if no lock is set, otherwise the timestamp for when the lock is set to expire.
35
-	 */
36
-	public function get_expiration( $lock_type ) {
37
-		return get_option( $this->get_key( $lock_type ) );
38
-	}
30
+    /**
31
+     * If a lock is set, return the timestamp it was set to expiry.
32
+     *
33
+     * @param string $lock_type A string to identify different lock types.
34
+     * @return bool|int False if no lock is set, otherwise the timestamp for when the lock is set to expire.
35
+     */
36
+    public function get_expiration( $lock_type ) {
37
+        return get_option( $this->get_key( $lock_type ) );
38
+    }
39 39
 
40
-	/**
41
-	 * Get the key to use for storing the lock in the transient
42
-	 *
43
-	 * @param string $lock_type A string to identify different lock types.
44
-	 * @return string
45
-	 */
46
-	protected function get_key( $lock_type ) {
47
-		return sprintf( 'action_scheduler_lock_%s', $lock_type );
48
-	}
40
+    /**
41
+     * Get the key to use for storing the lock in the transient
42
+     *
43
+     * @param string $lock_type A string to identify different lock types.
44
+     * @return string
45
+     */
46
+    protected function get_key( $lock_type ) {
47
+        return sprintf( 'action_scheduler_lock_%s', $lock_type );
48
+    }
49 49
 }
Please login to merge, or discard this patch.
includes/libraries/action-scheduler/classes/ActionScheduler_ActionClaim.php 1 patch
Indentation   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -4,20 +4,20 @@
 block discarded – undo
4 4
  * Class ActionScheduler_ActionClaim
5 5
  */
6 6
 class ActionScheduler_ActionClaim {
7
-	private $id = '';
8
-	private $action_ids = array();
7
+    private $id = '';
8
+    private $action_ids = array();
9 9
 
10
-	public function __construct( $id, array $action_ids ) {
11
-		$this->id = $id;
12
-		$this->action_ids = $action_ids;
13
-	}
10
+    public function __construct( $id, array $action_ids ) {
11
+        $this->id = $id;
12
+        $this->action_ids = $action_ids;
13
+    }
14 14
 
15
-	public function get_id() {
16
-		return $this->id;
17
-	}
15
+    public function get_id() {
16
+        return $this->id;
17
+    }
18 18
 
19
-	public function get_actions() {
20
-		return $this->action_ids;
21
-	}
19
+    public function get_actions() {
20
+        return $this->action_ids;
21
+    }
22 22
 }
23
- 
24 23
\ No newline at end of file
24
+    
25 25
\ No newline at end of file
Please login to merge, or discard this patch.
includes/libraries/action-scheduler/classes/ActionScheduler_QueueRunner.php 1 patch
Indentation   +191 added lines, -191 removed lines patch added patch discarded remove patch
@@ -4,195 +4,195 @@
 block discarded – undo
4 4
  * Class ActionScheduler_QueueRunner
5 5
  */
6 6
 class ActionScheduler_QueueRunner extends ActionScheduler_Abstract_QueueRunner {
7
-	const WP_CRON_HOOK = 'action_scheduler_run_queue';
8
-
9
-	const WP_CRON_SCHEDULE = 'every_minute';
10
-
11
-	/** @var ActionScheduler_AsyncRequest_QueueRunner */
12
-	protected $async_request;
13
-
14
-	/** @var ActionScheduler_QueueRunner  */
15
-	private static $runner = null;
16
-
17
-	/**
18
-	 * @return ActionScheduler_QueueRunner
19
-	 * @codeCoverageIgnore
20
-	 */
21
-	public static function instance() {
22
-		if ( empty(self::$runner) ) {
23
-			$class = apply_filters('action_scheduler_queue_runner_class', 'ActionScheduler_QueueRunner');
24
-			self::$runner = new $class();
25
-		}
26
-		return self::$runner;
27
-	}
28
-
29
-	/**
30
-	 * ActionScheduler_QueueRunner constructor.
31
-	 *
32
-	 * @param ActionScheduler_Store             $store
33
-	 * @param ActionScheduler_FatalErrorMonitor $monitor
34
-	 * @param ActionScheduler_QueueCleaner      $cleaner
35
-	 */
36
-	public function __construct( ActionScheduler_Store $store = null, ActionScheduler_FatalErrorMonitor $monitor = null, ActionScheduler_QueueCleaner $cleaner = null, ActionScheduler_AsyncRequest_QueueRunner $async_request = null ) {
37
-		parent::__construct( $store, $monitor, $cleaner );
38
-
39
-		if ( is_null( $async_request ) ) {
40
-			$async_request = new ActionScheduler_AsyncRequest_QueueRunner( $this->store );
41
-		}
42
-
43
-		$this->async_request = $async_request;
44
-	}
45
-
46
-	/**
47
-	 * @codeCoverageIgnore
48
-	 */
49
-	public function init() {
50
-
51
-		add_filter( 'cron_schedules', array( self::instance(), 'add_wp_cron_schedule' ) );
52
-
53
-		$cron_context = array( 'WP Cron' );
54
-
55
-		if ( ! wp_next_scheduled( self::WP_CRON_HOOK, $cron_context ) ) {
56
-
57
-			// Check for and remove any WP Cron hook scheduled by Action Scheduler < 3.0.0, which didn't include the $context param
58
-			$next_timestamp = wp_next_scheduled( self::WP_CRON_HOOK );
59
-			if ( $next_timestamp ) {
60
-				wp_unschedule_event( $next_timestamp, self::WP_CRON_HOOK );
61
-			}
62
-
63
-			$schedule = apply_filters( 'action_scheduler_run_schedule', self::WP_CRON_SCHEDULE );
64
-			wp_schedule_event( time(), $schedule, self::WP_CRON_HOOK, $cron_context );
65
-		}
66
-
67
-		add_action( self::WP_CRON_HOOK, array( self::instance(), 'run' ) );
68
-		$this->hook_dispatch_async_request();
69
-	}
70
-
71
-	/**
72
-	 * Hook check for dispatching an async request.
73
-	 */
74
-	public function hook_dispatch_async_request() {
75
-		add_action( 'shutdown', array( $this, 'maybe_dispatch_async_request' ) );
76
-	}
77
-
78
-	/**
79
-	 * Unhook check for dispatching an async request.
80
-	 */
81
-	public function unhook_dispatch_async_request() {
82
-		remove_action( 'shutdown', array( $this, 'maybe_dispatch_async_request' ) );
83
-	}
84
-
85
-	/**
86
-	 * Check if we should dispatch an async request to process actions.
87
-	 *
88
-	 * This method is attached to 'shutdown', so is called frequently. To avoid slowing down
89
-	 * the site, it mitigates the work performed in each request by:
90
-	 * 1. checking if it's in the admin context and then
91
-	 * 2. haven't run on the 'shutdown' hook within the lock time (60 seconds by default)
92
-	 * 3. haven't exceeded the number of allowed batches.
93
-	 *
94
-	 * The order of these checks is important, because they run from a check on a value:
95
-	 * 1. in memory - is_admin() maps to $GLOBALS or the WP_ADMIN constant
96
-	 * 2. in memory - transients use autoloaded options by default
97
-	 * 3. from a database query - has_maximum_concurrent_batches() run the query
98
-	 *    $this->store->get_claim_count() to find the current number of claims in the DB.
99
-	 *
100
-	 * If all of these conditions are met, then we request an async runner check whether it
101
-	 * should dispatch a request to process pending actions.
102
-	 */
103
-	public function maybe_dispatch_async_request() {
104
-		if ( is_admin() && ! ActionScheduler::lock()->is_locked( 'async-request-runner' ) ) {
105
-			// Only start an async queue at most once every 60 seconds
106
-			ActionScheduler::lock()->set( 'async-request-runner' );
107
-			$this->async_request->maybe_dispatch();
108
-		}
109
-	}
110
-
111
-	/**
112
-	 * Process actions in the queue. Attached to self::WP_CRON_HOOK i.e. 'action_scheduler_run_queue'
113
-	 *
114
-	 * The $context param of this method defaults to 'WP Cron', because prior to Action Scheduler 3.0.0
115
-	 * that was the only context in which this method was run, and the self::WP_CRON_HOOK hook had no context
116
-	 * passed along with it. New code calling this method directly, or by triggering the self::WP_CRON_HOOK,
117
-	 * should set a context as the first parameter. For an example of this, refer to the code seen in
118
-	 * @see ActionScheduler_AsyncRequest_QueueRunner::handle()
119
-	 *
120
-	 * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
121
-	 *        Generally, this should be capitalised and not localised as it's a proper noun.
122
-	 * @return int The number of actions processed.
123
-	 */
124
-	public function run( $context = 'WP Cron' ) {
125
-		ActionScheduler_Compatibility::raise_memory_limit();
126
-		ActionScheduler_Compatibility::raise_time_limit( $this->get_time_limit() );
127
-		do_action( 'action_scheduler_before_process_queue' );
128
-		$this->run_cleanup();
129
-		$processed_actions = 0;
130
-		if ( false === $this->has_maximum_concurrent_batches() ) {
131
-			$batch_size = apply_filters( 'action_scheduler_queue_runner_batch_size', 25 );
132
-			do {
133
-				$processed_actions_in_batch = $this->do_batch( $batch_size, $context );
134
-				$processed_actions         += $processed_actions_in_batch;
135
-			} while ( $processed_actions_in_batch > 0 && ! $this->batch_limits_exceeded( $processed_actions ) ); // keep going until we run out of actions, time, or memory
136
-		}
137
-
138
-		do_action( 'action_scheduler_after_process_queue' );
139
-		return $processed_actions;
140
-	}
141
-
142
-	/**
143
-	 * Process a batch of actions pending in the queue.
144
-	 *
145
-	 * Actions are processed by claiming a set of pending actions then processing each one until either the batch
146
-	 * size is completed, or memory or time limits are reached, defined by @see $this->batch_limits_exceeded().
147
-	 *
148
-	 * @param int $size The maximum number of actions to process in the batch.
149
-	 * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
150
-	 *        Generally, this should be capitalised and not localised as it's a proper noun.
151
-	 * @return int The number of actions processed.
152
-	 */
153
-	protected function do_batch( $size = 100, $context = '' ) {
154
-		$claim = $this->store->stake_claim($size);
155
-		$this->monitor->attach($claim);
156
-		$processed_actions = 0;
157
-
158
-		foreach ( $claim->get_actions() as $action_id ) {
159
-			// bail if we lost the claim
160
-			if ( ! in_array( $action_id, $this->store->find_actions_by_claim_id( $claim->get_id() ) ) ) {
161
-				break;
162
-			}
163
-			$this->process_action( $action_id, $context );
164
-			$processed_actions++;
165
-
166
-			if ( $this->batch_limits_exceeded( $processed_actions ) ) {
167
-				break;
168
-			}
169
-		}
170
-		$this->store->release_claim($claim);
171
-		$this->monitor->detach();
172
-		$this->clear_caches();
173
-		return $processed_actions;
174
-	}
175
-
176
-	/**
177
-	 * Running large batches can eat up memory, as WP adds data to its object cache.
178
-	 *
179
-	 * If using a persistent object store, this has the side effect of flushing that
180
-	 * as well, so this is disabled by default. To enable:
181
-	 *
182
-	 * add_filter( 'action_scheduler_queue_runner_flush_cache', '__return_true' );
183
-	 */
184
-	protected function clear_caches() {
185
-		if ( ! wp_using_ext_object_cache() || apply_filters( 'action_scheduler_queue_runner_flush_cache', false ) ) {
186
-			wp_cache_flush();
187
-		}
188
-	}
189
-
190
-	public function add_wp_cron_schedule( $schedules ) {
191
-		$schedules['every_minute'] = array(
192
-			'interval' => 60, // in seconds
193
-			'display'  => __( 'Every minute', 'action-scheduler' ),
194
-		);
195
-
196
-		return $schedules;
197
-	}
7
+    const WP_CRON_HOOK = 'action_scheduler_run_queue';
8
+
9
+    const WP_CRON_SCHEDULE = 'every_minute';
10
+
11
+    /** @var ActionScheduler_AsyncRequest_QueueRunner */
12
+    protected $async_request;
13
+
14
+    /** @var ActionScheduler_QueueRunner  */
15
+    private static $runner = null;
16
+
17
+    /**
18
+     * @return ActionScheduler_QueueRunner
19
+     * @codeCoverageIgnore
20
+     */
21
+    public static function instance() {
22
+        if ( empty(self::$runner) ) {
23
+            $class = apply_filters('action_scheduler_queue_runner_class', 'ActionScheduler_QueueRunner');
24
+            self::$runner = new $class();
25
+        }
26
+        return self::$runner;
27
+    }
28
+
29
+    /**
30
+     * ActionScheduler_QueueRunner constructor.
31
+     *
32
+     * @param ActionScheduler_Store             $store
33
+     * @param ActionScheduler_FatalErrorMonitor $monitor
34
+     * @param ActionScheduler_QueueCleaner      $cleaner
35
+     */
36
+    public function __construct( ActionScheduler_Store $store = null, ActionScheduler_FatalErrorMonitor $monitor = null, ActionScheduler_QueueCleaner $cleaner = null, ActionScheduler_AsyncRequest_QueueRunner $async_request = null ) {
37
+        parent::__construct( $store, $monitor, $cleaner );
38
+
39
+        if ( is_null( $async_request ) ) {
40
+            $async_request = new ActionScheduler_AsyncRequest_QueueRunner( $this->store );
41
+        }
42
+
43
+        $this->async_request = $async_request;
44
+    }
45
+
46
+    /**
47
+     * @codeCoverageIgnore
48
+     */
49
+    public function init() {
50
+
51
+        add_filter( 'cron_schedules', array( self::instance(), 'add_wp_cron_schedule' ) );
52
+
53
+        $cron_context = array( 'WP Cron' );
54
+
55
+        if ( ! wp_next_scheduled( self::WP_CRON_HOOK, $cron_context ) ) {
56
+
57
+            // Check for and remove any WP Cron hook scheduled by Action Scheduler < 3.0.0, which didn't include the $context param
58
+            $next_timestamp = wp_next_scheduled( self::WP_CRON_HOOK );
59
+            if ( $next_timestamp ) {
60
+                wp_unschedule_event( $next_timestamp, self::WP_CRON_HOOK );
61
+            }
62
+
63
+            $schedule = apply_filters( 'action_scheduler_run_schedule', self::WP_CRON_SCHEDULE );
64
+            wp_schedule_event( time(), $schedule, self::WP_CRON_HOOK, $cron_context );
65
+        }
66
+
67
+        add_action( self::WP_CRON_HOOK, array( self::instance(), 'run' ) );
68
+        $this->hook_dispatch_async_request();
69
+    }
70
+
71
+    /**
72
+     * Hook check for dispatching an async request.
73
+     */
74
+    public function hook_dispatch_async_request() {
75
+        add_action( 'shutdown', array( $this, 'maybe_dispatch_async_request' ) );
76
+    }
77
+
78
+    /**
79
+     * Unhook check for dispatching an async request.
80
+     */
81
+    public function unhook_dispatch_async_request() {
82
+        remove_action( 'shutdown', array( $this, 'maybe_dispatch_async_request' ) );
83
+    }
84
+
85
+    /**
86
+     * Check if we should dispatch an async request to process actions.
87
+     *
88
+     * This method is attached to 'shutdown', so is called frequently. To avoid slowing down
89
+     * the site, it mitigates the work performed in each request by:
90
+     * 1. checking if it's in the admin context and then
91
+     * 2. haven't run on the 'shutdown' hook within the lock time (60 seconds by default)
92
+     * 3. haven't exceeded the number of allowed batches.
93
+     *
94
+     * The order of these checks is important, because they run from a check on a value:
95
+     * 1. in memory - is_admin() maps to $GLOBALS or the WP_ADMIN constant
96
+     * 2. in memory - transients use autoloaded options by default
97
+     * 3. from a database query - has_maximum_concurrent_batches() run the query
98
+     *    $this->store->get_claim_count() to find the current number of claims in the DB.
99
+     *
100
+     * If all of these conditions are met, then we request an async runner check whether it
101
+     * should dispatch a request to process pending actions.
102
+     */
103
+    public function maybe_dispatch_async_request() {
104
+        if ( is_admin() && ! ActionScheduler::lock()->is_locked( 'async-request-runner' ) ) {
105
+            // Only start an async queue at most once every 60 seconds
106
+            ActionScheduler::lock()->set( 'async-request-runner' );
107
+            $this->async_request->maybe_dispatch();
108
+        }
109
+    }
110
+
111
+    /**
112
+     * Process actions in the queue. Attached to self::WP_CRON_HOOK i.e. 'action_scheduler_run_queue'
113
+     *
114
+     * The $context param of this method defaults to 'WP Cron', because prior to Action Scheduler 3.0.0
115
+     * that was the only context in which this method was run, and the self::WP_CRON_HOOK hook had no context
116
+     * passed along with it. New code calling this method directly, or by triggering the self::WP_CRON_HOOK,
117
+     * should set a context as the first parameter. For an example of this, refer to the code seen in
118
+     * @see ActionScheduler_AsyncRequest_QueueRunner::handle()
119
+     *
120
+     * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
121
+     *        Generally, this should be capitalised and not localised as it's a proper noun.
122
+     * @return int The number of actions processed.
123
+     */
124
+    public function run( $context = 'WP Cron' ) {
125
+        ActionScheduler_Compatibility::raise_memory_limit();
126
+        ActionScheduler_Compatibility::raise_time_limit( $this->get_time_limit() );
127
+        do_action( 'action_scheduler_before_process_queue' );
128
+        $this->run_cleanup();
129
+        $processed_actions = 0;
130
+        if ( false === $this->has_maximum_concurrent_batches() ) {
131
+            $batch_size = apply_filters( 'action_scheduler_queue_runner_batch_size', 25 );
132
+            do {
133
+                $processed_actions_in_batch = $this->do_batch( $batch_size, $context );
134
+                $processed_actions         += $processed_actions_in_batch;
135
+            } while ( $processed_actions_in_batch > 0 && ! $this->batch_limits_exceeded( $processed_actions ) ); // keep going until we run out of actions, time, or memory
136
+        }
137
+
138
+        do_action( 'action_scheduler_after_process_queue' );
139
+        return $processed_actions;
140
+    }
141
+
142
+    /**
143
+     * Process a batch of actions pending in the queue.
144
+     *
145
+     * Actions are processed by claiming a set of pending actions then processing each one until either the batch
146
+     * size is completed, or memory or time limits are reached, defined by @see $this->batch_limits_exceeded().
147
+     *
148
+     * @param int $size The maximum number of actions to process in the batch.
149
+     * @param string $context Optional identifer for the context in which this action is being processed, e.g. 'WP CLI' or 'WP Cron'
150
+     *        Generally, this should be capitalised and not localised as it's a proper noun.
151
+     * @return int The number of actions processed.
152
+     */
153
+    protected function do_batch( $size = 100, $context = '' ) {
154
+        $claim = $this->store->stake_claim($size);
155
+        $this->monitor->attach($claim);
156
+        $processed_actions = 0;
157
+
158
+        foreach ( $claim->get_actions() as $action_id ) {
159
+            // bail if we lost the claim
160
+            if ( ! in_array( $action_id, $this->store->find_actions_by_claim_id( $claim->get_id() ) ) ) {
161
+                break;
162
+            }
163
+            $this->process_action( $action_id, $context );
164
+            $processed_actions++;
165
+
166
+            if ( $this->batch_limits_exceeded( $processed_actions ) ) {
167
+                break;
168
+            }
169
+        }
170
+        $this->store->release_claim($claim);
171
+        $this->monitor->detach();
172
+        $this->clear_caches();
173
+        return $processed_actions;
174
+    }
175
+
176
+    /**
177
+     * Running large batches can eat up memory, as WP adds data to its object cache.
178
+     *
179
+     * If using a persistent object store, this has the side effect of flushing that
180
+     * as well, so this is disabled by default. To enable:
181
+     *
182
+     * add_filter( 'action_scheduler_queue_runner_flush_cache', '__return_true' );
183
+     */
184
+    protected function clear_caches() {
185
+        if ( ! wp_using_ext_object_cache() || apply_filters( 'action_scheduler_queue_runner_flush_cache', false ) ) {
186
+            wp_cache_flush();
187
+        }
188
+    }
189
+
190
+    public function add_wp_cron_schedule( $schedules ) {
191
+        $schedules['every_minute'] = array(
192
+            'interval' => 60, // in seconds
193
+            'display'  => __( 'Every minute', 'action-scheduler' ),
194
+        );
195
+
196
+        return $schedules;
197
+    }
198 198
 }
Please login to merge, or discard this patch.