diff --git a/.gitignore b/.gitignore index fbdaef1b15..ec3818bb0b 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ assets/js/style.js assets/js/vue-admin.js.LICENSE.txt assets/js/vue-bootstrap.js.LICENSE.txt assets/js/vue-frontend.js.LICENSE.txt +assets/js/dokan-tailwind.js diff --git a/assets/src/js/product-editor.js b/assets/src/js/product-editor.js index 9c2a5886f9..d60aa411bf 100755 --- a/assets/src/js/product-editor.js +++ b/assets/src/js/product-editor.js @@ -6,6 +6,15 @@ var $image_gallery_ids = $('#product_image_gallery'); var $product_images = $('#product_images_container ul.product_images'); + var validatorError = function( error, element ) { + var form_group = $( element ).closest( '.dokan-form-group' ); + form_group.addClass( 'has-error' ).append( error ); + }; + + var validatorSuccess = function( label, element ) { + $( element ).closest( '.dokan-form-group' ).removeClass( 'has-error' ); + }; + var Dokan_Editor = { modal: false, @@ -13,6 +22,8 @@ * Constructor function */ init: function() { + var self = this; + this.productFormValidation(self); product_type = 'simple'; @@ -49,27 +60,9 @@ return false; }); - $( 'body' ).on( 'submit', 'form.dokan-product-edit-form', this.inputValidate ); - - // For new desing in product page - $( '.dokan-product-listing' ).on( 'click', 'a.dokan-add-new-product', this.addProductPopup ); - this.loadSelect2(); this.bindProductTagDropdown(); - this.attribute.sortable(); this.checkProductPostboxToggle(); - $( '.product-edit-container .dokan-product-attribute-wrapper' ).on( 'click', 'a.dokan-product-toggle-attribute, .dokan-product-attribute-heading', this.attribute.toggleAttribute ); - $( '.product-edit-container .dokan-product-attribute-wrapper' ).on( 'click', 'a.add_new_attribute', this.attribute.addNewAttribute ); - $( '.product-edit-container .dokan-product-attribute-wrapper' ).on( 'keyup', 'input.dokan-product-attribute-name', this.attribute.dynamicAttrNameChange ); - $( '.dokan-product-attribute-wrapper ul.dokan-attribute-option-list' ).on( 'click', 'button.dokan-select-all-attributes', this.attribute.selectAllAttr ); - $( '.dokan-product-attribute-wrapper ul.dokan-attribute-option-list' ).on( 'click', 'button.dokan-select-no-attributes', this.attribute.selectNoneAttr ); - $( '.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(); - this.setCorrectProductId(); $( 'body' ).trigger( 'dokan-product-editor-loaded', this ); @@ -87,6 +80,38 @@ Dokan_Editor.dokan_show_earning_suggestion( Dokan_Editor.earning_suggestion_callbak ); } ); } + + this.renderCustomSectionBasedOnCategory(); + + wp.hooks.addAction( + 'dokan_selected_multistep_category', + 'render_custom_section_based_on_chosen_category', + Dokan_Editor.renderCustomSectionBasedOnCategory + ); + }, + + productFormValidation: function( self ) { + window.dokanProductformValidator = $( "form.dokan-product-edit-form" ).validate( { + errorElement: 'span', + errorClass: 'error validation-error', + errorPlacement: validatorError, + success: validatorSuccess, + } ); + }, + + renderCustomSectionBasedOnCategory: function( categoryId ) { + let productCatSelectorVal = $( '.dokan_chosen_product_cat' ).val(); + let productCustomSections = $( '.dokan-product-custom-section' ); + + productCustomSections.each( function() { + let sectionCatId = $( this ).data( 'category-id' ); + + if ( sectionCatId == productCatSelectorVal || 0 == sectionCatId ) { + $( this ).removeClass( 'dokan-hide' ); + } else { + $( this ).addClass( 'dokan-hide' ); + } + } ); }, setCorrectProductId : function () { @@ -314,390 +339,6 @@ }); }, - addProductPopup: function (e) { - e.preventDefault(); - Dokan_Editor.openProductPopup(); - }, - - openProductPopup: function() { - const productTemplate = wp.template( 'dokan-add-new-product' ), - modalElem = $( '#dokan-add-product-popup' ); - Dokan_Editor.modal = modalElem.iziModal( { - headerColor : dokan.modal_header_color, - overlayColor: 'rgba(0, 0, 0, 0.8)', - width : 690, - top : 32, - onOpening : () => { - Dokan_Editor.reRenderPopupElements(); - }, - onClosed: () => { - product_gallery_frame = undefined; - product_featured_frame = undefined; - $( '#dokan-add-new-product-popup input[name="_sale_price_dates_from"], #dokan-add-new-product-popup input[name="_sale_price_dates_to"]' ).datepicker( 'destroy' ); - }, - } ); - Dokan_Editor.modal.iziModal( 'setContent', productTemplate().trim() ); - Dokan_Editor.modal.iziModal( 'open' ); - }, - - reRenderPopupElements: function() { - Dokan_Editor.loadSelect2(); - Dokan_Editor.bindProductTagDropdown(); - - $( '#dokan-add-new-product-popup .sale_price_dates_fields input' ).daterangepicker({ - singleDatePicker: true, - showDropdowns: false, - autoApply: true, - parentEl: '#dokan-add-new-product-popup', - opens: 'left', - autoUpdateInput : false, - } ).on( 'apply.daterangepicker', function( ev, picker ) { - $( this ).val( picker.startDate.format( 'YYYY-MM-DD' ) ); - } ); - - $( '.tips' ).tooltip(); - - Dokan_Editor.gallery.sortable(); - $( '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) { - e.preventDefault(); - - var self = $(this), - list = self.closest('li'), - item = list.find('.dokan-product-attribute-item'); - - if ( $(item).hasClass('dokan-hide') ) { - self.closest('.dokan-product-attribute-heading').css({ borderBottom: '1px solid #e3e3e3' }); - $(item).slideDown( 200, function() { - self.find('i.fa').removeClass('fa-flip-horizointal').addClass('fa-flip-vertical'); - $(this).removeClass('dokan-hide'); - if ( ! $(e.target).hasClass( 'dokan-product-attribute-heading' ) ) { - $(e.target).closest('a').css('top', '12px'); - } else if ( $(e.target).hasClass( 'dokan-product-attribute-heading' ) ) { - self.find( 'a.dokan-product-toggle-attribute' ).css('top', '12px'); - } - }); - } else { - $(item).slideUp( 200, function() { - $(this).addClass('dokan-hide'); - self.find('i.fa').removeClass('fa-flip-vertical').addClass('fa-flip-horizointal'); - if ( ! $(e.target).hasClass('dokan-product-attribute-heading') ) { - $(e.target).closest('a').css('top', '7px'); - } else if ( $(e.target).hasClass( 'dokan-product-attribute-heading' ) ) { - self.find('a.dokan-product-toggle-attribute').css('top', '7px'); - } - self.closest('.dokan-product-attribute-heading').css({ borderBottom: 'none' }); - - }) - } - return false; - }, - - sortable: function() { - $('.dokan-product-attribute-wrapper ul').sortable({ - items: 'li.product-attribute-list', - cursor: 'move', - scrollSensitivity:40, - forcePlaceholderSize: true, - forceHelperSize: false, - helper: 'clone', - opacity: 0.65, - placeholder: 'dokan-sortable-placeholder', - start:function(event,ui){ - ui.item.css('background-color','#f6f6f6'); - }, - stop:function(event,ui){ - ui.item.removeAttr('style'); - }, - update: function(event, ui) { - var attachment_ids = ''; - Dokan_Editor.attribute.reArrangeAttribute(); - } - }); - }, - - dynamicAttrNameChange: function(e) { - e.preventDefault(); - var self = $(this), - value = self.val(); - - if ( value == '' ) { - self.closest( 'li' ).find( 'strong' ).html( dokan.i18n_attribute_label ); - } else { - self.closest( 'li' ).find( 'strong' ).html( value ); - } - }, - - selectAllAttr: function(e) { - e.preventDefault(); - $( this ).closest( 'li.product-attribute-list' ).find( 'select.dokan_attribute_values option' ).attr( 'selected', true ); - $( this ).closest( 'li.product-attribute-list' ).find( 'select.dokan_attribute_values' ).trigger( 'change' ); - return false; - }, - - selectNoneAttr: function(e) { - e.preventDefault(); - $( this ).closest( 'li.product-attribute-list' ).find( 'select.dokan_attribute_values option' ).attr( 'selected', false ); - $( this ).closest( 'li.product-attribute-list' ).find( 'select.dokan_attribute_values' ).trigger( 'change' ); - return false; - }, - - reArrangeAttribute: function() { - var attributeWrapper = $('.dokan-product-attribute-wrapper').find('ul.dokan-attribute-option-list'); - attributeWrapper.find( 'li.product-attribute-list' ).css( 'cursor', 'default' ).each(function( i ) { - $(this).find('.attribute_position').val(i); - }); - }, - - addNewExtraAttr: async function(e) { - e.preventDefault(); - - var $wrapper = $(this).closest( 'li.product-attribute-list' ); - var attribute = $wrapper.data( 'taxonomy' ); - let result = await dokan_sweetalert( dokan.new_attribute_prompt, { - action : 'prompt', - input :'text' - } ); - var new_attribute_name = result.value; - - if ( new_attribute_name ) { - - var data = { - action: 'dokan_add_new_attribute', - taxonomy: attribute, - term: new_attribute_name, - _wpnonce : dokan.nonce - }; - - $.post( dokan.ajaxurl, data, function( response ) { - if ( response.error ) { - dokan_sweetalert( response.error, { - action : 'alert', - icon : 'warning' - } ); - } else if ( response.slug ) { - $wrapper.find( 'select.dokan_attribute_values' ).append( '' ); - $wrapper.find( 'select.dokan_attribute_values' ).trigger( 'change' ); - } - - }); - } - }, - - addNewAttribute: function(e) { - e.preventDefault(); - - var self = $(this), - attrWrap = self.closest('.dokan-attribute-type').find('select#predefined_attribute'), - attribute = attrWrap.val(), - size = $( 'ul.dokan-attribute-option-list .product-attribute-list' ).length; - - - var data = { - action : 'dokan_get_pre_attribute', - taxonomy : attribute, - i : size, - _wpnonce : dokan.nonce - }; - - self.closest('.dokan-attribute-type').find('span.dokan-attribute-spinner').removeClass('dokan-hide'); - - $.post( dokan.ajaxurl, data, function( resp ) { - if ( resp.success ) { - var attributeWrapper = $('.dokan-product-attribute-wrapper').find('ul.dokan-attribute-option-list'); - $html = $.parseHTML(resp.data); - $($html).find('.dokan-product-attribute-item').removeClass('dokan-hide'); - $($html).find('i.fa.fa-sort-desc').removeClass('fa-flip-horizointal').addClass('fa-flip-vertical'); - $($html).find('a.dokan-product-toggle-attribute').css('top','12px'); - $($html).find('.dokan-product-attribute-heading').css({ borderBottom: '1px solid #e3e3e3' }); - - attributeWrapper.append( $html ); - Dokan_Editor.loadSelect2(); - Dokan_Editor.bindProductTagDropdown(); - Dokan_Editor.attribute.reArrangeAttribute(); - - if ( "variable" !== $( 'select#product_type' ).val() ) { - let labels = $( 'div.dokan-product-attribute-wrapper label.show_if_variable' ); - - for( let label of labels ) { - let checkBox = $( label ).find('input[type="checkbox"]'); - - if ( checkBox.length > 0 && checkBox[0].getAttribute('name')?.startsWith('attribute_variation[') ) { - $( label ).hide(); - } - } - } - } - - self.closest('.dokan-attribute-type').find('span.dokan-attribute-spinner').addClass('dokan-hide'); - - if ( attribute ) { - attrWrap.find( 'option[value="' + attribute + '"]' ).attr( 'disabled','disabled' ); - attrWrap.val( '' ); - } - }) - .done(function() { - $( 'select#product_type' ).trigger('change'); - }); - }, - - removeAttribute: async function(evt) { - evt.stopPropagation(); - evt.preventDefault(); - - const isRemoved = await dokan_sweetalert( dokan.remove_attribute, { - action :'confirm', - icon :'warning' - } ); - - if ( 'undefined' !== isRemoved && isRemoved.isConfirmed ) { - var $parent = $( this ).closest('li.product-attribute-list'); - - $parent.fadeOut( 300, function() { - if ( $parent.is( '.taxonomy' ) ) { - $parent.find( 'select, input[type=text]' ).val( '' ); - $( 'select.dokan_attribute_taxonomy' ).find( 'option[value="' + $parent.data( 'taxonomy' ) + '"]' ).removeAttr( 'disabled' ); - } else { - $parent.find( 'select, input[type=text]' ).val( '' ); - $parent.hide(); - } - - Dokan_Editor.attribute.reArrangeAttribute(); - }); - } - - return false; - }, - - saveAttribute: function(e) { - e.preventDefault(); - - var self = $(this), - data = { - post_id: $('#dokan-edit-product-id').val(), - data: $( 'ul.dokan-attribute-option-list' ).find( 'input, select, textarea' ).serialize(), - action: 'dokan_save_attributes' - }; - - $( '.dokan-product-attribute-wrapper' ).block({ - message: null, - fadeIn: 50, - fadeOut: 1000, - overlayCSS: { - background: '#fff', - opacity: 0.6 - } - }); - - $.post( dokan.ajaxurl, data, function( resp ) { - // Load variations panel. - $( '#dokan-variable-product-options' ).load( window.location.toString() + ' #dokan-variable-product-options-inner', function() { - $( '#dokan-variable-product-options' ).trigger( 'reload' ); - $( 'select#product_type' ).trigger('change'); - $( '.dokan-product-attribute-wrapper' ).unblock(); - }); - }); - - }, - - disbalePredefinedAttribute: function() { - $( 'ul.dokan-attribute-option-list li.product-attribute-list' ).each( function( index, el ) { - if ( $( el ).css( 'display' ) !== 'none' && $( el ).is( '.taxonomy' ) ) { - $( 'select#predefined_attribute' ).find( 'option[value="' + $( el ).data( 'taxonomy' ) + '"]' ).attr( 'disabled', 'disabled' ); - } - }); - } - }, - - inputValidate: function( e ) { - e.preventDefault(); - - if ( $( '#post_title' ).val().trim() == '' ) { - $( '#post_title' ).focus(); - $( 'div.dokan-product-title-alert' ).removeClass('dokan-hide'); - return; - }else{ - $( 'div.dokan-product-title-alert' ).hide(); - } - - if ( $( 'select.product_cat' ).val() == -1 ) { - $( 'select.product_cat' ).focus(); - $( 'div.dokan-product-cat-alert' ).removeClass('dokan-hide'); - return; - }else{ - $( 'div.dokan-product-cat-alert' ).hide(); - } - $( 'input[type=submit]' ).attr( 'disabled', 'disabled' ); - this.submit(); - }, - downloadable: function() { if ( $(this).prop('checked') ) { $(this).closest('aside').find('.dokan-side-body').removeClass('dokan-hide'); @@ -1097,6 +738,8 @@ $wrap.find( '.cancel_sale_schedule' ).show(); $wrap.find( '.sale_price_dates_fields' ).show(); + $( '#is_discount_schedule_enabled' ).val( 1 ); + return false; }); @@ -1108,6 +751,8 @@ $wrap.find( '.sale_price_dates_fields' ).hide(); $wrap.find( '.sale_price_dates_fields' ).find( 'input' ).val(''); + $( '#is_discount_schedule_enabled' ).val( 0 ); + return false; }); diff --git a/assets/src/less/dashboard.less b/assets/src/less/dashboard.less index 102c4d67ff..6746356e90 100644 --- a/assets/src/less/dashboard.less +++ b/assets/src/less/dashboard.less @@ -503,6 +503,14 @@ div.media-sidebar a.edit-attachment{ margin-bottom: 10px; } } + + .product-edit-container { + span { + &.validation-error { + display: block; + } + } + } } div.chart-container > div.chart-placeholder > div.legend { diff --git a/assets/src/less/products.less b/assets/src/less/products.less index 21c945959b..aa7ce9555c 100644 --- a/assets/src/less/products.less +++ b/assets/src/less/products.less @@ -281,6 +281,10 @@ } .product-edit-new-container { + .dokan-hide { + display: none; + } + .dokan-edit-row { &.dokan-other-options { margin-bottom: 20px; @@ -465,14 +469,36 @@ .dokan-shipping-container { .dokan-shipping-dimention-options { - margin-bottom: 20px; - input { + margin-bottom: 5px; + + .dokan-form-group { width: 23.3%; float: left; margin-right: 2%; + &#dokan-form-group-height { + margin-right: 0; + } + + input { + &.dokan-form-control { + width: 100%; + margin-right: 0; + margin-bottom: 0; + } + } + } + + input { + &.dokan-form-control { + float: left; + width: 23.3%; + margin-right: 2%; + margin-bottom: 15px; + } + &#_height { - margin-right: 0px; + margin-right: 0; } } } @@ -972,7 +998,7 @@ .instruction-inside { text-align: center; - padding-top: 40px; + padding-top: 80px; color: #aaa; } diff --git a/dokan-class.php b/dokan-class.php index 167241bd02..bde632fee9 100755 --- a/dokan-class.php +++ b/dokan-class.php @@ -14,6 +14,7 @@ * @property WeDevs\Dokan\BackgroundProcess\Manager $bg_process Instance of WeDevs\Dokan\BackgroundProcess\Manager class * @property WeDevs\Dokan\Withdraw\Manager $withdraw Instance of WeDevs\Dokan\Withdraw\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 * @property WeDevs\Dokan\Registration $registration Instance of WeDevs\Dokan\Registration class */ final class WeDevs_Dokan { @@ -214,6 +215,7 @@ public function define_constants() { defined( 'DOKAN_PLUGIN_VERSION' ) || define( 'DOKAN_PLUGIN_VERSION', $this->version ); defined( 'DOKAN_DIR' ) || define( 'DOKAN_DIR', __DIR__ ); defined( 'DOKAN_INC_DIR' ) || define( 'DOKAN_INC_DIR', __DIR__ . '/includes' ); + defined( 'DOKAN_TEMPLATE_DIR' ) || define( 'DOKAN_TEMPLATE_DIR', __DIR__ . '/templates' ); defined( 'DOKAN_LIB_DIR' ) || define( 'DOKAN_LIB_DIR', __DIR__ . '/lib' ); defined( 'DOKAN_PLUGIN_ASSEST' ) || define( 'DOKAN_PLUGIN_ASSEST', plugins_url( 'assets', DOKAN_FILE ) ); diff --git a/includes/Admin/Settings.php b/includes/Admin/Settings.php index 5ddaef5b95..2d37a2cb8d 100644 --- a/includes/Admin/Settings.php +++ b/includes/Admin/Settings.php @@ -634,25 +634,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 b7f93a7c52..502828cf22 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 2279fd01ca..4d3e210000 100644 --- a/includes/Assets.php +++ b/includes/Assets.php @@ -6,6 +6,7 @@ use WeDevs\Dokan\ReverseWithdrawal\SettingsHelper; use WeDevs\Dokan\ProductCategory\Helper as CategoryHelper; use WeDevs\Dokan\Utilities\OrderUtil; +use WeDevs\Dokan\ProductForm\Factory as ProductFormFactory; class Assets { @@ -527,7 +528,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, ], @@ -572,6 +573,15 @@ public function get_scripts() { return $scripts; } + public function get_product_fields() { + $temp_fields = []; + foreach ( ProductFormFactory::get_fields() as $field_id => $field ) { + $temp_fields[ $field_id ] = $field->toArray(); + } + + return json_encode( $temp_fields ); + } + /** * Enqueue front-end scripts */ @@ -617,6 +627,7 @@ public function enqueue_front_scripts() { 'currency_format' => esc_attr( str_replace( [ '%1$s', '%2$s' ], [ '%s', '%v' ], get_woocommerce_price_format() ) ), // For accounting JS 'round_at_subtotal' => get_option( 'woocommerce_tax_round_at_subtotal', 'no' ), 'product_types' => apply_filters( 'dokan_product_types', [ 'simple' ] ), + 'product_form_fields' => $this->get_product_fields(), 'loading_img' => DOKAN_PLUGIN_ASSEST . '/images/loading.gif', 'store_product_search_nonce' => wp_create_nonce( 'dokan_store_product_search_nonce' ), 'i18n_download_permission' => __( 'Are you sure you want to revoke access to this download?', 'dokan-lite' ), @@ -792,11 +803,9 @@ public function load_dokan_global_scripts() { ], ], 'sweetalert_local' => [ - 'cancelButtonText' => __( 'Cancel', 'dokan-lite' ), - 'closeButtonText' => __( 'Close', 'dokan-lite' ), - 'confirmButtonText' => __( 'OK', 'dokan-lite' ), - 'denyButtonText' => __( 'No', 'dokan-lite' ), - 'closeButtonAriaLabel' => __( 'Close this dialog', 'dokan-lite' ), + 'confirmButtonText' => __( 'OK', 'dokan-lite' ), + 'denyButtonText' => __( 'No', 'dokan-lite' ), + 'cancelButtonText' => __( 'Cancel', 'dokan-lite' ), ], ] ); @@ -988,43 +997,21 @@ public function conditional_localized_args( $default_args ) { $has_flex_height = ! empty( $general_settings['store_banner_flex_height'] ) ? $general_settings['store_banner_flex_height'] : true; $custom_args = [ - 'i18n_choose_featured_img' => __( 'Upload featured image', 'dokan-lite' ), - 'i18n_choose_file' => __( 'Choose a file', 'dokan-lite' ), - 'i18n_choose_gallery' => __( 'Add Images to Product Gallery', 'dokan-lite' ), - 'i18n_choose_featured_img_btn_text' => __( 'Set featured image', 'dokan-lite' ), - 'i18n_choose_file_btn_text' => __( 'Insert file URL', 'dokan-lite' ), - 'i18n_choose_gallery_btn_text' => __( 'Add to gallery', 'dokan-lite' ), - 'duplicates_attribute_messg' => __( 'Sorry, this attribute option already exists, Try a different one.', 'dokan-lite' ), - 'variation_unset_warning' => __( 'Warning! This product will not have any variations if this option is not checked.', 'dokan-lite' ), - 'new_attribute_prompt' => __( 'Enter a name for the new attribute term:', 'dokan-lite' ), - 'remove_attribute' => __( 'Remove this attribute?', 'dokan-lite' ), - 'dokan_placeholder_img_src' => wc_placeholder_img_src(), - 'add_variation_nonce' => wp_create_nonce( 'add-variation' ), - 'link_variation_nonce' => wp_create_nonce( 'link-variations' ), - 'delete_variations_nonce' => wp_create_nonce( 'delete-variations' ), - 'load_variations_nonce' => wp_create_nonce( 'load-variations' ), - 'save_variations_nonce' => wp_create_nonce( 'save-variations' ), - 'bulk_edit_variations_nonce' => wp_create_nonce( 'bulk-edit-variations' ), - /* translators: %d: max linked variation. */ - 'i18n_link_all_variations' => esc_js( sprintf( __( 'Are you sure you want to link all variations? This will create a new variation for each and every possible combination of variation attributes (max %d per run).', 'dokan-lite' ), defined( 'WC_MAX_LINKED_VARIATIONS' ) ? WC_MAX_LINKED_VARIATIONS : 50 ) ), - 'i18n_enter_a_value' => esc_js( __( 'Enter a value', 'dokan-lite' ) ), - 'i18n_enter_menu_order' => esc_js( __( 'Variation menu order (determines position in the list of variations)', 'dokan-lite' ) ), - 'i18n_enter_a_value_fixed_or_percent' => esc_js( __( 'Enter a value (fixed or %)', 'dokan-lite' ) ), - 'i18n_delete_all_variations' => esc_js( __( 'Are you sure you want to delete all variations? This cannot be undone.', 'dokan-lite' ) ), - 'i18n_last_warning' => esc_js( __( 'Last warning, are you sure?', 'dokan-lite' ) ), - 'i18n_choose_image' => esc_js( __( 'Choose an image', 'dokan-lite' ) ), - 'i18n_set_image' => esc_js( __( 'Set variation image', 'dokan-lite' ) ), - 'i18n_variation_added' => esc_js( __( 'variation added', 'dokan-lite' ) ), - 'i18n_variations_added' => esc_js( __( 'variations added', 'dokan-lite' ) ), - 'i18n_no_variations_added' => esc_js( __( 'No variations added', 'dokan-lite' ) ), - 'i18n_remove_variation' => esc_js( __( 'Are you sure you want to remove this variation?', 'dokan-lite' ) ), - 'i18n_scheduled_sale_start' => esc_js( __( 'Sale start date (YYYY-MM-DD format or leave blank)', 'dokan-lite' ) ), - 'i18n_scheduled_sale_end' => esc_js( __( 'Sale end date (YYYY-MM-DD format or leave blank)', 'dokan-lite' ) ), - 'i18n_edited_variations' => esc_js( __( 'Save changes before changing page?', 'dokan-lite' ) ), - 'i18n_variation_count_single' => esc_js( __( '%qty% variation', 'dokan-lite' ) ), - 'i18n_variation_count_plural' => esc_js( __( '%qty% variations', 'dokan-lite' ) ), - 'i18n_no_result_found' => esc_js( __( 'No Result Found', 'dokan-lite' ) ), - 'i18n_sales_price_error' => esc_js( __( 'Please insert value less than the regular price!', 'dokan-lite' ) ), + 'i18n_choose_featured_img' => __( 'Upload featured image', 'dokan-lite' ), + 'i18n_choose_file' => __( 'Choose a file', 'dokan-lite' ), + 'i18n_choose_gallery' => __( 'Add Images to Product Gallery', 'dokan-lite' ), + 'i18n_choose_featured_img_btn_text' => __( 'Set featured image', 'dokan-lite' ), + 'i18n_choose_file_btn_text' => __( 'Insert file URL', 'dokan-lite' ), + 'i18n_choose_gallery_btn_text' => __( 'Add to gallery', 'dokan-lite' ), + 'duplicates_attribute_messg' => __( 'Sorry, this attribute option already exists, Try a different one.', 'dokan-lite' ), + 'variation_unset_warning' => __( 'Warning! This product will not have any variations if this option is not checked.', 'dokan-lite' ), + 'new_attribute_prompt' => __( 'Enter a name for the new attribute term:', 'dokan-lite' ), + 'remove_attribute' => __( 'Remove this attribute?', 'dokan-lite' ), + 'dokan_placeholder_img_src' => wc_placeholder_img_src(), + 'i18n_choose_image' => esc_js( __( 'Choose an image', 'dokan-lite' ) ), + 'i18n_set_image' => esc_js( __( 'Set variation image', 'dokan-lite' ) ), + 'i18n_no_result_found' => esc_js( __( 'No Result Found', 'dokan-lite' ) ), + 'i18n_sales_price_error' => esc_js( __( 'Please insert value less than the regular price!', 'dokan-lite' ) ), /* translators: %s: decimal */ 'i18n_decimal_error' => sprintf( __( 'Please enter with one decimal point (%s) without thousand separators.', 'dokan-lite' ), $decimal ), /* translators: %s: price decimal separator */ @@ -1071,6 +1058,30 @@ public function conditional_localized_args( $default_args ) { 'update_settings' => __( 'Update Settings', 'dokan-lite' ), 'search_downloadable_products_nonce' => wp_create_nonce( 'search-downloadable-products' ), 'search_downloadable_products_placeholder' => __( 'Please enter 3 or more characters', 'dokan-lite' ), + // Todo: Variation related i18n strings will be moved to the Dokan Pro Assets class after releasing product form manager module. + // Variation related i18n, file: product-variation.js + 'add_variation_nonce' => wp_create_nonce( 'add-variation' ), + 'link_variation_nonce' => wp_create_nonce( 'link-variations' ), + 'delete_variations_nonce' => wp_create_nonce( 'delete-variations' ), + 'load_variations_nonce' => wp_create_nonce( 'load-variations' ), + 'save_variations_nonce' => wp_create_nonce( 'save-variations' ), + 'bulk_edit_variations_nonce' => wp_create_nonce( 'bulk-edit-variations' ), + /* translators: %d: max linked variation. */ + 'i18n_link_all_variations' => esc_js( sprintf( __( 'Are you sure you want to link all variations? This will create a new variation for each and every possible combination of variation attributes (max %d per run).', 'dokan-lite' ), defined( 'WC_MAX_LINKED_VARIATIONS' ) ? WC_MAX_LINKED_VARIATIONS : 50 ) ), + 'i18n_enter_a_value' => esc_js( __( 'Enter a value', 'dokan-lite' ) ), + 'i18n_enter_menu_order' => esc_js( __( 'Variation menu order (determines position in the list of variations)', 'dokan-lite' ) ), + 'i18n_enter_a_value_fixed_or_percent' => esc_js( __( 'Enter a value (fixed or %)', 'dokan-lite' ) ), + 'i18n_delete_all_variations' => esc_js( __( 'Are you sure you want to delete all variations? This cannot be undone.', 'dokan-lite' ) ), + 'i18n_last_warning' => esc_js( __( 'Last warning, are you sure?', 'dokan-lite' ) ), + 'i18n_variation_added' => esc_js( __( 'Variation added', 'dokan-lite' ) ), + 'i18n_variations_added' => esc_js( __( 'Variations added', 'dokan-lite' ) ), + 'i18n_no_variations_added' => esc_js( __( 'No variations added', 'dokan-lite' ) ), + 'i18n_remove_variation' => esc_js( __( 'Are you sure you want to remove this variation?', 'dokan-lite' ) ), + 'i18n_scheduled_sale_start' => esc_js( __( 'Sale start date (YYYY-MM-DD format or leave blank)', 'dokan-lite' ) ), + 'i18n_scheduled_sale_end' => esc_js( __( 'Sale end date (YYYY-MM-DD format or leave blank)', 'dokan-lite' ) ), + 'i18n_edited_variations' => esc_js( __( 'Save changes before changing page?', 'dokan-lite' ) ), + 'i18n_variation_count_single' => esc_js( __( '%qty% variation', 'dokan-lite' ) ), + 'i18n_variation_count_plural' => esc_js( __( '%qty% variations', 'dokan-lite' ) ), ]; $default_args = array_merge( $default_args, $custom_args ); @@ -1216,6 +1227,7 @@ public function get_admin_localized_scripts() { 'decimal_point' => $decimal, 'mon_decimal_point' => wc_get_price_decimal_separator(), 'i18n_date_format' => wc_date_format(), + 'product_types' => apply_filters( 'dokan_product_types', [ 'simple' ] ), ] ); } 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/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 bf84cf9d0e..ce566f3d3a 100644 --- a/includes/CatalogMode/Dashboard/Products.php +++ b/includes/CatalogMode/Dashboard/Products.php @@ -29,8 +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, 1 ); // save catalog mode section data - add_action( 'dokan_product_updated', [ $this, 'save_catalog_mode_data' ], 13 ); - add_action( 'dokan_new_product_added', [ $this, 'save_catalog_mode_data' ], 13 ); + add_filter( 'dokan_product_edit_meta_data', [ $this, 'add_catalog_mode_data' ], 13, 1 ); } /** @@ -48,20 +47,23 @@ public function render_product_section( $product_id ) { return; } - // get product data $product = wc_get_product( $product_id ); - // return if product type is optional - if ( ! $product || 'auction' === $product->get_type() ) { + if ( ! $product ) { + return; + } + + // 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, ] @@ -73,17 +75,17 @@ public function render_product_section( $product_id ) { * * @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 add_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 = [ @@ -96,6 +98,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/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 25547dadca..959fc30aa7 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' ); + 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,31 @@ 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; + } - // 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(); + $section = ProductFormFactory::get_section( 'others' ); + if ( is_wp_error( $section ) || ! $section->is_visible() ) { + return; + } 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, + '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 +341,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 +355,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 +370,207 @@ 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' ); - } + $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' ); - 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 ); - } + return; + } - 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' ); - } + if ( ! dokan_is_product_author( $product_id ) ) { + self::$errors[] = __( 'I swear this is not your product!', 'dokan-lite' ); - /* 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 ); - } + return; + } - /** 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' ); + // 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 ); - // 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 ) ); - } + /** + * @var WC_Product $product + */ + if ( ! $product ) { + self::$errors[] = __( 'No product found with given product id!', 'dokan-lite' ); - if ( isset( $postdata['_regular_price'] ) ) { - update_post_meta( $product_id, '_regular_price', ( $postdata['_regular_price'] === '' ) ? '' : wc_format_decimal( $postdata['_regular_price'] ) ); - } + return; + } - 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'] ) ); - } - } + $props = []; + $meta_data = []; + $is_new_product = 'auto-draft' === $product->get_status(); - update_post_meta( $product_id, '_visibility', 'visible' ); - update_post_meta( $product_id, '_stock_status', 'instock' ); + // stock quantity + $props[ ProductFormElements::STOCK_QUANTITY ] = null; - do_action( 'dokan_new_product_added', $product_id, $postdata ); + 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 ( current_user_can( 'dokan_edit_product' ) ) { - $redirect = dokan_edit_product_url( $product_id ); - } else { - $redirect = dokan_get_navigation_url( 'products' ); - } + $field_name = sanitize_key( $field->get_name() ); - 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' ) - ); - } + // Skip all of the "Downloads" section fields if "Downloadable" field disabled. + $downloadable_fields = array_keys( ProductFormFactory::get_fields_by_type( '', 'downloadable' ) ); + if ( in_array( $field->get_id(), $downloadable_fields, true ) && isset( $_POST['_downloadable'] ) && 'no' === $_POST['_downloadable'] ) { + continue; + } - wp_safe_redirect( apply_filters( 'dokan_add_new_product_redirect', $redirect, $product_id ) ); - exit; - } + // Skip "This product requires shipping" checkbox field if "Virtual" field enabled. + if ( ProductFormElements::DISABLE_SHIPPING_META === $field->get_id() && isset( $_POST['_virtual'] ) && 'yes' === $_POST['_virtual'] ) { + continue; } - } - } - /** - * Handle product update - * - * @return void - */ - public function handle_product_update() { - if ( ! is_user_logged_in() ) { - return; - } + // Skip shipping related fields if "This product requires shipping" field disabled. + $shipping_fields = [ 'weight', 'length', 'width', 'height', 'shipping_class_id' ]; + if ( in_array( $field->get_id(), $shipping_fields, true ) && isset( $_POST['_disable_shipping'] ) && 'no' === $_POST['_disable_shipping'] ) { + continue; + } - if ( ! dokan_is_user_seller( get_current_user_id() ) ) { - return; - } + // Skip the "Stock Status" field if "Enable Product Stock Management" field enabled. + if ( ProductFormElements::STOCK_STATUS === $field->get_id() && isset( $_POST['_manage_stock'] ) && 'yes' === $_POST['_manage_stock'] ) { + continue; + } - if ( ! isset( $_POST['dokan_update_product'] ) ) { - return; - } + // Skip the "Stock Qty", "Low Stock Threshold" and "Allow Backorders" fields if "Enable Product Stock Management" field disabled. + $manage_stock_fields = [ ProductFormElements::STOCK_QUANTITY, ProductFormElements::LOW_STOCK_AMOUNT, ProductFormElements::BACKORDERS ]; + if ( in_array( $field->get_id(), $manage_stock_fields, true ) && isset( $_POST['_manage_stock'] ) && 'no' === $_POST['_manage_stock'] ) { + continue; + } - if ( ! isset( $_POST['dokan_edit_product_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['dokan_edit_product_nonce'] ), 'dokan_edit_product' ) ) { - return; - } + // Skip the "Minimum Quantity" and "Discount %" fields if "Enable Bulk Discount" field disabled. + if ( in_array( $field->get_id(), [ '_lot_discount_quantity', '_lot_discount_amount' ], true ) && isset( $_POST['_is_lot_discount'] ) && 'no' === $_POST['_is_lot_discount'] ) { + continue; + } - $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'] ) ) : ''; + if ( apply_filters( 'dokan_product_form_skip_field_validation', false, $field ) ) { + continue; + } - if ( empty( $post_title ) ) { - $errors[] = __( 'Please enter product title', 'dokan-lite' ); - } + // check if field is required + 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() ); + continue; + } - $post_id = isset( $_POST['dokan_product_id'] ) ? absint( $_POST['dokan_product_id'] ) : 0; - if ( ! $post_id ) { - $errors[] = __( 'No product found!', 'dokan-lite' ); - } + // check if the field is present + if ( ! isset( $_POST[ $field_name ] ) ) { + continue; + } - $current_post_status = get_post_status( $post_id ); - $is_new_product = 'auto-draft' === $current_post_status; + // 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_name ] ), $original_stock, $product ); //phpcs:ignore + break; + + case ProductFormElements::GALLERY_IMAGE_IDS: + $attachment_ids = isset( $_POST['gallery_image_ids'] ) ? apply_filters( 'dokan_restrict_product_gallery_images_on_edit', wp_unslash( $_POST['gallery_image_ids'] ) ) : ''; + $attachment_ids = array_filter( explode( ',', wc_clean( $attachment_ids ) ) ); + $field_value = $field->sanitize( implode( ',', $attachment_ids ), $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_name ] ), $product ); // phpcs:ignore + if ( null === $field_value ) { + $field_value = $field->sanitize( wp_unslash( $_POST[ $field_name ] ) ); //phpcs:ignore + } + break; + } - if ( empty( $post_status ) ) { - $post_status = $current_post_status; - } + 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 ( ! dokan_is_product_author( $post_id ) ) { - $errors[] = __( 'I swear this is not your product!', 'dokan-lite' ); + // store props and meta data + if ( $field->is_prop() ) { + $props[ $field->get_id() ] = $field_value; + } elseif ( $field->is_meta() || $field->is_custom() ) { + $meta_data[ $field->get_id() ] = $field_value; + } } - if ( empty( $_POST['chosen_product_cat'] ) ) { - $errors[] = __( 'Please select a category', 'dokan-lite' ); + // 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 } - 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 ); + // apply hooks before saving 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 ); - // 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 ) ); - } + $errors = $product->set_props( $props ); + if ( is_wp_error( $errors ) ) { + self::$errors = array_merge( self::$errors, $errors->get_error_messages() ); } - self::$errors = apply_filters( 'dokan_can_edit_product', $errors ); - - if ( self::$errors ) { + // return early for validation errors + self::$errors = apply_filters( 'dokan_can_edit_product', self::$errors ); + if ( ! empty( self::$errors ) ) { return; } - $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 ); - } - - wp_update_post( $product_info ); - - /** Set Product tags */ - if ( isset( $_POST['product_tag'] ) ) { - $tags_ids = array_map( 'absint', (array) wp_unslash( $_POST['product_tag'] ) ); + if ( $is_new_product ) { + do_action( 'dokan_before_new_product_added', $product, $props, $meta_data ); } else { - $tags_ids = []; + do_action( 'dokan_before_product_updated', $product, $props, $meta_data ); } - 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 ); + $postdata = wp_unslash( $_POST ); // phpcs:ignore - //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' ); + // add product meta data + foreach ( $meta_data as $meta_key => $meta_value ) { + $product->add_meta_data( $meta_key, $meta_value, true ); - // 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 ); + unset( $postdata[ $meta_key ] ); } - $postdata = wp_unslash( $_POST ); // phpcs:ignore - /** Process all variation products meta */ - dokan_process_product_meta( $post_id, $postdata ); + // save product + $product->save(); if ( $is_new_product ) { - do_action( 'dokan_new_product_added', $post_id, $postdata ); + do_action( 'dokan_new_product_added', $product_id, $postdata ); } else { - do_action( 'dokan_product_updated', $post_id, $postdata ); - } - - $redirect = apply_filters( 'dokan_add_new_product_redirect', dokan_edit_product_url( $post_id ), $post_id ); - - // if any error inside dokan_process_product_meta function - global $woocommerce_errors; - - if ( $woocommerce_errors ) { - wp_safe_redirect( add_query_arg( [ 'errors' => array_map( 'urlencode', $woocommerce_errors ) ], $redirect ) ); - exit; + do_action( 'dokan_product_updated', $product_id, $postdata ); } + $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 * @@ -632,5 +617,4 @@ public function handle_delete_product() { wp_safe_redirect( add_query_arg( [ 'message' => 'product_deleted' ], $redirect ) ); exit; } - } diff --git a/includes/DependencyManagement/Providers/CommonServiceProvider.php b/includes/DependencyManagement/Providers/CommonServiceProvider.php index 03d0fb2858..410dd9f2c9 100644 --- a/includes/DependencyManagement/Providers/CommonServiceProvider.php +++ b/includes/DependencyManagement/Providers/CommonServiceProvider.php @@ -65,5 +65,9 @@ public function register(): void { $this->getContainer() ->addShared( \WeDevs\Dokan\Privacy::class, \WeDevs\Dokan\Privacy::class ) ->addTag( self::TAG ); + + $this->getContainer() + ->addShared( \WeDevs\Dokan\ProductForm\Init::class, \WeDevs\Dokan\ProductForm\Init::class ) + ->addTag( self::TAG ); } } diff --git a/includes/Emails/NewProduct.php b/includes/Emails/NewProduct.php index eee751f3d9..d665b7d3db 100644 --- a/includes/Emails/NewProduct.php +++ b/includes/Emails/NewProduct.php @@ -92,7 +92,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 981df44403..1382e13ebb 100644 --- a/includes/Emails/NewProductPending.php +++ b/includes/Emails/NewProductPending.php @@ -93,7 +93,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 02f45a9847..5d364928fa 100644 --- a/includes/Product/Hooks.php +++ b/includes/Product/Hooks.php @@ -6,6 +6,8 @@ use WeDevs\Dokan\ProductCategory\Helper; use WC_Product; +defined( 'ABSPATH' ) || exit; + /** * Admin Hooks * @@ -31,7 +33,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' ] ); @@ -370,16 +372,20 @@ public function set_product_status( $all_statuses, int $product_id ) { * * @since 3.8.2 * - * @param int|WC_Product $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 ) { - 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' ); + public function set_new_product_email_status( array $meta_data, WC_Product $product, bool $is_new_product ) { + if ( ! $is_new_product ) { + return $meta_data; } + + $meta_data['_dokan_new_product_email_sent'] = 'no'; + + return $meta_data; } /** diff --git a/includes/Product/Manager.php b/includes/Product/Manager.php index 0a6a74cb69..bfec90d36f 100644 --- a/includes/Product/Manager.php +++ b/includes/Product/Manager.php @@ -3,12 +3,16 @@ namespace WeDevs\Dokan\Product; use WC_Product; +use WC_Product_Attribute; use WC_Product_Download; use WeDevs\Dokan\Cache; use WeDevs\Dokan\Commission\Model\Setting; use WeDevs\Dokan\Commission\Settings\Product; use WP_Error; use WP_Query; +use WeDevs\Dokan\ProductForm\Elements as FormElements; + +defined( 'ABSPATH' ) || exit; /** * Product manager Class @@ -30,7 +34,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, @@ -73,100 +77,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 ) ) { @@ -177,45 +180,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(); } @@ -223,13 +226,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' ) ) { @@ -249,11 +256,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 { @@ -270,9 +277,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 ) { @@ -286,9 +293,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 ) { @@ -302,47 +309,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 ] ); } } @@ -352,51 +359,95 @@ 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, [] ); + } else { + do_action( 'dokan_product_updated', $product_id, [] ); + } + + 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 bool + */ + public function delete( $product_id, $force = false ) { + $product = $this->get( $product_id ); + + if ( ! $product ) { + return false; + } + + return $product->delete( [ 'force_delete' => $force ] ); } /** @@ -404,7 +455,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 @@ -460,39 +511,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. * @@ -503,42 +521,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. * @@ -549,10 +644,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; @@ -561,8 +658,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 */ @@ -595,10 +692,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 ) { @@ -617,6 +711,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 * @@ -681,7 +806,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/Product/functions.php b/includes/Product/functions.php index 8b2f44bdba..72e203de4c 100644 --- a/includes/Product/functions.php +++ b/includes/Product/functions.php @@ -10,158 +10,22 @@ /** * Dokan insert new product * - * @since 2.5.1 + * @since 2.5.1 * * @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'] ) : ''; - } - - 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'] ) ) ) : []; - } - - 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 ); - } + wc_deprecated_function( __FUNCTION__, 'DOKAN_SINCE', 'dokan()->product->create()' ); - if ( ! $is_updating ) { - do_action( 'dokan_new_product_added', $product->get_id(), $data ); - } else { - do_action( 'dokan_product_updated', $product->get_id(), $data ); + try { + return dokan()->product->create( $args ); + } catch ( Exception $e ) { + return new WP_Error( $e->getCode(), $e->getMessage() ); } - - if ( $product ) { - return $product->get_id(); - } - - return false; } /** @@ -172,178 +36,163 @@ function dokan_save_product( $args ) { * @return void */ function dokan_product_output_variations() { - global $post, $wpdb; + global $post, $wpdb, $product_object; - // Get attributes - $attributes = maybe_unserialize( get_post_meta( $post->ID, '_product_attributes', true ) ); + $product_object = wc_get_product( $post->ID ); - // See if any are set - $variation_attribute_found = false; - - if ( $attributes ) { - foreach ( $attributes as $attribute ) { - if ( ! empty( $attribute['is_variation'] ) ) { - $variation_attribute_found = true; - break; - } - } - } + /* phpcs:disable WooCommerce.Commenting.CommentHooks.MissingHookComment */ + $variation_attributes = array_filter( + $product_object->get_attributes(), function ( $attribute ) { + return true === $attribute->get_variation(); + } + ); + $default_attributes = $product_object->get_default_attributes(); + $variations_count = absint( apply_filters( 'woocommerce_admin_meta_boxes_variations_count', $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(ID) FROM $wpdb->posts WHERE post_parent = %d AND post_type = 'product_variation' AND post_status IN ('publish', 'private', 'pending')", $post->ID ) ), $post->ID ) ); + $variations_per_page = absint( apply_filters( 'dokan_product_variations_per_page', 15 ) ); + $variations_total_pages = ceil( $variations_count / $variations_per_page ); - $variations_count = absint( $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(ID) FROM $wpdb->posts WHERE post_parent = %d AND post_type = 'product_variation' AND post_status IN ('publish', 'private', 'pending')", $post->ID ) ) ); - $variations_per_page = absint( apply_filters( 'woocommerce_admin_meta_boxes_variations_per_page', 15 ) ); - $variations_total_pages = ceil( $variations_count / $variations_per_page ); ?> + // See if any are set + $variation_attribute_found = count( $variation_attributes ) > 0; + ?>
- - - -
-

