Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Module/form manager #2098

Open
wants to merge 126 commits into
base: develop
Choose a base branch
from
Open

Module/form manager #2098

wants to merge 126 commits into from

Conversation

nurul-umbhiya
Copy link
Contributor

@nurul-umbhiya nurul-umbhiya commented Dec 11, 2023

All Submissions:

  • My code follow the WordPress' coding standards
  • My code satisfies feature requirements
  • My code is tested
  • My code passes the PHPCS tests
  • My code has proper inline documentation
  • I've included related pull request(s) (optional)
  • I've included developer documentation (optional)
  • I've added proper labels to this pull request

Changes proposed in this Pull Request:

Related Pull Request(s)

https://github.com/getdokan/dokan-pro/pull/2883

Closes

How to test the changes in this Pull Request:

  • Steps or issue link

Changelog entry

Title

Detailed Description of the pull request. What was previous behaviour
and what will be changed in this PR.

Before Changes

Describe the issue before changes with screenshots(s).

After Changes

Describe the issue after changes with screenshot(s).

Feature Video (optional)

Link of detailed video if this PR is for a feature.

PR Self Review Checklist:

  • Code is not following code style guidelines
  • Bad naming: make sure you would understand your code if you read it a few months from now.
  • KISS: Keep it simple, Sweetie (not stupid!).
  • DRY: Don't Repeat Yourself.
  • Code that is not readable: too many nested 'if's are a bad sign.
  • Performance issues
  • Complicated constructions that need refactoring or comments: code should almost always be self-explanatory.
  • Grammar errors.

FOR PR REVIEWER ONLY:

As a reviewer, your feedback should be focused on the idea, not the person. Seek to understand, be respectful, and focus on constructive dialog.

As a contributor, your responsibility is to learn from suggestions and iterate your pull request should it be needed based on feedback. Seek to collaborate and produce the best possible contribution to the greater whole.

  • Correct — Does the change do what it’s supposed to? ie: code 100% fulfilling the requirements?
  • Secure — Would a nefarious party find some way to exploit this change? ie: everything is sanitized/escaped appropriately for any SQL or XSS injection possibilities?
  • Readable — Will your future self be able to understand this change months down the road?
  • Elegant — Does the change fit aesthetically within the overall style and architecture?

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a description property for various product types, enhancing product detail visibility.
    • Updated category label display to indicate required fields more clearly.
    • Added functionality for resetting all fields in the settings interface.
    • Enhanced flexibility in custom field components with new props for better integration.
    • Introduced a structured template for editing products within the Dokan Dashboard.
    • Added a method for managing product attributes and improved product creation and updating processes.
    • Enhanced product form with new validation and dynamic category handling in the product editor.
  • Bug Fixes

    • Improved handling of the required attribute in input fields for better validation.
    • Simplified logic for notice visibility assertions in the promotion page.
  • Style

    • Adjusted styling for product listing and editing components for improved layout.
    • Minor layout and styling adjustments on the settings page for better visual presentation.
  • Documentation

    • Enhanced comments and documentation for clarity, particularly around new features and changes.
  • Refactor

    • Modernized product handling logic to align with WooCommerce standards, improving maintainability.
  • Chores

    • Removed deprecated methods related to product file downloads and coupon totals.

Sorry, something went wrong.

devAsadNur and others added 25 commits September 1, 2023 11:50
…nd made Single Step Product Edit page as default

refactor: replaced product add/edit page code with OOP
fix: added Dokan Pro checks for `product_vendors_can_create_tags` and `product_category_style` admin settings
… dokan_product_edit_meta_data, this will save some executions

refactor: removed update_post_meta and get_post_meta references
…Manager.php to prevent direct access to the files
…o module/form_manager
@nurul-umbhiya nurul-umbhiya added the In Progress The issues is being worked on label Dec 11, 2023
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Outside diff range and nitpick comments (3)
assets/src/less/products.less (1)

472-499: LGTM: Well-structured form layout implementation.

The shipping dimension options layout is well-organized with proper spacing and width calculations. The 23.3% width with 2% margin for form groups ensures proper alignment in the grid system.

Consider using CSS custom properties (variables) for the repeated values:

 .dokan-shipping-dimention-options {
   .dokan-form-group {
-    width: 23.3%;
+    width: var(--form-group-width, 23.3%);
-    margin-right: 2%;
+    margin-right: var(--form-group-margin, 2%);
   }
 }
includes/Assets.php (1)

1040-1063: Track refactoring of variation-related strings.

The variation-related strings are properly implemented, but there's a TODO comment indicating they should be moved to Dokan Pro Assets class.

Would you like me to create a GitHub issue to track the planned refactoring of these strings to the Dokan Pro Assets class?

includes/ProductForm/Init.php (1)

269-269: Address the TODO: Add support for creating tags

There's a TODO comment indicating that support for creating tags needs to be added. Implement this functionality to enhance the product tagging feature.

Would you like assistance in implementing support for creating tags, or should I open a GitHub issue to track this task?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 40b08c6 and dd6f3db.

📒 Files selected for processing (3)
  • assets/src/less/products.less (3 hunks)
  • includes/Assets.php (8 hunks)
  • includes/ProductForm/Init.php (1 hunks)
🔇 Additional comments (8)
assets/src/less/products.less (1)

284-286: LGTM: Clean utility class implementation.

The .dokan-hide class follows standard naming conventions and uses the correct CSS property for hiding elements.

includes/Assets.php (4)

9-9: LGTM: Import statement follows PSR-4 standard.

The import statement for ProductFormFactory is properly namespaced and follows PSR-4 autoloading conventions.


522-522: LGTM: Proper dependency management.

The addition of 'jquery-tiptip' as a dependency is appropriate as it's a WooCommerce-provided script needed for tooltip functionality.


979-993: LGTM: Proper internationalization of UI strings.

The added localization strings are:

  • Properly internationalized using WordPress i18n functions
  • Correctly escaped where necessary
  • Clear and user-friendly

1209-1209: LGTM: Proper filter implementation with sensible default.

The product types filter is properly implemented with 'simple' as the default value, allowing for easy extension through the WordPress filter system.

includes/ProductForm/Init.php (3)

581-583: Check sanitize_callback parameters for Downloads field

The sanitize_callback for the Downloads field accepts multiple parameters ($file_names, $file_urls, $file_hashes), which may not align with the expected callback signature. Ensure that the callback function receives the appropriate parameters as per the implementation.

Please confirm that the form handling logic passes these parameters correctly to the sanitize_callback. If not, adjust the function to accept a single $value parameter or modify the form processing accordingly.


92-94: Verify sanitize_callback parameters for Slug field

The sanitize_callback for the Slug field accepts multiple parameters ($slug, $product_id, $status, $post_parent), which may not match the expected callback signature. Ensure that this function is called with the correct parameters during form processing.

Confirm that the form processing code provides these parameters to the sanitize_callback. If not, adjust the callback to match the expected usage.


223-231: Ensure proper parameter passing in sanitize_callback for Categories field

The sanitize_callback function for the Categories field accepts $categories and $product as parameters. Verify that the form processing logic supplies these parameters when invoking the callback.

Please ensure that the callback receives the correct parameters during execution. If necessary, adjust the form handling code to pass the required arguments.

@@ -972,7 +998,7 @@

