HEX
Server: nginx/1.27.1
System: Linux in-3 5.15.0-161-generic #171-Ubuntu SMP Sat Oct 11 08:17:01 UTC 2025 x86_64
User: ivenus-clone (3297)
PHP: 7.4.33
Disabled: exec,passthru,shell_exec,system,proc_open,popen,parse_ini_file,show_source
Upload Files
File: /storage/v4513/tepnot/public_html/wp-content/plugins/dokan-pro/includes/ProductBulkEdit.php
<?php

namespace WeDevs\DokanPro;

use WC_Tax;
use WC_Product;
use WC_Product_Variable;
use WeDevs\Dokan\ProductCategory\Helper;
use Automattic\WooCommerce\Utilities\NumberUtil;

/**
 * Dokan Pro Product Bulk Edit Class
 *
 * @since   3.4.0
 *
 * @package dokan
 */
class ProductBulkEdit {

    /**
     * Load automatically when class initiate
     *
     * @since 3.4.0
     *
     * @uses  actions
     * @uses  filters
     */
    public function __construct() {
        add_filter( 'dokan_bulk_product_statuses', [ $this, 'bulk_product_status' ] );
        add_action( 'dokan_product_list_before_table_body_start', [ $this, 'bulk_edit_form' ] );
        add_action( 'template_redirect', [ $this, 'bulk_edit' ] );
        add_action( 'dokan_product_dashboard_errors', [ $this, 'display_updated_message' ] );
        add_action( 'dokan_before_bulk_edit_save_single_item', [ $this, 'bulk_edit_save_product_brands' ], 10, 2 );
        add_action( 'dokan_after_bulk_edit_save_single_item', 'dokan_trigger_product_create_email' );
    }

    /**
     * Show Updated success message
     *
     * @since 2.6.3
     *
     * @return void
     */
    public function display_updated_message( $type ) {
        if ( 'product_bulk_edit_save_success' === $type ) {
            dokan_get_template_part(
                'global/dokan-success',
                '',
                [
                    'deleted' => true,
                    'message' => __( 'Product successfully updated.', 'dokan' ),
                ]
            );
        }
    }

    /**
     * Add bulk edit status.
     *
     * @since 3.4.0
     *
     * @param array $bulk_statuses previous status.
     *
     * @return array
     */
    public function bulk_product_status( $bulk_statuses ) {
        if ( ! current_user_can( 'dokan_edit_product' ) ) {
            return $bulk_statuses;
        }

        return dokan_array_insert_after( $bulk_statuses, [ 'edit' => __( 'Edit', 'dokan' ) ], - 1 );
    }

