balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

background-task.php (9058B)


      1 <?php
      2 namespace Elementor\Core\Base;
      3 
      4 use Elementor\Plugin;
      5 use Elementor\Core\Base\BackgroundProcess\WP_Background_Process;
      6 
      7 /**
      8  * Based on https://github.com/woocommerce/woocommerce/blob/master/includes/abstracts/class-wc-background-process.php
      9  * & https://github.com/woocommerce/woocommerce/blob/master/includes/class-wc-background-updater.php
     10  */
     11 
     12 defined( 'ABSPATH' ) || exit;
     13 
     14 /**
     15  * WC_Background_Process class.
     16  */
     17 abstract class Background_Task extends WP_Background_Process {
     18 	protected $current_item;
     19 
     20 	/**
     21 	 * Dispatch updater.
     22 	 *
     23 	 * Updater will still run via cron job if this fails for any reason.
     24 	 */
     25 	public function dispatch() {
     26 		$dispatched = parent::dispatch();
     27 
     28 		if ( is_wp_error( $dispatched ) ) {
     29 			wp_die( esc_html( $dispatched ) );
     30 		}
     31 	}
     32 
     33 	public function query_col( $sql ) {
     34 		global $wpdb;
     35 
     36 		// Add Calc.
     37 		$item = $this->get_current_item();
     38 		if ( empty( $item['total'] ) ) {
     39 			$sql = preg_replace( '/^SELECT/', 'SELECT SQL_CALC_FOUND_ROWS', $sql );
     40 		}
     41 
     42 		// Add offset & limit.
     43 		$sql = preg_replace( '/;$/', '', $sql );
     44 		$sql .= ' LIMIT %d, %d;';
     45 
     46 		$results = $wpdb->get_col( $wpdb->prepare( $sql, $this->get_current_offset(), $this->get_limit() ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
     47 
     48 		if ( ! empty( $results ) ) {
     49 			$this->set_total();
     50 		}
     51 
     52 		return $results;
     53 	}
     54 
     55 	public function should_run_again( $updated_rows ) {
     56 		return count( $updated_rows ) === $this->get_limit();
     57 	}
     58 
     59 	public function get_current_offset() {
     60 		$limit = $this->get_limit();
     61 		return ( $this->current_item['iterate_num'] - 1 ) * $limit;
     62 	}
     63 
     64 	public function get_limit() {
     65 		return $this->manager->get_query_limit();
     66 	}
     67 
     68 	public function set_total() {
     69 		global $wpdb;
     70 
     71 		if ( empty( $this->current_item['total'] ) ) {
     72 			$total_rows = $wpdb->get_var( 'SELECT FOUND_ROWS();' );
     73 			$total_iterates = ceil( $total_rows / $this->get_limit() );
     74 			$this->current_item['total'] = $total_iterates;
     75 		}
     76 	}
     77 
     78 	/**
     79 	 * Complete
     80 	 *
     81 	 * Override if applicable, but ensure that the below actions are
     82 	 * performed, or, call parent::complete().
     83 	 */
     84 	protected function complete() {
     85 		$this->manager->on_runner_complete( true );
     86 
     87 		parent::complete();
     88 	}
     89 
     90 	public function continue_run() {
     91 		// Used to fire an action added in WP_Background_Process::_construct() that calls WP_Background_Process::handle_cron_healthcheck().
     92 		// This method will make sure the database updates are executed even if cron is disabled. Nothing will happen if the updates are already running.
     93 		do_action( $this->cron_hook_identifier );
     94 	}
     95 
     96 	/**
     97 	 * @return mixed
     98 	 */
     99 	public function get_current_item() {
    100 		return $this->current_item;
    101 	}
    102 
    103 	/**
    104 	 * Get batch.
    105 	 *
    106 	 * @return \stdClass Return the first batch from the queue.
    107 	 */
    108 	protected function get_batch() {
    109 		$batch = parent::get_batch();
    110 		$batch->data = array_filter( (array) $batch->data );
    111 
    112 		return $batch;
    113 	}
    114 
    115 	/**
    116 	 * Handle cron healthcheck
    117 	 *
    118 	 * Restart the background process if not already running
    119 	 * and data exists in the queue.
    120 	 */
    121 	public function handle_cron_healthcheck() {
    122 		if ( $this->is_process_running() ) {
    123 			// Background process already running.
    124 			return;
    125 		}
    126 
    127 		if ( $this->is_queue_empty() ) {
    128 			// No data to process.
    129 			$this->clear_scheduled_event();
    130 			return;
    131 		}
    132 
    133 		$this->handle();
    134 	}
    135 
    136 	/**
    137 	 * Schedule fallback event.
    138 	 */
    139 	protected function schedule_event() {
    140 		if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) {
    141 			wp_schedule_event( time() + 10, $this->cron_interval_identifier, $this->cron_hook_identifier );
    142 		}
    143 	}
    144 
    145 	/**
    146 	 * Is the updater running?
    147 	 *
    148 	 * @return boolean
    149 	 */
    150 	public function is_running() {
    151 		return false === $this->is_queue_empty();
    152 	}
    153 
    154 	/**
    155 	 * See if the batch limit has been exceeded.
    156 	 *
    157 	 * @return bool
    158 	 */
    159 	protected function batch_limit_exceeded() {
    160 		return $this->time_exceeded() || $this->memory_exceeded();
    161 	}
    162 
    163 	/**
    164 	 * Handle.
    165 	 *
    166 	 * Pass each queue item to the task handler, while remaining
    167 	 * within server memory and time limit constraints.
    168 	 */
    169 	protected function handle() {
    170 		$this->manager->on_runner_start();
    171 
    172 		$this->lock_process();
    173 
    174 		do {
    175 			$batch = $this->get_batch();
    176 
    177 			foreach ( $batch->data as $key => $value ) {
    178 				$task = $this->task( $value );
    179 
    180 				if ( false !== $task ) {
    181 					$batch->data[ $key ] = $task;
    182 				} else {
    183 					unset( $batch->data[ $key ] );
    184 				}
    185 
    186 				if ( $this->batch_limit_exceeded() ) {
    187 					// Batch limits reached.
    188 					break;
    189 				}
    190 			}
    191 
    192 			// Update or delete current batch.
    193 			if ( ! empty( $batch->data ) ) {
    194 				$this->update( $batch->key, $batch->data );
    195 			} else {
    196 				$this->delete( $batch->key );
    197 			}
    198 		} while ( ! $this->batch_limit_exceeded() && ! $this->is_queue_empty() );
    199 
    200 		$this->unlock_process();
    201 
    202 		// Start next batch or complete process.
    203 		if ( ! $this->is_queue_empty() ) {
    204 			$this->dispatch();
    205 		} else {
    206 			$this->complete();
    207 		}
    208 	}
    209 
    210 	/**
    211 	 * Use the protected `is_process_running` method as a public method.
    212 	 * @return bool
    213 	 */
    214 	public function is_process_locked() {
    215 		return $this->is_process_running();
    216 	}
    217 
    218 	public function handle_immediately( $callbacks ) {
    219 		$this->manager->on_runner_start();
    220 
    221 		$this->lock_process();
    222 
    223 		foreach ( $callbacks as $callback ) {
    224 			$item = [
    225 				'callback' => $callback,
    226 			];
    227 
    228 			do {
    229 				$item = $this->task( $item );
    230 			} while ( $item );
    231 		}
    232 
    233 		$this->unlock_process();
    234 	}
    235 
    236 	/**
    237 	 * Task
    238 	 *
    239 	 * Override this method to perform any actions required on each
    240 	 * queue item. Return the modified item for further processing
    241 	 * in the next pass through. Or, return false to remove the
    242 	 * item from the queue.
    243 	 *
    244 	 * @param array $item
    245 	 *
    246 	 * @return array|bool
    247 	 */
    248 	protected function task( $item ) {
    249 		$result = false;
    250 
    251 		if ( ! isset( $item['iterate_num'] ) ) {
    252 			$item['iterate_num'] = 1;
    253 		}
    254 
    255 		$logger = Plugin::$instance->logger->get_logger();
    256 		$callback = $this->format_callback_log( $item );
    257 
    258 		if ( is_callable( $item['callback'] ) ) {
    259 			$progress = '';
    260 
    261 			if ( 1 < $item['iterate_num'] ) {
    262 				if ( empty( $item['total'] ) ) {
    263 					$progress = sprintf( '(x%s)', $item['iterate_num'] );
    264 				} else {
    265 					$percent = ceil( $item['iterate_num'] / ( $item['total'] / 100 ) );
    266 					$progress = sprintf( '(%s of %s, %s%%)', $item['iterate_num'], $item['total'], $percent );
    267 				}
    268 			}
    269 
    270 			$logger->info( sprintf( '%s Start %s', $callback, $progress ) );
    271 
    272 			$this->current_item = $item;
    273 
    274 			$result = (bool) call_user_func( $item['callback'], $this );
    275 
    276 			// get back the updated item.
    277 			$item = $this->current_item;
    278 			$this->current_item = null;
    279 
    280 			if ( $result ) {
    281 				if ( empty( $item['total'] ) ) {
    282 					$logger->info( sprintf( '%s callback needs to run again', $callback ) );
    283 				} elseif ( 1 === $item['iterate_num'] ) {
    284 					$logger->info( sprintf( '%s callback needs to run more %d times', $callback, $item['total'] - $item['iterate_num'] ) );
    285 				}
    286 
    287 				$item['iterate_num']++;
    288 			} else {
    289 				$logger->info( sprintf( '%s Finished', $callback ) );
    290 			}
    291 		} else {
    292 			$logger->notice( sprintf( 'Could not find %s callback', $callback ) );
    293 		}
    294 
    295 		return $result ? $item : false;
    296 	}
    297 
    298 	/**
    299 	 * Schedule cron healthcheck.
    300 	 *
    301 	 * @param array $schedules Schedules.
    302 	 * @return array
    303 	 */
    304 	public function schedule_cron_healthcheck( $schedules ) {
    305 		$interval = apply_filters( $this->identifier . '_cron_interval', 5 );
    306 
    307 		// Adds every 5 minutes to the existing schedules.
    308 		$schedules[ $this->identifier . '_cron_interval' ] = array(
    309 			'interval' => MINUTE_IN_SECONDS * $interval,
    310 			/* translators: %d: interval */
    311 			'display' => sprintf( esc_html__( 'Every %d minutes', 'elementor' ), $interval ),
    312 		);
    313 
    314 		return $schedules;
    315 	}
    316 
    317 	/**
    318 	 * See if the batch limit has been exceeded.
    319 	 *
    320 	 * @return bool
    321 	 */
    322 	public function is_memory_exceeded() {
    323 		return $this->memory_exceeded();
    324 	}
    325 
    326 	/**
    327 	 * Delete all batches.
    328 	 *
    329 	 * @return self
    330 	 */
    331 	public function delete_all_batches() {
    332 		global $wpdb;
    333 
    334 		$table = $wpdb->options;
    335 		$column = 'option_name';
    336 
    337 		if ( is_multisite() ) {
    338 			$table = $wpdb->sitemeta;
    339 			$column = 'meta_key';
    340 		}
    341 
    342 		$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
    343 
    344 		$wpdb->query( $wpdb->prepare( "DELETE FROM {$table} WHERE {$column} LIKE %s", $key ) ); // @codingStandardsIgnoreLine.
    345 
    346 		return $this;
    347 	}
    348 
    349 	/**
    350 	 * Kill process.
    351 	 *
    352 	 * Stop processing queue items, clear cronjob and delete all batches.
    353 	 */
    354 	public function kill_process() {
    355 		if ( ! $this->is_queue_empty() ) {
    356 			$this->delete_all_batches();
    357 			wp_clear_scheduled_hook( $this->cron_hook_identifier );
    358 		}
    359 	}
    360 
    361 	public function set_current_item( $item ) {
    362 		$this->current_item = $item;
    363 	}
    364 
    365 	protected function format_callback_log( $item ) {
    366 		return implode( '::', (array) $item['callback'] );
    367 	}
    368 
    369 	/**
    370 	 * @var \Elementor\Core\Base\Background_Task_Manager
    371 	 */
    372 	protected $manager;
    373 
    374 	public function __construct( $manager ) {
    375 		$this->manager = $manager;
    376 		// Uses unique prefix per blog so each blog has separate queue.
    377 		$this->prefix = 'elementor_' . get_current_blog_id();
    378 		$this->action = $this->manager->get_action();
    379 
    380 		parent::__construct();
    381 	}
    382 }