.instruction-inside {
text-align: center;
padding-top: 40px;
padding-top: 80px;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Padding change appears inconsistent with responsive design patterns

The codebase shows multiple declarations of .instruction-inside with different padding approaches:

  • One instance uses percentage-based padding (20% top, 30% bottom)
  • Another uses the new fixed 80px padding-top
  • These different padding styles appear in the same file without clear responsive breakpoints

The change from percentage-based padding to fixed pixels could cause layout issues across different viewport sizes, especially since the file already contains mobile-specific media queries (@media max-width: 430px).

Recommend:

  • Maintain consistent padding strategy (either percentage or fixed)
  • If fixed padding is required, add appropriate media queries for smaller screens
  • Consider reverting to percentage-based padding for better responsiveness
🔗 Analysis chain

Verify the increased padding impact on layout.

The padding-top has been significantly increased from 20px to 80px. While this might improve visual spacing, please verify that it doesn't cause any layout issues or excessive whitespace in different viewport sizes.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any responsive design considerations for the instruction-inside class
# Look for media queries or responsive adjustments that might affect this padding

rg -A 5 "instruction-inside.*{" --type less
rg "@media.*{" --type less

Length of output: 262


Script:

#!/bin/bash
# Fix the regex syntax and search for instruction-inside class and media queries
rg -A 5 "instruction-inside" --type less
rg "@media" --type less

Length of output: 4309

Comment on lines +13 to +14
* @since DOKAN_SINCE
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Replace placeholder 'DOKAN_SINCE' with the actual version number

The @since DOKAN_SINCE tag in the class docblock should be replaced with the actual version number before release for accurate documentation.

Comment on lines +20 to +22
* @since DOKAN_SINCE
*
* @return void
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Update @since DOKAN_SINCE in method docblock

The @since DOKAN_SINCE tag in the __construct() method docblock should be updated with the appropriate version number.

Comment on lines +31 to +34
* @since DOKAN_SINCE
*
* @return void
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure @since DOKAN_SINCE tags are updated

All occurrences of @since DOKAN_SINCE in the method docblocks should be replaced with the actual version number to maintain accurate documentation.

Also applies to: 45-48, 55-58, 88-91, 107-110

Comment on lines 354 to 366
Elements::DOWNLOADABLE, [
'title' => __( 'Downloadable', 'dokan-lite' ),
'description' => __( 'Virtual products are intangible and are not shipped.', 'dokan-lite' ),
'field_type' => 'checkbox',
'name' => '_downloadable',
'additional_properties' => [
'value' => 'yes',
],
'sanitize_callback' => function ( $value ) {
return ! empty( $value ) && 'yes' === $value;
},
]
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Inconsistent use of checkbox values: 'yes' vs 'on'

The checkboxes for Elements::DOWNLOADABLE and Elements::VIRTUAL use 'value' => 'yes' in additional_properties, and the sanitize_callback checks for 'yes'. However, the dependency_condition checks for 'value' => 'on'. This inconsistency may lead to dependency conditions not working as expected.

Update the dependency_condition values to match the checkbox values:

// For Date On Sale From field
- 'value'    => 'on',
+ 'value'    => 'yes',

// For Date On Sale To field
- 'value'    => 'on',
+ 'value'    => 'yes',

// For Downloadable Options section
- 'value'    => 'on',
+ 'value'    => 'yes',

Also applies to: 368-381

Comment on lines 355 to 356
'title' => __( 'Downloadable', 'dokan-lite' ),
'description' => __( 'Virtual products are intangible and are not shipped.', 'dokan-lite' ),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Descriptions for 'Downloadable' and 'Virtual' fields are swapped

The description for the Downloadable field describes virtual products, and the description for the Virtual field describes downloadable products. Please swap the descriptions to match the correct fields.

Apply this diff to fix the descriptions:

// For the 'Downloadable' field
- 'description'           => __( 'Virtual products are intangible and are not shipped.', 'dokan-lite' ),
+ 'description'           => __( 'Downloadable products give access to a file upon purchase.', 'dokan-lite' ),

// For the 'Virtual' field
- 'description'           => __( 'Downloadable products give access to a file upon purchase.', 'dokan-lite' ),
+ 'description'           => __( 'Virtual products are intangible and are not shipped.', 'dokan-lite' ),

Also applies to: 370-371

Comment on lines +255 to +277
/**
* 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle errors properly in sanitize_callback for Tags field

In the sanitize_callback function for the Tags field, returning a string error message is not appropriate for error handling. Instead, consider returning a WP_Error object or using a validation mechanism to handle the error.

Apply this diff to correct the error handling:

- return sprintf( __( 'You can only select %s tags', 'dokan-lite' ), number_format_i18n( $maximum_tags_select_length ) );
+ return new \WP_Error(
+     'too_many_tags',
+     sprintf( __( 'You can only select %s tags', 'dokan-lite' ), number_format_i18n( $maximum_tags_select_length ) )
+ );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* 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
/**
* 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 new \WP_Error(
'too_many_tags',
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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Outside diff range and nitpick comments (2)
templates/products/dokan-category-header-ui.php (1)

25-25: Consider using more specific escaping function

While wp_kses_post() is secure, consider using esc_html() if $required_symbol is meant to only contain plain text (like an asterisk *). If it's meant to contain HTML (like <span class="required">*</span>), then wp_kses_post() is appropriate.

-        <?php echo wp_kses_post( $required_symbol ?? '' ); ?>
+        <?php echo esc_html( $required_symbol ?? '' ); ?>
includes/Dashboard/Templates/Products.php (1)

373-375: Enhance nonce verification failure handling

When nonce verification fails, it's good practice to inform the user about the issue. Consider adding an error message or redirecting the user to an error page to improve the user experience.

Apply this diff to add an error message upon nonce verification failure:

if ( ! isset( $_POST['dokan_edit_product_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['dokan_edit_product_nonce'] ), 'dokan_edit_product' ) ) {
+   self::$errors[] = __( 'Security verification failed. Please try again.', 'dokan-lite' );
    return;
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between dd6f3db and fabbc1a.

📒 Files selected for processing (3)
  • includes/Dashboard/Templates/Products.php (9 hunks)
  • includes/ProductForm/Init.php (1 hunks)
  • templates/products/dokan-category-header-ui.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • includes/ProductForm/Init.php
🔇 Additional comments (4)
templates/products/dokan-category-header-ui.php (2)

23-26: HTML structure improvement was previously suggested

A previous review already suggested improving the HTML structure by using the <label> tag directly instead of wrapping it in a <div>. That suggestion is still valid but unrelated to the current changes.


23-26: Consider PHP version compatibility with null coalescing operator

The null coalescing operator (??) requires PHP 7.0+. While most modern WordPress installations use PHP 7.0+, it's good practice to ensure compatibility or document the minimum PHP version requirement.

✅ Verification successful

PHP version compatibility is not a concern

The codebase already requires PHP 7.4 as specified in README.md, which fully supports the null coalescing operator (introduced in PHP 7.0). The usage is safe and aligned with the project's minimum requirements.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if minimum PHP version is specified in composer.json or readme
rg -i "require.*php.*[0-9]" composer.json README.md

Length of output: 87

includes/Dashboard/Templates/Products.php (2)

153-157: Verify product creation permissions and error handling

While creating a new product, ensure that necessary permission checks are in place and handle any potential exceptions or errors that may occur during the product creation process.

Run the following script to confirm that product creation respects user permissions:

#!/bin/bash
# Description: Verify that only authorized users can create new products.

# Test: Search for permission checks before product creation.
rg 'new WC_Product_Simple' -A 10 | rg 'current_user_can\('

481-487: Ensure proper sanitization of input data

While sanitizing the $file_names, $file_urls, and $file_hashes, consider adding additional validation to ensure the data is safe and meets expected formats. This helps prevent potential security issues related to file handling.

Run the following script to check for any unsanitized inputs:

*
* @var array
*/
public static $errors = [];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid using static properties for instance-specific data

Using static properties like $errors, $product_cat, and $post_content can lead to unexpected behavior in environments where multiple instances of the Products class are used concurrently. This may cause data conflicts between users. Consider converting these static properties to instance properties to ensure data encapsulation and prevent potential conflicts.

Apply this diff to change static properties to instance properties:

-public static $errors = [];
-public static $product_cat;
-public static $post_content;
+public $errors = [];
+public $product_cat;
+public $post_content;

Also applies to: 38-38, 45-45

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' );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Replace informal error message with a user-friendly message

The error message 'I swear this is not your product!' is unprofessional and may not be appropriate for users. Please replace it with a clearer and more formal message, such as 'You do not have permission to edit this product.'

Apply this diff to fix the error message:

- self::$errors[] = __( 'I swear this is not your product!', 'dokan-lite' );
+ self::$errors[] = __( 'You do not have permission to edit this product.', 'dokan-lite' );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
self::$errors[] = __( 'I swear this is not your product!', 'dokan-lite' );
self::$errors[] = __( 'You do not have permission to edit this product.', 'dokan-lite' );

Comment on lines +136 to +145
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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Replace informal error message with a user-friendly message

The error message 'Are you cheating?' is informal and may confuse or offend users. Consider replacing it with a more professional and descriptive message, such as 'Invalid request.' or 'Security verification failed.'

Apply this diff to fix the error message:

- 'message' => __( 'Are you cheating?', 'dokan-lite' ),
+ 'message' => __( 'Invalid request.', 'dokan-lite' ),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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;
}
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' => __( 'Invalid request.', 'dokan-lite' ),
]
);
return;
}

Comment on lines +483 to +485
$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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Validate and sanitize file upload inputs

Ensure that the file URLs provided in $_POST['_wc_file_urls'] are properly validated and sanitized to prevent potential security risks such as XSS or file inclusion vulnerabilities.

Consider using esc_url_raw() to sanitize URLs:

$field_value = $field->sanitize(
    $file_names,
-   $file_urls,
+   array_map( 'esc_url_raw', $file_urls ),
    $file_hashes
);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$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
$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, array_map( 'esc_url_raw', $file_urls ), $file_hashes ); //phpcs:ignore

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (2)
includes/ProductForm/Factory.php (2)

188-188: Typo in documentation comment.

There's a misspelling in the docblock comment on line 188. The word "registerd" should be corrected to "registered."

Apply this diff to fix the typo:

-     * Returns registerd section with given section id.
+     * Returns registered section with given section id.

285-293: Improve exception handling in create_item method.

In the create_item method, exceptions are caught broadly, which might obscure specific errors during item creation. Additionally, the exception class is not fully qualified, which could cause issues if there's a local Exception class.

Consider specifying the global namespace for the Exception class and handling specific exceptions if applicable:

         try {
             return apply_filters( 'dokan_product_form_item_created', new $class_name( $id, $args ), $type );
-        } catch ( \Exception $e ) {
+        } catch ( \Exception $e ) {
             return new WP_Error(
                 'dokan_product_form_' . $type . '_class_creation',
                 $e->getMessage()
             );
         }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between fabbc1a and ec05bc8.

📒 Files selected for processing (2)
  • includes/Assets.php (8 hunks)
  • includes/ProductForm/Factory.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • includes/Assets.php

Comment on lines +278 to +281
esc_html__( 'You have attempted to register a duplicate form %1$s with WooCommerce Form: %2$s', 'dokan-lite' ),
$type,
'`' . $id . '`'
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Incorrect product name in error message.

The error message references "WooCommerce Form" instead of "Dokan Product Form." This may cause confusion for developers using the Dokan plugin.

Update the error message to correctly reference "Dokan Product Form":

                         esc_html__( 'You have attempted to register a duplicate form %1$s with WooCommerce Form: %2$s', 'dokan-lite' ),
+                        esc_html__( 'You have attempted to register a duplicate %1$s with Dokan Product Form: %2$s', 'dokan-lite' ),

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +84 to +92
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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Validate required parameters in add_section method.

Similarly, the add_section method does not validate that the required argument title is provided in the $args array. Omitting this can cause issues when creating a new section.

