PageRenderTime 53ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-content/plugins/wp-multilang/includes/libraries/abstract-wp-background-process.php

https://bitbucket.org/FlintstoneChurchill/onoi
PHP | 502 lines | 217 code | 83 blank | 202 comment | 31 complexity | 80583d3f92db5cf1e39e780d00171a19 MD5 | raw file
Possible License(s): GPL-3.0
  1. <?php
  2. namespace WPM\Includes\Libraries;
  3. if ( ! defined( 'ABSPATH' ) ) {
  4. exit;
  5. }
  6. /**
  7. * Abstract WP_Background_Process class.
  8. *
  9. * @abstract
  10. * @package WP-Background-Processing
  11. * @extends WP_Async_Request
  12. */
  13. abstract class WP_Background_Process extends WP_Async_Request {
  14. /**
  15. * Action
  16. *
  17. * (default value: 'background_process')
  18. *
  19. * @var string
  20. * @access protected
  21. */
  22. protected $action = 'background_process';
  23. /**
  24. * Start time of current process.
  25. *
  26. * (default value: 0)
  27. *
  28. * @var int
  29. * @access protected
  30. */
  31. protected $start_time = 0;
  32. /**
  33. * Cron_hook_identifier
  34. *
  35. * @var mixed
  36. * @access protected
  37. */
  38. protected $cron_hook_identifier;
  39. /**
  40. * Cron_interval_identifier
  41. *
  42. * @var mixed
  43. * @access protected
  44. */
  45. protected $cron_interval_identifier;
  46. /**
  47. * Initiate new background process
  48. */
  49. public function __construct() {
  50. parent::__construct();
  51. $this->cron_hook_identifier = $this->identifier . '_cron';
  52. $this->cron_interval_identifier = $this->identifier . '_cron_interval';
  53. add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) );
  54. add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) );
  55. }
  56. /**
  57. * Dispatch
  58. *
  59. * @access public
  60. * @return void
  61. */
  62. public function dispatch() {
  63. // Schedule the cron healthcheck.
  64. $this->schedule_event();
  65. // Perform remote post.
  66. return parent::dispatch();
  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. return $this;
  78. }
  79. /**
  80. * Save queue
  81. *
  82. * @return $this
  83. */
  84. public function save() {
  85. $key = $this->generate_key();
  86. if ( ! empty( $this->data ) ) {
  87. update_site_option( $key, $this->data );
  88. }
  89. return $this;
  90. }
  91. /**
  92. * Update queue
  93. *
  94. * @param string $key Key.
  95. * @param array $data Data.
  96. *
  97. * @return $this
  98. */
  99. public function update( $key, $data ) {
  100. if ( ! empty( $data ) ) {
  101. update_site_option( $key, $data );
  102. }
  103. return $this;
  104. }
  105. /**
  106. * Delete queue
  107. *
  108. * @param string $key Key.
  109. *
  110. * @return $this
  111. */
  112. public function delete( $key ) {
  113. delete_site_option( $key );
  114. return $this;
  115. }
  116. /**
  117. * Generate key
  118. *
  119. * Generates a unique key based on microtime. Queue items are
  120. * given a unique key so that they can be merged upon save.
  121. *
  122. * @param int $length Length.
  123. *
  124. * @return string
  125. */
  126. protected function generate_key( $length = 64 ) {
  127. $unique = md5( microtime() . rand() );
  128. $prepend = $this->identifier . '_batch_';
  129. return substr( $prepend . $unique, 0, $length );
  130. }
  131. /**
  132. * Maybe process queue
  133. *
  134. * Checks whether data exists within the queue and that
  135. * the process is not already running.
  136. */
  137. public function maybe_handle() {
  138. // Don't lock up other requests while processing
  139. session_write_close();
  140. if ( $this->is_process_running() ) {
  141. // Background process already running.
  142. wp_die();
  143. }
  144. if ( $this->is_queue_empty() ) {
  145. // No data to process.
  146. wp_die();
  147. }
  148. check_ajax_referer( $this->identifier, 'nonce' );
  149. $this->handle();
  150. wp_die();
  151. }
  152. /**
  153. * Is queue empty
  154. *
  155. * @return bool
  156. */
  157. protected function is_queue_empty() {
  158. global $wpdb;
  159. $table = $wpdb->options;
  160. $column = 'option_name';
  161. if ( is_multisite() ) {
  162. $table = $wpdb->sitemeta;
  163. $column = 'meta_key';
  164. }
  165. $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
  166. $count = $wpdb->get_var( $wpdb->prepare( "
  167. SELECT COUNT(*)
  168. FROM {$table}
  169. WHERE {$column} LIKE %s
  170. ", $key ) );
  171. return ( $count > 0 ) ? false : true;
  172. }
  173. /**
  174. * Is process running
  175. *
  176. * Check whether the current process is already running
  177. * in a background process.
  178. */
  179. protected function is_process_running() {
  180. if ( get_site_transient( $this->identifier . '_process_lock' ) ) {
  181. // Process already running.
  182. return true;
  183. }
  184. return false;
  185. }
  186. /**
  187. * Lock process
  188. *
  189. * Lock the process so that multiple instances can't run simultaneously.
  190. * Override if applicable, but the duration should be greater than that
  191. * defined in the time_exceeded() method.
  192. */
  193. protected function lock_process() {
  194. $this->start_time = time(); // Set start time of current process.
  195. $lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; // 1 minute
  196. $lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration );
  197. set_site_transient( $this->identifier . '_process_lock', microtime(), $lock_duration );
  198. }
  199. /**
  200. * Unlock process
  201. *
  202. * Unlock the process so that other instances can spawn.
  203. *
  204. * @return $this
  205. */
  206. protected function unlock_process() {
  207. delete_site_transient( $this->identifier . '_process_lock' );
  208. return $this;
  209. }
  210. /**
  211. * Get batch
  212. *
  213. * @return \stdClass Return the first batch from the queue
  214. */
  215. protected function get_batch() {
  216. global $wpdb;
  217. $table = $wpdb->options;
  218. $column = 'option_name';
  219. $key_column = 'option_id';
  220. $value_column = 'option_value';
  221. if ( is_multisite() ) {
  222. $table = $wpdb->sitemeta;
  223. $column = 'meta_key';
  224. $key_column = 'meta_id';
  225. $value_column = 'meta_value';
  226. }
  227. $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
  228. $query = $wpdb->get_row( $wpdb->prepare( "
  229. SELECT *
  230. FROM {$table}
  231. WHERE {$column} LIKE %s
  232. ORDER BY {$key_column} ASC
  233. LIMIT 1
  234. ", $key ) );
  235. $batch = new \stdClass();
  236. $batch->key = $query->$column;
  237. $batch->data = maybe_unserialize( $query->$value_column );
  238. return $batch;
  239. }
  240. /**
  241. * Handle
  242. *
  243. * Pass each queue item to the task handler, while remaining
  244. * within server memory and time limit constraints.
  245. */
  246. protected function handle() {
  247. $this->lock_process();
  248. do {
  249. $batch = $this->get_batch();
  250. foreach ( $batch->data as $key => $value ) {
  251. $task = $this->task( $value );
  252. if ( false !== $task ) {
  253. $batch->data[ $key ] = $task;
  254. } else {
  255. unset( $batch->data[ $key ] );
  256. }
  257. if ( $this->time_exceeded() || $this->memory_exceeded() ) {
  258. // Batch limits reached.
  259. break;
  260. }
  261. }
  262. // Update or delete current batch.
  263. if ( ! empty( $batch->data ) ) {
  264. $this->update( $batch->key, $batch->data );
  265. } else {
  266. $this->delete( $batch->key );
  267. }
  268. } while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() );
  269. $this->unlock_process();
  270. // Start next batch or complete process.
  271. if ( ! $this->is_queue_empty() ) {
  272. $this->dispatch();
  273. } else {
  274. $this->complete();
  275. }
  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 === intval( $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_identifier );
  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', 'wp-multilang' ), $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. }