    /**
     * Dokan bulk edit form.
     *
     * @since 3.4.0
     *
     * @return void
     */
    public function bulk_edit_form() {
        if ( ! dokan_is_seller_enabled( dokan_get_current_user_id() ) ) {
            return;
        }

        $shipping_class = get_terms( 'product_shipping_class', [ 'hide_empty' => false ] );
        $post_statuses  = [
            ''        => __( '- No change -', 'dokan' ),
            'draft'   => dokan_get_post_status( 'draft' ),
        ];

        if ( 'publish' === dokan_get_new_post_status() ) {
            $post_statuses['publish'] = dokan_get_post_status( 'publish' );
        } else {
            $post_statuses['pending'] = dokan_get_post_status( 'pending' );
        }

        $comment_status = [
            ''      => __( '— No change —', 'dokan' ),
            'open'  => __( 'Allow', 'dokan' ),
            'close' => __( 'Do not Allow', 'dokan' ),
        ];
        $price          = [
            ''  => __( '— No change —', 'dokan' ),
            '1' => __( 'Change to:', 'dokan' ),
            '2' => __( 'Increase existing price by (fixed amount or %):', 'dokan' ),
            '3' => __( 'Decrease existing price by (fixed amount or %):', 'dokan' ),
        ];
        $sale           = [
            ''  => __( '— No change —', 'dokan' ),
            '1' => __( 'Change to:', 'dokan' ),
            '2' => __( 'Increase existing sale price by (fixed amount or %):', 'dokan' ),
            '3' => __( 'Decrease existing sale price by (fixed amount or %):', 'dokan' ),
            '4' => __( 'Set to regular price decreased by (fixed amount or %):', 'dokan' ),
        ];
        $tax_status     = [
            ''         => __( '— No change —', 'dokan' ),
            'taxable'  => __( 'Taxable', 'dokan' ),
            'shipping' => __( 'Shipping only', 'dokan' ),
            'none'     => _x( 'None', 'Tax status', 'dokan' ),
        ];
        $tax_classes    = WC_Tax::get_tax_classes();
        if ( ! in_array( '', $tax_classes, true ) ) { // Make sure "Standard rate" (empty class name) is present.
            array_unshift( $tax_classes, 'Standard' );
        }
        $weight             = [
            ''  => __( '— No change —', 'dokan' ),
            '1' => __( 'Change to:', 'dokan' ),
        ];
        $lwh                = [
            ''  => __( '— No change —', 'dokan' ),
            '1' => __( 'Change to:', 'dokan' ),
        ];
        $visibility         = [
            ''        => __( '— No change —', 'dokan' ),
            'visible' => __( 'Catalog &amp; search', 'dokan' ),
            'catalog' => __( 'Catalog', 'dokan' ),
            'search'  => __( 'Search', 'dokan' ),
            'hidden'  => __( 'Hidden', 'dokan' ),
        ];
        $featured           = [
            ''    => __( '— No change —', 'dokan' ),
            'yes' => __( 'Yes', 'dokan' ),
            'no'  => __( 'No', 'dokan' ),
        ];
        $manage_stock       = [
            ''    => __( '— No change —', 'dokan' ),
            'yes' => __( 'Yes', 'dokan' ),
            'no'  => __( 'No', 'dokan' ),
        ];
        $stock_qty          = [
            ''  => __( '— No change —', 'dokan' ),
            '1' => __( 'Change to:', 'dokan' ),
            '2' => __( 'Increase existing stock by:', 'dokan' ),
            '3' => __( 'Decrease existing stock by:', 'dokan' ),
        ];
        $sold_individually  = [
            ''    => __( '— No change —', 'dokan' ),
            'yes' => __( 'Yes', 'dokan' ),
            'no'  => __( 'No', 'dokan' ),
        ];
        $is_single_category = 'single' === dokan_get_option( 'product_category_style', 'dokan_selling', 'single' );
        $args               = [
            'pro'                => true,
            'comment_status'     => $comment_status,
            'price'              => $price,
            'sale'               => $sale,
            'tax_status'         => $tax_status,
            'tax_classes'        => $tax_classes,
            'weight'             => $weight,
            'lwh'                => $lwh,
            'visibility'         => $visibility,
            'featured'           => $featured,
            'shipping_class'     => $shipping_class,
            'post_statuses'      => $post_statuses,
            'manage_stock'       => $manage_stock,
            'stock_qty'          => $stock_qty,
            'sold_individually'  => $sold_individually,
            'is_single_category' => $is_single_category,
        ];

        dokan_get_template_part( 'products/edit/bulk-edit-form', '', $args );
    }


    /**
     * Bulk saving product info.
     *
     * @since 3.4.0
     *
     * @throws \WC_Data_Exception
     *
     * @return void
     */
    public function bulk_edit() {
        if ( ! isset( $_POST['dokan-bulk-product-edit'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['dokan-bulk-product-edit'] ) ), 'dokan-bulk-product-edit-action' ) ) {
            return;
        }

        if ( ! current_user_can( 'dokan_edit_product' ) ) {
            wc_add_notice( __( 'You don\'t have the permission.', 'dokan' ), 'error' );

            return;
        }

        $request_data       = wp_unslash( $_POST );
        $bulk_products      = isset( $request_data['products_id'] ) ? array_map( 'sanitize_text_field', (array) $request_data['products_id'] ) : [];
        $is_single_category = 'single' === dokan_get_option( 'product_category_style', 'dokan_selling', 'single' );

        // $request_data['status'] is product bulk action status ( edit/publish... ), when user publishes products we have to skip updating chosen cats.
        if ( isset( $request_data['status'] ) && 'publish' !== $request_data['status'] && isset( $request_data['chosen_product_cat_bulk'] ) ) {
            $chosen_product_cat = $request_data['chosen_product_cat_bulk'];
            $chosen_cat = $is_single_category ? reset( $chosen_product_cat ) : $chosen_product_cat;

            foreach ( $bulk_products as $id ) {
                Helper::set_object_terms_from_chosen_categories( $id, (array) $chosen_cat );
            }
        }