Add validation to ensure the title parameter is provided:

 public static function add_section( $id, $args ) {
     if ( empty( $args['id'] ) ) {
         $args['id'] = $id;
     }

+    if ( empty( $args['title'] ) ) {
+        return new WP_Error( 'dokan_product_form_missing_section_title', esc_html__( 'Section title is required.', 'dokan-lite' ) );
+    }
+
     $new_section = self::create_item( 'section', 'Section', $id, $args );
     if ( is_wp_error( $new_section ) ) {
         return $new_section;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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;
}
public static function add_section( $id, $args ) {
if ( empty( $args['id'] ) ) {
$args['id'] = $id;
}
if ( empty( $args['title'] ) ) {
return new WP_Error( 'dokan_product_form_missing_section_title', esc_html__( 'Section title is required.', 'dokan-lite' ) );
}
$new_section = self::create_item( 'section', 'Section', $id, $args );
if ( is_wp_error( $new_section ) ) {
return $new_section;
}

Comment on lines +55 to +63
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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Validate required parameters in add_field method.

The add_field method does not validate that required arguments such as title and section are provided in the $args array. This may lead to errors if these parameters are missing when creating a new field.

Consider adding validation to ensure that required parameters are provided:

 public static function add_field( $id, $args ) {
     if ( empty( $args['id'] ) ) {
         $args['id'] = $id;
     }

+    if ( empty( $args['title'] ) ) {
+        return new WP_Error( 'dokan_product_form_missing_field_title', esc_html__( 'Field title is required.', 'dokan-lite' ) );
+    }
+
+    if ( empty( $args['section'] ) ) {
+        return new WP_Error( 'dokan_product_form_missing_field_section', esc_html__( 'Field section is required.', 'dokan-lite' ) );
+    }
+
     $new_field = self::create_item( 'field', 'Field', $id, $args );
     if ( is_wp_error( $new_field ) ) {
         return $new_field;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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;
}
public static function add_field( $id, $args ) {
if ( empty( $args['id'] ) ) {
$args['id'] = $id;
}
if ( empty( $args['title'] ) ) {
return new WP_Error( 'dokan_product_form_missing_field_title', esc_html__( 'Field title is required.', 'dokan-lite' ) );
}
if ( empty( $args['section'] ) ) {
return new WP_Error( 'dokan_product_form_missing_field_section', esc_html__( 'Field section is required.', 'dokan-lite' ) );
}
$new_field = self::create_item( 'field', 'Field', $id, $args );
if ( is_wp_error( $new_field ) ) {
return $new_field;
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (5)
dokan-class.php (1)

17-17: Consider documenting the dashboard integration pattern

The addition of the dashboard manager and template directory constant suggests an architectural shift towards better separation of concerns. Consider:

  1. Documenting the integration pattern between the dashboard manager and other components.
  2. Adding usage examples in the developer documentation.
  3. Creating migration guides if this changes existing template handling patterns.

Also applies to: 218-218

includes/Product/Hooks.php (1)

Line range hint 420-460: Add null check and optimize array operations.

The method needs some improvements for robustness and efficiency:

  1. Add null check for vendor object
  2. Simplify array operations

Consider this refactor:

 public function product_review_notification_recipients( $emails, $comment_id ) {
     $comment = get_comment( $comment_id );
     
     $product = wc_get_product( $comment->comment_post_ID );
     if ( ! $product ) {
         return $emails;
     }
     
-    // Facilitate unsetting below without knowing the keys.
-    $filtered_emails = array_flip( $emails );
-
     $vendor = dokan_get_vendor_by_product( $product->get_id() );
+    if ( ! $vendor ) {
+        return $emails;
+    }
     
-    if ( array_key_exists( $vendor->get_email(), $filtered_emails ) ) {
-        unset( $filtered_emails[ $vendor->get_email() ] );
-    }
+    $filtered_emails = array_diff( $emails, [ $vendor->get_email() ] );
     
-    // revert the array flip
-    $filtered_emails = array_flip( $filtered_emails );
-
     // get admin email
     $admin_email = get_option( 'admin_email' );
-    if ( ! in_array( $admin_email, $filtered_emails, true ) ) {
-        $filtered_emails[] = $admin_email;
-    }
+    $filtered_emails = array_unique( [...$filtered_emails, $admin_email] );
     
     return $filtered_emails;
 }
includes/Dashboard/Templates/Products.php (1)

205-208: Consider caching ProductFormFactory section instances

Multiple calls to ProductFormFactory::get_section() with the same section name could be optimized by caching the section instances. This would reduce unnecessary object creation and improve performance.

+private static $cached_sections = [];

 public static function load_download_virtual_template( $post, $post_id ) {
     $product = wc_get_product( $post_id );
     if ( ! $product ) {
         return;
     }

-    $section = ProductFormFactory::get_section( 'downloadable' );
+    if ( ! isset( self::$cached_sections['downloadable'] ) ) {
+        self::$cached_sections['downloadable'] = ProductFormFactory::get_section( 'downloadable' );
+    }
+    $section = self::$cached_sections['downloadable'];

Also applies to: 237-240, 273-276, 303-306

tests/pw/utils/interfaces.ts (1)

Line range hint 126-131: Remove duplicate description properties in interfaces.

Several interfaces have duplicate description properties which is redundant and may cause confusion:

  • simple interface (lines 126 and 131)
  • downloadable interface (lines 140 and 145)
  • variable interface (lines 161 and 171)
  • external interface (lines 182 and 185)
  • vendorSubscription interface (lines 247 and 253)

Remove the duplicate properties to maintain clean and unambiguous interfaces. For each interface, keep only one description property.

interface simple {
    productType: string;
    productName: () => string;
    category: string;
    regularPrice: () => string;
    description: string;
    storeName: string;
    status: string;
    stockStatus: boolean;
    editProduct: string;
-   description: string;
    saveSuccessMessage: string;
}

interface downloadable {
    productType: string;
    productName: () => string;
    category: string;
    regularPrice: () => string;
    description: string;
    storeName: string;
    status: string;
    stockStatus: boolean;
    editProduct: string;
-   description: string;
    saveSuccessMessage: string;
    // ... rest of the interface
}

interface variable {
    productType: string;
    productName: () => string;
    category: string;
    regularPrice: () => string;
    description: string;
    storeName: string;
    status: string;
    stockStatus: boolean;
    attribute: string;
    attributeTerms: string[];
    variations: {
        linkAllVariation: string;
        variableRegularPrice: string;
    };
-   description: string;
    saveSuccessMessage: string;
}

interface external {
    productType: string;
    productName: () => string;
    productUrl: string;
    buttonText: string;
    category: string;
    regularPrice: () => string;
    description: string;
    storeName: string;
    status: string;
-   description: string;
    saveSuccessMessage: string;
}

interface vendorSubscription {
    productType: string;
    productName: () => string;
    category: string;
    regularPrice: () => string;
    description: string;
    numberOfProducts: string;
    packValidity: string;
    advertisementSlot: string;
    expireAfterDays: string;
    storeName: string;
-   description: string;
    status: string;
}

Also applies to: 140-145, 161-171, 182-185, 247-253

includes/functions.php (1)

702-716: LGTM! Consider adding ARIA attributes for accessibility

The addition of the description tooltip is well implemented with proper escaping. For better accessibility, consider adding ARIA attributes.

 <i
     class="fas fa-question-circle tips"
     aria-hidden="true"
+    role="tooltip"
+    aria-label="<?php echo esc_attr( $desc ); ?>"
     data-title="<?php echo esc_attr( $desc ); ?>">
 </i>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ec05bc8 and faae548.

📒 Files selected for processing (10)
  • assets/src/less/products.less (3 hunks)
  • dokan-class.php (2 hunks)
  • includes/Assets.php (8 hunks)
  • includes/Dashboard/Templates/Products.php (9 hunks)
  • includes/DependencyManagement/Providers/CommonServiceProvider.php (1 hunks)
  • includes/Product/Hooks.php (4 hunks)
  • includes/functions.php (2 hunks)
  • tests/pw/pages/settingsPage.ts (0 hunks)
  • tests/pw/utils/dbData.ts (0 hunks)
  • tests/pw/utils/interfaces.ts (7 hunks)
💤 Files with no reviewable changes (2)
  • tests/pw/pages/settingsPage.ts
  • tests/pw/utils/dbData.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • assets/src/less/products.less
  • includes/Assets.php
🔇 Additional comments (10)
includes/DependencyManagement/Providers/CommonServiceProvider.php (2)

69-71: LGTM! Service registration follows established patterns.

The registration of ProductForm\Init service maintains consistency with other service registrations in the provider, properly utilizing singleton pattern via addShared() and tagged appropriately.


69-71: Verify ProductForm\Init implementation.

Let's ensure the ProductForm\Init class exists and is properly implemented.

✅ Verification successful

Let me check the actual implementation of the Init class to verify it properly.


The ProductForm\Init class is properly implemented and verified.

The class exists at includes/ProductForm/Init.php and is well-structured with:

  • Proper namespace WeDevs\Dokan\ProductForm
  • Constructor that hooks into WordPress init action
  • Complete implementation of form field initialization methods for:
    • General product fields
    • Inventory management
    • Downloadable options
    • Other product settings

The class is correctly registered in the dependency container and its implementation matches its intended purpose of initializing product form fields.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the existence and implementation of ProductForm\Init class

# Check if the Init class file exists
fd -e php "Init.php" includes/ProductForm

# Check the class implementation
ast-grep --pattern 'class Init {
  $$$
}'

# Look for any usage of this class
rg -l "ProductForm.Init" --type php

Length of output: 217


Script:

#!/bin/bash
# Get the implementation of Init class
cat includes/ProductForm/Init.php

Length of output: 29629

dokan-class.php (1)

17-17: LGTM: Well-documented dashboard property addition

The new dashboard property is properly documented with the correct namespace and follows the existing pattern of property documentation in the class.

includes/Product/Hooks.php (3)

8-8: LGTM! Good security practice.

The addition of the direct access prevention check follows WordPress security best practices.


35-35: LGTM! Hook registration is correct.

The filter hook is properly registered with appropriate priority and follows WordPress naming conventions.


369-382: Previous review comment is still applicable.

The suggested refactor from the previous review would improve the code's clarity and efficiency.

includes/Dashboard/Templates/Products.php (2)

153-156: Consider adding status transition handling for auto-draft products

When creating a new product with 'auto-draft' status, consider implementing cleanup logic for abandoned drafts to prevent database clutter. Also, verify that the status transitions correctly when the product is actually saved.


57-70: Verify hook priorities for potential race conditions

The action hooks have different priorities (11, 10, 5, 85) which could potentially lead to execution order issues. Consider documenting the reasoning behind these specific priority numbers or standardizing them if there's no specific requirement.

✅ Verification successful

Let me gather more context about the template loading sequence by checking the implementation of these methods.


Let me try a different approach using ripgrep to find these methods.


Let me check if there are any template files that these methods load to understand their dependencies.


Based on the analysis of the template loading sequence and their implementations, I can now provide a final response:

Hook priorities are correctly set based on template dependencies

The different priority numbers in the action hooks are intentional and necessary:

  • load_product_edit_template (11): Main template loader that sets up the product editing environment
  • load_download_virtual_template (10): Loads first section after title
  • load_inventory_template (5): Loads before downloadable template as inventory data is prerequisite
  • load_downloadable_template (10): Depends on inventory being loaded first
  • load_others_template (85): Intentionally loads last after all core product data sections

Each template section loads independently with proper product validation, making the sequence safe from race conditions.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for any other plugins hooking into the same actions with competing priorities
rg -A 2 "add_action\(\s*'dokan_render_product_edit_template'|add_action\(\s*'dokan_product_edit_after_title'|add_action\(\s*'dokan_product_edit_after_main'|add_action\(\s*'dokan_product_edit_after_inventory_variants'"

Length of output: 1129


Script:

#!/bin/bash
# Check the implementation of template loading methods to understand the dependency order
ast-grep --pattern 'function load_product_edit_template($$$) { $$$ }'
ast-grep --pattern 'function load_download_virtual_template($$$) { $$$ }'
ast-grep --pattern 'function load_inventory_template($$$) { $$$ }'
ast-grep --pattern 'function load_downloadable_template($$$) { $$$ }'
ast-grep --pattern 'function load_others_template($$$) { $$$ }'

Length of output: 354


Script:

#!/bin/bash
# Search for the template loading methods implementations
rg -A 5 "public (static )?function (load_product_edit_template|load_download_virtual_template|load_inventory_template|load_downloadable_template|load_others_template)"

Length of output: 2424


Script:

#!/bin/bash
# Search for template loading calls within these methods
rg -B 2 -A 10 "dokan_get_template(_part)?\(" includes/Dashboard/Templates/Products.php

Length of output: 3270

tests/pw/utils/interfaces.ts (1)

126-126: LGTM: Description property additions are consistent.

The addition of the description property across product-related interfaces is consistent and well-structured. The changes align well with the PR objectives of enhancing product form handling.

Also applies to: 140-140, 161-161, 182-182, 247-247

includes/functions.php (1)

652-652: Improved required attribute validation

The change from isset() to ! empty() provides more robust validation by ensuring the required attribute has a truthy value, not just that it exists.

@@ -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' );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Template directory constant needs to be used consistently

The verification reveals inconsistent template path handling across the codebase:

  • Most template loading is handled through functions like dokan_locate_template() and dokan_get_template() which use dynamic path resolution
  • Email templates use DOKAN_DIR . '/templates/'
  • Only one direct usage of DOKAN_TEMPLATE_DIR found in templates/products/edit/edit-product-single.php

For consistency and maintainability:

  • Replace DOKAN_DIR . '/templates/' usage in email classes with DOKAN_TEMPLATE_DIR
  • Consider updating the direct include in edit-product-single.php to use template loading functions instead
🔗 Analysis chain

Verify template directory constant usage

The constant definition looks good, but let's ensure it's being used consistently across the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for existing usages of hardcoded template paths that should use DOKAN_TEMPLATE_DIR

# Search for hardcoded template paths
rg -g '*.php' "(?i)(templates?.*directory|template.*path|template.*dir)"

# Search for current usage of the new constant
rg "DOKAN_TEMPLATE_DIR"

Length of output: 6453

Comment on lines +379 to +381
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure all error messages are translatable

Some error messages are not wrapped in translation functions. Ensure all user-facing messages are translatable for proper localization.

-self::$errors[] = 'No product id is set!';
+self::$errors[] = __( 'No product id is set!', 'dokan-lite' );

-self::$errors[] = 'No product found with given product id!';
+self::$errors[] = __( 'No product found with given product id!', 'dokan-lite' );

Also applies to: 399-402

Comment on lines +452 to +455
// 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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add validation for discount percentage range

When handling bulk discounts, add validation to ensure the discount percentage is within a reasonable range (e.g., 0-100%). Currently, there's no validation for the discount amount.

 if ( in_array( $field->get_id(), [ '_lot_discount_quantity', '_lot_discount_amount' ], true ) && isset( $_POST['_is_lot_discount'] ) && 'no' === $_POST['_is_lot_discount'] ) {
     continue;
 }

+// Add validation for discount percentage
+if ( '_lot_discount_amount' === $field->get_id() && isset( $_POST['_is_lot_discount'] ) && 'yes' === $_POST['_is_lot_discount'] ) {
+    $discount = floatval( $_POST['_lot_discount_amount'] );
+    if ( $discount < 0 || $discount > 100 ) {
+        self::$errors['_lot_discount_amount'] = __( 'Discount percentage must be between 0 and 100.', 'dokan-lite' );
+        continue;
+    }
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 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;
}
// 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;
}
// Add validation for discount percentage
if ( '_lot_discount_amount' === $field->get_id() && isset( $_POST['_is_lot_discount'] ) && 'yes' === $_POST['_is_lot_discount'] ) {
$discount = floatval( $_POST['_lot_discount_amount'] );
if ( $discount < 0 || $discount > 100 ) {
self::$errors['_lot_discount_amount'] = __( 'Discount percentage must be between 0 and 100.', 'dokan-lite' );
continue;
}
}

# Conflicts:
#	includes/ProductCategory/Categories.php
#	templates/products/edit-product-single.php
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Outside diff range and nitpick comments (4)
src/admin/pages/Settings.vue (2)

52-54: Add prop validation for action button properties.

Consider adding prop validation to ensure action_button and action_button_label are properly defined in the section object.

 export default {
   // ...
+  props: {
+    section: {
+      type: Object,
+      required: true,
+      validator: function(obj) {
+        return typeof obj.action_button === 'string' && 
+               typeof obj.action_button_label === 'string';
+      }
+    }
+  },
   // ...
 }

71-71: Optimize prop passing to Fields component.

Passing the entire settingFields object could trigger unnecessary re-renders. Consider passing only the required subset of fields for the current section.

-:setting-fields='settingFields'
+:setting-fields='settingFields[index]'
src/admin/components/Fields.vue (1)

498-499: Add validation for custom field components.

The custom field components receive new props but lack validation. Consider adding prop validation in the component registration process.

Add validation when registering custom components through the getDokanCustomFieldComponents filter:

// Example validation in custom component registration
dokan.hooks.addFilter('getDokanCustomFieldComponents', 'dokan', function(components) {
  return components.map(component => {
    // Ensure component has prop validation
    if (!component.props) {
      component.props = {
        allSettingsValues: {
          type: Object,
          required: true
        },
        settingFields: {
          type: Object,
          required: true
        }
      };
    }
    return component;
  });
});
templates/products/edit/edit-product-single.php (1)

15-15: Avoid using global variables and disabling coding standards

Using global $post; and disabling WordPress.WP.GlobalVariablesOverride.Prohibited is discouraged. Refactor the code to pass the $post variable explicitly without relying on the global scope to enhance code clarity and maintainability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between faae548 and 1c91942.

📒 Files selected for processing (6)
  • assets/src/less/products.less (3 hunks)
  • dokan-class.php (2 hunks)
  • src/admin/components/Fields.vue (2 hunks)
  • src/admin/pages/Settings.vue (6 hunks)
  • templates/products/dokan-category-header-ui.php (1 hunks)
  • templates/products/edit/edit-product-single.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • assets/src/less/products.less
  • dokan-class.php
  • templates/products/dokan-category-header-ui.php
🔇 Additional comments (2)
src/admin/pages/Settings.vue (1)

Line range hint 763-835: LGTM! Styles are well-organized and consistent.

The new styles for the settings header and action link follow the existing patterns and maintain visual consistency.

templates/products/edit/edit-product-single.php (1)

170-170: 🛠️ Refactor suggestion

Use <button> element instead of <a> for dismissing alerts

Replace the <a> tag with a <button> element for the close button to improve accessibility and semantics, especially since the anchor lacks an href attribute.

Apply this diff to fix the issue:

-<a class="dokan-close" data-dismiss="alert">&times;</a>
+<button type="button" class="dokan-close" data-dismiss="alert">&times;</button>

Likely invalid or redundant comment.

Comment on lines +552 to +556
handleAction( sectionId ) {
if ( 'dokan_form_manager' === sectionId ) {
this.resetAllFields( sectionId );
}
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Decouple section-specific logic from handleAction method.

The method is tightly coupled to a specific section ID. Consider implementing a more flexible approach using a strategy pattern or action map.

-handleAction( sectionId ) {
-    if ( 'dokan_form_manager' === sectionId ) {
-        this.resetAllFields( sectionId );
-    }
-},
+const ACTION_HANDLERS = {
+    'dokan_form_manager': function(sectionId) {
+        this.resetAllFields(sectionId);
+    }
+};
+
+handleAction(sectionId) {
+    const handler = ACTION_HANDLERS[sectionId];
+    if (handler) {
+        handler.call(this, sectionId);
+    }
+},

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +558 to +575
resetAllFields( sectionId ) {
let settingFields = Object.entries( this.settingFields[sectionId] );
let clonedSectionSettingValues = JSON.parse( JSON.stringify( this.settingValues[sectionId] ) );

settingFields.forEach( ( key, value ) => {
let blockId = key[0];

if ( blockId ) {
let sectionValue = key[1];
for (const [FieldId, fieldData] of Object.entries(sectionValue.fields)) {
clonedSectionSettingValues[blockId]['fields'][FieldId]['visibility'] = fieldData.default.visibility;
clonedSectionSettingValues[blockId]['fields'][FieldId]['required'] = fieldData.default.required;
}
}
} );

this.settingValues[sectionId] = clonedSectionSettingValues;
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve resetAllFields method implementation.

The current implementation has several potential issues:

  1. Uses JSON parse/stringify for cloning which doesn't handle special values
  2. Nested loops could impact performance
  3. No error handling for malformed data
 resetAllFields(sectionId) {
+    try {
-        let settingFields = Object.entries(this.settingFields[sectionId]);
-        let clonedSectionSettingValues = JSON.parse(JSON.stringify(this.settingValues[sectionId]));
+        const clonedSectionSettingValues = structuredClone(this.settingValues[sectionId]);
+        const settingFields = this.settingFields[sectionId];
 
-        settingFields.forEach((key, value) => {
-            let blockId = key[0];
+        Object.entries(settingFields).forEach(([blockId, sectionValue]) => {
+            if (!blockId || !sectionValue?.fields) return;
 
-            if (blockId) {
-                let sectionValue = key[1];
-                for (const [FieldId, fieldData] of Object.entries(sectionValue.fields)) {
-                    clonedSectionSettingValues[blockId]['fields'][FieldId]['visibility'] = fieldData.default.visibility;
-                    clonedSectionSettingValues[blockId]['fields'][FieldId]['required'] = fieldData.default.required;
-                }
+            Object.entries(sectionValue.fields).forEach(([fieldId, fieldData]) => {
+                if (!fieldData?.default) return;
+                
+                clonedSectionSettingValues[blockId].fields[fieldId] = {
+                    ...clonedSectionSettingValues[blockId].fields[fieldId],
+                    visibility: fieldData.default.visibility,
+                    required: fieldData.default.required
+                };
+            });
+        });
 
-        });
         this.settingValues[sectionId] = clonedSectionSettingValues;
+    } catch (error) {
+        console.error('Failed to reset fields:', error);
+        // Consider showing user-friendly error message
     }
 },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
resetAllFields( sectionId ) {
let settingFields = Object.entries( this.settingFields[sectionId] );
let clonedSectionSettingValues = JSON.parse( JSON.stringify( this.settingValues[sectionId] ) );
settingFields.forEach( ( key, value ) => {
let blockId = key[0];
if ( blockId ) {
let sectionValue = key[1];
for (const [FieldId, fieldData] of Object.entries(sectionValue.fields)) {
clonedSectionSettingValues[blockId]['fields'][FieldId]['visibility'] = fieldData.default.visibility;
clonedSectionSettingValues[blockId]['fields'][FieldId]['required'] = fieldData.default.required;
}
}
} );
this.settingValues[sectionId] = clonedSectionSettingValues;
},
resetAllFields( sectionId ) {
try {
const clonedSectionSettingValues = structuredClone(this.settingValues[sectionId]);
const settingFields = this.settingFields[sectionId];
Object.entries(settingFields).forEach(([blockId, sectionValue]) => {
if (!blockId || !sectionValue?.fields) return;
Object.entries(sectionValue.fields).forEach(([fieldId, fieldData]) => {
if (!fieldData?.default) return;
clonedSectionSettingValues[blockId].fields[fieldId] = {
...clonedSectionSettingValues[blockId].fields[fieldId],
visibility: fieldData.default.visibility,
required: fieldData.default.required
};
});
});
this.settingValues[sectionId] = clonedSectionSettingValues;
} catch (error) {
console.error('Failed to reset fields:', error);
// Consider showing user-friendly error message
}
},

@@ -535,7 +537,7 @@
WithdrawCharges
},

props: ['id', 'fieldData', 'sectionId', 'fieldValue', 'allSettingsValues', 'errors', 'toggleLoadingState', 'validationErrors', 'dokanAssetsUrl'],
props: ['id', 'fieldData', 'sectionId', 'fieldValue', 'allSettingsValues', 'errors', 'toggleLoadingState', 'validationErrors', 'dokanAssetsUrl', 'settingFields'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add prop validation and documentation.

The new settingFields prop should include type validation and documentation to improve maintainability and prevent runtime errors.

-props: ['id', 'fieldData', 'sectionId', 'fieldValue', 'allSettingsValues', 'errors', 'toggleLoadingState', 'validationErrors', 'dokanAssetsUrl', 'settingFields'],
+props: {
+  id: { type: String, required: true },
+  fieldData: { type: Object, required: true },
+  sectionId: { type: String, required: true },
+  fieldValue: { type: Object, required: true },
+  allSettingsValues: { type: Object, required: true },
+  errors: { type: Array, default: () => [] },
+  toggleLoadingState: { type: Function, required: true },
+  validationErrors: { type: Array, default: () => [] },
+  dokanAssetsUrl: { type: String, required: true },
+  settingFields: {
+    type: Object,
+    required: true,
+    description: 'Configuration object containing all available setting fields'
+  }
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
props: ['id', 'fieldData', 'sectionId', 'fieldValue', 'allSettingsValues', 'errors', 'toggleLoadingState', 'validationErrors', 'dokanAssetsUrl', 'settingFields'],
props: {
id: { type: String, required: true },
fieldData: { type: Object, required: true },
sectionId: { type: String, required: true },
fieldValue: { type: Object, required: true },
allSettingsValues: { type: Object, required: true },
errors: { type: Array, default: () => [] },
toggleLoadingState: { type: Function, required: true },
validationErrors: { type: Array, default: () => [] },
dokanAssetsUrl: { type: String, required: true },
settingFields: {
type: Object,
required: true,
description: 'Configuration object containing all available setting fields'
}
}


<?php if ( $product->get_status() === 'publish' ) : ?>
<span class="dokan-right">
<a class="dokan-btn dokan-btn-theme dokan-btn-sm" href="<?php echo esc_url( $product->get_permalink() ); ?>" target="_blank"><?php esc_html_e( 'View Product', 'dokan-lite' ); ?></a>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add rel="noopener noreferrer" when using target="_blank

Including rel="noopener noreferrer" on links with target="_blank" prevents the new page from accessing the original window object via window.opener, enhancing security against phishing and tabnabbing attacks.

Apply this diff to fix the issue:

-<a class="dokan-btn dokan-btn-theme dokan-btn-sm" href="<?php echo esc_url( $product->get_permalink() ); ?>" target="_blank"><?php esc_html_e( 'View Product', 'dokan-lite' ); ?></a>
+<a class="dokan-btn dokan-btn-theme dokan-btn-sm" href="<?php echo esc_url( $product->get_permalink() ); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'View Product', 'dokan-lite' ); ?></a>

-<a href="<?php echo esc_url( $product->get_permalink() ); ?>" target="_blank"><?php esc_html_e( 'View Product &rarr;', 'dokan-lite' ); ?></a>
+<a href="<?php echo esc_url( $product->get_permalink() ); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'View Product &rarr;', 'dokan-lite' ); ?></a>

Also applies to: 182-182

Comment on lines +189 to +190
// we are using require_once intentionally
include DOKAN_TEMPLATE_DIR . '/products/edit/sections/general.php';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix inconsistency between comment and code regarding file inclusion

The comment mentions using require_once intentionally, but the code uses include. To ensure the file is included only once and prevent potential issues, replace include with require_once.

Apply this diff to fix the issue:

-// we are using require_once intentionally
-include DOKAN_TEMPLATE_DIR . '/products/edit/sections/general.php';
+// we are using require_once intentionally
+require_once DOKAN_TEMPLATE_DIR . '/products/edit/sections/general.php';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// we are using require_once intentionally
include DOKAN_TEMPLATE_DIR . '/products/edit/sections/general.php';
// we are using require_once intentionally
require_once DOKAN_TEMPLATE_DIR . '/products/edit/sections/general.php';

}

// while calling from theme, we need to check if the product id is passed or not
$post_id = isset( $_GET['product_id'] ) ? intval( wp_unslash( $_GET['product_id'] ) ) : $post->ID; //phpcs:ignore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use absint instead of intval for sanitizing numeric input

Using absint ensures you get a positive integer and aligns with WordPress coding standards for sanitizing user input.

Apply this diff to fix the issue:

-$post_id = isset( $_GET['product_id'] ) ? intval( wp_unslash( $_GET['product_id'] ) ) : $post->ID; //phpcs:ignore
+$post_id = isset( $_GET['product_id'] ) ? absint( wp_unslash( $_GET['product_id'] ) ) : $post->ID; //phpcs:ignore
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$post_id = isset( $_GET['product_id'] ) ? intval( wp_unslash( $_GET['product_id'] ) ) : $post->ID; //phpcs:ignore
$post_id = isset( $_GET['product_id'] ) ? absint( wp_unslash( $_GET['product_id'] ) ) : $post->ID; //phpcs:ignore

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Outside diff range and nitpick comments (5)
templates/products/edit/sections/general.php (5)

74-96: Simplify handling of single product type option

When there's only one product type option available (in this case, 'simple'), you're currently using a hidden input field. This can be simplified for better code clarity.

Instead of using an if...else block, you can set the default value directly or consider removing the product type selection when only one option is available.


37-66: Handle product slug editing consistently

The current implementation includes hidden fields for editable-post-name and edited-post-name but the slug editing functionality isn't fully implemented.

Ensure that the product slug can be edited if required, or remove the unused fields to prevent confusion.


392-456: Improve accessibility for text editors

The labels for the short description and description fields are correctly associated, but consider enhancing the accessibility of the wp_editor instances.

Add aria-label attributes or ensure the editors are fully accessible to users with assistive technologies.


327-345: Provide alt text for featured images

The featured image markup lacks alt attributes, which can affect accessibility and SEO.

Add meaningful alt text to the <img> tags to improve accessibility.

 <?php else : ?>
-    <img height="" width="" src="" alt="">
+    <img height="" width="" src="" alt="<?php esc_attr_e( 'Featured image', 'dokan-lite' ); ?>">
 <?php endif; ?>

255-261: Optimize category selection template inclusion

You're including a template part for the category selection. Ensure that the data passed to the template is sufficient and sanitized.

Double-check the Helper::get_saved_products_category() function to confirm it returns the expected data.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b92694d and cd4c744.

📒 Files selected for processing (1)
  • templates/products/edit/sections/general.php (1 hunks)
🔇 Additional comments (2)
templates/products/edit/sections/general.php (2)

103-135: Consistent use of currency symbols

Ensure that the currency symbol is consistently displayed across all price input fields.

The implementation correctly uses get_woocommerce_currency_symbol(), and the currency symbol is displayed as expected.

Also applies to: 138-185


163-185: Check sale schedule toggle functionality

The toggling between "Schedule" and "Cancel" links for sale scheduling might be confusing if not properly handled.

Ensure that the JavaScript handling the show/hide functionality of the sale schedule fields works correctly.

Run the following script to confirm that the event listeners are properly set:

✅ Verification successful

Let me analyze the JavaScript handling of sale scheduling with these broader search patterns.


Let me analyze the JavaScript handling of sale scheduling with these more targeted search patterns.


From analyzing the code, I found that the sale schedule functionality is properly implemented in the JavaScript code. The relevant code shows:

  1. The sale schedule toggle functionality is handled by the saleSchedule and cancelSchedule methods
  2. The toggle behavior is properly bound to click events on .sale_schedule and .cancel_sale_schedule elements
  3. The methods handle showing/hiding the date fields and clearing values appropriately

Sale schedule toggle functionality works as expected

The JavaScript implementation shows that clicking the "Schedule" link will:

  • Hide the schedule link
  • Show the cancel link
  • Show the date fields

And clicking "Cancel" will:

  • Hide the cancel link
  • Show the schedule link
  • Hide and clear the date fields
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that the sale schedule toggle links have correct event handlers.

# Test: Search for JavaScript handling 'sale_schedule' and 'cancel_sale_schedule' classes.
rg -A 5 '$(document).on\(' assets/js/ | rg 'sale_schedule|cancel_sale_schedule'

Length of output: 81


Script:

#!/bin/bash
# Let's try a broader search for sale schedule related JavaScript code
rg -i "sale.*schedule" assets/js/

# Also search for any click handlers in JavaScript files
rg "\.on\('click'" assets/js/

# Search for any files that might handle product scheduling
fd -e js . assets/js/ -x cat {} | rg -i "schedule|sale.*price"

Length of output: 125766


Script:

#!/bin/bash
# Let's search for JavaScript files specifically in the product-related directories
fd -e js . assets/js/dokan/product -x cat {} | rg -i "sale.*schedule|discount_schedule"

# Also search for any event handlers related to sale price scheduling
rg -i "sale.*schedule|discount_schedule" assets/js/dokan/product

# Let's also check the main product script
rg -i "sale.*schedule|discount_schedule" assets/js/dokan.js

Length of output: 68107

Comment on lines 107 to 135
<label for="<?php echo esc_attr( $regular_price->get_name() ); ?>" class="form-label">
<?php echo esc_html( $regular_price->get_title() ); ?>
<?php $regular_price->print_required_symbol(); ?>
<i
class="fas fa-question-circle tips <?php echo empty( $regular_price->get_help_content() ) ? 'dokan-hide' : ''; ?>"
aria-hidden="true"
data-title="<?php echo esc_attr( $regular_price->get_help_content() ); ?>">
</i>
</label>
<div class="dokan-input-group">
<span class="dokan-input-group-addon">
<?php echo esc_html( get_woocommerce_currency_symbol() ); ?>
</span>
<?php
dokan_post_input_box(
$product->get_id(),
$regular_price->get_name(),
[
'value' => $regular_price->get_value( $product ),
'class' => 'dokan-product-regular-price',
'placeholder' => $regular_price->get_placeholder(),
'required' => $regular_price->is_required(),
],
'price'
);
?>
</div>
</div>
<?php endif; ?>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure proper validation for sale price and regular price

It's important to validate that the sale price is not higher than the regular price, as this could cause confusion or incorrect pricing.

Consider adding a validation check to ensure that the sale price is less than the regular price. You can implement this by adding a custom JavaScript validation or server-side validation during form submission.

Also applies to: 151-185

Comment on lines +358 to +381
$product_images = is_array( $product_images ) ? $product_images : explode( ',', $product_images );

if ( $product_images ) :
foreach ( $product_images as $image_id ) :
if ( empty( $image_id ) ) :
continue;
endif;

$attachment_image = wp_get_attachment_image_src( $image_id, 'thumbnail' );
?>
<li class="image" data-attachment_id="<?php echo esc_attr( $image_id ); ?>">
<img src="<?php echo esc_url( $attachment_image[0] ); ?>" alt="">
<a href="#" class="action-delete" title="<?php esc_attr_e( 'Delete image', 'dokan-lite' ); ?>">&times;</a>
</li>
<?php
endforeach;
endif;
?>
<li class="add-image add-product-images tips" data-title="<?php esc_html_e( 'Add gallery image', 'dokan-lite' ); ?>">
<a href="#" class="add-product-images"><i class="fas fa-plus" aria-hidden="true"></i></a>
</li>
</ul>

<input type="hidden" id="product_image_gallery" name="<?php echo esc_attr( $gallery_images->get_name() ); ?>" value="<?php echo esc_attr( implode( ',', $product_images ) ); ?>">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Sanitize and validate gallery image IDs

When handling the gallery images, ensure that the image IDs are properly sanitized to prevent security issues.

Implement sanitization when processing the $product_images array to prevent potential security vulnerabilities.

Comment on lines +270 to +292
<label for="<?php echo esc_attr( $tags->get_name() ); ?>" class="form-label">
<?php echo $tags->get_title(); ?>
<?php $tags->print_required_symbol(); ?>
<i
class="fas fa-question-circle tips <?php echo empty( $tags->get_help_content() ) ? 'dokan-hide' : ''; ?>"
aria-hidden="true"
data-title="<?php echo esc_attr( $tags->get_help_content() ); ?>">
</i>
</label>
<select multiple="multiple" id="<?php echo esc_attr( $tags->get_name() ); ?>" name="<?php echo esc_attr( $tags->get_name() ); ?>" class="product_tag_search dokan-form-control" data-placeholder="<?php echo esc_attr( $tags->get_placeholder() ); ?>" <?php echo $tags->is_required() ? 'required' : ''; ?>>
<?php if ( ! empty( $terms ) ) : ?>
<?php
foreach ( $terms as $tax_term ) :
$tax_term = get_term_by( 'id', $tax_term, 'product_tag' );
if ( is_wp_error( $tax_term ) ) {
continue;
}
?>
<option value="<?php echo esc_attr( $tax_term->term_id ); ?>" selected="selected"><?php echo esc_html( $tax_term->name ); ?></option>
<?php endforeach ?>
<?php endif ?>
</select>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Escape output in tags selection

When outputting the tag terms, make sure all outputs are properly escaped to prevent XSS vulnerabilities.

Apply proper escaping functions like esc_attr() and esc_html() where necessary.

Comment on lines +210 to +241
type="text"
name="<?php echo esc_attr( $date_on_sale_from->get_name() ); ?>"
class="dokan-form-control dokan-start-date"
value="<?php echo esc_attr( $sale_price_dates_from ); ?>"
maxlength="10"
pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])"
placeholder="<?php echo esc_attr( $date_on_sale_from->get_placeholder() ); ?>"
<?php echo $date_on_sale_from->is_required() ? 'required' : ''; ?>>
</div>
</div>

<div class="dokan-form-group content-half-part to">
<div class="dokan-input-group">
<span class="dokan-input-group-addon">
<?php echo esc_html( $date_on_sale_to->get_title() ); ?>
<?php $date_on_sale_to->print_required_symbol(); ?>
<i
class="fas fa-question-circle tips <?php echo empty( $date_on_sale_to->get_help_content() ) ? 'dokan-hide' : ''; ?>"
aria-hidden="true"
data-title="<?php echo esc_attr( $date_on_sale_to->get_help_content() ); ?>">
</i>
</span>
<input
type="text"
name="<?php echo esc_attr( $date_on_sale_to->get_name() ); ?>"
class="dokan-form-control dokan-end-date"
value="<?php echo esc_attr( $sale_price_dates_to ); ?>"
maxlength="10"
pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])"
placeholder="<?php echo esc_attr( $date_on_sale_to->get_placeholder() ); ?>"
<?php echo $date_on_sale_to->is_required() ? 'required' : ''; ?>>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prevent setting sale schedule dates in the past

Currently, vendors can select past dates for the sale schedule (date_on_sale_from and date_on_sale_to), which allows them to set discount prices in the past. This could lead to inconsistencies in pricing and potential issues with customer expectations.

To resolve this, you should restrict the date inputs to prevent selecting past dates. You can achieve this by adding the min attribute to the input fields, setting it to today's date. Here's how you can modify the input fields:

 <input
     type="text"
     name="<?php echo esc_attr( $date_on_sale_from->get_name() ); ?>"
     class="dokan-form-control dokan-start-date"
     value="<?php echo esc_attr( $sale_price_dates_from ); ?>"
     maxlength="10"
     pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])"
+    min="<?php echo esc_attr( date( 'Y-m-d' ) ); ?>"
     placeholder="<?php echo esc_attr( $date_on_sale_from->get_placeholder() ); ?>"
     <?php echo $date_on_sale_from->is_required() ? 'required' : ''; ?>>

 <input
     type="text"
     name="<?php echo esc_attr( $date_on_sale_to->get_name() ); ?>"
     class="dokan-form-control dokan-end-date"
     value="<?php echo esc_attr( $sale_price_dates_to ); ?>"
     maxlength="10"
     pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])"
