Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<exclude name="WordPress.Files.FileName.NotHyphenatedLowercase" />
<exclude name="WordPress.WP.I18n.MissingTranslatorsComment" />
<exclude name="Generic.Arrays.DisallowShortArraySyntax" />
<exclude name="Universal.Arrays.DisallowShortArraySyntax" />
</rule>

<rule ref="Generic.Arrays.DisallowLongArraySyntax.Found">
Expand All @@ -29,7 +30,9 @@

<rule ref="WordPress.WP.I18n">
<properties>
<property name="text_domain" type="array" value="perform"/>
<property name="text_domain" type="array">
<element value="perform"/>
</property>
</properties>
</rule>
</ruleset>
18 changes: 0 additions & 18 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,6 @@ parameters:
count: 1
path: src/Admin/Settings/Api.php

-
message: '#^Parameter \#1 \$var of static method Perform\\Includes\\Helpers\:\:clean\(\) expects array\|string, mixed given\.$#'
identifier: argument.type
count: 1
path: src/Admin/Settings/Menu.php

-
message: '#^Strict comparison using \!\=\= between ''0'' and mixed~\(0\|0\.0\|''''\|''0''\|array\{\}\|false\|null\) will always evaluate to true\.$#'
identifier: notIdentical.alwaysTrue
Expand Down Expand Up @@ -234,12 +228,6 @@ parameters:
count: 2
path: src/Modules/Assets/AssetsManager.php

-
message: '#^Parameter \#1 \$text of function esc_attr expects string, int\|false given\.$#'
identifier: argument.type
count: 1
path: src/Modules/Assets/AssetsManager.php

-
message: '#^Property Perform\\Modules\\Assets\\AssetsManager\:\:\$loaded_assets type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
Expand All @@ -252,12 +240,6 @@ parameters:
count: 1
path: src/Modules/Assets/AssetsManager.php

-
message: '#^Variable \$post_type might not be defined\.$#'
identifier: variable.undefined
count: 1
path: src/Modules/Assets/AssetsManager.php

-
message: '#^Method Perform\\Modules\\Basic\\DisableEmbeds\:\:disable_embeds\(\) has no return type specified\.$#'
identifier: missingType.return
Expand Down
110 changes: 74 additions & 36 deletions src/Admin/Settings/Menu.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,12 @@ public function save_settings() {
// If the JS sent a JSON payload in `data`, decode it. Otherwise fall back to regular POST fields.
$posted_data = [];
if ( isset( $_POST['data'] ) ) {
$raw = wp_unslash( $_POST['data'] );
$raw = wp_unslash( $_POST['data'] );
$decoded = json_decode( $raw, true );
if ( is_array( $decoded ) ) {
// Clean decoded values recursively
$posted_data = Helpers::clean( $decoded );
$posted_data = $decoded;
} else {
// Fallback: clean the entire $_POST array
// Fallback: clean the entire $_POST array.
$posted_data = Helpers::clean( $_POST );
}
} else {
Expand All @@ -113,30 +112,35 @@ public function save_settings() {

// Per-field sanitization based on field definitions provided by Helpers::get_settings_fields().
$sanitized_post = [];
foreach ( $posted_data as $key => $val ) {
// Skip known control keys early
foreach ( $posted_data as $key => $val ) {
// Skip known control keys early.
if ( in_array( $key, [ 'perform_settings_barrier', '_wp_http_referer', 'action', 'nonce', 'data' ], true ) ) {
continue;
}

// If value is an array, recursively clean it (for list-type fields)
if ( is_array( $val ) ) {
$sanitized_post[ $key ] = Helpers::clean( $val );
continue;
}

$field_def = Helpers::find_field_by_id( $key );
$raw_val = wp_unslash( $val );
$field_def = Helpers::find_field_by_id( $key );

// Keep existing secrets when UI sends masked placeholder.
if ( in_array( $key, ClientPayload::get_sensitive_keys(), true ) && ClientPayload::is_masked_secret( $raw_val ) ) {
if ( is_array( $val ) ) {
if ( $field_def && isset( $field_def['type'] ) && 'textarea' === $field_def['type'] ) {
$raw_val = implode( "\n", array_map( 'strval', $val ) );
} else {
// If value is an array, recursively clean it for list-type fields.
$sanitized_post[ $key ] = Helpers::clean( $val );
continue;
}
} else {
$raw_val = is_scalar( $val ) ? wp_unslash( $val ) : '';
}

if ( $field_def && isset( $field_def['type'] ) ) {
// Keep existing secrets when UI sends masked placeholder.
if ( in_array( $key, ClientPayload::get_sensitive_keys(), true ) && ClientPayload::is_masked_secret( $raw_val ) ) {
continue;
}

if ( $field_def && isset( $field_def['type'] ) ) {
switch ( $field_def['type'] ) {
case 'toggle':
// Normalize truthy values to 1, else 0
// Normalize truthy values to 1, else 0.
$sanitized_post[ $key ] = ! empty( $raw_val ) && '0' !== $raw_val ? 1 : 0;
break;
case 'textarea':
Expand All @@ -146,15 +150,14 @@ public function save_settings() {
$sanitized_post[ $key ] = esc_url_raw( $raw_val );
break;
case 'select':
// Ensure value is one of allowed options when provided
$opts = $field_def['options'] ?? [];
$is_ok = false;
// Ensure value is one of allowed options when provided.
$opts = $field_def['options'] ?? [];
$is_ok = false;
if ( is_array( $opts ) && ! empty( $opts ) ) {
// If associative array (value=>label) check keys, otherwise check values
// If associative array (value=>label) check keys, otherwise check values.
$keys = array_keys( $opts );
$vals = array_values( $opts );
if ( array_diff_key( $opts, array_values( $opts ) ) ) {
// associative
$is_ok = in_array( $raw_val, $keys, true );
} else {
$is_ok = in_array( $raw_val, $vals, true );
Expand All @@ -169,30 +172,65 @@ public function save_settings() {
$sanitized_post[ $key ] = sanitize_text_field( $raw_val );
}
} else {
// No field definition foundfall back to a safe cleaning
// No field definition found; fall back to a safe cleaning.
$sanitized_post[ $key ] = is_scalar( $raw_val ) ? sanitize_text_field( $raw_val ) : Helpers::clean( $raw_val );
}
}

// Merge sanitized values with existing settings to preserve missing keys
// Merge sanitized values with existing settings to preserve missing keys.
$new_settings = wp_parse_args( $sanitized_post, is_array( $settings ) ? $settings : [] );

// Handle newline-separated lists
$new_settings['dns_prefetch'] = ! empty( $new_settings['dns_prefetch'] ) ? explode( "\n", $new_settings['dns_prefetch'] ) : '';
$new_settings['preconnect'] = ! empty( $new_settings['preconnect'] ) ? explode( "\n", $new_settings['preconnect'] ) : '';
// Handle newline-separated lists.
$new_settings['dns_prefetch'] = $this->normalize_multiline_setting( $new_settings['dns_prefetch'] ?? '' );
$new_settings['preconnect'] = $this->normalize_multiline_setting( $new_settings['preconnect'] ?? '' );

$is_saved = update_option( 'perform_settings', $new_settings, false );

if ( $is_saved ) {
wp_send_json_success( [
'type' => 'success',
'message' => esc_html__( 'Settings saved successfully.', 'perform' ),
] );
wp_send_json_success(
[
'type' => 'success',
'message' => esc_html__( 'Settings saved successfully.', 'perform' ),
]
);
} else {
wp_send_json_error(
[
'type' => 'error',
'message' => esc_html__( 'Unable to save the settings. Please try again.', 'perform' ),
]
);
}
}

/**
* Normalize a textarea setting to the stored newline-list shape.
*
* @param mixed $value Textarea value.
*
* @return array<int, string>|string
*/
private function normalize_multiline_setting( $value ) {
if ( is_array( $value ) ) {
$lines = $value;
} elseif ( is_scalar( $value ) && '' !== (string) $value ) {
$lines = preg_split( '/\r\n|\r|\n/', (string) $value );
} else {
wp_send_json_error( [
'type' => 'error',
'message' => esc_html__( 'Unable to save the settings. Please try again.', 'perform' ),
] );
return '';
}

$lines = array_filter(
array_map(
static function ( $line ) {
return sanitize_text_field( wp_unslash( $line ) );
},
$lines
),
static function ( $line ) {
return '' !== $line;
}
);

return array_values( $lines );
}
}
4 changes: 2 additions & 2 deletions src/Includes/Actions.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function __construct() {
public function enqueue_styles() {

// Bailout, if can't display assets manager.
if ( ! Helpers::can_display_assets_manager() ) {
if ( ! current_user_can( 'manage_options' ) || ! Helpers::can_display_assets_manager() ) {
return;
}

Expand All @@ -57,7 +57,7 @@ public function enqueue_styles() {
public function enqueue_scripts() {

// Bailout, if can't display assets manager.
if ( ! Helpers::can_display_assets_manager() ) {
if ( ! current_user_can( 'manage_options' ) || ! Helpers::can_display_assets_manager() ) {
return;
}

Expand Down
Loading
Loading