PageRenderTime 29ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://gitlab.com/campus-academy/krowkaramel
PHP | 503 lines | 215 code | 84 blank | 204 comment | 31 complexity | e782024486a44778575cf7aad44ae3c0 MD5 | raw file
  1. <?php // @codingStandardsIgnoreLine.
  2. /**
  3. * Abstract WP_Background_Process class.
  4. *
  5. * @package WP-Background-Processing
  6. * @extends WP_Async_Request
  7. */
  8. defined( 'ABSPATH' ) || exit;
  9. /**
  10. * Abstract WP_Background_Process class.
  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 = $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 );
  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 = $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. wp_die();
  276. }
  277. /**
  278. * Memory exceeded
  279. *
  280. * Ensures the batch process never exceeds 90%
  281. * of the maximum WordPress memory.
  282. *
  283. * @return bool
  284. */
  285. protected function memory_exceeded() {
  286. $memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory
  287. $current_memory = memory_get_usage( true );
  288. $return = false;
  289. if ( $current_memory >= $memory_limit ) {
  290. $return = true;
  291. }
  292. return apply_filters( $this->identifier . '_memory_exceeded', $return );
  293. }
  294. /**
  295. * Get memory limit
  296. *
  297. * @return int
  298. */
  299. protected function get_memory_limit() {
  300. if ( function_exists( 'ini_get' ) ) {
  301. $memory_limit = ini_get( 'memory_limit' );
  302. } else {
  303. // Sensible default.
  304. $memory_limit = '128M';
  305. }
  306. if ( ! $memory_limit || -1 === $memory_limit ) {
  307. // Unlimited, set to 32GB.
  308. $memory_limit = '32000M';
  309. }
  310. return intval( $memory_limit ) * 1024 * 1024;
  311. }
  312. /**
  313. * Time exceeded.
  314. *
  315. * Ensures the batch never exceeds a sensible time limit.
  316. * A timeout limit of 30s is common on shared hosting.
  317. *
  318. * @return bool
  319. */
  320. protected function time_exceeded() {
  321. $finish = $this->start_time + apply_filters( $this->identifier . '_default_time_limit', 20 ); // 20 seconds
  322. $return = false;
  323. if ( time() >= $finish ) {
  324. $return = true;
  325. }
  326. return apply_filters( $this->identifier . '_time_exceeded', $return );
  327. }
  328. /**
  329. * Complete.
  330. *
  331. * Override if applicable, but ensure that the below actions are
  332. * performed, or, call parent::complete().
  333. */
  334. protected function complete() {
  335. // Unschedule the cron healthcheck.
  336. $this->clear_scheduled_event();
  337. }
  338. /**
  339. * Schedule cron healthcheck
  340. *
  341. * @access public
  342. * @param mixed $schedules Schedules.
  343. * @return mixed
  344. */
  345. public function schedule_cron_healthcheck( $schedules ) {
  346. $interval = apply_filters( $this->identifier . '_cron_interval', 5 );
  347. if ( property_exists( $this, 'cron_interval' ) ) {
  348. $interval = apply_filters( $this->identifier . '_cron_interval', $this->cron_interval );
  349. }
  350. // Adds every 5 minutes to the existing schedules.
  351. $schedules[ $this->identifier . '_cron_interval' ] = array(
  352. 'interval' => MINUTE_IN_SECONDS * $interval,
  353. 'display' => sprintf( __( 'Every %d minutes', 'woocommerce' ), $interval ),
  354. );
  355. return $schedules;
  356. }
  357. /**
  358. * Handle cron healthcheck
  359. *
  360. * Restart the background process if not already running
  361. * and data exists in the queue.
  362. */
  363. public function handle_cron_healthcheck() {
  364. if ( $this->is_process_running() ) {
  365. // Background process already running.
  366. exit;
  367. }
  368. if ( $this->is_queue_empty() ) {
  369. // No data to process.
  370. $this->clear_scheduled_event();
  371. exit;
  372. }
  373. $this->handle();
  374. exit;
  375. }
  376. /**
  377. * Schedule event
  378. */
  379. protected function schedule_event() {
  380. if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) {
  381. wp_schedule_event( time(), $this->cron_interval_identifier, $this->cron_hook_identifier );
  382. }
  383. }
  384. /**
  385. * Clear scheduled event
  386. */
  387. protected function clear_scheduled_event() {
  388. $timestamp = wp_next_scheduled( $this->cron_hook_identifier );
  389. if ( $timestamp ) {
  390. wp_unschedule_event( $timestamp, $this->cron_hook_identifier );
  391. }
  392. }
  393. /**
  394. * Cancel Process
  395. *
  396. * Stop processing queue items, clear cronjob and delete batch.
  397. *
  398. */
  399. public function cancel_process() {
  400. if ( ! $this->is_queue_empty() ) {
  401. $batch = $this->get_batch();
  402. $this->delete( $batch->key );
  403. wp_clear_scheduled_hook( $this->cron_hook_identifier );
  404. }
  405. }
  406. /**
  407. * Task
  408. *
  409. * Override this method to perform any actions required on each
  410. * queue item. Return the modified item for further processing
  411. * in the next pass through. Or, return false to remove the
  412. * item from the queue.
  413. *
  414. * @param mixed $item Queue item to iterate over.
  415. *
  416. * @return mixed
  417. */
  418. abstract protected function task( $item );
  419. }