+    min="<?php echo esc_attr( date( 'Y-m-d' ) ); ?>"
     placeholder="<?php echo esc_attr( $date_on_sale_to->get_placeholder() ); ?>"
     <?php echo $date_on_sale_to->is_required() ? 'required' : ''; ?>>

This ensures that vendors cannot select a date earlier than today.

Committable suggestion skipped: line range outside the PR's diff.

# Conflicts:
#	assets/src/js/product-editor.js
#	includes/Admin/Notices/Manager.php
#	includes/Product/Manager.php
#	templates/products/edit-product-single.php
#	templates/products/tmpl-add-product-popup.php
…age after resolving merge conflict
@devAsadNur devAsadNur added Needs: Testing This requires further testing and removed Merge Conflict labels Dec 5, 2024
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (7)
tests/pw/pages/noticeAndPromotionPage.ts (1)

19-22: LGTM! Consider enhancing test readability.

The simplified implementation is more straightforward and deterministic. The use of notToHaveCount effectively verifies the presence of notice elements.

Consider improving test readability by extracting these assertions into a custom helper method:

async assertNoticeElementsPresent() {
    await this.notToHaveCount(selector.admin.dokan.notice.noticeDiv, 0);
    await this.notToHaveCount(selector.admin.dokan.notice.slider, 0);
    await this.notToHaveCount(selector.admin.dokan.notice.sliderPrev, 0);
    await this.notToHaveCount(selector.admin.dokan.notice.sliderNext, 0);
}

