PluginProbe
Tiered Pricing Table for WooCommerce / 2.1
Tiered Pricing Table for WooCommerce v2.1
7.1.7 7.1.5 7.1.4 7.1.1 6.5.0 6.4.0 6.1.0 trunk 1.0 1.1 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.2.0 2.2.1 2.2.2 2.2.3 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 All 90 releases
tier-pricing-table / src / BackgroundProcessing / BackgroundTask.php

BackgroundTask.php in Tiered Pricing Table for WooCommerce 2.1, at src/BackgroundProcessing/BackgroundTask.php

456 lines 10.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php namespace TierPricingTable\BackgroundProcessing;
2
3 /**
4 * Abstract WP_Background_Process class.
5 *
6 * @abstract
7 * @extends WP_Async_Request
8 */
9 abstract class BackgroundTask extends AsyncRequest {
10 /**
11 * Action
12 *
13 * (default value: 'background_process')
14 *
15 * @var string
16 * @access protected
17 */
18 protected $action = 'background_process';
19 /**
20 * Start time of current process.
21 *
22 * (default value: 0)
23 *
24 * @var int
25 * @access protected
26 */
27 protected $start_time = 0;
28 /**
29 * Cron_hook_identifier
30 *
31 * @var mixed
32 * @access protected
33 */
34 protected $cron_hook_identifier;
35 /**
36 * Cron_interval_identifier
37 *
38 * @var mixed
39 * @access protected
40 */
41 protected $cron_interval_identifier;
42
43 /**
44 * Initiate new background process
45 */
46 public function __construct() {
47 parent::__construct();
48 $this->cron_hook_identifier = $this->identifier . '_cron';
49 $this->cron_interval_identifier = $this->identifier . '_cron_interval';
50 add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) );
51 add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) );
52 }
53
54 /**
55 * Dispatch
56 *
57 * @access public
58 * @return void
59 */
60 public function dispatch() {
61 // Schedule the cron healthcheck.
62 $this->schedule_event();
63
64 // Perform remote post.
65 return parent::dispatch();
66 }
67
68 /**
69 * Push to queue
70 *
71 * @param mixed $data Data.
72 *
73 * @return $this
74 */
75 public function push_to_queue( $data ) {
76 $this->data[] = $data;
77
78 return $this;
79 }
80
81 /**
82 * Save queue
83 *
84 * @return $this
85 */
86 public function save() {
87 $key = $this->generate_key();
88 if ( ! empty( $this->data ) ) {
89 update_site_option( $key, $this->data );
90 }
91
92 return $this;
93 }
94
95 /**
96 * Update queue
97 *
98 * @param string $key Key.
99 * @param array $data Data.
100 *
101 * @return $this
102 */
103 public function update( $key, $data ) {
104 if ( ! empty( $data ) ) {
105 update_site_option( $key, $data );
106 }
107
108 return $this;
109 }
110
111 /**
112 * Delete queue
113 *
114 * @param string $key Key.
115 *
116 * @return $this
117 */
118 public function delete( $key ) {
119 delete_site_option( $key );
120
121 return $this;
122 }
123
124 /**
125 * Generate key
126 *
127 * Generates a unique key based on microtime. Queue items are
128 * given a unique key so that they can be merged upon save.
129 *
130 * @param int $length Length.
131 *
132 * @return string
133 */
134 protected function generate_key( $length = 64 ) {
135 $unique = md5( microtime() . rand() );
136 $prepend = $this->identifier . '_batch_';
137
138 return substr( $prepend . $unique, 0, $length );
139 }
140
141 /**
142 * Maybe process queue
143 *
144 * Checks whether data exists within the queue and that
145 * the process is not already running.
146 */
147 public function maybe_handle() {
148 // Don't lock up other requests while processing
149 session_write_close();
150 if ( $this->is_process_running() ) {
151 // Background process already running.
152 wp_die();
153 }
154 if ( $this->is_queue_empty() ) {
155 // No data to process.
156 wp_die();
157 }
158 check_ajax_referer( $this->identifier, 'nonce' );
159 $this->handle();
160 wp_die();
161 }
162
163 /**
164 * Is queue empty
165 *
166 * @return bool
167 */
168 protected function is_queue_empty() {
169 global $wpdb;
170 $table = $wpdb->options;
171 $column = 'option_name';
172 if ( is_multisite() ) {
173 $table = $wpdb->sitemeta;
174 $column = 'meta_key';
175 }
176 $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
177 $count = $wpdb->get_var( $wpdb->prepare( "
178 SELECT COUNT(*)
179 FROM {$table}
180 WHERE {$column} LIKE %s
181 ", $key ) );
182
183 return ( $count > 0 ) ? false : true;
184 }
185
186 /**
187 * Is process running
188 *
189 * Check whether the current process is already running
190 * in a background process.
191 */
192 protected function is_process_running() {
193 if ( get_site_transient( $this->identifier . '_process_lock' ) ) {
194 // Process already running.
195 return true;
196 }
197
198 return false;
199 }
200
201 /**
202 * Lock process
203 *
204 * Lock the process so that multiple instances can't run simultaneously.
205 * Override if applicable, but the duration should be greater than that
206 * defined in the time_exceeded() method.
207 */
208 protected function lock_process() {
209 $this->start_time = time(); // Set start time of current process.
210 $lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; // 1 minute
211 $lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration );
212 set_site_transient( $this->identifier . '_process_lock', microtime(), $lock_duration );
213 }
214
215 /**
216 * Unlock process
217 *
218 * Unlock the process so that other instances can spawn.
219 *
220 * @return $this
221 */
222 protected function unlock_process() {
223 delete_site_transient( $this->identifier . '_process_lock' );
224
225 return $this;
226 }
227
228 /**
229 * Get batch
230 *
231 * @return stdClass Return the first batch from the queue
232 */
233 protected function get_batch() {
234 global $wpdb;
235 $table = $wpdb->options;
236 $column = 'option_name';
237 $key_column = 'option_id';
238 $value_column = 'option_value';
239 if ( is_multisite() ) {
240 $table = $wpdb->sitemeta;
241 $column = 'meta_key';
242 $key_column = 'meta_id';
243 $value_column = 'meta_value';
244 }
245 $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
246 $query = $wpdb->get_row( $wpdb->prepare( "
247 SELECT *
248 FROM {$table}
249 WHERE {$column} LIKE %s
250 ORDER BY {$key_column} ASC
251 LIMIT 1
252 ", $key ) );
253 $batch = new \stdClass();
254 $batch->key = $query->$column;
255 $batch->data = maybe_unserialize( $query->$value_column );
256
257 return $batch;
258 }
259
260 /**
261 * Handle
262 *
263 * Pass each queue item to the task handler, while remaining
264 * within server memory and time limit constraints.
265 */
266 protected function handle() {
267 $this->lock_process();
268 do {
269 $batch = $this->get_batch();
270 foreach ( $batch->data as $key => $value ) {
271 $task = $this->task( $value );
272 if ( false !== $task ) {
273 $batch->data[ $key ] = $task;
274 } else {
275 unset( $batch->data[ $key ] );
276 }
277 if ( $this->time_exceeded() || $this->memory_exceeded() ) {
278 // Batch limits reached.
279 break;
280 }
281 }
282 // Update or delete current batch.
283 if ( ! empty( $batch->data ) ) {
284 $this->update( $batch->key, $batch->data );
285 } else {
286 $this->delete( $batch->key );
287 }
288 } while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() );
289 $this->unlock_process();
290 // Start next batch or complete process.
291 if ( ! $this->is_queue_empty() ) {
292 $this->dispatch();
293 } else {
294 $this->complete();
295 }
296 wp_die();
297 }
298
299 /**
300 * Memory exceeded
301 *
302 * Ensures the batch process never exceeds 90%
303 * of the maximum WordPress memory.
304 *
305 * @return bool
306 */
307 protected function memory_exceeded() {
308 $memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory
309 $current_memory = memory_get_usage( true );
310 $return = false;
311 if ( $current_memory >= $memory_limit ) {
312 $return = true;
313 }
314
315 return apply_filters( $this->identifier . '_memory_exceeded', $return );
316 }
317
318 /**
319 * Get memory limit
320 *
321 * @return int
322 */
323 protected function get_memory_limit() {
324 if ( function_exists( 'ini_get' ) ) {
325 $memory_limit = ini_get( 'memory_limit' );
326 } else {
327 // Sensible default.
328 $memory_limit = '128M';
329 }
330 if ( ! $memory_limit || - 1 === intval( $memory_limit ) ) {
331 // Unlimited, set to 32GB.
332 $memory_limit = '32000M';
333 }
334
335 return intval( $memory_limit ) * 1024 * 1024;
336 }
337
338 /**
339 * Time exceeded.
340 *
341 * Ensures the batch never exceeds a sensible time limit.
342 * A timeout limit of 30s is common on shared hosting.
343 *
344 * @return bool
345 */
346 protected function time_exceeded() {
347 $finish = $this->start_time + apply_filters( $this->identifier . '_default_time_limit', 20 ); // 20 seconds
348 $return = false;
349 if ( time() >= $finish ) {
350 $return = true;
351 }
352
353 return apply_filters( $this->identifier . '_time_exceeded', $return );
354 }
355
356 /**
357 * Complete.
358 *
359 * Override if applicable, but ensure that the below actions are
360 * performed, or, call parent::complete().
361 */
362 protected function complete() {
363 // Unschedule the cron healthcheck.
364 $this->clear_scheduled_event();
365 }
366
367 /**
368 * Schedule cron healthcheck
369 *
370 * @access public
371 *
372 * @param mixed $schedules Schedules.
373 *
374 * @return mixed
375 */
376 public function schedule_cron_healthcheck( $schedules ) {
377 $interval = apply_filters( $this->identifier . '_cron_interval', 5 );
378 if ( property_exists( $this, 'cron_interval' ) ) {
379 $interval = apply_filters( $this->identifier . '_cron_interval', $this->cron_interval );
380 }
381 // Adds every 5 minutes to the existing schedules.
382 $schedules[ $this->identifier . '_cron_interval' ] = array(
383 'interval' => MINUTE_IN_SECONDS * $interval,
384 'display' => sprintf( __( 'Every %d Minutes' ), $interval ),
385 );
386
387 return $schedules;
388 }
389
390 /**
391 * Handle cron healthcheck
392 *
393 * Restart the background process if not already running
394 * and data exists in the queue.
395 */
396 public function handle_cron_healthcheck() {
397 if ( $this->is_process_running() ) {
398 // Background process already running.
399 exit;
400 }
401 if ( $this->is_queue_empty() ) {
402 // No data to process.
403 $this->clear_scheduled_event();
404 exit;
405 }
406 $this->handle();
407 exit;
408 }
409
410 /**
411 * Schedule event
412 */
413 protected function schedule_event() {
414 if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) {
415 wp_schedule_event( time(), $this->cron_interval_identifier, $this->cron_hook_identifier );
416 }
417 }
418
419 /**
420 * Clear scheduled event
421 */
422 protected function clear_scheduled_event() {
423 $timestamp = wp_next_scheduled( $this->cron_hook_identifier );
424 if ( $timestamp ) {
425 wp_unschedule_event( $timestamp, $this->cron_hook_identifier );
426 }
427 }
428
429 /**
430 * Cancel Process
431 *
432 * Stop processing queue items, clear cronjob and delete batch.
433 *
434 */
435 public function cancel_process() {
436 if ( ! $this->is_queue_empty() ) {
437 $batch = $this->get_batch();
438 $this->delete( $batch->key );
439 wp_clear_scheduled_hook( $this->cron_hook_identifier );
440 }
441 }
442
443 /**
444 * Task
445 *
446 * Override this method to perform any actions required on each
447 * queue item. Return the modified item for further processing
448 * in the next pass through. Or, return false to remove the
449 * item from the queue.
450 *
451 * @param mixed $item Queue item to iterate over.
452 *
453 * @return mixed
454 */
455 abstract protected function task( $item );
456 }