Skip to content
Open

Release #4499

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
4258f03
Sync branch [skip ci]
pirate-bot Mar 9, 2026
e4b7030
Sync branch [skip ci]
pirate-bot Mar 9, 2026
242dbd9
chore(deps): bump codeinwp/themeisle-sdk from 3.3.50 to 3.3.51
dependabot[bot] Mar 30, 2026
9b7945e
fix: infinite scroll on archive page
girishpanchal30 Mar 31, 2026
581b173
fix: enhance query sanitization for infinite scroll
girishpanchal30 Mar 31, 2026
c6098b4
Merge pull request #4485 from Codeinwp/dependabot/composer/developmen…
Soare-Robert-Daniel Mar 31, 2026
7420a89
fix: button shadow in the customizer
girishpanchal30 Apr 10, 2026
18722f2
fix: ensure compatibility with PPOM popup button
girishpanchal30 Apr 10, 2026
729c7d6
refactor: update the black friday labels (#4483)
Soare-Robert-Daniel Apr 23, 2026
43e0510
Initial plan
Copilot May 4, 2026
809af14
Update starter sites count from 30+ to 100+ in admin notice
Copilot May 4, 2026
aaca831
fix: handle sticky header toggle correctly with multiple headers
girishpanchal30 May 14, 2026
c21872d
fix: include tablet state in default responsive toggle
girishpanchal30 May 14, 2026
dbcfe80
Merge pull request #4487 from Codeinwp/bugfix/3874
vytisbulkevicius May 15, 2026
1a4f244
Merge pull request #4495 from Codeinwp/copilot/update-notice-for-star…
vytisbulkevicius May 15, 2026
670170e
Merge pull request #4488 from Codeinwp/bugfix/4019
vytisbulkevicius May 15, 2026
b0311d2
Merge pull request #4486 from Codeinwp/bugfix/pro/3151
vytisbulkevicius May 15, 2026
9c6eb87
Merge pull request #4498 from Codeinwp/bugfix/pro/2967
vytisbulkevicius May 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ const ResponsiveToggleComponent = ({ control }) => {
if (e.detail.id !== control.id) {
return false;
}
setValue(e.detail.value);
const val = e.detail.value;
setValue(val);
control.setting.set(
val && typeof val === 'object'
? val
: { desktop: false, tablet: false, mobile: false }
);
});
}, []);

Expand Down
8 changes: 8 additions & 0 deletions assets/scss/components/compat/woocommerce/_product.scss
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@
margin: 0;
}
}

> .ppom-fieldspopup-btn-wrap {
width: 100%;

button {
width: 100%;
}
}
}

.group_table {
Expand Down
1 change: 1 addition & 0 deletions assets/scss/components/main/_extends.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
line-height: var(--btnlineheight, 1.6);
letter-spacing: var(--btnletterspacing, var(--bodyletterspacing));
text-transform: var(--btntexttransform, none);
box-shadow: var(--primarybtnshadow, none);
}