This would make the test more maintainable and self-documenting.

assets/src/js/product-editor.js (2)

25-25: Avoid unnecessary aliasing of this

The alias var self = this; is unnecessary since arrow functions inherit this from their enclosing scope. Remove the alias and use this directly.

🧰 Tools
🪛 Biome (1.9.4)

[error] 25-25: This aliasing of this is unnecessary.

Arrow functions inherits this from their enclosing scope.
Safe fix: Use this instead of an alias.

(lint/complexity/noUselessThisAlias)


93-93: Remove unused parameter self from productFormValidation

The parameter self in the productFormValidation method is not used. Consider removing it for clarity.

src/admin/pages/Settings.vue (1)

590-594: Consider using a more flexible action handling pattern.

The current implementation tightly couples the action handler to a specific section ID. This could become harder to maintain as more actions are added.

Consider using a strategy pattern:

-handleAction( sectionId ) {
-    if ( 'dokan_form_manager' === sectionId ) {
-        this.resetAllFields( sectionId );
-    }
-},
+const ACTION_HANDLERS = {
+    'dokan_form_manager': function(sectionId) {
+        this.resetAllFields(sectionId);
+    }
+};
+
+handleAction(sectionId) {
+    const handler = ACTION_HANDLERS[sectionId];
+    if (handler) {
+        handler.call(this, sectionId);
+    }
+},
includes/Assets.php (1)

