Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/wc-ajax-product-filter/build/form.asset.php
+++ b/wc-ajax-product-filter/build/form.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-primitives'), 'version' => '989ca2867ffeee42d68e');
+<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-primitives'), 'version' => 'ce3dee3c876dd11dfb86');
--- a/wc-ajax-product-filter/build/list-forms.asset.php
+++ b/wc-ajax-product-filter/build/list-forms.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices'), 'version' => 'c3d09ea5c2c68ff72f3a');
+<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices'), 'version' => '6de23891432cc434b519');
--- a/wc-ajax-product-filter/build/seo-rules.asset.php
+++ b/wc-ajax-product-filter/build/seo-rules.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'react-dom', 'react-jsx-runtime', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices'), 'version' => '4316b0ecd265165c27a5');
+<?php return array('dependencies' => array('lodash', 'react-dom', 'react-jsx-runtime', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-notices'), 'version' => 'e0c6202a23641340fe26');
--- a/wc-ajax-product-filter/build/settings.asset.php
+++ b/wc-ajax-product-filter/build/settings.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-media-utils', 'wp-notices', 'wp-primitives'), 'version' => '0fdff61b197883c24f65');
+<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'react-jsx-runtime', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-media-utils', 'wp-notices', 'wp-primitives'), 'version' => 'c32df8c5d038ac348b75');
--- a/wc-ajax-product-filter/includes/class-wcapf-admin.php
+++ b/wc-ajax-product-filter/includes/class-wcapf-admin.php
@@ -5,9 +5,14 @@
* @since 3.0.0
* @package wc-ajax-product-filter
* @subpackage wc-ajax-product-filter/includes
- * @author wptools.io
+ * @author Mainul Hassan
*/
+// Exit if accessed directly.
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
/**
* WCAPF_Admin class.
*
@@ -16,9 +21,21 @@
class WCAPF_Admin {
/**
+ * Core plugin version.
+ *
+ * Uses the free/basic plugin version when available; otherwise falls back
+ * to the main plugin version.
+ *
+ * @var string
+ */
+ private $core_version;
+
+ /**
* The constructor.
*/
public function __construct() {
+ $this->core_version = defined( 'WCAPF_BASIC_VERSION' ) ? WCAPF_BASIC_VERSION : WCAPF_VERSION;
+
$plugin_file = plugin_basename( WCAPF_PLUGIN_FILE );
add_filter( 'plugin_action_links_' . $plugin_file, array( $this, 'plugin_action_links' ) );
@@ -92,7 +109,7 @@
*/
public function register_admin_pages() {
add_menu_page(
- 'WCAPF - WooCommerce Ajax Product Filter',
+ 'WCAPF – Ajax Product Filter for WooCommerce',
'WCAPF',
'manage_options',
'wcapf',
@@ -100,18 +117,9 @@
'dashicons-filter'
);
- // add_submenu_page(
- // 'wcapf',
- // __( 'WCAPF - WooCommerce Ajax Product Filter - SEO Rules', 'wc-ajax-product-filter' ),
- // __( 'SEO Rules', 'wc-ajax-product-filter' ),
- // 'manage_options',
- // 'wcapf-seo-rules',
- // array( $this, 'render_seo_rules' )
- // );
-
add_submenu_page(
'wcapf',
- __( 'WCAPF - WooCommerce Ajax Product Filter - Settings', 'wc-ajax-product-filter' ),
+ __( 'WCAPF – Ajax Product Filter for WooCommerce - Settings', 'wc-ajax-product-filter' ),
__( 'Settings', 'wc-ajax-product-filter' ),
'manage_options',
'wcapf-settings',
@@ -119,28 +127,31 @@
);
}
+ /**
+ * Renders the root element for the React UI for forms' admin pages.
+ *
+ * @return void
+ */
public function render_form() {
- if ( isset( $_GET['id'] ) ) {
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Used only to determine which admin screen to load.
+ if ( isset( $_GET['id'] ) && absint( wp_unslash( $_GET['id'] ) ) ) {
$element = '<div id="wcapf-form-admin-ui"></div>';
} else {
$element = '<div id="wcapf-forms-list-admin-ui"></div>';
}
- echo $element;
+ echo wp_kses_post( $element );
}
- // public function render_seo_rules() {
- // echo '<div id="wcapf-seo-rules-admin-ui"></div>';
- // }
-
+ /**
+ * Renders the root element for the React UI for plugin settings page.
+ *
+ * @return void
+ */
public function render_settings() {
echo '<div id="wcapf-settings-admin-ui"></div>';
}
- // public function render_upgrade_page() {
- // WCAPF_Template_Loader::get_instance()->load( 'admin/upgrade-to-pro' );
- // }
-
/**
* Modify the label of custom admin menu.
*
@@ -158,6 +169,7 @@
$new_data[0][0] = __( 'Forms', 'wc-ajax-product-filter' );
}
+ // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Intentionally modifying submenu label for this plugin menu.
$submenu['wcapf'] = $new_data;
}
}
@@ -172,7 +184,7 @@
* @return void
*/
public function disable_admin_notices() {
- if ( in_array( $this->current_screen_id(), $this->slugs_of_custom_admin_pages() ) ) {
+ if ( in_array( $this->current_screen_id(), $this->slugs_of_custom_admin_pages(), true ) ) {
remove_all_actions( 'admin_notices' );
}
}
@@ -191,6 +203,8 @@
}
/**
+ * Returns the slugs of admin pages.
+ *
* @return string[]
*/
private function slugs_of_custom_admin_pages() {
@@ -213,8 +227,8 @@
*/
public function enqueue_admin_ui_scripts( $hook ) {
// Load the dependent js variables before loading other scripts.
- if ( in_array( $hook, $this->slugs_of_custom_admin_pages() ) ) {
- wp_register_script( 'wcapf-admin-scripts', false );
+ if ( in_array( $hook, $this->slugs_of_custom_admin_pages(), true ) ) {
+ wp_register_script( 'wcapf-admin-scripts', false, array(), $this->core_version, true );
wp_localize_script(
'wcapf-admin-scripts',
@@ -235,7 +249,7 @@
if ( 'toplevel_page_wcapf' === $hook ) {
// Forms list admin ui scripts.
- if ( ! isset( $_GET['id'] ) ) {
+ if ( ! isset( $_GET['id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Used only to determine which admin screen to load.
$this->load_scripts( 'list-forms' );
} else {
// Loads the media utils.
@@ -243,12 +257,6 @@
// Single form admin ui scripts.
$this->load_scripts( 'form' );
-
- // Loads the js script that converts our filter key into slug.
- wp_enqueue_script(
- 'wcapf-sanitize-title',
- WCAPF_PLUGIN_URL . 'admin/lib/wp-fe-sanitize-title.js'
- );
}
}
@@ -266,12 +274,6 @@
wp_enqueue_media();
$this->load_scripts( 'settings' );
-
- // Loads the js script that converts our filter key into slug.
- wp_enqueue_script(
- 'wcapf-sanitize-title',
- WCAPF_PLUGIN_URL . 'admin/lib/wp-fe-sanitize-title.js'
- );
}
}
@@ -283,41 +285,39 @@
private function admin_js_params() {
$params = array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
- 'free_version' => defined( 'WCAPF_BASIC_VERSION' ) ? WCAPF_BASIC_VERSION : WCAPF_VERSION,
+ 'free_version' => $this->core_version,
'pro_version' => defined( 'WCAPF_PRO_VERSION' ) ? WCAPF_PRO_VERSION : false,
'wp_version' => get_bloginfo( 'version' ),
'dirty' => false,
'nonce' => wp_create_nonce( 'wcapf-nonce' ),
);
- $helper = new WCAPF_Helper();
- $utils = new WCAPF_API_Utils();
-
- $params['forms_page_link'] = $helper::forms_page_url();
- $params['seo_rules_page_link'] = $helper::seo_rules_page_url();
- $params['settings_page_link'] = $helper::settings_page_url();
- $params['upgrade_page_link'] = $helper::upgrade_page_url();
+ $params['forms_page_link'] = WCAPF_Helper::forms_page_url();
+ $params['seo_rules_page_link'] = WCAPF_Helper::seo_rules_page_url();
+ $params['settings_page_link'] = WCAPF_Helper::settings_page_url();
+ $params['upgrade_page_link'] = WCAPF_Helper::upgrade_page_url();
$screen_id = $this->current_screen_id();
- $user_roles = $utils::user_role_options();
+ $user_roles = WCAPF_API_Utils::user_role_options();
if ( 'toplevel_page_wcapf' === $screen_id ) {
$params['form_default_data'] = WCAPF_Default_Data::form_default_data();
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Used only to determine which admin screen to load.
if ( ! isset( $_GET['id'] ) ) {
- $params['forms'] = $utils::get_forms();
+ $params['forms'] = WCAPF_API_Utils::get_forms();
} else {
- $settings = $helper::get_settings();
+ $settings = WCAPF_Helper::get_settings();
$params['filter_default_data'] = WCAPF_Default_Data::filter_default_data();
- $params['filter_types'] = $utils::get_filter_types();
- $params['meta_keys'] = $helper::get_available_meta_keys();
- $params['date_formats'] = $utils::display_date_formats();
- $params['status_options'] = $utils::product_status_options();
- $params['time_periods'] = $utils::time_period_options();
- $params['sort_by_options'] = $utils::sort_by_options();
- $params['meta_types'] = $utils::meta_type_options();
+ $params['filter_types'] = WCAPF_API_Utils::get_filter_types();
+ $params['meta_keys'] = WCAPF_Helper::get_available_meta_keys();
+ $params['date_formats'] = WCAPF_API_Utils::display_date_formats();
+ $params['status_options'] = WCAPF_API_Utils::product_status_options();
+ $params['time_periods'] = WCAPF_API_Utils::time_period_options();
+ $params['sort_by_options'] = WCAPF_API_Utils::sort_by_options();
+ $params['meta_types'] = WCAPF_API_Utils::meta_type_options();
$params['user_roles'] = $user_roles;
$params['author_roles'] = isset( $settings['author_roles'] )
@@ -325,12 +325,13 @@
$params['multiple_form_locations'] = isset( $settings['multiple_form_locations'] )
? $settings['multiple_form_locations'] : '';
- $post_id = $_GET['id'];
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Used only to fetch the form ID for admin UI rendering.
+ $post_id = absint( wp_unslash( $_GET['id'] ) );
$post = get_post( $post_id );
$params['form_data'] = array(
'post_id' => $post_id,
- 'post_title' => $post->post_title,
+ 'post_title' => $post ? sanitize_text_field( $post->post_title ) : '',
);
}
}
@@ -339,9 +340,9 @@
$params['default_settings'] = WCAPF_Default_Data::default_settings();
$params['user_roles'] = $user_roles;
- $params['settings'] = $utils::get_settings();
+ $params['settings'] = WCAPF_API_Utils::get_settings();
- $params['global_filter_keys'] = $utils::get_filter_keys( true );
+ $params['global_filter_keys'] = WCAPF_API_Utils::get_filter_keys( true );
}
$params['widgets_page_link'] = admin_url( 'widgets.php' );
@@ -354,9 +355,9 @@
}
/**
- * The helper function to load the js build scripts.
+ * Loads the built admin scripts and styles for a given screen.
*
- * @param string $file The file name.
+ * @param string $file The build file name without extension.
*
* @return void
*/
@@ -364,13 +365,21 @@
$asset_path = WCAPF_PLUGIN_DIR . '/build/' . $file . '.asset.php';
if ( ! file_exists( $asset_path ) ) {
- /** @noinspection PhpMultipleClassDeclarationsInspection */
- throw new Error(
- 'You need to run `npm start` or `npm run build` for the ' . $file . ' admin ui'
+ wp_die(
+ wp_kses_post(
+ sprintf(
+ /* translators: %s: admin build file name. */
+ __(
+ 'You need to run <code>npm start</code> or <code>npm run build</code> for the %s admin UI.',
+ 'wc-ajax-product-filter'
+ ),
+ esc_html( $file )
+ )
+ )
);
}
- $asset_file = require( $asset_path );
+ $asset_file = require $asset_path;
$handle = 'wcapf-' . $file . '-admin';
$js_file = 'build/' . $file . '.js';
$css_file = 'build/' . $file . '.css';
@@ -385,15 +394,23 @@
$handle,
plugins_url( $js_file, WCAPF_PLUGIN_FILE ),
$asset_file['dependencies'],
- $asset_file['version']
+ $asset_file['version'],
+ true
);
// Set up translations for the script.
- wp_set_script_translations(
- $handle,
- 'wc-ajax-product-filter',
- plugin_dir_path( WCAPF_PLUGIN_FILE ) . 'languages'
- );
+ if ( defined( 'WCAPF_PRO_VERSION' ) ) {
+ wp_set_script_translations(
+ $handle,
+ 'wc-ajax-product-filter',
+ plugin_dir_path( WCAPF_PLUGIN_FILE ) . 'languages'
+ );
+ } else {
+ wp_set_script_translations(
+ $handle,
+ 'wc-ajax-product-filter'
+ );
+ }
// Load the style file.
wp_enqueue_style(
@@ -412,7 +429,7 @@
* @return void
*/
public function enqueue_review_notices_styles() {
- if ( ! in_array( $this->current_screen_id(), $this->slugs_of_custom_admin_pages() ) ) {
+ if ( ! in_array( $this->current_screen_id(), $this->slugs_of_custom_admin_pages(), true ) ) {
return;
}
?>
@@ -433,7 +450,7 @@
* @return void
*/
public function enqueue_review_notices_scripts() {
- if ( ! in_array( $this->current_screen_id(), $this->slugs_of_custom_admin_pages() ) ) {
+ if ( ! in_array( $this->current_screen_id(), $this->slugs_of_custom_admin_pages(), true ) ) {
return;
}
@@ -456,7 +473,7 @@
var data = {
action: 'wcapf_dismiss_review_notices',
- nonce: '<?php echo $nonce; ?>',
+ nonce: '<?php echo esc_js( $nonce ); ?>',
type,
};
@@ -479,12 +496,16 @@
// Verify the AJAX request nonce.
check_ajax_referer( 'wcapf-dismiss-review-notices-nonce', 'nonce' );
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_send_json_error( __( 'Unauthorized request', 'wc-ajax-product-filter' ) );
+ }
+
// Get the type parameter from the request.
- $type = ! empty( $_REQUEST['type'] ) ? sanitize_text_field( $_REQUEST['type'] ) : '';
+ $type = ! empty( $_REQUEST['type'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['type'] ) ) : '';
// If the type parameter is missing, send an error response.
if ( ! $type ) {
- wp_send_json_error( 'Invalid type for dismissing the review notices' );
+ wp_send_json_error( __( 'Invalid type for dismissing the review notices', 'wc-ajax-product-filter' ) );
}
// Get the current user ID.
@@ -504,9 +525,8 @@
}
// Send a success response.
- wp_send_json_success( 'WCAPF review notice dismissed' );
+ wp_send_json_success( __( 'WCAPF review notice dismissed', 'wc-ajax-product-filter' ) );
}
-
}
if ( is_admin() ) {
--- a/wc-ajax-product-filter/includes/class-wcapf-api-utils.php
+++ b/wc-ajax-product-filter/includes/class-wcapf-api-utils.php
@@ -5,9 +5,14 @@
* @since 4.0.0
* @package wc-ajax-product-filter
* @subpackage wc-ajax-product-filter/includes
- * @author wptools.io
+ * @author Mainul Hassan
*/
+// Exit if accessed directly.
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
/**
* WCAPF_API_Utils class.
*
@@ -15,6 +20,16 @@
*/
class WCAPF_API_Utils {
+ /**
+ * Retrieves the global filter key for the given filter data.
+ *
+ * Matches the provided filter data against the registered filter keys and
+ * returns the corresponding filter key when a match is found.
+ *
+ * @param array $filter_data Filter data.
+ *
+ * @return string Global filter key if found, otherwise an empty string.
+ */
public static function get_global_filter_key( $filter_data ) {
$filter_keys = self::get_filter_keys();
$filter_type = isset( $filter_data['type'] ) ? $filter_data['type'] : '';
@@ -53,11 +68,16 @@
}
/**
- * @param $global
+ * Retrieves filter keys.
+ *
+ * Returns filter key data for all saved filters. When `$is_global` is true,
+ * duplicate global filters are excluded and additional label data is added.
*
- * @return array
+ * @param bool $is_global Whether to return only unique global filter keys.
+ *
+ * @return array Filter keys.
*/
- public static function get_filter_keys( $global = false ) {
+ public static function get_filter_keys( $is_global = false ) {
$filters = get_posts(
array(
'post_type' => 'wcapf-filter',
@@ -73,11 +93,14 @@
$taxonomy_options = self::get_available_taxonomies();
$global_filter_keys = array();
+ $taxonomy_lookup = array_column( $taxonomy_options, null, 'value' );
+ $type_lookup = array_column( $filter_types, null, 'value' );
+
foreach ( $filters as $filter ) {
$filter_id = $filter->ID;
$filter_key = $filter->post_name;
$post_excerpt = html_entity_decode( $filter->post_excerpt );
- $filter_data = explode( '>', $post_excerpt );
+ $filter_data = explode( '>', trim( $post_excerpt ) );
$type = isset( $filter_data[0] ) ? $filter_data[0] : '';
$filter_type = $type;
$property = isset( $filter_data[1] ) ? $filter_data[1] : '';
@@ -94,34 +117,38 @@
if ( 'taxonomy' === $type ) {
$data['taxonomy'] = $property;
- } else if ( 'post-meta' === $type ) {
+ } elseif ( 'post-meta' === $type ) {
+ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Not a database query.
$data['meta_key'] = $property;
}
- if ( $global ) {
+ if ( $is_global ) {
unset( $data['id'] );
if ( 'taxonomy' === $type ) {
- $data_index = array_search( $property, array_column( $taxonomy_options, 'value' ) );
- $taxonomy_data = $taxonomy_options[ $data_index ];
- $data['label'] = $taxonomy_data['label'];
+ $taxonomy_data = isset( $taxonomy_lookup[ $property ] ) ? $taxonomy_lookup[ $property ] : null;
+
+ if ( $taxonomy_data ) {
+ $data['label'] = $taxonomy_data['label'];
+ }
} else {
- $data_index = array_search( $type, array_column( $filter_types, 'value' ) );
- $_filter_data = $filter_types[ $data_index ];
+ $_filter_data = isset( $type_lookup[ $type ] ) ? $type_lookup[ $type ] : null;
- $label = $_filter_data['label'];
+ if ( $_filter_data ) {
+ $label = $_filter_data['label'];
- if ( 'post-meta' === $type ) {
- $label .= '[' . $property . ']';
- }
+ if ( 'post-meta' === $type ) {
+ $label .= '[' . $property . ']';
+ }
- $data['label'] = $label;
+ $data['label'] = $label;
+ }
}
$data['secondary_type'] = $post_excerpt;
$data['_field_key'] = $filter_key; // Keep a backup for updating purposes.
- if ( ! in_array( $post_excerpt, $global_filter_keys ) ) {
+ if ( ! in_array( $post_excerpt, $global_filter_keys, true ) ) {
$filter_keys[] = $data;
$global_filter_keys[] = $post_excerpt;
@@ -131,13 +158,13 @@
}
}
- return apply_filters( 'wcapf_filter_keys', $filter_keys, $global );
+ return apply_filters( 'wcapf_filter_keys', $filter_keys, $is_global );
}
/**
- * Gets the filter types.
+ * Retrieves available filter types.
*
- * @return array[]
+ * @return array Filter types.
*/
public static function get_filter_types() {
return array(
@@ -207,29 +234,19 @@
'type' => 'component',
'isPro' => true,
),
- // array(
- // 'label' => __( 'Apply Mode', 'wc-ajax-product-filter' ),
- // 'value' => 'apply-mode',
- // 'type' => 'component',
- // 'isPro' => true,
- // ),
- // array(
- // 'label' => __( 'Submit Mode', 'wc-ajax-product-filter' ),
- // 'value' => 'submit-mode',
- // 'type' => 'component',
- // 'isPro' => true,
- // ),
),
),
);
}
/**
- * Gets the available taxonomies after sorting them.
+ * Retrieves available product taxonomies.
+ *
+ * Returns taxonomy data formatted for use in the plugin UI.
*
- * @param bool $only_with_archive Whether to return the taxonomies with archive enabled or not.
+ * @param bool $only_with_archive Whether to return only viewable taxonomies with archive support.
*
- * @return array
+ * @return array Available taxonomies.
*/
public static function get_available_taxonomies( $only_with_archive = false ) {
$tax_data = get_object_taxonomies( 'product', 'objects' );
@@ -244,7 +261,7 @@
foreach ( $tax_data as $taxonomy ) {
$name = $taxonomy->name;
- if ( ! in_array( $name, $array ) ) {
+ if ( ! in_array( $name, $array, true ) ) {
$others[] = $name;
}
}
@@ -260,9 +277,9 @@
continue;
}
- if ( in_array( $name, $main_taxonomies ) || in_array( $name, $optional_taxonomies ) ) {
+ if ( in_array( $name, $main_taxonomies, true ) || in_array( $name, $optional_taxonomies, true ) ) {
$default_filter_key = str_replace( '_', '-', $name );
- } elseif ( in_array( $name, $attributes ) ) {
+ } elseif ( in_array( $name, $attributes, true ) ) {
$default_filter_key = str_replace( 'pa_', '', $name );
} else {
// Check if pro version found and pretty url is enabled then don't add the underscore.
@@ -281,6 +298,11 @@
return $taxonomies;
}
+ /**
+ * Retrieves available display date formats.
+ *
+ * @return array Display date formats.
+ */
public static function display_date_formats() {
return apply_filters(
'wcapf_display_date_formats',
@@ -297,6 +319,11 @@
);
}
+ /**
+ * Retrieves product status options.
+ *
+ * @return array Product status options.
+ */
public static function product_status_options() {
return apply_filters(
'wcapf_product_status_options',
@@ -314,7 +341,9 @@
}
/**
- * @return array
+ * Retrieves time period options.
+ *
+ * @return array Time period options.
*/
public static function time_period_options() {
$_time_period_options = WCAPF_Helper::get_time_period_options();
@@ -331,7 +360,9 @@
}
/**
- * @return array
+ * Retrieves sort-by options.
+ *
+ * @return array Sort-by options.
*/
public static function sort_by_options() {
return apply_filters(
@@ -398,9 +429,9 @@
}
/**
- * Gets the meta types.
+ * Retrieves meta type options.
*
- * @return array
+ * @return array Meta type options.
*/
public static function meta_type_options() {
return apply_filters(
@@ -435,6 +466,11 @@
);
}
+ /**
+ * Retrieves user role options.
+ *
+ * @return array User role options.
+ */
public static function user_role_options() {
$_user_roles = WCAPF_Product_Filter_Utils::get_user_roles();
$user_roles = array();
@@ -449,11 +485,21 @@
return $user_roles;
}
+ /**
+ * Sanitizes manual option data.
+ *
+ * Allows limited HTML for label-related fields and sanitizes all other values
+ * as plain text.
+ *
+ * @param array $data Manual option data.
+ *
+ * @return array Sanitized manual option data.
+ */
public static function sanitize_manual_option_data( $data ) {
$sanitized = array();
foreach ( $data as $key => $_value ) {
- if ( in_array( $key, array( 'label', 'secondary_label', 'tooltip' ) ) ) {
+ if ( in_array( $key, array( 'label', 'secondary_label', 'tooltip' ), true ) ) {
$value = wp_kses_post( $_value );
} else {
$value = sanitize_text_field( $_value );
@@ -466,21 +512,21 @@
}
/**
- * Gets the plugin settings for our React UI.
+ * Retrieves plugin settings for the admin UI.
*
- * @return array
+ * @return array Plugin settings.
*/
public static function get_settings() {
$settings = WCAPF_Helper::get_settings();
// Send the author roles with labels.
- if ( ! empty ( $settings['author_roles'] ) ) {
+ if ( ! empty( $settings['author_roles'] ) ) {
$array = WCAPF_Product_Filter_Utils::get_user_roles();
$with_labels = array();
foreach ( $settings['author_roles'] as $role_name ) {
$with_labels[] = array(
- 'label' => $array[ $role_name ],
+ 'label' => isset( $array[ $role_name ] ) ? $array[ $role_name ] : $role_name,
'value' => $role_name,
);
}
@@ -488,13 +534,23 @@
$settings['author_roles'] = $with_labels;
}
+ /**
+ * Filters the parsed plugin settings.
+ *
+ * Allows modification of the settings array before it is returned for use
+ * in the admin UI or other plugin logic.
+ *
+ * @param array $settings Parsed plugin settings.
+ *
+ * @return array Filtered plugin settings.
+ */
return apply_filters( 'wcapf_parse_settings', $settings );
}
/**
- * Gets the forms for our React UI.
+ * Retrieves all forms for the admin forms list UI.
*
- * @return array
+ * @return array Forms data.
*/
public static function get_forms() {
$args = array(
@@ -507,18 +563,24 @@
$forms = array();
foreach ( $posts as $post ) {
- $forms[] = self::get_form_data( $post );
+ $form_data = self::get_form_data( $post );
+
+ if ( ! empty( $form_data ) ) {
+ $forms[] = $form_data;
+ }
}
return $forms;
}
/**
- * Gets the form data for given id.
+ * Retrieves form data for the admin forms list UI.
*
- * @param int|WP_Post $post Post ID or post object.
+ * Accepts a post object or post ID and returns the parsed form data.
*
- * @return array
+ * @param WP_Post|int $post Post object or post ID.
+ *
+ * @return array Form data.
*/
public static function get_form_data( $post ) {
if ( $post instanceof WP_Post ) {
@@ -527,6 +589,10 @@
$_post = get_post( $post );
}
+ if ( ! $_post instanceof WP_Post ) {
+ return array();
+ }
+
$id = $_post->ID;
$data = array(
@@ -539,5 +605,4 @@
*/
return apply_filters( 'wcapf_admin_form_data', $data, $id );
}
-
}
--- a/wc-ajax-product-filter/includes/class-wcapf-default-data.php
+++ b/wc-ajax-product-filter/includes/class-wcapf-default-data.php
@@ -5,9 +5,14 @@
* @since 4.0.0
* @package wc-ajax-product-filter
* @subpackage wc-ajax-product-filter/includes
- * @author wptools.io
+ * @author Mainul Hassan
*/
+// Exit if accessed directly.
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
/**
* WCAPF_Default_Data class.
*
@@ -15,19 +20,24 @@
*/
class WCAPF_Default_Data {
+ /**
+ * Returns an array containing the default data for the form.
+ *
+ * @return array An array containing the default data for the form.
+ */
public static function form_default_data() {
return array(
'form_locations' => '',
'priority' => '0',
- // 'form_layout' => 'vertical',
- // 'columns_per_row' => '4',
- // 'show_form_on_top_of_products' => '1',
- // 'filter_mode' => 'immediate',
- // 'form_visibility' => 'always_display',
'show_clear_btn' => '',
);
}
+ /**
+ * Returns an array containing the default settings for the plugin.
+ *
+ * @return array An array containing the default settings for the plugin.
+ */
public static function default_settings() {
return array(
// General
@@ -104,11 +114,18 @@
// Miscellaneous
'debug_mode' => '1',
'disable_wcapf' => '',
- 'send_anonymous_data' => '',
'remove_data' => '',
);
}
+ /**
+ * Returns sample filter data.
+ *
+ * Generates a sample set of filters for a form, including product category,
+ * price, attributes, product tag, product status, rating, and reset button.
+ *
+ * @return array Sample filter data.
+ */
public static function get_sample_filters() {
$filters_basic_data = array();
@@ -152,20 +169,22 @@
)
);
- if ( $attribute_terms ) {
+ if ( ! is_wp_error( $attribute_terms ) && ! empty( $attribute_terms ) ) {
$taxonomy_data = get_taxonomy( $attribute );
- $filters_basic_data[] = array(
- 'title' => $taxonomy_data->labels->singular_name,
- 'type' => 'taxonomy',
- 'taxonomy' => $attribute,
- 'display_type' => 'select',
- 'all_items_label' => __( 'Any', 'wc-ajax-product-filter' ),
- );
+ if ( $taxonomy_data && isset( $taxonomy_data->labels->singular_name ) ) {
+ $filters_basic_data[] = array(
+ 'title' => $taxonomy_data->labels->singular_name,
+ 'type' => 'taxonomy',
+ 'taxonomy' => $attribute,
+ 'display_type' => 'select',
+ 'all_items_label' => __( 'Any', 'wc-ajax-product-filter' ),
+ );
- $used[] = $attribute;
+ $used[] = $attribute;
- break;
+ break;
+ }
}
}
@@ -173,7 +192,7 @@
$found_second_attribute = false;
foreach ( $attributes as $attribute ) {
- if ( in_array( $attribute, $used ) ) {
+ if ( in_array( $attribute, $used, true ) ) {
continue;
}
@@ -184,19 +203,21 @@
)
);
- if ( $attribute_terms ) {
+ if ( ! is_wp_error( $attribute_terms ) && ! empty( $attribute_terms ) ) {
$taxonomy_data = get_taxonomy( $attribute );
- $filters_basic_data[] = array(
- 'title' => $taxonomy_data->labels->singular_name,
- 'type' => 'taxonomy',
- 'taxonomy' => $attribute,
- 'display_type' => 'multi-select',
- );
+ if ( $taxonomy_data && isset( $taxonomy_data->labels->singular_name ) ) {
+ $filters_basic_data[] = array(
+ 'title' => $taxonomy_data->labels->singular_name,
+ 'type' => 'taxonomy',
+ 'taxonomy' => $attribute,
+ 'display_type' => 'multi-select',
+ );
- $found_second_attribute = true;
+ $found_second_attribute = true;
- break;
+ break;
+ }
}
}
@@ -209,7 +230,7 @@
)
);
- if ( $tags ) {
+ if ( ! is_wp_error( $tags ) && ! empty( $tags ) ) {
$filters_basic_data[] = array(
'title' => __( 'Tag', 'wc-ajax-product-filter' ),
'type' => 'taxonomy',
@@ -258,6 +279,15 @@
return $filters;
}
+ /**
+ * Returns the default data for a product filter.
+ *
+ * Defines the initial values used for a filter, including general settings,
+ * taxonomy settings, price settings, post meta settings, advanced settings,
+ * active filters settings, reset button settings, and error fields.
+ *
+ * @return array Default filter data.
+ */
public static function filter_default_data() {
return array(
'id' => '',
@@ -265,7 +295,7 @@
'type' => '',
'taxonomy' => '',
'taxHierarchical' => '',
- 'meta_key' => '',
+ 'meta_key' => '', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Not a database query.
'component' => '',
'value_type' => 'text',
'field_key' => '',
@@ -384,5 +414,4 @@
'field_key_error_' => '', // Comes from server side.
);
}
-
}
--- a/wc-ajax-product-filter/includes/class-wcapf-field-instance.php
+++ b/wc-ajax-product-filter/includes/class-wcapf-field-instance.php
@@ -5,9 +5,14 @@
* @since 3.0.0
* @package wc-ajax-product-filter
* @subpackage wc-ajax-product-filter/includes
- * @author wptools.io
+ * @author Mainul Hassan
*/
+// Exit if accessed directly.
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
/**
* WCAPF_Field_Instance class.
*
@@ -15,39 +20,239 @@
*/
class WCAPF_Field_Instance {
+ /**
+ * Parsed display type.
+ *
+ * @var string
+ */
public $display_type;
+
+ /**
+ * Filter layout.
+ *
+ * @var string
+ */
public $layout;
+
+ /**
+ * Query type used for combining selected values.
+ *
+ * Determines the relation applied between multiple selected values,
+ * such as `and` or `or`.
+ *
+ * @var string
+ */
public $query_type;
+
+ /**
+ * Label used for the "all items" option.
+ *
+ * @var string
+ */
public $all_items_label;
+
+ /**
+ * Whether the field should use combobox behavior.
+ *
+ * @var mixed
+ */
public $use_combobox;
+
+ /**
+ * Whether multiple filtering is enabled.
+ *
+ * @var mixed
+ */
public $enable_multiple_filter;
+
+ /**
+ * Whether option counts should be displayed.
+ *
+ * @var mixed
+ */
public $show_count;
+
+ /**
+ * Whether empty options should be hidden.
+ *
+ * @var mixed
+ */
public $hide_empty;
+
+ /**
+ * Filter options source mode.
+ *
+ * Supported values typically include `automatically` and `manual_entry`.
+ *
+ * @var string
+ */
public $get_options;
+
+ /**
+ * Manually configured options.
+ *
+ * @var array
+ */
public $manual_options;
+
+ /**
+ * Field type.
+ *
+ * @var string
+ */
public $type;
+
+ /**
+ * Filter post ID.
+ *
+ * @var mixed
+ */
public $filter_id;
+
+ /**
+ * Filter key used in request and query handling.
+ *
+ * Identifies the filter in frontend requests and internal query parsing.
+ *
+ * @var string
+ */
public $filter_key;
+
+ /**
+ * Normalized filter type.
+ *
+ * @var string
+ */
public $filter_type;
+
+ /**
+ * Associated taxonomy name.
+ *
+ * @var string
+ */
public $taxonomy;
+
+ /**
+ * Whether the taxonomy options are hierarchical.
+ *
+ * @var bool
+ */
public $hierarchical;
+
+ /**
+ * Whether hierarchy accordion mode is enabled.
+ *
+ * @var bool
+ */
public $enable_hierarchy_accordion;
+
+ /**
+ * Filter value type.
+ *
+ * Determines how the filter value is interpreted and compared, such as
+ * text, number, or date.
+ *
+ * @var string
+ */
public $value_type;
+
+ /**
+ * Associated meta key.
+ *
+ * @var string
+ */
public $meta_key;
+
+ /**
+ * Associated post property.
+ *
+ * @var string
+ */
public $post_property;
+
+ /**
+ * Whether store name should be used for vendor-related fields.
+ *
+ * @var string
+ */
public $use_store_name;
+
+ /**
+ * Whether term slugs should be used instead of term IDs.
+ *
+ * @var mixed
+ */
public $use_term_slug;
+
+ /**
+ * SQL data type used for numeric comparisons.
+ *
+ * Defines the SQL type used when numeric values are compared in queries,
+ * such as `SIGNED` or `DECIMAL`.
+ *
+ * @var string
+ */
public $number_data_type;
+
+ /**
+ * Parent form ID.
+ *
+ * Holds the ID of the form this filter belongs to.
+ *
+ * @var mixed
+ */
public $form_id;
+
+ /**
+ * Whether the search field is enabled.
+ *
+ * @var mixed
+ */
public $enable_search_field;
+
+ /**
+ * Reduce-height mode.
+ *
+ * Stores the configured mode used to limit the option list height, such as
+ * soft limit or max height.
+ *
+ * @var false|string
+ */
public $enable_reduce_height;
+
+ /**
+ * Maximum height for option lists.
+ *
+ * @var float
+ */
public $max_height;
+
+ /**
+ * Number of initially visible options when soft limit is enabled.
+ *
+ * @var int
+ */
public $soft_limit;
+
+ /**
+ * Whether the soft limit feature is enabled.
+ *
+ * @var bool
+ */
public $enable_soft_limit;
+
+ /**
+ * Whether the max-height feature is enabled.
+ *
+ * @var bool
+ */
public $enable_max_height;
/**
- * The raw field instance.
+ * The raw field instance data.
+ *
+ * Holds the original filter configuration used to derive the parsed
+ * field properties.
*
* @var array
*/
@@ -193,16 +398,22 @@
}
/**
- * @return string
+ * Retrieves the raw field type.
+ *
+ * @return string Field type.
*/
private function get_field_type() {
return $this->get_sub_field_value( 'type' );
}
/**
- * @param string $name
+ * Retrieves a raw sub-field value from the field instance.
+ *
+ * Returns an empty string when the requested key is not available.
*
- * @return mixed
+ * @param string $name Sub-field name.
+ *
+ * @return mixed Sub-field value or an empty string if not set.
*/
public function get_sub_field_value( $name ) {
if ( isset( $this->instance[ $name ] ) ) {
@@ -213,7 +424,9 @@
}
/**
- * @return string
+ * Retrieves the default value type for the current field.
+ *
+ * @return string Default value type.
*/
private function field_default_value_type() {
$field_type = $this->get_field_type();
@@ -227,7 +440,9 @@
}
/**
- * @param string $display_type
+ * Parses and normalizes the configured display type.
+ *
+ * @param string $display_type Raw display type.
*
* @return string
*/
@@ -252,7 +467,7 @@
}
foreach ( $available_display_types as $key => $display_types ) {
- if ( in_array( $display_type, $display_types ) ) {
+ if ( in_array( $display_type, $display_types, true ) ) {
$_display_type = $key;
break;
}
@@ -262,9 +477,13 @@
}
/**
- * @param string $all_items_label
+ * Parses the all-items label for the filter.
*
- * @return string
+ * Applies context-aware defaults when no label is configured.
+ *
+ * @param string $all_items_label Raw all-items label.
+ *
+ * @return string Parsed all-items label.
*/
private function parse_all_items_label( $all_items_label ) {
$type = $this->get_sub_field_value( 'type' );
@@ -279,31 +498,42 @@
}
}
- return $all_items_label ?: __( 'All Items', 'wc-ajax-product-filter' );
+ if ( ! $all_items_label ) {
+ return __( 'All Items', 'wc-ajax-product-filter' );
+ }
+
+ return $all_items_label;
}
/**
+ * Retrieves the layout for the filter.
+ *
+ * Returns the allowed layout based on the current display type and falls
+ * back to `list` when the configured layout is invalid.
+ *
* @since 4.0.0
*
- * @return string
+ * @return string Filter layout.
*/
private function get_layout() {
$native_layouts = apply_filters( 'wcapf_native_layouts', array( 'list', 'inline' ) );
$custom_layouts = apply_filters( 'wcapf_custom_layouts', array( 'inline' ) );
- if ( in_array( $this->display_type, array( 'checkbox', 'radio' ) ) ) {
+ if ( in_array( $this->display_type, array( 'checkbox', 'radio' ), true ) ) {
$value = $this->get_sub_field_value( 'native_display_type_layout' );
- return in_array( $value, $native_layouts ) ? $value : 'list';
+ return in_array( $value, $native_layouts, true ) ? $value : 'list';
} else {
$value = $this->get_sub_field_value( 'custom_display_type_layout' );
- return in_array( $value, $custom_layouts ) ? $value : 'list';
+ return in_array( $value, $custom_layouts, true ) ? $value : 'list';
}
}
/**
- * @return string
+ * Retrieves the normalized filter type.
+ *
+ * @return string Filter type.
*/
private function get_filter_type() {
$field_type = $this->get_field_type();
@@ -319,12 +549,14 @@
}
/**
- * @return string
+ * Retrieves the taxonomy assigned to the current field.
+ *
+ * @return string Taxonomy name.
*/
private function get_taxonomy() {
$taxonomy = $this->get_sub_field_value( 'taxonomy' );
- if ( 'rating' === $this->type && 'automatically' == $this->get_options ) {
+ if ( 'rating' === $this->type && 'automatically' === $this->get_options ) {
$taxonomy = 'product_visibility';
}
@@ -332,7 +564,9 @@
}
/**
- * @return bool
+ * Determines whether the current taxonomy field should behave hierarchically.
+ *
+ * @return bool True if the field is hierarchical, otherwise false.
*/
private function taxonomy_is_hierarchical() {
$taxonomy = $this->get_taxonomy();
@@ -347,12 +581,12 @@
$non_hierarchical_display_types = array( 'label', 'color', 'image' );
- if ( in_array( $this->display_type, $non_hierarchical_display_types ) ) {
+ if ( in_array( $this->display_type, $non_hierarchical_display_types, true ) ) {
return false;
}
// Disable hierarchy for inline and grid layout.
- if ( in_array( $this->display_type, array( 'checkbox', 'radio' ) ) && 'list' !== $this->layout ) {
+ if ( in_array( $this->display_type, array( 'checkbox', 'radio' ), true ) && 'list' !== $this->layout ) {
return false;
}
@@ -360,7 +594,9 @@
}
/**
- * @return bool
+ * Determines whether hierarchy accordion mode is enabled.
+ *
+ * @return bool True if hierarchy accordion mode is enabled, otherwise false.
*/
private function is_hierarchy_accordion_enabled() {
if ( ! $this->taxonomy_is_hierarchical() ) {
@@ -369,7 +605,7 @@
$non_hierarchy_accordion_display_types = array( 'select', 'multiselect' );
- if ( in_array( $this->display_type, $non_hierarchy_accordion_display_types ) ) {
+ if ( in_array( $this->display_type, $non_hierarchy_accordion_display_types, true ) ) {
return false;
}
@@ -377,7 +613,9 @@
}
/**
- * @return string
+ * Retrieves the meta key assigned to the current field.
+ *
+ * @return string Meta key.
*/
private function get_meta_key() {
$meta_key = $this->get_sub_field_value( 'meta_key' );
@@ -386,7 +624,9 @@
}
/**
- * @return string
+ * Retrieves the value type for the current field.
+ *
+ * @return string Value type.
*/
private function get_value_type() {
$default_value_type = $this->field_default_value_type();
@@ -395,7 +635,9 @@
}
/**
- * @return string
+ * Retrieves the SQL data type used for numeric filtering.
+ *
+ * @return string SQL data type.
*/
private function get_number_data_type() {
$data_type = 'SIGNED';
@@ -418,6 +660,11 @@
return apply_filters( 'wcapf_number_data_type', $data_type, $this->instance );
}
+ /**
+ * Retrieves the post property mapped to the current field type.
+ *
+ * @return string Post property.
+ */
private function get_post_property() {
$type = $this->get_field_type();
$property = '';
@@ -430,9 +677,13 @@
}
/**
+ * Determines whether store name usage is enabled.
+ *
+ * Returns an empty string when no supported vendor plugin is active.
+ *
* @since 3.3.0
*
- * @return string
+ * @return string Store name setting value.
*/
private function is_store_name_enabled() {
if ( ! WCAPF_Helper::is_vendor_plugin_found() ) {
@@ -469,13 +720,15 @@
}
/**
- * Determines if reduce height is possible according to the display type.
+ * Determines whether reduce-height behavior is allowed for the current field.
*
- * @param string $field
+ * The result depends on the current display type and the requested context.
*
* @since 4.0.0
*
- * @return bool
+ * @param string $field Context for the check. Accepts `reduce-height` or `search`.
+ *
+ * @return bool True if reduce-height behavior is allowed, otherwise false.
*/
private function is_reduce_height_possible( $field = 'reduce-height' ) {
if ( $this->taxonomy_is_hierarchical() && 'search' === $field ) {
@@ -504,7 +757,7 @@
);
}
- if ( in_array( $this->display_type, $not_allowed_display_types ) ) {
+ if ( in_array( $this->display_type, $not_allowed_display_types, true ) ) {
return false;
}
@@ -534,7 +787,7 @@
* @return float
*/
private function filter_options_max_height() {
- return floatval( $this->get_sub_field_value( 'max_height' ) );
+ return (float) $this->get_sub_field_value( 'max_height' );
}
/**
@@ -569,5 +822,4 @@
private function is_max_height_enabled() {
return 'max_height' === $this->enable_reduce_height && 1 <= $this->max_height;
}
-
}
--- a/wc-ajax-product-filter/includes/class-wcapf-form-filters-utils.php
+++ b/wc-ajax-product-filter/includes/class-wcapf-form-filters-utils.php
@@ -5,9 +5,14 @@
* @since 4.0.0
* @package wc-ajax-product-filter
* @subpackage wc-ajax-product-filter/includes
- * @author wptools.io
+ * @author Mainul Hassan
*/
+// Exit if accessed directly.
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
/**
* WCAPF_Form_Filters_Utils class.
*
@@ -16,9 +21,11 @@
class WCAPF_Form_Filters_Utils {
/**
- * @param array $form_filters
- * @param int $new_form_id
- * @param bool $migrate
+ * Saves form filters and returns sanitized filters with collected errors.
+ *
+ * @param array $form_filters Form filter data.
+ * @param int $new_form_id Parent form post ID.
+ * @param bool $migrate Whether the filters are being migrated.
*
* @return array
*/
@@ -30,7 +37,7 @@
$errors = array();
// Check for error coming for filter keys.
- if ( $form_filters ) {
+ if ( ! empty( $form_filters ) && is_array( $form_filters ) ) {
foreach ( $form_filters as $filter_order => $filter ) {
list(
,
@@ -61,7 +68,7 @@
wp_send_json_error( array( 'errors' => $errors ) );
}
- if ( $form_filters ) {
+ if ( ! empty( $form_filters ) && is_array( $form_filters ) ) {
foreach ( $form_filters as $filter_order => $filter ) {
$is_new = isset( $filter['isNew'] ) ? $filter['isNew'] : '';
$unique_index = isset( $filter['uniqueIndex'] ) ? $filter['uniqueIndex'] : '';
@@ -76,7 +83,7 @@
$component
) = $this->get_filter_data( $filter );
- if ( ! in_array( $type, $valid_types ) ) {
+ if ( ! in_array( $type, $valid_types, true ) ) {
continue;
}
@@ -121,7 +128,7 @@
}
// Don't add same filter type in a form multiple times.
- if ( in_array( $filter_type, $filter_types ) ) {
+ if ( in_array( $filter_type, $filter_types, true ) ) {
$post_status = 'draft';
} else {
$post_status = 'publish';
@@ -201,11 +208,13 @@
'menu_order' => $filter_order,
);
- add_filter( 'pre_wp_unique_post_slug', function () use ( $post_name ) {
+ $unique_post_slug_callback = static function () use ( $post_name ) {
return $post_name;
- } );
+ };
+ add_filter( 'pre_wp_unique_post_slug', $unique_post_slug_callback );
$new_filter_id = wp_update_post( $post_arr, true );
+ remove_filter( 'pre_wp_unique_post_slug', $unique_post_slug_callback );
if ( ! is_wp_error( $new_filter_id ) ) {
// Add 'isNew', 'uniqueIndex' when returning the filter data for React UI.
@@ -226,14 +235,16 @@
}
/**
- * @param $filter
+ * Retrieves normalized filter data.
+ *
+ * @param array $filter Raw filter data.
*
* @return array
*/
private function get_filter_data( $filter ) {
$filter_title = isset( $filter['title'] ) ? sanitize_text_field( $filter['title'] ) : '';
$filter_id = isset( $filter['id'] ) ? absint( $filter['id'] ) : 0;
- $post_name = isset( $filter['field_key'] ) ? sanitize_title( $filter['field_key'] ) : '';
+ $post_name = isset( $filter['field_key'] ) ? urldecode( sanitize_title( $filter['field_key'] ) ) : '';
$type = isset( $filter['type'] ) ? sanitize_text_field( $filter['type'] ) : '';
$taxonomy = isset( $filter['taxonomy'] ) ? sanitize_text_field( $filter['taxonomy'] ) : '';
$meta_key = isset( $filter['meta_key'] ) ? sanitize_text_field( $filter['meta_key'] ) : '';
@@ -243,17 +254,26 @@
}
/**
- * Tries to retrieve the filter key for the filter.
+ * Retrieves and validates the filter key for a filter configuration.
*
- * @param string $type
- * @param array $possible_types
- * @param string $taxonomy
- * @param array $filter_data
- * @param string $post_name
- * @param string $meta_key
- * @param int $filter_order
+ * Determines the correct field key (post_name) for a filter based on its
+ * type, taxonomy, meta key, or global configuration. It also validates the
+ * key to prevent conflicts with existing post types, taxonomies, or reserved
+ * search keys.
*
- * @return array
+ * @param string $type Filter type (taxonomy, post-meta, component, etc).
+ * @param array $possible_types List of available filter types from the API.
+ * @param string $taxonomy Selected taxonomy when the filter type is taxonomy.
+ * @param array $filter_data Raw filter configuration data.
+ * @param string $post_name Proposed filter key (slug).
+ * @param string $meta_key Meta key when the filter type is post-meta.
+ * @param int $filter_order Position of the filter in the form.
+ *
+ * @return array {
+ * @type string $post_name Final filter key (slug).
+ * @type array $error_data Error information if the key conflicts.
+ * @type array $filter_type_data Filter type metadata.
+ * }
*/
public function retrieve_filter_key(
$type,
@@ -273,13 +293,13 @@
}
if ( 'taxonomy' === $type ) {
- $taxonomy_index = array_search( 'taxonomy', array_column( $possible_types, 'value' ) );
+ $taxonomy_index = array_search( 'taxonomy', array_column( $possible_types, 'value' ), true );
$taxonomy_options = $possible_types[ $taxonomy_index ];
$taxonomy_types = $taxonomy_options['options'];
- $filter_type_index = array_search( $taxonomy, array_column( $taxonomy_types, 'value' ) );
+ $filter_type_index = array_search( $taxonomy, array_column( $taxonomy_types, 'value' ), true );
$filter_type_data = $taxonomy_types[ $filter_type_index ];
} else {
- $filter_type_index = array_search( $type, array_column( $possible_types, 'value' ) );
+ $filter_type_index = array_search( $type, array_column( $possible_types, 'value' ), true );
$filter_type_data = $possible_types[ $filter_type_index ];
}
@@ -301,9 +321,6 @@
$error_data = array();
- // Check if pro version found and pretty url is enabled then don't generate the below errors.
- // if ( ! WCAPF_Helper::found_pro_version() ) {
-
if ( post_type_exists( $post_name ) ) {
$error_data = array(
'key' => 'field_key_error_',
@@ -329,36 +346,33 @@
);
}
- // }
-
return array( $post_name, $error_data, $filter_type_data );
}
/**
- * Generates the filter key error message.
+ * Generates a user-facing error message when a filter key conflicts with
+ * an existing WordPress post type or taxonomy.
*
- * @param string $type
+ * @param string $type Conflict type. Expected values: 'post-type' or 'taxonomy'.
*
- * @return string
+ * @return string Localized error message explaining the conflict.
*/
private function generate_filter_key_error_message( $type ) {
- $post_type_err = __(
- "Post type name can't be used as a filter key, it'll create conflict.",
- 'wc-ajax-product-filter'
+ $messages = array(
+ 'post-type' => __(
+ "Post type name can't be used as a filter key, it'll create conflict.",
+ 'wc-ajax-product-filter'
+ ),
+ 'taxonomy' => __(
+ "Taxonomy name can't be used as a filter key, it'll create conflict.",
+ 'wc-ajax-product-filter'
+ ),
);
- $tax_type_err = __(
- "Taxonomy name can't be used as a filter key, it'll create conflict.",
- 'wc-ajax-product-filter'
- );
+ $common_message = __( 'Please make it unique by adding an underscore.', 'wc-ajax-product-filter' );
+ $type_message = isset( $messages[ $type ] ) ? $messages[ $type ] : $messages['post-type'];
- $common_err = __( 'Please make it unique by adding an underscore.', 'wc-ajax-product-filter' );
-
- return sprintf(
- '%s %s',
- 'taxonomy' === $type ? $tax_type_err : $post_type_err,
- $common_err
- );
+ return sprintf( '%s %s', $type_message, $common_message );
}
/**
@@ -396,8 +410,8 @@
$sanitized_filter = array();
foreach ( $filter as $key => $value ) {
- if ( in_array( $key, $float_fields ) ) {
- $value = floatval( $value );
+ if ( in_array( $key, $float_fields, true ) ) {
+ $value = (float) $value;
if ( 'min_value' === $key && ! $value ) {
$value = 0;
@@ -410,34 +424,32 @@
if ( 'step' === $key && ! $value ) {
$value = 1;
}
- } elseif ( in_array( $key, $absint_fields ) ) {
+ } elseif ( in_array( $key, $absint_fields, true ) ) {
$value = absint( $value );
- } elseif ( in_array( $key, $limit_fields ) ) {
+ } elseif ( in_array( $key, $limit_fields, true ) ) {
if ( ! $migrate ) {
// Pick the ids only.
- $value = wp_list_pluck( $value, 'value' );
+ $value = is_array( $value ) ? wp_list_pluck( $value, 'value' ) : array();
$value = array_map( 'sanitize_text_field', $value );
}
- } elseif ( in_array( $key, $single_array_fields ) ) {
- $value = isset( $value['value'] ) ? $value['value'] : '';
- } elseif ( in_array( $key, $value_may_have_spaces ) ) {
+ } elseif ( in_array( $key, $single_array_fields, true ) ) {
+ $value = ( is_array( $value ) && isset( $value['value'] ) ) ? $value['value'] : '';
+ } elseif ( in_array( $key, $value_may_have_spaces, true ) ) {
$value = str_replace( ' ', ' ', $value );
$with_markup = array( 'values_separator', 'text_before_min_value', 'text_before_max_value' );
- if ( in_array( $key, $with_markup ) ) {
+ if ( in_array( $key, $with_markup, true ) ) {
$value = wp_kses_data( $value );
} else {
$value = sanitize_text_field( $value );
}
- } elseif ( in_array( $key, $markup_fields ) ) {
+ } elseif ( in_array( $key, $markup_fields, true ) ) {
$value = wp_kses_post( $value );
} elseif ( 'product_status_options' === $key ) {
$value = $this->sanitize_product_status_options( $value );
- } else {
- if ( ! $migrate ) {
- $value = sanitize_text_field( $value );
- }
+ } elseif ( ! $migrate ) {
+ $value = sanitize_text_field( $value );
}
$sanitized_filter[ $key ] = $value;
@@ -447,6 +459,8 @@
}
/**
+ * Sanitizes product status options.
+ *
* @param array $options The array of options.
*
* @return array
@@ -461,8 +475,8 @@
foreach ( $options as $option ) {
$option = WCAPF_API_Utils::sanitize_manual_option_data( $option );
- $value = $option['value'];
- $label = $option['label'];
+ $value = isset( $option['value'] ) ? $option['value'] : '';
+ $label = isset( $option['label'] ) ? $option['label'] : '';
if ( ! strlen( $value ) ) {
continue;
@@ -477,5 +491,4 @@
return $array;
}
-
}
--- a/wc-ajax-product-filter/includes/class-wcapf-form.php
+++ b/wc-ajax-product-filter/includes/class-wcapf-form.php
@@ -5,9 +5,14 @@
* @since 4.0.0
* @package wc-ajax-product-filter
* @subpackage wc-ajax-product-filter/includes
- * @author wptools.io
+ * @author Mainul Hassan
*/
+// Exit if accessed directly.
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
/**
* WCAPF_Form class.
*
@@ -16,20 +21,46 @@
class WCAPF_Form {
/**
- * @var $form array
+ * The form array.
+ *
+ * @var array
*/
protected $form;
+ /**
+ * Initializes the form instance.
+ *
+ * Retrieves the current form data and stores it for later use during
+ * frontend rendering.
+ *
+ * @return void
+ */
public function __construct() {
$this->form = $this->retrieve_form();
}
+ /**
+ * Retrieves the current form data.
+ *
+ * Reads the active form data from the global form variable prepared by the
+ * plugin before rendering.
+ *
+ * @return array Form data.
+ */
protected function retrieve_form() {
global $wcapf_form;
return $wcapf_form;
}
+ /**
+ * Renders the filter form on the frontend.
+ *
+ * Outputs the full form markup, including all configured filters and
+ * components for the form instance.
+ *
+ * @return void
+ */
public function render_form() {
if ( ! $this->should_we_proceed() ) {
return;
@@ -37,9 +68,13 @@
$form_id = isset( $this->form['form_id'] ) ? $this->form['form_id'] : '';
- $form_classes = 'wcapf-form wcapf-form-' . $form_id;
+ $form_classes = 'wcapf-form wcapf-form-' . sanitize_html_class( $form_id );
- echo '<div class="' . esc_attr( $form_classes ) . '" data-id="' . esc_attr( $form_id ) . '">';
+ printf(
+ '<div class="%1$s" data-id="%2$s">',
+ esc_attr( $form_classes ),
+ esc_attr( $form_id )
+ );
do_action( 'wcapf_before_form_filters', $form_id );
@@ -49,7 +84,12 @@
$number_input_types = WCAPF_Helper::number_input_display_types();
foreach ( $filters as $filter ) {
- $field_data = $filter['field'];
+ $field_data = isset( $filter['field'] ) ? $filter['field'] : array();
+
+ if ( empty( $field_data ) || ! is_array( $field_data ) ) {
+ continue;
+ }
+
$field_instance = new WCAPF_Field_Instance( $field_data );
$field_type = $field_instance->type;
@@ -59,7 +99,10 @@
if ( 'component' === $field_type ) {
$this->render_components( $field_instance );
- } elseif ( 'price' === $field_type && in_array( $field_instance->display_type, $number_input_types ) ) {
+ } elseif (
+ 'price' === $field_type &&
+ in_array( $field_instance->display_type, $number_input_types, true )
+ ) {
$this->render_price_filter( $field_instance );
} elseif ( 'keyword' === $field_type ) {
$this->render_keyword_filter( $field_instance );
@@ -68,26 +111,26 @@
}
}
- if ( $this->is_debugging() ) {
+ if ( WCAPF_Helper::is_debug_mode_enabled() ) {
$edit_url = WCAPF_Helper::form_edit_url( $form_id );
if ( ! $filters ) {
- /** @noinspection HtmlUnknownTarget */
- echo WCAPF_Helper::get_debug_message(
- sprintf(
- __(
- 'The form is empty. Please add some filters by editing the form <a href="%s">here</a>.',
- 'wc-ajax-product-filter'
- ),
- esc_url( $edit_url )
- )
+ $message = sprintf(
+ /* translators: %s: Form edit URL. */
+ __(
+ 'The form is empty. Please add some filters by editing the form <a href="%s">here</a>.',
+ 'wc-ajax-product-filter'
+ ),
+ esc_url( $edit_url )
);
+
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is escaped inside WCAPF_Helper::get_debug_message().
+ echo WCAPF_Helper::get_debug_message( $message );
} else {
- /** @noinspection HtmlUnknownTarget */
- echo sprintf(
- '<p><a href="%s">%s</a></p>',
+ printf(
+ '<p><a href="%1$s">%2$s</a></p>',
esc_url( $edit_url ),
- __( 'Edit form', 'wc-ajax-product-filter' )
+ esc_html__( 'Edit form', 'wc-ajax-product-filter' )
);
}
}
@@ -99,6 +142,14 @@
$this->set_done();
}
+ /**
+ * Determines whether the form can be rendered.
+ *
+ * Prevents rendering when the form data is missing or the form has already
+ * been rendered.
+ *
+ * @return bool True if rendering should continue, otherwise false.
+ */
private function should_we_proceed() {
if ( ! $this->form ) {
return false;
@@ -112,7 +163,9 @@
}
/**
- * @param WCAPF_Field_Instance $field_instance
+ * Render the form components.
+ *
+ * @param WCAPF_Field_Instance $field_instance The field instance.
*
* @return void
*/
@@ -127,7 +180,9 @@
}
/**
- * @param WCAPF_Field_Instance $field_instance
+ * Renders the active filters.
+ *
+ * @param WCAPF_Field_Instance $field_instance The field instance.
*
* @return void
*/
@@ -140,8 +195,7 @@
$clear_btn_label = WCAPF_Helper::clear_all_button_label();
$clear_btn_layout = $field_instance->get_sub_field_value( 'clear_all_btn_layout' );
- WCAPF_Template_Loader::get_instance()->load(
- 'active-filters',
+ $args = WCAPF_Helper::prepare_active_filters_args(
array(
'title' => $title,
'show_title' => $show_title,
@@ -152,25 +206,32 @@
'clear_all_btn_layout' => $clear_btn_layout,
)
);
+
+ WCAPF_Template_Loader::get_instance()->load( 'active-filters', $args );
}
/**
- * @param WCAPF_Field_Instance $field_instance
+ * Renders the reset button.
+ *
+ * @param WCAPF_Field_Instance $field_instance The field instance.
*
* @return void
*/
private function render_reset_button( $field_instance ) {
- WCAPF_Template_Loader::get_instance()->load(
- 'reset-button',
+ $args = WCAPF_Helper::prepare_reset_button_args(
array(
'btn_label' => WCAPF_Helper::reset_button_label(),
'show_always' => $field_instance->get_sub_field_value( 'show_if_empty' ),
)
);
+
+ WCAPF_Template_Loader::get_instance()->load( 'reset-button', $args );
}
/**
- * @param WCAPF_Field_Instance $field_instance
+ * Renders the