- Attributes section', 'dokan-lite' ), [ 'strong' => [] ] ); ?> -

-
- - - -
-
-
- - -
+ +
+

+ Attributes section', 'dokan-lite' ), [ 'strong' => [] ] ); ?> +

+ +
+
+
+ + +
+
-
- -
- - - ID, '_default_attributes', true ) ); - - foreach ( $attributes as $attribute ) { - - // Only deal with attributes that are variations - if ( ! $attribute['is_variation'] ) { - continue; - } - - echo '
'; - - // Get current value for variation (if set) - $variation_selected_value = isset( $default_attributes[ sanitize_title( $attribute['name'] ) ] ) ? $default_attributes[ sanitize_title( $attribute['name'] ) ] : ''; - - // Name will be something like attribute_pa_color - echo ' + + + is_taxonomy() ) : ?> + get_terms() as $option ) : ?> + + + + + + get_options() as $option ) : ?> + + + + + + +
+ '; - echo '
'; - } - ?> -
- + ?> +
+
- +
-
-
- -
-
- -
- - - -
- - " + data-total_pages="" + data-page="1" + data-edited="false"> +
+ +
+ + +
+ + - - - ( / ) - - - « - - - - - + ?> + + + ( + + / + + ) + + + « + + + + + + + + » - - » - +
+
-
-
-
- - +
-
postmeta} postmeta_type ON posts.ID = postmeta_type.post_id "; - $type_where = " AND ( postmeta_type.meta_key = %s AND postmeta_type.meta_value = 'yes' ) "; + $type_join = " LEFT JOIN {$wpdb->postmeta} postmeta_type ON posts.ID = postmeta_type.post_id "; + $type_where = " AND ( postmeta_type.meta_key = %s AND postmeta_type.meta_value = 'yes' ) "; $query_args[] = "_{$type}"; } } @@ -399,7 +248,7 @@ function dokan_search_seller_products( $term, $user_ids = false, $type = '', $in if ( is_array( $user_ids ) ) { $users_where = " AND posts.post_author IN ('" . implode( "','", array_filter( array_map( 'absint', $user_ids ) ) ) . "')"; } elseif ( is_numeric( $user_ids ) ) { - $users_where = ' AND posts.post_author = %d'; + $users_where = ' AND posts.post_author = %d'; $query_args[] = $user_ids; } } @@ -542,11 +391,11 @@ function dokan_product_get_row_action( $post, $format_html = true ) { /** * Dokan get vendor by product * - * @param int|WC_Product $product Product ID or Product Object - * @param bool $get_vendor return true to get vendor id, otherwise it will return \WeDevs\Dokan\Vendor\Vendor object - * * @since 2.9.8 - * @since 3.2.16 added $id parameter + * @since 3.2.16 added $id parameter + * + * @param int|WC_Product $product Product ID or Product Object + * @param bool $get_vendor return true to get vendor id, otherwise it will return \WeDevs\Dokan\Vendor\Vendor object * * @return int|\WeDevs\Dokan\Vendor\Vendor|false on failure */ @@ -612,7 +461,7 @@ function dokan_store_product_catalog_orderby() { ); $default_orderby = wc_get_loop_prop( 'is_search' ) ? 'relevance' : apply_filters( 'dokan_default_store_products_orderby', get_option( 'woocommerce_default_catalog_orderby', '' ) ); - $orderby = isset( $_GET['product_orderby'] ) ? wc_clean( wp_unslash( $_GET['product_orderby'] ) ) : $default_orderby; //phpcs:ignore + $orderby = isset( $_GET['product_orderby'] ) ? wc_clean( wp_unslash( $_GET['product_orderby'] ) ) : $default_orderby; //phpcs:ignore if ( wc_get_loop_prop( 'is_search' ) ) { $catalog_orderby_options = array_merge( array( 'relevance' => __( 'Relevance', 'dokan-lite' ) ), $catalog_orderby_options ); @@ -633,9 +482,9 @@ function dokan_store_product_catalog_orderby() { } $orderby_options = array( - 'show_default_orderby' => $show_default_orderby, - 'orderby' => $orderby, - 'catalogs' => $catalog_orderby_options, + 'show_default_orderby' => $show_default_orderby, + 'orderby' => $orderby, + 'catalogs' => $catalog_orderby_options, ); return $orderby_options; diff --git a/includes/ProductCategory/Helper.php b/includes/ProductCategory/Helper.php index c582787ff6..da9eeb0066 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' ); } @@ -154,6 +158,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 * @@ -169,22 +211,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 ); @@ -258,12 +285,21 @@ 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 []; + } + + $chosen_product_cat = $product->get_meta( 'chosen_product_cat', true ); + + return empty( $chosen_product_cat ) ? [] : $chosen_product_cat; } /** diff --git a/includes/ProductForm/Component.php b/includes/ProductForm/Component.php new file mode 100644 index 0000000000..3c1ecacf5b --- /dev/null +++ b/includes/ProductForm/Component.php @@ -0,0 +1,411 @@ + '', // 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 + 'dependency_condition' => [], // dependency condition for the field + 'order' => 30, // sorting sequence order for the field + 'error_message' => '', + 'show_in_admin_settings' => true, + ]; + + public function __construct( string $id, array $args = [] ) { + foreach ( $args as $key => $value ) { + if ( method_exists( $this, "set_{$key}" ) && null !== $value ) { + $this->{"set_{$key}"}( $value ); + } + } + } + + /** + * Get array of data. + * + * @since DOKAN_SINCE + * + * @return array + */ + public function toArray(): array { + return (array) $this->data; + } + + /** + * 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 wp_kses_post() + * + * @return $this + */ + public function set_title( string $title ): self { + $this->data['title'] = wp_kses_post( $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 Sorting Sequence Order. + * + * @since DOKAN_SINCE + * + * @return int + */ + public function get_order(): int { + return $this->data['order']; + } + + /** + * Set Field Sorting Sequence Order. + * + * @since DOKAN_SINCE + * + * @param int $order + * + * @return $this + */ + public function set_order( int $order ): self { + $this->data['order'] = $order; + + return $this; + } + + /** + * Get Dependency Condition. + * + * @since DOKAN_SINCE + * + * @return array + */ + public function get_dependency_condition(): array { + return $this->data['dependency_condition']; + } + + /** + * Set Dependency Condition. + * + * @since DOKAN_SINCE + * + * @param array $condition + * + * @return $this + */ + public function set_dependency_condition( array $condition ): self { + $this->data['dependency_condition'] = $condition; + + 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; + } + + /** + * Get show in admin settings + * + * @since DOKAN_SINCE + * + * @param bool $show_in_admin_settings + * + * @return void + */ + public function set_show_in_admin_settings( bool $show_in_admin_settings ): void { + $this->data['show_in_admin_settings'] = $show_in_admin_settings; + } + + /** + * Set show in admin settings + * + * @since DOKAN_SINCE + * + * @return bool + */ + public function get_show_in_admin_settings(): bool { + return wc_string_to_bool( $this->data['show_in_admin_settings'] ); + } + + /** + * Check the field is shown in admin settings or not + * + * @since DOKAN_SINCE + * + * @return bool + */ + public function is_shown_in_admin_settings(): bool { + return $this->get_show_in_admin_settings(); + } + + /** + * 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 true; + } + + return false; + } + ) + ); + } +} diff --git a/includes/ProductForm/Elements.php b/includes/ProductForm/Elements.php new file mode 100644 index 0000000000..fc1b7189df --- /dev/null +++ b/includes/ProductForm/Elements.php @@ -0,0 +1,78 @@ + '', // (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 ) { + 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; + } + + // 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 ) { + 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; + } + + // 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 Field[] list of registered fields. + */ + public static function get_fields(): array { + return apply_filters( 'dokan_product_form_fields', self::$form_fields ); + } + + /** + * Return list of registered fields by type eg: custom, props etc. + * + * @since DOKAN_SINCE + * + * @param string $type + * @param string $section_id + * + * @return \WeDevs\Dokan\ProductForm\Field[] + */ + public static function get_fields_by_type( string $type = '', string $section_id = '' ): array { + if ( ! empty( $section_id ) ) { + $section = static::get_section( $section_id ); + $items = is_wp_error( $section ) ? [] : $section->get_fields(); + } else { + $items = self::get_fields(); + } + + if ( empty( $type ) ) { + return $items; + } + + return array_filter( + $items, + function ( $item ) use ( $type ) { + return $type === $item->get_type(); + } + ); + } + + /** + * 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 list of registered sections by type eg: default, custom. + * + * @param string $type + * @param string $sort_by + * + * @return \WeDevs\Dokan\ProductForm\Section[] + */ + public static function get_sections_by_type( string $type = 'default', string $sort_by = 'asc' ): array { + $items = self::get_sections( $sort_by ); + + return array_filter( + $items, + function ( $item ) use ( $type ) { + return $type === $item->get_type(); + } + ); + } + + /** + * 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 ); + + if ( class_exists( $class_name ) && method_exists( $class_name, 'sort' ) ) { + /** + * @var Component $class_name + */ + uasort( + $item_list, + function ( $a, $b ) use ( $sort_by, $class_name ) { + return $class_name::sort( $a, $b, $sort_by ); + } + ); + } + + return $item_list; + } + + /** + * @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 ) { + $class_name = __NAMESPACE__ . '\\' . $class_name; + 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 apply_filters( 'dokan_product_form_item_created', new $class_name( $id, $args ), $type ); + } 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..43955dc943 --- /dev/null +++ b/includes/ProductForm/Field.php @@ -0,0 +1,533 @@ + '', // 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', 'custom' + 'field_type' => '', // html field type + 'placeholder' => '', // html placeholder attribute value for the field + 'options' => [], // if the field is select, radio, checkbox, etc + 'options_callback' => '', // callback function to get field options via a callback + 'dependency_condition' => [], // dependency condition for the field + 'additional_properties' => [], // additional arguments for the field + 'sanitize_callback' => '', // callback function for sanitizing the field value + 'value_callback' => '', // callback function to get field value via a callback + ]; + $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 sanitize_text_field() + * + * @since DOKAN_SINCE + * + * @param string $name + * + * @see sanitize_text_field() + * + * @return $this + */ + public function set_name( string $name ): Field { + $this->data['name'] = sanitize_text_field( $name ); + + return $this; + } + + /** + * Get callable function to get field value + * + * @since DOKAN_SINCE + * + * @return callable|string + */ + public function get_value_callback() { + return $this->data['value_callback']; + } + + /** + * Set callable function to get field value + * + * @since DOKAN_SINCE + * + * @param callable|string $method + * + * @return $this + */ + public function set_value_callback( $method ): Field { + $this->data['value_callback'] = $method; + + return $this; + } + + /** + * Get field value + * + * @since DOKAN_SINCE + * + * @return mixed + */ + public function get_value( ...$value ) { + $callback = $this->get_value_callback(); + if ( ! empty( $callback ) && is_callable( $callback ) ) { + $value[] = $this->data['value']; + return call_user_func( $callback, ...$value ); + } + + // if value is set under the field, return that value + if ( '' !== $this->data['value'] || empty( $value ) ) { + return $this->data['value']; + } + + $product = current( $value ); + if ( ! $product instanceof \WC_Product ) { + return ''; + } + + $field_name = sanitize_key( $this->get_name() ); + if ( $this->is_prop() && 0 === strpos( $field_name, '_' ) ) { + $field_name = preg_replace( '/_/', '', $field_name, 1 ); + } + + if ( $this->is_prop() ) { + $method_name = 'get_' . $field_name; + return $product->{$method_name}(); + } elseif ( $this->is_meta() || $this->is_custom() ) { + return $product->get_meta( $field_name ); + } + + return $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 callable function to get field options + * + * @since DOKAN_SINCE + * + * @return callable|string + */ + public function get_options_callback() { + return $this->data['options_callback']; + } + + /** + * Set callable function to get field options + * + * @since DOKAN_SINCE + * + * @param callable|string $method + * + * @return $this + */ + public function set_options_callback( $method ): Field { + $this->data['options_callback'] = $method; + + return $this; + } + + /** + * Get field value + * + * @since DOKAN_SINCE + * + * @return mixed + */ + public function get_options( ...$value ) { + $callback = $this->get_options_callback(); + if ( ! empty( $callback ) && is_callable( $callback ) ) { + $value[] = $this->data['options']; + return call_user_func( $callback, ...$value ); + } + + // if options are set under the field, return that options + if ( '' !== $this->data['options'] || empty( $value ) ) { + return $this->data['options']; + } + + return $value; + } + + /** + * 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 + * + * @return string + */ + public function get_property(): string { + return $this->data['property']; + } + + /** + * Set field property, validated by sanitize_key() + * + * @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 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 custom. + * + * @since DOKAN_SINCE + * + * @return bool + */ + public function is_custom(): bool { + return 'custom' === $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', 'custom' ], true ) ? sanitize_key( $type ) : 'other'; + + 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 wp_kses_post() + * + * @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 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; + } + + /** + * 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; + } + + /** + * Print required field symbol. + * + * @since DOKAN_SINCE + * + * @param bool $output + * + * @return string|void + */ + public function print_required_symbol( bool $output = true ) { + if ( ! $this->is_required() ) { + return ''; + } + + $symbol = '*'; + + if ( $output ) { + echo $symbol; + return; + } + + return $symbol; + } +} diff --git a/includes/ProductForm/Init.php b/includes/ProductForm/Init.php new file mode 100644 index 0000000000..fa57b9fa09 --- /dev/null +++ b/includes/ProductForm/Init.php @@ -0,0 +1,740 @@ +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, + 'required' => true, + ] + ); + + $section->add_field( + Elements::NAME, + [ + 'title' => __( 'Title', 'dokan-lite' ), + 'field_type' => 'text', + 'name' => 'post_title', + 'placeholder' => __( 'Enter product title...', 'dokan-lite' ), + 'required' => true, + 'error_msg' => __( 'Please enter product title!', 'dokan-lite' ), + 'value_callback' => function ( $product, $value = '' ) { + if ( '' !== $value ) { + return $value; + } + + if ( ! $product instanceof WC_Product ) { + return ''; + } + + return $product->get_name(); + }, + ] + ); + + $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 ); + }, + 'value_callback' => function ( $product, $value = '' ) { + if ( '' !== $value ) { + return $value; + } + + if ( ! $product instanceof WC_Product ) { + return ''; + } + + return $product->get_slug(); + }, + ] + ); + + $section->add_field( + Elements::TYPE, [ + 'title' => __( 'Product Type', 'dokan-lite' ), + 'field_type' => 'select', + 'type' => 'other', + 'id' => 'product_type', + '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' ), + 'value_callback' => function ( $product, $value = '' ) { + if ( '' !== $value ) { + return $value; + } + if ( ! $product instanceof WC_Product ) { + return 'simple'; + } + + return $product->get_type(); + }, + ] + ); + + $section->add_field( + Elements::REGULAR_PRICE, [ + 'title' => __( 'Price', 'dokan-lite' ), + 'field_type' => 'text', + 'name' => '_regular_price', + 'placeholder' => '0.00', + ] + ); + + $section->add_field( + Elements::SALE_PRICE, [ + 'title' => __( 'Discounted Price', 'dokan-lite' ), + 'field_type' => 'text', + 'name' => '_sale_price', + 'placeholder' => '0.00', + ] + ); + + $section->add_field( + Elements::DATE_ON_SALE_FROM, [ + 'title' => __( 'From', 'dokan-lite' ), + 'field_type' => 'text', + 'name' => '_sale_price_dates_from', + 'placeholder' => 'YYYY-MM-DD', + 'value_callback' => function ( $product, $value = '' ) { + if ( '' !== $value ) { + $time = dokan_current_datetime()->modify( $value ); + + return $time ? $time->getTimestamp() : false; + } + + if ( ! $product instanceof WC_Product ) { + return false; + } + + return $product->get_date_on_sale_from( 'edit' ) ? $product->get_date_on_sale_from( 'edit' )->getTimestamp() : false; + }, + 'dependency_condition' => [ + 'section' => 'general', + 'field' => Elements::SALE_PRICE, + 'operator' => 'equal', + 'value' => 'on', + ], + ] + ); + + $section->add_field( + Elements::DATE_ON_SALE_TO, [ + 'title' => __( 'To', 'dokan-lite' ), + 'field_type' => 'text', + 'name' => '_sale_price_dates_to', + 'placeholder' => 'YYYY-MM-DD', + 'value_callback' => function ( $product, $value = '' ) { + if ( '' !== $value ) { + $time = dokan_current_datetime()->modify( $value ); + + return $time ? $time->getTimestamp() : false; + } + + if ( ! $product instanceof WC_Product ) { + return false; + } + + return $product->get_date_on_sale_to( 'edit' ) ? $product->get_date_on_sale_to( 'edit' )->getTimestamp() : false; + }, + 'dependency_condition' => [ + 'section' => 'general', + 'field' => Elements::SALE_PRICE, + 'operator' => 'equal', + 'value' => 'on', + ], + ] + ); + + $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' ), + '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 ) ); + }, + 'value_callback' => function ( $product, $value = '' ) { + if ( '' !== $value ) { + return $value; + } + + if ( ! $product instanceof WC_Product ) { + return []; + } + + return $product->get_category_ids(); + }, + ] + ); + + $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' ), + '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 + ); + }, + 'value_callback' => function ( $product, $value = '' ) { + if ( '' !== $value ) { + return $value; + } + + if ( ! $product instanceof WC_Product ) { + return []; + } + + return $product->get_tag_ids(); + }, + ] + ); + + $section->add_field( + Elements::FEATURED_IMAGE_ID, [ + 'title' => __( 'Product Image', 'dokan-lite' ), + 'field_type' => 'image', + 'name' => 'image_id', + 'placeholder' => __( 'Select product image', 'dokan-lite' ), + ] + ); + + $section->add_field( + Elements::GALLERY_IMAGE_IDS, [ + 'title' => __( 'Product Gallery', 'dokan-lite' ), + 'field_type' => 'gallery', + 'name' => 'gallery_image_ids', + 'placeholder' => __( 'Select product gallery images', 'dokan-lite' ), + ] + ); + + $section->add_field( + Elements::SHORT_DESCRIPTION, [ + 'title' => __( 'Short Description', 'dokan-lite' ), + 'field_type' => 'textarea', + 'name' => 'post_excerpt', + 'placeholder' => __( 'Enter product short description', 'dokan-lite' ), + 'value_callback' => function ( $product, $value = '' ) { + if ( '' !== $value ) { + return $value; + } + + if ( ! $product instanceof WC_Product ) { + return ''; + } + + return $product->get_short_description(); + }, + ] + ); + + $section->add_field( + Elements::DESCRIPTION, [ + 'title' => __( 'Description', 'dokan-lite' ), + 'field_type' => 'textarea', + 'name' => 'post_content', + 'placeholder' => __( 'Enter product description', 'dokan-lite' ), + 'required' => true, + 'value_callback' => function ( $product, $value = '' ) { + if ( '' !== $value ) { + return $value; + } + + if ( ! $product instanceof WC_Product ) { + return ''; + } + + return $product->get_description(); + }, + ] + ); + + $section->add_field( + Elements::DOWNLOADABLE, [ + 'title' => __( 'Downloadable', 'dokan-lite' ), + 'description' => __( 'Downloadable products give access to a file upon purchase.', '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' => __( 'Virtual products are intangible and are not shipped.', 'dokan-lite' ), + 'field_type' => 'checkbox', + 'name' => '_virtual', + 'additional_properties' => [ + 'value' => 'yes', + ], + 'sanitize_callback' => function ( $value ) { + return ! empty( $value ) && 'yes' === $value; + }, + ] + ); + } + + /** + * Init inventory fields + * + * @since DOKAN_SINCE + * + * @return void + */ + public function init_inventory_fields() { + $section = Factory::add_section( + 'inventory', + [ + 'title' => __( 'Inventory', 'dokan-lite' ), + 'description' => __( 'Manage inventory for this product', 'dokan-lite' ), + 'order' => 20, + ] + ); + + $section->add_field( + Elements::SKU, [ + 'title' => sprintf( '%s (%s)', esc_html__( 'SKU', 'dokan-lite' ), esc_html__( 'Stock Keeping Unit', 'dokan-lite' ) ), + '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' ), + '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' ), + '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' ), + '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 ); + }, + 'value_callback' => function ( $product, $value = '' ) { + if ( '' !== $value ) { + return $value; + } + + if ( ! $product instanceof WC_Product ) { + return 0; + } + + return $product->get_stock_quantity(); + }, + 'dependency_condition' => [ + 'section' => 'inventory', + 'field' => Elements::MANAGE_STOCK, + 'operator' => 'equal', + 'value' => 'on', + ], + ] + ); + + $section->add_field( + Elements::LOW_STOCK_AMOUNT, [ + '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' => function ( $value ) { + if ( '' !== $value ) { + return wc_stock_amount( $value ); + } + + return ''; + }, + 'dependency_condition' => [ + 'section' => 'inventory', + 'field' => Elements::MANAGE_STOCK, + 'operator' => 'equal', + 'value' => 'on', + ], + ] + ); + + $section->add_field( + Elements::BACKORDERS, [ + '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(), + 'dependency_condition' => [ + 'section' => 'inventory', + 'field' => Elements::MANAGE_STOCK, + 'operator' => 'equal', + 'value' => 'on', + ], + ] + ); + + $section->add_field( + Elements::SOLD_INDIVIDUALLY, [ + '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', + 'additional_properties' => [ + 'value' => 'yes', + ], + 'sanitize_callback' => function ( $value ) { + return ! empty( $value ) && 'yes' === $value; + }, + ] + ); + } + + /** + * 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, + 'dependency_condition' => [ + 'section' => 'general', + 'field' => Elements::DOWNLOADABLE, + 'operator' => 'equal', + 'value' => 'on', + ], + ] + ); + + $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' ), + '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' => function ( $value ) { + if ( '' !== $value ) { + return absint( $value ); + } + + return ''; + }, + ] + ); + + $section->add_field( + Elements::DOWNLOAD_EXPIRY, [ + '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' => function ( $value ) { + if ( '' !== $value ) { + return absint( $value ); + } + + return ''; + }, + ] + ); + } + + /** + * 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' ), + 'field_type' => 'select', + 'name' => 'status', + 'options' => [], + 'options_callback' => function ( $product, $value = [] ) { + if ( ! empty( $value ) ) { + return $value; + } + + return dokan_get_available_post_status( $product instanceof WC_Product ? $product->get_id() : 0 ); + }, + 'value_callback' => function ( $product ) { + if ( ! $product instanceof WC_Product ) { + return 'pending'; + } + + if ( 'auto-draft' !== $product->get_status() ) { + return $product->get_status(); + } + + // set new post-status based on vendor settings + $seller_id = dokan_get_vendor_by_product( $product->get_id(), true ); + $new_product_status = dokan_get_default_product_status( $seller_id ); + if ( 'publish' === $new_product_status ) { + $current_status = 'publish'; + } elseif ( 'pending' === $new_product_status ) { + $current_status = 'pending'; + } else { + $current_status = 'draft'; + } + + return apply_filters( 'dokan_post_edit_default_status', $current_status, $product ); + }, + ] + ); + + $section->add_field( + Elements::CATALOG_VISIBILITY, [ + 'title' => __( 'Visibility', 'dokan-lite' ), + 'field_type' => 'select', + 'name' => '_visibility', + 'options' => dokan_get_product_visibility_options(), + 'value_callback' => function ( $product, $value = '' ) { + if ( '' !== $value ) { + return $value; + } + + if ( ! $product instanceof WC_Product ) { + return ''; + } + + return $product->get_catalog_visibility(); + }, + ] + ); + + $section->add_field( + Elements::PURCHASE_NOTE, [ + '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' ), + 'field_type' => 'checkbox', + 'name' => 'comment_status', + 'additional_properties' => [ + 'value' => 'yes', + ], + 'sanitize_callback' => function ( $value ) { + return ! empty( $value ) && 'yes' === $value; + }, + 'value_callback' => function ( $product, $value = '' ) { + if ( '' !== $value ) { + return $value; + } + + if ( ! $product instanceof WC_Product ) { + return ''; + } + + return $product->get_reviews_allowed(); + }, + ] + ); + } +} diff --git a/includes/ProductForm/Section.php b/includes/ProductForm/Section.php new file mode 100644 index 0000000000..35c6a5d784 --- /dev/null +++ b/includes/ProductForm/Section.php @@ -0,0 +1,194 @@ + 'default', // field type, accept value can be 'default', 'custom' + 'fields' => [], + 'product_type' => '', + 'product_category' => '', + ]; + $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 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 ); + } + + /** + * 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 + * + * @since DOKAN_SINCE + * + * @return $this + */ + private function init_fields() { + $this->data['fields'] = []; + foreach ( Factory::get_fields() as $field ) { + if ( $field->get_section() === $this->get_id() ) { + $this->data['fields'][ $field->get_id() ] = $field; + } + } + + return $this; + } + + /** + * Get type of the current field. + * + * @since DOKAN_SINCE + * + * @return string + */ + public function get_type(): string { + return $this->data['type']; + } + + /** + * Set field type. + * + * @since DOKAN_SINCE + * + * @param string $type + * + * @return $this + */ + public function set_type( string $type ): Section { + $this->data['type'] = in_array( $type, [ 'default', 'custom' ], true ) ? sanitize_key( $type ) : 'other'; + + return $this; + } + + /** + * Get product type for the current section. + * + * @since DOKAN_SINCE + * + * @return string + */ + public function get_product_type(): string { + return $this->data['product_type']; + } + + /** + * Set product type for the current section. + * + * @since DOKAN_SINCE + * + * @param string $product_type + * + * @return $this + */ + public function set_product_type( string $product_type ): Section { + $this->data['product_type'] = ! empty( $product_type ) ? $product_type : ''; + + return $this; + } + + /** + * Get product category for the current section. + * + * @since DOKAN_SINCE + * + * @return string + */ + public function get_product_category(): string { + return $this->data['product_category']; + } + + /** + * Set product category for the current section. + * + * @since DOKAN_SINCE + * + * @param string $product_category + * + * @return $this + */ + public function set_product_category( string $product_category ): Section { + $this->data['product_category'] = ! empty( $product_category ) ? $product_category : 0; + + return $this; + } +} diff --git a/includes/REST/Manager.php b/includes/REST/Manager.php index 10d0a5970d..51ea3fa87e 100644 --- a/includes/REST/Manager.php +++ b/includes/REST/Manager.php @@ -142,7 +142,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 32db2499c8..886d64bb99 100755 --- a/includes/functions.php +++ b/includes/functions.php @@ -563,7 +563,7 @@ function dokan_post_input_box( $post_id, $meta_key, $attr = [], $type = 'text' ) $name = isset( $attr['name'] ) ? esc_attr( $attr['name'] ) : $meta_key; $value = isset( $attr['value'] ) ? $attr['value'] : get_post_meta( $post_id, $meta_key, true ); $size = isset( $attr['size'] ) ? $attr['size'] : 30; - $required = isset( $attr['required'] ) ? 'required' : ''; + $required = ! empty( $attr['required'] ) ? 'required' : ''; switch ( $type ) { case 'text': @@ -613,6 +613,7 @@ class="" case 'checkbox': $label = isset( $attr['label'] ) ? $attr['label'] : ''; + $desc = isset( $attr['desc'] ) ? $attr['desc'] : ''; $class = ( $class === 'dokan-form-control' ) ? '' : $class; ?> @@ -620,8 +621,14 @@ class="" name="" id="" value="yes" type="checkbox"> + + + - - + +