Line range hint 1000-1084: Consider moving variation-related strings to a separate configuration file.

The large block of variation-related localization strings (lines 1061-1084) should be moved to a separate configuration file for better maintainability, as indicated by the TODO comment.

Consider creating a new file includes/i18n/variation-strings.php to hold these strings and import them here. This will:

  1. Improve code organization
  2. Make it easier to maintain and update strings
  3. Follow the separation of concerns principle
templates/products/edit/sections/general.php (2)

16-19: Consider enhancing error handling display

While the code checks for WP_Error, it silently returns without informing the user about what went wrong.

 $section = ProductFormFactory::get_section( 'general' );
 if ( is_wp_error( $section ) ) {
+    dokan_get_template_part( 'global/dokan-error', '', array( 'deleted' => true, 'message' => $section->get_error_message() ) );
     return;
 }

427-442: Enhance wp_editor security settings

The current wp_editor implementation could be more secure by limiting allowed HTML tags and protocols.

 wp_editor(
     $short_description->get_value( $product ),
     $short_description->get_name(),
     apply_filters(
         'dokan_product_short_description',
         [
             'editor_height' => 50,
             'quicktags'     => true,
             'media_buttons' => false,
             'teeny'         => false,
             'editor_class'  => 'post_excerpt',
             'required'      => $short_description->is_required(),
+            'tinymce'       => [
+                'valid_elements'          => 'p,span,b,strong,em,i,ul,ol,li,a[href|target]',
+                'valid_children'          => '+body[p|ul|ol]',
+                'extended_valid_elements' => 'span[*]',
+            ],
         ]
     )
 );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between cd4c744 and b13cd3b.

