Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

PCH: Update Settings API structure #2351

Merged
merged 17 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from 11 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
2 changes: 1 addition & 1 deletion bin/install-wp-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ install_wp() {
while true; do
read -p "The $WP_CORE_DIR directory already exists. Overwrite it? (y/n) " yn
vaurdan marked this conversation as resolved.
Show resolved Hide resolved
case $yn in
y|Y ) rm -rf $WP_CORE_DIR; break;;
y|Y ) rm -rf $WP_CORE_DIR; rm -rf $WP_TESTS_DIR; break;;
vaurdan marked this conversation as resolved.
Show resolved Hide resolved
n|N ) exit;;
* ) echo "Invalid response. Please answer with y or n.";
esac
Expand Down
2 changes: 1 addition & 1 deletion build/content-helper/editor-sidebar.asset.php
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-block-editor', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-edit-post', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-plugins', 'wp-primitives', 'wp-url'), 'version' => '6d954f9308df4ceed20f');
<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-block-editor', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-edit-post', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-plugins', 'wp-primitives', 'wp-url'), 'version' => '88deb0e5bb1401306bdc');
28 changes: 14 additions & 14 deletions build/content-helper/editor-sidebar.js

Large diffs are not rendered by default.

186 changes: 155 additions & 31 deletions src/Endpoints/user-meta/class-base-endpoint-user-meta.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,26 +199,44 @@ protected function set_value( array $meta_value ): bool {
* @since 3.14.0 Added support for nested arrays.
*
* @param array<string, mixed> $meta_value The meta value to sanitize.
* @param string $parent_key The parent key for the current level of the meta.
*
* @return array<string, mixed> The sanitized meta as an array of subvalues.
*/
protected function sanitize_value( array $meta_value ): array {
protected function sanitize_value( array $meta_value, string $parent_key = '' ): array {
$sanitized_value = array();

foreach ( $meta_value as $key => $value ) {
$key = sanitize_text_field( $key );

// Skip if the key isn't valid.
if ( ! array_key_exists( $key, $this->valid_subvalues ) ) {
continue;
// Determine the current level's specifications based on the parent key.
/**
* Current level's specifications.
*
* @var array<string, Subvalue_Spec> $current_specs
*/
$current_specs = ( '' === $parent_key ) ? $this->get_subvalues_specs() : $this->get_nested_specs( $parent_key );

foreach ( $current_specs as $key => $spec ) {
$composite_key = '' === $parent_key ? $key : $parent_key . '.' . $key;

// Check if the key exists in the input meta value array.
if ( array_key_exists( $key, $meta_value ) ) {
$value = $meta_value[ $key ];
} else {
// Key is missing in the input, use the default value from the specifications.
$value = $this->get_default( explode( '.', $composite_key ) );
}

// Use the enhanced sanitize_subvalue method.
$sanitized_value[ $key ] = $this->sanitize_subvalue( $key, $value );
}

// If not all subvalues are set, return the default meta value.
if ( count( array_diff_key( $this->valid_subvalues, $sanitized_value ) ) !== 0 ) {
return $this->default_value;
/**
* Spec for the current key.
*
* @var array{default: mixed, values?: array<mixed>} $spec
*/
if ( is_array( $value ) && isset( $spec['values'] ) ) {
// Recursively handle nested arrays if 'values' spec exists for this key.
$sanitized_value[ $key ] = $this->sanitize_value( $value, $composite_key );
} else {
// Directly sanitize non-array values or non-nested specs.
$sanitized_value[ $key ] = $this->sanitize_subvalue( $composite_key, $value );
}
}

return $sanitized_value;
Expand All @@ -227,41 +245,147 @@ protected function sanitize_value( array $meta_value ): array {
/**
* Sanitizes the passed subvalue.
*
* @since 3.13.0
* @since 3.14.0 Added support for nested arrays.
* @since 3.13.0
*
* @param string $key The subvalue's key.
* @param string $composite_key The subvalue's key.
* @param mixed $value The value to sanitize.
*
* @return mixed The sanitized subvalue.
*/
protected function sanitize_subvalue( string $key, $value ) {
// Handle nested arrays recursively.
protected function sanitize_subvalue( string $composite_key, $value ) {
$keys = explode( '.', $composite_key );
$valid_values = $this->get_valid_values( $keys );

if ( is_array( $value ) ) {
$sanitized_array = array();
foreach ( $value as $subkey => $subvalue ) {
// Sanitize keys of nested arrays.
$sanitized_subkey = sanitize_text_field( $subkey );
// Recursively sanitize each value in the nested array.
$sanitized_array[ $sanitized_subkey ] = $this->sanitize_subvalue( $sanitized_subkey, $subvalue );
// Check if $value elements are inside $valid_values
// If not, the value should be the default value.
$valid_value = array();
foreach ( $value as $key => $val ) {
if ( in_array( $val, $valid_values, true ) ) {
$valid_value[ $key ] = $val;
}
}
return $sanitized_array;
return $valid_value;
}

// Sanitize simple values.
if ( is_string( $value ) ) {
$value = sanitize_text_field( $value );
}

// Allow any value when no valid subvalues are given.
if ( ! isset( $this->valid_subvalues[ $key ] ) || count( $this->valid_subvalues[ $key ] ) === 0 ) {
if ( count( $valid_values ) === 0 ) {
return $value;
}

// Use default value if the actual value is not valid.
if ( ! in_array( $value, $this->valid_subvalues[ $key ], true ) ) {
$value = $this->default_value[ $key ];
if ( ! in_array( $value, $valid_values, true ) ) {
return $this->get_default( $keys );
}

return $value;
}

/**
* Checks if a given composite key is valid.
*
* @since 3.14.3
*
* @param string|mixed $composite_key The composite key representing the nested path.
* @return bool Whether the key is valid.
*/
protected function is_valid_key( $composite_key ): bool {
if ( ! is_string( $composite_key ) ) {
return false; // Key path is not a string.
}

$keys = explode( '.', $composite_key );
$current = $this->valid_subvalues;

foreach ( $keys as $key ) {
if ( ! is_array( $current ) || ! isset( $current[ $key ] ) ) {
return false; // Key path is invalid.
}

if ( isset( $current[ $key ]['values'] ) ) {
$current = $current[ $key ]['values'];
} else {
$current = $current[ $key ];
}
}

return true;
}

/**
* Gets the valid values for a given setting path.
*
* @since 3.14.3
*
* @param array<string> $keys The path to the setting.
* @return array<mixed> The valid values for the setting path.
*/
protected function get_valid_values( array $keys ): array {
$current = $this->valid_subvalues;

foreach ( $keys as $key ) {
if ( ! is_array( $current ) || ! isset( $current[ $key ] ) ) {
return array(); // No valid values for invalid key path.
}
if ( isset( $current[ $key ]['values'] ) ) {
$current = $current[ $key ]['values'];
} else {
$current = $current[ $key ];
}
}

return is_array( $current ) ? $current : array();
}

/**
* Gets the default value for a given setting path.
*
* @since 3.14.3
*
* @param array<string> $keys The path to the setting.
* @return mixed|array<mixed>|null The default value for the setting path.
*/
protected function get_default( array $keys ) {
$current = $this->default_value;

foreach ( $keys as $key ) {
if ( ! is_array( $current ) || ! isset( $current[ $key ] ) ) {
return null; // No default value for invalid key path.
}
if ( isset( $current[ $key ]['default'] ) ) {
$current = $current[ $key ]['default'];
} else {
$current = $current[ $key ];
}
}

return $current; // Return default value for valid key path.
}


/**
* Gets the specifications for nested settings based on a composite key.
*
* @since 3.14.3
*
* @param string $composite_key The composite key representing the nested path.
* @return array<mixed> The specifications for the nested path.
*/
protected function get_nested_specs( string $composite_key ): array {
$keys = explode( '.', $composite_key );
$specs = $this->get_subvalues_specs();

foreach ( $keys as $key ) {
if ( is_array( $specs[ $key ] ) && array_key_exists( 'values', $specs[ $key ] ) ) {
$specs = $specs[ $key ]['values'];
} else {
break;
}
}

return $specs;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,55 +41,51 @@ protected function get_meta_key(): string {
*/
protected function get_subvalues_specs(): array {
return array(
'InitialTabName' => array(
'InitialTabName' => array(
'values' => array( 'tools', 'performance' ),
'default' => 'tools',
),
'PerformanceStatsSettings' => array(
'PerformanceStats' => array(
'values' => array(
'Period' => array( '10m', '1h', '2h', '4h', '24h', '7d', '30d' ),
'VisiblePanels' => array( 'overview', 'categories', 'referrers' ),
'VisibleDataPoints' => array( 'views', 'visitors', 'avgEngaged', 'recirculation' ),
'VisiblePanels' => array( 'overview', 'categories', 'referrers' ),
),
'default' => array(
'Period' => '7d',
'VisiblePanels' => array( 'overview', 'categories', 'referrers' ),
'VisibleDataPoints' => array( 'views', 'visitors', 'avgEngaged', 'recirculation' ),
'VisiblePanels' => array( 'overview', 'categories', 'referrers' ),
),
),
'RelatedPostsFilterBy' => array(
'values' => array( 'unavailable', 'tag', 'section', 'author' ),
'default' => 'unavailable',
),
'RelatedPostsFilterValue' => array(
'values' => array(),
'default' => '',
),
'RelatedPostsMetric' => array(
'values' => array( 'views', 'avg_engaged' ),
'default' => 'views',
),
'RelatedPostsOpen' => array(
'values' => array( true, false ),
'default' => false,
),
'RelatedPostsPeriod' => array(
'values' => array( '10m', '1h', '2h', '4h', '24h', '7d', '30d' ),
'default' => '7d',
),
'SmartLinkingMaxLinks' => array(
'values' => array(),
'default' => 10,
),
'SmartLinkingMaxLinkWords' => array(
'values' => array(),
'default' => 4,
'RelatedPosts' => array(
'values' => array(
'FilterBy' => array( 'unavailable', 'tag', 'section', 'author' ),
'FilterValue' => array(),
'Metric' => array( 'views', 'avg_engaged' ),
'Open' => array( true, false ),
'Period' => array( '10m', '1h', '2h', '4h', '24h', '7d', '30d' ),
),
'default' => array(
'FilterBy' => 'unavailable',
'FilterValue' => '',
'Metric' => 'views',
'Open' => false,
'Period' => '7d',
),
),
'SmartLinkingOpen' => array(
'values' => array( true, false ),
'default' => false,
'SmartLinking' => array(
'values' => array(
'MaxLinks' => array(),
'MaxLinkWords' => array(),
'Open' => array( true, false ),
),
'default' => array(
'MaxLinks' => 10,
'MaxLinkWords' => 4,
'Open' => false,
),
),
'TitleSuggestionsSettings' => array(
'TitleSuggestions' => array(
'values' => array(
'Open' => array( true, false ),
'Persona' => array(),
Expand Down
8 changes: 6 additions & 2 deletions src/content-helper/common/settings/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,23 @@
* Import the settings types.
*/
import type {
PerformanceStatsSettings,
RelatedPostsSettings,
SidebarSettings,
SmartLinkingSettings,
TitleSuggestionsSettings,
PerformanceStatsSettings,
} from './sidebar-settings';
import type { TopPostsSettings } from './top-posts-settings';

/**
* Export the settings types.
*/
export type {
PerformanceStatsSettings, // Part of SidebarSettings type.
RelatedPostsSettings, // Part of SidebarSettings type.
SidebarSettings,
SmartLinkingSettings, // Part of SidebarSettings type.
TitleSuggestionsSettings, // Part of SidebarSettings type.
PerformanceStatsSettings, // Part of SidebarSettings type.
TopPostsSettings,
};

Expand Down
38 changes: 28 additions & 10 deletions src/content-helper/common/settings/types/sidebar-settings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,10 @@ import { Metric, Period } from '../../utils/constants';
*/
export interface SidebarSettings {
InitialTabName: string;
PerformanceStatsSettings: PerformanceStatsSettings;
RelatedPostsFilterBy: string;
RelatedPostsFilterValue: string;
RelatedPostsMetric: Metric;
RelatedPostsOpen: boolean;
RelatedPostsPeriod: Period;
SmartLinkingMaxLinks: number;
SmartLinkingMaxLinkWords: number;
SmartLinkingOpen: boolean;
TitleSuggestionsSettings: TitleSuggestionsSettings;
PerformanceStats: PerformanceStatsSettings;
RelatedPosts: RelatedPostsSettings;
SmartLinking: SmartLinkingSettings;
TitleSuggestions: TitleSuggestionsSettings;
}

/**
Expand All @@ -41,3 +35,27 @@ export interface TitleSuggestionsSettings {
Persona: string;
Tone: string;
}

/**
* Defines the settings structure for the RelatedPosts component.
*
* @since 3.14.3
*/
export interface RelatedPostsSettings {
FilterBy: string;
FilterValue: string;
Metric: Metric;
Open: boolean;
Period: Period;
}

/**
* Defines the settings structure for the SmartLinking component.
*
* @since 3.14.3
*/
export interface SmartLinkingSettings {
MaxLinks: number;
MaxLinkWords: number;
Open: boolean;
}
Loading
Loading