%nv-button-primary-hover {
Expand Down
14 changes: 7 additions & 7 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

78 changes: 51 additions & 27 deletions inc/admin/dashboard/main.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public function init() {
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
add_action( 'init', array( $this, 'register_settings' ) );
add_action( 'init', array( $this, 'register_about_page' ), 1 );

add_action( 'admin_notices', array( $this, 'render_custom_layout_header' ) );
}

Expand Down Expand Up @@ -999,7 +999,7 @@ private function get_available_modules() {
*/
public function render_custom_layout_header() {
$screen = get_current_screen();

if ( ! $screen || ! ( $screen->id === 'edit-neve_custom_layouts' || $screen->id === 'neve_page_neve-custom-layout-upsell' ) ) {
return;
}
Expand Down Expand Up @@ -1255,18 +1255,18 @@ public function render_neve_header() {

/**
* Register the survey.
*
*
* @param array $dash_data The dashboard data.
*
*
* @return void
*/
public function register_survey( $dash_data ) {
add_filter(
'themeisle-sdk/survey/neve',
function( $data, $page_slug ) use ( $dash_data ) {

$install_days_number = intval( ( time() - get_option( 'neve_install', time() ) ) / DAY_IN_SECONDS );

$data = array(
'environmentId' => 'clr0ply35522h8up0bay2de4y',
'attributes' => array(
Expand All @@ -1288,43 +1288,67 @@ function( $data, $page_slug ) use ( $dash_data ) {
return $data;
},
10,
2
2
);
}

/**
* Get the Black Friday config settings.
*
*
* @param array $default Optional. The default values.
*
*
* @return array The data.
*/
public static function get_black_friday_data( $default = array() ) {
$config = $default;

// translators: %1$s - HTML tag, %2$s - discount, %3$s - HTML tag, %4$s - product name.
$message_template = __( 'Our biggest sale of the year: %1$sup to %2$s OFF%3$s on %4$s. Don\'t miss this limited-time offer.', 'neve' );
$product_label = __( 'Neve', 'neve' );
$discount = '70%';
$message = __( 'Custom layouts, WooCommerce tools, starter sites. What you\'ve been doing manually, Neve Pro handles for you. Exclusively for existing Neve users.', 'neve' );
$cta_label = __( 'Get Neve Pro', 'neve' );

$plan = apply_filters( 'product_neve_license_plan', 0 );
$plan = apply_filters( 'product_neve_license_plan', false );
$is_pro = 0 < $plan;

if ( $is_pro ) {
// translators: %1$s - HTML tag, %2$s - discount, %3$s - HTML tag, %4$s - product name.
$message_template = __( 'Get %1$sup to %2$s off%3$s when you upgrade your %4$s plan or renew early.', 'neve' );
$product_label = __( 'Neve Pro', 'neve' );
$discount = '30%';
$license_status = apply_filters( 'product_neve_license_status', false );
$has_valid_license = 'valid' === $license_status;
$has_expired_license = 'expired' === $license_status || 'active-expired' === $license_status;

$neve_pro_product_slug = defined( 'NEVE_PRO_BASEFILE' ) ? basename( dirname( NEVE_PRO_BASEFILE ) ) : '';

if ( $has_valid_license ) {
// translators: %s is the discount percentage.
$config['plugin_meta_message'] = sprintf( __( 'Black Friday Sale - up to %s off', 'neve' ), '30%' );
// translators: %1$s - the discount percentage for the upgrade, %2$s - the discount percentage for the renewal.
$message = sprintf( __( 'Upgrade your Neve Pro plan: %1$s off this week. Already on the plan you need? Renew early and save up to %2$s.', 'neve' ), '30%', '20%' );
$cta_label = __( 'See your options', 'neve' );
} elseif ( $has_expired_license ) {
// translators: %s is the discount percentage.
$config['plugin_meta_message'] = sprintf( __( 'Black Friday Sale - %s off', 'neve' ), '50%' );
// translators: %s is the discount percentage.
$config['upgrade_menu_text'] = sprintf( __( 'BF Sale - %s off', 'neve' ), '50%' );
$message = __( 'Your Neve Pro features are still here, just locked. Renew at a reduced rate this week and pick up right where you left off.', 'neve' );
$cta_label = __( 'Reactivate now', 'neve' );
} else {
// translators: %s is the discount percentage.
$config['plugin_meta_message'] = sprintf( __( 'Black Friday Sale - %s off', 'neve' ), '60%' );
// translators: %s is the discount percentage.
$config['upgrade_menu_text'] = sprintf( __( 'BF Sale - %s off', 'neve' ), '60%' );
// translators: %s - the discount percentage for the upgrade.
$config['title'] = sprintf( __( 'Neve Pro: %s off this week', 'neve' ), '60%' );
}

if ( $has_valid_license || $has_expired_license ) {
$config['plugin_meta_targets'] = array( $neve_pro_product_slug );
}

$product_label = sprintf( '<strong>%s</strong>', $product_label );
$url_params = array(

$url_params = array(
'utm_term' => $is_pro ? 'plan-' . $plan : 'free',
'lkey' => apply_filters( 'product_neve_license_key', false ),
'expired' => $has_expired_license ? '1' : false,
);

$config['message'] = sprintf( $message_template, '<strong>', $discount, '</strong>', $product_label );
$config['sale_url'] = add_query_arg(

$config['message'] = $message;
$config['cta_label'] = $cta_label;
$config['sale_url'] = add_query_arg(
$url_params,
tsdk_translate_link( tsdk_utmify( 'https://themeisle.link/neve-bf', 'bfcm', 'neve' ) )
);
Expand All @@ -1334,9 +1358,9 @@ public static function get_black_friday_data( $default = array() ) {

/**
* Add the Black Friday data.
*
*
* @param array $configs An array of configurations.
*
*
* @return array The configurations.
*/
public function add_black_friday_data( $configs ) {
Expand Down
2 changes: 1 addition & 1 deletion inc/core/admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,7 @@ public function welcome_notice_content() {

$notice_picture .= '<div class="overlay">';
$notice_picture .= '<span>';
$notice_picture .= '<strong>30+</strong> ' . __( 'Starter Sites', 'neve' );
$notice_picture .= '<strong>100+</strong> ' . __( 'Starter Sites', 'neve' );
$notice_picture .= '</span>';
$notice_picture .= '<div class="builder-logos">';
$notice_picture .= '<div class="builder-logo">' . $elementor_logo . '</div>';
Expand Down
66 changes: 63 additions & 3 deletions inc/views/pluggable/pagination.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,18 @@ public function filter_localization( $data ) {
global $wp_query;
$max_pages = $wp_query->max_num_pages;

$query = $wp_query->query;

if ( is_tax() ) {
$post_type = get_post_type();
if ( $post_type ) {
$query['post_type'] = $post_type;
}
}
$data['infScroll'] = 'enabled';
$data['maxPages'] = $max_pages;
$data['endpoint'] = rest_url( 'nv/v1/posts/page/' );
$data['query'] = wp_json_encode( $wp_query->query );
$data['query'] = wp_json_encode( $query );
$data['lang'] = get_locale();

// WPML language parameter
Expand Down Expand Up @@ -300,7 +308,7 @@ public function render_post_navigation() {
* Sanitize query arguments for infinite scroll to prevent query manipulation.
*
* This method implements a strict allowlist approach to prevent:
* - Expensive database queries (DoS risk via meta_query, tax_query, etc.)
* - Expensive database queries (DoS risk via meta_query, fields etc.)
* - Exposure of unintended content types
* - Manipulation of query parameters by anonymous users
*
Expand Down Expand Up @@ -371,6 +379,59 @@ private function sanitize_infinite_scroll_query_args( $args ) {
$sanitized['post_type'] = $post_type;
} else {
$sanitized['post_type'] = 'post';
$post_type = 'post';
}

// Build an allowlist of taxonomies that are both publicly queryable and registered to the resolved post type.
$allowed_taxonomies = array_filter(
get_object_taxonomies( $post_type, 'objects' ),
function ( $tax_obj ) {
return ! empty( $tax_obj->publicly_queryable );
}
);
$allowed_taxonomy_names = array_keys( $allowed_taxonomies );

$tax_queries = array();
foreach ( $args as $key => $value ) {
if ( ! in_array( $key, $allowed_taxonomy_names, true ) ) {
continue;
}

$terms = array();
if ( is_array( $value ) ) {
foreach ( $value as $maybe_term ) {
if ( is_scalar( $maybe_term ) ) {
$sanitized_term = sanitize_title( (string) $maybe_term );
if ( $sanitized_term !== '' ) {
$terms[] = $sanitized_term;
}
}
}
} elseif ( is_scalar( $value ) ) {
$sanitized_term = sanitize_title( (string) $value );
if ( $sanitized_term !== '' ) {
$terms[] = $sanitized_term;
}
}

if ( empty( $terms ) ) {
continue;
}

$tax_queries[] = array(
'taxonomy' => $key,
'field' => 'slug',
'terms' => $terms,
);
}

if ( ! empty( $tax_queries ) ) {
$sanitized['tax_query'] = array_merge( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
array(
'relation' => 'AND',
),
$tax_queries
);
}

// Explicitly unset dangerous query args that could be smuggled in.
Expand All @@ -381,7 +442,6 @@ private function sanitize_infinite_scroll_query_args( $args ) {
'meta_value',
'meta_value_num',
'meta_compare',
'tax_query',
'fields',
'post__in',
'post__not_in',
Expand Down
Loading