📒 Files selected for processing (18)
  • assets/src/js/product-editor.js (4 hunks)
  • dokan-class.php (2 hunks)
  • includes/Admin/Settings.php (0 hunks)
  • includes/Assets.php (8 hunks)
  • includes/Product/Hooks.php (3 hunks)
  • includes/Product/Manager.php (15 hunks)
  • includes/ProductCategory/Helper.php (4 hunks)
  • includes/REST/Manager.php (1 hunks)
  • includes/functions.php (2 hunks)
  • src/admin/components/Fields.vue (2 hunks)
  • src/admin/pages/Settings.vue (6 hunks)
  • templates/products/edit/sections/general.php (1 hunks)
  • tests/pw/pages/noticeAndPromotionPage.ts (1 hunks)
  • tests/pw/pages/settingsPage.ts (0 hunks)
  • tests/pw/utils/dbData.ts (0 hunks)
  • tests/pw/utils/interfaces.ts (7 hunks)
  • tests/pw/utils/schemas.ts (1 hunks)
  • tests/pw/utils/testData.ts (8 hunks)
💤 Files with no reviewable changes (3)
  • tests/pw/pages/settingsPage.ts
  • includes/Admin/Settings.php
  • tests/pw/utils/dbData.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • dokan-class.php
  • src/admin/components/Fields.vue
  • tests/pw/utils/interfaces.ts
  • tests/pw/utils/schemas.ts
🧰 Additional context used
🪛 GitHub Check: Run PHPCS inspection
includes/Assets.php

[warning] 582-582:
json_encode() is discouraged. Use wp_json_encode() instead.

includes/Product/Manager.php

[failure] 588-588:
Blank line found at start of control structure

🪛 Biome (1.9.4)
assets/src/js/product-editor.js

[error] 25-25: This aliasing of this is unnecessary.

Arrow functions inherits this from their enclosing scope.
Safe fix: Use this instead of an alias.

(lint/complexity/noUselessThisAlias)

tests/pw/utils/testData.ts

[error] 230-230: This property value named description is later overwritten by an object member with the same name.

Overwritten with this value.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named description

(lint/suspicious/noDuplicateObjectKeys)


[error] 244-244: This property value named description is later overwritten by an object member with the same name.

Overwritten with this value.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named description

(lint/suspicious/noDuplicateObjectKeys)


[error] 265-266: This property value named description is later overwritten by an object member with the same name.

Overwritten with this value.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named description

(lint/suspicious/noDuplicateObjectKeys)


[error] 279-280: This property value named description is later overwritten by an object member with the same name.

Overwritten with this value.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named description

(lint/suspicious/noDuplicateObjectKeys)


[error] 300-301: This property value named description is later overwritten by an object member with the same name.

Overwritten with this value.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named description

(lint/suspicious/noDuplicateObjectKeys)


[error] 324-325: This property value named description is later overwritten by an object member with the same name.

Overwritten with this value.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named description

(lint/suspicious/noDuplicateObjectKeys)


[error] 342-343: This property value named description is later overwritten by an object member with the same name.

Overwritten with this value.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named description

(lint/suspicious/noDuplicateObjectKeys)


[error] 365-366: This property value named description is later overwritten by an object member with the same name.

Overwritten with this value.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named description

(lint/suspicious/noDuplicateObjectKeys)

🔇 Additional comments (12)
includes/ProductCategory/Helper.php (1)

191-193: Optimize array merging inside the loop to improve performance

Using array_merge inside a loop can lead to performance issues with large datasets. Consider accumulating the ancestors in a temporary array and merging them after the loop.

includes/Product/Manager.php (2)

695-695: Remove the unnecessary blank line at the start of the control structure

As per coding standards, remove the blank line at the start of the control structure to maintain code consistency.


443-450: ⚠️ Potential issue

Add error handling for product deletion

Ensure that the product deletion process includes error handling to manage potential issues when deleting the product.

includes/REST/Manager.php (1)

145-145: ⚠️ Potential issue

Verify impact of changing action hook parameters

The second parameter passed to do_action( 'dokan_new_product_added', $object->get_id(), [] ); has been changed from $request to an empty array. This may affect hooked functions that rely on $request. Ensure that all hooked functions are updated to handle this change to prevent potential issues.

Run the following script to identify functions hooked to dokan_new_product_added that may be affected:

includes/Product/Hooks.php (1)

374-387: ⚠️ Potential issue

Verify consistency of the set_new_product_email_status method signature

The method set_new_product_email_status has an updated signature. Ensure that all calls to this method are updated accordingly, and the documentation reflects the new parameters and return type to prevent any errors.

Run the following script to find all invocations of set_new_product_email_status:

✅ Verification successful

Let me try to find any references to this method in the codebase to verify its usage and previous signature. The previous command failed because the --php flag is not supported by ripgrep. I've simplified the search and added context lines to better understand the method's implementation history.


I need to verify if there was actually a change in the method signature and understand how this hook is being used in the codebase to ensure consistency.


Method signature is consistent with its usage

The method set_new_product_email_status is only used as a hook callback registered via add_filter('dokan_product_edit_meta_data', ...) with 3 parameters, which matches exactly with the current method signature. The hook is properly applied in includes/Dashboard/Templates/Products.php with all three parameters ($meta_data, $product, $is_new_product). No inconsistencies were found.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all calls to 'set_new_product_email_status' and check their parameters.

# Search for calls to 'set_new_product_email_status' in the codebase
rg "set_new_product_email_status\s*\(" --php

Length of output: 454


Script:

#!/bin/bash
# Search for calls to 'set_new_product_email_status' in the codebase
rg "set_new_product_email_status"

# Also search for the method definition to understand its previous signature
rg -B 5 -A 5 "function set_new_product_email_status"

Length of output: 1012


Script:

#!/bin/bash
# Let's check the git history of this file to see if the method signature was changed
git log -L :set_new_product_email_status:includes/Product/Hooks.php

# Also check how this hook is used in the codebase
rg "dokan_product_edit_meta_data" -B 2 -A 2

Length of output: 1531

src/admin/pages/Settings.vue (2)

58-60: LGTM: Action button implementation is clean and follows Vue best practices.

The conditional rendering and event handling are implemented correctly.


596-613: 🛠️ Refactor suggestion

Improve resetAllFields implementation for better performance and reliability.

The current implementation has several potential issues:

  1. Uses JSON parse/stringify for cloning which doesn't handle special values
  2. Nested loops could impact performance with large datasets
  3. No error handling for malformed data

Consider this improved implementation:

 resetAllFields(sectionId) {
+    try {
-        let settingFields = Object.entries(this.settingFields[sectionId]);
-        let clonedSectionSettingValues = JSON.parse(JSON.stringify(this.settingValues[sectionId]));
+        const clonedSectionSettingValues = structuredClone(this.settingValues[sectionId]);
+        const settingFields = this.settingFields[sectionId];
 
-        settingFields.forEach((key, value) => {
-            let blockId = key[0];
+        Object.entries(settingFields).forEach(([blockId, sectionValue]) => {
+            if (!blockId || !sectionValue?.fields) return;
 
-            if (blockId) {
-                let sectionValue = key[1];
-                for (const [FieldId, fieldData] of Object.entries(sectionValue.fields)) {
-                    clonedSectionSettingValues[blockId]['fields'][FieldId]['visibility'] = fieldData.default.visibility;
-                    clonedSectionSettingValues[blockId]['fields'][FieldId]['required'] = fieldData.default.required;
-                }
+            Object.entries(sectionValue.fields).forEach(([fieldId, fieldData]) => {
+                if (!fieldData?.default) return;
+                
+                clonedSectionSettingValues[blockId].fields[fieldId] = {
+                    ...clonedSectionSettingValues[blockId].fields[fieldId],
+                    visibility: fieldData.default.visibility,
+                    required: fieldData.default.required
+                };
+            });
+        });
 
-        });
         this.settingValues[sectionId] = clonedSectionSettingValues;
+    } catch (error) {
+        console.error('Failed to reset fields:', error);
+        // Consider showing user-friendly error message
     }
 },

Likely invalid or redundant comment.

includes/functions.php (2)

566-566: Improved required attribute validation

The change from isset() to !empty() is a good improvement as it handles both existence and non-empty value checks.


616-630: Enhanced checkbox input with description tooltip

Good addition of tooltip functionality for checkbox descriptions. This improves user experience by providing contextual help.

The implementation uses Font Awesome icons and proper ARIA attributes for accessibility.

templates/products/edit/sections/general.php (3)

485-489: LGTM! Proper security measures in place.

The form submission implementation includes proper nonce verification and escaping.


173-182: 🛠️ Refactor suggestion

Add client-side validation for sale dates

The sale schedule implementation should prevent setting end dates before start dates and past dates.

+<script>
+document.addEventListener('DOMContentLoaded', function() {
+    const startDate = document.querySelector('.dokan-start-date');
+    const endDate = document.querySelector('.dokan-end-date');
+    
+    if (startDate && endDate) {
+        startDate.addEventListener('change', function() {
+            endDate.min = this.value;
+        });
+        
+        endDate.addEventListener('change', function() {
+            if (this.value < startDate.value) {
+                this.value = startDate.value;
+            }
+        });
+        
+        // Set minimum date to today
+        const today = new Date().toISOString().split('T')[0];
+        startDate.min = today;
+        endDate.min = today;
+    }
+});
+</script>

Likely invalid or redundant comment.


377-401: 🛠️ Refactor suggestion

Enhance gallery images validation and sanitization

The current implementation needs additional checks for image IDs and proper array handling.

-$product_images = is_array( $product_images ) ? $product_images : explode( ',', $product_images );
+$product_images = is_array( $product_images ) ? array_filter( array_map( 'absint', $product_images ) ) : array_filter( array_map( 'absint', explode( ',', $product_images ) ) );

 if ( $product_images ) :
     foreach ( $product_images as $image_id ) :
-        if ( empty( $image_id ) ) :
+        if ( empty( $image_id ) || ! wp_attachment_is_image( $image_id ) ) :
             continue;
         endif;

Likely invalid or redundant comment.

Comment on lines +576 to +583
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 );
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use wp_json_encode for WordPress compatibility.

The current implementation uses PHP's native json_encode(). WordPress provides wp_json_encode() which handles additional edge cases and ensures better compatibility.

Apply this fix:

 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 );
+    return wp_json_encode( $temp_fields );
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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 );
}
public function get_product_fields() {
$temp_fields = [];
foreach ( ProductFormFactory::get_fields() as $field_id => $field ) {
$temp_fields[ $field_id ] = $field->toArray();
}
return wp_json_encode( $temp_fields );
}
🧰 Tools
🪛 GitHub Check: Run PHPCS inspection