        if ( ! empty( $request_data['product_brands'] ) ) {
            $request_data['product_brands'] = array_map( 'absint', (array) $request_data['product_brands'] );
        }

        if ( ! empty( $request_data['product_tags'] ) ) {
            $product_tags    = [];
            $can_create_tags = 'on' === dokan_get_option( 'product_vendors_can_create_tags', 'dokan_selling', 'off' );

            foreach ( (array) $request_data['product_tags'] as $tag ) {
                // include existing tags
                if ( is_numeric( $tag ) ) {
                    $product_tags[] = intval( $tag );
                    continue;
                }
                // check if vendors can create tags
                if ( ! $can_create_tags ) {
                    continue;
                }
                // create new tag
                $new_tag = wp_insert_term( $tag, 'product_tag' );
                if ( ! is_wp_error( $new_tag ) ) {
                    $product_tags[] = $new_tag['term_id'];
                }
            }

            $request_data['product_tags'] = $product_tags;
        }

        // set product status
        $vendor_id   = dokan_get_current_user_id();
        $post_status = dokan_get_new_post_status( $vendor_id );
        if ( ! empty( $request_data['post_status'] ) && 'publish' !== $request_data['post_status'] ) {
            $post_status = $request_data['post_status'];
        }

        $request_data['post_status'] = $post_status;

