PageRenderTime 36ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-content/plugins/woocommerce/includes/libraries/wp-background-process.php

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