[warning] 582-582:
json_encode() is discouraged. Use wp_json_encode() instead.

@@ -227,6 +227,7 @@ export const data = {
productName: () => `${faker.commerce.productName()}_${faker.string.nanoid(5)} (Simple)`,
category: 'Uncategorized',
regularPrice: () => faker.finance.amount({ min: 100, max: 200, dec: faker.helpers.arrayElement([1, 2]) }).replace('.', ','),
description: 'test long description',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix duplicate description properties in product type objects

Each product type object has duplicate description properties which can lead to unexpected behavior since the second property will overwrite the first one.

Apply this fix to consolidate the descriptions:

simple: {
    productType: 'simple',
    productName: () => `${faker.commerce.productName()}_${faker.string.nanoid(5)} (Simple)`,
    category: 'Uncategorized',
    regularPrice: () => faker.finance.amount({ min: 100, max: 200, dec: faker.helpers.arrayElement([1, 2]) }).replace('.', ','),
-   description: 'test long description',
    storeName: `${VENDOR}store`,
    status: 'publish',
    stockStatus: false,
    editProduct: '',
    description: 'test description',
    saveSuccessMessage: 'Success! The product has been saved successfully. View Product →',
}

Apply similar fixes to other product type objects (downloadable, virtual, variable, external, simpleSubscription, variableSubscription, vendorSubscription).

Also applies to: 244-244, 265-265, 279-279, 300-300, 324-324, 342-342, 365-365

🧰 Tools
🪛 Biome (1.9.4)

[error] 230-230: This property value named description is later overwritten by an object member with the same name.

Overwritten with this value.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named description

(lint/suspicious/noDuplicateObjectKeys)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
tests/pw/utils/testData.ts (1)

242-244: ⚠️ Potential issue

Fix duplicate description properties in product type objects

Each product type object has duplicate description properties which can lead to unexpected behavior since the second property will overwrite the first one.

Apply this fix to consolidate the descriptions:

simple: {
    productType: 'simple',
    productName: () => `${faker.commerce.productName()}_${faker.string.nanoid(5)} (Simple)`,
    category: 'Uncategorized',
    regularPrice: () => faker.finance.amount({ min: 100, max: 200, dec: faker.helpers.arrayElement([1, 2]) }).replace('.', ','),
-   description: 'test long description',
    storeName: `${VENDOR}store`,
    status: 'publish',
    stockStatus: false,
    editProduct: '',
    description: 'test description',
    saveSuccessMessage: 'Success! The product has been saved successfully. View Product →',
}

Apply similar fixes to other product type objects (downloadable, virtual, variable, external, simpleSubscription, variableSubscription, vendorSubscription).

Also applies to: 256-258, 277-279, 291-293, 313-315, 337-339, 355-357, 379-381

🧰 Tools
🪛 Biome (1.9.4)

[error] 242-242: This property value named description is later overwritten by an object member with the same name.

Overwritten with this value.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named description

(lint/suspicious/noDuplicateObjectKeys)

🧹 Nitpick comments (1)
tests/pw/utils/testData.ts (1)

Line range hint 1-1000: Consider improving the test data structure for better maintainability

The test data file could benefit from the following improvements:

  1. Extract common strings into constants to avoid repetition
  2. Add TypeScript interfaces for better type safety
  3. Split the large data object into smaller, more focused modules

Example implementation:

// constants.ts
export const DESCRIPTIONS = {
  TEST: 'test description',
  TEST_LONG: 'test long description',
  // ... other common strings
};

// interfaces.ts
interface ProductBase {
  productType: string;
  productName: () => string;
  category: string;
  description: string;
  // ... other common properties
}

interface SimpleProduct extends ProductBase {
  stockStatus: boolean;
  // ... simple product specific properties
}

// productData.ts
export const simpleProduct: SimpleProduct = {
  productType: 'simple',
  productName: () => `${faker.commerce.productName()}_${faker.string.nanoid(5)} (Simple)`,
  category: 'Uncategorized',
  description: DESCRIPTIONS.TEST,
  // ... other properties
};

This structure would:

  • Reduce code duplication
  • Improve type safety
  • Make the code more maintainable
  • Make it easier to update common values
🧰 Tools
🪛 Biome (1.9.4)

[error] 242-242: This property value named description is later overwritten by an object member with the same name.

Overwritten with this value.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named description

(lint/suspicious/noDuplicateObjectKeys)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b13cd3b and a04c74b.

📒 Files selected for processing (6)
  • dokan-class.php (2 hunks)
  • includes/Product/Hooks.php (3 hunks)
  • tests/pw/utils/dbData.ts (0 hunks)
  • tests/pw/utils/interfaces.ts (7 hunks)
  • tests/pw/utils/schemas.ts (1 hunks)
  • tests/pw/utils/testData.ts (8 hunks)
💤 Files with no reviewable changes (1)
  • tests/pw/utils/dbData.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • dokan-class.php
  • tests/pw/utils/schemas.ts
  • tests/pw/utils/interfaces.ts
🧰 Additional context used
🪛 Biome (1.9.4)
tests/pw/utils/testData.ts

[error] 242-242: This property value named description is later overwritten by an object member with the same name.

Overwritten with this value.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named description

(lint/suspicious/noDuplicateObjectKeys)


[error] 256-256: This property value named description is later overwritten by an object member with the same name.

Overwritten with this value.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named description

(lint/suspicious/noDuplicateObjectKeys)


[error] 277-278: This property value named description is later overwritten by an object member with the same name.

Overwritten with this value.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named description

(lint/suspicious/noDuplicateObjectKeys)


[error] 291-292: This property value named description is later overwritten by an object member with the same name.

Overwritten with this value.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named description

(lint/suspicious/noDuplicateObjectKeys)


[error] 313-314: This property value named description is later overwritten by an object member with the same name.

Overwritten with this value.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named description

(lint/suspicious/noDuplicateObjectKeys)


[error] 337-338: This property value named description is later overwritten by an object member with the same name.

Overwritten with this value.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named description

(lint/suspicious/noDuplicateObjectKeys)


[error] 355-356: This property value named description is later overwritten by an object member with the same name.

Overwritten with this value.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named description

(lint/suspicious/noDuplicateObjectKeys)


[error] 379-380: This property value named description is later overwritten by an object member with the same name.

Overwritten with this value.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named description

(lint/suspicious/noDuplicateObjectKeys)

🔇 Additional comments (2)
includes/Product/Hooks.php (2)

9-9: LGTM! Security enhancement added.

The addition of the direct script access prevention check follows WordPress security best practices.


375-388: Consider the previously suggested optimization for better readability.

The implementation is functionally correct, but could be more concise as suggested in the previous review.

Apply this optimization:

-    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;
-    }
+    public function set_new_product_email_status( array $meta_data, WC_Product $product, bool $is_new_product ) {
+        if ( $is_new_product ) {
+            $meta_data['_dokan_new_product_email_sent'] = 'no';
+        }
+        return $meta_data;
+    }

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
includes/wc-functions.php (2)

34-37: Consider optimizing the data cleaning sequence.

The current implementation cleans the data twice:

  1. wp_unslash() on line 35
  2. wc_clean() on line 37

This could be optimized for better performance.

-        $data = apply_filters( 'dokan_restrict_product_gallery_images_on_edit', wp_unslash( $data['gallery_image_ids'] ) );
-        $attachment_ids = array_filter( explode( ',', wc_clean( $data ) ) );
+        $data = wc_clean( wp_unslash( $data['gallery_image_ids'] ) );
+        $data = apply_filters( 'dokan_restrict_product_gallery_images_on_edit', $data );
+        $attachment_ids = array_filter( explode( ',', $data ) );

Line range hint 305-315: Enhance error logging information.

The current error logging is too generic and might not provide enough context for debugging.

             try {
                 wc_update_product_stock_status( $post_id, $stock_status );
             } catch ( Exception $ex ) {
-                dokan_log( 'product stock update exception' );
+                dokan_log( sprintf( 'Failed to update stock status for product #%d: %s', $post_id, $ex->getMessage() ) );
             }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a04c74b and 0ecb790.

📒 Files selected for processing (2)
  • includes/Dashboard/Templates/Products.php (9 hunks)
  • includes/wc-functions.php (4 hunks)
🧰 Additional context used
🪛 GitHub Check: Run PHPCS inspection
includes/Dashboard/Templates/Products.php

[failure] 494-494:
Detected usage of a non-sanitized input variable: $_POST['gallery_image_ids']

🔇 Additional comments (7)
includes/wc-functions.php (2)

Line range hint 305-315: LGTM! Improved stock management with error handling.

The changes add proper error handling and prevent unnecessary stock updates.


Line range hint 317-329: LGTM! Efficient stock updates with proper handling of variable products.

The changes optimize database operations by:

  1. Only updating stock when it has changed
  2. Properly handling variable products with different update logic
includes/Dashboard/Templates/Products.php (5)

28-45: Convert static properties to instance properties

Using static properties for instance-specific data can lead to race conditions in concurrent scenarios.


57-70: LGTM! Well-organized hook setup

The constructor properly organizes action hooks by functionality, making the code maintainable and easy to understand.


136-145: Replace informal error message

The error message "Are you cheating?" should be replaced with a more professional message.


Line range hint 194-312: LGTM! Robust template loading implementation

The template loading methods consistently check for product existence and section visibility before rendering.


530-569: LGTM! Well-structured product update handling

The product update logic includes:

  • Proper hooks for extensibility
  • Comprehensive field validation
  • Appropriate error handling

Comment on lines +494 to +496
$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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Sanitize gallery image IDs before processing

The gallery image IDs from POST data should be sanitized before processing to prevent potential security issues.

Apply this diff to properly sanitize the input:

-$attachment_ids = isset( $_POST['gallery_image_ids'] ) ? apply_filters( 'dokan_restrict_product_gallery_images_on_edit', wp_unslash( $_POST['gallery_image_ids'] ) ) : '';
+$attachment_ids = isset( $_POST['gallery_image_ids'] ) ? apply_filters( 'dokan_restrict_product_gallery_images_on_edit', sanitize_text_field( wp_unslash( $_POST['gallery_image_ids'] ) ) ) : '';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$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
$attachment_ids = isset( $_POST['gallery_image_ids'] ) ? apply_filters( 'dokan_restrict_product_gallery_images_on_edit', sanitize_text_field( 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
🧰 Tools
🪛 GitHub Check: Run PHPCS inspection

[failure] 494-494:
Detected usage of a non-sanitized input variable: $_POST['gallery_image_ids']

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs: Dev Review It requires a developer review and approval Needs: Testing This requires further testing
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants