Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/contextual-related-posts/contextual-related-posts.php
+++ b/contextual-related-posts/contextual-related-posts.php
@@ -15,7 +15,7 @@
* Plugin Name: Contextual Related Posts
* Plugin URI: https://webberzone.com/plugins/contextual-related-posts/
* Description: Display related posts on your website or in your feed. Increase reader retention and reduce bounce rates.
- * Version: 4.2.1
+ * Version: 4.2.2
* Author: WebberZone
* Author URI: https://webberzone.com
* License: GPL-2.0+
@@ -36,7 +36,7 @@
* @since 2.9.3
*/
if ( ! defined( 'WZ_CRP_VERSION' ) ) {
- define( 'WZ_CRP_VERSION', '4.2.1' );
+ define( 'WZ_CRP_VERSION', '4.2.2' );
}
--- a/contextual-related-posts/includes/admin/class-admin-banner.php
+++ b/contextual-related-posts/includes/admin/class-admin-banner.php
@@ -29,42 +29,42 @@
*
* @var array<string, mixed>
*/
- private array $config = array();
+ public array $config = array();
/**
* Derived class names keyed by component.
*
* @var array<string, array<int, string>>
*/
- private array $class_names = array();
+ public array $class_names = array();
/**
* Localized strings.
*
* @var array<string, string>
*/
- private array $strings = array();
+ public array $strings = array();
/**
* Style configuration.
*
* @var array<string, mixed>
*/
- private array $style = array();
+ public array $style = array();
/**
* Base class prefix shared by all banners.
*
* @var string
*/
- private string $base_prefix = 'wz-admin-banner';
+ public string $base_prefix = 'wz-admin-banner';
/**
* Unique class prefix derived from the provided prefix.
*
* @var string
*/
- private string $unique_prefix = 'admin-banner';
+ public string $unique_prefix = 'admin-banner';
/**
* Constructor.
@@ -108,7 +108,7 @@
/**
* Register hooks.
*/
- private function hooks(): void {
+ public function hooks(): void {
Hook_Registry::add_action( 'admin_enqueue_scripts', array( $this, 'maybe_enqueue_styles' ) );
Hook_Registry::add_action( 'in_admin_header', array( $this, 'render' ) );
}
@@ -197,7 +197,7 @@
/**
* Enqueue the banner stylesheet.
*/
- private function enqueue_style(): void {
+ public function enqueue_style(): void {
wp_register_style(
$this->style['handle'],
$this->style['url'],
@@ -213,7 +213,7 @@
* @param WP_Screen $screen Current admin screen.
* @param string $page_slug Current request page slug.
*/
- private function should_render_on_screen( WP_Screen $screen, string $page_slug ): bool {
+ public function should_render_on_screen( WP_Screen $screen, string $page_slug ): bool {
$screen_base = (string) $screen->base;
if ( '' !== $screen_base && in_array( $screen_base, (array) $this->config['exclude_screen_bases'], true ) ) {
return false;
@@ -237,7 +237,7 @@
* @param WP_Screen $screen Current admin screen.
* @param string $page_slug Current request page slug.
*/
- private function resolve_current_section( WP_Screen $screen, string $page_slug ): string {
+ public function resolve_current_section( WP_Screen $screen, string $page_slug ): string {
$screen_id = (string) $screen->id;
foreach ( $this->config['sections'] as $section_key => $section ) {
@@ -260,7 +260,7 @@
*
* @param array $strings Raw strings array.
*/
- private function prepare_strings( array $strings ): array {
+ public function prepare_strings( array $strings ): array {
$defaults = array(
'region_label' => '',
'nav_label' => '',
@@ -277,7 +277,7 @@
*
* @param string $prefix Base prefix.
*/
- private function resolve_wrapper_prefix( string $prefix ): string {
+ public function resolve_wrapper_prefix( string $prefix ): string {
$prefix = sanitize_key( $prefix );
if ( '' === $prefix ) {
@@ -292,7 +292,7 @@
*
* @param array $style Style configuration.
*/
- private function prepare_style_config( array $style ): array {
+ public function prepare_style_config( array $style ): array {
$defaults = array(
'handle' => $this->sanitize_handle( "{$this->unique_prefix}-styles" ),
'deps' => array(),
@@ -306,7 +306,8 @@
if ( empty( $style_config['url'] ) ) {
$assets_base = trailingslashit( plugin_dir_url( __FILE__ ) ) . 'css/';
$min_suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
- $style_config['url'] = $assets_base . $style_config['filename'] . $min_suffix . '.css';
+ $rtl_suffix = is_rtl() ? '-rtl' : '';
+ $style_config['url'] = $assets_base . $style_config['filename'] . $rtl_suffix . $min_suffix . '.css';
}
return $style_config;
@@ -319,7 +320,7 @@
*
* @return array
*/
- private function sanitize_sections( array $sections ): array {
+ public function sanitize_sections( array $sections ): array {
$sanitized = array();
foreach ( $sections as $key => $section ) {
@@ -348,7 +349,7 @@
*
* @return array<string, array<int, string>>
*/
- private function derive_class_names(): array {
+ public function derive_class_names(): array {
$build = function ( string $suffix = '' ): array {
$classes = array( $this->base_prefix . $suffix );
@@ -381,7 +382,7 @@
*
* @return array
*/
- private function collect_targets_from_sections( string $target_key ): array {
+ public function collect_targets_from_sections( string $target_key ): array {
$values = array();
foreach ( $this->config['sections'] as $section ) {
@@ -401,7 +402,7 @@
*
* @param array $section Section configuration.
*/
- private function get_section_link_classes( array $section ): array {
+ public function get_section_link_classes( array $section ): array {
$classes = $this->class_names['link'] ?? array();
$type = isset( $section['type'] ) ? sanitize_key( $section['type'] ) : 'secondary';
$type = '' !== $type ? $type : 'secondary';
@@ -422,7 +423,7 @@
* @param array $classes Class list.
* @return string Class attribute string.
*/
- private function implode_classes( array $classes ): string {
+ public function implode_classes( array $classes ): string {
return implode( ' ', array_unique( array_filter( $classes ) ) );
}
@@ -432,7 +433,7 @@
* @param string $key Classes array key.
* @return string Class attribute string.
*/
- private function class_attr( string $key ): string {
+ public function class_attr( string $key ): string {
return $this->implode_classes( $this->class_names[ $key ] ?? array() );
}
@@ -441,19 +442,15 @@
*
* @param string $handle Raw handle.
*/
- private function sanitize_handle( string $handle ): string {
+ public function sanitize_handle( string $handle ): string {
return sanitize_title_with_dashes( $handle );
}
/**
* Get the current page slug from the request.
*/
- private function get_request_page_slug(): string {
- $page_param_raw = filter_input( INPUT_GET, 'page', FILTER_UNSAFE_RAW );
-
- if ( is_string( $page_param_raw ) && '' !== $page_param_raw ) {
- $page_raw = sanitize_text_field( $page_param_raw );
- } elseif ( isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ public function get_request_page_slug(): string {
+ if ( isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$page_raw = sanitize_text_field( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
} else {
return '';
--- a/contextual-related-posts/includes/admin/class-metabox.php
+++ b/contextual-related-posts/includes/admin/class-metabox.php
@@ -469,12 +469,16 @@
array( 'dashicons' ),
WZ_CRP_VERSION
);
- wp_enqueue_script(
- 'wz-taxonomy-suggest-js',
- WZ_CRP_PLUGIN_URL . "includes/admin/settings/js/taxonomy-suggest{$file_prefix}.js",
- array( 'jquery' ),
- WZ_CRP_VERSION,
- true
+
+ // Enqueue Tom Select using Settings_API method.
+ WebberZoneContextual_Related_PostsAdminSettingsSettings_API::enqueue_scripts_styles(
+ 'crp',
+ array(
+ 'strings' => array(
+ /* translators: %s: search term */
+ 'no_results' => esc_html__( 'No results found for "%s"', 'contextual-related-posts' ),
+ ),
+ )
);
}
}
--- a/contextual-related-posts/includes/admin/class-settings.php
+++ b/contextual-related-posts/includes/admin/class-settings.php
@@ -568,17 +568,16 @@
'name' => esc_html__( 'Number of posts to display', 'contextual-related-posts' ),
'desc' => esc_html__( 'Maximum number of posts that will be displayed in the list. This option is used if you do not specify the number of posts in the widget or shortcodes', 'contextual-related-posts' ),
'type' => 'number',
- 'default' => '6',
- 'min' => '1',
- 'size' => 'small',
+ 'default' => 6,
+ 'min' => 1,
),
'daily_range' => array(
'id' => 'daily_range',
'name' => esc_html__( 'Related posts should be newer than', 'contextual-related-posts' ),
'desc' => esc_html__( 'This sets the cut-off period for which posts will be displayed. e.g. setting it to 365 will show related posts from the last year only. Set to 0 to disable limiting posts by date.', 'contextual-related-posts' ),
'type' => 'number',
- 'default' => '0',
- 'min' => '0',
+ 'default' => 0,
+ 'min' => 0,
),
'ordering' => array(
'id' => 'ordering',
@@ -614,7 +613,7 @@
'desc' => __( 'The weight to give to the post title when calculating the relevance of the post.', 'contextual-related-posts' ),
'type' => 'number',
'default' => 10,
- 'min' => '0',
+ 'min' => 0,
'size' => 'small',
'pro' => true,
),
@@ -624,7 +623,7 @@
'desc' => __( 'The weight to give to the post content when calculating the relevance of the post. This may make the query take longer to process.', 'contextual-related-posts' ),
'type' => 'number',
'default' => 0,
- 'min' => '0',
+ 'min' => 0,
'size' => 'small',
'pro' => true,
),
@@ -634,7 +633,7 @@
'desc' => __( 'The weight to give to the post excerpt when calculating the relevance of the post.', 'contextual-related-posts' ),
'type' => 'number',
'default' => 0,
- 'min' => '0',
+ 'min' => 0,
'size' => 'small',
'pro' => true,
),
@@ -644,7 +643,7 @@
'desc' => __( 'Weight to give category matches when calculating relevance.', 'contextual-related-posts' ),
'type' => 'number',
'default' => 0,
- 'min' => '0',
+ 'min' => 0,
'size' => 'small',
'pro' => true,
),
@@ -654,7 +653,7 @@
'desc' => __( 'Weight to give tag matches when calculating relevance.', 'contextual-related-posts' ),
'type' => 'number',
'default' => 0,
- 'min' => '0',
+ 'min' => 0,
'size' => 'small',
'pro' => true,
),
@@ -664,7 +663,7 @@
'desc' => __( 'Weight to give other taxonomy matches when calculating relevance.', 'contextual-related-posts' ),
'type' => 'number',
'default' => 0,
- 'min' => '0',
+ 'min' => 0,
'size' => 'small',
'pro' => true,
),
@@ -674,7 +673,7 @@
'desc' => __( 'Additional weight multiplier for primary terms. This is usually set using your SEO plugin and will default to the first category/term returned by WordPress. CRP supports Yoast, Rank Math SEO, The SEO Framework and SEOExpress plugins that allow you to set a primary category.', 'contextual-related-posts' ),
'type' => 'number',
'default' => 0,
- 'min' => '0',
+ 'min' => 0,
'size' => 'small',
'pro' => true,
),
@@ -692,8 +691,8 @@
/* translators: 1: Number. */
'desc' => sprintf( esc_html__( 'This sets the maximum words of the post content that will be matched. Set to 0 for no limit. Max value: %1$s.', 'contextual-related-posts' ), CRP_MAX_WORDS ),
'type' => 'number',
- 'default' => '0',
- 'min' => '0',
+ 'default' => 0,
+ 'min' => 0,
'max' => CRP_MAX_WORDS,
),
'post_filter_header' => array(
@@ -768,8 +767,8 @@
'name' => esc_html__( 'Number of common terms', 'contextual-related-posts' ),
'desc' => esc_html__( 'Enter the minimum number of common terms that have to be matched before a post is considered related.', 'contextual-related-posts' ),
'type' => 'number',
- 'default' => '1',
- 'min' => '1',
+ 'default' => 1,
+ 'min' => 1,
),
'related_meta_keys' => array(
'id' => 'related_meta_keys',
--- a/contextual-related-posts/includes/admin/settings/class-settings-api.php
+++ b/contextual-related-posts/includes/admin/settings/class-settings-api.php
@@ -18,7 +18,7 @@
/**
* Settings API wrapper class
*
- * @version 2.8.1
+ * @version 2.8.2
*/
class Settings_API {
@@ -27,7 +27,7 @@
*
* @var string
*/
- public const VERSION = '2.8.1';
+ public const VERSION = '2.8.2';
/**
* Settings Key.
@@ -483,7 +483,7 @@
wp_register_script(
'wz-' . $this->prefix . '-admin',
plugins_url( 'js/settings-admin-scripts' . $minimize . '.js', __FILE__ ),
- array( 'jquery' ),
+ array( 'jquery', 'wp-color-picker', 'jquery-ui-tabs' ),
self::VERSION,
true
);
@@ -495,23 +495,16 @@
true
);
wp_register_script(
- 'wz-' . $this->prefix . '-taxonomy-suggest',
- plugins_url( 'js/taxonomy-suggest' . $minimize . '.js', __FILE__ ),
- array( 'jquery' ),
- self::VERSION,
- true
- );
- wp_register_script(
'wz-' . $this->prefix . '-media-selector',
plugins_url( 'js/media-selector' . $minimize . '.js', __FILE__ ),
- array( 'jquery' ),
+ array( 'jquery', 'media-editor', 'media-views' ),
self::VERSION,
true
);
wp_register_style(
'wz-' . $this->prefix . '-admin',
plugins_url( 'css/admin-style' . $minimize . '.css', __FILE__ ),
- array(),
+ array( 'wp-color-picker' ),
self::VERSION
);
@@ -563,13 +556,7 @@
*/
public static function enqueue_scripts_styles( $prefix, $args = array() ) {
- wp_enqueue_style( 'wp-color-picker' );
-
wp_enqueue_media();
- wp_enqueue_script( 'wp-color-picker' );
- wp_enqueue_script( 'jquery' );
- wp_enqueue_script( 'jquery-ui-autocomplete' );
- wp_enqueue_script( 'jquery-ui-tabs' );
wp_enqueue_code_editor(
array(
@@ -583,7 +570,6 @@
wp_enqueue_script( "wz-{$prefix}-admin" );
wp_enqueue_script( "wz-{$prefix}-codemirror" );
- wp_enqueue_script( "wz-{$prefix}-taxonomy-suggest" );
wp_enqueue_script( "wz-{$prefix}-media-selector" );
// Enqueue Tom Select.
@@ -866,6 +852,7 @@
// Get the various settings we've registered.
$settings = get_option( $this->settings_key );
+ $settings = is_array( $settings ) ? $settings : array();
$settings_types = $this->get_registered_settings_types();
// Get the tab. This is also our settings' section.
@@ -896,18 +883,15 @@
continue;
}
- if ( array_key_exists( $key, $output ) ) {
+ if ( array_key_exists( $key, $input ) ) {
$sanitize_callback = $this->get_sanitize_callback( $key );
// If callback is set, call it.
if ( $sanitize_callback ) {
- // Pass the field configuration for repeater fields.
- if ( 'repeater' === $type && isset( $this->registered_settings[ $key ] ) ) {
- $output[ $key ] = call_user_func( $sanitize_callback, $output[ $key ], $this->registered_settings[ $key ] );
- } elseif ( 'sensitive' === $type ) {
- $output[ $key ] = call_user_func( $sanitize_callback, $output[ $key ], $key );
+ if ( 'sensitive' === $type ) {
+ $output[ $key ] = call_user_func( $sanitize_callback, $input[ $key ], $key );
} else {
- $output[ $key ] = call_user_func( $sanitize_callback, $output[ $key ] );
+ $output[ $key ] = call_user_func( $sanitize_callback, $input[ $key ] );
}
continue;
}
@@ -1170,7 +1154,7 @@
* @param string $prefix Optional prefix for fallback key.
* @return string The encryption key.
*/
- private static function get_encryption_key( $prefix = '' ) {
+ public static function get_encryption_key( $prefix = '' ) {
$fallback = $prefix ? str_replace( '-', '_', $prefix ) . '_encryption_fallback' : 'settings_api_encryption_fallback';
return defined( 'AUTH_SALT' ) ? AUTH_SALT : ( defined( 'SECURE_AUTH_SALT' ) ? SECURE_AUTH_SALT : hash( 'sha256', __NAMESPACE__ . $fallback ) );
}
--- a/contextual-related-posts/includes/admin/settings/class-settings-form.php
+++ b/contextual-related-posts/includes/admin/settings/class-settings-form.php
@@ -238,6 +238,8 @@
* @param array $args Array of arguments.
*/
public function callback_color( $args ) {
+ // Add color-field class for wpColorPicker initialization.
+ $args['field_class'] = isset( $args['field_class'] ) ? $args['field_class'] . ' color-field' : 'color-field';
$this->callback_text( $args );
}
@@ -842,8 +844,8 @@
ob_start();
?>
- <div class="<?php echo esc_attr( $class ); ?> wz-repeater-wrapper" id="<?php echo esc_attr( $args['id'] ); ?>-wrapper" <?php echo $attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
- <div class="<?php echo esc_attr( $args['id'] ); ?>-items">
+ <div class="<?php echo esc_attr( $class ); ?> wz-repeater-wrapper" id="<?php echo esc_attr( $args['id'] ); ?>-wrapper" data-index="<?php echo esc_attr( (string) count( $value ) ); ?>" data-live-update-field="<?php echo esc_attr( ! empty( $args['live_update_field'] ) ? $args['live_update_field'] : 'name' ); ?>" data-fallback-title="<?php echo esc_attr( ! empty( $args['new_item_text'] ) ? $args['new_item_text'] : $this->translation_strings['repeater_new_item'] ); ?>" <?php echo $attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
+ <div class="<?php echo esc_attr( $args['id'] ); ?>-items wz-repeater-items">
<?php
if ( ! empty( $value ) ) {
foreach ( array_values( $value ) as $index => $item ) {
@@ -860,83 +862,6 @@
<?php $this->render_repeater_item( $args, '{{INDEX}}' ); ?>
</script>
</div>
-
- <script>
- jQuery(document).ready(function($) {
- var wrapper = $('#<?php echo esc_js( $args['id'] ); ?>-wrapper');
- var itemsContainer = wrapper.find('.<?php echo esc_js( $args['id'] ); ?>-items');
- var index = <?php echo esc_js( (string) count( $value ) ); ?>;
-
- // Add Item
- wrapper.on('click', '.add-item', function() {
- var template = wrapper.find('.repeater-template').html();
- template = template.replace(/{{INDEX}}/g, index);
- itemsContainer.append(template);
- index++;
-
- // Ensure the toggle icon for the new item is set to the collapsed state (▲)
- itemsContainer.find('.repeater-item-header:last .toggle-icon').text('▲');
-
- // Ensure that .repeater-item-content is set to display:block
- itemsContainer.find('.repeater-item-content:last').css('display', 'block');
- });
-
- // Remove Item
- wrapper.on('click', '.remove-item', function() {
- $(this).closest('.wz-repeater-item').remove();
- reindexItems();
- });
-
- // Move Up
- wrapper.on('click', '.move-up', function() {
- var item = $(this).closest('.wz-repeater-item');
- var prev = item.prev();
- if (prev.length) {
- item.insertBefore(prev);
- reindexItems();
- }
- });
-
- // Move Down
- wrapper.on('click', '.move-down', function() {
- var item = $(this).closest('.wz-repeater-item');
- var next = item.next();
- if (next.length) {
- item.insertAfter(next);
- reindexItems();
- }
- });
-
- // Toggle Accordion
- wrapper.on('click', '.repeater-item-header', function() {
- var $this = $(this);
- var $toggleIcon = $this.find('.toggle-icon');
- var $content = $this.next('.repeater-item-content');
-
- // Check if content is currently visible or hidden, and toggle accordingly
- if ($content.is(':visible')) {
- $content.slideUp();
- $toggleIcon.text('▼'); // Expanded state
- } else {
- $content.slideDown();
- $toggleIcon.text('▲'); // Collapsed state
- }
- });
-
- // Reindex Items After Adding, Removing, or Moving
- function reindexItems() {
- itemsContainer.find('.wz-repeater-item').each(function(idx) {
- $(this).find(':input').each(function() {
- var name = $(this).attr('name');
- if (name) {
- name = name.replace(/[d+]/, '[' + idx + ']');
- $(this).attr('name', name);
- }
- });
- });
- }
- });
- </script>
<?php
$html = ob_get_clean();
$html .= $this->get_field_description( $args );
@@ -953,18 +878,33 @@
* @param array|null $item Item data if exists.
* @return void
*/
- private function render_repeater_item( $args, $index, $item = null ) {
+ public function render_repeater_item( $args, $index, $item = null ) {
if ( empty( $args['fields'] ) || ! is_array( $args['fields'] ) ) {
return;
}
+ $fallback_title = ! empty( $args['new_item_text'] ) ? $args['new_item_text'] : $this->translation_strings['repeater_new_item'];
+
+ // Generate or retrieve unique row ID.
+ $item_id = '';
+ if ( is_array( $item ) && isset( $item['row_id'] ) ) {
+ $item_id = $item['row_id'];
+ } elseif ( '{{INDEX}}' !== $index ) {
+ // For existing items without row_id, generate a persistent one.
+ $item_id = 'row_' . md5( $args['id'] . '_' . $index );
+ } else {
+ // For new items, use a placeholder that will be replaced.
+ $item_id = '{{ROW_ID}}';
+ }
+
?>
- <div class="wz-repeater-item">
- <div class="repeater-item-header">
+ <div class="wz-repeater-item" data-row-id="<?php echo esc_attr( $item_id ); ?>">
+ <input type="hidden" name="<?php echo esc_attr( $this->settings_key ); ?>[<?php echo esc_attr( $args['id'] ); ?>][<?php echo esc_attr( $index ); ?>][row_id]" value="<?php echo esc_attr( $item_id ); ?>" />
+ <div class="repeater-item-header">
<?php
$display_field = ! empty( $args['live_update_field'] ) ? $args['live_update_field'] : 'name';
?>
- <span class="repeater-title"><?php echo esc_html( ! empty( $item['fields'][ $display_field ] ) ? $item['fields'][ $display_field ] : $this->translation_strings['repeater_new_item'] ); ?></span>
+ <span class="repeater-title"><?php echo esc_html( ! empty( $item['fields'][ $display_field ] ) ? $item['fields'][ $display_field ] : $fallback_title ); ?></span>
<span class="toggle-icon">▼</span>
</div>
<div class="repeater-item-content" style="display: none;">
@@ -1027,22 +967,7 @@
</div>
</div>
- <script>
- jQuery(document).ready(function($) {
- var wrapper = $('#<?php echo esc_js( $args['id'] ); ?>-wrapper');
- var itemsContainer = wrapper.find('.<?php echo esc_js( $args['id'] ); ?>-items');
-
- // Live update repeater title when the specified field changes
- var liveUpdateField = '<?php echo esc_js( ! empty( $args['live_update_field'] ) ? $args['live_update_field'] : 'name' ); ?>';
- wrapper.on('input', '.wz-repeater-item input[name$="[fields][' + liveUpdateField + ']"]', function() {
- var $this = $(this);
- var newName = $this.val();
- var $repeaterTitle = $this.closest('.wz-repeater-item').find('.repeater-title');
- $repeaterTitle.text(newName || '<?php echo esc_js( $this->translation_strings['repeater_new_item'] ); ?>'); // Update title or set default if empty
- });
- });
- </script>
- <?php
+ <?php
}
--- a/contextual-related-posts/includes/admin/settings/class-settings-sanitize.php
+++ b/contextual-related-posts/includes/admin/settings/class-settings-sanitize.php
@@ -298,9 +298,22 @@
}
$sanitized_value = array();
+ $existing_rows = array();
// Get the subfields configuration.
$subfields = ! empty( $field['fields'] ) ? $field['fields'] : array();
+ if ( ! empty( $field['id'] ) ) {
+ $stored_value = $this->get_option( $field['id'], array() );
+ $existing_rows = is_array( $stored_value ) ? $stored_value : array();
+ }
+
+ // Create a lookup table for existing rows by row_id.
+ $existing_by_id = array();
+ foreach ( $existing_rows as $existing_row ) {
+ if ( isset( $existing_row['row_id'] ) ) {
+ $existing_by_id[ $existing_row['row_id'] ] = $existing_row;
+ }
+ }
foreach ( $value as $index => $row ) {
// Ensure we have a valid row structure.
@@ -312,6 +325,17 @@
'fields' => array(),
);
+ // Preserve row_id if it exists.
+ if ( isset( $row['row_id'] ) ) {
+ $sanitized_row['row_id'] = sanitize_text_field( $row['row_id'] );
+ }
+
+ // Get the corresponding existing row for sensitive field preservation.
+ $existing_row = null;
+ if ( isset( $row['row_id'] ) && isset( $existing_by_id[ $row['row_id'] ] ) ) {
+ $existing_row = $existing_by_id[ $row['row_id'] ];
+ }
+
foreach ( $row['fields'] as $field_key => $field_value ) {
$field_key = sanitize_key( $field_key );
@@ -331,10 +355,22 @@
// Get the field type from the subfield configuration.
$field_type = isset( $field_config['type'] ) ? $field_config['type'] : 'text';
+ // Preserve existing encrypted sensitive values when form submits masked/empty value.
+ if ( 'sensitive' === $field_type && ( empty( $field_value ) || ( is_string( $field_value ) && false !== strpos( $field_value, '**' ) ) ) ) {
+ if ( $existing_row && isset( $existing_row['fields'][ $field_key ] ) ) {
+ $sanitized_row['fields'][ $field_key ] = $existing_row['fields'][ $field_key ];
+ }
+ continue;
+ }
+
// Call the appropriate sanitization method.
$sanitize_method = 'sanitize_' . $field_type . '_field';
if ( method_exists( $this, $sanitize_method ) ) {
- $sanitized_row['fields'][ $field_key ] = $this->$sanitize_method( $field_value, $field_config );
+ if ( 'sensitive' === $field_type ) {
+ $sanitized_row['fields'][ $field_key ] = $this->$sanitize_method( $field_value, $field_key );
+ } else {
+ $sanitized_row['fields'][ $field_key ] = $this->$sanitize_method( $field_value, $field_config );
+ }
} else {
$sanitized_row['fields'][ $field_key ] = $this->sanitize_text_field( $field_value );
}
--- a/contextual-related-posts/includes/admin/settings/class-settings-wizard-api.php
+++ b/contextual-related-posts/includes/admin/settings/class-settings-wizard-api.php
@@ -239,7 +239,7 @@
( ( $this->args['hide_when_completed'] ?? true ) && $this->is_wizard_completed() );
if ( $hide_submenu ) {
- add_action( 'admin_head', array( $this, 'hide_completed_wizard_submenu' ) );
+ add_action( 'admin_enqueue_scripts', array( $this, 'hide_completed_wizard_submenu' ) );
}
}
@@ -250,14 +250,11 @@
*/
public function hide_completed_wizard_submenu() {
$slug = sanitize_key( $this->page_slug );
- ?>
- <style>
- #adminmenu a[href$="page=<?php echo esc_attr( $slug ); ?>"],
- #adminmenu a[href*="page=<?php echo esc_attr( $slug ); ?>&"] {
+ $css = '#adminmenu a[href$="page=' . $slug . '"],
+ #adminmenu a[href*="page=' . $slug . '&"] {
display: none;
- }
- </style>
- <?php
+ }';
+ wp_add_inline_style( 'wp-admin', $css );
}
/**
--- a/contextual-related-posts/includes/admin/settings/sidebar.php
+++ b/contextual-related-posts/includes/admin/settings/sidebar.php
@@ -5,6 +5,9 @@
* @package WebberZoneContextual_Related_Posts
*/
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
?>
<div class="postbox-container">
<div id="qlinksdiv" class="postbox meta-box-sortables">
--- a/contextual-related-posts/includes/class-crp-core-query.php
+++ b/contextual-related-posts/includes/class-crp-core-query.php
@@ -1141,7 +1141,7 @@
}
}
- if ( ! empty( $this->manual_related ) && ( $this->no_of_manual_related >= $this->query_args['limit'] ) ) {
+ if ( ! empty( $this->manual_related ) && ( $this->no_of_manual_related >= (int) $this->query_args['limit'] ) ) {
$post_ids = array_merge( $post_ids, $this->manual_related );
}
@@ -1242,7 +1242,7 @@
// Manual Posts (manual_related - set via the Post Meta) or Include Posts (can be set as a parameter).
$post_ids = array();
- if ( ! empty( $this->manual_related ) && ( $this->no_of_manual_related < $this->query_args['limit'] ) ) {
+ if ( ! empty( $this->manual_related ) && ( $this->no_of_manual_related < (int) $this->query_args['limit'] ) ) {
$post_ids = array_merge( $post_ids, $this->manual_related );
}
if ( ! empty( $post_ids ) ) {
@@ -1271,7 +1271,7 @@
$fill_random_posts = apply_filters( 'crp_fill_random_posts', false, $posts, $query );
if ( $fill_random_posts ) {
- $no_of_random_posts = $this->query_args['limit'] - count( $posts );
+ $no_of_random_posts = (int) $this->query_args['limit'] - count( $posts );
if ( $no_of_random_posts > 0 ) {
$random_posts = get_posts(
array(
@@ -1296,7 +1296,7 @@
);
}
- $limit = $this->query_args['limit'];
+ $limit = (int) $this->query_args['limit'];
$posts = array_slice( $posts, 0, $limit );
// Support caching to speed up retrieval - set cache AFTER final limiting.
--- a/contextual-related-posts/includes/class-main.php
+++ b/contextual-related-posts/includes/class-main.php
@@ -10,7 +10,6 @@
if ( ! defined( 'WPINC' ) ) {
exit;
}
-
/**
* Main plugin class.
*
@@ -99,7 +98,6 @@
self::$instance = new self();
self::$instance->init();
}
-
return self::$instance;
}
@@ -123,10 +121,8 @@
$this->styles = new FrontendStyles_Handler();
$this->shortcodes = new FrontendShortcodes();
$this->blocks = new FrontendBlocksBlocks();
-
// Load all hooks.
new Hook_Loader();
-
// Initialize admin on init action to ensure translations are loaded.
add_action( 'init', array( $this, 'init_admin' ) );
}
--- a/contextual-related-posts/includes/frontend/class-display.php
+++ b/contextual-related-posts/includes/frontend/class-display.php
@@ -189,7 +189,7 @@
*/
$post_classes = apply_filters( 'crp_post_class', $post_classes, $args, $post );
- $output = '<div class="' . $post_classes . '">';
+ $output = '<div class="' . esc_attr( $post_classes ) . '">';
if ( $results ) {
$loop_counter = 0;
@@ -1013,7 +1013,7 @@
$add_to = crp_get_option( 'add_to', array( 'single', 'page' ) );
$add_to = wp_parse_list( $add_to );
- $limit_feed = crp_get_option( 'limit_feed' );
+ $limit_feed = (int) crp_get_option( 'limit_feed' );
$show_excerpt_feed = crp_get_option( 'show_excerpt_feed' );
$post_thumb_op_feed = crp_get_option( 'post_thumb_op_feed' );
--- a/contextual-related-posts/includes/frontend/class-styles-handler.php
+++ b/contextual-related-posts/includes/frontend/class-styles-handler.php
@@ -102,8 +102,8 @@
public static function get_style( $style = '' ) {
$style_array = array();
- $thumb_width = crp_get_option( 'thumb_width', 150 );
- $thumb_height = crp_get_option( 'thumb_height', 150 );
+ $thumb_width = (int) crp_get_option( 'thumb_width', 150 );
+ $thumb_height = (int) crp_get_option( 'thumb_height', 150 );
$aspect_ratio = $thumb_width / $thumb_height;
$crp_style = ! empty( $style ) ? $style : crp_get_option( 'crp_styles' );
--- a/contextual-related-posts/includes/frontend/widgets/class-related-posts-widget.php
+++ b/contextual-related-posts/includes/frontend/widgets/class-related-posts-widget.php
@@ -202,7 +202,7 @@
$instance['show_date'] = isset( $new_instance['show_date'] ) ? (bool) $new_instance['show_date'] : false;
$instance['offset'] = ( ! empty( $new_instance['offset'] ) ) ? intval( $new_instance['offset'] ) : '';
$instance['ordering'] = isset( $new_instance['ordering'] ) ? $new_instance['ordering'] : '';
- $instance['random_order'] = isset( $new_instance['random_order'] ) ? (bool) $new_instance['show_date'] : false;
+ $instance['random_order'] = isset( $new_instance['random_order'] ) ? (bool) $new_instance['random_order'] : false;
// Process post types to be selected.
$wp_post_types = get_post_types(
@@ -290,9 +290,9 @@
*/
$title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
- $limit = isset( $instance['limit'] ) ? $instance['limit'] : crp_get_option( 'limit' );
+ $limit = isset( $instance['limit'] ) ? (int) $instance['limit'] : (int) crp_get_option( 'limit' );
if ( empty( $limit ) ) {
- $limit = crp_get_option( 'limit' );
+ $limit = (int) crp_get_option( 'limit' );
}
$offset = isset( $instance['offset'] ) ? $instance['offset'] : 0;
--- a/contextual-related-posts/includes/util/class-helpers.php
+++ b/contextual-related-posts/includes/util/class-helpers.php
@@ -342,7 +342,18 @@
public static function sanitize_args( $args ): array {
foreach ( $args as $key => $value ) {
if ( is_string( $value ) ) {
- $args[ $key ] = wp_kses_post( $value );
+ switch ( $key ) {
+ case 'class':
+ case 'className':
+ case 'extra_class':
+ $classes = explode( ' ', $value );
+ $sanitized_classes = array_map( 'sanitize_html_class', $classes );
+ $args[ $key ] = implode( ' ', $sanitized_classes );
+ break;
+ default:
+ $args[ $key ] = wp_kses_post( $value );
+ break;
+ }
}
}
return $args;
--- a/contextual-related-posts/load-freemius.php
+++ b/contextual-related-posts/load-freemius.php
@@ -25,23 +25,24 @@
require_once __DIR__ . '/vendor/freemius/start.php';
$crp_freemius = fs_dynamic_init(
array(
- 'id' => '15040',
- 'slug' => 'contextual-related-posts',
- 'premium_slug' => 'contextual-related-posts-pro',
- 'type' => 'plugin',
- 'public_key' => 'pk_4aec305b9c97637276da2e55b723f',
- 'is_premium' => false,
- 'premium_suffix' => 'Pro',
- 'has_addons' => false,
- 'has_paid_plans' => true,
- 'menu' => array(
+ 'id' => '15040',
+ 'slug' => 'contextual-related-posts',
+ 'premium_slug' => 'contextual-related-posts-pro',
+ 'type' => 'plugin',
+ 'public_key' => 'pk_4aec305b9c97637276da2e55b723f',
+ 'is_premium' => false,
+ 'premium_suffix' => 'Pro',
+ 'has_addons' => false,
+ 'has_paid_plans' => true,
+ 'menu' => array(
'slug' => 'crp_options_page',
'first-path' => ( is_multisite() && is_network_admin() ? '' : 'admin.php?page=crp_wizard' ),
'contact' => false,
'support' => false,
'network' => true,
),
- 'is_live' => true,
+ 'is_live' => true,
+ 'is_org_compliant' => true,
)
);
}