PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.36.0
MailPoet – Newsletters, Email Marketing, and Automation v5.36.0
5.36.0 5.35.1 5.35.0 5.34.3 5.34.2 5.34.1 5.34.0 5.33.1 5.33.0 5.32.0 5.31.0 5.30.0 5.29.0 5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 4.44.0 4.44.1 4.45.0 4.46.0 4.47.0 4.48.0 4.48.1 4.48.2 4.49.0 4.49.1 4.5.0 4.5.1 4.5.2 4.50.0 4.50.1 4.51.0 4.51.1 4.51.2 4.52.0 4.53.0 4.54.0 4.55.0 4.56.0 4.57.0 4.58.0 4.58.1 4.58.2 4.6.0 4.6.1 4.6.2 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 5.0.0 5.0.1 5.0.2 5.1.0 5.1.1 5.10.0 5.10.1 5.11.0 5.12.0 5.12.1 5.12.10 5.12.11 5.12.12 5.12.13 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13.0 5.13.1 5.13.2 5.14.0 5.14.1 5.14.2 5.14.3 5.15.0 5.15.1 5.16.0 5.16.1 5.16.2 5.16.3 5.16.4 5.17.0 5.17.1 5.17.2 5.17.3 5.17.4 5.17.5 5.17.6 5.18.0 5.19.0 5.2.0 5.2.1 5.2.2 5.2.3 5.20.0 5.21.0 5.21.1 5.21.2 5.21.3 5.22.0 5.22.1 5.22.2 5.22.3 5.22.4 5.23.0 5.23.1 5.23.2 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.6.3 5.6.4 5.7.0 5.7.1 5.8.0 5.8.1 5.9.0 3.0.0-beta.15 3.7.1 3.0.0-beta.16 3.7.2 3.0.0-beta.17 3.7.3 3.0.0-beta.18 3.7.4 3.0.0-beta.19 3.7.5 3.0.0-beta.2 3.7.6 3.0.0-beta.20 3.7.8 3.0.0-beta.21 3.70.0 3.0.0-beta.22 3.71.0 3.0.0-beta.23 3.71.1 3.0.0-beta.23.1 3.71.2 3.0.0-beta.23.2 3.71.3 3.0.0-beta.24 3.72.0 3.0.0-beta.25 3.73.0 3.0.0-beta.26 3.73.1 3.0.0-beta.27 3.73.2 3.0.0-beta.28 3.74.0 3.0.0-beta.29 3.74.1 3.0.0-beta.3 3.74.2 3.0.0-beta.30 3.74.3 3.0.0-beta.31 3.75.0 3.0.0-beta.32 3.75.1 3.0.0-beta.33 3.76.0 3.0.0-beta.33.1 3.77.0 3.0.0-beta.34.0.0 3.77.1 3.0.0-beta.36.0.0 3.78.0 3.0.0-beta.36.0.1 3.79.0 3.0.0-beta.36.2.0 3.8 3.0.0-beta.36.3.0 3.8.1 3.0.0-beta.36.3.1 3.8.2 3.0.0-beta.37.0.0 3.8.3 3.0.0-beta.4 3.8.4 3.0.0-beta.5 3.8.5 3.0.0-beta.6 3.8.6 3.0.0-beta.7 3.80.0 3.0.0-beta.7.1 3.81.0 3.0.0-beta.8 3.82.0 3.0.0-beta.9 3.83.0 3.0.0-rc.1.0.0 3.84.0 3.0.0-rc.1.0.1 3.84.1 3.0.0-rc.1.0.2 3.85.0 3.0.0-rc.1.0.3 3.85.1 3.0.0-rc.1.0.4 3.86.0 3.0.0-rc.2.0.0 3.87.0 3.0.0-rc.2.0.1 3.87.1 3.0.0-rc.2.0.2 3.87.2 3.0.0-rc.2.0.3 3.88.0 3.0.1 3.88.1 3.0.2 3.88.2 3.0.3 3.89.0 3.0.4 3.89.1 3.0.5 3.89.2 3.0.6 3.89.3 3.0.7 3.89.4 3.0.8 3.9.0 3.0.9 3.9.1 3.1.0 3.90.0 3.10 3.90.1 3.10.1 3.90.2 3.100.0 3.91.0 3.100.1 3.91.1 3.100.2 3.92.0 3.101.0 3.92.1 3.101.1 3.93.0 3.102.0 3.93.1 3.102.1 3.94.0 3.103.0 3.95.0 3.103.1 3.95.1 3.11.0 3.96.0 3.11.1 3.96.1 3.11.2 3.97.0 3.11.3 3.98.0 3.11.4 3.98.1 3.11.5 3.99.0 3.12.0 3.99.1 3.12.1 4.0.0 3.13.0 4.0.1 3.14.0 4.1.0 3.14.1 4.1.1 3.15.0 4.10.0 3.16.0 4.11.0 3.16.1 4.11.1 3.16.2 4.12.0 3.16.3 4.12.1 3.17.0 4.12.2 3.17.1 4.13.0 3.17.2 4.14.0 3.18.0 4.15.0 3.18.1 4.16.0 3.18.2 4.17.0 3.19.0 4.17.1 3.19.1 4.18.0 3.19.2 4.18.1 3.19.3 4.19.0 3.2.0 4.2.0 3.2.1 4.20.0 3.2.2 4.20.1 3.2.3 4.20.2 3.2.4 4.21.0 3.2.5 4.22.0 3.20.0 4.22.1 3.21.0 4.22.2 3.21.1 4.23.0 3.22.0 4.24.0 3.23.0 4.25.0 3.23.1 4.26.0 3.23.2 4.26.1 3.24.0 4.27.0 3.25.0 4.28.0 3.25.1 4.29.0 3.26.0 4.3.0 3.26.1 4.3.1 3.27.0 4.30.0 3.28.0 4.31.0 3.29.0 4.31.1 3.3.0 4.32.0 3.3.1 4.33.0 3.3.2 4.34.0 3.3.3 4.35.0 3.3.4 4.35.1 3.3.5 4.36.0 3.3.6 4.37.0 3.30.0 4.38.0 3.31.0 4.39.0 3.31.1 4.4.0 3.32.0 4.40.0 3.32.1 4.41.0 3.32.2 4.41.1 3.33.0 4.41.2 3.34.0 4.41.3 3.34.1 4.42.0 3.34.2 4.42.1 3.34.3 3.34.4 3.35.0 3.35.1 3.35.3 3.35.4 3.36.0 3.37.0 3.37.1 3.37.2 3.37.3 3.38.0 3.38.1 3.39.0 3.39.1 3.39.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.40.0 3.40.1 3.41.0 3.41.1 3.41.2 3.42.0 3.42.1 3.42.2 3.42.3 3.43.0 3.43.1 3.44.0 3.45.0 3.45.1 3.46.0 3.46.1 3.46.10 3.46.11 3.46.12 3.46.13 3.46.14 3.46.2 3.46.3 3.46.4 3.46.5 3.46.6 3.46.7 3.46.8 3.46.9 3.47.0 3.47.1 3.47.10 3.47.11 3.47.2 3.47.3 3.47.5 3.47.6 3.47.7 3.47.9 3.48.0 3.48.1 3.49.0 3.49.1 3.5.0 3.5.1 3.50.0 3.51.0 3.51.1 3.51.2 3.52.0 3.53.0 3.54.0 3.54.1 3.54.2 3.54.3 3.55.0 3.55.1 3.56.0 3.56.1 3.56.2 3.57.0 3.57.1 3.58.0 3.59.0 3.59.1 3.59.2 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.60.0 3.60.1 3.60.10 3.60.11 3.60.12 3.60.2 3.60.3 3.60.4 3.60.6 3.60.7 3.60.8 3.60.9 3.61.0 3.62.0 3.62.1 3.63.0 3.64.0 3.64.1 3.64.2 3.64.3 3.65.0 trunk 3.65.1 3.0.0 3.66.0 3.0.0-beta.1 3.67.0 3.0.0-beta.10 3.67.1 3.0.0-beta.11 3.68.0 3.0.0-beta.12 3.69.0 3.0.0-beta.13 3.69.1 3.0.0-beta.14 3.7.0
mailpoet / vendor / woocommerce / action-scheduler / classes / data-stores / ActionScheduler_wpPostStore.php
mailpoet / vendor / woocommerce / action-scheduler / classes / data-stores Last commit date
ActionScheduler_DBLogger.php 1 year ago ActionScheduler_DBStore.php 1 year ago ActionScheduler_HybridStore.php 1 year ago ActionScheduler_wpCommentLogger.php 1 year ago ActionScheduler_wpPostStore.php 1 year ago ActionScheduler_wpPostStore_PostStatusRegistrar.php 4 years ago ActionScheduler_wpPostStore_PostTypeRegistrar.php 1 year ago ActionScheduler_wpPostStore_TaxonomyRegistrar.php 1 year ago index.php 3 years ago
ActionScheduler_wpPostStore.php
595 lines
1 <?php
2 if (!defined('ABSPATH')) exit;
3 class ActionScheduler_wpPostStore extends ActionScheduler_Store {
4 const POST_TYPE = 'scheduled-action';
5 const GROUP_TAXONOMY = 'action-group';
6 const SCHEDULE_META_KEY = '_action_manager_schedule';
7 const DEPENDENCIES_MET = 'as-post-store-dependencies-met';
8 private $claim_before_date = null;
9 protected $local_timezone = null;
10 public function save_action( ActionScheduler_Action $action, ?DateTime $scheduled_date = null ) {
11 try {
12 $this->validate_action( $action );
13 $post_array = $this->create_post_array( $action, $scheduled_date );
14 $post_id = $this->save_post_array( $post_array );
15 $this->save_post_schedule( $post_id, $action->get_schedule() );
16 $this->save_action_group( $post_id, $action->get_group() );
17 do_action( 'action_scheduler_stored_action', $post_id );
18 return $post_id;
19 } catch ( Exception $e ) {
20 throw new RuntimeException( sprintf( __( 'Error saving action: %s', 'action-scheduler' ), $e->getMessage() ), 0 );
21 }
22 }
23 protected function create_post_array( ActionScheduler_Action $action, ?DateTime $scheduled_date = null ) {
24 $post = array(
25 'post_type' => self::POST_TYPE,
26 'post_title' => $action->get_hook(),
27 'post_content' => wp_json_encode( $action->get_args() ),
28 'post_status' => ( $action->is_finished() ? 'publish' : 'pending' ),
29 'post_date_gmt' => $this->get_scheduled_date_string( $action, $scheduled_date ),
30 'post_date' => $this->get_scheduled_date_string_local( $action, $scheduled_date ),
31 );
32 return $post;
33 }
34 protected function save_post_array( $post_array ) {
35 add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
36 add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
37 $has_kses = false !== has_filter( 'content_save_pre', 'wp_filter_post_kses' );
38 if ( $has_kses ) {
39 // Prevent KSES from corrupting JSON in post_content.
40 kses_remove_filters();
41 }
42 $post_id = wp_insert_post( $post_array );
43 if ( $has_kses ) {
44 kses_init_filters();
45 }
46 remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 );
47 remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
48 if ( is_wp_error( $post_id ) || empty( $post_id ) ) {
49 throw new RuntimeException( __( 'Unable to save action.', 'action-scheduler' ) );
50 }
51 return $post_id;
52 }
53 public function filter_insert_post_data( $postdata ) {
54 if ( self::POST_TYPE === $postdata['post_type'] ) {
55 $postdata['post_author'] = 0;
56 if ( 'future' === $postdata['post_status'] ) {
57 $postdata['post_status'] = 'publish';
58 }
59 }
60 return $postdata;
61 }
62 public function set_unique_post_slug( $override_slug, $slug, $post_ID, $post_status, $post_type ) {
63 if ( self::POST_TYPE === $post_type ) {
64 $override_slug = uniqid( self::POST_TYPE . '-', true ) . '-' . wp_generate_password( 32, false );
65 }
66 return $override_slug;
67 }
68 protected function save_post_schedule( $post_id, $schedule ) {
69 update_post_meta( $post_id, self::SCHEDULE_META_KEY, $schedule );
70 }
71 protected function save_action_group( $post_id, $group ) {
72 if ( empty( $group ) ) {
73 wp_set_object_terms( $post_id, array(), self::GROUP_TAXONOMY, false );
74 } else {
75 wp_set_object_terms( $post_id, array( $group ), self::GROUP_TAXONOMY, false );
76 }
77 }
78 public function fetch_action( $action_id ) {
79 $post = $this->get_post( $action_id );
80 if ( empty( $post ) || self::POST_TYPE !== $post->post_type ) {
81 return $this->get_null_action();
82 }
83 try {
84 $action = $this->make_action_from_post( $post );
85 } catch ( ActionScheduler_InvalidActionException $exception ) {
86 do_action( 'action_scheduler_failed_fetch_action', $post->ID, $exception );
87 return $this->get_null_action();
88 }
89 return $action;
90 }
91 protected function get_post( $action_id ) {
92 if ( empty( $action_id ) ) {
93 return null;
94 }
95 return get_post( $action_id );
96 }
97 protected function get_null_action() {
98 return new ActionScheduler_NullAction();
99 }
100 protected function make_action_from_post( $post ) {
101 $hook = $post->post_title;
102 $args = json_decode( $post->post_content, true );
103 $this->validate_args( $args, $post->ID );
104 $schedule = get_post_meta( $post->ID, self::SCHEDULE_META_KEY, true );
105 $this->validate_schedule( $schedule, $post->ID );
106 $group = wp_get_object_terms( $post->ID, self::GROUP_TAXONOMY, array( 'fields' => 'names' ) );
107 $group = empty( $group ) ? '' : reset( $group );
108 return ActionScheduler::factory()->get_stored_action( $this->get_action_status_by_post_status( $post->post_status ), $hook, $args, $schedule, $group );
109 }
110 protected function get_action_status_by_post_status( $post_status ) {
111 switch ( $post_status ) {
112 case 'publish':
113 $action_status = self::STATUS_COMPLETE;
114 break;
115 case 'trash':
116 $action_status = self::STATUS_CANCELED;
117 break;
118 default:
119 if ( ! array_key_exists( $post_status, $this->get_status_labels() ) ) {
120 throw new InvalidArgumentException( sprintf( 'Invalid post status: "%s". No matching action status available.', $post_status ) );
121 }
122 $action_status = $post_status;
123 break;
124 }
125 return $action_status;
126 }
127 protected function get_post_status_by_action_status( $action_status ) {
128 switch ( $action_status ) {
129 case self::STATUS_COMPLETE:
130 $post_status = 'publish';
131 break;
132 case self::STATUS_CANCELED:
133 $post_status = 'trash';
134 break;
135 default:
136 if ( ! array_key_exists( $action_status, $this->get_status_labels() ) ) {
137 throw new InvalidArgumentException( sprintf( 'Invalid action status: "%s".', $action_status ) );
138 }
139 $post_status = $action_status;
140 break;
141 }
142 return $post_status;
143 }
144 protected function get_query_actions_sql( array $query, $select_or_count = 'select' ) {
145 if ( ! in_array( $select_or_count, array( 'select', 'count' ), true ) ) {
146 throw new InvalidArgumentException( __( 'Invalid schedule. Cannot save action.', 'action-scheduler' ) );
147 }
148 $query = wp_parse_args(
149 $query,
150 array(
151 'hook' => '',
152 'args' => null,
153 'date' => null,
154 'date_compare' => '<=',
155 'modified' => null,
156 'modified_compare' => '<=',
157 'group' => '',
158 'status' => '',
159 'claimed' => null,
160 'per_page' => 5,
161 'offset' => 0,
162 'orderby' => 'date',
163 'order' => 'ASC',
164 'search' => '',
165 )
166 );
167 global $wpdb;
168 $sql = ( 'count' === $select_or_count ) ? 'SELECT count(p.ID)' : 'SELECT p.ID ';
169 $sql .= "FROM {$wpdb->posts} p";
170 $sql_params = array();
171 if ( empty( $query['group'] ) && 'group' === $query['orderby'] ) {
172 $sql .= " LEFT JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
173 $sql .= " LEFT JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
174 $sql .= " LEFT JOIN {$wpdb->terms} t ON tt.term_id=t.term_id";
175 } elseif ( ! empty( $query['group'] ) ) {
176 $sql .= " INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id=p.ID";
177 $sql .= " INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id=tt.term_taxonomy_id";
178 $sql .= " INNER JOIN {$wpdb->terms} t ON tt.term_id=t.term_id";
179 $sql .= ' AND t.slug=%s';
180 $sql_params[] = $query['group'];
181 }
182 $sql .= ' WHERE post_type=%s';
183 $sql_params[] = self::POST_TYPE;
184 if ( $query['hook'] ) {
185 $sql .= ' AND p.post_title=%s';
186 $sql_params[] = $query['hook'];
187 }
188 if ( ! is_null( $query['args'] ) ) {
189 $sql .= ' AND p.post_content=%s';
190 $sql_params[] = wp_json_encode( $query['args'] );
191 }
192 if ( $query['status'] ) {
193 $post_statuses = array_map( array( $this, 'get_post_status_by_action_status' ), (array) $query['status'] );
194 $placeholders = array_fill( 0, count( $post_statuses ), '%s' );
195 $sql .= ' AND p.post_status IN (' . join( ', ', $placeholders ) . ')';
196 $sql_params = array_merge( $sql_params, array_values( $post_statuses ) );
197 }
198 if ( $query['date'] instanceof DateTime ) {
199 $date = clone $query['date'];
200 $date->setTimezone( new DateTimeZone( 'UTC' ) );
201 $date_string = $date->format( 'Y-m-d H:i:s' );
202 $comparator = $this->validate_sql_comparator( $query['date_compare'] );
203 $sql .= " AND p.post_date_gmt $comparator %s";
204 $sql_params[] = $date_string;
205 }
206 if ( $query['modified'] instanceof DateTime ) {
207 $modified = clone $query['modified'];
208 $modified->setTimezone( new DateTimeZone( 'UTC' ) );
209 $date_string = $modified->format( 'Y-m-d H:i:s' );
210 $comparator = $this->validate_sql_comparator( $query['modified_compare'] );
211 $sql .= " AND p.post_modified_gmt $comparator %s";
212 $sql_params[] = $date_string;
213 }
214 if ( true === $query['claimed'] ) {
215 $sql .= " AND p.post_password != ''";
216 } elseif ( false === $query['claimed'] ) {
217 $sql .= " AND p.post_password = ''";
218 } elseif ( ! is_null( $query['claimed'] ) ) {
219 $sql .= ' AND p.post_password = %s';
220 $sql_params[] = $query['claimed'];
221 }
222 if ( ! empty( $query['search'] ) ) {
223 $sql .= ' AND (p.post_title LIKE %s OR p.post_content LIKE %s OR p.post_password LIKE %s)';
224 for ( $i = 0; $i < 3; $i++ ) {
225 $sql_params[] = sprintf( '%%%s%%', $query['search'] );
226 }
227 }
228 if ( 'select' === $select_or_count ) {
229 switch ( $query['orderby'] ) {
230 case 'hook':
231 $orderby = 'p.post_title';
232 break;
233 case 'group':
234 $orderby = 't.name';
235 break;
236 case 'status':
237 $orderby = 'p.post_status';
238 break;
239 case 'modified':
240 $orderby = 'p.post_modified';
241 break;
242 case 'claim_id':
243 $orderby = 'p.post_password';
244 break;
245 case 'schedule':
246 case 'date':
247 default:
248 $orderby = 'p.post_date_gmt';
249 break;
250 }
251 if ( 'ASC' === strtoupper( $query['order'] ) ) {
252 $order = 'ASC';
253 } else {
254 $order = 'DESC';
255 }
256 $sql .= " ORDER BY $orderby $order";
257 if ( $query['per_page'] > 0 ) {
258 $sql .= ' LIMIT %d, %d';
259 $sql_params[] = $query['offset'];
260 $sql_params[] = $query['per_page'];
261 }
262 }
263 return $wpdb->prepare( $sql, $sql_params ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
264 }
265 public function query_actions( $query = array(), $query_type = 'select' ) {
266 global $wpdb;
267 $sql = $this->get_query_actions_sql( $query, $query_type );
268 return ( 'count' === $query_type ) ? $wpdb->get_var( $sql ) : $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
269 }
270 public function action_counts() {
271 $action_counts_by_status = array();
272 $action_stati_and_labels = $this->get_status_labels();
273 $posts_count_by_status = (array) wp_count_posts( self::POST_TYPE, 'readable' );
274 foreach ( $posts_count_by_status as $post_status_name => $count ) {
275 try {
276 $action_status_name = $this->get_action_status_by_post_status( $post_status_name );
277 } catch ( Exception $e ) {
278 // Ignore any post statuses that aren't for actions.
279 continue;
280 }
281 if ( array_key_exists( $action_status_name, $action_stati_and_labels ) ) {
282 $action_counts_by_status[ $action_status_name ] = $count;
283 }
284 }
285 return $action_counts_by_status;
286 }
287 public function cancel_action( $action_id ) {
288 $post = get_post( $action_id );
289 if ( empty( $post ) || ( self::POST_TYPE !== $post->post_type ) ) {
290 throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s: we were unable to cancel this action. It may may have been deleted by another process.', 'action-scheduler' ), $action_id ) );
291 }
292 do_action( 'action_scheduler_canceled_action', $action_id );
293 add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
294 wp_trash_post( $action_id );
295 remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
296 }
297 public function delete_action( $action_id ) {
298 $post = get_post( $action_id );
299 if ( empty( $post ) || ( self::POST_TYPE !== $post->post_type ) ) {
300 throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s: we were unable to delete this action. It may may have been deleted by another process.', 'action-scheduler' ), $action_id ) );
301 }
302 do_action( 'action_scheduler_deleted_action', $action_id );
303 wp_delete_post( $action_id, true );
304 }
305 public function get_date( $action_id ) {
306 $next = $this->get_date_gmt( $action_id );
307 return ActionScheduler_TimezoneHelper::set_local_timezone( $next );
308 }
309 public function get_date_gmt( $action_id ) {
310 $post = get_post( $action_id );
311 if ( empty( $post ) || ( self::POST_TYPE !== $post->post_type ) ) {
312 throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s: we were unable to determine the date of this action. It may may have been deleted by another process.', 'action-scheduler' ), $action_id ) );
313 }
314 if ( 'publish' === $post->post_status ) {
315 return as_get_datetime_object( $post->post_modified_gmt );
316 } else {
317 return as_get_datetime_object( $post->post_date_gmt );
318 }
319 }
320 public function stake_claim( $max_actions = 10, ?DateTime $before_date = null, $hooks = array(), $group = '' ) {
321 $this->claim_before_date = $before_date;
322 $claim_id = $this->generate_claim_id();
323 $this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group );
324 $action_ids = $this->find_actions_by_claim_id( $claim_id );
325 $this->claim_before_date = null;
326 return new ActionScheduler_ActionClaim( $claim_id, $action_ids );
327 }
328 public function get_claim_count() {
329 global $wpdb;
330 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
331 return $wpdb->get_var(
332 $wpdb->prepare(
333 "SELECT COUNT(DISTINCT post_password) FROM {$wpdb->posts} WHERE post_password != '' AND post_type = %s AND post_status IN ('in-progress','pending')",
334 array( self::POST_TYPE )
335 )
336 );
337 }
338 protected function generate_claim_id() {
339 $claim_id = md5( microtime( true ) . wp_rand( 0, 1000 ) );
340 return substr( $claim_id, 0, 20 ); // to fit in db field with 20 char limit.
341 }
342 protected function claim_actions( $claim_id, $limit, ?DateTime $before_date = null, $hooks = array(), $group = '' ) {
343 // Set up initial variables.
344 $date = null === $before_date ? as_get_datetime_object() : clone $before_date;
345 $limit_ids = ! empty( $group );
346 $ids = $limit_ids ? $this->get_actions_by_group( $group, $limit, $date ) : array();
347 // If limiting by IDs and no posts found, then return early since we have nothing to update.
348 if ( $limit_ids && 0 === count( $ids ) ) {
349 return 0;
350 }
351 global $wpdb;
352 $update = "UPDATE {$wpdb->posts} SET post_password = %s, post_modified_gmt = %s, post_modified = %s";
353 $params = array(
354 $claim_id,
355 current_time( 'mysql', true ),
356 current_time( 'mysql' ),
357 );
358 // Build initial WHERE clause.
359 $where = "WHERE post_type = %s AND post_status = %s AND post_password = ''";
360 $params[] = self::POST_TYPE;
361 $params[] = ActionScheduler_Store::STATUS_PENDING;
362 if ( ! empty( $hooks ) ) {
363 $placeholders = array_fill( 0, count( $hooks ), '%s' );
364 $where .= ' AND post_title IN (' . join( ', ', $placeholders ) . ')';
365 $params = array_merge( $params, array_values( $hooks ) );
366 }
367 if ( $limit_ids ) {
368 $where .= ' AND ID IN (' . join( ',', $ids ) . ')';
369 } else {
370 $where .= ' AND post_date_gmt <= %s';
371 $params[] = $date->format( 'Y-m-d H:i:s' );
372 }
373 // Add the ORDER BY clause and,ms limit.
374 $order = 'ORDER BY menu_order ASC, post_date_gmt ASC, ID ASC LIMIT %d';
375 $params[] = $limit;
376 // Run the query and gather results.
377 $rows_affected = $wpdb->query( $wpdb->prepare( "{$update} {$where} {$order}", $params ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
378 if ( false === $rows_affected ) {
379 throw new RuntimeException( __( 'Unable to claim actions. Database error.', 'action-scheduler' ) );
380 }
381 return (int) $rows_affected;
382 }
383 protected function get_actions_by_group( $group, $limit, DateTime $date ) {
384 // Ensure the group exists before continuing.
385 if ( ! term_exists( $group, self::GROUP_TAXONOMY ) ) {
386 throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'action-scheduler' ), $group ) );
387 }
388 // Set up a query for post IDs to use later.
389 $query = new WP_Query();
390 $query_args = array(
391 'fields' => 'ids',
392 'post_type' => self::POST_TYPE,
393 'post_status' => ActionScheduler_Store::STATUS_PENDING,
394 'has_password' => false,
395 'posts_per_page' => $limit * 3,
396 'suppress_filters' => true, // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters
397 'no_found_rows' => true,
398 'orderby' => array(
399 'menu_order' => 'ASC',
400 'date' => 'ASC',
401 'ID' => 'ASC',
402 ),
403 'date_query' => array(
404 'column' => 'post_date_gmt',
405 'before' => $date->format( 'Y-m-d H:i' ),
406 'inclusive' => true,
407 ),
408 'tax_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery
409 array(
410 'taxonomy' => self::GROUP_TAXONOMY,
411 'field' => 'slug',
412 'terms' => $group,
413 'include_children' => false,
414 ),
415 ),
416 );
417 return $query->query( $query_args );
418 }
419 public function find_actions_by_claim_id( $claim_id ) {
420 global $wpdb;
421 $action_ids = array();
422 $before_date = isset( $this->claim_before_date ) ? $this->claim_before_date : as_get_datetime_object();
423 $cut_off = $before_date->format( 'Y-m-d H:i:s' );
424 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
425 $results = $wpdb->get_results(
426 $wpdb->prepare(
427 "SELECT ID, post_date_gmt FROM {$wpdb->posts} WHERE post_type = %s AND post_password = %s",
428 array(
429 self::POST_TYPE,
430 $claim_id,
431 )
432 )
433 );
434 // Verify that the scheduled date for each action is within the expected bounds (in some unusual
435 // cases, we cannot depend on MySQL to honor all of the WHERE conditions we specify).
436 foreach ( $results as $claimed_action ) {
437 if ( $claimed_action->post_date_gmt <= $cut_off ) {
438 $action_ids[] = absint( $claimed_action->ID );
439 }
440 }
441 return $action_ids;
442 }
443 public function release_claim( ActionScheduler_ActionClaim $claim ) {
444 $action_ids = $this->find_actions_by_claim_id( $claim->get_id() );
445 if ( empty( $action_ids ) ) {
446 return; // nothing to do.
447 }
448 $action_id_string = implode( ',', array_map( 'intval', $action_ids ) );
449 global $wpdb;
450 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
451 $result = $wpdb->query(
452 $wpdb->prepare(
453 "UPDATE {$wpdb->posts} SET post_password = '' WHERE ID IN ($action_id_string) AND post_password = %s", //phpcs:ignore
454 array(
455 $claim->get_id(),
456 )
457 )
458 );
459 if ( false === $result ) {
460 throw new RuntimeException( sprintf( __( 'Unable to unlock claim %s. Database error.', 'action-scheduler' ), $claim->get_id() ) );
461 }
462 }
463 public function unclaim_action( $action_id ) {
464 global $wpdb;
465 //phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
466 $result = $wpdb->query(
467 $wpdb->prepare(
468 "UPDATE {$wpdb->posts} SET post_password = '' WHERE ID = %d AND post_type = %s",
469 $action_id,
470 self::POST_TYPE
471 )
472 );
473 if ( false === $result ) {
474 throw new RuntimeException( sprintf( __( 'Unable to unlock claim on action %s. Database error.', 'action-scheduler' ), $action_id ) );
475 }
476 }
477 public function mark_failure( $action_id ) {
478 global $wpdb;
479 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
480 $result = $wpdb->query(
481 $wpdb->prepare( "UPDATE {$wpdb->posts} SET post_status = %s WHERE ID = %d AND post_type = %s", self::STATUS_FAILED, $action_id, self::POST_TYPE )
482 );
483 if ( false === $result ) {
484 throw new RuntimeException( sprintf( __( 'Unable to mark failure on action %s. Database error.', 'action-scheduler' ), $action_id ) );
485 }
486 }
487 public function get_claim_id( $action_id ) {
488 return $this->get_post_column( $action_id, 'post_password' );
489 }
490 public function get_status( $action_id ) {
491 $status = $this->get_post_column( $action_id, 'post_status' );
492 if ( null === $status ) {
493 throw new InvalidArgumentException( __( 'Invalid action ID. No status found.', 'action-scheduler' ) );
494 }
495 return $this->get_action_status_by_post_status( $status );
496 }
497 private function get_post_column( $action_id, $column_name ) {
498 global $wpdb;
499 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
500 return $wpdb->get_var(
501 $wpdb->prepare(
502 "SELECT {$column_name} FROM {$wpdb->posts} WHERE ID=%d AND post_type=%s", // phpcs:ignore
503 $action_id,
504 self::POST_TYPE
505 )
506 );
507 }
508 public function log_execution( $action_id ) {
509 global $wpdb;
510 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
511 $status_updated = $wpdb->query(
512 $wpdb->prepare(
513 "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",
514 self::STATUS_RUNNING,
515 current_time( 'mysql', true ),
516 current_time( 'mysql' ),
517 $action_id,
518 self::POST_TYPE
519 )
520 );
521 if ( ! $status_updated ) {
522 throw new Exception(
523 sprintf(
524 __( 'Unable to update the status of action %1$d to %2$s.', 'action-scheduler' ),
525 $action_id,
526 self::STATUS_RUNNING
527 )
528 );
529 }
530 }
531 public function mark_complete( $action_id ) {
532 $post = get_post( $action_id );
533 if ( empty( $post ) || ( self::POST_TYPE !== $post->post_type ) ) {
534 throw new InvalidArgumentException( sprintf( __( 'Unidentified action %s: we were unable to mark this action as having completed. It may may have been deleted by another process.', 'action-scheduler' ), $action_id ) );
535 }
536 add_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10, 1 );
537 add_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10, 5 );
538 $result = wp_update_post(
539 array(
540 'ID' => $action_id,
541 'post_status' => 'publish',
542 ),
543 true
544 );
545 remove_filter( 'wp_insert_post_data', array( $this, 'filter_insert_post_data' ), 10 );
546 remove_filter( 'pre_wp_unique_post_slug', array( $this, 'set_unique_post_slug' ), 10 );
547 if ( is_wp_error( $result ) ) {
548 throw new RuntimeException( $result->get_error_message() );
549 }
550 do_action( 'action_scheduler_completed_action', $action_id );
551 }
552 public function mark_migrated( $action_id ) {
553 wp_update_post(
554 array(
555 'ID' => $action_id,
556 'post_status' => 'migrated',
557 )
558 );
559 }
560 public function migration_dependencies_met( $setting ) {
561 global $wpdb;
562 $dependencies_met = get_transient( self::DEPENDENCIES_MET );
563 if ( empty( $dependencies_met ) ) {
564 $maximum_args_length = apply_filters( 'action_scheduler_maximum_args_length', 191 );
565 $found_action = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
566 $wpdb->prepare(
567 "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND CHAR_LENGTH(post_content) > %d LIMIT 1",
568 $maximum_args_length,
569 self::POST_TYPE
570 )
571 );
572 $dependencies_met = $found_action ? 'no' : 'yes';
573 set_transient( self::DEPENDENCIES_MET, $dependencies_met, DAY_IN_SECONDS );
574 }
575 return 'yes' === $dependencies_met ? $setting : false;
576 }
577 protected function validate_action( ActionScheduler_Action $action ) {
578 try {
579 parent::validate_action( $action );
580 } catch ( Exception $e ) {
581 $message = sprintf( __( '%s Support for strings longer than this will be removed in a future version.', 'action-scheduler' ), $e->getMessage() );
582 _doing_it_wrong( 'ActionScheduler_Action::$args', esc_html( $message ), '2.1.0' );
583 }
584 }
585 public function init() {
586 add_filter( 'action_scheduler_migration_dependencies_met', array( $this, 'migration_dependencies_met' ) );
587 $post_type_registrar = new ActionScheduler_wpPostStore_PostTypeRegistrar();
588 $post_type_registrar->register();
589 $post_status_registrar = new ActionScheduler_wpPostStore_PostStatusRegistrar();
590 $post_status_registrar->register();
591 $taxonomy_registrar = new ActionScheduler_wpPostStore_TaxonomyRegistrar();
592 $taxonomy_registrar->register();
593 }
594 }
595