array( label, pro ) */ public static function tabs() { return array( 'products' => array( 'label' => __( 'Products', 'gridsmith' ), 'pro' => false, ), 'orders' => array( 'label' => __( 'Orders', 'gridsmith' ), 'pro' => true, ), 'coupons' => array( 'label' => __( 'Coupons', 'gridsmith' ), 'pro' => true, ), 'history' => array( 'label' => __( 'History', 'gridsmith' ), 'pro' => true, ), 'scheduled' => array( 'label' => __( 'Scheduled', 'gridsmith' ), 'pro' => true, ), ); } /** * Current tab. * * @return string */ public static function current_tab() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only navigation parameter. $tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'products'; return array_key_exists( $tab, self::tabs() ) ? $tab : 'products'; } /** * Register menus. */ public static function menu() { self::$hooks[] = add_menu_page( __( 'Gridsmith — Bulk Edit', 'gridsmith' ), __( 'Gridsmith', 'gridsmith' ), 'manage_woocommerce', 'bdbe', array( __CLASS__, 'render_main' ), 'dashicons-editor-table', 56 ); self::$hooks[] = add_submenu_page( 'bdbe', __( 'Gridsmith — Bulk Edit', 'gridsmith' ), __( 'Bulk Edit', 'gridsmith' ), 'manage_woocommerce', 'bdbe', array( __CLASS__, 'render_main' ) ); self::$hooks[] = add_submenu_page( 'bdbe', __( 'Gridsmith Settings', 'gridsmith' ), __( 'Settings', 'gridsmith' ), 'manage_woocommerce', 'bdbe-settings', array( __CLASS__, 'render_settings' ) ); } /** * Scripts and styles, only on Gridsmith screens. * * @param string $hook Current screen hook. */ public static function enqueue( $hook ) { if ( ! in_array( $hook, self::$hooks, true ) ) { return; } wp_enqueue_style( 'bdbe-admin', BDBE_URL . 'assets/css/bdbe-admin.css', array(), BDBE_VERSION ); if ( 'toplevel_page_bdbe' !== $hook ) { return; } $tab = self::current_tab(); $entity = array( 'products' => 'product', 'orders' => 'order', 'coupons' => 'coupon', ); $key = isset( $entity[ $tab ] ) ? $entity[ $tab ] : ''; $object = $key ? BDBE_Plugin::entity( $key ) : null; wp_enqueue_script( 'bdbe-admin', BDBE_URL . 'assets/js/bdbe-admin.js', array(), BDBE_VERSION, true ); $settings = BDBE_Plugin::settings(); wp_localize_script( 'bdbe-admin', 'bdbeData', array( 'root' => esc_url_raw( rest_url( BDBE_Rest::NS . '/' ) ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'entity' => $object ? $object->key() : '', 'pro' => BDBE_Edition::is_pro(), 'columns' => $object ? BDBE_Fields::columns( $object->fields() ) : array(), 'perPage' => (int) $settings['per_page'], 'undo' => BDBE_Changes::undo_info(), 'decimals' => wc_get_price_decimals(), 'currency' => html_entity_decode( get_woocommerce_currency_symbol(), ENT_QUOTES, 'UTF-8' ), 'priceModes' => BDBE_Bulk::price_modes(), 'i18n' => self::js_strings(), ) ); } /** * Strings used by the grid script. * * @return array */ private static function js_strings() { return array( 'loading' => __( 'Loading…', 'gridsmith' ), 'empty' => __( 'Nothing matches these filters. Clear a filter to see more.', 'gridsmith' ), /* translators: %d: number of changed cells. */ 'saveN' => __( 'Save %d changes', 'gridsmith' ), 'save' => __( 'Save changes', 'gridsmith' ), 'saving' => __( 'Saving…', 'gridsmith' ), /* translators: %d: number of records. */ 'saved' => __( 'Saved: %d record(s) updated.', 'gridsmith' ), /* translators: %d: number of records with errors. */ 'saveErrors' => __( 'Not saved: %d record(s). Fix the highlighted cells and save again.', 'gridsmith' ), 'unsaved' => __( 'You have unsaved changes. Discard them?', 'gridsmith' ), 'discard' => __( 'Discard all unsaved changes?', 'gridsmith' ), /* translators: %s: label of the last change. */ 'undoConfirm' => __( 'Undo "%s"? Values changed since then by someone else are kept.', 'gridsmith' ), /* translators: 1: restored values, 2: skipped values. */ 'undone' => __( 'Undone: %1$d values restored, %2$d skipped because they changed since.', 'gridsmith' ), 'nothingUndo' => __( 'Nothing to undo', 'gridsmith' ), /* translators: %s: label of the last change. */ 'undoLabel' => __( 'Undo: %s', 'gridsmith' ), /* translators: 1: first row, 2: last row, 3: total. */ 'showing' => __( '%1$d–%2$d of %3$d', 'gridsmith' ), /* translators: %d: number of selected rows. */ 'selected' => __( '%d selected', 'gridsmith' ), /* translators: %d: number of matching records. */ 'allMatching' => __( 'All %d matching products', 'gridsmith' ), /* translators: %d: number of selected products. */ 'selectedOnly' => __( 'Only the %d selected rows', 'gridsmith' ), /* translators: 1: processed, 2: total. */ 'progress' => __( 'Working… %1$d of %2$d', 'gridsmith' ), /* translators: 1: changed records, 2: skipped records. */ 'bulkDone' => __( 'Done: %1$d updated, %2$d skipped.', 'gridsmith' ), 'variableSkip' => __( 'Variable products are skipped — their prices live on the variations (Gridsmith Pro edits those).', 'gridsmith' ), 'requestFailed' => __( 'The request failed. Check your connection and try again.', 'gridsmith' ), 'selectRows' => __( 'Select at least one row first.', 'gridsmith' ), 'chooseColumn' => __( 'Choose a column', 'gridsmith' ), 'setValue' => __( 'Set', 'gridsmith' ), 'locked' => __( 'This value cannot be edited here.', 'gridsmith' ), 'editProduct' => __( 'Open in the editor', 'gridsmith' ), 'selectRow' => __( 'Select row', 'gridsmith' ), 'selectAll' => __( 'Select all rows on this page', 'gridsmith' ), 'scheduled' => __( 'Scheduled. You can follow it on the Scheduled tab.', 'gridsmith' ), 'viewName' => __( 'Name this view', 'gridsmith' ), 'viewSaved' => __( 'View saved.', 'gridsmith' ), 'viewDeleted' => __( 'View deleted.', 'gridsmith' ), 'deleteView' => __( 'Delete this saved view?', 'gridsmith' ), /* translators: 1: number of changes restored, 2: skipped. */ 'rolledBack' => __( 'Rolled back: %1$d values restored, %2$d skipped because they changed since.', 'gridsmith' ), 'rollbackAsk' => __( 'Roll back this batch? Values changed since then are kept.', 'gridsmith' ), 'cancelAsk' => __( 'Cancel this scheduled change?', 'gridsmith' ), 'runAsk' => __( 'Run this scheduled change now?', 'gridsmith' ), 'variationsNote' => __( 'variations', 'gridsmith' ), ); } /** * Render the main screen. */ public static function render_main() { if ( ! current_user_can( 'manage_woocommerce' ) ) { return; } $tab = self::current_tab(); ?>


array( __( 'Bulk edit orders', 'gridsmith' ), __( 'Change the status of many orders at once in the same grid, filtered by status or customer. Works with High-Performance Order Storage.', 'gridsmith' ) ), 'coupons' => array( __( 'Bulk edit coupons', 'gridsmith' ), __( 'Edit coupon codes, amounts, discount types, expiry dates and usage limits in a spreadsheet.', 'gridsmith' ) ), 'history' => array( __( 'Change history', 'gridsmith' ), __( 'Every grid edit and bulk action is logged with who changed what, from which value to which value — and any batch can be rolled back with one click.', 'gridsmith' ) ), 'scheduled' => array( __( 'Scheduled bulk changes', 'gridsmith' ), __( 'Schedule a price change for later — start a sale at midnight on Friday and end it on Monday — and Gridsmith applies it in the background.', 'gridsmith' ) ), ); $info = isset( $text[ $tab ] ) ? $text[ $tab ] : array( '', '' ); ?>

key(); $controls = method_exists( $entity, 'filter_controls' ) ? $entity->filter_controls() : self::product_filter_controls(); ?>
'product_cat', 'hide_empty' => false, 'orderby' => 'name', ) ); if ( ! is_wp_error( $terms ) ) { foreach ( $terms as $term ) { $cats[ $term->slug ] = $term->name; } } return array( array( 'key' => 'search', 'type' => 'search', 'label' => __( 'Search', 'gridsmith' ), 'placeholder' => __( 'Name or SKU…', 'gridsmith' ), ), array( 'key' => 'category', 'type' => 'select', 'label' => __( 'Category', 'gridsmith' ), 'all' => __( 'All categories', 'gridsmith' ), 'options' => $cats, ), array( 'key' => 'stock_status', 'type' => 'select', 'label' => __( 'Stock status', 'gridsmith' ), 'all' => __( 'Any stock status', 'gridsmith' ), 'options' => wc_get_product_stock_status_options(), ), array( 'key' => 'type', 'type' => 'select', 'label' => __( 'Type', 'gridsmith' ), 'all' => __( 'All types', 'gridsmith' ), 'options' => wc_get_product_types(), ), array( 'key' => 'status', 'type' => 'select', 'label' => __( 'Status', 'gridsmith' ), 'all' => __( 'Any status', 'gridsmith' ), 'options' => BDBE_Entity_Products::statuses(), ), ); } /** * Bulk price dialog. */ private static function render_price_dialog() { $pro = BDBE_Edition::is_pro(); ?>

>
'array', 'sanitize_callback' => array( __CLASS__, 'sanitize_settings' ), 'default' => BDBE_Plugin::default_settings(), ) ); register_setting( 'bdbe_settings_group', 'bdbe_delete_data', array( 'type' => 'integer', 'sanitize_callback' => 'absint', 'default' => 0, ) ); } /** * Sanitise settings. * * @param mixed $input Raw input. * @return array */ public static function sanitize_settings( $input ) { $input = is_array( $input ) ? $input : array(); $old = BDBE_Plugin::settings(); $out = $old; $per = isset( $input['per_page'] ) ? absint( $input['per_page'] ) : 50; $out['per_page'] = in_array( $per, array( 20, 50, 100, 200 ), true ) ? $per : 50; if ( BDBE_Edition::is_pro() ) { if ( isset( $input['custom_fields'] ) ) { $keys = preg_split( '/[\s,]+/', sanitize_textarea_field( (string) $input['custom_fields'] ) ); $keys = array_filter( array_map( 'trim', (array) $keys ), function ( $key ) { return (bool) preg_match( '/^[A-Za-z0-9_\-]{1,64}$/', $key ); } ); $out['custom_fields'] = implode( "\n", array_slice( array_unique( $keys ), 0, 15 ) ); } if ( isset( $input['history_days'] ) ) { $out['history_days'] = min( 3650, max( 1, absint( $input['history_days'] ) ) ); } } BDBE_Plugin::flush_settings(); return $out; } /** * Settings screen. */ public static function render_settings() { if ( ! current_user_can( 'manage_woocommerce' ) ) { return; } $s = BDBE_Plugin::settings(); $pro = BDBE_Edition::is_pro(); ?>