        if ( ! empty( $bulk_products ) ) {
            foreach ( $bulk_products as $post_id ) {
                // Get the product and save.
                $product = wc_get_product( $post_id );

                // Make sure it's a product object
                if ( ! is_a( $product, 'WC_Product' ) ) {
                    continue;
                }

                // Make sure it's current vendor's product
                if ( dokan_get_vendor_by_product( $product, true ) !== (int) $vendor_id ) {
                    continue;
                }

                $this->bulk_edit_save( $product, $request_data );
            }

            /**
             * Hook for bulk edit after save single item.
             *
             * @since 3.4.0
             *
             * @param WC_Product $product
             */
            do_action( 'dokan_after_bulk_edit_save', $bulk_products );

            wc_add_notice( __( 'Product successfully updated.', 'dokan' ), 'success' );

            global $wp;
            $redirect_url = home_url(
                add_query_arg(
                    [
                        $_GET,
                    ], $wp->request
                )
            );

            wp_safe_redirect( $redirect_url );
            exit;
        }
    }

    /**
     * Bulk edit saving.
     *
     * @since 3.4.0
     *
     * @param mixed|WC_Product $product WC_Product object.
     *
     * @throws \WC_Data_Exception
     *
     * @return void
     */
    public function bulk_edit_save( $product, $request_data ) {
        $data_store = $product->get_data_store();

        if ( ! empty( $request_data['change_weight'] ) && isset( $request_data['_weight'] ) ) {
            $product->set_weight( wc_clean( $request_data['_weight'] ) );
        }

        if ( ! empty( $request_data['change_dimensions'] ) ) {
            if ( isset( $request_data['_length'] ) ) {
                $product->set_length( wc_clean( $request_data['_length'] ) );
            }
            if ( isset( $request_data['_width'] ) ) {
                $product->set_width( wc_clean( $request_data['_width'] ) );
            }
            if ( isset( $request_data['_height'] ) ) {
                $product->set_height( wc_clean( $request_data['_height'] ) );
            }
        }

        if ( ! empty( $request_data['_tax_status'] ) ) {
            $product->set_tax_status( wc_clean( $request_data['_tax_status'] ) );
        }

        if ( ! empty( $request_data['_tax_class'] ) ) {
            $tax_class = wc_clean( $request_data['_tax_class'] );
            if ( 'standard' === $tax_class ) {
                $tax_class = '';
            }
            $product->set_tax_class( $tax_class );
        }

        if ( ! $product->is_virtual() ) {
            if ( ! empty( $request_data['_shipping_class'] ) ) {
                if ( '_no_shipping_class' === $request_data['_shipping_class'] ) {
                    $product->set_shipping_class_id( 0 );
                } else {
                    $shipping_class_id = $data_store->get_shipping_class_id_by_slug( wc_clean( $request_data['_shipping_class'] ) );
                    $product->set_shipping_class_id( $shipping_class_id );
                }
            }
        }

        if ( ! empty( $request_data['_visibility'] ) ) {
            $product->set_catalog_visibility( wc_clean( $request_data['_visibility'] ) );
        }

        if ( ! empty( $request_data['_featured'] ) ) {
            // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
            $product->set_featured( $request_data['_featured'] );
            // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
        }

        if ( ! empty( $request_data['_sold_individually'] ) ) {
            if ( 'yes' === $request_data['_sold_individually'] ) {
                $product->set_sold_individually( 'yes' );
            } else {
                $product->set_sold_individually( '' );
            }
        }

        // Handle price - remove dates and set to lowest.
        $change_price_product_types    = [ 'simple', 'external' ];
        $can_product_type_change_price = false;

        foreach ( $change_price_product_types as $product_type ) {
            if ( $product->is_type( $product_type ) ) {
                $can_product_type_change_price = true;
                break;
            }
        }

        if ( $can_product_type_change_price ) {
            $regular_price_changed = $this->set_new_price( $product, 'regular', $request_data );
            $sale_price_changed    = $this->set_new_price( $product, 'sale', $request_data );

            if ( $regular_price_changed || $sale_price_changed ) {
                $product->set_date_on_sale_to( '' );
                $product->set_date_on_sale_from( '' );

                if ( $product->get_regular_price() < $product->get_sale_price() ) {
                    $product->set_sale_price( '' );
                }
            }
        }

        // Handle Stock Data.
        $was_managing_stock = $product->get_manage_stock() ? 'yes' : 'no';
        $backorders         = $product->get_backorders();
        $backorders         = ! empty( $request_data['_backorders'] ) ? wc_clean( $request_data['_backorders'] ) : $backorders;

        if ( ! empty( $request_data['_manage_stock'] ) ) {
            $manage_stock = 'yes' === wc_clean( $request_data['_manage_stock'] ) && 'grouped' !== $product->get_type() ? 'yes' : 'no';
        } else {
            $manage_stock = $was_managing_stock;
        }

        $stock_amount = 'yes' === $manage_stock && ! empty( $request_data['change_stock'] ) && isset( $request_data['_stock'] ) ? wc_stock_amount( $request_data['_stock'] ) : $product->get_stock_quantity();

        $product->set_manage_stock( $manage_stock );

        if ( 'external' !== $product->get_type() ) {
            $product->set_backorders( $backorders );
        }

        if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
            $change_stock = absint( $request_data['change_stock'] );
            switch ( $change_stock ) {
                case 2:
                    wc_update_product_stock( $product, $stock_amount, 'increase', true );
                    break;
                case 3:
                    wc_update_product_stock( $product, $stock_amount, 'decrease', true );
                    break;
                default:
                    wc_update_product_stock( $product, $stock_amount, 'set', true );
                    break;
            }
        } else {
            // Reset values if WooCommerce Setting - Manage Stock status is disabled.
            $product->set_stock_quantity( '' );
            $product->set_manage_stock( 'no' );
        }

        // set product status
        if ( ! empty( $request_data['post_status'] ) ) {
            $product->set_status( $request_data['post_status'] );
        }

        if ( ! empty( $request_data['product_tags'] ) ) {
            $tags_to_add                = array_merge( $product->get_tag_ids(), $request_data['product_tags'] );
            $maximum_tags_select_length = apply_filters( 'dokan_product_tags_select_max_length', - 1 );

            // Setting limitation for how many product tags that vendor can input.
            if ( $maximum_tags_select_length !== - 1 && count( $tags_to_add ) > $maximum_tags_select_length ) {
                $tags_to_add = array_slice( $tags_to_add, 0, $maximum_tags_select_length );
            }

            if ( ! empty( $tags_to_add ) ) {
                $product->set_tag_ids( $tags_to_add );
            }
        }

        if ( ! empty( $request_data['product_cat'] ) ) {
            $product_cat = array_merge( $product->get_category_ids(), $request_data['product_cat'] );
            $product->set_category_ids( $product_cat );
        }

        $stock_status = empty( $request_data['_stock_status'] ) ? null : wc_clean( $request_data['_stock_status'] );
        $product      = $this->maybe_update_stock_status( $product, $stock_status );

        /**
         * Hook for bulk edit before save single item.
         *
         * @since 3.4.0
         *
         * @param WC_Product $product
         * @param mixed      $request_data
         */
        do_action( 'dokan_before_bulk_edit_save_single_item', $product, $request_data );

        $product->save();

        /**
         * Hook for bulk edit after save single item.
         *
         * @since 3.4.0
         *
         * @param WC_Product $product
         */
        do_action( 'dokan_after_bulk_edit_save_single_item', $product );
    }

    /**
     * Set new price.
     *
     * @since 3.4.0
     *
     * @param \WC_Product $product      WC Product object.
     * @param string      $price_type   which type of price.
     * @param mixed       $request_data super global post data.
     *
     * @return bool
     */
    private function set_new_price( $product, $price_type, $request_data ) {
        if ( empty( $request_data[ "change_{$price_type}_price" ] ) || ! isset( $request_data[ "_{$price_type}_price" ] ) ) {
            return false;
        }

        $old_price     = empty( $product->{"get_{$price_type}_price"}() ) ? 0 : $product->{"get_{$price_type}_price"}();
        $price_changed = false;

        $change_price  = absint( $request_data[ "change_{$price_type}_price" ] );
        $raw_price     = wc_clean( wp_unslash( $request_data[ "_{$price_type}_price" ] ) );
        $is_percentage = (bool) strstr( $raw_price, '%' );
        $price         = wc_format_decimal( $raw_price );

        switch ( $change_price ) {
            case 1:
                $new_price = $price;
                break;
            case 2:
                if ( $is_percentage ) {
                    $percent   = $price / 100;
                    $new_price = $old_price + ( $old_price * $percent );
                } else {
                    $new_price = $old_price + $price;
                }
                break;
            case 3:
                if ( $is_percentage ) {
                    $percent   = $price / 100;
                    $new_price = max( 0, $old_price - ( $old_price * $percent ) );
                } else {
                    $new_price = max( 0, $old_price - $price );
                }
                break;
            case 4:
                if ( 'sale' !== $price_type ) {
                    break;
                }
                $regular_price = $product->get_regular_price();
                if ( $is_percentage ) {
                    $percent   = $price / 100;
                    $new_price = max( 0, $regular_price - ( NumberUtil::round( $regular_price * $percent, wc_get_price_decimals() ) ) );
                } else {
                    $new_price = max( 0, $regular_price - $price );
                }
                break;

            default:
                break;
        }

        if ( isset( $new_price ) && $new_price !== $old_price ) {
            $price_changed = true;
            $new_price     = NumberUtil::round( $new_price, wc_get_price_decimals() );
            $product->{"set_{$price_type}_price"}( $new_price );
        }

        return $price_changed;
    }

    /**
     * Apply product type constraints to stock status.
     *
     * @since 3.4.0
     *
     * @param \WC_Product $product      The product whose stock status will be adjusted.
     * @param string|null $stock_status The stock status to use for adjustment, or null if no new stock status has been supplied in the request.
     *
     * @return \WC_Product The supplied product, or the synced product if it was a variable product.
     */
    private function maybe_update_stock_status( $product, $stock_status ) {
        if ( $product->is_type( 'external' ) ) {
            // External products are always in stock.
            $product->set_stock_status( 'instock' );
        } elseif ( isset( $stock_status ) ) {
            if ( $product->is_type( 'variable' ) && ! $product->get_manage_stock() ) {
                // Stock status is determined by children.
                foreach ( $product->get_children() as $child_id ) {
                    $child = wc_get_product( $child_id );
                    if ( ! $product->get_manage_stock() ) {
                        $child->set_stock_status( $stock_status );
                        $child->save();
                    }
                }
                $product = WC_Product_Variable::sync( $product, false );
            } else {
                $product->set_stock_status( $stock_status );
            }
        }

        return $product;
    }

    /**
     * Bulk save product brands for a product.
     *
     * Collects existing and new product brand IDs, merges them, and sets the brands for the product.
     *
     * @since 4.0.5
     *
     * @param WC_Product $product      The WooCommerce product object.
     * @param array      $request_data The request data containing product brands.
     *
     * @return void
     */
    public function bulk_edit_save_product_brands( WC_Product $product, array $request_data ): void {
        if ( empty( $request_data['product_brands'] ) ) {
            return;
        }

        // Collect product brands from the original product and the request data.
        $brand_ids    = dokan()->product->get_brand_ids( $product->get_id() );
        $added_brands = array_unique( array_merge( $brand_ids, $request_data['product_brands'] ) );

        // Set brands for the product.
        dokan()->product->save_brands( $product->get_id(), $added_brands );
    }
}