From 9d90ea6b3e9a17480ea34f0e6e236b5f4d666d8a Mon Sep 17 00:00:00 2001 From: Nurul Umbhiya <zikubd@gmail.com> Date: Fri, 1 Dec 2023 11:56:41 +0600 Subject: [PATCH 1/5] new: added skeleton for product fields feature --- includes/Product/Manager.php | 409 ++++++++++++++--------- includes/ProductForm/Component.php | 328 ++++++++++++++++++ includes/ProductForm/Elements.php | 78 +++++ includes/ProductForm/Factory.php | 238 ++++++++++++++ includes/ProductForm/Field.php | 264 +++++++++++++++ includes/ProductForm/Init.php | 512 +++++++++++++++++++++++++++++ includes/ProductForm/Section.php | 99 ++++++ 7 files changed, 1769 insertions(+), 159 deletions(-) create mode 100644 includes/ProductForm/Component.php create mode 100644 includes/ProductForm/Elements.php create mode 100644 includes/ProductForm/Factory.php create mode 100644 includes/ProductForm/Field.php create mode 100644 includes/ProductForm/Init.php create mode 100644 includes/ProductForm/Section.php diff --git a/includes/Product/Manager.php b/includes/Product/Manager.php index 6fdeaf0362..cdade3e327 100644 --- a/includes/Product/Manager.php +++ b/includes/Product/Manager.php @@ -3,10 +3,12 @@ namespace WeDevs\Dokan\Product; use WC_Product; +use WC_Product_Attribute; use WC_Product_Download; use WeDevs\Dokan\Cache; use WP_Query; use WP_Error; +use WeDevs\Dokan\ProductForm\Elements as FormElements; /** * Product manager Class @@ -28,7 +30,7 @@ public function all( $args = [] ) { $defaults = [ 'post_type' => 'product', 'post_status' => $post_statuses, - 'posts_per_page' => - 1, + 'posts_per_page' => -1, 'orderby' => 'post_date ID', 'order' => 'DESC', 'paged' => 1, @@ -71,100 +73,99 @@ public function get( $product_id ) { * @return WC_Product|null|false */ public function create( $args = [] ) { - $id = isset( $args['id'] ) ? absint( $args['id'] ) : 0; + $product_id = isset( $args[ FormElements::ID ] ) ? absint( $args[ FormElements::ID ] ) : 0; + $is_update = ! empty( $product_id ); // Type is the most important part here because we need to be using the correct class and methods. - if ( isset( $args['type'] ) ) { - $classname = \WC_Product_Factory::get_classname_from_product_type( $args['type'] ); + if ( isset( $args[ FormElements::TYPE ] ) ) { + $classname = \WC_Product_Factory::get_classname_from_product_type( $args[ FormElements::TYPE ] ); if ( ! class_exists( $classname ) ) { $classname = 'WC_Product_Simple'; } - $product = new $classname( $id ); - } elseif ( isset( $args['id'] ) ) { - $product = wc_get_product( $id ); + $product = new $classname( $product_id ); + } elseif ( isset( $args[ FormElements::ID ] ) ) { + $product = wc_get_product( $product_id ); } else { $product = new \WC_Product_Simple(); } // Post title. - if ( isset( $args['name'] ) ) { - $product->set_name( wp_filter_post_kses( $args['name'] ) ); + if ( isset( $args[ FormElements::NAME ] ) ) { + $product->set_name( wp_filter_post_kses( $args[ FormElements::NAME ] ) ); } // Post content. - if ( isset( $args['description'] ) ) { - $product->set_description( wp_filter_post_kses( $args['description'] ) ); + if ( isset( $args[ FormElements::DESCRIPTION ] ) ) { + $product->set_description( wp_filter_post_kses( $args[ FormElements::DESCRIPTION ] ) ); } // Post excerpt. - if ( isset( $args['short_description'] ) ) { - $product->set_short_description( wp_filter_post_kses( $args['short_description'] ) ); + if ( isset( $args[ FormElements::SHORT_DESCRIPTION ] ) ) { + $product->set_short_description( wp_filter_post_kses( $args[ FormElements::SHORT_DESCRIPTION ] ) ); } // Post status. - if ( isset( $args['status'] ) ) { - $product->set_status( get_post_status_object( $args['status'] ) ? $args['status'] : 'draft' ); + if ( isset( $args[ FormElements::STATUS ] ) ) { + $product->set_status( get_post_status_object( $args[ FormElements::STATUS ] ) ? $args[ FormElements::STATUS ] : 'draft' ); } // Post slug. - if ( isset( $args['slug'] ) ) { - $product->set_slug( $args['slug'] ); + if ( isset( $args[ FormElements::SLUG ] ) ) { + $product->set_slug( $args[ FormElements::SLUG ] ); } // Menu order. - if ( isset( $args['menu_order'] ) ) { - $product->set_menu_order( $args['menu_order'] ); + if ( isset( $args[ FormElements::MENU_ORDER ] ) ) { + $product->set_menu_order( $args[ FormElements::MENU_ORDER ] ); } // Comment status. - if ( isset( $args['reviews_allowed'] ) ) { - $product->set_reviews_allowed( $args['reviews_allowed'] ); + if ( isset( $args[ FormElements::REVIEWS_ALLOWED ] ) ) { + $product->set_reviews_allowed( $args[ FormElements::REVIEWS_ALLOWED ] ); } // Virtual. - if ( isset( $args['virtual'] ) ) { - $product->set_virtual( $args['virtual'] ); + if ( isset( $args[ FormElements::VIRTUAL ] ) ) { + $product->set_virtual( $args[ FormElements::VIRTUAL ] ); } // Tax status. - if ( isset( $args['tax_status'] ) ) { - $product->set_tax_status( $args['tax_status'] ); + if ( isset( $args[ FormElements::TAX_STATUS ] ) ) { + $product->set_tax_status( $args[ FormElements::TAX_STATUS ] ); } // Tax Class. - if ( isset( $args['tax_class'] ) ) { - $product->set_tax_class( $args['tax_class'] ); + if ( isset( $args[ FormElements::TAX_CLASS ] ) ) { + $product->set_tax_class( $args[ FormElements::TAX_CLASS ] ); } // Catalog Visibility. - if ( isset( $args['catalog_visibility'] ) ) { - $product->set_catalog_visibility( $args['catalog_visibility'] ); + if ( isset( $args[ FormElements::CATALOG_VISIBILITY ] ) ) { + $product->set_catalog_visibility( $args[ FormElements::CATALOG_VISIBILITY ] ); } // Purchase Note. - if ( isset( $args['purchase_note'] ) ) { - $product->set_purchase_note( wp_kses_post( wp_unslash( $args['purchase_note'] ) ) ); + if ( isset( $args[ FormElements::PURCHASE_NOTE ] ) ) { + $product->set_purchase_note( wp_kses_post( wp_unslash( $args[ FormElements::PURCHASE_NOTE ] ) ) ); } // Featured Product. - if ( isset( $args['featured'] ) ) { - $product->set_featured( $args['featured'] ); + if ( isset( $args[ FormElements::FEATURED ] ) ) { + $product->set_featured( $args[ FormElements::FEATURED ] ); } // Shipping data. $product = $this->save_product_shipping_data( $product, $args ); // SKU. - if ( isset( $args['sku'] ) ) { - $product->set_sku( wc_clean( $args['sku'] ) ); + if ( isset( $args[ FormElements::SKU ] ) ) { + $product->set_sku( wc_clean( $args[ FormElements::SKU ] ) ); } // Attributes. - if ( isset( $args['attributes'] ) ) { - $product->set_attributes( $args['attributes'] ); - } + $product = $this->save_product_attribute_data( $product, $args ); // Sales and prices. if ( in_array( $product->get_type(), [ 'variable', 'grouped' ], true ) ) { @@ -175,45 +176,45 @@ public function create( $args = [] ) { $product->set_price( '' ); } else { // Regular Price. - if ( isset( $args['regular_price'] ) ) { - $product->set_regular_price( $args['regular_price'] ); + if ( isset( $args[ FormElements::REGULAR_PRICE ] ) ) { + $product->set_regular_price( $args[ FormElements::REGULAR_PRICE ] ); } // Sale Price. - if ( isset( $args['sale_price'] ) ) { - $product->set_sale_price( $args['sale_price'] ); + if ( isset( $args[ FormElements::SALE_PRICE ] ) ) { + $product->set_sale_price( $args[ FormElements::SALE_PRICE ] ); } - if ( isset( $args['date_on_sale_from'] ) ) { - $product->set_date_on_sale_from( $args['date_on_sale_from'] ); + if ( isset( $args[ FormElements::DATE_ON_SALE_FROM ] ) ) { + $product->set_date_on_sale_from( $args[ FormElements::DATE_ON_SALE_FROM ] ); } - if ( isset( $args['date_on_sale_from_gmt'] ) ) { - $product->set_date_on_sale_from( $args['date_on_sale_from_gmt'] ? strtotime( $args['date_on_sale_from_gmt'] ) : null ); + if ( isset( $args[ FormElements::DATE_ON_SALE_FROM_GMT ] ) ) { + $product->set_date_on_sale_from( $args[ FormElements::DATE_ON_SALE_FROM_GMT ] ? strtotime( $args[ FormElements::DATE_ON_SALE_FROM_GMT ] ) : null ); } - if ( isset( $args['date_on_sale_to'] ) ) { - $product->set_date_on_sale_to( $args['date_on_sale_to'] ); + if ( isset( $args[ FormElements::DATE_ON_SALE_TO ] ) ) { + $product->set_date_on_sale_to( $args[ FormElements::DATE_ON_SALE_TO ] ); } - if ( isset( $args['date_on_sale_to_gmt'] ) ) { - $product->set_date_on_sale_to( $args['date_on_sale_to_gmt'] ? strtotime( $args['date_on_sale_to_gmt'] ) : null ); + if ( isset( $args[ FormElements::DATE_ON_SALE_TO_GMT ] ) ) { + $product->set_date_on_sale_to( $args[ FormElements::DATE_ON_SALE_TO_GMT ] ? strtotime( $args[ FormElements::DATE_ON_SALE_TO_GMT ] ) : null ); } } // Product parent ID. - if ( isset( $args['parent_id'] ) ) { - $product->set_parent_id( $args['parent_id'] ); + if ( isset( $args[ FormElements::PARENT_ID ] ) ) { + $product->set_parent_id( $args[ FormElements::PARENT_ID ] ); } // Sold individually. - if ( isset( $args['sold_individually'] ) ) { - $product->set_sold_individually( $args['sold_individually'] ); + if ( isset( $args[ FormElements::SOLD_INDIVIDUALLY ] ) ) { + $product->set_sold_individually( $args[ FormElements::SOLD_INDIVIDUALLY ] ); } // Stock status; stock_status has priority over in_stock. - if ( isset( $args['stock_status'] ) ) { - $stock_status = $args['stock_status']; + if ( isset( $args[ FormElements::STOCK_STATUS ] ) ) { + $stock_status = $args[ FormElements::STOCK_STATUS ]; } else { $stock_status = $product->get_stock_status(); } @@ -221,13 +222,17 @@ public function create( $args = [] ) { // Stock data. if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) { // Manage stock. - if ( isset( $args['manage_stock'] ) ) { - $product->set_manage_stock( $args['manage_stock'] ); + if ( isset( $args[ FormElements::MANAGE_STOCK ] ) ) { + $product->set_manage_stock( $args[ FormElements::MANAGE_STOCK ] ); + } + + if ( isset( $args[ FormElements::LOW_STOCK_AMOUNT ] ) ) { + $product->set_low_stock_amount( $args[ FormElements::LOW_STOCK_AMOUNT ] ); } // Backorders. - if ( isset( $args['backorders'] ) ) { - $product->set_backorders( $args['backorders'] ); + if ( isset( $args[ FormElements::BACKORDERS ] ) ) { + $product->set_backorders( $args[ FormElements::BACKORDERS ] ); } if ( $product->is_type( 'grouped' ) ) { @@ -247,11 +252,11 @@ public function create( $args = [] ) { } // Stock quantity. - if ( isset( $args['stock_quantity'] ) ) { - $product->set_stock_quantity( wc_stock_amount( $args['stock_quantity'] ) ); - } elseif ( isset( $args['inventory_delta'] ) ) { + if ( isset( $args[ FormElements::STOCK_QUANTITY ] ) ) { + $product->set_stock_quantity( wc_stock_amount( $args[ FormElements::STOCK_QUANTITY ] ) ); + } elseif ( isset( $args[ FormElements::INVENTORY_DELTA ] ) ) { $stock_quantity = wc_stock_amount( $product->get_stock_quantity() ); - $stock_quantity += wc_stock_amount( $args['inventory_delta'] ); + $stock_quantity += wc_stock_amount( $args[ FormElements::INVENTORY_DELTA ] ); $product->set_stock_quantity( wc_stock_amount( $stock_quantity ) ); } } else { @@ -268,9 +273,9 @@ public function create( $args = [] ) { $product = $this->maybe_update_stock_status( $product, $stock_status ); // Upsells. - if ( isset( $args['upsell_ids'] ) ) { + if ( isset( $args[ FormElements::UPSELL_IDS ] ) ) { $upsells = []; - $ids = $args['upsell_ids']; + $ids = $args[ FormElements::UPSELL_IDS ]; if ( ! empty( $ids ) ) { foreach ( $ids as $id ) { @@ -284,9 +289,9 @@ public function create( $args = [] ) { } // Cross sells. - if ( isset( $args['cross_sell_ids'] ) ) { + if ( isset( $args[ FormElements::CROSS_SELL_IDS ] ) ) { $crosssells = []; - $ids = $args['cross_sell_ids']; + $ids = $args[ FormElements::CROSS_SELL_IDS ]; if ( ! empty( $ids ) ) { foreach ( $ids as $id ) { @@ -300,47 +305,47 @@ public function create( $args = [] ) { } // Product categories. - if ( isset( $args['categories'] ) && is_array( $args['categories'] ) ) { - $product = $this->save_taxonomy_terms( $product, $args['categories'] ); + if ( isset( $args[ FormElements::CATEGORIES ] ) && is_array( $args[ FormElements::CATEGORIES ] ) ) { + $product = $this->save_taxonomy_terms( $product, $args[ FormElements::CATEGORIES ] ); } // Product tags. - if ( isset( $args['tags'] ) && is_array( $args['tags'] ) ) { - $product = $this->save_taxonomy_terms( $product, $args['tags'], 'tag' ); + if ( isset( $args[ FormElements::TAGS ] ) && is_array( $args[ FormElements::TAGS ] ) ) { + $product = $this->save_taxonomy_terms( $product, $args[ FormElements::TAGS ], 'tag' ); } // Downloadable. - if ( isset( $args['downloadable'] ) ) { - $product->set_downloadable( $args['downloadable'] ); + if ( isset( $args[ FormElements::DOWNLOADABLE ] ) ) { + $product->set_downloadable( $args[ FormElements::DOWNLOADABLE ] ); } // Downloadable options. if ( $product->get_downloadable() ) { // Downloadable files. - if ( isset( $args['downloads'] ) && is_array( $args['downloads'] ) ) { - $product = $this->save_downloadable_files( $product, $args['downloads'] ); + if ( isset( $args[ FormElements::DOWNLOADS ] ) && is_array( $args[ FormElements::DOWNLOADS ] ) ) { + $product = $this->save_downloadable_files( $product, $args[ FormElements::DOWNLOADS ] ); } // Download limit. - if ( isset( $args['download_limit'] ) ) { - $product->set_download_limit( $args['download_limit'] ); + if ( isset( $args[ FormElements::DOWNLOAD_LIMIT ] ) ) { + $product->set_download_limit( $args[ FormElements::DOWNLOAD_LIMIT ] ); } // Download expiry. - if ( isset( $args['download_expiry'] ) ) { - $product->set_download_expiry( $args['download_expiry'] ); + if ( isset( $args[ FormElements::DOWNLOAD_EXPIRY ] ) ) { + $product->set_download_expiry( $args[ FormElements::DOWNLOAD_EXPIRY ] ); } } // Product url and button text for external products. if ( $product->is_type( 'external' ) ) { - if ( isset( $args['external_url'] ) ) { - $product->set_product_url( $args['external_url'] ); + if ( isset( $args[ FormElements::EXTERNAL_URL ] ) ) { + $product->set_product_url( $args[ FormElements::EXTERNAL_URL ] ); } - if ( isset( $args['button_text'] ) ) { - $product->set_button_text( $args['button_text'] ); + if ( isset( $args[ FormElements::BUTTON_TEXT ] ) ) { + $product->set_button_text( $args[ FormElements::BUTTON_TEXT ] ); } } @@ -350,51 +355,94 @@ public function create( $args = [] ) { } // Set children for a grouped product. - if ( $product->is_type( 'grouped' ) && isset( $args['grouped_products'] ) ) { - $product->set_children( $args['grouped_products'] ); + if ( $product->is_type( 'grouped' ) && isset( $args[ FormElements::GROUP_PRODUCTS ] ) ) { + $product->set_children( $args[ FormElements::GROUP_PRODUCTS ] ); } // Set featured image id - if ( ! empty( $args['featured_image_id'] ) ) { - $product->set_image_id( $args['featured_image_id'] ); + if ( ! empty( $args[ FormElements::FEATURED_IMAGE_ID ] ) ) { + $product->set_image_id( $args[ FormElements::FEATURED_IMAGE_ID ] ); } // Set gallery image ids - if ( ! empty( $args['gallery_image_ids'] ) ) { - $product->set_gallery_image_ids( $args['gallery_image_ids'] ); + if ( ! empty( $args[ FormElements::GALLERY_IMAGE_IDS ] ) ) { + $product->set_gallery_image_ids( $args[ FormElements::GALLERY_IMAGE_IDS ] ); } // Allow set meta_data. - if ( ! empty( $args['meta_data'] ) && is_array( $args['meta_data'] ) ) { - foreach ( $args['meta_data'] as $meta ) { + if ( ! empty( $args[ FormElements::META_DATA ] ) && is_array( $args[ FormElements::META_DATA ] ) ) { + foreach ( $args[ FormElements::META_DATA ] as $meta ) { $product->update_meta_data( $meta['key'], $meta['value'], isset( $meta['id'] ) ? $meta['id'] : '' ); } } - if ( ! empty( $args['date_created'] ) ) { - $date = rest_parse_date( $args['date_created'] ); + // Set total sales for newly created product + if ( ! $is_update ) { + // Set date created. + if ( ! empty( $args[ FormElements::DATE_CREATED ] ) ) { + $date = rest_parse_date( $args[ FormElements::DATE_CREATED ] ); - if ( $date ) { - $product->set_date_created( $date ); + if ( $date ) { + $product->set_date_created( $date ); + } } - } - if ( ! empty( $args['date_created_gmt'] ) ) { - $date = rest_parse_date( $args['date_created_gmt'], true ); + // set date created gmt + if ( ! empty( $args[ FormElements::DATE_CREATED_GMT ] ) ) { + $date = rest_parse_date( $args[ FormElements::DATE_CREATED_GMT ], true ); - if ( $date ) { - $product->set_date_created( $date ); + if ( $date ) { + $product->set_date_created( $date ); + } } - } - // Set total sales for newly created product - if ( ! empty( $id ) ) { + // Set total sales to zero $product->set_total_sales( 0 ); } $product_id = $product->save(); - return wc_get_product( $product_id ); + //call dokan hooks + if ( ! $is_update ) { + do_action( 'dokan_new_product_added', $product_id, $product ); + } else { + do_action( 'dokan_product_updated', $product_id, $product ); + } + + return $this->get( $product_id ); + } + + /** + * Update product data + * + * @since 3.0.0 + * + * @return WC_Product|WP_Error|false + */ + public function update( $args = [] ) { + $id = isset( $args['id'] ) ? absint( $args['id'] ) : 0; + + if ( empty( $id ) ) { + return new WP_Error( 'no-id-found', __( 'No product ID found for updating', 'dokan-lite' ), [ 'status' => 401 ] ); + } + + return $this->create( $args ); + } + + /** + * Delete product data + * + * @since 3.0.0 + * + * @return WC_Product|null|false + */ + public function delete( $product_id, $force = false ) { + $product = $this->get( $product_id ); + if ( $product ) { + $product->delete( [ 'force_delete' => $force ] ); + } + + return $product; } /** @@ -402,7 +450,7 @@ public function create( $args = [] ) { * * @since 3.0.0 * - * @param WC_Product $product Product instance. + * @param WC_Product $product Product instance. * @param \WP_REST_Request $request Request data. * * @return WC_Product @@ -458,39 +506,6 @@ public function save_default_attributes( $product, $request ) { return $product; } - /** - * Update product data - * - * @since 3.0.0 - * - * @return WC_Product|WP_Error|false - */ - public function update( $args = [] ) { - $id = isset( $args['id'] ) ? absint( $args['id'] ) : 0; - - if ( empty( $id ) ) { - return new WP_Error( 'no-id-found', __( 'No product ID found for updating', 'dokan-lite' ), [ 'status' => 401 ] ); - } - - return $this->create( $args ); - } - - /** - * Delete product data - * - * @since 3.0.0 - * - * @return WC_Product|null|false - */ - public function delete( $product_id, $force = false ) { - $product = $this->get( $product_id ); - if ( $product ) { - $product->delete( [ 'force_delete' => $force ] ); - } - - return $product; - } - /** * Save product shipping data. * @@ -501,42 +516,119 @@ public function delete( $product_id, $force = false ) { */ protected function save_product_shipping_data( $product, $data ) { // Virtual. - if ( isset( $data['virtual'] ) && true === $data['virtual'] ) { + if ( isset( $data[ FormElements::VIRTUAL ] ) && true === $data[ FormElements::VIRTUAL ] ) { $product->set_weight( '' ); $product->set_height( '' ); $product->set_length( '' ); $product->set_width( '' ); } else { - if ( isset( $data['weight'] ) ) { - $product->set_weight( $data['weight'] ); + if ( isset( $data[ FormElements::WEIGHT ] ) ) { + $product->set_weight( $data[ FormElements::WEIGHT ] ); } // Height. - if ( isset( $data['dimensions']['height'] ) ) { - $product->set_height( $data['dimensions']['height'] ); + if ( isset( $data[ FormElements::DIMENSIONS ][ FormElements::DIMENSIONS_HEIGHT ] ) ) { + $product->set_height( $data[ FormElements::DIMENSIONS ][ FormElements::DIMENSIONS_HEIGHT ] ); } // Width. - if ( isset( $data['dimensions']['width'] ) ) { - $product->set_width( $data['dimensions']['width'] ); + if ( isset( $data[ FormElements::DIMENSIONS ][ FormElements::DIMENSIONS_WIDTH ] ) ) { + $product->set_width( $data[ FormElements::DIMENSIONS ][ FormElements::DIMENSIONS_WIDTH ] ); } // Length. - if ( isset( $data['dimensions']['length'] ) ) { - $product->set_length( $data['dimensions']['length'] ); + if ( isset( $data[ FormElements::DIMENSIONS ][ FormElements::DIMENSIONS_LENGTH ] ) ) { + $product->set_length( $data[ FormElements::DIMENSIONS ][ FormElements::DIMENSIONS_LENGTH ] ); } } // Shipping class. - if ( isset( $data['shipping_class'] ) ) { + if ( isset( $data[ FormElements::SHIPPING_CLASS ] ) ) { $data_store = $product->get_data_store(); - $shipping_class_id = $data_store->get_shipping_class_id_by_slug( wc_clean( $data['shipping_class'] ) ); + $shipping_class_id = $data_store->get_shipping_class_id_by_slug( wc_clean( $data[ FormElements::SHIPPING_CLASS ] ) ); $product->set_shipping_class_id( $shipping_class_id ); } return $product; } + /** + * Save product attribute data. + * + * @param WC_Product $product Product instance. + * @param array $args Product data's. + * + * @return WC_Product + */ + protected function save_product_attribute_data( $product, $args ) { + if ( isset( $args[ FormElements::ATTRIBUTES ] ) ) { + $attributes = []; + + foreach ( $args[ FormElements::ATTRIBUTES ] as $attribute ) { + $attribute_id = 0; + $attribute_name = ''; + + // Check ID for global attributes or name for product attributes. + if ( ! empty( $attribute[ FormElements::ATTRIBUTES_ID ] ) ) { + $attribute_id = absint( $attribute[ FormElements::ATTRIBUTES_ID ] ); + $attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id ); + } elseif ( ! empty( $attribute[ FormElements::ATTRIBUTES_NAME ] ) ) { + $attribute_name = wc_clean( $attribute[ FormElements::ATTRIBUTES_NAME ] ); + } + + if ( ! $attribute_id && ! $attribute_name ) { + continue; + } + + if ( $attribute_id ) { + + if ( isset( $attribute[ FormElements::ATTRIBUTES_OPTIONS ] ) ) { + $options = $attribute[ FormElements::ATTRIBUTES_OPTIONS ]; + + if ( ! is_array( $attribute[ FormElements::ATTRIBUTES_OPTIONS ] ) ) { + // Text based attributes - Posted values are term names. + $options = explode( WC_DELIMITER, $options ); + } + + $values = array_map( 'wc_sanitize_term_text_based', $options ); + $values = array_filter( $values, 'strlen' ); + } else { + $values = []; + } + + if ( ! empty( $values ) ) { + // Add attribute to array, but don't set values. + $attribute_object = new WC_Product_Attribute(); + $attribute_object->set_id( $attribute_id ); + $attribute_object->set_name( $attribute_name ); + $attribute_object->set_options( $values ); + $attribute_object->set_position( isset( $attribute[ FormElements::ATTRIBUTES_POSITION ] ) ? (string) absint( $attribute[ FormElements::ATTRIBUTES_POSITION ] ) : '0' ); + $attribute_object->set_visible( ( isset( $attribute[ FormElements::ATTRIBUTES_VISIBLE ] ) && $attribute[ FormElements::ATTRIBUTES_VISIBLE ] ) ? 1 : 0 ); + $attribute_object->set_variation( ( isset( $attribute[ FormElements::ATTRIBUTES_VARIATION ] ) && $attribute[ FormElements::ATTRIBUTES_VARIATION ] ) ? 1 : 0 ); + $attributes[] = $attribute_object; + } + } elseif ( isset( $attribute[ FormElements::ATTRIBUTES_OPTIONS ] ) ) { + // Custom attribute - Add attribute to array and set the values. + if ( is_array( $attribute[ FormElements::ATTRIBUTES_OPTIONS ] ) ) { + $values = $attribute[ FormElements::ATTRIBUTES_OPTIONS ]; + } else { + $values = explode( WC_DELIMITER, $attribute[ FormElements::ATTRIBUTES_OPTIONS ] ); + } + $attribute_object = new WC_Product_Attribute(); + $attribute_object->set_name( $attribute_name ); + $attribute_object->set_options( $values ); + $attribute_object->set_position( isset( $attribute[ FormElements::ATTRIBUTES_POSITION ] ) ? (string) absint( $attribute[ FormElements::ATTRIBUTES_POSITION ] ) : '0' ); + $attribute_object->set_visible( ( isset( $attribute[ FormElements::ATTRIBUTES_VISIBLE ] ) && $attribute[ FormElements::ATTRIBUTES_VISIBLE ] ) ? 1 : 0 ); + $attribute_object->set_variation( ( isset( $attribute[ FormElements::ATTRIBUTES_VARIATION ] ) && $attribute[ FormElements::ATTRIBUTES_VARIATION ] ) ? 1 : 0 ); + $attributes[] = $attribute_object; + } + } + $product->set_attributes( $attributes ); + } + + return $product; + } + /** * Save taxonomy terms. * @@ -547,10 +639,12 @@ protected function save_product_shipping_data( $product, $data ) { * @return WC_Product */ protected function save_taxonomy_terms( $product, $terms, $taxonomy = 'cat' ) { + $term_ids = wp_list_pluck( $terms, 'id' ); + if ( 'cat' === $taxonomy ) { - $product->set_category_ids( $terms ); + $product->set_category_ids( $term_ids ); } elseif ( 'tag' === $taxonomy ) { - $product->set_tag_ids( $terms ); + $product->set_tag_ids( $term_ids ); } return $product; @@ -559,8 +653,8 @@ protected function save_taxonomy_terms( $product, $terms, $taxonomy = 'cat' ) { /** * Save downloadable files. * - * @param WC_Product $product Product instance. - * @param array $downloads Downloads data. + * @param WC_Product $product Product instance. + * @param array $downloads Downloads data. * * @return WC_Product */ @@ -593,10 +687,7 @@ protected function save_downloadable_files( $product, $downloads ) { * @return mixed */ protected 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 ( 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 ) { @@ -679,7 +770,7 @@ public function best_selling( $args = [] ) { $args['orderby'] = 'meta_value_num'; $product_visibility_term_ids = wc_get_product_visibility_term_ids(); - $args['tax_query'][] = [ // phpcs:ignore + $args['tax_query'][] = [ // phpcs:ignore 'taxonomy' => 'product_visibility', 'field' => 'term_taxonomy_id', 'terms' => is_search() ? $product_visibility_term_ids['exclude-from-search'] : $product_visibility_term_ids['exclude-from-catalog'], diff --git a/includes/ProductForm/Component.php b/includes/ProductForm/Component.php new file mode 100644 index 0000000000..1ad7f6d1ae --- /dev/null +++ b/includes/ProductForm/Component.php @@ -0,0 +1,328 @@ +<?php + +namespace WeDevs\Dokan\ProductForm; + +defined( 'ABSPATH' ) || exit; + +abstract class Component { + + /** + * Required field attributes + * + * @since DOKAN_SINCE + * + * @var array $required_attributes + */ + protected $required_fields = [ + 'id', + 'title', + ]; + + /** + * Field Data + * + * @since DOKAN_SINCE + * + * @var array $data + */ + protected $data = [ + 'id' => '', // html id attribute of the field, required + 'title' => '', // label for the field + 'description' => '', // description of the field + 'help_content' => '', // help content for the field + 'visibility' => true, // field visibility, if the field is visible under frontend + 'required' => false, // by default, all fields are not required + 'order' => 30, + 'error_message' => '', + ]; + + public function __construct( string $id, array $args = [] ) { + foreach ( $this->data as $key => $value ) { + if ( method_exists( $this, "set_{$key}" ) ) { + $this->{"set_{$key}"}( $args[ $key ] ); + } + } + } + + /** + * Get field id + * + * @since DOKAN_SINCE + * + * @return string + */ + public function get_id(): string { + return $this->data['id']; + } + + /** + * Set Field ID + * + * @since DOKAN_SINCE + * + * @param string $id + * + * @see sanitize_key() + * + * @return $this + */ + public function set_id( string $id ): self { + $this->data['id'] = sanitize_key( $id ); + + return $this; + } + + /** + * Get field label + * + * @since DOKAN_SINCE + * + * @return string + */ + public function get_title(): string { + return $this->data['title']; + } + + /** + * Set field label validated with + * + * @since DOKAN_SINCE + * + * @param string $title + * + * @see sanitize_text_field() + * + * @return $this + */ + public function set_title( string $title ): self { + $this->data['title'] = sanitize_text_field( $title ); + + return $this; + } + + /** + * Get field description + * + * @since DOKAN_SINCE + * + * @return string + */ + public function get_description(): string { + return $this->data['description']; + } + + /** + * Set field description, validated with + * + * @since DOKAN_SINCE + * + * @param string $description + * + * @see wp_kses_post() + * + * @return $this + */ + public function set_description( string $description ): self { + $this->data['description'] = wp_kses_post( $description ); + + return $this; + } + + /** + * Get field help content + * + * @since DOKAN_SINCE + * + * @return string + */ + public function get_help_content(): string { + return $this->data['help_content']; + } + + /** + * Set field help content, validated with + * + * @since DOKAN_SINCE + * + * @param string $help_content + * + * @see wp_kses_post() + * + * @return $this + */ + public function set_help_content( string $help_content ): self { + $this->data['help_content'] = wp_kses_post( $help_content ); + + return $this; + } + + /** + * Get field visibility + * + * @since DOKAN_SINCE + * + * @return bool + */ + public function get_visibility(): bool { + return wc_string_to_bool( $this->data['visibility'] ); + } + + /** + * Check the field is visible or not + * + * @since DOKAN_SINCE + * + * @return bool + */ + public function is_visible(): bool { + return $this->get_visibility(); + } + + /** + * Set field visibility + * + * @since DOKAN_SINCE + * + * @param bool $visibility + * + * @return $this + */ + public function set_visibility( bool $visibility ): self { + $this->data['visibility'] = $visibility; + + return $this; + } + + /** + * Get field required status + * + * @since DOKAN_SINCE + * + * @return bool + */ + public function get_required(): bool { + return wc_string_to_bool( $this->data['required'] ); + } + + /** + * Check the field is required or not + * + * @since DOKAN_SINCE + * + * @return bool + */ + public function is_required(): bool { + return $this->get_required(); + } + + /** + * Set field required status + * + * @since DOKAN_SINCE + * + * @param bool $required + * + * @return $this + */ + public function set_required( bool $required ): self { + $this->data['required'] = $required; + + return $this; + } + + /** + * Get field options + * + * @since DOKAN_SINCE + * + * @return int + */ + public function get_order(): int { + return $this->data['order']; + } + + /** + * Set field order + * + * @since DOKAN_SINCE + * + * @param int $order + * + * @return $this + */ + public function set_order( int $order ): self { + $this->data['order'] = $order; + + return $this; + } + + /** + * Get a field error message + * + * @since DOKAN_SINCE + * + * @return string + */ + public function get_error_message() { + return $this->data['error_message']; + } + + /** + * Set field error message + * + * @since DOKAN_SINCE + * + * @param string $error_message + * + * @see wp_kses_post() + * + * @return $this + */ + public function set_error_message( string $error_message ): self { + $this->data['error_message'] = wp_kses_post( $error_message ); + + return $this; + } + + /** + * Sorting function for product fields form Component class. + * + * @param Component $a Component a. + * @param Component $b Component b. + * @param string $sort_by sort order. + * + * @return int + */ + public static function sort( Component $a, Component $b, string $sort_by = 'asc' ) { + $a_val = $a->get_order(); + $b_val = $b->get_order(); + if ( 'asc' === $sort_by ) { + return $a_val <=> $b_val; + } else { + return $b_val <=> $a_val; + } + } + + /** + * Get missing arguments of args array. + * + * @param array $args field arguments. + * + * @return array + */ + public function get_missing_arguments( $args ) { + return array_values( + array_filter( + $this->required_fields, + function ( $arg_key ) use ( $args ) { + // return false if not exists or empty + if ( ! array_key_exists( $arg_key, $args ) || empty( $args[ $arg_key ] ) ) { + return false; + } + + return true; + } + ) + ); + } +} diff --git a/includes/ProductForm/Elements.php b/includes/ProductForm/Elements.php new file mode 100644 index 0000000000..5c6f73c8f1 --- /dev/null +++ b/includes/ProductForm/Elements.php @@ -0,0 +1,78 @@ +<?php + +namespace WeDevs\Dokan\ProductForm; + +defined( 'ABSPATH' ) || exit; + +/** + * Product Form Elements + * + * @since DOKAN_SINCE + */ +class Elements { + const ID = 'id'; + const TYPE = 'type'; + const NAME = 'name'; + const DESCRIPTION = 'description'; + const SHORT_DESCRIPTION = 'short_description'; + const STATUS = 'status'; + const SLUG = 'slug'; + const MENU_ORDER = 'menu_order'; + const REVIEWS_ALLOWED = 'reviews_allowed'; + const VIRTUAL = 'virtual'; + const TAX_STATUS = 'tax_status'; + const TAX_CLASS = 'tax_class'; + const CATALOG_VISIBILITY = 'catalog_visibility'; + const PURCHASE_NOTE = 'purchase_note'; + const FEATURED = 'featured'; + const SKU = 'sku'; + const WEIGHT = 'weight'; + const DIMENSIONS = 'dimensions'; + const DIMENSIONS_HEIGHT = 'height'; + const DIMENSIONS_WIDTH = 'width'; + const DIMENSIONS_LENGTH = 'length'; + const SHIPPING_CLASS = 'shipping_class_id'; + const ATTRIBUTES = 'attributes'; + const ATTRIBUTES_ID = 'id'; + const ATTRIBUTES_NAME = 'name'; + const ATTRIBUTES_OPTIONS = 'options'; + const ATTRIBUTES_POSITION = 'position'; + const ATTRIBUTES_VISIBLE = 'visible'; + const ATTRIBUTES_VARIATION = 'variation'; + const DEFAULT_ATTRIBUTES = 'default_attributes'; + const REGULAR_PRICE = 'regular_price'; + const SALE_PRICE = 'sale_price'; + const DATE_CREATED = 'date_created'; + const DATE_CREATED_GMT = 'date_created_gmt'; + const DATE_ON_SALE_FROM = 'date_on_sale_from'; + const DATE_ON_SALE_FROM_GMT = 'date_on_sale_from_gmt'; + const DATE_ON_SALE_TO = 'date_on_sale_to'; + const DATE_ON_SALE_TO_GMT = 'date_on_sale_to_gmt'; + const PARENT_ID = 'parent_id'; + const SOLD_INDIVIDUALLY = 'sold_individually'; + const LOW_STOCK_AMOUNT = 'low_stock_amount'; + const STOCK_STATUS = 'stock_status'; + const MANAGE_STOCK = 'manage_stock'; + const BACKORDERS = 'backorders'; + const STOCK_QUANTITY = 'stock_quantity'; + const INVENTORY_DELTA = 'inventory_delta'; + const UPSELL_IDS = 'upsell_ids'; + const CROSS_SELL_IDS = 'cross_sell_ids'; + const CATEGORIES = 'categories'; + const TAGS = 'tags'; + const DOWNLOADABLE = 'downloadable'; + const DOWNLOADS = 'downloads'; + const DOWNLOAD_LIMIT = 'download_limit'; + const DOWNLOAD_EXPIRY = 'download_expiry'; + const EXTERNAL_URL = 'product_url'; + const BUTTON_TEXT = 'button_text'; + const GROUP_PRODUCTS = 'children'; + const FEATURED_IMAGE_ID = 'featured_image_id'; + const GALLERY_IMAGE_IDS = 'gallery_image_ids'; + const META_DATA = 'meta_data'; + const DISABLE_SHIPPING_META = '_disable_shipping'; + const OVERWRITE_SHIPPING_META = '_overwrite_shipping'; + const ADDITIONAL_SHIPPING_COST_META = '_additional_price'; + const ADDITIONAL_SHIPPING_QUANTITY_META = '_additional_qty'; + const ADDITIONAL_SHIPPING_PROCESSING_TIME_META = '_dps_processing_time'; +} diff --git a/includes/ProductForm/Factory.php b/includes/ProductForm/Factory.php new file mode 100644 index 0000000000..336c6c2495 --- /dev/null +++ b/includes/ProductForm/Factory.php @@ -0,0 +1,238 @@ +<?php + +namespace WeDevs\Dokan\ProductForm; + +use WeDevs\Dokan\Traits\Singleton; +use WP_Error; + +defined( 'ABSPATH' ) || exit; + +/** + * Product Form Factory + * + * @since DOKAN_SINCE + */ +class Factory { + use Singleton; + + /** + * Store form sections. + * + * @var array + */ + protected static $form_sections = []; + + /** + * Store form fields. + * + * @var array + */ + protected static $form_fields = []; + + /** + * Adds a field to the product form. + * + * @param string $id Field id. + * @param array $args Array containing the necessary arguments. + * $args = array( + * 'id' => '', // (string - required) html id attribute of the field + * 'title' => '', // (string - required) label for the field + * 'section' => '', // (string - required) section id + * 'name' => '', // (string) html name attribute of the field, if not exists id value will be used as name + * 'property' => '', // (string) if exists, this will be the name of the field + * 'field_type' => '', // (string) html field type + * 'placeholder' => '', // (string) html placeholder attribute value for the field + * 'options' => '', // (array) if the field is select, radio, checkbox, etc + * 'additional_args' => [], // (array) additional arguments for the field + * 'description' => '', // (string) description of the field + * 'help_content' => '', // (string) help content for the field + * 'visibility' => true, // (bool) field visibility, if the field is visible under frontend + * 'required' => false, // (bool) by default, all fields are not required + * 'order' => 30, // (int) field order + * ). + * + * @return Field|WP_Error New field or WP_Error. + */ + public static function add_field( $id, $args ) { + $new_field = self::create_item( 'field', 'Field', $id, $args ); + if ( is_wp_error( $new_field ) ) { + return $new_field; + } + + // store newly created field + self::$form_fields[ $id ] = $new_field; + + // sort fields after adding a new item + self::$form_fields = self::get_items( 'field', 'Field' ); + + return $new_field; + } + + /** + * Adds a section to the product form. + * + * @since DOKAN_SINCE + * + * @param string $id Card id. + * @param array $args Array containing the necessary arguments. + * + * @return Section|WP_Error New section or WP_Error. + */ + public static function add_section( $id, $args ) { + $new_section = self::create_item( 'section', 'Section', $id, $args ); + if ( is_wp_error( $new_section ) ) { + return $new_section; + } + + // store newly created section + self::$form_sections[ $id ] = $new_section; + + //sort sections after adding a new item + self::$form_sections = self::get_items( 'section', 'Section' ); + + return $new_section; + } + + /** + * Returns list of registered fields. + * + * @return array list of registered fields. + */ + public static function get_fields() { + return self::$form_fields; + } + + /** + * Returns registered field based on given field id. + * + * @param string $id Field id. + * + * @return Field|WP_Error New field or WP_Error. + */ + public static function get_field( string $id ) { + if ( array_key_exists( $id, self::$form_fields ) ) { + return self::$form_fields[ $id ]; + } + + return new WP_Error( 'dokan_product_form_missing_field', esc_html__( 'No field found with given id', 'dokan-lite' ) ); + } + + /** + * Returns list of registered sections. + * + * @param string $sort_by key and order to sort by. + * + * @return array list of registered sections. + */ + public static function get_sections( string $sort_by = 'asc' ): array { + return self::get_items( 'section', 'Section', $sort_by ); + } + + /** + * Returns registerd section with given section id. + * + * @param string $id Section id. + * + * @return Section|WP_Error New section or WP_Error. + */ + public static function get_section( string $id ) { + if ( array_key_exists( $id, self::$form_sections ) ) { + return self::$form_sections[ $id ]; + } + + return new WP_Error( 'dokan_product_form_missing_section', esc_html__( 'No section found with given id', 'dokan-lite' ) ); + } + + /** + * Returns list of registered items. + * + * @param string $type Form component type. + * @param class-string $class_name Class name of \WeDevs\Dokan\ProductForm\Component type. + * @param string $sort_by key and order to sort by. + * + * @return array list of registered items. + */ + private static function get_items( string $type, string $class_name, string $sort_by = 'asc' ) { + $item_list = self::get_item_list( $type ); + $items = array_values( $item_list ); + + if ( class_exists( $class_name ) && method_exists( $class_name, 'sort' ) ) { + /** + * @var \WeDevs\Dokan\ProductForm\Component $class_name + */ + usort( + $items, + function ( $a, $b ) use ( $sort_by, $class_name ) { + return $class_name::sort( $a, $b, $sort_by ); + } + ); + } + + return $items; + } + + /** + * @param string $type + * + * @return array + */ + private static function get_item_list( string $type ): array { + $mapping = [ + 'field' => self::$form_fields, + 'section' => self::$form_sections, + ]; + + if ( array_key_exists( $type, $mapping ) ) { + return $mapping[ $type ]; + } + + return []; + } + + /** + * Creates a new item. + * + * @since DOKAN_SINCE + * + * @param string $type Form component type. + * @param class-string $class_name Class name of \WeDevs\Dokan\ProductForm\Component type. + * @param string $id Item id. + * @param array $args additional arguments for item. + * + * @return Field|Section|WP_Error New product form item or WP_Error. + */ + private static function create_item( string $type, string $class_name, string $id, array $args ) { + if ( ! class_exists( $class_name ) ) { + return new WP_Error( + 'dokan_product_form_' . $type . '_missing_form_class', + sprintf( + /* translators: 1: missing class name. */ + esc_html__( '%1$s class does not exist.', 'dokan-lite' ), + $class_name + ) + ); + } + + $item_list = self::get_item_list( $type ); + if ( isset( $item_list[ $id ] ) ) { + return new WP_Error( + 'dokan_product_form_' . $type . '_duplicate_field_id', + sprintf( + /* translators: 1: Item type 2: Duplicate registered item id. */ + esc_html__( 'You have attempted to register a duplicate form %1$s with WooCommerce Form: %2$s', 'dokan-lite' ), + $type, + '`' . $id . '`' + ) + ); + } + + try { + return new $class_name( $id, $args ); + } catch ( \Exception $e ) { + return new WP_Error( + 'dokan_product_form_' . $type . '_class_creation', + $e->getMessage() + ); + } + } +} diff --git a/includes/ProductForm/Field.php b/includes/ProductForm/Field.php new file mode 100644 index 0000000000..f1230e0ba5 --- /dev/null +++ b/includes/ProductForm/Field.php @@ -0,0 +1,264 @@ +<?php + +namespace WeDevs\Dokan\ProductForm; + +defined( 'ABSPATH' ) || exit; + +/** + * Product Form Field + * + * @since DOKAN_SINCE + */ +class Field extends Component { + + /** + * Required field attributes + * + * @since DOKAN_SINCE + * + * @var array $required_attributes + */ + protected $required_fields = [ + 'id', + 'title', + 'section', + ]; + + /** + * Class constructor + * + * @since DOKAN_SINCE + * + * @param array $args + * + * @throws \Exception + */ + public function __construct( string $id, array $args = [] ) { + $data = [ + 'name' => '', // html name attribute of the field, if not exists id value will be used as name + 'property' => '', // if exists, this will be the name of the field + 'section' => '', // section id, required + 'field_type' => '', // html field type + 'placeholder' => '', // html placeholder attribute value for the field + 'options' => '', // if the field is select, radio, checkbox, etc + 'additional_properties' => [], // additional arguments for the field + ]; + $this->data = array_merge( $this->data, $data ); + + // set id from the args + $this->set_id( $id ); + + // call parent constructor + parent::__construct( $id, $args ); + + // check if the required attributes are exists + $missing_arguments = self::get_missing_arguments( $this->data ); + if ( count( $missing_arguments ) > 0 ) { + throw new \Exception( + sprintf( + /* translators: 1: Missing arguments list. */ + esc_html__( 'You are missing required arguments of Dokan ProductForm Field: %1$s', 'dokan-lite' ), + esc_attr( join( ', ', $missing_arguments ) ) + ) + ); + } + } + + /** + * Get field name + * + * @since DOKAN_SINCE + * + * @return string + */ + public function get_name(): string { + return $this->data['name']; + } + + /** + * Set field name, validated by @since DOKAN_SINCE + * + * @param string $name + * + * @see sanitize_title() + * + * @return $this + */ + public function set_name( string $name ): Field { + $this->data['name'] = sanitize_title( $name ); + + return $this; + } + + /** + * Get field property + * + * @since DOKAN_SINCE + * + * @return string + */ + public function get_property(): string { + return $this->data['property']; + } + + /** + * Set field property, validated by + * + * @since DOKAN_SINCE + * + * @param string $property + * + * @see sanitize_key() + * + * @return $this + */ + public function set_property( string $property ): Field { + $this->data['property'] = sanitize_key( $property ); + + return $this; + } + + /** + * Get the field section + * + * @since DOKAN_SINCE + * + * @return string + */ + public function get_section(): string { + return $this->data['section']; + } + + /** + * Set field section + * + * @since DOKAN_SINCE + * + * @param string $section + * + * @return $this + */ + public function set_section( string $section ): Field { + $this->data['section'] = sanitize_key( $section ); + + return $this; + } + + /** + * Get the field type + * + * @since DOKAN_SINCE + * + * @return string + */ + public function get_field_type(): string { + return $this->data['field_type']; + } + + /** + * Set field type + * + * @since DOKAN_SINCE + * + * @param string $field_type + * + * @return $this + */ + public function set_field_type( string $field_type ): Field { + // todo: add field type validation + $this->data['field_type'] = sanitize_key( $field_type ); + + return $this; + } + + /** + * Get field placeholder + * + * @since DOKAN_SINCE + * + * @return string + */ + public function get_placeholder(): string { + return $this->data['placeholder']; + } + + /** + * Set field placeholder, validated with + * + * @since DOKAN_SINCE + * + * @param string $placeholder + * + * @see wp_kses_post() + * + * @return $this + */ + public function set_placeholder( string $placeholder ): Field { + $this->data['placeholder'] = wp_kses_post( $placeholder ); + + return $this; + } + + /** + * Get field options + * + * @since DOKAN_SINCE + * + * @return array + */ + public function get_options(): array { + return $this->data['options']; + } + + /** + * Set field options + * + * @since DOKAN_SINCE + * + * @param array $options + * + * @return $this + */ + public function set_options( array $options ): Field { + $this->data['options'] = $options; + + return $this; + } + + /** + * Get field property + * + * @since DOKAN_SINCE + * + * @param string $property + * + * @return string|array + */ + public function get_additional_properties( string $property = '' ) { + if ( ! empty( $property ) ) { + return array_key_exists( $property, $this->data['additional_properties'] ) ? $this->data['additional_properties'][ $property ] : ''; + } + + return $this->data['additional_properties']; + } + + /** + * Set additional properties + * + * @since DOKAN_SINCE + * + * @param mixed $property + * @param mixed $value + * + * @return $this + */ + public function set_additional_properties( $property, $value = null ): Field { + if ( ! empty( $property ) && null !== $value ) { + $this->data['additional_properties'][ $property ] = $value; + } elseif ( is_array( $property ) ) { + $this->data['additional_properties'] = $property; + } + + return $this; + } +} diff --git a/includes/ProductForm/Init.php b/includes/ProductForm/Init.php new file mode 100644 index 0000000000..4df60fb6c3 --- /dev/null +++ b/includes/ProductForm/Init.php @@ -0,0 +1,512 @@ +<?php + +namespace WeDevs\Dokan\ProductForm; + +defined( 'ABSPATH' ) || exit; + +/** + * Init Product Form Fields + * + * @since DOKAN_SINCE + */ +class Init { + + /** + * Class constructor + * + * @since DOKAN_SINCE + * + * @return void + */ + public function __construct() { + add_action( 'init', [ $this, 'init_form_fields' ], 1 ); + } + + /** + * Init form fields + * + * @since DOKAN_SINCE + * + * @return void + */ + public function init_form_fields() { + $this->init_general_fields(); + $this->init_inventory_fields(); + $this->init_downloadable_fields(); + $this->init_other_fields(); + } + + /** + * Init general fields + * + * @since DOKAN_SINCE + * + * @return void + */ + public function init_general_fields() { + $section = Factory::add_section( + 'general', + [ + 'title' => __( 'General', 'dokan-lite' ), + 'order' => 10, + ] + ); + + $section->add_field( + Elements::ID, + [ + 'title' => __( 'Title', 'dokan-lite' ), + 'type' => 'text', + 'name' => 'post_title', + 'placeholder' => __( 'Enter product title...', 'dokan-lite' ), + 'error_msg' => __( 'Please enter product title!', 'dokan-lite' ), + ] + ); + + $section->add_field( + Elements::TYPE, [ + 'title' => __( 'Product Type', 'dokan-lite' ), + 'type' => 'select', + 'name' => 'product_type', + 'options' => apply_filters( + 'dokan_product_types', + [ + 'simple' => __( 'Simple', 'dokan-lite' ), // dokan lite only supports simple product + ] + ), + 'help_content' => __( 'Choose Variable if your product has multiple attributes - like sizes, colors, quality etc', 'dokan-lite' ), + ] + ); + + $section->add_field( + Elements::REGULAR_PRICE, [ + 'title' => __( 'Price', 'dokan-lite' ), + 'type' => 'text', + 'name' => '_regular_price', + 'placeholder' => '0.00', + ] + ); + + $section->add_field( + Elements::SALE_PRICE, [ + 'title' => __( 'Discounted Price', 'dokan-lite' ), + 'type' => 'text', + 'name' => '_sale_price', + 'placeholder' => '0.00', + ] + ); + + $section->add_field( + Elements::DATE_ON_SALE_FROM, [ + 'title' => __( 'From', 'dokan-lite' ), + 'type' => 'text', + 'name' => '_sale_price_dates_from', + 'placeholder' => wc_date_format(), + ] + ); + + $section->add_field( + Elements::DATE_ON_SALE_TO, [ + 'title' => __( 'To', 'dokan-lite' ), + 'type' => 'text', + 'name' => '_sale_price_dates_to', + 'placeholder' => wc_date_format(), + ] + ); + + $section->add_field( + Elements::CATEGORIES, [ + 'title' => __( 'Categories', 'dokan-lite' ), + 'type' => 'select', + 'name' => 'product_cat[]', + 'placeholder' => __( 'Select product categories', 'dokan-lite' ), + 'options' => [], + ] + ); + + $can_create_tags = dokan_get_option( 'product_vendors_can_create_tags', 'dokan_selling' ); + $tags_placeholder = 'on' === $can_create_tags ? __( 'Select tags/Add tags', 'dokan-lite' ) : __( 'Select product tags', 'dokan-lite' ); + $section->add_field( + Elements::TAGS, [ + 'title' => __( 'Tags', 'dokan-lite' ), + 'type' => 'select', + 'name' => 'product_tag[]', + 'placeholder' => $tags_placeholder, + ] + ); + + $section->add_field( + Elements::FEATURED_IMAGE_ID, [ + 'title' => __( 'Product Image', 'dokan-lite' ), + 'type' => 'image', + 'name' => 'feat_image_id', + 'placeholder' => __( 'Select product image', 'dokan-lite' ), + ] + ); + + $section->add_field( + Elements::GALLERY_IMAGE_IDS, [ + 'title' => __( 'Product Gallery', 'dokan-lite' ), + 'type' => 'gallery', + 'name' => 'product_image_gallery', + 'placeholder' => __( 'Select product gallery images', 'dokan-lite' ), + ] + ); + + $section->add_field( + Elements::SHORT_DESCRIPTION, [ + 'title' => __( 'Short Description', 'dokan-lite' ), + 'type' => 'textarea', + 'name' => 'post_excerpt', + 'placeholder' => __( 'Enter product short description', 'dokan-lite' ), + 'visibility' => false, + ] + ); + + $section->add_field( + Elements::DESCRIPTION, [ + 'title' => __( 'Description', 'dokan-lite' ), + 'type' => 'textarea', + 'name' => 'post_content', + 'placeholder' => __( 'Enter product description', 'dokan-lite' ), + 'required' => true, + 'visibility' => false, + ] + ); + } + + /** + * Init inventory fields + * + * @since DOKAN_SINCE + * + * @return void + */ + public function init_inventory_fields() { + $section = Factory::add_section( + 'inventory', + [ + 'title' => __( 'Inventory', 'dokan-lite' ), + 'description' => __( 'Manage your product stock', 'dokan-lite' ), + 'order' => 20, + ] + ); + + $section->add_field( + Elements::SKU, [ + 'title' => __( 'SKU', 'dokan-lite' ), + 'type' => 'text', + 'name' => '_sku', + 'placeholder' => __( 'Enter product SKU', 'dokan-lite' ), + ] + ); + + $section->add_field( + Elements::STOCK_STATUS, [ + 'title' => __( 'Stock Status', 'dokan-lite' ), + 'type' => 'select', + 'name' => '_stock_status', + 'options' => wc_get_product_stock_status_options(), + ] + ); + + $section->add_field( + Elements::MANAGE_STOCK, [ + 'title' => __( 'Enable product stock management', 'dokan-lite' ), + 'type' => 'checkbox', + 'name' => '_manage_stock', + 'value' => 'yes', + ] + ); + + $section->add_field( + Elements::STOCK_QUANTITY, [ + 'title' => __( 'Stock quantity', 'dokan-lite' ), + 'type' => 'number', + 'name' => '_stock', + 'placeholder' => '1', + 'min' => 0, + 'step' => 1, + ] + ); + + $section->add_field( + Elements::LOW_STOCK_AMOUNT, [ + 'title' => __( 'Low stock threshold', 'dokan-lite' ), + 'type' => 'number', + 'name' => '_low_stock_amount', + 'placeholder' => 1, + 'min' => 0, + 'step' => 1, + ] + ); + + $section->add_field( + Elements::BACKORDERS, [ + 'title' => __( 'Allow Backorders', 'dokan-lite' ), + 'type' => 'select', + 'name' => '_backorders', + 'options' => wc_get_product_backorder_options(), + ] + ); + + $section->add_field( + Elements::SOLD_INDIVIDUALLY, [ + 'title' => __( 'Sold Individually', 'dokan-lite' ), + 'type' => 'checkbox', + 'name' => '_sold_individually', + 'value' => 'yes', + ] + ); + } + + /** + * Init downloadable fields + * + * @since DOKAN_SINCE + * + * @return void + */ + public function init_downloadable_fields() { + $section = Factory::add_section( + 'downloadable', + [ + 'title' => __( 'Downloadable Options', 'dokan-lite' ), + 'description' => __( 'Configure your downloadable product settings', 'dokan-lite' ), + 'order' => 30, + ] + ); + + $section->add_field( + Elements::DOWNLOAD_LIMIT, [ + 'title' => __( 'Download Limit', 'dokan-lite' ), + 'type' => 'number', + 'name' => '_download_limit', + 'placeholder' => __( 'Unlimited', 'dokan-lite' ), + 'min' => 0, + 'step' => 1, + ] + ); + + $section->add_field( + Elements::DOWNLOAD_EXPIRY, [ + 'title' => __( 'Download Expiry', 'dokan-lite' ), + 'type' => 'number', + 'name' => '_download_expiry', + 'placeholder' => __( 'Never', 'dokan-lite' ), + 'min' => 0, + 'step' => 1, + ] + ); + } + + /** + * Init other fields + * + * @since DOKAN_SINCE + * + * @return void + */ + public function init_other_fields() { + $section = Factory::add_section( + 'others', + [ + 'title' => __( 'Other Options', 'dokan-lite' ), + 'description' => __( 'Set your extra product options', 'dokan-lite' ), + 'order' => 30, + ] + ); + + $section->add_field( + Elements::STATUS, [ + 'title' => __( 'Product Status', 'dokan-lite' ), + 'type' => 'select', + 'name' => 'post_status', + 'default' => dokan_get_default_product_status( dokan_get_current_user_id() ), + 'options' => dokan_get_available_post_status(), // get it with product_id param + ] + ); + + $section->add_field( + Elements::CATALOG_VISIBILITY, [ + 'title' => __( 'Visibility', 'dokan-lite' ), + 'type' => 'select', + 'name' => '_visibility', + 'options' => dokan_get_product_visibility_options(), + ] + ); + + $section->add_field( + Elements::PURCHASE_NOTE, [ + 'title' => __( 'Purchase Note', 'dokan-lite' ), + 'type' => 'textarea', + 'name' => '_purchase_note', + 'placeholder' => __( 'Customer will get this info in their order email', 'dokan-lite' ), + ] + ); + + $section->add_field( + Elements::REVIEWS_ALLOWED, [ + 'title' => __( 'Enable product reviews', 'dokan-lite' ), + 'type' => 'checkbox', + 'name' => '_enable_reviews', + 'value' => 'yes', + ] + ); + } + + /** + * Init shipping fields + * + * @since DOKAN_SINCE + * + * @return void + */ + public function init_shipping_fields() { + if ( ! dokan()->is_pro_exists() ) { + return; + } + + $is_shipping_disabled = 'sell_digital' === dokan_pro()->digital_product->get_selling_product_type(); + $tab_title = $is_shipping_disabled ? __( 'Tax', 'dokan-lite' ) : __( 'Shipping and Tax', 'dokan-lite' ); + $tab_desc = $is_shipping_disabled ? __( 'Manage tax for this product', 'dokan-lite' ) : __( 'Manage shipping and tax for this product', 'dokan-lite' ); + + $section = Factory::add_section( + 'shipping', + [ + 'title' => $tab_title, + 'description' => $tab_desc, + 'order' => 30, + ] + ); + + $section->add_field( + Elements::DISABLE_SHIPPING_META, [ + 'title' => __( 'Disable shipping', 'dokan-lite' ), + 'type' => 'checkbox', + 'name' => '_disable_shipping', + 'value' => 'no', + ] + ); + + $section->add_field( + Elements::WEIGHT, [ + 'title' => __( 'Weight', 'dokan-lite' ), + 'type' => 'number', + 'name' => '_weight', + 'placeholder' => '0.00', + 'min' => 0, + 'step' => 0.01, + ] + ); + + $section->add_field( + Elements::DIMENSIONS_LENGTH, [ + 'title' => __( 'Length', 'dokan-lite' ), + 'type' => 'number', + 'name' => '_length', + 'placeholder' => '0.00', + 'min' => 0, + 'step' => 0.01, + ] + ); + + $section->add_field( + Elements::DIMENSIONS_WIDTH, [ + 'title' => __( 'Width', 'dokan-lite' ), + 'type' => 'number', + 'name' => '_width', + 'placeholder' => '0.00', + 'min' => 0, + 'step' => 0.01, + ] + ); + + $section->add_field( + Elements::DIMENSIONS_HEIGHT, [ + 'title' => __( 'Height', 'dokan-lite' ), + 'type' => 'number', + 'name' => '_height', + 'placeholder' => '0.00', + 'min' => 0, + 'step' => 0.01, + ] + ); + + $shipping_settings_link = sprintf( '<a href="%1$s">', dokan_get_navigation_url( 'settings/shipping' ) ); + /* translators: %1$s is replaced with "HTML open entities," %2$s is replaced with "HTML close entities"*/ + $product_shipping_help_block = sprintf( esc_html__( 'Shipping classes are used by certain shipping methods to group similar products. Before adding a product, please configure the %1$s shipping settings %2$s', 'dokan-lite' ), $shipping_settings_link, '</a>' ); + + $section->add_field( + Elements::SHIPPING_CLASS, [ + 'title' => __( 'Shipping Class', 'dokan-lite' ), + 'type' => 'select', + 'name' => '_shipping_class', + 'placeholder' => __( 'Select shipping class', 'dokan-lite' ), + 'options' => [], + ] + ); + + $section->add_field( + Elements::OVERWRITE_SHIPPING_META, [ + 'title' => __( 'Override your store\'s default shipping cost for this product', 'dokan-lite' ), + 'type' => 'checkbox', + 'name' => '_overwrite_shipping', + 'value' => 'no', + ] + ); + + $section->add_field( + Elements::ADDITIONAL_SHIPPING_COST_META, [ + 'title' => __( 'Additional cost', 'dokan-lite' ), + 'type' => 'number', + 'name' => '_additional_price', + 'placeholder' => '0.00', + 'min' => 0, + 'step' => 0.01, + ] + ); + + $section->add_field( + Elements::ADDITIONAL_SHIPPING_QUANTITY_META, [ + 'title' => __( 'Per Qty Additional Price', 'dokan-lite' ), + 'type' => 'number', + 'name' => '_additional_qty', + 'placeholder' => '0', + 'min' => 0, + 'step' => 0.01, + ] + ); + + $section->add_field( + Elements::ADDITIONAL_SHIPPING_PROCESSING_TIME_META, [ + 'title' => __( 'Processing Time', 'dokan-lite' ), + 'type' => 'select', + 'name' => '_dps_processing_time', + 'options' => [], + ] + ); + + $section->add_field( + Elements::TAX_STATUS, [ + 'title' => __( 'Tax Status', 'dokan-lite' ), + 'type' => 'select', + 'name' => '_tax_status', + 'options' => [ + 'taxable' => __( 'Taxable', 'dokan-lite' ), + 'shipping' => __( 'Shipping only', 'dokan-lite' ), + 'none' => _x( 'None', 'Tax status', 'dokan-lite' ), + ], + ] + ); + + $section->add_field( + Elements::TAX_CLASS, [ + 'title' => __( 'Tax Class', 'dokan-lite' ), + 'type' => 'select', + 'name' => '_tax_class', + 'options' => wc_get_product_tax_class_options(), + ] + ); + } +} diff --git a/includes/ProductForm/Section.php b/includes/ProductForm/Section.php new file mode 100644 index 0000000000..ee306074f6 --- /dev/null +++ b/includes/ProductForm/Section.php @@ -0,0 +1,99 @@ +<?php + +namespace WeDevs\Dokan\ProductForm; + +defined( 'ABSPATH' ) || exit; + +/** + * Product Form Section + * + * @since DOKAN_SINCE + */ +class Section extends Component { + + /** + * Section constructor. + * + * @since DOKAN_SINCE + * + * @param string $id + * @param array $args + * + * @throws \Exception + * + * @return void + */ + public function __construct( string $id, array $args = [] ) { + $this->data['fields'] = []; + $this->data = wp_parse_args( $args, $this->data ); + + // set id from the args + $this->set_id( $id ); + + // call parent constructor + parent::__construct( $id, $args ); + + // check if the required attributes are exists + $missing_arguments = self::get_missing_arguments( $this->data ); + if ( count( $missing_arguments ) > 0 ) { + throw new \Exception( + sprintf( + /* translators: 1: Missing arguments list. */ + esc_html__( 'You are missing required arguments of Dokan ProductForm Field: %1$s', 'dokan-lite' ), + esc_attr( join( ', ', $missing_arguments ) ) + ) + ); + } + } + + /** + * Get fields of the section + * + * @since DOKAN_SINCE + * + * @return \WeDevs\Dokan\ProductForm\Field[] + */ + public function get_fields(): array { + // initialize fields + $this->init_fields(); + + return $this->data['fields']; + } + + /** + * Add field to the section + * + * @since DOKAN_SINCE + * + * @param string $id + * @param array $args + * + * @return void + */ + public function add_field( string $id, array $args ) { + // set section id manually + $args['section'] = $this->get_id(); + + // todo: handle error + Factory::add_field( $id, $args ); + } + + /** + * Initialize fields + * + * @since DOKAN_SINCE + * + * @return $this + */ + private function init_fields() { + $fields = Factory::instance()->get_fields(); + $this->data['fields'] = array_filter( + $fields, + function ( Field $field ) { + return $field->get_section() === $this->get_id(); + } + ); + + return $this; + } +} From af756c634e8ab2aee3bfddc21958ca9704ca77e7 Mon Sep 17 00:00:00 2001 From: Nurul Umbhiya <zikubd@gmail.com> Date: Thu, 7 Dec 2023 19:06:11 +0600 Subject: [PATCH 2/5] refactor: removed `Add New Product` page and modal related codebase and made Single Step Product Edit page as default refactor: replaced product add/edit page code with OOP fix: added Dokan Pro checks for `product_vendors_can_create_tags` and `product_category_style` admin settings --- assets/src/js/product-editor.js | 64 -- dokan.php | 3 + includes/Admin/Settings.php | 19 - includes/Ajax.php | 39 -- includes/Assets.php | 2 +- includes/Blocks/ProductBlock.php | 2 +- includes/CatalogMode/Dashboard/Products.php | 15 +- includes/Dashboard/Manager.php | 7 + includes/Dashboard/Templates/Manager.php | 20 +- includes/Dashboard/Templates/Products.php | 655 ++++++++---------- includes/Emails/NewProduct.php | 2 +- includes/Emails/NewProductPending.php | 2 +- includes/Product/Hooks.php | 14 +- includes/Product/Manager.php | 35 +- includes/Product/functions.php | 152 +--- includes/ProductCategory/Helper.php | 72 +- includes/ProductForm/Component.php | 14 +- includes/ProductForm/Elements.php | 6 +- includes/ProductForm/Factory.php | 22 +- includes/ProductForm/Field.php | 164 ++++- includes/ProductForm/Init.php | 429 ++++++++---- includes/ProductForm/Section.php | 25 +- includes/REST/Manager.php | 2 +- includes/Rewrites.php | 2 +- includes/functions.php | 9 +- includes/woo-views/html-product-download.php | 39 +- templates/products/add-new-product-modal.php | 3 - templates/products/download-virtual.php | 13 - templates/products/downloadable.php | 71 -- templates/products/edit-product-single.php | 538 -------------- .../products/edit/edit-product-single.php | 213 ++++++ .../sections}/catalog-mode-content.php | 2 + .../edit/sections/download-virtual.php | 61 ++ .../products/edit/sections/downloadable.php | 150 ++++ templates/products/edit/sections/general.php | 383 ++++++++++ .../products/edit/sections/inventory.php | 229 ++++++ templates/products/edit/sections/others.php | 112 +++ templates/products/inventory.php | 96 --- templates/products/new-product.php | 367 ---------- templates/products/others.php | 59 -- templates/products/tmpl-add-product-popup.php | 145 ---- 41 files changed, 2134 insertions(+), 2123 deletions(-) delete mode 100644 templates/products/add-new-product-modal.php delete mode 100644 templates/products/download-virtual.php delete mode 100644 templates/products/downloadable.php delete mode 100755 templates/products/edit-product-single.php create mode 100755 templates/products/edit/edit-product-single.php rename templates/products/{ => edit/sections}/catalog-mode-content.php (99%) create mode 100644 templates/products/edit/sections/download-virtual.php create mode 100644 templates/products/edit/sections/downloadable.php create mode 100644 templates/products/edit/sections/general.php create mode 100644 templates/products/edit/sections/inventory.php create mode 100644 templates/products/edit/sections/others.php delete mode 100644 templates/products/inventory.php delete mode 100755 templates/products/new-product.php delete mode 100644 templates/products/others.php delete mode 100644 templates/products/tmpl-add-product-popup.php diff --git a/assets/src/js/product-editor.js b/assets/src/js/product-editor.js index dce29dc460..ebdd90acbf 100755 --- a/assets/src/js/product-editor.js +++ b/assets/src/js/product-editor.js @@ -66,7 +66,6 @@ $( '.dokan-product-attribute-wrapper ul.dokan-attribute-option-list' ).on( 'click', 'button.dokan-add-new-attribute', this.attribute.addNewExtraAttr ); $( '.product-edit-container .dokan-product-attribute-wrapper' ).on( 'click', 'a.dokan-product-remove-attribute', this.attribute.removeAttribute ); $( '.product-edit-container .dokan-product-attribute-wrapper' ).on( 'click', 'a.dokan-save-attribute', this.attribute.saveAttribute ); - $( 'body' ).on( 'click', '.product-container-footer input[type="submit"]', this.createNewProduct ); this.attribute.disbalePredefinedAttribute(); @@ -340,69 +339,6 @@ $( 'body' ).trigger( 'dokan-product-editor-popup-opened', Dokan_Editor ); }, - createNewProduct: function (e) { - e.preventDefault(); - - var self = $(this), - form = self.closest('form#dokan-add-new-product-form'), - btn_id = self.attr('data-btn_id'); - - form.find( 'span.dokan-show-add-product-success' ).html(''); - form.find( 'span.dokan-show-add-product-error' ).html(''); - form.find( 'span.dokan-add-new-product-spinner' ).css( 'display', 'inline-block' ); - - self.attr( 'disabled', 'disabled' ); - - if ( form.find( 'input[name="post_title"]' ).val() == '' ) { - $( 'span.dokan-show-add-product-error' ).html( dokan.product_title_required ); - self.removeAttr( 'disabled' ); - form.find( 'span.dokan-add-new-product-spinner' ).css( 'display', 'none' ); - return; - } - - if ( form.find( 'select[name="product_cat"]' ).val() == '-1' ) { - $( 'span.dokan-show-add-product-error' ).html( dokan.product_category_required ); - self.removeAttr( 'disabled' ); - form.find( 'span.dokan-add-new-product-spinner' ).css( 'display', 'none' ); - return; - } - - var data = { - action: 'dokan_create_new_product', - postdata: form.serialize(), - _wpnonce : dokan.nonce - }; - - Dokan_Editor.modal.iziModal('startLoading'); - $.post( dokan.ajaxurl, data, function( resp ) { - if ( resp.success ) { - self.removeAttr( 'disabled' ); - if ( btn_id === 'create_new' ) { - $( '#dokan-add-product-popup' ).iziModal('close'); - window.location.href = resp.data; - } else { - product_featured_frame = undefined; - $('.dokan-dashboard-product-listing-wrapper').load( window.location.href + ' table.product-listing-table' ); - Dokan_Editor.modal.iziModal('resetContent'); - Dokan_Editor.openProductPopup(); - Dokan_Editor.reRenderPopupElements(); - $( 'span.dokan-show-add-product-success' ).html( dokan.product_created_response ); - - setTimeout(function() { - $( 'span.dokan-show-add-product-success' ).html( '' ); - }, 3000); - } - } else { - self.removeAttr( 'disabled' ); - $( 'span.dokan-show-add-product-error' ).html( resp.data ); - } - form.find( 'span.dokan-add-new-product-spinner' ).css( 'display', 'none' ); - }) - .always( function () { - Dokan_Editor.modal.iziModal('stopLoading'); - }); - }, - attribute: { toggleAttribute: function(e) { diff --git a/dokan.php b/dokan.php index 503d542a6a..c47fe6f109 100755 --- a/dokan.php +++ b/dokan.php @@ -55,6 +55,7 @@ * @property WeDevs\Dokan\Vendor\Manager $vendor Instance of Vendor Manager Class * @property WeDevs\Dokan\BackgroundProcess\Manager $bg_process Instance of WeDevs\Dokan\BackgroundProcess\Manager class * @property WeDevs\Dokan\Frontend\Frontend $frontend_manager Instance of \WeDevs\Dokan\Frontend\Frontend class + * @property WeDevs\Dokan\Dashboard\Manager $dashboard Instance of \WeDevs\Dokan\Dashboard\Manager class */ final class WeDevs_Dokan { @@ -256,6 +257,7 @@ public function define_constants() { $this->define( 'DOKAN_FILE', __FILE__ ); $this->define( 'DOKAN_DIR', __DIR__ ); $this->define( 'DOKAN_INC_DIR', __DIR__ . '/includes' ); + $this->define( 'DOKAN_TEMPLATE_DIR', __DIR__ . '/templates' ); $this->define( 'DOKAN_LIB_DIR', __DIR__ . '/lib' ); $this->define( 'DOKAN_PLUGIN_ASSEST', plugins_url( 'assets', __FILE__ ) ); @@ -373,6 +375,7 @@ public function init_classes() { new \WeDevs\Dokan\Vendor\UserSwitch(); new \WeDevs\Dokan\CacheInvalidate(); new \WeDevs\Dokan\Shipping\Hooks(); + new \WeDevs\Dokan\ProductForm\Init(); if ( is_admin() ) { new \WeDevs\Dokan\Admin\Hooks(); diff --git a/includes/Admin/Settings.php b/includes/Admin/Settings.php index bf19079aea..bc6e37afee 100644 --- a/includes/Admin/Settings.php +++ b/includes/Admin/Settings.php @@ -557,25 +557,6 @@ public function get_settings_fields() { 'default' => 'on', 'tooltip' => __( 'If checked, vendors will have permission to sell immediately after registration. If unchecked, newly registered vendors cannot add products until selling capability is activated manually from admin dashboard.', 'dokan-lite' ), ], - 'one_step_product_create' => [ - 'name' => 'one_step_product_create', - 'label' => __( 'One Page Product Creation', 'dokan-lite' ), - 'desc' => __( 'Add new product in single page view', 'dokan-lite' ), - 'type' => 'switcher', - 'default' => 'on', - 'tooltip' => __( 'If disabled, instead of a single add product page it will open a pop up window or vendor will redirect to product page when adding new product.', 'dokan-lite' ), - ], - 'disable_product_popup' => [ - 'name' => 'disable_product_popup', - 'label' => __( 'Disable Product Popup', 'dokan-lite' ), - 'desc' => __( 'Disable add new product in popup view', 'dokan-lite' ), - 'type' => 'switcher', - 'default' => 'off', - 'show_if' => [ - 'dokan_selling.one_step_product_create' => [ 'equal' => 'off' ], - ], - 'tooltip' => __( 'If disabled, instead of a pop up window vendor will redirect to product page when adding new product.', 'dokan-lite' ), - ], 'order_status_change' => [ 'name' => 'order_status_change', 'label' => __( 'Order Status Change', 'dokan-lite' ), diff --git a/includes/Ajax.php b/includes/Ajax.php index 7b983a18f7..ed3a17607a 100755 --- a/includes/Ajax.php +++ b/includes/Ajax.php @@ -41,8 +41,6 @@ public function __construct() { add_action( 'wp_ajax_dokan_seller_listing_search', [ $this, 'seller_listing_search' ] ); add_action( 'wp_ajax_nopriv_dokan_seller_listing_search', [ $this, 'seller_listing_search' ] ); - add_action( 'wp_ajax_dokan_create_new_product', [ $this, 'create_product' ] ); - add_action( 'wp_ajax_custom-header-crop', [ $this, 'crop_store_banner' ] ); add_action( 'wp_ajax_dokan_json_search_products_tags', [ $this, 'dokan_json_search_products_tags' ] ); @@ -57,43 +55,6 @@ public function __construct() { add_action( 'wp_ajax_dokan-upgrade-dissmiss', [ $this, 'dismiss_pro_notice' ] ); } - /** - * Create product from popup submission - * - * @since 2.5.0 - * - * @return void - */ - public function create_product() { - check_ajax_referer( 'dokan_reviews' ); - - if ( ! current_user_can( 'dokan_add_product' ) ) { - wp_send_json_error( __( 'You have no permission to do this action', 'dokan-lite' ) ); - } - - $submited_data = isset( $_POST['postdata'] ) ? wp_unslash( $_POST['postdata'] ) : ''; //phpcs:ignore - - parse_str( $submited_data, $postdata ); - - $response = dokan_save_product( $postdata ); - - if ( is_wp_error( $response ) ) { - wp_send_json_error( $response->get_error_message() ); - } - - if ( is_int( $response ) ) { - if ( current_user_can( 'dokan_edit_product' ) ) { - $redirect = dokan_edit_product_url( $response ); - } else { - $redirect = dokan_get_navigation_url( 'products' ); - } - - wp_send_json_success( $redirect ); - } else { - wp_send_json_error( __( 'Something wrong, please try again later', 'dokan-lite' ) ); - } - } - /** * Check the availability of shop name. * diff --git a/includes/Assets.php b/includes/Assets.php index db566c4305..afaa082cdd 100644 --- a/includes/Assets.php +++ b/includes/Assets.php @@ -514,7 +514,7 @@ public function get_scripts() { ], 'dokan-util-helper' => [ 'src' => $asset_url . '/js/helper.js', - 'deps' => [ 'jquery', 'dokan-sweetalert2', 'moment' ], + 'deps' => [ 'jquery', 'dokan-sweetalert2', 'moment', 'jquery-tiptip' ], 'version' => filemtime( $asset_path . 'js/helper.js' ), 'in_footer' => false, ], diff --git a/includes/Blocks/ProductBlock.php b/includes/Blocks/ProductBlock.php index 000624ce54..826703f139 100644 --- a/includes/Blocks/ProductBlock.php +++ b/includes/Blocks/ProductBlock.php @@ -17,7 +17,7 @@ class ProductBlock { * @return array */ public function get_configurations() { - $can_create_tags = dokan_get_option( 'product_vendors_can_create_tags', 'dokan_selling' ); + $can_create_tags = dokan()->is_pro_exists() ? dokan_get_option( 'product_vendors_can_create_tags', 'dokan_selling', 'off' ) : 'off'; return apply_filters( 'dokan_get_product_block_configurations', diff --git a/includes/CatalogMode/Dashboard/Products.php b/includes/CatalogMode/Dashboard/Products.php index d5f4afaffe..fd6aec805d 100644 --- a/includes/CatalogMode/Dashboard/Products.php +++ b/includes/CatalogMode/Dashboard/Products.php @@ -27,7 +27,7 @@ public function __construct() { return; } // render catalog mode section under single product edit page - add_action( 'dokan_product_edit_after_options', [ $this, 'render_product_section' ], 99, 1 ); + add_action( 'dokan_product_edit_after_options', [ $this, 'render_product_section' ], 99, 2 ); // save catalog mode section data add_action( 'dokan_product_updated', [ $this, 'save_catalog_mode_data' ], 13 ); } @@ -38,29 +38,28 @@ public function __construct() { * @since 3.6.4 * * @param $product_id int + * @param $product \WC_Product * * @return void */ - public function render_product_section( $product_id ) { + public function render_product_section( $product_id, $product ) { // check permission, don't let vendor staff view this section if ( ! current_user_can( 'dokandar' ) ) { return; } - // get product data - $product = wc_get_product( $product_id ); - // return if product type is optional - if ( ! $product || 'auction' === $product->get_type() ) { + // return if product type is auction + if ( 'auction' === $product->get_type() ) { return; } $defaults = Helper::get_defaults(); // check for saved values - $catalog_mode_data = get_post_meta( $product_id, '_dokan_catalog_mode', true ); + $catalog_mode_data = $product->get_meta( '_dokan_catalog_mode', true ); //load template dokan_get_template_part( - 'products/catalog-mode-content', '', [ + 'products/edit/sections/catalog-mode-content', '', [ 'product_id' => $product_id, 'saved_data' => $catalog_mode_data ? $catalog_mode_data : $defaults, ] diff --git a/includes/Dashboard/Manager.php b/includes/Dashboard/Manager.php index d15f96ce14..b99340dca1 100644 --- a/includes/Dashboard/Manager.php +++ b/includes/Dashboard/Manager.php @@ -5,6 +5,13 @@ use WeDevs\Dokan\Dashboard\Templates\Manager as TemplateManager; use WeDevs\Dokan\Traits\ChainableContainer; +/** + * Dashboard Manager + * + * @since 3.0.0 + * + * @property TemplateManager $templates Instance of TemplateManager class + */ class Manager { use ChainableContainer; diff --git a/includes/Dashboard/Templates/Manager.php b/includes/Dashboard/Templates/Manager.php index ca75447fa7..ae1e5c8cf3 100644 --- a/includes/Dashboard/Templates/Manager.php +++ b/includes/Dashboard/Templates/Manager.php @@ -2,15 +2,21 @@ namespace WeDevs\Dokan\Dashboard\Templates; -use WeDevs\Dokan\Dashboard\Templates\Dashboard; -use WeDevs\Dokan\Dashboard\Templates\Main; -use WeDevs\Dokan\Dashboard\Templates\Orders; -use WeDevs\Dokan\Dashboard\Templates\Products; -use WeDevs\Dokan\Dashboard\Templates\Settings; -use WeDevs\Dokan\Dashboard\Templates\Withdraw; -use WeDevs\Dokan\Dashboard\Templates\MultiStepCategories; use WeDevs\Dokan\Traits\ChainableContainer; +/** + * Dashboard Template Manager + * + * @since 3.0.0 + * + * @property Dashboard $dashboard Instance of Dashboard class + * @property Orders $orders Instance of Orders class + * @property Products $products Instance of Dashboard class + * @property Settings $settings Instance of Settings class + * @property Withdraw $withdraw Instance of Withdraw class + * @property MultiStepCategories $product_category Instance of MultiStepCategories class + * @property ReverseWithdrawal $reverse_withdrawal Instance of ReverseWithdrawal class + */ class Manager { use ChainableContainer; diff --git a/includes/Dashboard/Templates/Products.php b/includes/Dashboard/Templates/Products.php index 7128a1092f..437a7f23c2 100644 --- a/includes/Dashboard/Templates/Products.php +++ b/includes/Dashboard/Templates/Products.php @@ -2,7 +2,12 @@ namespace WeDevs\Dokan\Dashboard\Templates; -use WeDevs\Dokan\ProductCategory\Helper; +use WC_Product; +use WC_Product_Factory; +use WC_Product_Simple; +use WeDevs\Dokan\ProductForm\Elements as ProductFormElements; +use WeDevs\Dokan\ProductForm\Factory as ProductFormFactory; +use WP_Post; /** * Product Functionality for Product Handler @@ -13,8 +18,31 @@ */ class Products { - public static $errors; + /** + * Errors + * + * @since 3.0.0 + * + * @var array + */ + public static $errors = []; + + /** + * Product Category + * + * @since 3.0.0 + * + * @var array + */ public static $product_cat; + + /** + * Post Content + * + * @since 3.0.0 + * + * @var array + */ public static $post_content; /** @@ -26,19 +54,20 @@ class Products { * @uses filters */ public function __construct() { + // render product listing template add_action( 'dokan_render_product_listing_template', [ $this, 'render_product_listing_template' ], 11 ); - add_action( 'template_redirect', [ $this, 'handle_product_add' ], 11 ); - add_action( 'template_redirect', [ $this, 'handle_product_update' ], 11 ); - add_action( 'template_redirect', [ $this, 'handle_delete_product' ] ); - add_action( 'dokan_render_new_product_template', [ $this, 'render_new_product_template' ], 10 ); + + // render product edit page template sections add_action( 'dokan_render_product_edit_template', [ $this, 'load_product_edit_template' ], 11 ); - add_action( 'template_redirect', [ $this, 'render_product_edit_page_for_email' ], 1 ); - add_action( 'dokan_after_listing_product', [ $this, 'load_add_new_product_popup' ], 10 ); - add_action( 'dokan_after_listing_product', [ $this, 'load_add_new_product_modal' ], 10 ); add_action( 'dokan_product_edit_after_title', [ __CLASS__, 'load_download_virtual_template' ], 10, 2 ); add_action( 'dokan_product_edit_after_main', [ __CLASS__, 'load_inventory_template' ], 5, 2 ); add_action( 'dokan_product_edit_after_main', [ __CLASS__, 'load_downloadable_template' ], 10, 2 ); add_action( 'dokan_product_edit_after_inventory_variants', [ __CLASS__, 'load_others_template' ], 85, 2 ); + + // handle product edit/delete operations + add_action( 'template_redirect', [ $this, 'render_product_edit_page_for_email' ], 1 ); + add_action( 'template_redirect', [ $this, 'handle_delete_product' ] ); + add_action( 'template_redirect', [ $this, 'handle_product_update' ], 11 ); } /** @@ -46,7 +75,7 @@ public function __construct() { * * @since 3.0.0 * - * @param void $errors + * @param array $errors * * @return void */ @@ -76,56 +105,145 @@ public function get_errors() { return self::$errors; } + /** + * Load Product Edit Template + * + * @since 2.4 + * + * @return void + */ + public function load_product_edit_template() { + // check for permission + if ( ! current_user_can( 'dokan_edit_product' ) ) { + dokan_get_template_part( + 'global/dokan-error', '', [ + 'deleted' => false, + 'message' => __( 'You have no permission to view this page', 'dokan-lite' ), + ] + ); + + return; + } + + // check if seller is enabled for selling + if ( ! dokan_is_seller_enabled( dokan_get_current_user_id() ) ) { + dokan_seller_not_enabled_notice(); + + return; + } + + // check for valid nonce and product id + if ( ! isset( $_GET['_dokan_edit_product_nonce'], $_GET['product_id'] ) || ! wp_verify_nonce( sanitize_key( $_GET['_dokan_edit_product_nonce'] ), 'dokan_edit_product_nonce' ) ) { + dokan_get_template_part( + 'global/dokan-error', '', [ + 'deleted' => false, + 'message' => __( 'Are you cheating?', 'dokan-lite' ), + ] + ); + + return; + } + + // check if this is a product add or edit page + $product_id = intval( wp_unslash( $_GET['product_id'] ) ); + $new_product = false; + if ( ! $product_id ) { + // this is a product add page request + // now create a new product with auto draft status + $product = new WC_Product_Simple(); + $product->set_status( 'auto-draft' ); + $product->save(); + $new_product = true; + } else { + // this is a product edit page request + $product = wc_get_product( $product_id ); + } + + if ( ! dokan_is_product_author( $product->get_id() ) ) { + dokan_get_template_part( + 'global/dokan-error', '', [ + 'deleted' => false, + 'message' => __( 'Access Denied. Given product is not yours.', 'dokan-lite' ), + ] + ); + + return; + } + + dokan_get_template_part( + 'products/edit/edit-product-single', '', + [ + 'product' => $product, + 'new_product' => $new_product, + 'from_shortcode' => true, + ] + ); + } + /** * Load product * * @since 1.0.0 * + * @param WP_Post $post + * @param int $post_id + * * @return void */ public static function load_download_virtual_template( $post, $post_id ) { - $_downloadable = get_post_meta( $post_id, '_downloadable', true ); - $_virtual = get_post_meta( $post_id, '_virtual', true ); - $is_downloadable = 'yes' === $_downloadable; - $is_virtual = 'yes' === $_virtual; - $digital_mode = dokan_get_option( 'global_digital_mode', 'dokan_general', 'sell_both' ); + $product = wc_get_product( $post_id ); + if ( ! $product ) { + return; + } + $digital_mode = dokan_get_option( 'global_digital_mode', 'dokan_general', 'sell_both' ); if ( 'sell_physical' === $digital_mode ) { return; } + // get section data + $section = ProductFormFactory::get_section( 'downloadable_virtual' ); + if ( is_wp_error( $section ) || ! $section->is_visible() ) { + return; + } + dokan_get_template_part( - 'products/download-virtual', '', [ - 'post_id' => $post_id, - 'post' => $post, - 'is_downloadable' => $is_downloadable, - 'is_virtual' => $is_virtual, - 'digital_mode' => $digital_mode, - 'class' => 'show_if_subscription hide_if_variable-subscription show_if_simple', + 'products/edit/sections/download-virtual', '', [ + 'product' => $product, + 'section' => $section, + 'digital_mode' => $digital_mode, + 'class' => 'show_if_subscription hide_if_variable-subscription show_if_simple', ] ); } /** - * Load invendor template + * Load inventory template * * @since 2.9.2 * + * @param WP_Post $post + * @param int $post_id + * * @return void */ public static function load_inventory_template( $post, $post_id ) { - $_sold_individually = get_post_meta( $post_id, '_sold_individually', true ); - $_stock = get_post_meta( $post_id, '_stock', true ); - $_low_stock_amount = get_post_meta( $post_id, '_low_stock_amount', true ); + $product = wc_get_product( $post_id ); + if ( ! $product ) { + return; + } + + // get section data + $section = ProductFormFactory::get_section( 'inventory' ); + if ( is_wp_error( $section ) || ! $section->is_visible() ) { + return; + } dokan_get_template_part( - 'products/inventory', '', [ - 'post_id' => $post_id, - 'post' => $post, - '_sold_individually' => $_sold_individually, - '_stock' => $_stock, - '_low_stock_amount' => $_low_stock_amount, - 'class' => '', + 'products/edit/sections/inventory', '', [ + 'section' => $section, + 'product' => $product, + 'class' => '', ] ); } @@ -135,19 +253,32 @@ public static function load_inventory_template( $post, $post_id ) { * * @since 2.9.2 * + * @param WP_Post $post + * @param int $post_id + * * @return void */ public static function load_downloadable_template( $post, $post_id ) { - $digital_mode = dokan_get_option( 'global_digital_mode', 'dokan_general', 'sell_both' ); + $product = wc_get_product( $post_id ); + if ( ! $product ) { + return; + } + $digital_mode = dokan_get_option( 'global_digital_mode', 'dokan_general', 'sell_both' ); if ( 'sell_physical' === $digital_mode ) { return; } + // get section data + $section = ProductFormFactory::get_section( 'downloadable' ); + if ( is_wp_error( $section ) || ! $section->is_visible() ) { + return; + } + dokan_get_template_part( - 'products/downloadable', '', [ - 'post_id' => $post_id, - 'post' => $post, + 'products/edit/sections/downloadable', '', [ + 'section' => $section, + 'product' => $product, 'class' => 'show_if_downloadable', ] ); @@ -158,64 +289,36 @@ public static function load_downloadable_template( $post, $post_id ) { * * @since 2.9.2 * + * @param WP_Post $post + * @param int $post_id + * * @return void */ public static function load_others_template( $post, $post_id ) { - $product = wc_get_product( $post_id ); - $_visibility = $product->get_catalog_visibility(); - $visibility_options = dokan_get_product_visibility_options(); + $product = wc_get_product( $post_id ); + if ( ! $product ) { + return; + } + + $section = ProductFormFactory::get_section( 'others' ); + if ( is_wp_error( $section ) || ! $section->is_visible() ) { + return; + } // set new post status $post_status = dokan_get_default_product_status( dokan_get_current_user_id() ); $post_status = $product->get_status() === 'auto-draft' ? $post_status : $product->get_status(); dokan_get_template_part( - 'products/others', '', [ - 'post_id' => $post_id, - 'post' => $post, - 'post_status' => apply_filters( 'dokan_post_edit_default_status', $post_status, $product ), - '_visibility' => $_visibility, - 'visibility_options' => $visibility_options, - 'class' => '', + 'products/edit/sections/others', '', [ + 'section' => $section, + 'product' => $product, + 'post_status' => apply_filters( 'dokan_post_edit_default_status', $post_status, $product ), + 'class' => '', ] ); } - /** - * Render New Product Template for only free version - * - * @since 2.4 - * - * @param array $query_vars - * - * @return void - */ - public function render_new_product_template( $query_vars ) { - if ( isset( $query_vars['new-product'] ) && ! dokan()->is_pro_exists() ) { - dokan_get_template_part( 'products/new-product' ); - } - } - - /** - * Load Product Edit Template - * - * @since 2.4 - * - * @return void - */ - public function load_product_edit_template() { - if ( current_user_can( 'dokan_edit_product' ) ) { - dokan_get_template_part( 'products/edit-product-single' ); - } else { - dokan_get_template_part( - 'global/dokan-error', '', [ - 'deleted' => false, - 'message' => __( 'You have no permission to view this page', 'dokan-lite' ), - ] - ); - } - } - /** * Render Product Edit Page for Email. * @@ -243,11 +346,9 @@ public function render_product_edit_page_for_email() { * * @since 2.4 * - * @param string $action - * * @return void */ - public function render_product_listing_template( $action ) { + public function render_product_listing_template() { $bulk_statuses = apply_filters( 'dokan_bulk_product_statuses', [ '-1' => __( 'Bulk Actions', 'dokan-lite' ), @@ -259,11 +360,13 @@ public function render_product_listing_template( $action ) { } /** - * Handle product add + * Handle product update + * + * @var $product WC_Product * * @return void */ - public function handle_product_add() { + public function handle_product_update() { if ( ! is_user_logged_in() ) { return; } @@ -272,320 +375,157 @@ public function handle_product_add() { return; } - if ( ! isset( $_POST['dokan_add_new_product_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['dokan_add_new_product_nonce'] ), 'dokan_add_new_product' ) ) { + if ( ! isset( $_POST['dokan_edit_product_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['dokan_edit_product_nonce'] ), 'dokan_edit_product' ) ) { return; } - $postdata = wp_unslash( $_POST ); // phpcs:ignore - - $errors = []; - self::$product_cat = - 1; - self::$post_content = __( 'Details of your product ...', 'dokan-lite' ); - - if ( isset( $postdata['add_product'] ) ) { - $post_title = sanitize_text_field( $postdata['post_title'] ); - $post_content = wp_kses_post( $postdata['post_content'] ); - $post_excerpt = wp_kses_post( $postdata['post_excerpt'] ); - $featured_image = absint( sanitize_text_field( $postdata['feat_image_id'] ) ); - - if ( empty( $post_title ) ) { - $errors[] = __( 'Please enter product title', 'dokan-lite' ); - } - - if ( ! isset( $postdata['chosen_product_cat'] ) ) { - if ( Helper::product_category_selection_is_single() ) { - if ( absint( $postdata['product_cat'] ) < 0 ) { - $errors[] = __( 'Please select a category', 'dokan-lite' ); - } - } else { - if ( ! isset( $postdata['product_cat'] ) || empty( $postdata['product_cat'] ) ) { - $errors[] = __( 'Please select at least one category', 'dokan-lite' ); - } - } - } elseif ( empty( $postdata['chosen_product_cat'] ) ) { - $errors[] = __( 'Please select a category', 'dokan-lite' ); - } - - self::$errors = apply_filters( 'dokan_can_add_product', $errors ); - - if ( ! self::$errors ) { - $timenow = dokan_current_datetime()->setTimezone( new \DateTimeZone( 'UTC' ) ); - $product_status = dokan_get_default_product_status( dokan_get_current_user_id() ); - $post_data = apply_filters( - 'dokan_insert_product_post_data', [ - 'post_type' => 'product', - 'post_status' => $product_status, - 'post_title' => $post_title, - 'post_content' => $post_content, - 'post_excerpt' => $post_excerpt, - 'post_date_gmt' => $timenow->format( 'Y-m-d H:i:s' ), - 'post_modified_gmt' => $timenow->format( 'Y-m-d H:i:s' ), - ] - ); - - $product_id = wp_insert_post( $post_data ); - - if ( $product_id ) { - // set images - if ( $featured_image ) { - set_post_thumbnail( $product_id, $featured_image ); - } - - if ( isset( $postdata['product_tag'] ) && ! empty( $postdata['product_tag'] ) ) { - $tags_ids = array_map( 'absint', (array) $postdata['product_tag'] ); - wp_set_object_terms( $product_id, $tags_ids, 'product_tag' ); - } - - /* set product category */ - if ( ! isset( $postdata['chosen_product_cat'] ) ) { - if ( Helper::product_category_selection_is_single() ) { - wp_set_object_terms( $product_id, (int) $postdata['product_cat'], 'product_cat' ); - } else { - if ( isset( $postdata['product_cat'] ) && ! empty( $postdata['product_cat'] ) ) { - $cat_ids = array_map( 'absint', (array) $postdata['product_cat'] ); - wp_set_object_terms( $product_id, $cat_ids, 'product_cat' ); - } - } - } else { - $chosen_cat = Helper::product_category_selection_is_single() ? [ reset( $postdata['chosen_product_cat'] ) ] : $postdata['chosen_product_cat']; - Helper::set_object_terms_from_chosen_categories( $product_id, $chosen_cat ); - } - - /** Set Product type, default is simple */ - $product_type = empty( $postdata['product_type'] ) ? 'simple' : $postdata['product_type']; - wp_set_object_terms( $product_id, $product_type, 'product_type' ); - - // Gallery Images - if ( ! empty( $postdata['product_image_gallery'] ) ) { - $attachment_ids = array_filter( explode( ',', wc_clean( $postdata['product_image_gallery'] ) ) ); - update_post_meta( $product_id, '_product_image_gallery', implode( ',', $attachment_ids ) ); - } - - if ( isset( $postdata['_regular_price'] ) ) { - update_post_meta( $product_id, '_regular_price', ( $postdata['_regular_price'] === '' ) ? '' : wc_format_decimal( $postdata['_regular_price'] ) ); - } - - if ( isset( $postdata['_sale_price'] ) ) { - update_post_meta( $product_id, '_sale_price', ( $postdata['_sale_price'] === '' ? '' : wc_format_decimal( $postdata['_sale_price'] ) ) ); - $date_from = isset( $postdata['_sale_price_dates_from'] ) ? wc_clean( $postdata['_sale_price_dates_from'] ) : ''; - $date_to = isset( $postdata['_sale_price_dates_to'] ) ? wc_clean( $postdata['_sale_price_dates_to'] ) : ''; - $now = dokan_current_datetime(); - - // Dates - if ( $date_from ) { - update_post_meta( $product_id, '_sale_price_dates_from', $now->modify( $date_from )->setTime( 0, 0, 0 )->getTimestamp() ); - } else { - update_post_meta( $product_id, '_sale_price_dates_from', '' ); - } - - if ( $date_to ) { - update_post_meta( $product_id, '_sale_price_dates_to', $now->modify( $date_to )->setTime( 23, 59, 59 )->getTimestamp() ); - } else { - update_post_meta( $product_id, '_sale_price_dates_to', '' ); - } - - if ( $date_to && ! $date_from ) { - update_post_meta( $product_id, '_sale_price_dates_from', $now->setTime( 0, 0, 0 )->getTimestamp() ); - } - - if ( '' !== $postdata['_sale_price'] && '' === $date_to && '' === $date_from ) { - update_post_meta( $product_id, '_price', wc_format_decimal( $postdata['_sale_price'] ) ); - } else { - update_post_meta( $product_id, '_price', ( $postdata['_regular_price'] === '' ) ? '' : wc_format_decimal( $postdata['_regular_price'] ) ); - } - // Update price if on sale - if ( '' !== $postdata['_sale_price'] && $date_from && $now->modify( $date_from )->getTimestamp() < $now->getTimestamp() ) { - update_post_meta( $product_id, '_price', wc_format_decimal( $postdata['_sale_price'] ) ); - } - } - - update_post_meta( $product_id, '_visibility', 'visible' ); - update_post_meta( $product_id, '_stock_status', 'instock' ); - - do_action( 'dokan_new_product_added', $product_id, $postdata ); - - if ( current_user_can( 'dokan_edit_product' ) ) { - $redirect = dokan_edit_product_url( $product_id ); - } else { - $redirect = dokan_get_navigation_url( 'products' ); - } - - if ( 'create_and_add_new' === $postdata['add_product'] ) { - $redirect = add_query_arg( - [ - 'created_product' => $product_id, - '_dokan_add_product_nonce' => wp_create_nonce( 'dokan_add_product_nonce' ), - ], - dokan_get_navigation_url( 'new-product' ) - ); - } - - wp_safe_redirect( apply_filters( 'dokan_add_new_product_redirect', $redirect, $product_id ) ); - exit; - } - } - } - } - - /** - * Handle product update - * - * @return void - */ - public function handle_product_update() { - if ( ! is_user_logged_in() ) { - return; - } + $product_id = isset( $_POST['dokan_product_id'] ) ? absint( $_POST['dokan_product_id'] ) : 0; + if ( ! $product_id ) { + self::$errors[] = __( 'No product id is set!', 'dokan-lite' ); - if ( ! dokan_is_user_seller( get_current_user_id() ) ) { return; } - if ( ! isset( $_POST['dokan_update_product'] ) ) { - return; - } + if ( ! dokan_is_product_author( $product_id ) ) { + self::$errors[] = __( 'I swear this is not your product!', 'dokan-lite' ); - if ( ! isset( $_POST['dokan_edit_product_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['dokan_edit_product_nonce'] ), 'dokan_edit_product' ) ) { return; } - $errors = []; - $post_title = isset( $_POST['post_title'] ) ? sanitize_text_field( wp_unslash( $_POST['post_title'] ) ) : ''; - $post_slug = ! empty( $_POST['editable-post-name'] ) ? sanitize_title( wp_unslash( $_POST['editable-post-name'] ) ) : ''; - $post_status = isset( $_POST['post_status'] ) ? sanitize_text_field( wp_unslash( $_POST['post_status'] ) ) : ''; - $post_content = isset( $_POST['post_content'] ) ? wp_kses_post( wp_unslash( $_POST['post_content'] ) ) : ''; - $post_excerpt = isset( $_POST['post_excerpt'] ) ? wp_kses_post( wp_unslash( $_POST['post_excerpt'] ) ) : ''; + // Process the product type first so that we have the correct class to run setters. + $product_type_field = ProductFormFactory::get_field( ProductFormElements::TYPE ); + $product_type = empty( $_POST[ $product_type_field->get_name() ] ) ? WC_Product_Factory::get_product_type( $product_id ) : sanitize_title( wp_unslash( $_POST[ $product_type_field->get_name() ] ) ); + $classname = WC_Product_Factory::get_product_classname( $product_id, $product_type ? $product_type : 'simple' ); + $product = new $classname( $product_id ); - if ( empty( $post_title ) ) { - $errors[] = __( 'Please enter product title', 'dokan-lite' ); - } + /** + * @var WC_Product $product + */ + if ( ! $product ) { + self::$errors[] = __( 'No product found with given product id!', 'dokan-lite' ); - $post_id = isset( $_POST['dokan_product_id'] ) ? absint( $_POST['dokan_product_id'] ) : 0; - if ( ! $post_id ) { - $errors[] = __( 'No product found!', 'dokan-lite' ); + return; } - $current_post_status = get_post_status( $post_id ); + $props = []; + $meta_data = []; + $current_post_status = $product->get_status(); $is_new_product = 'auto-draft' === $current_post_status; - if ( empty( $post_status ) ) { - $post_status = $current_post_status; - } - - if ( ! dokan_is_product_author( $post_id ) ) { - $errors[] = __( 'I swear this is not your product!', 'dokan-lite' ); - } + foreach ( ProductFormFactory::get_fields() as $field_id => $field ) { + if ( $field->is_other_type() ) { + // we will only process props and meta in this block + continue; + } + // check if the field is visible + if ( ! $field->is_visible() ) { + continue; + } - if ( empty( $_POST['chosen_product_cat'] ) ) { - $errors[] = __( 'Please select a category', 'dokan-lite' ); - } + // check if field is required + if ( $field->is_required() && empty( $_POST[ $field->get_name() ] ) ) { + self::$errors[ $field->get_id() ][] = ! empty( $field->get_error_message() ) + ? $field->get_error_message() + // translators: 1) field title + : sprintf( __( '%1$s is a required field.', 'dokan-lite' ), $field->get_title() ); + continue; + } - if ( isset( $_POST['product_tag'] ) ) { - /** - * Filter of maximun a vendor can add tags. - * - * @since 3.3.7 - * - * @param integer default -1 - */ - $maximum_tags_select_length = apply_filters( 'dokan_product_tags_select_max_length', - 1 ); + // check if the field is present + if ( ! isset( $_POST[ $field->get_name() ] ) ) { + continue; + } - // Setting limitation for how many product tags that vendor can input. - $tag_count = count( (array) $_POST['product_tag'] ); - if ( $maximum_tags_select_length !== - 1 && $tag_count > $maximum_tags_select_length ) { - // translators: %s: maximum tag length - $errors[] = sprintf( __( 'You can only select %s tags', 'dokan-lite' ), number_format_i18n( $maximum_tags_select_length ) ); + // validate field data + switch ( $field->get_id() ) { + case ProductFormElements::CATEGORIES: + $field_value = $field->sanitize( (array) wp_unslash( $_POST['chosen_product_cat'] ), $product ); // phpcs:ignore + break; + + case ProductFormElements::DOWNLOADS: + $file_names = isset( $_POST['_wc_file_names'] ) ? wp_unslash( $_POST['_wc_file_names'] ) : []; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $file_urls = isset( $_POST['_wc_file_urls'] ) ? wp_unslash( $_POST['_wc_file_urls'] ) : []; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $file_hashes = isset( $_POST['_wc_file_hashes'] ) ? wp_unslash( $_POST['_wc_file_hashes'] ) : []; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $field_value = $field->sanitize( $file_names, $file_urls, $file_hashes ); //phpcs:ignore + break; + + case ProductFormElements::STOCK_QUANTITY: + $original_stock = isset( $_POST['_original_stock'] ) ? wc_stock_amount( wp_unslash( $_POST['_original_stock'] ) ) : false; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $field_value = $field->sanitize( wp_unslash( $_POST[ $field->get_name() ] ), $original_stock, $product ); //phpcs:ignore + break; + + default: + // give a chance to other plugins to sanitize their data + $field_value = apply_filters( 'dokan_product_update_field_value', null, $field, wp_unslash( $_POST[ $field->get_name() ] ), $product ); // phpcs:ignore + if ( null === $field_value ) { + $field_value = $field->sanitize( wp_unslash( $_POST[ $field->get_name() ] ) ); //phpcs:ignore + } + break; } - } - self::$errors = apply_filters( 'dokan_can_edit_product', $errors ); + if ( is_wp_error( $field_value ) ) { + self::$errors[ $field->get_id() ] = $field_value->get_error_message(); + } else { + //store field value + $field->set_value( $field_value ); + } - if ( self::$errors ) { - return; + // store props and meta data + if ( $field->is_prop() ) { + $props[ $field->get_id() ] = $field_value; + } elseif ( $field->is_meta() ) { + $meta_data[ $field->get_id() ] = $field_value; + } } - $product_info = apply_filters( - 'dokan_update_product_post_data', [ - 'ID' => $post_id, - 'post_title' => $post_title, - 'post_content' => $post_content, - 'post_excerpt' => $post_excerpt, - 'post_status' => $post_status, - 'comment_status' => isset( $_POST['_enable_reviews'] ) ? 'open' : 'closed', - ] - ); - - if ( $post_slug ) { - $product_info['post_name'] = wp_unique_post_slug( $post_slug, $post_id, $post_status, 'product', 0 ); + // check for valid product tag + if ( ! empty( $_POST['editable-post-name'] ) ) { + $post_slug = ProductFormFactory::get_field( ProductFormElements::SLUG ); + $post_status = $props[ ProductFormElements::STATUS ] ?? dokan_get_default_product_status( dokan_get_current_user_id() ); + $props[ $post_slug->get_name() ] = $post_slug->sanitize( wp_unslash( $_POST['editable-post-name'] ), $product_id, $post_status ); //phpcs:ignore } - wp_update_post( $product_info ); + // apply hooks before saving product + $props = apply_filters( 'dokan_product_edit_props', $props, $product ); + $meta_data = apply_filters( 'dokan_product_edit_meta_data', $meta_data, $product ); - /** Set Product tags */ - if ( isset( $_POST['product_tag'] ) ) { - $tags_ids = array_map( 'absint', (array) wp_unslash( $_POST['product_tag'] ) ); - } else { - $tags_ids = []; + $errors = $product->set_props( $props ); + if ( is_wp_error( $errors ) ) { + self::$errors = array_merge( self::$errors, $errors->get_error_messages() ); + exit; } - wp_set_object_terms( $post_id, $tags_ids, 'product_tag' ); - - /* set product category */ - $chosen_product_categories = array_map( 'absint', (array) wp_unslash( $_POST['chosen_product_cat'] ) ); - $chosen_cat = Helper::product_category_selection_is_single() ? [ reset( $chosen_product_categories ) ] : $chosen_product_categories; - Helper::set_object_terms_from_chosen_categories( $post_id, $chosen_cat ); - - //set prodcuct type default is simple - $product_type = empty( $_POST['product_type'] ) ? 'simple' : sanitize_text_field( wp_unslash( $_POST['product_type'] ) ); - wp_set_object_terms( $post_id, $product_type, 'product_type' ); - - // set images - $featured_image = isset( $_POST['feat_image_id'] ) ? absint( wp_unslash( $_POST['feat_image_id'] ) ) : 0; - if ( $featured_image ) { - set_post_thumbnail( $post_id, $featured_image ); - } else { - delete_post_thumbnail( $post_id ); + // return early for validation errors + self::$errors = apply_filters( 'dokan_can_edit_product', self::$errors ); + if ( ! empty( self::$errors ) ) { + return; } - $postdata = wp_unslash( $_POST ); // phpcs:ignore - /** Process all variation products meta */ - dokan_process_product_meta( $post_id, $postdata ); - if ( $is_new_product ) { - do_action( 'dokan_new_product_added', $post_id, $postdata ); + do_action( 'dokan_before_new_product_added', $product, $props, $meta_data ); } else { - do_action( 'dokan_product_updated', $post_id, $postdata ); + do_action( 'dokan_before_product_updated', $product, $props, $meta_data ); } - $redirect = apply_filters( 'dokan_add_new_product_redirect', dokan_edit_product_url( $post_id ), $post_id ); + // add product meta data + foreach ( $meta_data as $meta_key => $meta_value ) { + $product->add_meta_data( $meta_key, $meta_value, true ); + } - // if any error inside dokan_process_product_meta function - global $woocommerce_errors; + // save product + $product->save(); - if ( $woocommerce_errors ) { - wp_safe_redirect( add_query_arg( [ 'errors' => array_map( 'urlencode', $woocommerce_errors ) ], $redirect ) ); - exit; + if ( $is_new_product ) { + do_action( 'dokan_new_product_added', $product_id, [] ); + } else { + do_action( 'dokan_product_updated', $product_id, [] ); } + $redirect = apply_filters( 'dokan_add_new_product_redirect', dokan_edit_product_url( $product_id ), $product_id ); wp_safe_redirect( add_query_arg( [ 'message' => 'success' ], $redirect ) ); exit; } - public function load_add_new_product_popup() { - dokan_get_template_part( 'products/tmpl-add-product-popup' ); - } - - /** - * Add new product open modal html - * - * @since 3.7.0 - * - * @return void - */ - public function load_add_new_product_modal() { - dokan_get_template_part( 'products/add-new-product-modal' ); - } - /** * Handle delete product link * @@ -630,5 +570,4 @@ public function handle_delete_product() { wp_safe_redirect( add_query_arg( [ 'message' => 'product_deleted' ], $redirect ) ); exit; } - } diff --git a/includes/Emails/NewProduct.php b/includes/Emails/NewProduct.php index 51b29c9b1d..6490f21a05 100644 --- a/includes/Emails/NewProduct.php +++ b/includes/Emails/NewProduct.php @@ -80,7 +80,7 @@ public function trigger( $product_id ) { return; } - $product->update_meta_data( '_dokan_new_product_email_sent', 'yes' ); + $product->add_meta_data( '_dokan_new_product_email_sent', 'yes', true ); $product->save(); if ( ! $this->is_enabled() || ! $this->get_recipient() ) { diff --git a/includes/Emails/NewProductPending.php b/includes/Emails/NewProductPending.php index cb86194dc6..707b09807f 100644 --- a/includes/Emails/NewProductPending.php +++ b/includes/Emails/NewProductPending.php @@ -81,7 +81,7 @@ public function trigger( $product_id ) { return; } - $product->update_meta_data( '_dokan_new_product_email_sent', 'yes' ); + $product->add_meta_data( '_dokan_new_product_email_sent', 'yes', true ); $product->save(); if ( ! $this->is_enabled() || ! $this->get_recipient() ) { diff --git a/includes/Product/Hooks.php b/includes/Product/Hooks.php index a2c9cb99a2..96e74e0275 100644 --- a/includes/Product/Hooks.php +++ b/includes/Product/Hooks.php @@ -362,16 +362,18 @@ public function set_product_status( $all_statuses, int $product_id ) { * * @since 3.8.2 * - * @param int|WC_Product $product_id + * @param int $product_id * * @return void */ public function set_new_product_email_status( $product_id ) { - if ( is_a( $product_id, 'WC_Product' ) ) { - $product_id->update_meta_data( '_dokan_new_product_email_sent', 'no' ); - } else { - update_post_meta( $product_id, '_dokan_new_product_email_sent', 'no' ); + $product = wc_get_product( $product_id ); + if ( ! $product ) { + return; } + + $product->add_meta_data( '_dokan_new_product_email_sent', 'no', true ); + $product->save(); } /** @@ -409,7 +411,7 @@ public function own_product_not_purchasable_notice() { wc_print_notice( __( 'As this is your own product, the "Add to Cart" button has been removed. Please visit as a guest to view it.', 'dokan-lite' ), 'notice' ); } - + /** * Filter the recipients of the product review notification. * diff --git a/includes/Product/Manager.php b/includes/Product/Manager.php index cdade3e327..623acf4344 100644 --- a/includes/Product/Manager.php +++ b/includes/Product/Manager.php @@ -404,9 +404,9 @@ public function create( $args = [] ) { //call dokan hooks if ( ! $is_update ) { - do_action( 'dokan_new_product_added', $product_id, $product ); + do_action( 'dokan_new_product_added', $product_id, [] ); } else { - do_action( 'dokan_product_updated', $product_id, $product ); + do_action( 'dokan_product_updated', $product_id, [] ); } return $this->get( $product_id ); @@ -706,6 +706,37 @@ protected function maybe_update_stock_status( $product, $stock_status ) { return $product; } + /** + * Prepare downloads for save. + * + * @since DOKAN_SINCE + * + * @param array $file_names File names. + * @param array $file_urls File urls. + * @param array $file_hashes File hashes. + * + * @return array + */ + public function prepare_downloads( $file_names, $file_urls, $file_hashes ) { + $downloads = []; + + if ( ! empty( $file_urls ) ) { + $file_url_size = count( $file_urls ); + + for ( $i = 0; $i < $file_url_size; $i++ ) { + if ( ! empty( $file_urls[ $i ] ) ) { + $downloads[] = [ + 'name' => wc_clean( $file_names[ $i ] ), + 'file' => wp_unslash( trim( $file_urls[ $i ] ) ), + 'download_id' => wc_clean( $file_hashes[ $i ] ), + ]; + } + } + } + + return $downloads; + } + /** * Get featured products * diff --git a/includes/Product/functions.php b/includes/Product/functions.php index 8b2f44bdba..d45533dda8 100644 --- a/includes/Product/functions.php +++ b/includes/Product/functions.php @@ -14,154 +14,18 @@ * * @param array $args * - * @return int|bool|WP_Error + * @deprecated DOKAN_SINCE + * + * @return \WP_Error|\WC_Product */ function dokan_save_product( $args ) { - $defaults = [ - 'post_title' => '', - 'post_content' => '', - 'post_excerpt' => '', - 'post_status' => '', - 'post_type' => 'product', - 'product_tag' => [], - '_visibility' => 'visible', - ]; - - $data = wp_parse_args( $args, $defaults ); - - if ( empty( $data['post_title'] ) ) { - return new WP_Error( 'no-title', __( 'Please enter product title', 'dokan-lite' ) ); - } - - if ( ! isset( $data['chosen_product_cat'] ) ) { - if ( Helper::product_category_selection_is_single() ) { - if ( absint( $data['product_cat'] ) < 0 ) { - return new WP_Error( 'no-category', __( 'Please select a category', 'dokan-lite' ) ); - } - } else { - if ( ! isset( $data['product_cat'] ) && empty( $data['product_cat'] ) ) { - return new WP_Error( 'no-category', __( 'Please select at least one category', 'dokan-lite' ) ); - } - } - } elseif ( empty( $data['chosen_product_cat'] ) ) { - return new WP_Error( 'no-category', __( 'Please select a category', 'dokan-lite' ) ); - } - - $error = apply_filters( 'dokan_new_product_popup_args', '', $data ); - - if ( is_wp_error( $error ) ) { - return $error; - } - - $post_status = ! empty( $data['post_status'] ) ? sanitize_text_field( $data['post_status'] ) : dokan_get_default_product_status(); - - $post_arr = [ - 'post_type' => 'product', - 'post_status' => $post_status, - 'post_title' => sanitize_text_field( $data['post_title'] ), - 'post_content' => wp_kses_post( $data['post_content'] ), - 'post_excerpt' => wp_kses_post( $data['post_excerpt'] ), - ]; - - if ( ! empty( $data['ID'] ) ) { - $post_arr['ID'] = absint( $data['ID'] ); - - if ( ! dokan_is_product_author( $post_arr['ID'] ) ) { - return new WP_Error( 'not-own', __( 'Sorry, You can not modify another vendor\'s product !', 'dokan-lite' ) ); - } - - $is_updating = true; - } else { - $is_updating = false; - } - - $post_arr = apply_filters( 'dokan_insert_product_post_data', $post_arr, $data ); - - $post_data = [ - 'id' => $is_updating ? $post_arr['ID'] : '', - 'name' => $post_arr['post_title'], - 'type' => ! empty( $data['product_type'] ) ? $data['product_type'] : 'simple', - 'description' => $post_arr['post_content'], - 'short_description' => $post_arr['post_excerpt'], - 'status' => $post_status, - ]; - - if ( ! isset( $data['chosen_product_cat'] ) ) { - if ( Helper::product_category_selection_is_single() ) { - $cat_ids[] = $data['product_cat']; - } else { - if ( ! empty( $data['product_cat'] ) ) { - $cat_ids = array_map( 'absint', (array) $data['product_cat'] ); - } - } - $post_data['categories'] = $cat_ids; - } - - if ( isset( $data['feat_image_id'] ) ) { - $post_data['featured_image_id'] = ! empty( $data['feat_image_id'] ) ? absint( $data['feat_image_id'] ) : ''; - } + wc_deprecated_function( __FUNCTION__, 'DOKAN_SINCE', 'dokan()->product->create()' ); - if ( isset( $data['product_image_gallery'] ) ) { - $post_data['gallery_image_ids'] = ! empty( $data['product_image_gallery'] ) ? array_filter( explode( ',', wc_clean( $data['product_image_gallery'] ) ) ) : []; + try { + return dokan()->product->create( $args ); + } catch ( Exception $e ) { + return new WP_Error( $e->getCode(), $e->getMessage() ); } - - if ( isset( $data['product_tag'] ) ) { - /** - * Filter of maximun a vendor can add tags. - * - * @since 3.3.7 - * - * @param integer default -1 - */ - $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( $data['product_tag'] ) !== 0 && count( $data['product_tag'] ) > $maximum_tags_select_length ) { - /* translators: %s: maximum tag length */ - return new WP_Error( 'tags-limit', sprintf( __( 'You can only select %s tags', 'dokan-lite' ), number_format_i18n( $maximum_tags_select_length ) ) ); - } - - $post_data['tags'] = array_map( 'absint', (array) $data['product_tag'] ); - } - - if ( isset( $data['_regular_price'] ) ) { - $post_data['regular_price'] = $data['_regular_price'] === '' ? '' : wc_format_decimal( $data['_regular_price'] ); - } - - if ( isset( $data['_sale_price'] ) ) { - $post_data['sale_price'] = wc_format_decimal( $data['_sale_price'] ); - } - - if ( isset( $data['_sale_price_dates_from'] ) ) { - $post_data['date_on_sale_from'] = wc_clean( $data['_sale_price_dates_from'] ); - } - - if ( isset( $data['_sale_price_dates_to'] ) ) { - $post_data['date_on_sale_to'] = wc_clean( $data['_sale_price_dates_to'] ); - } - - if ( isset( $data['_visibility'] ) && array_key_exists( $data['_visibility'], dokan_get_product_visibility_options() ) ) { - $post_data['catalog_visibility'] = sanitize_text_field( $data['_visibility'] ); - } - - $product = dokan()->product->create( $post_data ); - - if ( $product ) { - $chosen_cat = Helper::product_category_selection_is_single() ? [ reset( $data['chosen_product_cat'] ) ] : $data['chosen_product_cat']; - Helper::set_object_terms_from_chosen_categories( $product->get_id(), $chosen_cat ); - } - - if ( ! $is_updating ) { - do_action( 'dokan_new_product_added', $product->get_id(), $data ); - } else { - do_action( 'dokan_product_updated', $product->get_id(), $data ); - } - - if ( $product ) { - return $product->get_id(); - } - - return false; } /** diff --git a/includes/ProductCategory/Helper.php b/includes/ProductCategory/Helper.php index b8bdfd671e..2d40c6d827 100644 --- a/includes/ProductCategory/Helper.php +++ b/includes/ProductCategory/Helper.php @@ -19,6 +19,10 @@ class Helper { * @return boolean */ public static function product_category_selection_is_single() { + if ( ! dokan()->is_pro_exists() ) { + return 'single'; + } + return 'single' === dokan_get_option( 'product_category_style', 'dokan_selling', 'single' ); } @@ -150,6 +154,44 @@ public static function generate_chosen_categories( $terms ) { return $chosen_cats; } + /** + * Get all ancestors of chosen categories. + * + * @since 3.6.2 + * + * @param \WC_Product|int $product + * @param array $chosen_categories + * + * @return array + */ + public static function get_object_terms_from_chosen_categories( $product, $chosen_categories = [] ) { + if ( is_numeric( $product ) ) { + $product = wc_get_product( $product ); + } + + if ( ! $product ) { + return []; + } + + if ( empty( $chosen_categories ) || ! is_array( $chosen_categories ) ) { + // get chosen categories from product meta + $chosen_categories = self::get_product_chosen_category( $product->get_id() ); + } + + $all_ancestors = []; + // If category middle selection is true, then we will save only the chosen categories or we will save all the ancestors. + if ( self::is_any_category_selection_enabled() ) { + $all_ancestors = $chosen_categories; + } else { + // we need to assign all ancestor of chosen category to add to the given product + foreach ( $chosen_categories as $term_id ) { + $all_ancestors = array_merge( $all_ancestors, get_ancestors( $term_id, 'product_cat' ), [ (int) $term_id ] ); + } + } + + return array_map( 'absint', array_unique( $all_ancestors ) ); + } + /** * Set all ancestors to a product from chosen product categories * @@ -165,22 +207,7 @@ public static function set_object_terms_from_chosen_categories( $post_id, $chose return; } - /** - * If enabled any one middle category in dokan product multi-step category selection. - */ - $any_category_selection = self::is_any_category_selection_enabled(); - - $all_ancestors = []; - - // If category middle selection is true, then we will save only the chosen categories or we will save all the ancestors. - if ( $any_category_selection ) { - $all_ancestors = $chosen_categories; - } else { - // we need to assign all ancestor of chosen category to add to the given product - foreach ( $chosen_categories as $term_id ) { - $all_ancestors = array_merge( $all_ancestors, get_ancestors( $term_id, 'product_cat' ), [ $term_id ] ); - } - } + $all_ancestors = self::get_object_terms_from_chosen_categories( $post_id, $chosen_categories ); // save chosen cat to database update_post_meta( $post_id, 'chosen_product_cat', $chosen_categories ); @@ -254,12 +281,19 @@ public static function enqueue_and_localize_dokan_multistep_category() { * * @since 3.7.0 * - * @param int $product_id + * @param \WC_Product|int $product * * @return array */ - public static function get_product_chosen_category( $product_id ) { - return get_post_meta( $product_id, 'chosen_product_cat', true ); + public static function get_product_chosen_category( $product ) { + if ( is_numeric( $product ) ) { + $product = wc_get_product( $product ); + } + if ( ! $product ) { + return []; + } + + return (array) $product->get_meta( 'chosen_product_cat', true ); } /** diff --git a/includes/ProductForm/Component.php b/includes/ProductForm/Component.php index 1ad7f6d1ae..fece579c2a 100644 --- a/includes/ProductForm/Component.php +++ b/includes/ProductForm/Component.php @@ -37,9 +37,9 @@ abstract class Component { ]; public function __construct( string $id, array $args = [] ) { - foreach ( $this->data as $key => $value ) { - if ( method_exists( $this, "set_{$key}" ) ) { - $this->{"set_{$key}"}( $args[ $key ] ); + foreach ( $args as $key => $value ) { + if ( method_exists( $this, "set_{$key}" ) && null !== $value ) { + $this->{"set_{$key}"}( $value ); } } } @@ -90,12 +90,12 @@ public function get_title(): string { * * @param string $title * - * @see sanitize_text_field() + * @see wp_kses_post() * * @return $this */ public function set_title( string $title ): self { - $this->data['title'] = sanitize_text_field( $title ); + $this->data['title'] = wp_kses_post( $title ); return $this; } @@ -317,10 +317,10 @@ public function get_missing_arguments( $args ) { function ( $arg_key ) use ( $args ) { // return false if not exists or empty if ( ! array_key_exists( $arg_key, $args ) || empty( $args[ $arg_key ] ) ) { - return false; + return true; } - return true; + return false; } ) ); diff --git a/includes/ProductForm/Elements.php b/includes/ProductForm/Elements.php index 5c6f73c8f1..fc1b7189df 100644 --- a/includes/ProductForm/Elements.php +++ b/includes/ProductForm/Elements.php @@ -58,8 +58,8 @@ class Elements { const INVENTORY_DELTA = 'inventory_delta'; const UPSELL_IDS = 'upsell_ids'; const CROSS_SELL_IDS = 'cross_sell_ids'; - const CATEGORIES = 'categories'; - const TAGS = 'tags'; + const CATEGORIES = 'category_ids'; + const TAGS = 'tag_ids'; const DOWNLOADABLE = 'downloadable'; const DOWNLOADS = 'downloads'; const DOWNLOAD_LIMIT = 'download_limit'; @@ -67,7 +67,7 @@ class Elements { const EXTERNAL_URL = 'product_url'; const BUTTON_TEXT = 'button_text'; const GROUP_PRODUCTS = 'children'; - const FEATURED_IMAGE_ID = 'featured_image_id'; + const FEATURED_IMAGE_ID = 'image_id'; const GALLERY_IMAGE_IDS = 'gallery_image_ids'; const META_DATA = 'meta_data'; const DISABLE_SHIPPING_META = '_disable_shipping'; diff --git a/includes/ProductForm/Factory.php b/includes/ProductForm/Factory.php index 336c6c2495..5f9fcf0f1b 100644 --- a/includes/ProductForm/Factory.php +++ b/includes/ProductForm/Factory.php @@ -54,6 +54,10 @@ class Factory { * @return Field|WP_Error New field or WP_Error. */ public static function add_field( $id, $args ) { + if ( empty( $args['id'] ) ) { + $args['id'] = $id; + } + $new_field = self::create_item( 'field', 'Field', $id, $args ); if ( is_wp_error( $new_field ) ) { return $new_field; @@ -79,6 +83,10 @@ public static function add_field( $id, $args ) { * @return Section|WP_Error New section or WP_Error. */ public static function add_section( $id, $args ) { + if ( empty( $args['id'] ) ) { + $args['id'] = $id; + } + $new_section = self::create_item( 'section', 'Section', $id, $args ); if ( is_wp_error( $new_section ) ) { return $new_section; @@ -96,9 +104,9 @@ public static function add_section( $id, $args ) { /** * Returns list of registered fields. * - * @return array list of registered fields. + * @return Field[] list of registered fields. */ - public static function get_fields() { + public static function get_fields(): array { return self::$form_fields; } @@ -154,21 +162,20 @@ public static function get_section( string $id ) { */ private static function get_items( string $type, string $class_name, string $sort_by = 'asc' ) { $item_list = self::get_item_list( $type ); - $items = array_values( $item_list ); if ( class_exists( $class_name ) && method_exists( $class_name, 'sort' ) ) { /** - * @var \WeDevs\Dokan\ProductForm\Component $class_name + * @var Component $class_name */ - usort( - $items, + uasort( + $item_list, function ( $a, $b ) use ( $sort_by, $class_name ) { return $class_name::sort( $a, $b, $sort_by ); } ); } - return $items; + return $item_list; } /** @@ -202,6 +209,7 @@ private static function get_item_list( string $type ): array { * @return Field|Section|WP_Error New product form item or WP_Error. */ private static function create_item( string $type, string $class_name, string $id, array $args ) { + $class_name = __NAMESPACE__ . '\\' . $class_name; if ( ! class_exists( $class_name ) ) { return new WP_Error( 'dokan_product_form_' . $type . '_missing_form_class', diff --git a/includes/ProductForm/Field.php b/includes/ProductForm/Field.php index f1230e0ba5..d3f65b2452 100644 --- a/includes/ProductForm/Field.php +++ b/includes/ProductForm/Field.php @@ -22,6 +22,7 @@ class Field extends Component { 'id', 'title', 'section', + 'type', ]; /** @@ -34,14 +35,17 @@ class Field extends Component { * @throws \Exception */ public function __construct( string $id, array $args = [] ) { - $data = [ - 'name' => '', // html name attribute of the field, if not exists id value will be used as name - 'property' => '', // if exists, this will be the name of the field - 'section' => '', // section id, required - 'field_type' => '', // html field type - 'placeholder' => '', // html placeholder attribute value for the field - 'options' => '', // if the field is select, radio, checkbox, etc + $data = [ + 'name' => '', // html name attribute of the field, if not exists id value will be used as name + 'value' => '', // html value attribute of the field + 'property' => '', // if exists, this will be the name of the field + 'section' => '', // section id, required + 'type' => 'prop', // field type, accept value can be 'prop', 'meta' or 'other' + 'field_type' => '', // html field type + 'placeholder' => '', // html placeholder attribute value for the field + 'options' => [], // if the field is select, radio, checkbox, etc 'additional_properties' => [], // additional arguments for the field + 'sanitize_callback' => '', // callback function for sanitizing the field value ]; $this->data = array_merge( $this->data, $data ); @@ -56,7 +60,7 @@ public function __construct( string $id, array $args = [] ) { if ( count( $missing_arguments ) > 0 ) { throw new \Exception( sprintf( - /* translators: 1: Missing arguments list. */ + /* translators: 1: Missing arguments list. */ esc_html__( 'You are missing required arguments of Dokan ProductForm Field: %1$s', 'dokan-lite' ), esc_attr( join( ', ', $missing_arguments ) ) ) @@ -76,11 +80,13 @@ public function get_name(): string { } /** - * Set field name, validated by @since DOKAN_SINCE + * Set field name, validated by sanitize_title() + * + * @since DOKAN_SINCE * * @param string $name * - * @see sanitize_title() + * @see sanitize_title() * * @return $this */ @@ -90,6 +96,32 @@ public function set_name( string $name ): Field { return $this; } + /** + * Get field value + * + * @since DOKAN_SINCE + * + * @return mixed + */ + public function get_value() { + return $this->data['value']; + } + + /** + * Set field value + * + * @since DOKAN_SINCE + * + * @param mixed $value + * + * @return $this + */ + public function set_value( $value ): Field { + $this->data['value'] = $value; + + return $this; + } + /** * Get field property * @@ -102,7 +134,7 @@ public function get_property(): string { } /** - * Set field property, validated by + * Set field property, validated by sanitize_key() * * @since DOKAN_SINCE * @@ -144,6 +176,65 @@ public function set_section( string $section ): Field { return $this; } + /** + * Get type of the current field + * + * @since DOKAN_SINCE + * + * @return string + */ + public function get_type(): string { + return $this->data['type']; + } + + /** + * Return if the type is a prop + * + * @since DOKAN_SINCE + * + * @return bool + */ + public function is_prop(): bool { + return 'prop' === $this->data['type']; + } + + /** + * Return if the type is a meta_data + * + * @since DOKAN_SINCE + * + * @return bool + */ + public function is_meta(): bool { + return 'meta' === $this->data['type']; + } + + /** + * Return if the type is a date_prop + * + * @since DOKAN_SINCE + * + * @return bool + */ + public function is_other_type(): bool { + return 'other' === $this->data['type']; + } + + /** + * Set field type + * + * @since DOKAN_SINCE + * + * @param string $field_type + * + * @return $this + */ + public function set_type( string $type ): Field { + $this->data['type'] = in_array( $type, [ 'prop', 'meta', 'other' ], true ) ? sanitize_key( $type ) : 'other'; + + return $this; + } + /** * Get the field type * @@ -183,7 +274,7 @@ public function get_placeholder(): string { } /** - * Set field placeholder, validated with + * Set field placeholder, validated with wp_kses_post() * * @since DOKAN_SINCE * @@ -261,4 +352,53 @@ public function set_additional_properties( $property, $value = null ): Field { return $this; } + + /** + * Set field sanitize callback + * + * @since DOKAN_SINCE + * + * @param callable|string $callback + * + * @return void + */ + public function set_sanitize_callback( $callback ): Field { + $this->data['sanitize_callback'] = $callback; + + return $this; + } + + /** + * Get field sanitize callback + * + * @since DOKAN_SINCE + * + * @return callable|string + */ + public function get_sanitize_callback() { + return $this->data['sanitize_callback']; + } + + /** + * Get field sanitize callback + * + * @since DOKAN_SINCE + * + * @param mixed $value + * + * @return mixed|\WP_Error + */ + public function sanitize( ...$value ) { + $callback = $this->get_sanitize_callback(); + if ( ! empty( $callback ) && is_callable( $callback ) ) { + return call_user_func( $callback, ...$value ); + } + + $value = current( $value ); + if ( is_string( $value ) ) { + return wc_clean( $value ); + } + + return $value; + } } diff --git a/includes/ProductForm/Init.php b/includes/ProductForm/Init.php index 4df60fb6c3..6c3c977bc9 100644 --- a/includes/ProductForm/Init.php +++ b/includes/ProductForm/Init.php @@ -2,6 +2,9 @@ namespace WeDevs\Dokan\ProductForm; +use WC_Product; +use WeDevs\Dokan\ProductCategory\Helper as ProductCategoryHelper; + defined( 'ABSPATH' ) || exit; /** @@ -19,7 +22,7 @@ class Init { * @return void */ public function __construct() { - add_action( 'init', [ $this, 'init_form_fields' ], 1 ); + add_action( 'init', [ $this, 'init_form_fields' ], 5 ); } /** @@ -32,6 +35,7 @@ public function __construct() { public function init_form_fields() { $this->init_general_fields(); $this->init_inventory_fields(); + $this->init_downloadable_virtual_fields(); $this->init_downloadable_fields(); $this->init_other_fields(); } @@ -53,21 +57,39 @@ public function init_general_fields() { ); $section->add_field( - Elements::ID, + Elements::NAME, [ 'title' => __( 'Title', 'dokan-lite' ), - 'type' => 'text', + 'field_type' => 'text', 'name' => 'post_title', 'placeholder' => __( 'Enter product title...', 'dokan-lite' ), + 'required' => true, 'error_msg' => __( 'Please enter product title!', 'dokan-lite' ), ] ); + $section->add_field( + Elements::SLUG, + [ + 'title' => __( 'Slug', 'dokan-lite' ), + 'field_type' => 'text', + 'type' => 'other', + 'name' => 'slug', + 'placeholder' => __( 'Enter product slug...', 'dokan-lite' ), + 'required' => false, + 'error_msg' => __( 'Please enter product slug!', 'dokan-lite' ), + 'sanitize_callback' => function ( $slug, $product_id, $status, $post_parent = 0 ) { + return wp_unique_post_slug( $slug, $product_id, $status, 'product', $post_parent ); + }, + ] + ); + $section->add_field( Elements::TYPE, [ 'title' => __( 'Product Type', 'dokan-lite' ), - 'type' => 'select', - 'name' => 'product_type', + 'field_type' => 'select', + 'type' => 'other', + 'name' => 'product-type', 'options' => apply_filters( 'dokan_product_types', [ @@ -81,7 +103,7 @@ public function init_general_fields() { $section->add_field( Elements::REGULAR_PRICE, [ 'title' => __( 'Price', 'dokan-lite' ), - 'type' => 'text', + 'field_type' => 'text', 'name' => '_regular_price', 'placeholder' => '0.00', ] @@ -90,7 +112,7 @@ public function init_general_fields() { $section->add_field( Elements::SALE_PRICE, [ 'title' => __( 'Discounted Price', 'dokan-lite' ), - 'type' => 'text', + 'field_type' => 'text', 'name' => '_sale_price', 'placeholder' => '0.00', ] @@ -99,47 +121,88 @@ public function init_general_fields() { $section->add_field( Elements::DATE_ON_SALE_FROM, [ 'title' => __( 'From', 'dokan-lite' ), - 'type' => 'text', + 'field_type' => 'text', 'name' => '_sale_price_dates_from', - 'placeholder' => wc_date_format(), + 'placeholder' => 'YYYY-MM-DD', ] ); $section->add_field( Elements::DATE_ON_SALE_TO, [ 'title' => __( 'To', 'dokan-lite' ), - 'type' => 'text', + 'field_type' => 'text', 'name' => '_sale_price_dates_to', - 'placeholder' => wc_date_format(), + 'placeholder' => 'YYYY-MM-DD', ] ); + $category_error_message = ProductCategoryHelper::product_category_selection_is_single() + ? __( 'Please select at least one category!', 'dokan-lite' ) + : __( 'Please select a category', 'dokan-lite' ); + $section->add_field( Elements::CATEGORIES, [ 'title' => __( 'Categories', 'dokan-lite' ), - 'type' => 'select', - 'name' => 'product_cat[]', + 'field_type' => 'select', + 'name' => 'chosen_product_cat[]', 'placeholder' => __( 'Select product categories', 'dokan-lite' ), 'options' => [], + 'required' => true, + 'error_msg' => $category_error_message, + 'sanitize_callback' => function ( array $categories, WC_Product $product ) { + $chosen_product_categories = array_map( 'absint', $categories ); + $chosen_cat = ProductCategoryHelper::product_category_selection_is_single() ? [ reset( $chosen_product_categories ) ] : $chosen_product_categories; + + // store product category in meta data + $product->add_meta_data( 'chosen_product_cat', $chosen_cat, true ); + + return array_map( 'absint', ProductCategoryHelper::get_object_terms_from_chosen_categories( $product, $chosen_cat ) ); + }, ] ); - $can_create_tags = dokan_get_option( 'product_vendors_can_create_tags', 'dokan_selling' ); + $can_create_tags = dokan()->is_pro_exists() ? dokan_get_option( 'product_vendors_can_create_tags', 'dokan_selling', 'off' ) : 'off'; $tags_placeholder = 'on' === $can_create_tags ? __( 'Select tags/Add tags', 'dokan-lite' ) : __( 'Select product tags', 'dokan-lite' ); $section->add_field( Elements::TAGS, [ - 'title' => __( 'Tags', 'dokan-lite' ), - 'type' => 'select', - 'name' => 'product_tag[]', - 'placeholder' => $tags_placeholder, + 'title' => __( 'Tags', 'dokan-lite' ), + 'field_type' => 'select', + 'name' => 'product_tag[]', + 'placeholder' => $tags_placeholder, + 'sanitize_callback' => function ( $tags ) { + /** + * Maximum number of tags a vendor can add. + * + * @since 3.3.7 + * + * @args int -1 default to unlimited + */ + $maximum_tags_select_length = apply_filters( 'dokan_product_tags_select_max_length', -1 ); + + if ( $maximum_tags_select_length !== -1 && count( $tags ) > $maximum_tags_select_length ) { + // translators: %s: maximum tag length + return sprintf( __( 'You can only select %s tags', 'dokan-lite' ), number_format_i18n( $maximum_tags_select_length ) ); + } + + // todo: add support for creating tags + return array_map( + function ( $tag ) { + if ( is_numeric( $tag ) ) { + return absint( $tag ); + } else { + return sanitize_text_field( $tag ); + } + }, (array) $tags + ); + }, ] ); $section->add_field( Elements::FEATURED_IMAGE_ID, [ 'title' => __( 'Product Image', 'dokan-lite' ), - 'type' => 'image', - 'name' => 'feat_image_id', + 'field_type' => 'image', + 'name' => 'image_id', 'placeholder' => __( 'Select product image', 'dokan-lite' ), ] ); @@ -147,8 +210,8 @@ public function init_general_fields() { $section->add_field( Elements::GALLERY_IMAGE_IDS, [ 'title' => __( 'Product Gallery', 'dokan-lite' ), - 'type' => 'gallery', - 'name' => 'product_image_gallery', + 'field_type' => 'gallery', + 'name' => 'gallery_image_ids', 'placeholder' => __( 'Select product gallery images', 'dokan-lite' ), ] ); @@ -156,21 +219,67 @@ public function init_general_fields() { $section->add_field( Elements::SHORT_DESCRIPTION, [ 'title' => __( 'Short Description', 'dokan-lite' ), - 'type' => 'textarea', + 'field_type' => 'textarea', 'name' => 'post_excerpt', 'placeholder' => __( 'Enter product short description', 'dokan-lite' ), - 'visibility' => false, ] ); $section->add_field( Elements::DESCRIPTION, [ 'title' => __( 'Description', 'dokan-lite' ), - 'type' => 'textarea', + 'field_type' => 'textarea', 'name' => 'post_content', 'placeholder' => __( 'Enter product description', 'dokan-lite' ), 'required' => true, - 'visibility' => false, + ] + ); + } + + /** + * Init downloadable and virtual fields + * + * @since DOKAN_SINCE + * + * @return void + */ + public function init_downloadable_virtual_fields() { + $section = Factory::add_section( + 'downloadable_virtual', + [ + 'title' => __( 'Downloadable', 'dokan-lite' ), + 'description' => __( 'Downloadable products give access to a file upon purchase.', 'dokan-lite' ), + 'order' => 20, + ] + ); + + $section->add_field( + Elements::DOWNLOADABLE, [ + 'title' => __( 'Downloadable', 'dokan-lite' ), + 'description' => __( 'Virtual products are intangible and are not shipped.', 'dokan-lite' ), + 'field_type' => 'checkbox', + 'name' => '_downloadable', + 'additional_properties' => [ + 'value' => 'yes', + ], + 'sanitize_callback' => function ( $value ) { + return ! empty( $value ) && 'yes' === $value; + }, + ] + ); + + $section->add_field( + Elements::VIRTUAL, [ + 'title' => __( 'Virtual', 'dokan-lite' ), + 'description' => __( 'Downloadable products give access to a file upon purchase.', 'dokan-lite' ), + 'field_type' => 'checkbox', + 'name' => '_virtual', + 'additional_properties' => [ + 'value' => 'yes', + ], + 'sanitize_callback' => function ( $value ) { + return ! empty( $value ) && 'yes' === $value; + }, ] ); } @@ -187,75 +296,123 @@ public function init_inventory_fields() { 'inventory', [ 'title' => __( 'Inventory', 'dokan-lite' ), - 'description' => __( 'Manage your product stock', 'dokan-lite' ), + 'description' => __( 'Manage inventory for this product', 'dokan-lite' ), 'order' => 20, ] ); $section->add_field( Elements::SKU, [ - 'title' => __( 'SKU', 'dokan-lite' ), - 'type' => 'text', - 'name' => '_sku', + 'title' => '<abbr title="' . esc_attr__( 'Stock Keeping Unit', 'dokan-lite' ) . '">' . esc_html__( 'SKU', 'dokan-lite' ) . '</abbr>', + 'description' => __( 'SKU refers to a Stock-keeping unit, a unique identifier for each distinct product and service that can be purchased.', 'dokan-lite' ), 'placeholder' => __( 'Enter product SKU', 'dokan-lite' ), + 'field_type' => 'text', + 'name' => '_sku', ] ); $section->add_field( Elements::STOCK_STATUS, [ - 'title' => __( 'Stock Status', 'dokan-lite' ), - 'type' => 'select', - 'name' => '_stock_status', - 'options' => wc_get_product_stock_status_options(), + 'title' => __( 'Stock Status', 'dokan-lite' ), + 'description' => __( 'Controls whether or not the product is listed as "in stock" or "out of stock" on the frontend.', 'dokan-lite' ), + 'field_type' => 'select', + 'name' => '_stock_status', + 'options' => wc_get_product_stock_status_options(), ] ); $section->add_field( Elements::MANAGE_STOCK, [ - 'title' => __( 'Enable product stock management', 'dokan-lite' ), - 'type' => 'checkbox', - 'name' => '_manage_stock', - 'value' => 'yes', + 'title' => __( 'Enable product stock management', 'dokan-lite' ), + 'description' => __( 'Manage stock level (quantity)', 'dokan-lite' ), + 'field_type' => 'checkbox', + 'name' => '_manage_stock', + 'additional_properties' => [ + 'value' => 'yes', + ], + 'sanitize_callback' => function ( $value ) { + return ! empty( $value ) && 'yes' === $value; + }, ] ); $section->add_field( Elements::STOCK_QUANTITY, [ - 'title' => __( 'Stock quantity', 'dokan-lite' ), - 'type' => 'number', - 'name' => '_stock', - 'placeholder' => '1', - 'min' => 0, - 'step' => 1, + 'title' => __( 'Stock quantity', 'dokan-lite' ), + 'description' => __( 'Stock quantity. If this is a variable product this value will be used to control stock for all variations, unless you define stock at variation level.', 'dokan-lite' ), + 'field_type' => 'number', + 'name' => '_stock', + 'placeholder' => '1', + 'additional_properties' => [ + 'min' => 0, + 'step' => 'any', + ], + 'sanitize_callback' => function ( $value, $original_stock = false, $product = null ) { + if ( + false !== $original_stock + && $product instanceof WC_Product + && wc_stock_amount( $product->get_stock_quantity( 'edit' ) ) !== wc_stock_amount( $original_stock ) + ) { + return new \WP_Error( + 'invalid-stock-quantity', + sprintf( + /* translators: 1: product ID 2: quantity in stock */ + __( + 'The stock has not been updated because the value has changed since editing. Product %1$d has %2$d units in stock.', + 'dokan-lite' + ), + $product->get_id(), + $product->get_stock_quantity( 'edit' ) + ) + ); + } + + return wc_stock_amount( $value ); + }, ] ); $section->add_field( Elements::LOW_STOCK_AMOUNT, [ - 'title' => __( 'Low stock threshold', 'dokan-lite' ), - 'type' => 'number', - 'name' => '_low_stock_amount', - 'placeholder' => 1, - 'min' => 0, - 'step' => 1, + 'title' => __( 'Low stock threshold', 'dokan-lite' ), + 'description' => __( 'When product stock reaches this amount you will be notified by email. It is possible to define different values for each variation individually.', 'dokan-lite' ), + 'field_type' => 'number', + 'name' => '_low_stock_amount', + 'placeholder' => sprintf( + /* translators: %d: Amount of stock left */ + esc_attr__( 'Store-wide threshold (%d)', 'dokan-lite' ), + esc_attr( get_option( 'woocommerce_notify_low_stock_amount' ) ) + ), + 'additional_properties' => [ + 'min' => 0, + 'step' => 'any', + ], + 'sanitize_callback' => 'wc_stock_amount', ] ); $section->add_field( Elements::BACKORDERS, [ - 'title' => __( 'Allow Backorders', 'dokan-lite' ), - 'type' => 'select', - 'name' => '_backorders', - 'options' => wc_get_product_backorder_options(), + 'title' => __( 'Allow Backorders', 'dokan-lite' ), + 'description' => __( 'If managing stock, this controls whether or not backorders are allowed. If enabled, stock quantity can go below 0.', 'dokan-lite' ), + 'field_type' => 'select', + 'name' => '_backorders', + 'options' => wc_get_product_backorder_options(), ] ); $section->add_field( Elements::SOLD_INDIVIDUALLY, [ - 'title' => __( 'Sold Individually', 'dokan-lite' ), - 'type' => 'checkbox', - 'name' => '_sold_individually', - 'value' => 'yes', + 'title' => __( 'Sold Individually', 'dokan-lite' ), + 'description' => __( 'Check to let customers to purchase only 1 item in a single order. This is particularly useful for items that have limited quantity, for example art or handmade goods.', 'dokan-lite' ), + 'field_type' => 'checkbox', + 'name' => '_sold_individually', + 'additional_properties' => [ + 'value' => 'yes', + ], + 'sanitize_callback' => function ( $value ) { + return ! empty( $value ) && 'yes' === $value; + }, ] ); } @@ -277,25 +434,45 @@ public function init_downloadable_fields() { ] ); + $section->add_field( + Elements::DOWNLOADS, [ + 'title' => __( 'Downloadable Files', 'dokan-lite' ), + 'description' => __( 'Upload files that customers can download after purchase.', 'dokan-lite' ), + 'field_type' => 'downloadable', + 'name' => '_wc_file_urls[]', + 'sanitize_callback' => function ( $file_names, $file_urls, $file_hashes ) { + return dokan()->product->prepare_downloads( $file_names, $file_urls, $file_hashes ); + }, + ] + ); + $section->add_field( Elements::DOWNLOAD_LIMIT, [ - 'title' => __( 'Download Limit', 'dokan-lite' ), - 'type' => 'number', - 'name' => '_download_limit', - 'placeholder' => __( 'Unlimited', 'dokan-lite' ), - 'min' => 0, - 'step' => 1, + 'title' => __( 'Download Limit', 'dokan-lite' ), + 'placeholder' => __( 'Unlimited', 'dokan-lite' ), + 'description' => __( 'Leave blank for unlimited re-downloads.', 'dokan-lite' ), + 'field_type' => 'number', + 'name' => '_download_limit', + 'additional_properties' => [ + 'min' => 0, + 'step' => 1, + ], + 'sanitize_callback' => 'absint', ] ); $section->add_field( Elements::DOWNLOAD_EXPIRY, [ - 'title' => __( 'Download Expiry', 'dokan-lite' ), - 'type' => 'number', - 'name' => '_download_expiry', - 'placeholder' => __( 'Never', 'dokan-lite' ), - 'min' => 0, - 'step' => 1, + 'title' => __( 'Download Expiry', 'dokan-lite' ), + 'placeholder' => __( 'Never', 'dokan-lite' ), + 'description' => __( 'Enter the number of days before a download link expires, or leave blank.', 'dokan-lite' ), + 'field_type' => 'number', + 'name' => '_download_expiry', + 'additional_properties' => [ + 'min' => 0, + 'step' => 1, + ], + 'sanitize_callback' => 'absint', ] ); } @@ -319,38 +496,43 @@ public function init_other_fields() { $section->add_field( Elements::STATUS, [ - 'title' => __( 'Product Status', 'dokan-lite' ), - 'type' => 'select', - 'name' => 'post_status', - 'default' => dokan_get_default_product_status( dokan_get_current_user_id() ), - 'options' => dokan_get_available_post_status(), // get it with product_id param + 'title' => __( 'Product Status', 'dokan-lite' ), + 'field_type' => 'select', + 'name' => 'status', + 'options' => dokan_get_available_post_status(), // get it with product_id param ] ); $section->add_field( Elements::CATALOG_VISIBILITY, [ - 'title' => __( 'Visibility', 'dokan-lite' ), - 'type' => 'select', - 'name' => '_visibility', - 'options' => dokan_get_product_visibility_options(), + 'title' => __( 'Visibility', 'dokan-lite' ), + 'field_type' => 'select', + 'name' => '_visibility', + 'options' => dokan_get_product_visibility_options(), ] ); $section->add_field( Elements::PURCHASE_NOTE, [ - 'title' => __( 'Purchase Note', 'dokan-lite' ), - 'type' => 'textarea', - 'name' => '_purchase_note', - 'placeholder' => __( 'Customer will get this info in their order email', 'dokan-lite' ), + 'title' => __( 'Purchase Note', 'dokan-lite' ), + 'field_type' => 'textarea', + 'name' => '_purchase_note', + 'placeholder' => __( 'Customer will get this info in their order email', 'dokan-lite' ), + 'sanitize_callback' => 'wp_kses_post', ] ); $section->add_field( Elements::REVIEWS_ALLOWED, [ - 'title' => __( 'Enable product reviews', 'dokan-lite' ), - 'type' => 'checkbox', - 'name' => '_enable_reviews', - 'value' => 'yes', + 'title' => __( 'Enable product reviews', 'dokan-lite' ), + 'field_type' => 'checkbox', + 'name' => 'comment_status', + 'additional_properties' => [ + 'value' => 'yes', + ], + 'sanitize_callback' => function ( $value ) { + return ! empty( $value ) && 'yes' === $value; + }, ] ); } @@ -382,17 +564,20 @@ public function init_shipping_fields() { $section->add_field( Elements::DISABLE_SHIPPING_META, [ - 'title' => __( 'Disable shipping', 'dokan-lite' ), - 'type' => 'checkbox', - 'name' => '_disable_shipping', - 'value' => 'no', + 'title' => __( 'Disable shipping', 'dokan-lite' ), + 'type' => 'meta', + 'field_type' => 'checkbox', + 'name' => '_disable_shipping', + 'additional_properties' => [ + 'value' => 'no', + ], ] ); $section->add_field( Elements::WEIGHT, [ 'title' => __( 'Weight', 'dokan-lite' ), - 'type' => 'number', + 'field_type' => 'number', 'name' => '_weight', 'placeholder' => '0.00', 'min' => 0, @@ -403,7 +588,7 @@ public function init_shipping_fields() { $section->add_field( Elements::DIMENSIONS_LENGTH, [ 'title' => __( 'Length', 'dokan-lite' ), - 'type' => 'number', + 'field_type' => 'number', 'name' => '_length', 'placeholder' => '0.00', 'min' => 0, @@ -414,7 +599,7 @@ public function init_shipping_fields() { $section->add_field( Elements::DIMENSIONS_WIDTH, [ 'title' => __( 'Width', 'dokan-lite' ), - 'type' => 'number', + 'field_type' => 'number', 'name' => '_width', 'placeholder' => '0.00', 'min' => 0, @@ -425,7 +610,7 @@ public function init_shipping_fields() { $section->add_field( Elements::DIMENSIONS_HEIGHT, [ 'title' => __( 'Height', 'dokan-lite' ), - 'type' => 'number', + 'field_type' => 'number', 'name' => '_height', 'placeholder' => '0.00', 'min' => 0, @@ -439,27 +624,32 @@ public function init_shipping_fields() { $section->add_field( Elements::SHIPPING_CLASS, [ - 'title' => __( 'Shipping Class', 'dokan-lite' ), - 'type' => 'select', - 'name' => '_shipping_class', - 'placeholder' => __( 'Select shipping class', 'dokan-lite' ), - 'options' => [], + 'title' => __( 'Shipping Class', 'dokan-lite' ), + 'field_type' => 'select', + 'name' => 'product_shipping_class', + 'placeholder' => __( 'Select shipping class', 'dokan-lite' ), + 'options' => [], + 'sanitization_callback' => 'absint', ] ); $section->add_field( Elements::OVERWRITE_SHIPPING_META, [ - 'title' => __( 'Override your store\'s default shipping cost for this product', 'dokan-lite' ), - 'type' => 'checkbox', - 'name' => '_overwrite_shipping', - 'value' => 'no', + 'title' => __( 'Override your store\'s default shipping cost for this product', 'dokan-lite' ), + 'type' => 'meta', + 'field_type' => 'checkbox', + 'name' => '_overwrite_shipping', + 'additional_properties' => [ + 'value' => 'no', + ], ] ); $section->add_field( Elements::ADDITIONAL_SHIPPING_COST_META, [ 'title' => __( 'Additional cost', 'dokan-lite' ), - 'type' => 'number', + 'type' => 'meta', + 'field_type' => 'number', 'name' => '_additional_price', 'placeholder' => '0.00', 'min' => 0, @@ -470,7 +660,8 @@ public function init_shipping_fields() { $section->add_field( Elements::ADDITIONAL_SHIPPING_QUANTITY_META, [ 'title' => __( 'Per Qty Additional Price', 'dokan-lite' ), - 'type' => 'number', + 'type' => 'meta', + 'field_type' => 'number', 'name' => '_additional_qty', 'placeholder' => '0', 'min' => 0, @@ -480,19 +671,20 @@ public function init_shipping_fields() { $section->add_field( Elements::ADDITIONAL_SHIPPING_PROCESSING_TIME_META, [ - 'title' => __( 'Processing Time', 'dokan-lite' ), - 'type' => 'select', - 'name' => '_dps_processing_time', - 'options' => [], + 'title' => __( 'Processing Time', 'dokan-lite' ), + 'type' => 'meta', + 'field_type' => 'select', + 'name' => '_dps_processing_time', + 'options' => [], ] ); $section->add_field( Elements::TAX_STATUS, [ - 'title' => __( 'Tax Status', 'dokan-lite' ), - 'type' => 'select', - 'name' => '_tax_status', - 'options' => [ + 'title' => __( 'Tax Status', 'dokan-lite' ), + 'field_type' => 'select', + 'name' => '_tax_status', + 'options' => [ 'taxable' => __( 'Taxable', 'dokan-lite' ), 'shipping' => __( 'Shipping only', 'dokan-lite' ), 'none' => _x( 'None', 'Tax status', 'dokan-lite' ), @@ -502,10 +694,11 @@ public function init_shipping_fields() { $section->add_field( Elements::TAX_CLASS, [ - 'title' => __( 'Tax Class', 'dokan-lite' ), - 'type' => 'select', - 'name' => '_tax_class', - 'options' => wc_get_product_tax_class_options(), + 'title' => __( 'Tax Class', 'dokan-lite' ), + 'field_type' => 'select', + 'name' => '_tax_class', + 'options' => wc_get_product_tax_class_options(), + 'sanitize_callback' => 'sanitize_title', ] ); } diff --git a/includes/ProductForm/Section.php b/includes/ProductForm/Section.php index ee306074f6..09e796ae2a 100644 --- a/includes/ProductForm/Section.php +++ b/includes/ProductForm/Section.php @@ -2,6 +2,8 @@ namespace WeDevs\Dokan\ProductForm; +use WP_Error; + defined( 'ABSPATH' ) || exit; /** @@ -25,7 +27,6 @@ class Section extends Component { */ public function __construct( string $id, array $args = [] ) { $this->data['fields'] = []; - $this->data = wp_parse_args( $args, $this->data ); // set id from the args $this->set_id( $id ); @@ -78,6 +79,17 @@ public function add_field( string $id, array $args ) { Factory::add_field( $id, $args ); } + /** + * Returns registered field based on given field id. + * + * @param string $id Field id. + * + * @return Field|WP_Error New field or WP_Error. + */ + public function get_field( string $id ) { + return Factory::get_field( $id ); + } + /** * Initialize fields * @@ -86,13 +98,12 @@ public function add_field( string $id, array $args ) { * @return $this */ private function init_fields() { - $fields = Factory::instance()->get_fields(); - $this->data['fields'] = array_filter( - $fields, - function ( Field $field ) { - return $field->get_section() === $this->get_id(); + $this->data['fields'] = []; + foreach ( Factory::instance()->get_fields() as $field ) { + if ( $field->get_section() === $this->get_id() ) { + $this->data['fields'][ $field->get_id() ] = $field; } - ); + } return $this; } diff --git a/includes/REST/Manager.php b/includes/REST/Manager.php index ce0d1f936b..1adaed2ac4 100644 --- a/includes/REST/Manager.php +++ b/includes/REST/Manager.php @@ -176,7 +176,7 @@ public function on_dokan_rest_insert_product( $object, $request, $creating ) { return; } - do_action( 'dokan_new_product_added', $object->get_id(), $request ); + do_action( 'dokan_new_product_added', $object->get_id(), [] ); } /** diff --git a/includes/Rewrites.php b/includes/Rewrites.php index 542473ab45..134df16c16 100755 --- a/includes/Rewrites.php +++ b/includes/Rewrites.php @@ -267,7 +267,7 @@ public function product_edit_template( $template ) { return $template; } - $edit_product_url = dokan_locate_template( 'products/edit-product-single.php' ); + $edit_product_url = dokan_locate_template( 'products/edit/edit-product-single.php' ); return apply_filters( 'dokan_get_product_edit_template', $edit_product_url ); } diff --git a/includes/functions.php b/includes/functions.php index d39da0a525..0e96817e07 100755 --- a/includes/functions.php +++ b/includes/functions.php @@ -705,6 +705,7 @@ class="<?php echo esc_attr( $class ); ?>" case 'checkbox': $label = isset( $attr['label'] ) ? $attr['label'] : ''; + $desc = isset( $attr['desc'] ) ? $attr['desc'] : ''; $class = ( $class === 'dokan-form-control' ) ? '' : $class; ?> @@ -712,8 +713,14 @@ class="<?php echo esc_attr( $class ); ?>" <input type="hidden" name="<?php echo esc_attr( $name ); ?>" value="no"> <input <?php echo esc_attr( $required ); ?> name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( $name ); ?>" value="yes" type="checkbox"<?php checked( $value, 'yes' ); ?>> <?php echo esc_html( $label ); ?> + <?php if ( $desc ) { ?> + <i + class="fas fa-question-circle tips" + aria-hidden="true" + data-title="<?php echo esc_attr( $desc ); ?>"> + </i> + <?php } ?> </label> - <?php break; diff --git a/includes/woo-views/html-product-download.php b/includes/woo-views/html-product-download.php index a7c75ed107..db2eed492b 100755 --- a/includes/woo-views/html-product-download.php +++ b/includes/woo-views/html-product-download.php @@ -1,17 +1,48 @@ +<?php +/** + * Template used to form individual rows within the downloadable files table. + * + * @var array $file Product download data. + * @var string $key Product download key. + */ + +if ( ! defined( 'ABSPATH' ) ) { + exit; +} +?> <tr> <td> - <input type="text" class="dokan-form-control input_text" placeholder="<?php esc_attr_e( 'File Name', 'dokan-lite' ); ?>" name="_wc_file_names[]" value="<?php echo esc_attr( $file['name'] ); ?>" /> + <input + type="text" + class="dokan-form-control input_text" + placeholder="<?php esc_attr_e( 'File Name', 'dokan-lite' ); ?>" + name="_wc_file_names[]" + value="<?php echo esc_attr( $file['name'] ); ?>" /> + <input type="hidden" name="_wc_file_hashes[]" value="<?php echo esc_attr( $key ); ?>" /> </td> <td> <p> - <input type="text" class="dokan-form-control dokan-w8 input_text wc_file_url" placeholder="https://" name="_wc_file_urls[]" value="<?php echo esc_attr( $file['file'] ); ?>" style="margin-right: 8px;" /> - <a href="#" class="dokan-btn dokan-btn-sm dokan-btn-default upload_file_button" data-choose="<?php esc_attr_e( 'Choose file', 'dokan-lite' ); ?>" data-update="<?php esc_attr_e( 'Insert file URL', 'dokan-lite' ); ?>"><?php echo esc_html( str_replace( ' ', ' ', __( 'Choose file', 'dokan-lite' ) ) ); ?></a> + <input + type="text" + class="dokan-form-control dokan-w8 input_text wc_file_url" + placeholder="https://" name="_wc_file_urls[]" + value="<?php echo esc_attr( $file['file'] ); ?>" + style="margin-right: 8px;"/> + <a + href="#" + class="dokan-btn dokan-btn-sm dokan-btn-default upload_file_button" + data-choose="<?php esc_attr_e( 'Choose file', 'dokan-lite' ); ?>" + data-update="<?php esc_attr_e( 'Insert file URL', 'dokan-lite' ); ?>"> + <?php echo esc_html( str_replace( ' ', ' ', __( 'Choose file', 'dokan-lite' ) ) ); ?> + </a> </p> </td> <td> <p> - <a href="#" class="dokan-btn dokan-btn-sm dokan-btn-danger dokan-product-delete"><span><?php esc_html_e( 'Delete', 'dokan-lite' ); ?></span></a> + <a href="#" class="dokan-btn dokan-btn-sm dokan-btn-danger dokan-product-delete"> + <span><?php esc_html_e( 'Delete', 'dokan-lite' ); ?></span> + </a> </p> </td> </tr> diff --git a/templates/products/add-new-product-modal.php b/templates/products/add-new-product-modal.php deleted file mode 100644 index ed5811ed49..0000000000 --- a/templates/products/add-new-product-modal.php +++ /dev/null @@ -1,3 +0,0 @@ -<div id="dokan-add-product-popup" - data-izimodal-title="<i class='fas fa-briefcase'> </i> <?php esc_html_e( 'Add New Product', 'dokan-lite' ); ?>" -></div> diff --git a/templates/products/download-virtual.php b/templates/products/download-virtual.php deleted file mode 100644 index 8d62fe39f6..0000000000 --- a/templates/products/download-virtual.php +++ /dev/null @@ -1,13 +0,0 @@ -<div class="dokan-form-group dokan-product-type-container <?php echo esc_attr( $class ); ?>"> - <div class="content-half-part downloadable-checkbox"> - <label> - <input type="checkbox" <?php checked( $is_downloadable, true ); ?> class="_is_downloadable" name="_downloadable" id="_downloadable"> <?php esc_html_e( 'Downloadable', 'dokan-lite' ); ?> <i class="fas fa-question-circle tips" aria-hidden="true" data-title="<?php esc_attr_e( 'Downloadable products give access to a file upon purchase.', 'dokan-lite' ); ?>"></i> - </label> - </div> - <div class="content-half-part virtual-checkbox"> - <label> - <input type="checkbox" <?php checked( $is_virtual, true ); ?> class="_is_virtual" name="_virtual" id="_virtual"> <?php esc_html_e( 'Virtual', 'dokan-lite' ); ?> <i class="fas fa-question-circle tips" aria-hidden="true" data-title="<?php esc_attr_e( 'Virtual products are intangible and aren\'t shipped.', 'dokan-lite' ); ?>"></i> - </label> - </div> - <div class="dokan-clearfix"></div> -</div> diff --git a/templates/products/downloadable.php b/templates/products/downloadable.php deleted file mode 100644 index 50b0deaf89..0000000000 --- a/templates/products/downloadable.php +++ /dev/null @@ -1,71 +0,0 @@ -<div class="dokan-download-options dokan-edit-row dokan-clearfix <?php echo esc_attr( $class ); ?>"> - <div class="dokan-section-heading" data-togglehandler="dokan_download_options"> - <h2><i class="fas fa-download" aria-hidden="true"></i> <?php esc_html_e( 'Downloadable Options', 'dokan-lite' ); ?></h2> - <p><?php esc_html_e( 'Configure your downloadable product settings', 'dokan-lite' ); ?></p> - <a href="#" class="dokan-section-toggle"> - <i class="fas fa-sort-down fa-flip-vertical" aria-hidden="true"></i> - </a> - <div class="dokan-clearfix"></div> - </div> - - <div class="dokan-section-content"> - <div class="dokan-divider-top dokan-clearfix"> - - <?php do_action( 'dokan_product_edit_before_sidebar' ); ?> - - <div class="dokan-side-body dokan-download-wrapper"> - <table class="dokan-table"> - <tfoot> - <tr> - <th colspan="3"> - <?php - $file = [ - 'file' => '', - 'name' => '', - ]; - ob_start(); - require DOKAN_INC_DIR . '/woo-views/html-product-download.php'; - $row_html = ob_get_clean(); - ?> - <a href="#" class="insert-file-row dokan-btn dokan-btn-sm dokan-btn-success" data-row="<?php echo esc_attr( $row_html ); ?>"> - <?php esc_html_e( 'Add File', 'dokan-lite' ); ?> - </a> - </th> - </tr> - </tfoot> - <thead> - <tr> - <th><?php esc_html_e( 'Name', 'dokan-lite' ); ?> <span class="tips" title="<?php esc_attr_e( 'This is the name of the download shown to the customer.', 'dokan-lite' ); ?>">[?]</span></th> - <th><?php esc_html_e( 'File URL', 'dokan-lite' ); ?> <span class="tips" title="<?php esc_attr_e( 'This is the URL or absolute path to the file which customers will get access to.', 'dokan-lite' ); ?>">[?]</span></th> - <th><?php esc_html_e( 'Action', 'dokan-lite' ); ?></th> - </tr> - </thead> - <tbody> - <?php - $downloadable_files = get_post_meta( $post_id, '_downloadable_files', true ); - - if ( $downloadable_files ) { - foreach ( $downloadable_files as $key => $file ) { - include DOKAN_INC_DIR . '/woo-views/html-product-download.php'; - } - } - ?> - </tbody> - </table> - - <div class="dokan-clearfix"> - <div class="content-half-part"> - <label for="_download_limit" class="form-label"><?php esc_html_e( 'Download Limit', 'dokan-lite' ); ?></label> - <?php dokan_post_input_box( $post_id, '_download_limit', array( 'placeholder' => __( 'Unlimited', 'dokan-lite' ) ) ); ?> - </div><!-- .content-half-part --> - - <div class="content-half-part"> - <label for="_download_expiry" class="form-label"><?php esc_html_e( 'Download Expiry', 'dokan-lite' ); ?></label> - <?php dokan_post_input_box( $post_id, '_download_expiry', array( 'placeholder' => __( 'Never', 'dokan-lite' ) ) ); ?> - </div><!-- .content-half-part --> - </div> - - </div> <!-- .dokan-side-body --> - </div> <!-- .downloadable --> - </div> -</div> diff --git a/templates/products/edit-product-single.php b/templates/products/edit-product-single.php deleted file mode 100755 index 049cd14de0..0000000000 --- a/templates/products/edit-product-single.php +++ /dev/null @@ -1,538 +0,0 @@ -<?php - -use WeDevs\Dokan\ProductCategory\Helper; - -// phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited -global $post; - -$from_shortcode = false; -$new_product = false; - -if ( - ! isset( $post->ID ) - && ( - ! isset( $_GET['_dokan_edit_product_nonce'] ) || - ! wp_verify_nonce( sanitize_key( $_GET['_dokan_edit_product_nonce'] ), 'dokan_edit_product_nonce' ) || - ! isset( $_GET['product_id'] ) - ) -) { - wp_die( esc_html__( 'Access Denied, No product found', 'dokan-lite' ) ); -} - -if ( isset( $post->ID ) && $post->ID && 'product' === $post->post_type ) { - $post_id = $post->ID; - $post_title = $post->post_title; - $post_content = $post->post_content; - $post_excerpt = $post->post_excerpt; - $post_status = $post->post_status; - $product = wc_get_product( $post_id ); -} - -if ( isset( $_GET['product_id'] ) ) { - $post_id = intval( $_GET['product_id'] ); - $post = get_post( $post_id ); - $post_status = $post->post_status; - $auto_draft = 'auto-draft' === $post_status; - $post_title = $auto_draft ? '' : $post->post_title; - $post_content = $auto_draft ? '' : $post->post_content; - $post_excerpt = $post->post_excerpt; - $product = wc_get_product( $post_id ); - $from_shortcode = true; -} - -if ( isset( $_GET['product_id'] ) && 0 === absint( $_GET['product_id'] ) ) { - $post_id = intval( $_GET['product_id'] ); - $post_title = ''; - $post_content = ''; - $post_excerpt = ''; - $post_status = 'auto-draft'; - $product = new WC_Product( $post_id ); - - $product->set_name( $post_title ); - $product->set_status( $post_status ); - $post_id = $product->save(); - $post = get_post( $post_id ); - $from_shortcode = true; - $new_product = true; -} - -if ( ! dokan_is_product_author( $post_id ) ) { - wp_die( esc_html__( 'Access Denied', 'dokan-lite' ) ); -} - -$_regular_price = get_post_meta( $post_id, '_regular_price', true ); -$_sale_price = get_post_meta( $post_id, '_sale_price', true ); -$is_discount = ! empty( $_sale_price ); -$_sale_price_dates_from = get_post_meta( $post_id, '_sale_price_dates_from', true ); -$_sale_price_dates_to = get_post_meta( $post_id, '_sale_price_dates_to', true ); - -$_sale_price_dates_from = ! empty( $_sale_price_dates_from ) ? date_i18n( 'Y-m-d', $_sale_price_dates_from ) : ''; -$_sale_price_dates_to = ! empty( $_sale_price_dates_to ) ? date_i18n( 'Y-m-d', $_sale_price_dates_to ) : ''; -$show_schedule = false; - -if ( ! empty( $_sale_price_dates_from ) && ! empty( $_sale_price_dates_to ) ) { - $show_schedule = true; -} - -$_featured = get_post_meta( $post_id, '_featured', true ); -$terms = wp_get_object_terms( $post_id, 'product_type' ); -$product_type = ( ! empty( $terms ) ) ? sanitize_title( current( $terms )->name ) : 'simple'; -$variations_class = ( $product_type === 'simple' ) ? 'dokan-hide' : ''; -$_visibility = ( version_compare( WC_VERSION, '2.7', '>' ) ) ? $product->get_catalog_visibility() : get_post_meta( $post_id, '_visibility', true ); - -if ( ! $from_shortcode ) { - get_header(); -} - -if ( ! empty( $_GET['errors'] ) ) { - dokan()->dashboard->templates->products->set_errors( array_map( 'sanitize_text_field', wp_unslash( $_GET['errors'] ) ) ); -} - -/** - * Action hook to fire before dokan dashboard wrap - * - * @since 2.4 - */ -do_action( 'dokan_dashboard_wrap_before', $post, $post_id ); -?> - -<?php do_action( 'dokan_dashboard_wrap_start' ); ?> - - <div class="dokan-dashboard-wrap"> - - <?php - /** - * Action took to fire before dashboard content. - * - * @hooked get_dashboard_side_navigation - * - * @since 2.4 - */ - do_action( 'dokan_dashboard_content_before' ); - - /** - * Action hook to fire before product content area. - * - * @since 2.4 - */ - do_action( 'dokan_before_product_content_area' ); - ?> - - <div class="dokan-dashboard-content dokan-product-edit"> - - <?php - /** - * Action hook to fire inside product content area before - * - * @since 2.4 - */ - do_action( 'dokan_product_content_inside_area_before' ); - - if ( $new_product ) { - do_action( 'dokan_new_product_before_product_area' ); - } - ?> - - <header class="dokan-dashboard-header dokan-clearfix"> - <h1 class="entry-title"> - <?php - if ( $new_product || 'auto-draft' === $post->post_status ) { - esc_html_e( 'Add New Product', 'dokan-lite' ); - } else { - esc_html_e( 'Edit Product', 'dokan-lite' ); - } - ?> - <span class="dokan-label <?php echo esc_attr( dokan_get_post_status_label_class( $post->post_status ) ); ?> dokan-product-status-label"> - <?php echo esc_html( dokan_get_post_status( $post->post_status ) ); ?> - </span> - - <?php if ( $post->post_status === 'publish' ) : ?> - <span class="dokan-right"> - <a class="dokan-btn dokan-btn-theme dokan-btn-sm" href="<?php echo esc_url( get_permalink( $post->ID ) ); ?>" target="_blank"><?php esc_html_e( 'View Product', 'dokan-lite' ); ?></a> - </span> - <?php endif; ?> - - <?php if ( $_visibility === 'hidden' ) : ?> - <span class="dokan-right dokan-label dokan-label-default dokan-product-hidden-label"><i class="far fa-eye-slash"></i> <?php esc_html_e( 'Hidden', 'dokan-lite' ); ?></span> - <?php endif; ?> - </h1> - </header><!-- .entry-header --> - - <div class="product-edit-new-container product-edit-container"> - <?php if ( dokan()->dashboard->templates->products->has_errors() ) : ?> - <div class="dokan-alert dokan-alert-danger"> - <a class="dokan-close" data-dismiss="alert">×</a> - - <?php foreach ( dokan()->dashboard->templates->products->get_errors() as $error ) : ?> - <strong><?php esc_html_e( 'Error!', 'dokan-lite' ); ?></strong> <?php echo esc_html( $error ); ?>.<br> - <?php endforeach; ?> - </div> - <?php endif; ?> - - <?php if ( isset( $_GET['message'] ) && $_GET['message'] === 'success' ) : ?> - <div class="dokan-message"> - <button type="button" class="dokan-close" data-dismiss="alert">×</button> - <strong><?php esc_html_e( 'Success!', 'dokan-lite' ); ?></strong> <?php esc_html_e( 'The product has been saved successfully.', 'dokan-lite' ); ?> - - <?php if ( $post->post_status === 'publish' ) : ?> - <a href="<?php echo esc_url( get_permalink( $post_id ) ); ?>" target="_blank"><?php esc_html_e( 'View Product →', 'dokan-lite' ); ?></a> - <?php endif; ?> - </div> - <?php endif; ?> - - <?php if ( apply_filters( 'dokan_can_post', true ) ) : ?> - <?php if ( dokan_is_seller_enabled( get_current_user_id() ) ) : ?> - <form class="dokan-product-edit-form" role="form" method="post" id="post"> - - <?php do_action( 'dokan_product_data_panel_tabs' ); ?> - <?php do_action( 'dokan_product_edit_before_main' ); ?> - - <div class="dokan-form-top-area"> - - <div class="content-half-part dokan-product-meta"> - - <div id="dokan-product-title-area" class="dokan-form-group"> - <input type="hidden" name="dokan_product_id" id="dokan-edit-product-id" value="<?php echo esc_attr( $post_id ); ?>"/> - - <label for="post_title" class="form-label"><?php esc_html_e( 'Title', 'dokan-lite' ); ?></label> - <?php - dokan_post_input_box( - $post_id, - 'post_title', - [ - 'placeholder' => __( 'Product name..', 'dokan-lite' ), - 'value' => $post_title, - ] - ); - ?> - <div class="dokan-product-title-alert dokan-hide"> - <?php esc_html_e( 'Please enter product title!', 'dokan-lite' ); ?> - </div> - - <div id="edit-slug-box" class="hide-if-no-js"></div> - <?php wp_nonce_field( 'samplepermalink', 'samplepermalinknonce', false ); ?> - <input type="hidden" name="editable-post-name" class="dokan-hide" id="editable-post-name-full-dokan"> - <input type="hidden" value="<?php echo esc_attr( $post->post_name ); ?>" name="edited-post-name" class="dokan-hide" id="edited-post-name-dokan"> - </div> - - <?php $product_types = apply_filters( 'dokan_product_types', [ 'simple' => __( 'Simple', 'dokan-lite' ) ] ); ?> - - <?php if ( is_array( $product_types ) ) : ?> - <?php if ( count( $product_types ) === 1 && array_key_exists( 'simple', $product_types ) ) : ?> - <input type="hidden" id="product_type" name="product_type" value="simple"> - <?php else : ?> - <div class="dokan-form-group"> - <label for="product_type" class="form-label"><?php esc_html_e( 'Product Type', 'dokan-lite' ); ?> <i class="fas fa-question-circle tips" aria-hidden="true" data-title="<?php esc_html_e( 'Choose Variable if your product has multiple attributes - like sizes, colors, quality etc', 'dokan-lite' ); ?>"></i></label> - <select name="product_type" class="dokan-form-control" id="product_type"> - <?php foreach ( $product_types as $key => $value ) : ?> - <option value="<?php echo esc_attr( $key ); ?>" <?php selected( $product_type, $key ); ?>><?php echo esc_html( $value ); ?></option> - <?php endforeach; ?> - </select> - </div> - <?php endif; ?> - <?php endif; ?> - - <?php do_action( 'dokan_product_edit_after_title', $post, $post_id ); ?> - - <div class="show_if_simple dokan-clearfix show_if_external"> - - <div class="dokan-form-group dokan-clearfix dokan-price-container"> - - <div class="content-half-part regular-price"> - <label for="_regular_price" class="form-label"><?php esc_html_e( 'Price', 'dokan-lite' ); ?></label> - <div class="dokan-input-group"> - <span class="dokan-input-group-addon"><?php echo esc_html( get_woocommerce_currency_symbol() ); ?></span> - <?php - dokan_post_input_box( - $post_id, - '_regular_price', - [ - 'class' => 'dokan-product-regular-price', - 'placeholder' => __( '0.00', 'dokan-lite' ), - ], - 'price' - ); - ?> - </div> - </div> - - <div class="content-half-part sale-price"> - <label for="_sale_price" class="form-label"> - <?php esc_html_e( 'Discounted Price', 'dokan-lite' ); ?> - <a href="#" class="sale_schedule <?php echo $show_schedule ? 'dokan-hide' : ''; ?>"><?php esc_html_e( 'Schedule', 'dokan-lite' ); ?></a> - <a href="#" class="cancel_sale_schedule <?php echo ( ! $show_schedule ) ? 'dokan-hide' : ''; ?>"><?php esc_html_e( 'Cancel', 'dokan-lite' ); ?></a> - </label> - - <div class="dokan-input-group"> - <span class="dokan-input-group-addon"><?php echo esc_html( get_woocommerce_currency_symbol() ); ?></span> - <?php - dokan_post_input_box( - $post_id, - '_sale_price', - [ - 'class' => 'dokan-product-sales-price', - 'placeholder' => __( '0.00', 'dokan-lite' ), - ], - 'price' - ); - ?> - </div> - </div> - </div> - - <div class="dokan-form-group dokan-clearfix dokan-price-container"> - <div class="dokan-product-less-price-alert dokan-hide"> - <?php esc_html_e( 'Product price can\'t be less than the vendor fee!', 'dokan-lite' ); ?> - </div> - </div> - - <div class="sale_price_dates_fields dokan-clearfix dokan-form-group <?php echo ( ! $show_schedule ) ? 'dokan-hide' : ''; ?>"> - <div class="content-half-part from"> - <div class="dokan-input-group"> - <span class="dokan-input-group-addon"><?php esc_html_e( 'From', 'dokan-lite' ); ?></span> - <input type="text" name="_sale_price_dates_from" class="dokan-form-control dokan-start-date" value="<?php echo esc_attr( $_sale_price_dates_from ); ?>" maxlength="10" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" placeholder="<?php esc_html_e( 'YYYY-MM-DD', 'dokan-lite' ); ?>"> - </div> - </div> - - <div class="content-half-part to"> - <div class="dokan-input-group"> - <span class="dokan-input-group-addon"><?php esc_html_e( 'To', 'dokan-lite' ); ?></span> - <input type="text" name="_sale_price_dates_to" class="dokan-form-control dokan-end-date" value="<?php echo esc_attr( $_sale_price_dates_to ); ?>" maxlength="10" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" placeholder="<?php esc_html_e( 'YYYY-MM-DD', 'dokan-lite' ); ?>"> - </div> - </div> - </div><!-- .sale-schedule-container --> - </div> - - <div class="dokan-form-group"> - <?php - do_action( 'dokan_product_edit_after_pricing', $post, $post_id ); - - $data = Helper::get_saved_products_category( $post_id ); - $data['from'] = 'edit_product'; - - dokan_get_template_part( 'products/dokan-category-header-ui', '', $data ); - ?> - </div> - - <div class="dokan-form-group"> - <label for="product_tag_edit" class="form-label"><?php esc_html_e( 'Tags', 'dokan-lite' ); ?></label> - <?php - $terms = wp_get_post_terms( $post_id, 'product_tag', array( 'fields' => 'all' ) ); - $can_create_tags = dokan_get_option( 'product_vendors_can_create_tags', 'dokan_selling' ); - $tags_placeholder = 'on' === $can_create_tags ? __( 'Select tags/Add tags', 'dokan-lite' ) : __( 'Select product tags', 'dokan-lite' ); - - $drop_down_tags = array( - 'hide_empty' => 0, - ); - ?> - <select multiple="multiple" id="product_tag_edit" name="product_tag[]" class="product_tag_search dokan-form-control" data-placeholder="<?php echo esc_attr( $tags_placeholder ); ?>"> - <?php if ( ! empty( $terms ) ) : ?> - <?php foreach ( $terms as $tax_term ) : ?> - <option value="<?php echo esc_attr( $tax_term->term_id ); ?>" selected="selected" ><?php echo esc_html( $tax_term->name ); ?></option> - <?php endforeach ?> - <?php endif ?> - </select> - </div> - - <?php do_action( 'dokan_product_edit_after_product_tags', $post, $post_id ); ?> - </div><!-- .content-half-part --> - - <div class="content-half-part featured-image"> - - <div class="dokan-feat-image-upload dokan-new-product-featured-img"> - <?php - $wrap_class = ' dokan-hide'; - $instruction_class = ''; - $feat_image_id = 0; - - if ( has_post_thumbnail( $post_id ) ) { - $wrap_class = ''; - $instruction_class = ' dokan-hide'; - $feat_image_id = get_post_thumbnail_id( $post_id ); - } - ?> - - <div class="instruction-inside<?php echo esc_attr( $instruction_class ); ?>"> - <input type="hidden" name="feat_image_id" class="dokan-feat-image-id" value="<?php echo esc_attr( $feat_image_id ); ?>"> - - <i class="fas fa-cloud-upload-alt"></i> - <a href="#" class="dokan-feat-image-btn btn btn-sm"><?php esc_html_e( 'Upload a product cover image', 'dokan-lite' ); ?></a> - </div> - - <div class="image-wrap<?php echo esc_attr( $wrap_class ); ?>"> - <a class="close dokan-remove-feat-image">×</a> - <?php if ( $feat_image_id ) : ?> - <?php - echo get_the_post_thumbnail( - $post_id, - apply_filters( 'single_product_large_thumbnail_size', 'shop_single' ), - [ - 'height' => '', - 'width' => '', - ] - ); - ?> - <?php else : ?> - <img height="" width="" src="" alt=""> - <?php endif; ?> - </div> - </div><!-- .dokan-feat-image-upload --> - - <div class="dokan-product-gallery"> - <div class="dokan-side-body" id="dokan-product-images"> - <div id="product_images_container"> - <ul class="product_images dokan-clearfix"> - <?php - $product_images = get_post_meta( $post_id, '_product_image_gallery', true ); - $gallery = explode( ',', $product_images ); - - if ( $gallery ) : - foreach ( $gallery as $image_id ) : - if ( empty( $image_id ) ) : - continue; - endif; - - $attachment_image = wp_get_attachment_image_src( $image_id, 'thumbnail' ); - ?> - <li class="image" data-attachment_id="<?php echo esc_attr( $image_id ); ?>"> - <img src="<?php echo esc_url( $attachment_image[0] ); ?>" alt=""> - <a href="#" class="action-delete" title="<?php esc_attr_e( 'Delete image', 'dokan-lite' ); ?>">×</a> - </li> - <?php - endforeach; - endif; - ?> - <li class="add-image add-product-images tips" data-title="<?php esc_html_e( 'Add gallery image', 'dokan-lite' ); ?>"> - <a href="#" class="add-product-images"><i class="fas fa-plus" aria-hidden="true"></i></a> - </li> - </ul> - - <input type="hidden" id="product_image_gallery" name="product_image_gallery" value="<?php echo esc_attr( $product_images ); ?>"> - </div> - </div> - </div> <!-- .product-gallery --> - - <?php do_action( 'dokan_product_gallery_image_count' ); ?> - - </div><!-- .content-half-part --> - </div><!-- .dokan-form-top-area --> - - <div class="dokan-product-short-description"> - <label for="post_excerpt" class="form-label"><?php esc_html_e( 'Short Description', 'dokan-lite' ); ?></label> - <?php - wp_editor( - $post_excerpt, - 'post_excerpt', - apply_filters( - 'dokan_product_short_description', - [ - 'editor_height' => 50, - 'quicktags' => true, - 'media_buttons' => false, - 'teeny' => false, - 'editor_class' => 'post_excerpt', - ] - ) - ); - ?> - </div> - - <div class="dokan-product-description"> - <label for="post_content" class="form-label"><?php esc_html_e( 'Description', 'dokan-lite' ); ?></label> - <?php - wp_editor( - $post_content, - 'post_content', - apply_filters( - 'dokan_product_description', - [ - 'editor_height' => 50, - 'quicktags' => true, - 'media_buttons' => false, - 'teeny' => false, - 'editor_class' => 'post_content', - ] - ) - ); - ?> - </div> - - <?php do_action( 'dokan_new_product_form', $post, $post_id ); ?> - <?php do_action( 'dokan_product_edit_after_main', $post, $post_id ); ?> - - <?php do_action( 'dokan_product_edit_after_inventory_variants', $post, $post_id ); ?> - - <?php if ( $post_id ) : ?> - <?php do_action( 'dokan_product_edit_after_options', $post_id ); ?> - <?php endif; ?> - - <?php wp_nonce_field( 'dokan_edit_product', 'dokan_edit_product_nonce' ); ?> - - <input type="hidden" name="dokan_product_id" id="dokan_product_id" value="<?php echo esc_attr( $post_id ); ?>" /> - <!--hidden input for Firefox issue--> - <input type="hidden" name="dokan_update_product" value="<?php esc_attr_e( 'Save Product', 'dokan-lite' ); ?>"/> - <input type="submit" name="dokan_update_product" id="publish" class="dokan-btn dokan-btn-theme dokan-btn-lg dokan-right" value="<?php esc_attr_e( 'Save Product', 'dokan-lite' ); ?>"/> - <div class="dokan-clearfix"></div> - </form> - <?php else : ?> - <div class="dokan-alert dokan-alert"> - <?php echo esc_html( dokan_seller_not_enabled_notice() ); ?> - </div> - <?php endif; ?> - - <?php else : ?> - - <?php do_action( 'dokan_can_post_notice' ); ?> - - <?php endif; ?> - </div> <!-- #primary .content-area --> - - <?php - /** - * Action took to fire inside product content after. - * - * @since 2.4 - */ - do_action( 'dokan_product_content_inside_area_after' ); - ?> - </div> - - <?php - /** - * Action took to fire after dashboard content. - * - * @since 2.4 - */ - do_action( 'dokan_dashboard_content_after' ); - - /** - * Action took to fire after product content area. - * - * @since 2.4 - */ - do_action( 'dokan_after_product_content_area' ); - ?> - - </div><!-- .dokan-dashboard-wrap --> - -<?php do_action( 'dokan_dashboard_wrap_end' ); ?> - -<div class="dokan-clearfix"></div> - -<?php - -/** - * Action hook to fire after dokan dashboard wrap - * - * @since 2.4 - */ -do_action( 'dokan_dashboard_wrap_after', $post, $post_id ); - -wp_reset_postdata(); - -if ( ! $from_shortcode ) { - get_footer(); -} - -// phpcs:enable WordPress.WP.GlobalVariablesOverride.Prohibited -?> diff --git a/templates/products/edit/edit-product-single.php b/templates/products/edit/edit-product-single.php new file mode 100755 index 0000000000..bea7d138c8 --- /dev/null +++ b/templates/products/edit/edit-product-single.php @@ -0,0 +1,213 @@ +<?php +/** + * Dokan Dashboard Product Edit Template + * + * @since 2.4 + * + * @var $product WC_Product instance of WC_Product object + * @var $from_shortcode bool if the template loaded from shortcode + * @var $new_product bool if the product is new + * @package dokan + */ + +defined( 'ABSPATH' ) || exit; + +global $post; // phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited + +if ( ! isset( $from_shortcode ) || ! wc_string_to_bool( $from_shortcode ) ) { + // this file is loaded from theme + // apply security check for theme + if ( ! current_user_can( 'dokan_edit_product' ) ) { + dokan_get_template_part( + 'global/dokan-error', '', [ + 'deleted' => false, + 'message' => __( 'You have no permission to view this page', 'dokan-lite' ), + ] + ); + return; + } + + // check if seller is enabled for selling + if ( ! dokan_is_seller_enabled( dokan_get_current_user_id() ) ) { + dokan_seller_not_enabled_notice(); + return; + } + + // while calling from theme, we need to check if the product id is passed or not + $post_id = isset( $_GET['product_id'] ) ? intval( wp_unslash( $_GET['product_id'] ) ) : $post->ID; //phpcs:ignore + if ( ! $post_id ) { + // this is `add new` product page + $product = new WC_Product_Simple(); + $product->set_status( 'auto-draft' ); + $product->save(); + $new_product = true; + } else { + $product = wc_get_product( $post_id ); + $new_product = false; + } +} + +$post = get_post( $product->get_id() ); + +if ( ! dokan_is_product_author( $product->get_id() ) ) { + wp_die( esc_html__( 'Access Denied', 'dokan-lite' ) ); +} + +if ( ! $from_shortcode ) { + get_header(); +} + +if ( isset( $_GET['errors'] ) ) { // phpcs:ignore + dokan()->dashboard->templates->products->set_errors( array_map( 'sanitize_text_field', wp_unslash( $_GET['errors'] ) ) ); //phpcs:ignore +} + +/** + * Action hook to fire before dokan dashboard wrap + * + * @since 2.4 + */ +do_action( 'dokan_dashboard_wrap_before', $post, $product->get_id() ); +?> + +<?php do_action( 'dokan_dashboard_wrap_start' ); ?> + + <div class="dokan-dashboard-wrap"> + + <?php + /** + * Action took to fire before dashboard content. + * + * @hooked get_dashboard_side_navigation + * + * @since 2.4 + */ + do_action( 'dokan_dashboard_content_before' ); + + /** + * Action hook to fire before product content area. + * + * @since 2.4 + */ + do_action( 'dokan_before_product_content_area' ); + ?> + + <div class="dokan-dashboard-content dokan-product-edit"> + + <?php + /** + * Action hook to fire inside product content area before + * + * @since 2.4 + */ + do_action( 'dokan_product_content_inside_area_before' ); + + if ( $new_product ) { + do_action( 'dokan_new_product_before_product_area' ); + } + ?> + + <header class="dokan-dashboard-header dokan-clearfix"> + <h1 class="entry-title"> + <?php + if ( $new_product || 'auto-draft' === $product->get_status() ) { + esc_html_e( 'Add New Product', 'dokan-lite' ); + } else { + esc_html_e( 'Edit Product', 'dokan-lite' ); + } + ?> + <span class="dokan-label <?php echo esc_attr( dokan_get_post_status_label_class( $product->get_status() ) ); ?> dokan-product-status-label"> + <?php echo esc_html( dokan_get_post_status( $product->get_status() ) ); ?> + </span> + + <?php if ( $product->get_status() === 'publish' ) : ?> + <span class="dokan-right"> + <a class="dokan-btn dokan-btn-theme dokan-btn-sm" href="<?php echo esc_url( $product->get_permalink() ); ?>" target="_blank"><?php esc_html_e( 'View Product', 'dokan-lite' ); ?></a> + </span> + <?php endif; ?> + + <?php if ( $product->get_catalog_visibility() === 'hidden' ) : ?> + <span class="dokan-right dokan-label dokan-label-default dokan-product-hidden-label"><i class="far fa-eye-slash"></i> <?php esc_html_e( 'Hidden', 'dokan-lite' ); ?></span> + <?php endif; ?> + </h1> + </header><!-- .entry-header --> + + <div class="product-edit-new-container product-edit-container"> + <?php if ( dokan()->dashboard->templates->products->has_errors() ) : ?> + <div class="dokan-alert dokan-alert-danger"> + <a class="dokan-close" data-dismiss="alert">×</a> + + <?php foreach ( dokan()->dashboard->templates->products->get_errors() as $error ) : ?> + <strong><?php esc_html_e( 'Error!', 'dokan-lite' ); ?></strong> <?php echo esc_html( $error ); ?>.<br> + <?php endforeach; ?> + </div> + <?php endif; ?> + + <?php if ( isset( $_GET['message'] ) && sanitize_text_field( wp_unslash( $_GET['message'] ) ) === 'success' ) : //phpcs:ignore ?> + <div class="dokan-message"> + <button type="button" class="dokan-close" data-dismiss="alert">×</button> + <strong><?php esc_html_e( 'Success!', 'dokan-lite' ); ?></strong> <?php esc_html_e( 'The product has been saved successfully.', 'dokan-lite' ); ?> + + <?php if ( $product->get_status() === 'publish' ) : ?> + <a href="<?php echo esc_url( $product->get_permalink() ); ?>" target="_blank"><?php esc_html_e( 'View Product →', 'dokan-lite' ); ?></a> + <?php endif; ?> + </div> + <?php endif; ?> + + <?php + if ( apply_filters( 'dokan_can_post', true ) ) { + // we are using require_once intentionally + include DOKAN_TEMPLATE_DIR . '/products/edit/sections/general.php'; + } else { + do_action( 'dokan_can_post_notice' ); + } + ?> + </div> <!-- #primary .content-area --> + + <?php + /** + * Action took to fire inside product content after. + * + * @since 2.4 + */ + do_action( 'dokan_product_content_inside_area_after' ); + ?> + </div> + + <?php + /** + * Action took to fire after dashboard content. + * + * @since 2.4 + */ + do_action( 'dokan_dashboard_content_after' ); + + /** + * Action took to fire after product content area. + * + * @since 2.4 + */ + do_action( 'dokan_after_product_content_area' ); + ?> + + </div><!-- .dokan-dashboard-wrap --> + +<?php do_action( 'dokan_dashboard_wrap_end' ); ?> + +<div class="dokan-clearfix"></div> + +<?php + +/** + * Action hook to fire after dokan dashboard wrap + * + * @since 2.4 + */ +do_action( 'dokan_dashboard_wrap_after', $post, $product->get_id() ); + +wp_reset_postdata(); + +if ( ! $from_shortcode ) { + get_footer(); +} + +// phpcs:enable WordPress.WP.GlobalVariablesOverride.Prohibited diff --git a/templates/products/catalog-mode-content.php b/templates/products/edit/sections/catalog-mode-content.php similarity index 99% rename from templates/products/catalog-mode-content.php rename to templates/products/edit/sections/catalog-mode-content.php index d6c259d93f..28ad13b949 100644 --- a/templates/products/catalog-mode-content.php +++ b/templates/products/edit/sections/catalog-mode-content.php @@ -8,6 +8,8 @@ use WeDevs\Dokan\CatalogMode\Helper; +defined( 'ABSPATH' ) || exit; + ?> <?php do_action( 'dokan_product_edit_before_catalog_mode', $product_id, $saved_data ); ?> diff --git a/templates/products/edit/sections/download-virtual.php b/templates/products/edit/sections/download-virtual.php new file mode 100644 index 0000000000..f966427ba4 --- /dev/null +++ b/templates/products/edit/sections/download-virtual.php @@ -0,0 +1,61 @@ +<?php +/** + * Downloadable and Virtual product type data. + * + * @since 2.4 + * + * @var WC_Product $product + * @var Section $section + * @var string $class + * @var string $digital_mode + */ + +use WeDevs\Dokan\ProductForm\Elements; +use WeDevs\Dokan\ProductForm\Section; + +defined( 'ABSPATH' ) || exit; + + +?> +<div class="dokan-form-group dokan-product-type-container <?php echo esc_attr( $class ); ?>"> + <?php + $downloadable = $section->get_field( Elements::DOWNLOADABLE ); + if ( ! is_wp_error( $downloadable ) && $downloadable->is_visible() ) : + ?> + <div class="content-half-part downloadable-checkbox"> + <?php + dokan_post_input_box( + $product->get_id(), + $downloadable->get_name(), + [ + 'value' => $product->is_downloadable() ? 'yes' : 'no', + 'label' => $downloadable->get_title(), + 'desc' => $downloadable->get_description(), + ], + 'checkbox' + ); + ?> + </div> + <?php endif; ?> + + <?php + $virtual = $section->get_field( Elements::VIRTUAL ); + if ( ! is_wp_error( $virtual ) && $virtual->is_visible() ) : + ?> + <div class="content-half-part virtual-checkbox"> + <?php + dokan_post_input_box( + $product->get_id(), + $virtual->get_name(), + [ + 'value' => $product->is_virtual() ? 'yes' : 'no', + 'label' => $virtual->get_title(), + 'desc' => $virtual->get_description(), + ], + 'checkbox' + ); + ?> + </div> + <?php endif; ?> + <div class="dokan-clearfix"></div> +</div> diff --git a/templates/products/edit/sections/downloadable.php b/templates/products/edit/sections/downloadable.php new file mode 100644 index 0000000000..705980d618 --- /dev/null +++ b/templates/products/edit/sections/downloadable.php @@ -0,0 +1,150 @@ +<?php +/** + * Product general data panel. + * + * @var WC_Product $product + * @var Section $section + * @var string $class + * + * @package WooCommerce\Admin + */ + +use WeDevs\Dokan\ProductForm\Elements; +use WeDevs\Dokan\ProductForm\Section; + +defined( 'ABSPATH' ) || exit; + +$post = get_post( $product->get_id() ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited +?> + +<div class="dokan-download-options dokan-edit-row dokan-clearfix <?php echo esc_attr( $class ); ?>"> + <div class="dokan-section-heading" data-togglehandler="dokan_download_options"> + <h2><i class="fas fa-download" aria-hidden="true"></i> <?php esc_html_e( 'Downloadable Options', 'dokan-lite' ); ?></h2> + <p><?php esc_html_e( 'Configure your downloadable product settings', 'dokan-lite' ); ?></p> + <a href="#" class="dokan-section-toggle"> + <i class="fas fa-sort-down fa-flip-vertical" aria-hidden="true"></i> + </a> + <div class="dokan-clearfix"></div> + </div> + + <div class="dokan-section-content"> + <div class="dokan-divider-top dokan-clearfix"> + + <?php do_action( 'dokan_product_edit_before_sidebar' ); ?> + + <div class="dokan-side-body dokan-download-wrapper"> + <table class="dokan-table"> + <tfoot> + <tr> + <th colspan="3"> + <?php + $key = ''; + $file = [ + 'file' => '', + 'name' => '', + ]; + ob_start(); + require DOKAN_INC_DIR . '/woo-views/html-product-download.php'; + $row_html = ob_get_clean(); + ?> + <a href="#" class="insert-file-row dokan-btn dokan-btn-sm dokan-btn-success" data-row="<?php echo esc_attr( $row_html ); ?>"> + <?php esc_html_e( 'Add File', 'dokan-lite' ); ?> + </a> + </th> + </tr> + </tfoot> + <thead> + <tr> + <th> + <?php esc_html_e( 'Name', 'dokan-lite' ); ?> + <span class="tips" title="<?php esc_attr_e( 'This is the name of the download shown to the customer.', 'dokan-lite' ); ?>">[?]</span> + </th> + <th> + <?php esc_html_e( 'File URL', 'dokan-lite' ); ?> + <span class="tips" title="<?php esc_attr_e( 'This is the URL or absolute path to the file which customers will get access to.', 'dokan-lite' ); ?>">[?]</span> + </th> + <th><?php esc_html_e( 'Action', 'dokan-lite' ); ?></th> + </tr> + </thead> + <tbody> + <?php + + $downloadable_files = $product->get_downloads( 'edit' ); + $disabled_downloads_count = 0; + + if ( $downloadable_files ) { + foreach ( $downloadable_files as $key => $file ) { + include DOKAN_INC_DIR . '/woo-views/html-product-download.php'; + } + } + ?> + </tbody> + </table> + + <div class="dokan-clearfix"> + <?php + $download_limit = $section->get_field( Elements::DOWNLOAD_LIMIT ); + if ( ! is_wp_error( $download_limit ) && $download_limit->is_visible() ) : + ?> + <div class="content-half-part"> + <label for="<?php echo esc_attr( $download_limit->get_name() ); ?>" class="form-label"> + <?php echo esc_html( $download_limit->get_title() ); ?> + <i + class="fas fa-question-circle tips" + aria-hidden="true" + data-title="<?php echo esc_attr( $download_limit->get_description() ); ?>"> + </i> + </label> + <?php + dokan_post_input_box( + $product->get_id(), + $download_limit->get_name(), + [ + 'value' => - 1 === $product->get_download_limit( 'edit' ) ? '' : $product->get_download_limit( 'edit' ), + 'placeholder' => $download_limit->get_placeholder(), + 'min' => $download_limit->get_additional_properties( 'min' ), + 'step' => $download_limit->get_additional_properties( 'step' ), + ], + 'number' + ); + ?> + </div><!-- .content-half-part --> + <?php endif; ?> + + <?php + $download_expiry = $section->get_field( Elements::DOWNLOAD_EXPIRY ); + if ( ! is_wp_error( $download_expiry ) && $download_expiry->is_visible() ) : + ?> + <div class="content-half-part"> + <label for="<?php echo esc_attr( $download_expiry->get_name() ); ?>" class="form-label"> + <?php echo esc_html( $download_expiry->get_title() ); ?> + <i + class="fas fa-question-circle tips" + aria-hidden="true" + data-title="<?php echo esc_attr( $download_expiry->get_description() ); ?>"> + </i> + </label> + <?php + dokan_post_input_box( + $product->get_id(), + $download_expiry->get_name(), + [ + 'value' => - 1 === $product->get_download_expiry( 'edit' ) ? '' : $product->get_download_expiry( 'edit' ), + 'placeholder' => __( 'Never', 'dokan-lite' ), + 'min' => $download_expiry->get_additional_properties( 'min' ), + 'step' => $download_expiry->get_additional_properties( 'step' ), + ], + 'number' + ); + ?> + </div><!-- .content-half-part --> + <?php endif; ?> + </div> + </div> <!-- .dokan-side-body --> + </div> <!-- .downloadable --> + </div> + + <?php do_action( 'dokan_product_edit_after_downloadable', $post, $product->get_id(), $product ); ?> +</div> + + diff --git a/templates/products/edit/sections/general.php b/templates/products/edit/sections/general.php new file mode 100644 index 0000000000..a2062a5be1 --- /dev/null +++ b/templates/products/edit/sections/general.php @@ -0,0 +1,383 @@ +<?php +/** + * Product general data panel. + * + * @var WC_Product $product + * + * @package Dokan\Templates + */ + +use WeDevs\Dokan\ProductCategory\Helper; +use WeDevs\Dokan\ProductForm\Elements; +use WeDevs\Dokan\ProductForm\Factory as ProductFormFactory; + +defined( 'ABSPATH' ) || exit; + +$section = ProductFormFactory::get_section( 'general' ); +if ( is_wp_error( $section ) ) { + return; +} +?> +<form class="dokan-product-edit-form" role="form" method="post" id="post"> + + <?php do_action( 'dokan_product_data_panel_tabs', $post, $product->get_id(), $product ); ?> + <?php do_action( 'dokan_product_edit_before_main', $post, $product->get_id(), $product ); ?> + + <div class="dokan-form-top-area"> + + <div class="content-half-part dokan-product-meta"> + <?php + $product_title = $section->get_field( Elements::NAME ); + if ( ! is_wp_error( $product_title ) && $product_title->is_visible() ) : + ?> + <div id="dokan-product-title-area" class="dokan-form-group"> + <input type="hidden" name="dokan_product_id" id="dokan-edit-product-id" value="<?php echo esc_attr( $product->get_id() ); ?>" /> + + <label for="<?php echo esc_attr( $product_title->get_name() ); ?>" class="form-label"><?php echo esc_html( $product_title->get_title() ); ?></label> + <?php + dokan_post_input_box( + $product->get_id(), + $product_title->get_name(), + [ + 'name' => $product_title->get_name(), + 'placeholder' => $product_title->get_placeholder(), + 'value' => $product->get_name(), + ] + ); + ?> + <div class="dokan-product-title-alert dokan-hide"> + <?php echo wp_kses_post( $product_title->get_error_message() ); ?> + </div> + + <div id="edit-slug-box" class="hide-if-no-js"></div> + <?php wp_nonce_field( 'samplepermalink', 'samplepermalinknonce', false ); ?> + <input type="hidden" name="editable-post-name" class="dokan-hide" id="editable-post-name-full-dokan"> + <input type="hidden" value="<?php echo esc_attr( $product->get_slug() ); ?>" name="edited-post-name" class="dokan-hide" id="edited-post-name-dokan"> + </div> + <?php endif; ?> + + <?php + $product_type = $section->get_field( Elements::TYPE ); + if ( ! is_wp_error( $product_type ) && $product_type->is_visible() ) : + $product_type_options = $product_type->get_options(); + ?> + <?php if ( count( $product_type_options ) === 1 && array_key_exists( 'simple', $product_type_options ) ) : ?> + <input type="hidden" id="<?php echo esc_attr( $product_type->get_name() ); ?>" name="<?php echo esc_attr( $product_type->get_name() ); ?>" value="simple"> + <?php else : ?> + <div class="dokan-form-group"> + <label for="<?php echo esc_attr( $product_type->get_name() ); ?>" class="form-label"> + <?php echo esc_html( $product_type->get_title() ); ?> + <i + class="fas fa-question-circle tips" + aria-hidden="true" + data-title="<?php echo esc_attr( $product_type->get_help_content() ); ?>"> + </i> + </label> + <select name="<?php echo esc_attr( $product_type->get_name() ); ?>" class="dokan-form-control" id="<?php echo esc_attr( $product_type->get_name() ); ?>"> + <?php foreach ( $product_type_options as $key => $value ) : ?> + <option value="<?php echo esc_attr( $key ); ?>" <?php selected( $product->get_type(), $key ); ?>><?php echo esc_html( $value ); ?></option> + <?php endforeach; ?> + </select> + </div> + <?php endif; ?> + <?php endif; ?> + + <?php do_action( 'dokan_product_edit_after_title', $post, $product->get_id(), $product ); ?> + + <div class="show_if_simple dokan-clearfix show_if_external"> + <div class="dokan-form-group dokan-clearfix dokan-price-container"> + <?php + $regular_price = $section->get_field( Elements::REGULAR_PRICE ); + if ( ! is_wp_error( $regular_price ) && $regular_price->is_visible() ) : + ?> + <div class="content-half-part regular-price"> + <label for="<?php echo esc_attr( $regular_price->get_name() ); ?>" class="form-label"><?php echo esc_html( $regular_price->get_title() ); ?></label> + <div class="dokan-input-group"> + <span class="dokan-input-group-addon"><?php echo esc_html( get_woocommerce_currency_symbol() ); ?></span> + <?php + dokan_post_input_box( + $product->get_id(), + $regular_price->get_name(), + [ + 'value' => $product->get_regular_price(), + 'class' => 'dokan-product-regular-price', + 'placeholder' => $regular_price->get_placeholder(), + ], + 'price' + ); + ?> + </div> + </div> + <?php endif; ?> + + <?php + $sale_price = $section->get_field( Elements::SALE_PRICE ); + $sale_price_dates_from_timestamp = $product->get_date_on_sale_from( 'edit' ) ? $product->get_date_on_sale_from( 'edit' )->getOffsetTimestamp() : false; + $sale_price_dates_to_timestamp = $product->get_date_on_sale_to( 'edit' ) ? $product->get_date_on_sale_to( 'edit' )->getOffsetTimestamp() : false; + + $sale_price_dates_from = $sale_price_dates_from_timestamp ? date_i18n( 'Y-m-d', $sale_price_dates_from_timestamp ) : ''; + $sale_price_dates_to = $sale_price_dates_to_timestamp ? date_i18n( 'Y-m-d', $sale_price_dates_to_timestamp ) : ''; + $show_schedule = $sale_price_dates_from && $sale_price_dates_to; + if ( ! is_wp_error( $sale_price ) && $sale_price->is_visible() ) : + ?> + <div class="content-half-part sale-price"> + <label for="<?php echo esc_attr( $sale_price->get_name() ); ?>" class="form-label"> + <?php echo esc_html( $sale_price->get_title() ); ?> + <a href="#" class="sale_schedule <?php echo $show_schedule ? 'dokan-hide' : ''; ?>"><?php esc_html_e( 'Schedule', 'dokan-lite' ); ?></a> + <a href="#" class="cancel_sale_schedule <?php echo ( ! $show_schedule ) ? 'dokan-hide' : ''; ?>"><?php esc_html_e( 'Cancel', 'dokan-lite' ); ?></a> + </label> + + <div class="dokan-input-group"> + <span class="dokan-input-group-addon"><?php echo esc_html( get_woocommerce_currency_symbol() ); ?></span> + <?php + dokan_post_input_box( + $product->get_id(), + $sale_price->get_name(), + [ + 'name' => $sale_price->get_name(), + 'value' => $product->get_sale_price(), + 'class' => 'dokan-product-sales-price', + 'placeholder' => $sale_price->get_placeholder(), + ], + 'price' + ); + ?> + </div> + </div> + <?php endif; ?> + </div> + + <div class="dokan-form-group dokan-clearfix dokan-price-container"> + <div class="dokan-product-less-price-alert dokan-hide"> + <?php esc_html_e( 'Product price can\'t be less than the vendor fee!', 'dokan-lite' ); ?> + </div> + </div> + + <?php + if ( ! is_wp_error( $sale_price ) && $sale_price->is_visible() ) : + $date_on_sale_from = $section->get_field( Elements::DATE_ON_SALE_FROM ); + $date_on_sale_to = $section->get_field( Elements::DATE_ON_SALE_TO ); + ?> + <div class="sale_price_dates_fields dokan-clearfix dokan-form-group <?php echo ( ! $show_schedule ) ? 'dokan-hide' : ''; ?>"> + <div class="content-half-part from"> + <div class="dokan-input-group"> + <span class="dokan-input-group-addon"><?php echo esc_html( $date_on_sale_from->get_title() ); ?></span> + <input + type="text" + name="<?php echo esc_attr( $date_on_sale_from->get_name() ); ?>" + class="dokan-form-control dokan-start-date" + value="<?php echo esc_attr( $sale_price_dates_from ); ?>" + maxlength="10" + pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" + placeholder="<?php echo esc_attr( $date_on_sale_from->get_placeholder() ); ?>"> + </div> + </div> + + <div class="content-half-part to"> + <div class="dokan-input-group"> + <span class="dokan-input-group-addon"><?php echo esc_html( $date_on_sale_to->get_title() ); ?></span> + <input + type="text" + name="<?php echo esc_attr( $date_on_sale_to->get_name() ); ?>" + class="dokan-form-control dokan-end-date" + value="<?php echo esc_attr( $sale_price_dates_to ); ?>" + maxlength="10" + pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" + placeholder="<?php echo esc_attr( $date_on_sale_to->get_placeholder() ); ?>"> + </div> + </div> + </div><!-- .sale-schedule-container --> + <?php endif; ?> + </div> + + <?php do_action( 'dokan_product_edit_after_pricing', $post, $product->get_id(), $product ); ?> + + <?php + $category = $section->get_field( Elements::CATEGORIES ); + if ( ! is_wp_error( $category ) && $category->is_visible() ) : + ?> + <div class="dokan-form-group"> + <?php + $data = Helper::get_saved_products_category( $product->get_id() ); + $data['from'] = 'edit_product'; + + dokan_get_template_part( 'products/dokan-category-header-ui', '', $data ); + ?> + </div> + <?php endif; ?> + + <?php + $tags = $section->get_field( Elements::TAGS ); + if ( ! is_wp_error( $tags ) && $tags->is_visible() ) : + $terms = $product->get_tag_ids(); + ?> + <div class="dokan-form-group"> + <label for="product_tag_edit" class="form-label"><?php echo $tags->get_title(); ?></label> + <select multiple="multiple" id="product_tag_edit" name="<?php echo esc_attr( $tags->get_name() ); ?>" class="product_tag_search dokan-form-control" data-placeholder="<?php echo esc_attr( $tags->get_placeholder() ); ?>"> + <?php if ( ! empty( $terms ) ) : ?> + <?php + foreach ( $terms as $tax_term ) : + $tax_term = get_term_by( 'id', $tax_term, 'product_tag' ); + if ( is_wp_error( $tax_term ) ) { + continue; + } + ?> + <option value="<?php echo esc_attr( $tax_term->term_id ); ?>" selected="selected"><?php echo esc_html( $tax_term->name ); ?></option> + <?php endforeach ?> + <?php endif ?> + </select> + </div> + <?php do_action( 'dokan_product_edit_after_product_tags', $post, $product->get_id(), $product ); ?> + <?php endif; ?> + </div><!-- .content-half-part --> + + <div class="content-half-part featured-image"> + <?php + $featured_image = $section->get_field( Elements::FEATURED_IMAGE_ID ); + if ( ! is_wp_error( $featured_image ) && $featured_image->is_visible() ) : + ?> + <div class="dokan-feat-image-upload dokan-new-product-featured-img"> + <?php + $wrap_class = ' dokan-hide'; + $instruction_class = ''; + $feat_image_id = $product->get_image_id(); + + if ( $feat_image_id ) { + $wrap_class = ''; + $instruction_class = ' dokan-hide'; + } + ?> + + <div class="instruction-inside<?php echo esc_attr( $instruction_class ); ?>"> + <input type="hidden" name="<?php echo esc_attr( $featured_image->get_name() ); ?>" class="dokan-feat-image-id" value="<?php echo esc_attr( $feat_image_id ); ?>"> + + <i class="fas fa-cloud-upload-alt"></i> + <a href="#" class="dokan-feat-image-btn btn btn-sm"><?php esc_html_e( 'Upload a product cover image', 'dokan-lite' ); ?></a> + </div> + + <div class="image-wrap<?php echo esc_attr( $wrap_class ); ?>"> + <a class="close dokan-remove-feat-image">×</a> + <?php if ( $feat_image_id ) : ?> + <?php + // todo: need to change this with $product->get_image() + echo get_the_post_thumbnail( + $product->get_id(), + apply_filters( 'single_product_large_thumbnail_size', 'shop_single' ), + [ + 'height' => '', + 'width' => '', + ] + ); + ?> + <?php else : ?> + <img height="" width="" src="" alt=""> + <?php endif; ?> + </div> + </div><!-- .dokan-feat-image-upload --> + <?php endif; ?> + + <?php + $gallery_images = $section->get_field( Elements::GALLERY_IMAGE_IDS ); + if ( ! is_wp_error( $gallery_images ) && $gallery_images->is_visible() ) : + ?> + <div class="dokan-product-gallery"> + <div class="dokan-side-body" id="dokan-product-images"> + <div id="product_images_container"> + <ul class="product_images dokan-clearfix"> + <?php + $product_images = $product->get_gallery_image_ids(); + + if ( $product_images ) : + foreach ( $product_images as $image_id ) : + if ( empty( $image_id ) ) : + continue; + endif; + + $attachment_image = wp_get_attachment_image_src( $image_id, 'thumbnail' ); + ?> + <li class="image" data-attachment_id="<?php echo esc_attr( $image_id ); ?>"> + <img src="<?php echo esc_url( $attachment_image[0] ); ?>" alt=""> + <a href="#" class="action-delete" title="<?php esc_attr_e( 'Delete image', 'dokan-lite' ); ?>">×</a> + </li> + <?php + endforeach; + endif; + ?> + <li class="add-image add-product-images tips" data-title="<?php esc_html_e( 'Add gallery image', 'dokan-lite' ); ?>"> + <a href="#" class="add-product-images"><i class="fas fa-plus" aria-hidden="true"></i></a> + </li> + </ul> + + <input type="hidden" id="product_image_gallery" name="<?php echo esc_attr( $gallery_images->get_name() ); ?>" value="<?php echo esc_attr( implode( ',', $product_images ) ); ?>"> + </div> + </div> + </div> <!-- .product-gallery --> + + <?php do_action( 'dokan_product_gallery_image_count' ); ?> + <?php endif; ?> + + </div><!-- .content-half-part --> + </div><!-- .dokan-form-top-area --> + + <?php + $short_description = $section->get_field( Elements::SHORT_DESCRIPTION ); + if ( ! is_wp_error( $short_description ) && $short_description->is_visible() ) : + ?> + <div class="dokan-product-short-description"> + <label for="<?php echo esc_attr( $short_description->get_name() ); ?>" class="form-label"><?php echo esc_html( $short_description->get_title() ); ?></label> + <?php + wp_editor( + $product->get_short_description(), + $short_description->get_name(), + apply_filters( + 'dokan_product_short_description', + [ + 'editor_height' => 50, + 'quicktags' => true, + 'media_buttons' => false, + 'teeny' => false, + 'editor_class' => 'post_excerpt', + ] + ) + ); + ?> + </div> + <?php endif; ?> + + <?php + $description = $section->get_field( Elements::DESCRIPTION ); + if ( ! is_wp_error( $description ) && $description->is_visible() ) : + ?> + <div class="dokan-product-description"> + <label for="<?php echo esc_attr( $description->get_name() ); ?>" class="form-label"><?php echo esc_html( $description->get_title() ); ?></label> + <?php + wp_editor( + $product->get_description(), + $description->get_name(), + apply_filters( + 'dokan_product_description', + [ + 'editor_height' => 50, + 'quicktags' => true, + 'media_buttons' => false, + 'teeny' => false, + 'editor_class' => 'post_content', + ] + ) + ); + ?> + </div> + <?php endif; ?> + + <?php do_action( 'dokan_product_edit_form', $post, $product->get_id(), $product ); ?> + <?php do_action( 'dokan_product_edit_after_main', $post, $product->get_id(), $product ); ?> + <?php do_action( 'dokan_product_edit_after_inventory_variants', $post, $product->get_id(), $product ); ?> + <?php do_action( 'dokan_product_edit_after_options', $product->get_id(), $product ); ?> + + <?php wp_nonce_field( 'dokan_edit_product', 'dokan_edit_product_nonce' ); ?> + <input type="hidden" name="dokan_product_id" id="dokan_product_id" value="<?php echo esc_attr( $product->get_id() ); ?>" /> + <!--hidden input for Firefox issue--> + <input type="hidden" name="dokan_update_product" value="<?php esc_attr_e( 'Save Product', 'dokan-lite' ); ?>" /> + <input type="submit" name="dokan_update_product" id="publish" class="dokan-btn dokan-btn-theme dokan-btn-lg dokan-right" value="<?php esc_attr_e( 'Save Product', 'dokan-lite' ); ?>" /> + <div class="dokan-clearfix"></div> +</form> diff --git a/templates/products/edit/sections/inventory.php b/templates/products/edit/sections/inventory.php new file mode 100644 index 0000000000..7b76927d72 --- /dev/null +++ b/templates/products/edit/sections/inventory.php @@ -0,0 +1,229 @@ +<?php +/** + * Dokan Dashboard Product Edit Template + * + * @since 2.4 + * + * @var WC_Product $product instance of WC_Product object + * @var Section $section + * @var string $class + * + * @package dokan + */ + +use WeDevs\Dokan\ProductForm\Elements; +use WeDevs\Dokan\ProductForm\Section; + +defined( 'ABSPATH' ) || exit; + +$post = get_post( $product->get_id() ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited +?> + +<div class="dokan-product-inventory dokan-edit-row <?php echo esc_attr( $class ); ?>"> + <div class="dokan-section-heading" data-togglehandler="dokan_product_inventory"> + <h2><i class="fas fa-cubes" aria-hidden="true"></i> <?php echo esc_html( $section->get_title() ); ?></h2> + <p><?php echo esc_html( $section->get_description() ); ?></p> + <a href="#" class="dokan-section-toggle"> + <i class="fas fa-sort-down fa-flip-vertical" aria-hidden="true"></i> + </a> + <div class="dokan-clearfix"></div> + </div> + + <div class="dokan-section-content"> + <?php + $sku = $section->get_field( Elements::SKU ); + if ( ! is_wp_error( $sku ) && $sku->is_visible() ) : + ?> + <div class="content-half-part dokan-form-group"> + <label for="<?php echo esc_attr( $sku->get_name() ); ?>" class="form-label"> + <?php echo wp_kses_post( $sku->get_title() ); ?> + <i + class="fas fa-question-circle tips" + aria-hidden="true" + data-title="<?php echo esc_attr( $sku->get_description() ); ?>"> + </i> + </label> + <?php + dokan_post_input_box( + $product->get_id(), + $sku->get_name(), + [ + 'value' => $product->get_sku(), + 'class' => 'dokan-form-control', + ] + ); + ?> + </div> + <?php endif; ?> + + <?php + $stock_status = $section->get_field( Elements::STOCK_STATUS ); + if ( ! is_wp_error( $stock_status ) && $stock_status->is_visible() ) : + ?> + <div class="content-half-part hide_if_variable hide_if_external hide_if_stock_global"> + <label for="<?php echo esc_attr( $stock_status->get_name() ); ?>" class="form-label"> + <?php echo esc_html( $stock_status->get_title() ); ?> + <i + class="fas fa-question-circle tips" + aria-hidden="true" + data-title="<?php echo esc_attr( $stock_status->get_description() ); ?>"> + </i> + </label> + <?php + dokan_post_input_box( + $product->get_id(), + $stock_status->get_name(), + [ + 'name' => $product->get_name(), + 'value' => $product->get_stock_status(), + 'options' => $stock_status->get_options(), + ], + 'select' + ); + ?> + </div> + <?php endif; ?> + + <div class="dokan-clearfix"></div> + + <?php if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) : ?> + <?php + $manage_stock = $section->get_field( Elements::MANAGE_STOCK ); + if ( ! is_wp_error( $manage_stock ) && $manage_stock->is_visible() ) : + ?> + <div class="dokan-form-group hide_if_grouped hide_if_external"> + <?php + dokan_post_input_box( + $product->get_id(), + $manage_stock->get_name(), + [ + 'value' => $product->get_manage_stock( 'edit' ) ? 'yes' : 'no', + 'label' => $manage_stock->get_title(), + ], + 'checkbox' + ); + ?> + </div> + <?php endif; ?> + + <div class="show_if_stock dokan-stock-management-wrapper dokan-form-group dokan-clearfix"> + <?php + $stock_quantity = $section->get_field( Elements::STOCK_QUANTITY ); + if ( ! is_wp_error( $stock_quantity ) && $stock_quantity->is_visible() ) : + ?> + <div class="content-half-part"> + <label for="<?php echo esc_attr( $stock_quantity->get_name() ); ?>" class="form-label"> + <?php echo esc_html( $stock_quantity->get_title() ); ?> + <i + class="fas fa-question-circle tips" + aria-hidden="true" + data-title="<?php echo esc_attr( $stock_quantity->get_description() ); ?>"> + </i> + </label> + <?php + dokan_post_input_box( + $product->get_id(), + $stock_quantity->get_name(), + [ + 'name' => $stock_quantity->get_name(), + 'value' => wc_stock_amount( $product->get_stock_quantity( 'edit' ) ), + 'class' => 'dokan-form-control', + 'placeholder' => $stock_quantity->get_placeholder(), + 'min' => $stock_quantity->get_additional_properties( 'min' ), + 'step' => $stock_quantity->get_additional_properties( 'step' ), + ], + 'number' + ); + ?> + </div> + <input type="hidden" name="_original_stock" value="<?php echo esc_attr( wc_stock_amount( $product->get_stock_quantity( 'edit' ) ) ); ?>"/> + <?php endif; ?> + + <?php + $low_stock_amount = $section->get_field( Elements::LOW_STOCK_AMOUNT ); + if ( ! is_wp_error( $low_stock_amount ) && $low_stock_amount->is_visible() ) : + ?> + <div class="content-half-part"> + <label for="<?php echo esc_attr( $low_stock_amount->get_name() ); ?>" class="form-label"> + <?php echo esc_html( $low_stock_amount->get_title() ); ?> + <i + class="fas fa-question-circle tips" + aria-hidden="true" + data-title="<?php echo esc_attr( $low_stock_amount->get_description() ); ?>"> + </i> + </label> + <?php + dokan_post_input_box( + $product->get_id(), + $stock_quantity->get_name(), + [ + 'value' => $product->get_low_stock_amount( 'edit' ), + 'class' => 'dokan-form-control', + 'placeholder' => $stock_quantity->get_placeholder(), + 'min' => $stock_quantity->get_additional_properties( 'min' ), + 'step' => $stock_quantity->get_additional_properties( 'step' ), + ], + 'number' + ); + ?> + </div> + <?php endif; ?> + + <?php + $backorders = $section->get_field( Elements::BACKORDERS ); + if ( is_wp_error( $backorders ) && $backorders->is_visible() ) : + ?> + <div class="content-half-part last-child"> + <label for="<?php echo esc_attr( $backorders->get_name() ); ?>" class="form-label"> + <?php echo esc_html( $backorders->get_title() ); ?> + <i + class="fas fa-question-circle tips" + aria-hidden="true" + data-title="<?php echo esc_attr( $backorders->get_description() ); ?>"> + </i> + </label> + + <?php + dokan_post_input_box( + $product->get_id(), + $backorders->get_name(), + [ + 'options' => $backorders->get_options(), + ], + 'select' + ); + ?> + </div> + <?php endif; ?> + + <div class="dokan-clearfix"></div> + </div><!-- .show_if_stock --> + <?php endif; ?> + + <?php + $sold_individually = $section->get_field( Elements::SOLD_INDIVIDUALLY ); + if ( ! is_wp_error( $sold_individually ) && $sold_individually->is_visible() ) : + ?> + <div class="dokan-form-group hide_if_grouped hide_if_external"> + <label class="" for="<?php echo esc_attr( $sold_individually->get_name() ); ?>"> + <input + name="<?php echo esc_attr( $sold_individually->get_name() ); ?>" + id="<?php echo esc_attr( $sold_individually->get_name() ); ?>" + value="yes" + type="checkbox" <?php checked( $product->get_sold_individually( 'edit' ), true ); ?>> + <i + class="fas fa-question-circle tips" + aria-hidden="true" + data-title="<?php echo esc_attr( $sold_individually->get_description() ); ?>"> + </i> + <?php esc_html_e( 'Allow only one quantity of this product to be bought in a single order', 'dokan-lite' ); ?> + </label> + </div> + <?php endif; ?> + + <?php do_action( 'dokan_product_edit_after_inventory' ); ?> + <?php do_action( 'dokan_product_edit_after_sidebar', $post, $product->get_id(), $product ); ?> + <?php do_action( 'dokan_single_product_edit_after_sidebar', $post, $product->get_id(), $product ); ?> + + </div><!-- .dokan-side-right --> +</div><!-- .dokan-product-inventory --> diff --git a/templates/products/edit/sections/others.php b/templates/products/edit/sections/others.php new file mode 100644 index 0000000000..0dc022a092 --- /dev/null +++ b/templates/products/edit/sections/others.php @@ -0,0 +1,112 @@ +<?php +/** + * Product other data panel. + * + * @since 2.4 + * + * @var Section $section + * @var WC_Product $product + * @var string $post_status + * @var string $class + * + * @package WeDevs\Dokan + */ + +use WeDevs\Dokan\ProductForm\Elements; +use WeDevs\Dokan\ProductForm\Section; + +?> + +<div class="dokan-other-options dokan-edit-row dokan-clearfix <?php echo esc_attr( $class ); ?>"> + <div class="dokan-section-heading" data-togglehandler="dokan_other_options"> + <h2><i class="fas fa-cog" aria-hidden="true"></i> <?php echo esc_html( $section->get_title() ); ?></h2> + <p><?php echo esc_html( $section->get_description() ); ?></p> + <a href="#" class="dokan-section-toggle"> + <i class="fas fa-sort-down fa-flip-vertical" aria-hidden="true"></i> + </a> + <div class="dokan-clearfix"></div> + </div> + + <div class="dokan-section-content"> + <?php + $product_status = $section->get_field( Elements::STATUS ); + if ( ! is_wp_error( $product_status ) && $product_status->is_visible() ) : + ?> + <div class="dokan-form-group content-half-part"> + <label for="<?php echo esc_attr( $product_status->get_name() ); ?>" class="form-label"> + <?php echo esc_html( $product_status->get_title() ); ?> + </label> + <select id="<?php echo esc_attr( $product_status->get_name() ); ?>" class="dokan-form-control" name="<?php echo esc_attr( $product_status->get_name() ); ?>"> + <?php foreach ( $product_status->get_options() as $status => $label ) : // phpcs:ignore ?> + <option value="<?php echo esc_attr( $status ); ?>" <?php selected( $status, $post_status ); ?>> + <?php echo esc_html( $label ); ?> + </option> + <?php endforeach; ?> + </select> + </div> + <?php endif; ?> + + <?php + $catalog_visibility = $section->get_field( Elements::CATALOG_VISIBILITY ); + if ( ! is_wp_error( $catalog_visibility ) && $catalog_visibility->is_visible() ) : + ?> + <div class="dokan-form-group content-half-part"> + <label for="<?php echo esc_attr( $catalog_visibility->get_name() ); ?>" class="form-label"> + <?php echo esc_html( $catalog_visibility->get_title() ); ?> + </label> + <select name="<?php echo esc_attr( $catalog_visibility->get_name() ); ?>" id="<?php echo esc_attr( $catalog_visibility->get_name() ); ?>" class="dokan-form-control"> + <?php foreach ( $catalog_visibility->get_options() as $name => $label ) : ?> + <option value="<?php echo esc_attr( $name ); ?>" <?php selected( $product->get_catalog_visibility( 'edit' ), $name ); ?>> + <?php echo esc_html( $label ); ?> + </option> + <?php endforeach; ?> + </select> + </div> + <?php endif; ?> + + <div class="dokan-clearfix"></div> + + <?php + $purchase_note = $section->get_field( Elements::PURCHASE_NOTE ); + if ( ! is_wp_error( $purchase_note ) && $purchase_note->is_visible() ) : + ?> + <div class="dokan-form-group"> + <label for="<?php echo esc_attr( $purchase_note->get_name() ); ?>" class="form-label"> + <?php echo esc_html( $purchase_note->get_title() ); ?> + </label> + <?php + dokan_post_input_box( + $product->get_id(), + $purchase_note->get_name(), + [ + 'name' => $purchase_note->get_name(), + 'value' => $product->get_purchase_note( 'edit' ), + 'placeholder' => $purchase_note->get_placeholder(), + ], + 'textarea' + ); + ?> + </div> + <?php endif; ?> + + <?php + $enable_reviews = $section->get_field( Elements::REVIEWS_ALLOWED ); + if ( ! is_wp_error( $enable_reviews ) && $enable_reviews->is_visible() && post_type_supports( 'product', 'comments' ) ) : + ?> + <div class="dokan-form-group"> + <?php + dokan_post_input_box( + $product->get_id(), + $enable_reviews->get_name(), + [ + 'name' => $enable_reviews->get_name(), + 'value' => $product->get_reviews_allowed( 'edit' ) ? 'yes' : 'no', + 'label' => __( 'Enable product reviews', 'dokan-lite' ), + ], + 'checkbox' + ); + ?> + </div> + <?php endif; ?> + </div> +</div><!-- .dokan-other-options --> diff --git a/templates/products/inventory.php b/templates/products/inventory.php deleted file mode 100644 index ec3c665baf..0000000000 --- a/templates/products/inventory.php +++ /dev/null @@ -1,96 +0,0 @@ -<div class="dokan-product-inventory dokan-edit-row <?php echo esc_attr( $class ); ?>"> - <div class="dokan-section-heading" data-togglehandler="dokan_product_inventory"> - <h2><i class="fas fa-cubes" aria-hidden="true"></i> <?php esc_html_e( 'Inventory', 'dokan-lite' ); ?></h2> - <p><?php esc_html_e( 'Manage inventory for this product.', 'dokan-lite' ); ?></p> - <a href="#" class="dokan-section-toggle"> - <i class="fas fa-sort-down fa-flip-vertical" aria-hidden="true"></i> - </a> - <div class="dokan-clearfix"></div> - </div> - - <div class="dokan-section-content"> - - <div class="content-half-part dokan-form-group"> - <label for="_sku" class="form-label"><?php esc_html_e( 'SKU', 'dokan-lite' ); ?> <span><?php esc_html_e( '(Stock Keeping Unit)', 'dokan-lite' ); ?></span></label> - <?php dokan_post_input_box( $post_id, '_sku' ); ?> - </div> - - <div class="content-half-part hide_if_variable hide_if_external hide_if_stock_global"> - <label for="_stock_status" class="form-label"><?php esc_html_e( 'Stock Status', 'dokan-lite' ); ?></label> - - <?php - dokan_post_input_box( - $post_id, - '_stock_status', - [ - 'options' => [ - 'instock' => __( 'In Stock', 'dokan-lite' ), - 'outofstock' => __( 'Out of Stock', 'dokan-lite' ), - 'onbackorder' => __( 'On Backorder', 'dokan-lite' ), - ], - ], - 'select' - ); - ?> - </div> - - <div class="dokan-clearfix"></div> - - <?php if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) : ?> - <div class="dokan-form-group hide_if_grouped hide_if_external"> - <?php dokan_post_input_box( $post_id, '_manage_stock', array( 'label' => __( 'Enable product stock management', 'dokan-lite' ) ), 'checkbox' ); ?> - </div> - - <div class="show_if_stock dokan-stock-management-wrapper dokan-form-group dokan-clearfix"> - - <div class="content-half-part"> - <label for="_stock" class="form-label"><?php esc_html_e( 'Stock quantity', 'dokan-lite' ); ?></label> - <input type="number" class="dokan-form-control" name="_stock" placeholder="<?php esc_attr__( '1', 'dokan-lite' ); ?>" value="<?php echo esc_attr( wc_stock_amount( $_stock ) ); ?>" min="0" step="1"> - </div> - - <?php if ( version_compare( WC_VERSION, '3.4.7', '>' ) ) : ?> - <div class="content-half-part"> - <label for="_low_stock_amount" class="form-label"><?php esc_html_e( 'Low stock threshold', 'dokan-lite' ); ?></label> - <input type="number" class="dokan-form-control" name="_low_stock_amount" placeholder="<?php esc_attr__( '1', 'dokan-lite' ); ?>" value="<?php echo esc_attr( wc_stock_amount( $_low_stock_amount ) ); ?>" min="0" step="1"> - </div> - <?php endif; ?> - - <div class="content-half-part last-child"> - <label for="_backorders" class="form-label"><?php esc_html_e( 'Allow Backorders', 'dokan-lite' ); ?></label> - - <?php - dokan_post_input_box( - $post_id, - '_backorders', - [ - 'options' => [ - 'no' => __( 'Do not allow', 'dokan-lite' ), - 'notify' => __( 'Allow but notify customer', 'dokan-lite' ), - 'yes' => __( 'Allow', 'dokan-lite' ), - ], - ], - 'select' - ); - ?> - </div> - <div class="dokan-clearfix"></div> - </div><!-- .show_if_stock --> - <?php endif; ?> - - <div class="dokan-form-group hide_if_grouped hide_if_external"> - <label class="" for="_sold_individually"> - <input name="_sold_individually" id="_sold_individually" value="yes" type="checkbox" <?php checked( $_sold_individually, 'yes' ); ?>> - <?php esc_html_e( 'Allow only one quantity of this product to be bought in a single order', 'dokan-lite' ); ?> - </label> - </div> - - <?php if ( $post_id ) : ?> - <?php do_action( 'dokan_product_edit_after_inventory' ); ?> - <?php endif; ?> - - <?php do_action( 'dokan_product_edit_after_downloadable', $post, $post_id ); ?> - <?php do_action( 'dokan_product_edit_after_sidebar', $post, $post_id ); ?> - <?php do_action( 'dokan_single_product_edit_after_sidebar', $post, $post_id ); ?> - - </div><!-- .dokan-side-right --> -</div><!-- .dokan-product-inventory --> diff --git a/templates/products/new-product.php b/templates/products/new-product.php deleted file mode 100755 index 65b379528c..0000000000 --- a/templates/products/new-product.php +++ /dev/null @@ -1,367 +0,0 @@ -<?php - -use WeDevs\Dokan\ProductCategory\Helper; - -$created_product = null; -$feat_image_id = null; -$regular_price = ''; -$sale_price = ''; -$sale_price_date_from = ''; -$sale_price_date_to = ''; -$post_content = ''; -$post_excerpt = ''; -$product_images = ''; -$post_title = ''; -$terms = []; -$currency_symbol = get_woocommerce_currency_symbol(); - -if ( isset( $_REQUEST['_dokan_add_product_nonce'] ) && wp_verify_nonce( sanitize_key( $_REQUEST['_dokan_add_product_nonce'] ), 'dokan_add_product_nonce' ) ) { - if ( ! empty( $_REQUEST['created_product'] ) ) { - $created_product = intval( $_REQUEST['created_product'] ); - } - - if ( ! empty( $_REQUEST['feat_image_id'] ) ) { - $feat_image_id = intval( $_REQUEST['feat_image_id'] ); - } - - if ( ! empty( $_REQUEST['_regular_price'] ) ) { - $regular_price = floatval( $_REQUEST['_regular_price'] ); - } - - if ( ! empty( $_REQUEST['_sale_price'] ) ) { - $sale_price = floatval( $_REQUEST['_sale_price'] ); - } - - if ( ! empty( $_REQUEST['_sale_price_dates_from'] ) ) { - $sale_price_date_from = sanitize_text_field( wp_unslash( $_REQUEST['_sale_price_dates_from'] ) ); - } - - if ( ! empty( $_REQUEST['_sale_price_dates_to'] ) ) { - $sale_price_date_to = sanitize_text_field( wp_unslash( $_REQUEST['_sale_price_dates_to'] ) ); - } - - if ( ! empty( $_REQUEST['post_content'] ) ) { - $post_content = wp_kses_post( wp_unslash( $_REQUEST['post_content'] ) ); - } - - if ( ! empty( $_REQUEST['post_excerpt'] ) ) { - $post_excerpt = sanitize_textarea_field( wp_unslash( $_REQUEST['post_excerpt'] ) ); - } - - if ( ! empty( $_REQUEST['post_title'] ) ) { - $post_title = sanitize_text_field( wp_unslash( $_REQUEST['post_title'] ) ); - } - - if ( ! empty( $_REQUEST['product_image_gallery'] ) ) { - $product_images = sanitize_text_field( wp_unslash( $_REQUEST['product_image_gallery'] ) ); - } - - if ( ! empty( $_REQUEST['product_tag'] ) ) { - $terms = array_map( 'intval', (array) wp_unslash( $_REQUEST['product_tag'] ) ); - } -} - -/** - * Action hook to fire before new product wrap. - * - * @since 2.4 - */ -do_action( 'dokan_new_product_wrap_before' ); -?> - -<?php do_action( 'dokan_dashboard_wrap_start' ); ?> - - <div class="dokan-dashboard-wrap"> - <?php - /** - * Action hook to fire before rendering dashboard content. - * - * @hooked get_dashboard_side_navigation - * - * @since 2.4 - */ - do_action( 'dokan_dashboard_content_before' ); - - /** - * Action hook to fire before rendering product content - * - * @since 2.4 - */ - do_action( 'dokan_before_new_product_content_area' ); - ?> - - - <div class="dokan-dashboard-content"> - - <?php - /** - * Action hook to fire inside new product content before - * - * @since 2.4 - */ - do_action( 'dokan_before_new_product_inside_content_area' ); - ?> - - <header class="dokan-dashboard-header dokan-clearfix"> - <h1 class="entry-title"> - <?php esc_html_e( 'Add New Product', 'dokan-lite' ); ?> - </h1> - </header><!-- .entry-header --> - - <?php do_action( 'dokan_new_product_before_product_area' ); ?> - - <div class="dokan-new-product-area"> - <?php if ( dokan()->dashboard->templates->products->has_errors() ) : ?> - <div class="dokan-alert dokan-alert-danger"> - <a class="dokan-close" data-dismiss="alert">×</a> - - <?php foreach ( dokan()->dashboard->templates->products->get_errors() as $error_msg ) : ?> - <strong><?php esc_html_e( 'Error!', 'dokan-lite' ); ?></strong> <?php echo wp_kses_post( $error_msg ); ?>.<br> - <?php endforeach; ?> - </div> - <?php endif; ?> - - <?php if ( ! empty( $created_product ) ) : ?> - <div class="dokan-alert dokan-alert-success"> - <a class="dokan-close" data-dismiss="alert">×</a> - <strong><?php esc_html_e( 'Success!', 'dokan-lite' ); ?></strong> - <?php - printf( - /* translators: %s: product title with edit link */ - __( 'You have successfully created %s product', 'dokan-lite' ), - sprintf( - '<a href="%s"><strong>%s</strong></a>', - esc_url( dokan_edit_product_url( $created_product ) ), - get_the_title( $created_product ) - ) - ); - ?> - </div> - <?php endif ?> - - <?php - if ( apply_filters( 'dokan_can_post', true ) ) : - $feat_image_url = ''; - $hide_instruction = ''; - $hide_img_wrap = 'dokan-hide'; - - if ( ! empty( $feat_image_id ) ) { - $feat_image_url = wp_get_attachment_url( $image_id ); - $hide_instruction = 'dokan-hide'; - $hide_img_wrap = ''; - } - - if ( dokan_is_seller_enabled( get_current_user_id() ) ) : - ?> - <form class="dokan-form-container" method="post"> - <div class="product-edit-container dokan-clearfix"> - <div class="content-half-part featured-image"> - <div class="featured-image"> - <div class="dokan-feat-image-upload"> - <div class="instruction-inside <?php echo esc_attr( $hide_instruction ); ?>"> - <input type="hidden" name="feat_image_id" class="dokan-feat-image-id" value="<?php echo esc_attr( $feat_image_id ); ?>"> - <i class="fas fa-cloud-upload-alt"></i> - <a href="#" class="dokan-feat-image-btn dokan-btn"><?php esc_html_e( 'Upload Product Image', 'dokan-lite' ); ?></a> - </div> - - <div class="image-wrap <?php echo esc_attr( $hide_img_wrap ); ?>"> - <a class="close dokan-remove-feat-image">×</a> - <img src="<?php echo esc_url( $feat_image_url ); ?>" alt=""> - </div> - </div> - </div> - - <div class="dokan-product-gallery"> - <div class="dokan-side-body" id="dokan-product-images"> - <div id="product_images_container"> - <ul class="product_images dokan-clearfix"> - <?php - if ( ! empty( $product_images ) ) : - $gallery = explode( ',', $product_images ); - if ( $gallery ) : - foreach ( $gallery as $image_id ) : - if ( empty( $image_id ) ) : - continue; - endif; - - $attachment_image = wp_get_attachment_image_src( $image_id ); - - if ( ! $attachment_image ) : - continue; - endif; - ?> - <li class="image" data-attachment_id="<?php echo esc_attr( $image_id ); ?>"> - <img src="<?php echo esc_url( $attachment_image[0] ); ?>" alt=""> - <a href="#" class="action-delete" title="<?php esc_attr_e( 'Delete image', 'dokan-lite' ); ?>">×</a> - </li> - <?php - endforeach; - endif; - endif; - ?> - <li class="add-image add-product-images tips" data-title="<?php esc_attr_e( 'Add gallery image', 'dokan-lite' ); ?>"> - <a href="#" class="add-product-images"><i class="fas fa-plus" aria-hidden="true"></i></a> - </li> - </ul> - <input type="hidden" id="product_image_gallery" name="product_image_gallery" value=""> - </div> - </div> - </div> <!-- .product-gallery --> - <?php do_action( 'dokan_product_gallery_image_count' ); ?> - </div> - - <div class="content-half-part dokan-product-meta"> - <div class="dokan-form-group"> - <input class="dokan-form-control" name="post_title" id="post-title" type="text" placeholder="<?php esc_attr_e( 'Product name..', 'dokan-lite' ); ?>" value="<?php echo esc_attr( $post_title ); ?>"> - </div> - - <div class="dokan-form-group"> - <div class="dokan-form-group dokan-clearfix dokan-price-container"> - <div class="content-half-part"> - <label for="_regular_price" class="dokan-form-label"><?php esc_html_e( 'Price', 'dokan-lite' ); ?></label> - <div class="dokan-input-group"> - <span class="dokan-input-group-addon"><?php echo esc_attr( $currency_symbol ); ?></span> - <input type="text" class="dokan-form-control wc_input_price dokan-product-regular-price" name="_regular_price" placeholder="0.00" id="_regular_price" value="<?php echo esc_attr( $regular_price ); ?>"> - </div> - </div> - - <div class="content-half-part sale-price"> - <label for="_sale_price" class="form-label"> - <?php esc_html_e( 'Discounted Price', 'dokan-lite' ); ?> - <a href="#" class="sale_schedule"><?php esc_html_e( 'Schedule', 'dokan-lite' ); ?></a> - <a href="#" class="cancel_sale_schedule dokan-hide"><?php esc_html_e( 'Cancel', 'dokan-lite' ); ?></a> - </label> - - <div class="dokan-input-group"> - <span class="dokan-input-group-addon"><?php echo esc_attr( $currency_symbol ); ?></span> - <input type="text" class="dokan-form-control wc_input_price dokan-product-sales-price" name="_sale_price" placeholder="0.00" id="_sale_price" value="<?php echo esc_attr( $sale_price ); ?>"> - </div> - </div> - </div> - - <div class="dokan-hide sale-schedule-container sale_price_dates_fields dokan-clearfix dokan-form-group"> - <div class="content-half-part from"> - <div class="dokan-input-group"> - <span class="dokan-input-group-addon"><?php esc_html_e( 'From', 'dokan-lite' ); ?></span> - <input type="text" name="_sale_price_dates_from" class="dokan-form-control datepicker sale_price_dates_from" maxlength="10" value="<?php echo esc_attr( $sale_price_date_from ); ?>" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" placeholder="<?php esc_attr_e( 'YYYY-MM-DD', 'dokan-lite' ); ?>"> - </div> - </div> - - <div class="content-half-part to"> - <div class="dokan-input-group"> - <span class="dokan-input-group-addon"><?php esc_html_e( 'To', 'dokan-lite' ); ?></span> - <input type="text" name="_sale_price_dates_to" class="dokan-form-control datepicker sale_price_dates_to" value="<?php echo esc_attr( $sale_price_date_to ); ?>" maxlength="10" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" placeholder="<?php esc_attr_e( 'YYYY-MM-DD', 'dokan-lite' ); ?>"> - </div> - </div> - </div><!-- .sale-schedule-container --> - </div> - - <div class="dokan-form-group"> - <textarea name="post_excerpt" id="post-excerpt" rows="5" class="dokan-form-control" placeholder="<?php esc_attr_e( 'Short description of the product...', 'dokan-lite' ); ?>"><?php echo esc_attr( $post_excerpt ); ?></textarea> - </div> - - <?php - $can_create_tags = 'on' === dokan_get_option( 'product_vendors_can_create_tags', 'dokan_selling' ); - $saved_product_cat_data = array_merge( (array) Helper::get_saved_products_category(), [ 'from' => 'new_product' ] ); - dokan_get_template_part( 'products/dokan-category-header-ui', '', $saved_product_cat_data ); - ?> - - <div class="dokan-form-group"> - <label for="product_tag" class="form-label"><?php esc_html_e( 'Tags', 'dokan-lite' ); ?></label> - <select multiple="multiple" name="product_tag[]" id="product_tag_search" class="product_tag_search product_tags dokan-form-control dokan-select2" data-placeholder="<?php echo $can_create_tags ? esc_attr__( 'Select tags/Add tags', 'dokan-lite' ) : esc_attr__( 'Select product tags', 'dokan-lite' ); ?>"> - <?php if ( ! empty( $terms ) ) : ?> - <?php foreach ( $terms as $product_term_id ) : ?> - <?php $product_term = get_term( $product_term_id ); ?> - <option value="<?php echo esc_attr( $product_term->term_id ); ?>" selected="selected" ><?php echo esc_html( $product_term->name ); ?></option> - <?php endforeach ?> - <?php endif ?> - </select> - </div> - - <?php do_action( 'dokan_new_product_after_product_tags' ); ?> - </div> - </div> - - <div class="dokan-form-group"> - <label for="post_content" class="control-label"><?php esc_html_e( 'Description', 'dokan-lite' ); ?> <i class="fas fa-question-circle tips" data-title="<?php esc_attr_e( 'Add your product description', 'dokan-lite' ); ?>" aria-hidden="true"></i></label> - <?php - wp_editor( - htmlspecialchars_decode( $post_content, ENT_QUOTES ), - 'post_content', - [ - 'editor_height' => 50, - 'quicktags' => false, - 'media_buttons' => false, - 'teeny' => true, - 'editor_class' => 'post_content', - ] - ); - ?> - </div> - - <?php do_action( 'dokan_new_product_form' ); ?> - - <hr> - - <div class="dokan-form-group dokan-right"> - <?php - wp_nonce_field( 'dokan_add_new_product', 'dokan_add_new_product_nonce' ); - - $show_add_new_button = ! function_exists( 'dokan_pro' ) || ! dokan_pro()->module->is_active( 'product_subscription' ) || \DokanPro\Modules\Subscription\Helper::get_vendor_remaining_products( dokan_get_current_user_id() ) !== 1; - - if ( $show_add_new_button ) : - ?> - <button type="submit" name="add_product" class="dokan-btn dokan-btn-default" value="create_and_add_new"> - <?php esc_attr_e( 'Create & Add New', 'dokan-lite' ); ?> - </button> - <?php endif; ?> - <button type="submit" name="add_product" class="dokan-btn dokan-btn-default dokan-btn-theme" value="create_new"><?php esc_attr_e( 'Create Product', 'dokan-lite' ); ?></button> - </div> - </form> - <?php else : ?> - <?php dokan_seller_not_enabled_notice(); ?> - <?php endif; ?> - <?php else : ?> - <?php do_action( 'dokan_can_post_notice' ); ?> - <?php endif; ?> - </div> - <?php - /** - * Action hook to fire inside new product content after - * - * @since 2.4 - */ - do_action( 'dokan_after_new_product_inside_content_area' ); - ?> - </div> <!-- #primary .content-area --> - <?php - /** - * Action hook to fire after rendering dashboard content. - * - * @since 2.4 - */ - do_action( 'dokan_dashboard_content_after' ); - - /** - * Action hook to fire after rendering product content. - * - * @since 2.4 - */ - do_action( 'dokan_after_new_product_content_area' ); - ?> - </div><!-- .dokan-dashboard-wrap --> -<?php -/** - * Action hook to fire at end of the dahboard wrap. - * - * @since 2.4 - */ -do_action( 'dokan_dashboard_wrap_end' ); - -/** - * Action hook to fire after new product wrap. - * - * @since 2.4 - */ -do_action( 'dokan_new_product_wrap_after' ); -?> diff --git a/templates/products/others.php b/templates/products/others.php deleted file mode 100644 index 165f041ba1..0000000000 --- a/templates/products/others.php +++ /dev/null @@ -1,59 +0,0 @@ -<?php -$post_statuses = dokan_get_available_post_status( $post->ID ); -?> - -<div class="dokan-other-options dokan-edit-row dokan-clearfix <?php echo esc_attr( $class ); ?>"> - <div class="dokan-section-heading" data-togglehandler="dokan_other_options"> - <h2><i class="fas fa-cog" aria-hidden="true"></i> <?php esc_html_e( 'Other Options', 'dokan-lite' ); ?></h2> - <p><?php esc_html_e( 'Set your extra product options', 'dokan-lite' ); ?></p> - <a href="#" class="dokan-section-toggle"> - <i class="fas fa-sort-down fa-flip-vertical" aria-hidden="true"></i> - </a> - <div class="dokan-clearfix"></div> - </div> - - <div class="dokan-section-content"> - <div class="dokan-form-group content-half-part"> - <label for="post_status" class="form-label"><?php esc_html_e( 'Product Status', 'dokan-lite' ); ?></label> - <select id="post_status" class="dokan-form-control" name="post_status"> - <?php foreach ( $post_statuses as $status => $label ) : // phpcs:ignore ?> - <option value="<?php echo esc_attr( $status ); ?>" <?php selected( $status, $post_status ); ?>> - <?php echo esc_html( $label ); ?> - </option> - <?php endforeach; ?> - </select> - </div> - - <div class="dokan-form-group content-half-part"> - <label for="_visibility" class="form-label"><?php esc_html_e( 'Visibility', 'dokan-lite' ); ?></label> - <select name="_visibility" id="_visibility" class="dokan-form-control"> - <?php foreach ( $visibility_options as $name => $label ) : ?> - <option value="<?php echo esc_attr( $name ); ?>" <?php selected( $_visibility, $name ); ?>> - <?php echo esc_html( $label ); ?> - </option> - <?php endforeach; ?> - </select> - </div> - - <div class="dokan-clearfix"></div> - - <div class="dokan-form-group"> - <label for="_purchase_note" class="form-label"><?php esc_html_e( 'Purchase Note', 'dokan-lite' ); ?></label> - <?php dokan_post_input_box( $post_id, '_purchase_note', array( 'placeholder' => __( 'Customer will get this info in their order email', 'dokan-lite' ) ), 'textarea' ); ?> - </div> - - <div class="dokan-form-group"> - <?php - dokan_post_input_box( - $post_id, - '_enable_reviews', - [ - 'value' => 'open' === $post->comment_status ? 'yes' : 'no', - 'label' => __( 'Enable product reviews', 'dokan-lite' ), - ], - 'checkbox' - ); - ?> - </div> - </div> -</div><!-- .dokan-other-options --> diff --git a/templates/products/tmpl-add-product-popup.php b/templates/products/tmpl-add-product-popup.php deleted file mode 100644 index 119db898b7..0000000000 --- a/templates/products/tmpl-add-product-popup.php +++ /dev/null @@ -1,145 +0,0 @@ -<?php - -use WeDevs\Dokan\ProductCategory\Helper; -use WeDevs\Dokan\Walkers\TaxonomyDropdown; - -?> -<script type="text/html" id="tmpl-dokan-add-new-product"> - <div id="dokan-add-new-product-popup" class="white-popup dokan-add-new-product-popup"> - <?php do_action( 'dokan_new_product_before_product_area' ); ?> - <form action="" method="post" id="dokan-add-new-product-form"> - <div class="product-form-container"> - <div class="content-half-part dokan-feat-image-content"> - <div class="dokan-feat-image-upload"> - <?php - $wrap_class = ' dokan-hide'; - $instruction_class = ''; - $feat_image_id = 0; - $can_create_tags = dokan_get_option( 'product_vendors_can_create_tags', 'dokan_selling' ); - $tags_placeholder = 'on' === $can_create_tags ? __( 'Select tags/Add tags', 'dokan-lite' ) : __( 'Select product tags', 'dokan-lite' ); - ?> - <div class="instruction-inside<?php echo esc_attr( $instruction_class ); ?>"> - <input type="hidden" name="feat_image_id" class="dokan-feat-image-id" value="<?php echo esc_attr( $feat_image_id ); ?>"> - - <i class="fas fa-cloud-upload-alt"></i> - <a href="#" class="dokan-feat-image-btn btn btn-sm"><?php esc_html_e( 'Upload a product cover image', 'dokan-lite' ); ?></a> - </div> - - <div class="image-wrap<?php echo esc_attr( $wrap_class ); ?>"> - <a class="close dokan-remove-feat-image">×</a> - <img height="" width="" src="" alt=""> - </div> - </div> - - <div class="dokan-product-gallery"> - <div class="dokan-side-body" id="dokan-product-images"> - <div id="product_images_container"> - <ul class="product_images dokan-clearfix"> - - <li class="add-image add-product-images tips" data-title="<?php esc_attr_e( 'Add gallery image', 'dokan-lite' ); ?>"> - <a href="#" class="add-product-images"><i class="fas fa-plus" aria-hidden="true"></i></a> - </li> - </ul> - <input type="hidden" id="product_image_gallery" name="product_image_gallery" value=""> - </div> - </div> - </div> - - </div> - <div class="content-half-part dokan-product-field-content"> - <div class="dokan-form-group"> - <input type="text" class="dokan-form-control" name="post_title" placeholder="<?php esc_attr_e( 'Product name..', 'dokan-lite' ); ?>"> - </div> - - <div class="dokan-clearfix"> - <div class="dokan-form-group dokan-clearfix dokan-price-container"> - <div class="content-half-part"> - <label for="_regular_price" class="form-label"><?php esc_html_e( 'Price', 'dokan-lite' ); ?></label> - <div class="dokan-input-group"> - <span class="dokan-input-group-addon"><?php echo esc_html( get_woocommerce_currency_symbol() ); ?></span> - <input type="text" class="dokan-product-regular-price wc_input_price dokan-form-control" name="_regular_price" id="_regular_price" placeholder="0.00"> - </div> - </div> - - <div class="content-half-part sale-price"> - <label for="_sale_price" class="form-label"> - <?php esc_html_e( 'Discounted Price', 'dokan-lite' ); ?> - <a href="#" class="sale_schedule"><?php esc_html_e( 'Schedule', 'dokan-lite' ); ?></a> - <a href="#" class="cancel_sale_schedule dokan-hide"><?php esc_html_e( 'Cancel', 'dokan-lite' ); ?></a> - </label> - - <div class="dokan-input-group"> - <span class="dokan-input-group-addon"><?php echo esc_html( get_woocommerce_currency_symbol() ); ?></span> - <input type="text" class="dokan-product-sales-price wc_input_price dokan-form-control" name="_sale_price" placeholder="0.00" id="_sale_price"> - </div> - </div> - </div> - - <div class="dokan-hide sale-schedule-container sale_price_dates_fields dokan-clearfix dokan-form-group"> - <div class="content-half-part from"> - <div class="dokan-input-group"> - <span class="dokan-input-group-addon"><?php esc_html_e( 'From', 'dokan-lite' ); ?></span> - <input type="text" name="_sale_price_dates_from" class="dokan-form-control" value="" maxlength="10" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" placeholder="<?php esc_attr_e( 'YYYY-MM-DD', 'dokan-lite' ); ?>"> - </div> - </div> - - <div class="content-half-part to"> - <div class="dokan-input-group"> - <span class="dokan-input-group-addon"><?php esc_html_e( 'To', 'dokan-lite' ); ?></span> - <input type="text" name="_sale_price_dates_to" class="dokan-form-control" value="" maxlength="10" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" placeholder="<?php esc_attr_e( 'YYYY-MM-DD', 'dokan-lite' ); ?>"> - </div> - </div> - </div> - </div> - </div> - <div class="dokan-clearfix"></div> - <div class="product-full-container"> - - <?php - $data = Helper::get_saved_products_category(); - $data['from'] = 'new_product_popup'; - dokan_get_template_part( 'products/dokan-category-header-ui', '', $data ); - ?> - - <div class="dokan-form-group"> - <label for="product_tag_search" class="form-label"><?php esc_html_e( 'Tags', 'dokan-lite' ); ?></label> - <select multiple="multiple" name="product_tag[]" id="product_tag_search" class="product_tag_search product_tags" data-placeholder="<?php echo esc_attr( $tags_placeholder ); ?>" style="width: 100%;"></select> - </div> - - <?php do_action( 'dokan_new_product_after_product_tags' ); ?> - - <div class="dokan-form-group"> - <textarea name="post_excerpt" id="" class="dokan-form-control" rows="5" placeholder="<?php esc_attr_e( 'Enter some short description about this product...' , 'dokan-lite' ) ?>"></textarea> - </div> - </d> - </div> - <div class="product-container-footer"> - <span class="dokan-show-add-product-error"></span> - <span class="dokan-show-add-product-success"></span> - <span class="dokan-spinner dokan-add-new-product-spinner dokan-hide"></span> - <input type="submit" id="dokan-create-new-product-btn" class="dokan-btn dokan-btn-default" data-btn_id="create_new" value="<?php esc_attr_e( 'Create product', 'dokan-lite' ) ?>"> - <?php - $display_create_and_add_new_button = true; - if ( function_exists( 'dokan_pro' ) && dokan_pro()->module->is_active( 'product_subscription' ) ) { - if ( \DokanPro\Modules\Subscription\Helper::get_vendor_remaining_products( dokan_get_current_user_id() ) === 1 ) { - $display_create_and_add_new_button = false; - } - } - if ( $display_create_and_add_new_button ) : - ?> - <input type="submit" id="dokan-create-and-add-new-product-btn" class="dokan-btn dokan-btn-theme" data-btn_id="create_and_new" value="<?php esc_attr_e( 'Create & add new', 'dokan-lite' ) ?>"> - <?php endif; ?> - </div> - </form> - </div> - <style> - .select2-container--open .select2-dropdown--below { - margin-top: 0px; - } - - .select2-container--open .select2-dropdown--above { - margin-top: 0px; - } - </style> -</script> -<?php do_action( 'dokan_add_product_js_template_end' );?> From a634761056af75fbf67960ffb9d51cd21a8a10ca Mon Sep 17 00:00:00 2001 From: Nurul Umbhiya <zikubd@gmail.com> Date: Thu, 7 Dec 2023 19:24:31 +0600 Subject: [PATCH 3/5] refactor: replace action hook: dokan_product_updated with filter hook dokan_product_edit_meta_data, this will save some executions refactor: removed update_post_meta and get_post_meta references --- .../CatalogMode/Dashboard/ProductBulkEdit.php | 12 +++++++++-- includes/CatalogMode/Dashboard/Products.php | 16 ++++++++------- includes/CatalogMode/Helper.php | 9 +++------ includes/Dashboard/Templates/Products.php | 7 +++---- includes/Product/Hooks.php | 20 ++++++++++--------- 5 files changed, 36 insertions(+), 28 deletions(-) diff --git a/includes/CatalogMode/Dashboard/ProductBulkEdit.php b/includes/CatalogMode/Dashboard/ProductBulkEdit.php index 661e40c000..093694d63c 100644 --- a/includes/CatalogMode/Dashboard/ProductBulkEdit.php +++ b/includes/CatalogMode/Dashboard/ProductBulkEdit.php @@ -72,6 +72,12 @@ public function save_bulk_edit_catalog_mode_data( $status, $product_ids ) { // loop through the products and update the status if ( ! empty( $product_ids ) ) { foreach ( $product_ids as $product_id ) { + // get product object + $product = wc_get_product( $product_id ); + if ( ! $product ) { + continue; + } + // get existing product data $catalog_mode_data = Helper::get_catalog_mode_data_by_product( $product_id ); $count++; @@ -81,7 +87,7 @@ public function save_bulk_edit_catalog_mode_data( $status, $product_ids ) { if ( Helper::hide_add_to_cart_button_option_is_enabled_by_admin() ) { $catalog_mode_data['hide_add_to_cart_button'] = 'on'; } - // if admin didn't enabled hide product price, set this value to off + // if admin didn't enable hide product price, set this value to off if ( ! Helper::hide_product_price_option_is_enabled_by_admin() ) { $catalog_mode_data['hide_product_price'] = 'off'; } @@ -92,8 +98,10 @@ public function save_bulk_edit_catalog_mode_data( $status, $product_ids ) { $catalog_mode_data['hide_product_price'] = 'off'; break; } + // finally save catalog mode data - update_post_meta( $product_id, '_dokan_catalog_mode', $catalog_mode_data ); + $product->add_meta_data( '_dokan_catalog_mode', $catalog_mode_data, true ); + $product->save(); } } wp_safe_redirect( diff --git a/includes/CatalogMode/Dashboard/Products.php b/includes/CatalogMode/Dashboard/Products.php index fd6aec805d..8acef768a2 100644 --- a/includes/CatalogMode/Dashboard/Products.php +++ b/includes/CatalogMode/Dashboard/Products.php @@ -29,7 +29,7 @@ public function __construct() { // render catalog mode section under single product edit page add_action( 'dokan_product_edit_after_options', [ $this, 'render_product_section' ], 99, 2 ); // save catalog mode section data - add_action( 'dokan_product_updated', [ $this, 'save_catalog_mode_data' ], 13 ); + add_filter( 'dokan_product_edit_meta_data', [ $this, 'save_catalog_mode_data' ], 13, 1 ); } /** @@ -71,17 +71,17 @@ public function render_product_section( $product_id, $product ) { * * @since 3.6.4 * - * @param $product_id int + * @param array $meta_data * - * @return void + * @return array */ - public function save_catalog_mode_data( $product_id ) { + public function save_catalog_mode_data( array $meta_data ) { if ( ! isset( $_POST['_dokan_catalog_mode_frontend_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['_dokan_catalog_mode_frontend_nonce'] ), 'dokan_catalog_mode_frontend' ) ) { - return; + return $meta_data; } if ( ! dokan_is_user_seller( dokan_get_current_user_id() ) ) { - return; + return $meta_data; } $catalog_mode_data = [ @@ -94,6 +94,8 @@ public function save_catalog_mode_data( $product_id ) { $catalog_mode_data['hide_product_price'] = 'off'; } - update_post_meta( $product_id, '_dokan_catalog_mode', $catalog_mode_data ); + $meta_data['_dokan_catalog_mode'] = $catalog_mode_data; + + return $meta_data; } } diff --git a/includes/CatalogMode/Helper.php b/includes/CatalogMode/Helper.php index 2009b90bf4..254139804e 100644 --- a/includes/CatalogMode/Helper.php +++ b/includes/CatalogMode/Helper.php @@ -174,15 +174,12 @@ public static function get_catalog_mode_data_by_product( $product ) { // get product id if ( 'variation' === $product->get_type() ) { - $product_id = $product->get_parent_id(); - } else { - $product_id = $product->get_id(); + $product = wc_get_product( $product->get_parent_id() ); } // check for saved values - $catalog_mode_data = get_post_meta( $product_id, '_dokan_catalog_mode', true ); - $catalog_mode_data = is_array( $catalog_mode_data ) && ! empty( $catalog_mode_data ) ? $catalog_mode_data : $defaults; + $catalog_mode_data = $product->get_meta( '_dokan_catalog_mode', true ); - return $catalog_mode_data; + return is_array( $catalog_mode_data ) && ! empty( $catalog_mode_data ) ? $catalog_mode_data : $defaults; } } diff --git a/includes/Dashboard/Templates/Products.php b/includes/Dashboard/Templates/Products.php index 437a7f23c2..e7c7a08a68 100644 --- a/includes/Dashboard/Templates/Products.php +++ b/includes/Dashboard/Templates/Products.php @@ -409,8 +409,7 @@ public function handle_product_update() { $props = []; $meta_data = []; - $current_post_status = $product->get_status(); - $is_new_product = 'auto-draft' === $current_post_status; + $is_new_product = 'auto-draft' === $product->get_status(); foreach ( ProductFormFactory::get_fields() as $field_id => $field ) { if ( $field->is_other_type() ) { @@ -486,8 +485,8 @@ public function handle_product_update() { } // apply hooks before saving product - $props = apply_filters( 'dokan_product_edit_props', $props, $product ); - $meta_data = apply_filters( 'dokan_product_edit_meta_data', $meta_data, $product ); + $props = apply_filters( 'dokan_product_edit_props', $props, $product, $is_new_product ); + $meta_data = apply_filters( 'dokan_product_edit_meta_data', $meta_data, $product, $is_new_product ); $errors = $product->set_props( $props ); if ( is_wp_error( $errors ) ) { diff --git a/includes/Product/Hooks.php b/includes/Product/Hooks.php index 96e74e0275..27a261ed05 100644 --- a/includes/Product/Hooks.php +++ b/includes/Product/Hooks.php @@ -30,7 +30,7 @@ public function __construct() { add_action( 'woocommerce_new_product', [ $this, 'update_category_data_for_new_and_update_product' ], 10, 1 ); add_action( 'woocommerce_update_product', [ $this, 'update_category_data_for_new_and_update_product' ], 10, 1 ); add_filter( 'dokan_post_status', [ $this, 'set_product_status' ], 1, 2 ); - add_action( 'dokan_new_product_added', [ $this, 'set_new_product_email_status' ], 1, 1 ); + add_filter( 'dokan_product_edit_meta_data', [ $this, 'set_new_product_email_status' ], 1, 3 ); // Remove product type filter if pro not exists. add_filter( 'dokan_product_listing_filter_args', [ $this, 'remove_product_type_filter' ] ); @@ -362,18 +362,20 @@ public function set_product_status( $all_statuses, int $product_id ) { * * @since 3.8.2 * - * @param int $product_id + * @param array $meta_data + * @param WC_Product $product + * @param bool $is_new_product * - * @return void + * @return array */ - public function set_new_product_email_status( $product_id ) { - $product = wc_get_product( $product_id ); - if ( ! $product ) { - return; + public function set_new_product_email_status( array $meta_data, WC_Product $product, bool $is_new_product ) { + if ( ! $is_new_product ) { + return $meta_data; } - $product->add_meta_data( '_dokan_new_product_email_sent', 'no', true ); - $product->save(); + $meta_data['_dokan_new_product_email_sent'] = 'no'; + + return $meta_data; } /** From b869ff8c0064818147e834b88c01b386b986cfb3 Mon Sep 17 00:00:00 2001 From: Nurul Umbhiya <zikubd@gmail.com> Date: Thu, 7 Dec 2023 22:29:39 +0600 Subject: [PATCH 4/5] fix: fixed some minor issues --- includes/Dashboard/Templates/Products.php | 42 ++++++++++--------- includes/ProductForm/Field.php | 6 +-- includes/ProductForm/Init.php | 24 +++++++++-- .../products/edit/edit-product-single.php | 3 +- templates/products/edit/sections/general.php | 4 +- .../products/edit/sections/inventory.php | 35 ++++++++-------- 6 files changed, 66 insertions(+), 48 deletions(-) diff --git a/includes/Dashboard/Templates/Products.php b/includes/Dashboard/Templates/Products.php index e7c7a08a68..f1afebdf2b 100644 --- a/includes/Dashboard/Templates/Products.php +++ b/includes/Dashboard/Templates/Products.php @@ -185,8 +185,8 @@ public function load_product_edit_template() { * * @since 1.0.0 * - * @param WP_Post $post - * @param int $post_id + * @param WP_Post $post + * @param int $post_id * * @return void */ @@ -222,8 +222,8 @@ public static function load_download_virtual_template( $post, $post_id ) { * * @since 2.9.2 * - * @param WP_Post $post - * @param int $post_id + * @param WP_Post $post + * @param int $post_id * * @return void */ @@ -253,8 +253,8 @@ public static function load_inventory_template( $post, $post_id ) { * * @since 2.9.2 * - * @param WP_Post $post - * @param int $post_id + * @param WP_Post $post + * @param int $post_id * * @return void */ @@ -289,8 +289,8 @@ public static function load_downloadable_template( $post, $post_id ) { * * @since 2.9.2 * - * @param WP_Post $post - * @param int $post_id + * @param WP_Post $post + * @param int $post_id * * @return void */ @@ -407,9 +407,12 @@ public function handle_product_update() { return; } - $props = []; - $meta_data = []; - $is_new_product = 'auto-draft' === $product->get_status(); + $props = []; + $meta_data = []; + $is_new_product = 'auto-draft' === $product->get_status(); + + // stock quantity + $props[ ProductFormElements::STOCK_QUANTITY ] = null; foreach ( ProductFormFactory::get_fields() as $field_id => $field ) { if ( $field->is_other_type() ) { @@ -421,17 +424,19 @@ public function handle_product_update() { continue; } + $field_name = sanitize_key( $field->get_name() ); + // check if field is required - if ( $field->is_required() && empty( $_POST[ $field->get_name() ] ) ) { - self::$errors[ $field->get_id() ][] = ! empty( $field->get_error_message() ) + if ( $field->is_required() && empty( $_POST[ $field_name ] ) ) { + self::$errors[ $field->get_id() ] = ! empty( $field->get_error_message() ) ? $field->get_error_message() // translators: 1) field title - : sprintf( __( '%1$s is a required field.', 'dokan-lite' ), $field->get_title() ); + : sprintf( __( '<strong>%1$s</strong> is a required field.', 'dokan-lite' ), $field->get_title() ); continue; } // check if the field is present - if ( ! isset( $_POST[ $field->get_name() ] ) ) { + if ( ! isset( $_POST[ $field_name ] ) ) { continue; } @@ -450,14 +455,14 @@ public function handle_product_update() { case ProductFormElements::STOCK_QUANTITY: $original_stock = isset( $_POST['_original_stock'] ) ? wc_stock_amount( wp_unslash( $_POST['_original_stock'] ) ) : false; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - $field_value = $field->sanitize( wp_unslash( $_POST[ $field->get_name() ] ), $original_stock, $product ); //phpcs:ignore + $field_value = $field->sanitize( wp_unslash( $_POST[ $field_name ] ), $original_stock, $product ); //phpcs:ignore break; default: // give a chance to other plugins to sanitize their data - $field_value = apply_filters( 'dokan_product_update_field_value', null, $field, wp_unslash( $_POST[ $field->get_name() ] ), $product ); // phpcs:ignore + $field_value = apply_filters( 'dokan_product_update_field_value', null, $field, wp_unslash( $_POST[ $field_name ] ), $product ); // phpcs:ignore if ( null === $field_value ) { - $field_value = $field->sanitize( wp_unslash( $_POST[ $field->get_name() ] ) ); //phpcs:ignore + $field_value = $field->sanitize( wp_unslash( $_POST[ $field_name ] ) ); //phpcs:ignore } break; } @@ -491,7 +496,6 @@ public function handle_product_update() { $errors = $product->set_props( $props ); if ( is_wp_error( $errors ) ) { self::$errors = array_merge( self::$errors, $errors->get_error_messages() ); - exit; } // return early for validation errors diff --git a/includes/ProductForm/Field.php b/includes/ProductForm/Field.php index d3f65b2452..1e0c0a4e17 100644 --- a/includes/ProductForm/Field.php +++ b/includes/ProductForm/Field.php @@ -80,18 +80,18 @@ public function get_name(): string { } /** - * Set field name, validated by sanitize_title() + * Set field name, validated by sanitize_text_field() * * @since DOKAN_SINCE * * @param string $name * - * @see sanitize_title() + * @see sanitize_text_field() * * @return $this */ public function set_name( string $name ): Field { - $this->data['name'] = sanitize_title( $name ); + $this->data['name'] = sanitize_text_field( $name ); return $this; } diff --git a/includes/ProductForm/Init.php b/includes/ProductForm/Init.php index 6c3c977bc9..a28e171a58 100644 --- a/includes/ProductForm/Init.php +++ b/includes/ProductForm/Init.php @@ -387,7 +387,13 @@ public function init_inventory_fields() { 'min' => 0, 'step' => 'any', ], - 'sanitize_callback' => 'wc_stock_amount', + 'sanitize_callback' => function ( $value ) { + if ( '' !== $value ) { + return wc_stock_amount( $value ); + } + + return ''; + }, ] ); @@ -403,7 +409,7 @@ public function init_inventory_fields() { $section->add_field( Elements::SOLD_INDIVIDUALLY, [ - 'title' => __( 'Sold Individually', 'dokan-lite' ), + 'title' => __( 'Allow only one quantity of this product to be bought in a single order', 'dokan-lite' ), 'description' => __( 'Check to let customers to purchase only 1 item in a single order. This is particularly useful for items that have limited quantity, for example art or handmade goods.', 'dokan-lite' ), 'field_type' => 'checkbox', 'name' => '_sold_individually', @@ -457,7 +463,12 @@ public function init_downloadable_fields() { 'min' => 0, 'step' => 1, ], - 'sanitize_callback' => 'absint', + 'sanitize_callback' => function ( $value ) { + if ( '' !== $value ) { + return absint( $value ); + } + return ''; + }, ] ); @@ -472,7 +483,12 @@ public function init_downloadable_fields() { 'min' => 0, 'step' => 1, ], - 'sanitize_callback' => 'absint', + 'sanitize_callback' => function ( $value ) { + if ( '' !== $value ) { + return absint( $value ); + } + return ''; + }, ] ); } diff --git a/templates/products/edit/edit-product-single.php b/templates/products/edit/edit-product-single.php index bea7d138c8..3fa1855678 100755 --- a/templates/products/edit/edit-product-single.php +++ b/templates/products/edit/edit-product-single.php @@ -135,9 +135,8 @@ <?php if ( dokan()->dashboard->templates->products->has_errors() ) : ?> <div class="dokan-alert dokan-alert-danger"> <a class="dokan-close" data-dismiss="alert">×</a> - <?php foreach ( dokan()->dashboard->templates->products->get_errors() as $error ) : ?> - <strong><?php esc_html_e( 'Error!', 'dokan-lite' ); ?></strong> <?php echo esc_html( $error ); ?>.<br> + <strong><?php esc_html_e( 'Error!', 'dokan-lite' ); ?></strong> <?php echo wp_kses_post( $error ); ?>.<br> <?php endforeach; ?> </div> <?php endif; ?> diff --git a/templates/products/edit/sections/general.php b/templates/products/edit/sections/general.php index a2062a5be1..140445cbaa 100644 --- a/templates/products/edit/sections/general.php +++ b/templates/products/edit/sections/general.php @@ -212,8 +212,8 @@ class="dokan-form-control dokan-end-date" $terms = $product->get_tag_ids(); ?> <div class="dokan-form-group"> - <label for="product_tag_edit" class="form-label"><?php echo $tags->get_title(); ?></label> - <select multiple="multiple" id="product_tag_edit" name="<?php echo esc_attr( $tags->get_name() ); ?>" class="product_tag_search dokan-form-control" data-placeholder="<?php echo esc_attr( $tags->get_placeholder() ); ?>"> + <label for="<?php echo esc_attr( $tags->get_name() ); ?>" class="form-label"><?php echo $tags->get_title(); ?></label> + <select multiple="multiple" id="<?php echo esc_attr( $tags->get_name() ); ?>" name="<?php echo esc_attr( $tags->get_name() ); ?>" class="product_tag_search dokan-form-control" data-placeholder="<?php echo esc_attr( $tags->get_placeholder() ); ?>"> <?php if ( ! empty( $terms ) ) : ?> <?php foreach ( $terms as $tax_term ) : diff --git a/templates/products/edit/sections/inventory.php b/templates/products/edit/sections/inventory.php index 7b76927d72..71500dd36e 100644 --- a/templates/products/edit/sections/inventory.php +++ b/templates/products/edit/sections/inventory.php @@ -155,13 +155,13 @@ class="fas fa-question-circle tips" <?php dokan_post_input_box( $product->get_id(), - $stock_quantity->get_name(), + $low_stock_amount->get_name(), [ 'value' => $product->get_low_stock_amount( 'edit' ), 'class' => 'dokan-form-control', - 'placeholder' => $stock_quantity->get_placeholder(), - 'min' => $stock_quantity->get_additional_properties( 'min' ), - 'step' => $stock_quantity->get_additional_properties( 'step' ), + 'placeholder' => $low_stock_amount->get_placeholder(), + 'min' => $low_stock_amount->get_additional_properties( 'min' ), + 'step' => $low_stock_amount->get_additional_properties( 'step' ), ], 'number' ); @@ -171,7 +171,7 @@ class="fas fa-question-circle tips" <?php $backorders = $section->get_field( Elements::BACKORDERS ); - if ( is_wp_error( $backorders ) && $backorders->is_visible() ) : + if ( ! is_wp_error( $backorders ) && $backorders->is_visible() ) : ?> <div class="content-half-part last-child"> <label for="<?php echo esc_attr( $backorders->get_name() ); ?>" class="form-label"> @@ -205,19 +205,18 @@ class="fas fa-question-circle tips" if ( ! is_wp_error( $sold_individually ) && $sold_individually->is_visible() ) : ?> <div class="dokan-form-group hide_if_grouped hide_if_external"> - <label class="" for="<?php echo esc_attr( $sold_individually->get_name() ); ?>"> - <input - name="<?php echo esc_attr( $sold_individually->get_name() ); ?>" - id="<?php echo esc_attr( $sold_individually->get_name() ); ?>" - value="yes" - type="checkbox" <?php checked( $product->get_sold_individually( 'edit' ), true ); ?>> - <i - class="fas fa-question-circle tips" - aria-hidden="true" - data-title="<?php echo esc_attr( $sold_individually->get_description() ); ?>"> - </i> - <?php esc_html_e( 'Allow only one quantity of this product to be bought in a single order', 'dokan-lite' ); ?> - </label> + <?php + dokan_post_input_box( + $product->get_id(), + $sold_individually->get_name(), + [ + 'value' => $product->get_sold_individually() ? 'yes' : 'no', + 'label' => $sold_individually->get_title(), + 'desc' => $sold_individually->get_description(), + ], + 'checkbox' + ); + ?> </div> <?php endif; ?> From fc5ff41ce842105205177222065b5d9774e09ccc Mon Sep 17 00:00:00 2001 From: Nurul Umbhiya <zikubd@gmail.com> Date: Fri, 8 Dec 2023 15:00:31 +0600 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=94=92=20chore(Product):=20add=20defi?= =?UTF-8?q?ned(=20'ABSPATH'=20)=20||=20exit;=20to=20Hooks.php=20and=20Mana?= =?UTF-8?q?ger.php=20to=20prevent=20direct=20access=20to=20the=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- includes/Product/Hooks.php | 2 ++ includes/Product/Manager.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/includes/Product/Hooks.php b/includes/Product/Hooks.php index 27a261ed05..3ea9e40882 100644 --- a/includes/Product/Hooks.php +++ b/includes/Product/Hooks.php @@ -5,6 +5,8 @@ use WeDevs\Dokan\ProductCategory\Helper; use WC_Product; +defined( 'ABSPATH' ) || exit; + /** * Admin Hooks * diff --git a/includes/Product/Manager.php b/includes/Product/Manager.php index 623acf4344..bb5e844d5c 100644 --- a/includes/Product/Manager.php +++ b/includes/Product/Manager.php @@ -10,6 +10,8 @@ use WP_Error; use WeDevs\Dokan\ProductForm\Elements as FormElements; +defined( 'ABSPATH' ) || exit; + /** * Product manager Class *