├── .gitignore
├── README.md
├── assets
└── processor.js
├── changelog.txt
├── composer.json
├── composer.lock
├── examples
├── class-example-batch.php
└── processing.gif
├── includes
├── class-batch-ajax-handler.php
├── class-batch-item.php
├── class-batch-list-table.php
├── class-batch-processor-admin.php
├── class-batch-processor.php
├── class-batch.php
├── class-bp-helper.php
└── class-bp-singleton.php
├── views
├── batch-list.php
├── batch-manage.php
└── batch-view.php
└── wp-batch-processing.php
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/
2 | vendor/*
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WP Batch Processing
2 |
3 | WP Batch Processing is WordPress plugin for creating batches of data and processing the data items one by one. It allows you to define a batch and to process the queued batch items one by one. There is also option to resume/continue later in case your internet connection goes down.
4 |
5 | 
6 |
7 | ## Installation
8 |
9 | There are two ways to install this library:
10 |
11 | 1. Install this library as a plugin. It doesn't require anything from below or,
12 |
13 | 2. Install it as a composer package (read the **notes** bellow)
14 |
15 | ```
16 | composer require gdarko/wp-batch-processing
17 | ```
18 |
19 | ```php
20 | WP_Batch_Processor::boot();
21 | ```
22 |
23 | **Note**: The `boot()` method should be called only if you install the plugin via composer somewhere in your plugin or theme.
24 |
25 | **Note**: If using composer, the library will attempt to find its path, however if you see messed up screen it means that it was unable to find the stylesheet/JS files and you will need to define them manually before `boot()` method.
26 |
27 | ```php
28 | // Manually define the path constants to eliminate
29 | // possible errors when resolving the paths and also
30 | // include trailing slash at the end.
31 |
32 | if ( ! defined('WP_BP_PATH')) {
33 | define('WP_BP_PATH', '/path/to/wp-content/plugins/your-plugin/libraries/wp-batch-processing/');
34 | }
35 |
36 | if ( ! defined('WP_BP_URL')) {
37 | define('WP_BP_URL', 'https://site.com/wp-content/plugins/your-plugin/libraries/wp-batch-processing/');
38 | }
39 |
40 | WP_Batch_Processor::boot();
41 | ```
42 |
43 |
44 | ## How it works
45 |
46 | To define a batch you just need to extend the class `WP_Batch` and later register it. Follow the examples below to learn how.
47 |
48 | The class provides the following attributes and methods
49 |
50 | * `$id` - Identifies the batch (must be unique),
51 | * `$title` - Shown in the admin area,
52 | * `setup()` - Method that you use it to fed your data with `WP_Batch_Item` instances,
53 | * `process(WP_Batch_Item $item)` - Method that will be used to process each next item in the batch
54 |
55 | ```php
56 |
57 | if ( class_exists( 'WP_Batch' ) ) {
58 |
59 | /**
60 | * Class MY_Example_Batch
61 | */
62 | class MY_Example_Batch extends WP_Batch {
63 |
64 | /**
65 | * Unique identifier of each batch
66 | * @var string
67 | */
68 | public $id = 'email_post_authors';
69 |
70 | /**
71 | * Describe the batch
72 | * @var string
73 | */
74 | public $title = 'Email Post Authors';
75 |
76 | /**
77 | * To setup the batch data use the push() method to add WP_Batch_Item instances to the queue.
78 | *
79 | * Note: If the operation of obtaining data is expensive, cache it to avoid slowdowns.
80 | *
81 | * @return void
82 | */
83 | public function setup() {
84 |
85 | $users = get_users( array(
86 | 'number' => '40',
87 | 'role' => 'author',
88 | ) );
89 |
90 | foreach ( $users as $user ) {
91 | $this->push( new WP_Batch_Item( $user->ID, array( 'author_id' => $user->ID ) ) );
92 | }
93 | }
94 |
95 | /**
96 | * Handles processing of batch item. One at a time.
97 | *
98 | * In order to work it correctly you must return values as follows:
99 | *
100 | * - TRUE - If the item was processed successfully.
101 | * - WP_Error instance - If there was an error. Add message to display it in the admin area.
102 | *
103 | * @param WP_Batch_Item $item
104 | *
105 | * @return bool|\WP_Error
106 | */
107 | public function process( $item ) {
108 |
109 | // Retrieve the custom data
110 | $author_id = $item->get_value( 'author_id' );
111 |
112 | // Return WP_Error if the item processing failed (In our case we simply skip author with user id 5)
113 | if ( $author_id == 5 ) {
114 | return new WP_Error( 302, "Author skipped" );
115 | }
116 |
117 | // Do the expensive processing here.
118 | // ...
119 |
120 | // Return true if the item processing is successful.
121 | return true;
122 | }
123 |
124 | /**
125 | * Called when specific process is finished (all items were processed).
126 | * This method can be overriden in the process class.
127 | * @return void
128 | */
129 | public function finish() {
130 | // Do something after process is finished.
131 | // You have $this->items, etc.
132 | }
133 | }
134 | }
135 |
136 | ```
137 |
138 | After creating the class, class instance needs to be registered in order to be available in the batches list in the admin area.
139 |
140 | ```php
141 | /**
142 | * Initialize the batches.
143 | */
144 | function wp_batch_processing_init() {
145 | $batch = new MY_Example_Batch();
146 | WP_Batch_Processor::get_instance()->register( $batch );
147 | }
148 | add_action( 'wp_batch_processing_init', 'wp_batch_processing_init', 15, 1 );
149 | ```
150 |
151 | That's it.
152 |
153 | ## Filters and Actions
154 |
155 | Set delay between processing items. Default is 0 (no delay)
156 | ```php
157 | function wp_bp_my_custom_delay($delay) {
158 | return 2; // in seconds
159 | }
160 | add_filter('wp_batch_processing_delay', 'wp_bp_my_custom_delay', 10, 1);
161 | ```
162 |
163 | ## Example use cases
164 |
165 | The tool can be used in many different ways. For example
166 |
167 | * Importing data
168 | * Downloading data
169 | * Emailing
170 | * Database modifications
171 |
172 |
173 | ## Contribute
174 |
175 | If you notice a bug or you want to propose improvements feel free to create a pull request!
176 |
177 |
178 | ## License
179 |
180 | The plugin is licensed under GPL v2
181 |
182 | ```
183 | Copyright (C) 2021 Darko Gjorgjijoski (https://darkog.com)
184 |
185 | This file is part of WP Batch Processing
186 |
187 | WP Batch Processing is free software: you can redistribute it and/or modify
188 | it under the terms of the GNU General Public License as published by
189 | the Free Software Foundation, either version 2 of the License, or
190 | (at your option) any later version.
191 |
192 | WP Batch Processing is distributed in the hope that it will be useful,
193 | but WITHOUT ANY WARRANTY; without even the implied warranty of
194 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
195 | GNU General Public License for more details.
196 |
197 | You should have received a copy of the GNU General Public License
198 | along with WP Batch Processing. If not, see .
199 | ```
200 |
--------------------------------------------------------------------------------
/assets/processor.js:
--------------------------------------------------------------------------------
1 | (function($){
2 |
3 | var reload = function() {
4 | window.location.href = window.location.href;
5 | };
6 |
7 | var stop_process = function() {
8 | reload();
9 | };
10 |
11 | var restart_batch = function() {
12 | var nonce = DgBatchRunner.nonce;
13 | var batch = DgBatchRunner.batch_id;
14 | var ajax_url = DgBatchRunner.ajax_url;
15 | $.ajax({
16 | url: ajax_url + '?action=dg_restart_batch&nonce='+nonce,
17 | type: 'POST',
18 | data: {batch_id: batch},
19 | cache: false,
20 | success: function(response) {
21 | reload();
22 | }
23 | });
24 | };
25 |
26 | var process_next_item = function() {
27 | var nonce = DgBatchRunner.nonce;
28 | var batch = DgBatchRunner.batch_id;
29 | var ajax_url = DgBatchRunner.ajax_url;
30 | var delay = DgBatchRunner.delay;
31 | $.ajax({
32 | url: ajax_url + '?action=dg_process_next_batch_item&nonce='+nonce,
33 | type: 'POST',
34 | data: {batch_id: batch},
35 | cache: false,
36 | beforeSend: function() {
37 | $('#batch-process-start').text(DgBatchRunner.text.processing).prop('disabled', true);
38 | },
39 | success: function(response) {
40 | $(document).trigger('itemprocessed', [response])
41 | if(!response.data.is_finished) {
42 | if(delay > 0) {
43 | console.log('Waiting ' + DgBatchRunner.delay + ' seconds before processing next item.');
44 | setTimeout(function(){
45 | process_next_item();
46 | }, delay * 1000);
47 | } else {
48 | process_next_item();
49 | }
50 | } else {
51 | $('#batch-process-start').text(DgBatchRunner.text.start);
52 | }
53 | },
54 | error: function() {
55 | alert('HTTP Error.');
56 | },
57 | });
58 |
59 | }
60 |
61 | $(document).on('itemprocessed', function(e, response){
62 | var percentage = response.data.percentage;
63 | $('.batch-process-progress-bar-inner').css('width', percentage+'%');
64 | $('#batch-process-total').text(response.data.total_items);
65 | $('#batch-process-processed').text(response.data.total_processed);
66 | $('#batch-process-percentage').text('('+percentage+'%)');
67 | var color = response.success ? 'green' : 'red';
68 | var message = ''+response.data.message+'';
69 | $('.batch-process-current-item').html(message);
70 | if(response.data.is_finished) {
71 | $('#batch-process-start, #batch-process-stop').prop('disabled', true);
72 | }
73 | if(!response.success) {
74 | $('#batch-errors').show();
75 | $('#batch-errors-list').append('
'+response.data.message+'
');
76 | }
77 | });
78 |
79 | $(document).on('click', '#batch-process-start', function(e){
80 | e.preventDefault();
81 | process_next_item();
82 | });
83 |
84 | $(document).on('click', '#batch-process-stop', function(e){
85 | e.preventDefault();
86 | stop_process();
87 | });
88 |
89 | $(document).on('click', '#batch-process-restart', function(e){
90 | e.preventDefault();
91 | if(confirm(DgBatchRunner.text.confirm_restart)) {
92 | restart_batch();
93 | }
94 | });
95 |
96 |
97 | })(jQuery);
--------------------------------------------------------------------------------
/changelog.txt:
--------------------------------------------------------------------------------
1 | = Version 1.0.1 (2019-07-14) =
2 | - [New] Added finish() method that can be overriden in the derived classes. It is called when specific batch finished processing.
3 | - [Fix] Division by zero warnings
4 |
5 | = Version 1.0.0 (2019-06-30) =
6 | - Initial version
7 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gdarko/wp-batch-processing",
3 | "description": "Easily process large batches of data in WordPress. Provide the data, setup the processing procedure, run the batch processor from the admin dashboard. Profit.",
4 | "type": "library",
5 | "require": {
6 | "php": ">=5.3"
7 | },
8 | "license": "GPL-2.0-or-later",
9 | "authors": [
10 | {
11 | "name": "Darko Gjorgjijoski",
12 | "email": "dg@darkog.com"
13 | }
14 | ],
15 | "autoload": {
16 | "classmap": [
17 | "includes/"
18 | ]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "fac3bfb16f48815eb401dd55eefbde96",
8 | "packages": [],
9 | "packages-dev": [],
10 | "aliases": [],
11 | "minimum-stability": "stable",
12 | "stability-flags": [],
13 | "prefer-stable": false,
14 | "prefer-lowest": false,
15 | "platform": {
16 | "php": ">=5.3"
17 | },
18 | "platform-dev": [],
19 | "plugin-api-version": "2.1.0"
20 | }
21 |
--------------------------------------------------------------------------------
/examples/class-example-batch.php:
--------------------------------------------------------------------------------
1 | .
19 | **********************************************************************/
20 |
21 | if ( ! defined( 'ABSPATH' ) ) {
22 | die( 'Direct access is not allowed.' );
23 | }
24 |
25 | if ( class_exists( 'WP_Batch' ) ) {
26 |
27 | /**
28 | * Class MY_Example_Batch
29 | */
30 | class MY_Example_Batch extends WP_Batch {
31 |
32 | /**
33 | * Unique identifier of each batch
34 | * @var string
35 | */
36 | public $id = 'email_post_authors';
37 |
38 | /**
39 | * Describe the batch
40 | * @var string
41 | */
42 | public $title = 'Email Post Authors';
43 |
44 | /**
45 | * To setup the batch data use the push() method to add WP_Batch_Item instances to the queue.
46 | *
47 | * Note: If the operation of obtaining data is expensive, cache it to avoid slowdowns.
48 | *
49 | * @return void
50 | */
51 | public function setup() {
52 |
53 | $users = get_users( array(
54 | 'number' => '40',
55 | 'role' => 'author',
56 | ) );
57 |
58 | foreach ( $users as $user ) {
59 | $this->push( new WP_Batch_Item( $user->ID, array( 'author_id' => $user->ID ) ) );
60 | }
61 | }
62 |
63 | /**
64 | * Handles processing of batch item. One at a time.
65 | *
66 | * In order to work it correctly you must return values as follows:
67 | *
68 | * - TRUE - If the item was processed successfully.
69 | * - WP_Error instance - If there was an error. Add message to display it in the admin area.
70 | *
71 | * @param WP_Batch_Item $item
72 | *
73 | * @return bool|\WP_Error
74 | */
75 | public function process( $item ) {
76 |
77 | // Retrieve the custom data
78 | $author_id = $item->get_value( 'author_id' );
79 |
80 | // Return WP_Error if the item processing failed (In our case we simply skip author with user id 5)
81 | if ( $author_id == 5 ) {
82 | return new WP_Error( 302, "Author skipped" );
83 | }
84 |
85 | // Do the expensive processing here. eg. Sending email.
86 | // ...
87 |
88 | // Return true if the item processing is successful.
89 | return true;
90 | }
91 |
92 | /**
93 | * Called when specific process is finished (all items were processed).
94 | * This method can be overriden in the process class.
95 | * @return void
96 | */
97 | public function finish() {
98 | // Do something after process is finished.
99 | // You have $this->items, or other data you can set.
100 | }
101 |
102 | }
103 |
104 | /**
105 | * Initialize the batches.
106 | */
107 | function wp_batch_processing_init() {
108 | $batch = new MY_Example_Batch();
109 | WP_Batch_Processor::get_instance()->register( $batch );
110 | }
111 |
112 | add_action( 'wp_batch_processing_init', 'wp_batch_processing_init', 15, 1 );
113 | }
114 |
115 |
116 |
--------------------------------------------------------------------------------
/examples/processing.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdarko/wp-batch-processing/be2a7a7b30423df09a4dd1c8098c71fa249d49dd/examples/processing.gif
--------------------------------------------------------------------------------
/includes/class-batch-ajax-handler.php:
--------------------------------------------------------------------------------
1 | .
19 | **********************************************************************/
20 |
21 | if ( ! defined( 'ABSPATH' ) ) {
22 | die( 'Direct access is not allowed.' );
23 | }
24 |
25 | /**
26 | * Class WP_Batch_Processing_Ajax_Handler
27 | */
28 | class WP_Batch_Processing_Ajax_Handler {
29 |
30 | use WP_BP_Singleton;
31 |
32 | /**
33 | * Setup the ajax endpoints
34 | */
35 | protected function init() {
36 | add_action( 'wp_ajax_dg_process_next_batch_item', array( $this, 'process_next_item' ) );
37 | add_action( 'wp_ajax_dg_restart_batch', array( $this, 'restart_batch' ) );
38 | }
39 |
40 | /**
41 | * This is used to handle the processing of each item
42 | * and return the status to inform the user.
43 | */
44 | public function process_next_item() {
45 |
46 | // Check ajax referrer
47 | if ( ! check_ajax_referer( WP_Batch_Processor_Admin::NONCE, 'nonce', false ) ) {
48 | wp_send_json_error( array(
49 | 'message' => 'Permission denied.',
50 | ) );
51 | exit();
52 | }
53 |
54 | // Validate the batch id.
55 | $batch_id = isset( $_REQUEST['batch_id'] ) ? $_REQUEST['batch_id'] : false;
56 | if ( ! $batch_id ) {
57 | wp_send_json_error( array(
58 | 'message' => 'Invalid batch id',
59 | ) );
60 | exit();
61 | }
62 |
63 | // Get the batch object
64 | $batch = WP_Batch_Processor::get_instance()->get_batch( $batch_id );
65 |
66 | // Process the next item.
67 | $next_item = $batch->get_next_item();
68 |
69 | // No next item for processing. The batch processing is finished, probably.
70 | $is_finished = ( false === $next_item );
71 |
72 | if ( $is_finished ) {
73 | $total_processed = $batch->get_processed_count();
74 | $total_items = $batch->get_items_count();
75 | $percentage = $batch->get_percentage();
76 | $batch->finish();
77 | wp_send_json_success( array(
78 | 'message' => apply_filters( 'dg_batch_item_finished_message', __( 'Processing finished.', 'wp-batch-processing' ) ),
79 | 'is_finished' => 1,
80 | 'total_processed' => $total_processed,
81 | 'total_items' => $total_items,
82 | 'percentage' => $percentage,
83 | ) );
84 | } else {
85 | @set_time_limit( 0 );
86 | $response = $batch->process( $next_item );
87 | $batch->mark_as_processed( $next_item->id );
88 | $total_processed = $batch->get_processed_count();
89 | $total_items = $batch->get_items_count();
90 | $percentage = $batch->get_percentage();
91 | if ( is_wp_error( $response ) ) {
92 | $error_message = apply_filters( 'dg_batch_item_error_message', 'Error processing item with id ' . $next_item->id . ': ' . $response->get_error_message(), $next_item );
93 | wp_send_json_error( array(
94 | 'message' => $error_message,
95 | 'is_finished' => 0,
96 | 'total_processed' => $total_processed,
97 | 'total_items' => $total_items,
98 | 'percentage' => $percentage,
99 | ) );
100 | } else {
101 | $success_message = apply_filters( 'dg_batch_item_success_message', 'Processed item with id ' . $next_item->id, $next_item );
102 | wp_send_json_success( array(
103 | 'message' => $success_message,
104 | 'is_finished' => 0,
105 | 'total_processed' => $total_processed,
106 | 'total_items' => $total_items,
107 | 'percentage' => $percentage,
108 | ) );
109 | }
110 | exit;
111 |
112 | }
113 | }
114 |
115 | /**
116 | * Used to restart the batch.
117 | * Just clear the data.
118 | */
119 | public function restart_batch() {
120 | // Check ajax referrer
121 | if ( ! check_ajax_referer( WP_Batch_Processor_Admin::NONCE, 'nonce', false ) ) {
122 | wp_send_json_error( array(
123 | 'message' => 'Permission denied.',
124 | ) );
125 | exit;
126 | }
127 | // Validate the batch id.
128 | $batch_id = isset( $_REQUEST['batch_id'] ) ? $_REQUEST['batch_id'] : false;
129 | if ( ! $batch_id ) {
130 | wp_send_json_error( array(
131 | 'message' => 'Invalid batch id',
132 | ) );
133 | exit;
134 | }
135 | // Get the batch object
136 | $batch = WP_Batch_Processor::get_instance()->get_batch( $batch_id );
137 | // Restart the batch.
138 | $batch->restart();
139 | // Send json
140 | wp_send_json_success();
141 | }
142 | }
143 |
144 |
--------------------------------------------------------------------------------
/includes/class-batch-item.php:
--------------------------------------------------------------------------------
1 | .
19 | **********************************************************************/
20 |
21 | if ( ! defined( 'ABSPATH' ) ) {
22 | die( 'Direct access is not allowed.' );
23 | }
24 |
25 | /**
26 | * Class WP_Batch_Item
27 | */
28 | class WP_Batch_Item {
29 |
30 | /**
31 | * Unique identifier of the batch item
32 | * @var int
33 | */
34 | public $id;
35 |
36 | /**
37 | * Additional data for the item
38 | * @var mixed
39 | */
40 | public $data;
41 |
42 | /**
43 | * WP_Batch_Item constructor
44 | *
45 | * @param $id
46 | * @param $data
47 | */
48 | public function __construct($id, $data = null) {
49 | $this->id = $id;
50 | $this->data = $data;
51 | }
52 |
53 | /**
54 | * Return data value
55 | *
56 | * @param $key
57 | * @param null $default
58 | *
59 | * @return mixed|null
60 | */
61 | public function get_value($key, $default = null) {
62 | return isset($this->data[$key]) ? $this->data[$key] : $default;
63 | }
64 | }
--------------------------------------------------------------------------------
/includes/class-batch-list-table.php:
--------------------------------------------------------------------------------
1 | .
19 | **********************************************************************/
20 |
21 | if ( ! defined( 'ABSPATH' ) ) {
22 | die( 'Direct access is not allowed.' );
23 | }
24 |
25 | if ( ! class_exists( 'WP_List_Table' ) ) {
26 | require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
27 | }
28 |
29 | /**
30 | * Class WP_BP_List_Table
31 | */
32 | class WP_BP_List_Table extends \WP_List_Table {
33 |
34 | function __construct() {
35 | parent::__construct( array(
36 | 'singular' => 'batch',
37 | 'plural' => 'batches',
38 | 'ajax' => false
39 | ) );
40 | }
41 |
42 | function get_table_classes() {
43 | return array( 'widefat', 'fixed', 'striped', $this->_args['plural'] );
44 | }
45 |
46 | /**
47 | * Message to show if no designation found
48 | *
49 | * @return void
50 | */
51 | function no_items() {
52 | _e( 'No batches found. Read the documentation on the plugin github page to see how to register ones.', 'wp-batch-processing' );
53 | }
54 |
55 | /**
56 | * Default column values if no callback found
57 | *
58 | * @param object $item
59 | * @param string $column_name
60 | *
61 | * @return string
62 | */
63 | function column_default( $item, $column_name ) {
64 |
65 | switch ( $column_name ) {
66 | case 'title':
67 | return $item->title;
68 |
69 | case 'total_processed':
70 | return $item->get_processed_count();
71 |
72 | case 'total_items':
73 | return $item->get_items_count();
74 |
75 | default:
76 | return isset( $item->$column_name ) ? $item->$column_name : '';
77 | }
78 | }
79 |
80 | /**
81 | * Get the column names
82 | *
83 | * @return array
84 | */
85 | function get_columns() {
86 | $columns = array(
87 | 'title' => __( 'Title', 'wp-batch-processing' ),
88 | 'total_processed' => __( 'Total Processed', 'wp-batch-processing' ),
89 | 'total_items' => __( 'Total Items', 'wp-batch-processing' ),
90 | );
91 |
92 | return $columns;
93 | }
94 |
95 | /**
96 | * Render the designation name column
97 | *
98 | * @param object $item
99 | *
100 | * @return string
101 | */
102 | function column_title( $item ) {
103 |
104 | $actions = array();
105 | $actions['edit'] = sprintf( '%s', admin_url( 'admin.php?page=dg-batches&action=view&id=' . $item->id ), $item->id, __( 'Manage Batch', 'wp-batch-processing' ), __( 'Manage', 'wp-batch-processing' ) );
106 |
107 | return sprintf( '%2$s %3$s', admin_url( 'admin.php?page=dg-batches&action=view&id=' . $item->id ), $item->title, $this->row_actions( $actions ) );
108 | }
109 |
110 | /**
111 | * Get sortable columns
112 | *
113 | * @return array
114 | */
115 | function get_sortable_columns() {
116 | $sortable_columns = array();
117 |
118 | return $sortable_columns;
119 | }
120 |
121 | /**
122 | * Set the bulk actions
123 | *
124 | * @return array
125 | */
126 | function get_bulk_actions() {
127 | $actions = array();
128 |
129 | return $actions;
130 | }
131 |
132 | /**
133 | * Render the checkbox column
134 | *
135 | * @param object $item
136 | *
137 | * @return string
138 | */
139 | function column_cb( $item ) {
140 | return '';
141 | }
142 |
143 | /**
144 | * Prepare the class items
145 | *
146 | * @return void
147 | */
148 | function prepare_items() {
149 |
150 | $columns = $this->get_columns();
151 | $hidden = array();
152 | $sortable = $this->get_sortable_columns();
153 | $this->_column_headers = array( $columns, $hidden, $sortable );
154 | $this->page_status = isset( $_GET['status'] ) ? sanitize_text_field( $_GET['status'] ) : '2';
155 |
156 | $this->items = WP_Batch_Processor::get_instance()->get_batches();
157 | }
158 | }
--------------------------------------------------------------------------------
/includes/class-batch-processor-admin.php:
--------------------------------------------------------------------------------
1 | .
19 | **********************************************************************/
20 |
21 | if ( ! defined( 'ABSPATH' ) ) {
22 | die( 'Direct access is not allowed.' );
23 | }
24 |
25 | /**
26 | * Class WP_Batch_Processor_Admin
27 | */
28 | class WP_Batch_Processor_Admin {
29 |
30 | use WP_BP_Singleton;
31 |
32 | const NONCE = 'wp-batch-processing';
33 |
34 | /**
35 | * Kick-in the class
36 | */
37 | protected function init() {
38 | add_action( 'admin_menu', array( $this, 'admin_menu' ) );
39 | add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
40 | add_action( 'init', array( $this, 'setup' ), 0 );
41 | }
42 |
43 | /**
44 | * Init hook. Only run when it is on its own pages.
45 | */
46 | public function setup() {
47 | if ( $this->is_batch_runner_screen() || $this->is_batch_runner_ajax() ) {
48 | do_action( 'wp_batch_processing_init' );
49 | }
50 | }
51 |
52 | /**
53 | * Enqueues admin scripts
54 | *
55 | * @return void
56 | */
57 | public function enqueue_scripts() {
58 |
59 | if ( ! $this->is_batch_runner_screen( 'view' ) ) {
60 | return;
61 | }
62 |
63 | wp_enqueue_script(
64 | 'wp-batch-processing',
65 | WP_BP_URL . 'assets/processor.js',
66 | array( 'jquery' ),
67 | filemtime( WP_BP_PATH . 'assets/processor.js' ),
68 | true
69 | );
70 | wp_localize_script( 'wp-batch-processing', 'DgBatchRunner', array(
71 | 'ajax_url' => admin_url( 'admin-ajax.php' ),
72 | 'nonce' => wp_create_nonce( 'wp-batch-processing' ),
73 | 'batch_id' => isset( $_GET['id'] ) ? $_GET['id'] : 0,
74 | 'delay' => apply_filters( 'wp_batch_processing_delay', 0 ),
75 | // Set delay in seconds before processing the next item. Default 0. No delay.
76 | 'text' => array(
77 | 'processing' => __( 'Processing...', 'wp-batch-processing' ),
78 | 'start' => __( 'Start', 'wp-batch-processing' ),
79 | 'confirm_restart' => __('Are you sure you want to restart? This action can not be reverted.', 'wp-batch-processing')
80 | )
81 | ) );
82 | }
83 |
84 | /**
85 | * Add menu items
86 | *
87 | * @return void
88 | */
89 | public function admin_menu() {
90 |
91 | add_menu_page(
92 | __( 'Manage Batches', 'wp-batch-processing' ),
93 | __( 'Batches ', 'wp-batch-processing' ),
94 | 'manage_options', 'dg-batches',
95 | array( $this, 'plugin_page' ),
96 | 'dashicons-grid-view', null
97 | );
98 |
99 | add_submenu_page(
100 | 'dg-batches',
101 | __( 'Batches', 'wp-batch-processing' ),
102 | __( 'Batches', 'wp-batch-processing' ),
103 | 'manage_options',
104 | 'dg-batches',
105 | array( $this, 'plugin_page' )
106 | );
107 | }
108 |
109 | /**
110 | * Handles the plugin page
111 | *
112 | * @return void
113 | */
114 | public function plugin_page() {
115 | $action = isset( $_GET['action'] ) ? $_GET['action'] : 'list';
116 | $id = isset( $_GET['id'] ) ? $_GET['id'] : 0;
117 | switch ( $action ) {
118 | case 'view':
119 | $view = 'batch-view';
120 | break;
121 | default:
122 | $view = 'batch-list';
123 | break;
124 | }
125 | WP_BP_Helper::render( $view, array( 'id' => $id ) );
126 | }
127 |
128 | /**
129 | * Returns true if the it is ajax action
130 | * @return bool
131 | */
132 | private function is_batch_runner_ajax() {
133 | return isset( $_REQUEST['action'] ) && $_REQUEST['action'] === 'dg_process_next_batch_item' || isset( $_REQUEST['action'] ) && $_REQUEST['action'] === 'dg_restart_batch';
134 | }
135 |
136 | /**
137 | * Returns true if the current screen is batch runners one.
138 | * @return bool
139 | */
140 | private function is_batch_runner_screen( $action = null ) {
141 | $is_main_screen = isset( $_GET['page'] ) && $_GET['page'] === 'dg-batches';
142 | if ( ! is_null( $action ) ) {
143 | $is_main_screen = $is_main_screen && isset( $_GET['action'] ) && $_GET['action'] === $action;
144 | }
145 |
146 | return $is_main_screen;
147 | }
148 | }
149 |
150 |
--------------------------------------------------------------------------------
/includes/class-batch-processor.php:
--------------------------------------------------------------------------------
1 | .
19 | **********************************************************************/
20 |
21 | if ( ! defined( 'ABSPATH' ) ) {
22 | die( 'Direct access is not allowed.' );
23 | }
24 |
25 |
26 | /**
27 | * Class WP_Batch_Processor
28 | */
29 | class WP_Batch_Processor {
30 |
31 | use WP_BP_Singleton;
32 |
33 | /**
34 | * List of batches
35 | * @var WP_Batch[]
36 | */
37 | protected $batches;
38 |
39 | /**
40 | * Initializes the runner
41 | */
42 | public function init() {
43 | $this->batches = array();
44 | }
45 |
46 |
47 | /**
48 | * Register batch
49 | *
50 | * @param WP_Batch $batch
51 | */
52 | public function register( $batch ) {
53 | $this->batches[] = $batch;
54 | }
55 |
56 | /**
57 | * Returns array of registered batches
58 | * @return WP_Batch[]
59 | */
60 | public function get_batches() {
61 | return $this->batches;
62 | }
63 |
64 | /**
65 | * Returns a batch from the registered ones.
66 | *
67 | * @param $id
68 | *
69 | * @return null|WP_Batch
70 | */
71 | public function get_batch( $id ) {
72 | foreach ( $this->batches as $batch ) {
73 | if ( $batch->id === $id ) {
74 | return $batch;
75 | }
76 | }
77 |
78 | return null;
79 | }
80 |
81 | /**
82 | * Boot the plugin
83 | */
84 | public static function boot() {
85 | self::load_paths();
86 | WP_Batch_Processor_Admin::get_instance();
87 | WP_Batch_Processing_Ajax_Handler::get_instance();
88 | WP_Batch_Processor::get_instance();
89 | }
90 |
91 | /**
92 | * Determine the library URL.
93 | * Note: This won't work if the library is outside of the wp-content directory
94 | * and also contains multiple 'wp-content' words in the path.
95 | */
96 | private static function load_paths() {
97 | if ( ! defined( 'WP_BP_PATH' ) || ! defined( 'WP_BP_URL' ) ) {
98 | $path = trailingslashit( dirname( dirname( __FILE__ ) ) );
99 | $content_dir = basename( untrailingslashit( WP_CONTENT_DIR ) );
100 | $library_uri = substr( strstr( trailingslashit( $path ), $content_dir ), strlen( $content_dir ) );
101 | $url = untrailingslashit( WP_CONTENT_URL ) . $library_uri;
102 | if ( ! defined( 'WP_BP_PATH' ) ) {
103 | define( 'WP_BP_PATH', $path );
104 | }
105 | if ( ! defined( 'WP_BP_URL' ) ) {
106 | define( 'WP_BP_URL', trailingslashit( $url ) );
107 | }
108 | }
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/includes/class-batch.php:
--------------------------------------------------------------------------------
1 | .
19 | **********************************************************************/
20 |
21 | if ( ! defined( 'ABSPATH' ) ) {
22 | die( 'Direct access is not allowed.' );
23 | }
24 |
25 | /**
26 | * Class WP_Batch
27 | *
28 | * Extend this class to create your own batch
29 | *
30 | * Note: You must register the instance in the wp_batch_processing_init hook
31 | * in order to show in the admin area.
32 | *
33 | * eg. WP_Batch_Processor::get_instance()->register( $batch );
34 | */
35 | abstract class WP_Batch {
36 |
37 | /**
38 | * Unique identifier of each batch
39 | * @var string
40 | */
41 | public $id = 'my_first_batch';
42 |
43 | /**
44 | * Describe the batch
45 | * @var string
46 | */
47 | public $title = 'My first batch';
48 |
49 | /**
50 | * Data store of batch items
51 | * @var WP_Batch_Item[]
52 | */
53 | protected $items = array();
54 |
55 | /**
56 | * Initialize
57 | */
58 | public function __construct() {
59 | $this->setup();
60 | }
61 |
62 | /**
63 | * To setup the batch data use the push() method to add WP_Batch_Item instances to the queue.
64 | *
65 | * Note: If the operation of obtaining data is expensive, cache it to avoid slowdowns.
66 | *
67 | * @return void
68 | */
69 | abstract public function setup();
70 |
71 | /**
72 | * Handles processing of batch item. One at a time.
73 | *
74 | * In order to work it correctly you must return values as follows:
75 | *
76 | * - TRUE - If the item was processed successfully.
77 | * - WP_Error instance - If there was an error. Add message to display it in the admin area.
78 | *
79 | * @param WP_Batch_Item $item
80 | *
81 | * @return bool|\WP_Error
82 | */
83 | abstract public function process( $item );
84 |
85 | /**
86 | * Called when specific process is finished (all items were processed).
87 | * This method can be overriden in the process class.
88 | * @return void
89 | */
90 | public function finish() {}
91 |
92 | /**
93 | * Queues the item for processing.
94 | *
95 | * @param WP_Batch_Item $item
96 | */
97 | protected function push( $item ) {
98 | if ( ! is_array( $this->items ) ) {
99 | $this->items = array();
100 | }
101 | $this->items[] = $item;
102 | }
103 |
104 |
105 | /**
106 | * Returns false if no items left for processing or WP_Batch_Item object for processing.
107 | *
108 | * @param WP_Batch_Item $item
109 | *
110 | * @return bool|WP_Batch_Item
111 | */
112 | public function get_next_item() {
113 | $processed = $this->get_processed_items();
114 | foreach ( $this->items as $item ) {
115 | if ( ! in_array( $item->id, $processed ) ) {
116 | return $item;
117 | }
118 | }
119 |
120 | return false;
121 | }
122 |
123 | /**
124 | * Check if the batch is finished.
125 | * @return bool
126 | */
127 | public function is_finished() {
128 | $is_finished = true;
129 | foreach ( $this->items as $item ) {
130 | if ( ! $this->is_processed( $item ) ) {
131 | $is_finished = false;
132 | }
133 | }
134 |
135 | return $is_finished;
136 | }
137 |
138 |
139 | /**
140 | * Check if batch item was processed.
141 | *
142 | * @param WP_Batch_Item $item
143 | *
144 | * @return bool
145 | */
146 | public function is_processed( $item ) {
147 | return in_array( $item->id, $this->get_processed_items() );
148 | }
149 |
150 | /**
151 | * Returns processed batch item ids
152 | * @return array
153 | */
154 | public function get_processed_items() {
155 | $processed = get_option( $this->get_db_identifier(), array() );
156 |
157 | return $processed;
158 | }
159 |
160 | /**
161 | * Returns the count of the processed items
162 | * @return int
163 | */
164 | public function get_processed_count() {
165 | return count( $this->get_processed_items() );
166 | }
167 |
168 | /**
169 | * Mark specific id as processed.
170 | *
171 | * @param int $id
172 | */
173 | public function mark_as_processed( $id ) {
174 | $processed = $this->get_processed_items();
175 | array_push( $processed, $id );
176 | $processed = array_unique( $processed );
177 | update_option( $this->get_db_identifier(), $processed );
178 | }
179 |
180 | /**
181 | * Returns the count of the total items
182 | * @return int
183 | */
184 | public function get_items_count() {
185 | return count( $this->items );
186 | }
187 |
188 | /**
189 | * Returns the percentage
190 | * @return float
191 | */
192 | public function get_percentage() {
193 | $total_items = $this->get_items_count();
194 | $total_processed = $this->get_processed_count();
195 | $percentage = ( ! empty( $total_items ) ) ? 100 - ( ( ( $total_items - $total_processed ) / $total_items ) * 100 ) : 0;
196 |
197 | return number_format( (float) $percentage, 2, '.', '' );
198 | }
199 |
200 | /**
201 | * Returns the batch wp_options option_name identifier.
202 | * @return string
203 | */
204 | public function get_db_identifier() {
205 | return 'batch_' . $this->id . '_processed';
206 | }
207 |
208 | /**
209 | * Restarts the processed items store
210 | */
211 | public function restart() {
212 | delete_option( $this->get_db_identifier() );
213 | }
214 |
215 | }
216 |
--------------------------------------------------------------------------------
/includes/class-bp-helper.php:
--------------------------------------------------------------------------------
1 | .
19 | **********************************************************************/
20 |
21 | if ( ! defined( 'ABSPATH' ) ) {
22 | die( 'Direct access is not allowed.' );
23 | }
24 |
25 | /**
26 | * Class WP_BP_Helper
27 | */
28 | class WP_BP_Helper {
29 |
30 | /**
31 | * Renders a view
32 | *
33 | * @param $view
34 | * @param array $data
35 | */
36 | public static function render( $view, $data = array() ) {
37 | $path = WP_BP_PATH . 'views' . DIRECTORY_SEPARATOR . $view . '.php';
38 | if ( file_exists( $path ) ) {
39 | if ( ! empty( $data ) ) {
40 | extract( $data );
41 | }
42 | include( $path );
43 | } else {
44 | echo 'View ' . $view . ' not found';
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/includes/class-bp-singleton.php:
--------------------------------------------------------------------------------
1 | .
19 | **********************************************************************/
20 |
21 | if ( ! defined( 'ABSPATH' ) ) {
22 | die( 'Direct access is not allowed.' );
23 | }
24 |
25 | trait WP_BP_Singleton {
26 | /**
27 | * The current instance
28 | */
29 | protected static $instance;
30 |
31 | /**
32 | * Returns the current instance
33 | */
34 | final public static function get_instance() {
35 | return isset( static::$instance )
36 | ? static::$instance
37 | : static::$instance = new static;
38 | }
39 |
40 | /**
41 | * WP_BP_Singleton constructor.
42 | */
43 | final private function __construct() {
44 | $this->init();
45 | }
46 |
47 | // Prevent instances
48 | protected function init() {
49 | }
50 |
51 | public function __wakeup() {
52 | }
53 |
54 | private function __clone() {
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/views/batch-list.php:
--------------------------------------------------------------------------------
1 |