Skip to content
Open
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
17 changes: 14 additions & 3 deletions classes/PodsAdmin.php
Original file line number Diff line number Diff line change
Expand Up @@ -4676,7 +4676,11 @@ public function add_debug_information( $info ) {

$settings_values = $settings->get_settings();

$auto_start = pods_v( $auto_start, $settings_fields['session_auto_start']['data'], __( 'Unknown', 'pods' ) );
$session_auto_start_data = ( isset( $settings_fields['session_auto_start'] ) && isset( $settings_fields['session_auto_start']['data'] ) )
? $settings_fields['session_auto_start']['data']
: [];

$auto_start = pods_v( $auto_start, $session_auto_start_data, __( 'Unknown', 'pods' ) );

require_once ABSPATH . '/wp-admin/includes/file.php';

Expand All @@ -4687,6 +4691,13 @@ public function add_debug_information( $info ) {

global $wpdb;

// Cache session_save_path() once and guard the FS checks below. Hosts that
// restrict /var/lib/php/sessions through open_basedir (or run memcached/redis
// handlers with tcp:// handlers) would otherwise trigger file_exists()
// warnings on every Site Health request -- see issue #7263.
$save_path = session_save_path();
$can_check_session_fs = $filesystem_ok && $wp_filesystem && ! empty( $save_path ) && 0 !== strpos( $save_path, 'tcp://' );

$info['pods'] = [
'label' => 'Pods',
'description' => __( 'Debug information for Pods installations.', 'pods' ),
Expand Down Expand Up @@ -4717,11 +4728,11 @@ public function add_debug_information( $info ) {
],
'pods-session-save-path-exists' => [
'label' => __( 'Session Save Path Exists', 'pods' ),
'value' => ( $filesystem_ok && $wp_filesystem && $wp_filesystem->exists( session_save_path() ) ) ? __( 'Yes', 'pods' ) : __( 'No', 'pods' ),
'value' => ( $can_check_session_fs && $wp_filesystem->exists( $save_path ) ) ? __( 'Yes', 'pods' ) : __( 'No', 'pods' ),
],
'pods-session-save-path-writable' => [
'label' => __( 'Session Save Path Writeable', 'pods' ),
'value' => ( $filesystem_ok && $wp_filesystem && $wp_filesystem->is_writable( session_save_path() ) ) ? __( 'Yes', 'pods' ) : __( 'No', 'pods' ),
'value' => ( $can_check_session_fs && $wp_filesystem->is_writable( $save_path ) ) ? __( 'Yes', 'pods' ) : __( 'No', 'pods' ),
],
'pods-session-max-lifetime' => [
'label' => __( 'Session Max Lifetime', 'pods' ),
Expand Down
169 changes: 167 additions & 2 deletions classes/PodsField.php
Original file line number Diff line number Diff line change
Expand Up @@ -975,15 +975,180 @@ public function maybe_sanitize_output( $value, $options = null ) {

if ( $should_sanitize ) {
if ( is_string( $value ) ) {
$value = wp_kses_post( $value );
$value = $this->sanitize_output_string( $value );
} elseif ( is_array( $value ) || is_object( $value ) ) {
$value = wp_kses_post_deep( $value );
$value = $this->sanitize_output_array( $value );
}
}

return $value;
}

/**
* Sanitize a string field value before output.
*
* Code (Syntax Highlighting) fields commonly embed third-party scripts and
* iframes (SimpleShop, Google Reviews, TikTok widgets, etc.). WordPress's
* default {@see wp_kses_post()} allow-list drops external `<script src>`
* and arbitrary `<iframe>` embeds, which broke such widgets starting in
* Pods 3.1.x (issue #7263 and sibling #7319). For the `code` type we layer
* those embeds back on top of the standard post allow-list; all other
* field types keep the previous behaviour.
*
* @since 3.3.10
*
* @param string $value Raw field value.
*
* @return string Sanitized value.
*/
protected function sanitize_output_string( $value ) {
if ( 'code' === static::$type ) {
return $this->wp_kses_post_with_embeds( $value );
}

return wp_kses_post( $value );
}

/**
* Recursively sanitize an array/object field value before output.
*
* @since 3.3.10
*
* @param array|object $value Raw field value.
*
* @return array|object Sanitized value.
*/
protected function sanitize_output_array( $value ) {
if ( 'code' === static::$type ) {
return $this->wp_kses_post_deep_with_embeds( $value );
}

return wp_kses_post_deep( $value );
}

/**
* {@see wp_kses_post()} with extra allowance for external script and iframe embeds.
*
* @since 3.3.10
*
* @param string $value HTML to sanitize.
*
* @return string Sanitized HTML with embeds preserved.
*/
protected function wp_kses_post_with_embeds( $value ) {
return wp_kses( $value, $this->get_post_with_embeds_allowed_html() );
}

/**
* Recursive form of {@see PodsField::wp_kses_post_with_embeds()}.
*
* @since 3.3.10
*
* @param array|object $value Value to sanitize recursively.
*
* @return array|object Sanitized value.
*/
protected function wp_kses_post_deep_with_embeds( $value ) {
if ( is_array( $value ) ) {
foreach ( $value as $k => $v ) {
$value[ $k ] = $this->sanitize_output_array( $v );
}

return $value;
}

if ( is_object( $value ) ) {
foreach ( $value as $k => $v ) {
$value->{$k} = $this->sanitize_output_array( $v );
}

return $value;
}

if ( is_string( $value ) ) {
return $this->wp_kses_post_with_embeds( $value );
}

return $value;
}

/**
* Build the kses allow-list used for Code field output.
*
* Starts from {@see wp_kses_allowed_html()} for context `post` and adds
* the attributes typical embed widgets produce. {@see wp_kses_allowed_html()}
* already strips external script `src` and arbitrary iframe embeds, which
* is what regressed in #7263.
*
* @since 3.3.10
*
* @return array<string, array<string, bool>>
*/
protected function get_post_with_embeds_allowed_html() {
$allowed = wp_kses_allowed_html( 'post' );

// External <script src="..."> embeds (SimpleShop, ads, analytics widgets, etc.).
//
// This is deliberately gated. wp_kses() filters tags and attributes but never
// the *text content* of an element, so allowing <script> unconditionally would
// let anyone able to edit this field store executable JavaScript that then runs
// for every visitor -- and code_sanitize_html is the very option an admin turns
// on to prevent that. Allowing it only for unfiltered_html keeps the capability
// boundary WordPress already uses for raw HTML.
//
// Note this is an output-time check, so it reflects the *viewer*: script-based
// embeds render for users holding unfiltered_html, while iframe-based embeds
// (YouTube, Vimeo, most third-party forms) keep working for everyone. Sites that
// genuinely need public script embeds can re-add them via the
// pods_code_field_sanitize_allowed_html filter below.
if ( current_user_can( 'unfiltered_html' ) ) {
$allowed['script'] = [
'src' => true,
'type' => true,
'async' => true,
'defer' => true,
'integrity' => true,
'crossorigin' => true,
'nonce' => true,
'charset' => true,
'data-*' => true,
];
}

// Inline <iframe> embeds (YouTube, Vimeo, third-party forms). wp_kses_allowed_html
// already permits iframe; this widens the allowed attributes that embeds actually use.
$iframe_attrs = [
'src' => true,
'width' => true,
'height' => true,
'frameborder' => true,
'allow' => true,
'allowfullscreen' => true,
'title' => true,
'loading' => true,
'referrerpolicy' => true,
'sandbox' => true,
'name' => true,
'id' => true,
'style' => true,
'class' => true,
'data-*' => true,
];

$allowed['iframe'] = isset( $allowed['iframe'] ) && is_array( $allowed['iframe'] )
? array_merge( $allowed['iframe'], $iframe_attrs )
: $iframe_attrs;

/**
* Filter the kses allow-list used for Code (Syntax Highlighting) field output.
*
* @since 3.3.10
*
* @param array<string, array<string, bool>> $allowed Allowed HTML tags and attributes.
*/
return apply_filters( 'pods_code_field_sanitize_allowed_html', $allowed );
}

/**
* Strip shortcodes based on options.
*
Expand Down
124 changes: 124 additions & 0 deletions tests/codeception/wpunit/Pods/AdminDebugInformationTest.php

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

Loading