- - + + + +

- + + +

diff --git a/src/admin/components/Fields.vue b/src/admin/components/Fields.vue index bb9a9ec9ef..f25ab7ad1c 100644 --- a/src/admin/components/Fields.vue +++ b/src/admin/components/Fields.vue @@ -493,6 +493,8 @@ :fieldData="fieldData" :is="settingsComponent" :fieldValue="fieldValue" + :all-settings-values="allSettingsValues" + :setting-fields='settingFields' :assetsUrl="dokanAssetsUrl" :validationErrors="validationErrors" :toggleLoadingState="toggleLoadingState" @@ -536,7 +538,7 @@ WithdrawCharges }, - props: ['id', 'fieldData', 'sectionId', 'fieldValue', 'allSettingsValues', 'errors', 'toggleLoadingState', 'validationErrors', 'dokanAssetsUrl'], + props: ['id', 'fieldData', 'sectionId', 'fieldValue', 'allSettingsValues', 'errors', 'toggleLoadingState', 'validationErrors', 'dokanAssetsUrl', 'settingFields'], data() { return { diff --git a/src/admin/components/Switches.vue b/src/admin/components/Switches.vue index 41194b2496..d06420d45b 100644 --- a/src/admin/components/Switches.vue +++ b/src/admin/components/Switches.vue @@ -37,7 +37,7 @@ export default { default: '#fff', }, value: { - type: [String, Number] + type: [String, Number, Boolean] } }, diff --git a/src/admin/pages/Settings.vue b/src/admin/pages/Settings.vue index 522f88d425..73b05e31ff 100644 --- a/src/admin/pages/Settings.vue +++ b/src/admin/pages/Settings.vue @@ -55,6 +55,9 @@
{{ __( 'Documentation', 'dokan-lite' ) }}
+