PluginProbe
PW WooCommerce Bulk Edit / trunk
PW WooCommerce Bulk Edit vtrunk
2.143 2.142 trunk 1.0 1.1 1.10 1.11 1.15 1.16 1.17 1.18 1.19 1.2 1.20 1.21 1.22 1.23 1.24 1.25 1.26 1.27 1.28 1.29 1.3 1.30 All 175 releases
pw-bulk-edit / pw-bulk-edit.php

pw-bulk-edit.php in PW WooCommerce Bulk Edit trunk, at pw-bulk-edit.php

512 lines 20.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: PW WooCommerce Bulk Edit
4 * Plugin URI: https://www.pimwick.com/pw-bulk-edit/
5 * Description: A powerful way to update your WooCommerce product catalog. Finally, no more tedious clicking through countless pages making the same change to all products!
6 * Version: 2.143
7 * Author: Pimwick, LLC
8 * Author URI: https://www.pimwick.com
9 * Text Domain: pw-bulk-edit
10 * Domain Path: /languages
11 *
12 * WC requires at least: 4.0
13 * WC tested up to: 10.6
14 * Requires Plugins: woocommerce
15 *
16 */
17 define('PWBE_VERSION', '2.143');
18
19 /*
20 Copyright (C) Pimwick, LLC
21
22 This program is free software; you can redistribute it and/or
23 modify it under the terms of the GNU General Public License
24 as published by the Free Software Foundation; either version 2
25 of the License, or (at your option) any later version.
26
27 This program is distributed in the hope that it will be useful,
28 but WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 GNU General Public License for more details.
31
32 You should have received a copy of the GNU General Public License
33 along with this program; if not, write to the Free Software
34 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
35 */
36
37 // Exit if accessed directly
38 defined( 'ABSPATH' ) or exit;
39
40 // Ensure that nothing can interfere with the frontend for any reason.
41 if ( !is_admin() ) {
42 return;
43 }
44
45 // Increase the available memory since returning lots of data can often exhaust typical memory allocation amounts.
46 defined( 'PWBE_MEMORY_LIMIT' ) or define( 'PWBE_MEMORY_LIMIT', '1024M' );
47
48 // Only change this if you are comfortable with possible unexpected behavior!
49 defined( 'PWBE_MAX_RESULTS' ) or define( 'PWBE_MAX_RESULTS', '1000' );
50
51 // Number of fields to save in a single AJAX call. Lower number can avoid HTTP 504 Timeout errors.
52 defined( 'PWBE_SAVE_BATCH_SIZE' ) or define( 'PWBE_SAVE_BATCH_SIZE', 25 );
53
54 // If the data contains product_variation records that are children of Simple Products you will want to
55 // enable this flag. This could make the query run slower.
56 defined( 'PWBE_PREFILTER_VARIATIONS' ) or define( 'PWBE_PREFILTER_VARIATIONS', false );
57
58 defined( 'PWBE_REQUIRES_CAPABILITY' ) or define( 'PWBE_REQUIRES_CAPABILITY', 'manage_woocommerce' );
59
60 defined( 'PWBE_SEARCH_PARENT_CATEGORIES' ) or define( 'PWBE_SEARCH_PARENT_CATEGORIES', true );
61
62 // Verify this isn't called directly.
63 if ( !function_exists( 'add_action' ) ) {
64 echo 'Hi there! I\'m just a plugin, not much I can do when called directly.';
65 exit;
66 }
67
68 if ( ! class_exists( 'PW_Bulk_Edit' ) ) :
69
70 register_uninstall_hook( __FILE__, array( 'PW_Bulk_Edit', 'plugin_uninstall' ) );
71
72 final class PW_Bulk_Edit {
73
74 const NULL = '!!pwbe_null_value!!';
75
76 static $options = array(
77 'pwbe_help_dismiss_intro',
78 'pwbe_help_minimize_filter_help',
79 'pwbe_views',
80 'pwbe_selected_view'
81 );
82
83 function __construct() {
84 add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ) );
85 add_action( 'woocommerce_init', array( $this, 'woocommerce_init' ) );
86
87 // Do things when a new version is installed.
88 if ( get_option( 'pwbe_database_version' ) != PWBE_VERSION ) {
89
90 // Go from a per-plugin setting to a global setting for hiding the Pimwick Plugins menu.
91 if ( get_option( 'pwbe_hide_pimwick_menu', '' ) != '' ) {
92 update_option( 'hide_pimwick_menu', get_option( 'pwbe_hide_pimwick_menu' ), false );
93 delete_option( 'pwbe_hide_pimwick_menu' );
94 }
95
96 // Record that the new version is installed.
97 update_option( 'pwbe_database_version', PWBE_VERSION );
98 }
99
100 // WooCommerce High Performance Order Storage (HPOS) compatibility declaration.
101 add_action( 'before_woocommerce_init', function() {
102 if ( class_exists( '\Automattic\WooCommerce\Utilities\FeaturesUtil' ) ) {
103 \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true );
104 }
105 } );
106 }
107
108 function plugins_loaded() {
109 load_plugin_textdomain( 'pw-bulk-edit', false, basename( dirname( __FILE__ ) ) . '/languages' );
110 }
111
112 function woocommerce_init() {
113 remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
114 remove_action( 'admin_print_styles', 'print_emoji_styles' );
115
116 if ( is_admin() && current_user_can( get_option( 'pwbe_requires_capability', PWBE_REQUIRES_CAPABILITY ) ) ) {
117 require_once( 'includes/columns.php' );
118 require_once( 'includes/db.php' );
119 require_once( 'includes/filters.php' );
120 require_once( 'includes/settings.php' );
121 require_once( 'includes/select-options.php' );
122 require_once( 'includes/sql-builder.php' );
123 require_once( 'includes/views.php' );
124
125 add_action( 'admin_menu', array( $this, 'register_admin_menu' ) );
126 add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ), 9999 );
127 add_action( 'wp_ajax_pwbe_options', array( $this, 'ajax_options' ) );
128 add_action( 'wp_ajax_pwbe_filter_results', array( $this, 'ajax_filter_results' ) );
129 add_action( 'wp_ajax_pwbe_get_view', array( $this, 'ajax_get_view' ) );
130 add_action( 'wp_ajax_pwbe_save_view', array( $this, 'ajax_save_view' ) );
131 add_action( 'wp_ajax_pwbe_delete_view', array( $this, 'ajax_delete_view' ) );
132 add_action( 'wp_ajax_pwbe_save_products', array( $this, 'ajax_save_products' ) );
133 add_action( 'wp_ajax_pwbe_get_save_products_error', array( $this, 'ajax_get_save_products_error' ) );
134 }
135 }
136
137 public static function wc_min_version( $version ) {
138 return version_compare( WC()->version, $version, ">=" );
139 }
140
141 public static function plugin_uninstall() {
142 if ( ! current_user_can( 'activate_plugins' ) )
143 return;
144
145 foreach( PW_Bulk_Edit::$options as $option ) {
146 delete_option( $option );
147 }
148 }
149
150 function index() {
151 $data = get_plugin_data( __FILE__ );
152 $version = $data['Version'];
153 $settings_url = add_query_arg( array( 'page' => 'wc-settings', 'tab' => 'pw-bulk-edit' ), admin_url( 'admin.php' ) );
154 $help_url = plugins_url( '/docs/index.html', __FILE__ );
155
156 require( 'ui/index.php' );
157 }
158
159 function ajax_options() {
160 check_ajax_referer( 'pw-bulk-edit-options', 'security' );
161
162 $name = $_POST['option_name'];
163 $value = $_POST['option_value'];
164
165 if ( in_array( $name, PW_Bulk_Edit::$options ) ) {
166 update_option( $name, $value, false );
167 }
168 }
169
170 function ajax_filter_results() {
171 require( 'ui/results.php' );
172 wp_die();
173 }
174
175 function ajax_get_view() {
176 $view_name = stripslashes( $_POST['name'] );
177
178 update_option( 'pwbe_selected_view', $view_name, false );
179
180 $views = PWBE_Views::get();
181 $view = $views[ $view_name ];
182
183 wp_send_json( $view );
184
185 wp_die();
186 }
187
188 function ajax_save_view() {
189 check_ajax_referer( 'pw-bulk-edit-save-view', 'security' );
190
191 $option_value = get_option( 'pwbe_views' );
192 $views = maybe_unserialize( $option_value );
193
194 $clean_name = stripslashes( $_POST['name'] );
195
196 $views[ $clean_name ] = stripslashes( $_POST['view_data'] );
197
198 ksort( $views, SORT_NATURAL );
199
200 update_option( 'pwbe_views', $views, false );
201 update_option( 'pwbe_selected_view', $clean_name, false );
202
203 wp_die();
204 }
205
206 function ajax_delete_view() {
207 check_ajax_referer( 'pw-bulk-edit-delete-view', 'security' );
208
209 $option_value = get_option( 'pwbe_views' );
210 $views = maybe_unserialize( $option_value );
211
212 $view_name = stripslashes( $_POST['name'] );
213
214 unset( $views[ $view_name ] );
215
216 update_option( 'pwbe_views', $views, false );
217 update_option( 'pwbe_selected_view', '', false );
218
219 wp_die();
220 }
221
222 function ajax_save_products() {
223 register_shutdown_function( array( $this, 'save_products_exception' ) );
224
225 check_ajax_referer( 'pw-bulk-edit-save-products', 'security' );
226
227 require( 'includes/save-products.php' );
228
229 if ( isset( $_POST['fields'] ) ) {
230 $fields = $_POST['fields'];
231
232 $save = new PWBE_Save_Products();
233 $results = $save->save( $fields );
234
235 require( 'ui/products-saved.php' );
236 }
237
238 wp_die();
239 }
240
241 function ajax_get_save_products_error() {
242 $error_file = plugin_dir_path( __FILE__ ) . 'logs/save_products_exception.txt';
243
244 printf( __( 'Error while saving products: %s', 'pw-bulk-edit' ), file_get_contents( $error_file ) );
245
246 wp_die();
247 }
248
249 public function save_products_exception() {
250 $errfile = 'unknown file';
251 $errstr = 'shutdown';
252 $errno = E_CORE_ERROR;
253 $errline = 0;
254
255 $error = error_get_last();
256
257 if ( $error !== NULL ) {
258 $errno = $error['type'];
259 $errfile = $error['file'];
260 $errline = $error['line'];
261 $errstr = $error['message'];
262
263 if ( PW_Bulk_Edit::starts_with( plugin_dir_path( __FILE__ ), $errfile ) ) {
264 $output_dir = plugin_dir_path( __FILE__ ) . 'logs';
265 if ( ! file_exists( $output_dir ) ) {
266 mkdir( $output_dir, 0777, true );
267 }
268 file_put_contents( $output_dir . '/save_products_exception.txt', "$errstr in $errfile on line $errline" );
269 }
270 }
271 }
272
273 function error( $message ) {
274 ?>
275 <div class="error">
276 <p><?php _e( $message, 'pw-bulk-edit' ); ?></p>
277 </div>
278 <?php
279 }
280
281 function register_admin_menu() {
282
283 if ( get_option( 'hide_pimwick_menu', 'no' ) === 'no' ) {
284 if ( empty ( $GLOBALS['admin_page_hooks']['pimwick'] ) ) {
285 add_menu_page(
286 __( 'PW Bulk Edit', 'pw-bulk-edit' ),
287 __( 'Pimwick Plugins', 'pw-bulk-edit' ),
288 get_option( 'pwbe_requires_capability', PWBE_REQUIRES_CAPABILITY ),
289 'pimwick',
290 array( $this, 'index' ),
291 plugins_url( '/assets/images/pimwick-icon-120x120.png', __FILE__ ),
292 6
293 );
294
295 add_submenu_page(
296 'pimwick',
297 __( 'PW Bulk Edit', 'pw-bulk-edit' ),
298 __( 'Pimwick Plugins', 'pw-bulk-edit' ),
299 get_option( 'pwbe_requires_capability', PWBE_REQUIRES_CAPABILITY ),
300 'pimwick',
301 array( $this, 'index' )
302 );
303
304 remove_submenu_page('pimwick','pimwick');
305 }
306
307 add_submenu_page(
308 'pimwick',
309 __( 'PW Bulk Edit', 'pw-bulk-edit' ),
310 __( 'PW Bulk Edit', 'pw-bulk-edit' ),
311 get_option( 'pwbe_requires_capability', PWBE_REQUIRES_CAPABILITY ),
312 'pw-bulk-edit',
313 array( $this, 'index' )
314 );
315 }
316
317 add_submenu_page(
318 'edit.php?post_type=product',
319 __( 'PW Bulk Edit', 'pw-bulk-edit' ),
320 __( 'PW Bulk Edit', 'pw-bulk-edit' ),
321 get_option( 'pwbe_requires_capability', PWBE_REQUIRES_CAPABILITY ),
322 'wc-pw-bulk-edit',
323 array( $this, 'index' )
324 );
325 }
326
327 function admin_scripts( $hook ) {
328 if ( !empty( $hook ) && substr( $hook, -strlen( 'pw-bulk-edit' ) ) === 'pw-bulk-edit' ) {
329 wp_register_style( 'pwbe-font-awesome', plugins_url( '/assets/css/all.min.css', __FILE__ ), array(), PWBE_VERSION ); // 7.3.1
330 wp_enqueue_style( 'pwbe-font-awesome' );
331
332 wp_register_style( 'pwbe-font-awesome-v4-shims', plugins_url( '/assets/css/v4-shims.min.css', __FILE__ ), array( 'pwbe-font-awesome' ), PWBE_VERSION );
333 wp_enqueue_style( 'pwbe-font-awesome-v4-shims' );
334
335 wp_register_style( 'pwbe-select2', plugins_url( '/assets/css/select2.min.css', __FILE__ ), array(), PWBE_VERSION ); // 4.0.3
336 wp_enqueue_style( 'pwbe-select2' );
337
338 wp_register_style( 'pwbe-context-menu', plugins_url( '/assets/css/jquery.contextMenu.min.css', __FILE__ ), array(), PWBE_VERSION );
339 wp_enqueue_style( 'pwbe-context-menu' );
340
341 wp_enqueue_script( 'pwbe-select2', plugins_url( '/assets/js/select2.min.js', __FILE__ ), array(), PWBE_VERSION ); // 4.0.3
342
343 wp_register_style( 'pw-bulk-edit', plugins_url( '/assets/css/style.css', __FILE__ ), array(), PWBE_VERSION );
344 wp_enqueue_style( 'pw-bulk-edit' );
345
346 wp_enqueue_script( 'pwbe-context-menu', plugins_url( '/assets/js/jquery.contextMenu.min.js', __FILE__ ), array( 'jquery-ui-position' ), PWBE_VERSION ); // 2.2.3
347
348 wp_enqueue_script( 'pwbe-filters', plugins_url( '/assets/js/filters.js', __FILE__ ), array( 'jquery-form', 'pwbe-select2', 'pwbe-context-menu' ), PWBE_VERSION );
349
350 $string_types = array(
351 'contains' => __( 'contains', 'pw-bulk-edit' ),
352 'does not contain' => __( 'does not contain', 'pw-bulk-edit' ),
353 'is' => __( 'is', 'pw-bulk-edit' ),
354 'is not' => __( 'is not', 'pw-bulk-edit' ),
355 'begins with' => __( 'begins with', 'pw-bulk-edit' ),
356 'ends with' => __( 'ends with', 'pw-bulk-edit' ),
357 );
358
359 $boolean_types = array(
360 'is checked' => __( 'is checked', 'pw-bulk-edit' ),
361 'is not checked' => __( 'is not checked', 'pw-bulk-edit' ),
362 );
363
364 $numeric_types = array(
365 'is' => __( 'is', 'pw-bulk-edit' ),
366 'is not' => __( 'is not', 'pw-bulk-edit' ),
367 'is greater than' => __( 'is greater than', 'pw-bulk-edit' ),
368 'is less than' => __( 'is less than', 'pw-bulk-edit' ),
369 'is in the range' => __( 'is in the range', 'pw-bulk-edit' ),
370 );
371
372 $select_types = array(
373 'is any of' => __( 'is any of', 'pw-bulk-edit' ),
374 'is none of' => __( 'is none of', 'pw-bulk-edit' ),
375 );
376
377 wp_localize_script( 'pwbe-filters', 'pwbeFilters', array(
378 'stringTypes' => array_keys( $string_types ),
379 'booleanTypes' => array_keys( $boolean_types ),
380 'numericTypes' => array_keys( $numeric_types ),
381 'selectTypes' => array_keys( $select_types ),
382 'i18n' => array(
383 'stringTypes' => array_values( $string_types ),
384 'booleanTypes' => array_values( $boolean_types ),
385 'numericTypes' => array_values( $numeric_types ),
386 'selectTypes' => array_values( $select_types ),
387 'unsavedChangesPrompt' => __( 'Unsaved changes will be lost.', 'pw-bulk-edit' ),
388 'searching' => __( 'Searching', 'pw-bulk-edit' ),
389 )
390 ) );
391
392 wp_enqueue_script( 'pwbe-results', plugins_url( '/assets/js/results.js', __FILE__ ), array( 'pwbe-filters' ), PWBE_VERSION );
393 wp_localize_script( 'pwbe-results', 'pwbe', array(
394 'i18n' => array(
395 'view_name_prompt' => __( 'Name your custom view, for example "My View"', 'pw-bulk-edit' ),
396 'overwrite_view_prompt' => __( 'A view with this name already exists. Do you want to overwrite it?', 'pw-bulk-edit' ),
397 'editAllCheckedProducts' => __( 'Edit All Checked Products', 'pw-bulk-edit' ),
398 'sortAscending' => __( 'Sort Ascending', 'pw-bulk-edit' ),
399 'sortDescending' => __( 'Sort Descending', 'pw-bulk-edit' ),
400 'hideColumn' => __( 'Hide Column', 'pw-bulk-edit' ),
401 'acceptChanges' => __( 'Accept Changes', 'pw-bulk-edit' ),
402 'cancelChanges' => __( 'Cancel Changes', 'pw-bulk-edit' ),
403 'revertToOriginal' => __( 'Revert to the original value for this field', 'pw-bulk-edit' ),
404 'select' => __( 'Select', 'pw-bulk-edit' ),
405 'saving' => __( 'Saving', 'pw-bulk-edit' ),
406 'confirmDeleteView' => __( 'Are you sure you want to delete this view?', 'pw-bulk-edit' ),
407 'discardAllChanges' => __( 'Discard all unsaved changes? This can\'t be undone.', 'pw-bulk-edit' ),
408 ),
409 'saveBatchSize' => PWBE_SAVE_BATCH_SIZE,
410 'nonces' => array(
411 'save_products' => wp_create_nonce( 'pw-bulk-edit-save-products' ),
412 'save_view' => wp_create_nonce( 'pw-bulk-edit-save-view' ),
413 'delete_view' => wp_create_nonce( 'pw-bulk-edit-delete-view' ),
414 'options' => wp_create_nonce( 'pw-bulk-edit-options' ),
415 ),
416 ) );
417
418 wp_enqueue_script( 'jquery-form' );
419 wp_enqueue_script( 'jquery-ui-draggable' );
420 wp_enqueue_script( 'jquery-ui-position' );
421 }
422
423 wp_register_style( 'pw-bulk-edit-icon', plugins_url( '/assets/css/icon-style.css', __FILE__ ), array(), PWBE_VERSION );
424 wp_enqueue_style( 'pw-bulk-edit-icon' );
425 }
426
427 public static function starts_with( $needle, $haystack ) {
428 $length = strlen( $needle );
429 return ( substr( $haystack, 0, $length ) === $needle );
430 }
431
432 /**
433 * Source: http://wordpress.stackexchange.com/questions/14652/how-to-show-a-hierarchical-terms-list
434 * Recursively sort an array of taxonomy terms hierarchically. Child categories will be
435 * placed under a 'children' member of their parent term.
436 * @param Array $cats taxonomy term objects to sort
437 * @param Array $into result array to put them in
438 * @param integer $parentId the current parent ID to put them in
439 */
440 function sort_terms_hierarchically( array &$cats, array &$into, $parentId = 0 ) {
441 foreach ( $cats as $i => $cat ) {
442 if ( $cat->parent == $parentId ) {
443 $into[$cat->term_id] = $cat;
444 unset( $cats[$i] );
445 }
446 }
447
448 foreach ( $into as $topCat ) {
449 $topCat->children = array();
450 $this->sort_terms_hierarchically( $cats, $topCat->children, $topCat->term_id );
451 }
452 }
453
454 function hierarchical_select($categories, $level = 0, $parent = NULL, $prefix = '') {
455 $output = '';
456
457 foreach ( $categories as $category ) {
458 $output .= "<option value='{$category->slug}'>$prefix {$category->name}</option>\n";
459
460 if ( $category->parent == $parent ) {
461 $level = 0;
462 }
463
464 if ( count( $category->children ) > 0 ) {
465 $output .= $this->hierarchical_select( $category->children, ( $level + 1 ), $category->parent, "$prefix {$category->name} &#8594;" );
466 }
467 }
468
469 return $output;
470 }
471
472 function increase_memory_limit() {
473 if ( !defined( 'PWBE_MEMORY_LIMIT' ) || PWBE_MEMORY_LIMIT === false ) {
474 return;
475 }
476
477 // Get the current memory_limit
478 $current_limit = ini_get( 'memory_limit' );
479
480 if ( !empty( $current_limit ) ) {
481 $current_limit = $this->string_to_bytes( $current_limit );
482 }
483
484 $new_limit = $this->string_to_bytes( PWBE_MEMORY_LIMIT );
485
486 if ( $current_limit >= $new_limit ) {
487 return;
488 }
489
490 if ( !ini_get( 'safe_mode' ) ) {
491 ini_set( 'memory_limit', PWBE_MEMORY_LIMIT );
492 }
493 }
494
495 function string_to_bytes( $string ) {
496 return preg_replace_callback('/^\s*(\d+)\s*(?:([kmgt]?)b?)?\s*$/i', function ($m) {
497 switch (strtolower($m[2])) {
498 case 't': $m[1] *= 1024;
499 case 'g': $m[1] *= 1024;
500 case 'm': $m[1] *= 1024;
501 case 'k': $m[1] *= 1024;
502 }
503 return $m[1];
504 }, $string );
505 }
506 }
507
508 global $pw_bulk_edit;
509 $pw_bulk_edit = new PW_Bulk_Edit();
510
511 endif;
512