Release 2.4.2: core 1.4.2 bugfix #11
@@ -92,7 +92,7 @@
|
||||
if ( data.success ) {
|
||||
showStatus( statusEl, i18n.saved, false );
|
||||
} else {
|
||||
showStatus( statusEl, i18n.error, true );
|
||||
showStatus( statusEl, ( data.data && data.data.message ) || i18n.error, true );
|
||||
}
|
||||
} )
|
||||
.catch( () => {
|
||||
|
||||
+81
-24
@@ -398,17 +398,7 @@ class TMDO_Admin {
|
||||
|
||||
// v2.5.4: Entity Bridge settings (v2.11.0: post added).
|
||||
update_option( 'wpdo_hook_bus_enabled', isset( $_POST['wpdo_hook_bus_enabled'] ) ? '1' : '0', false );
|
||||
if ( class_exists( 'TMDO_Mode_Manager' ) ) {
|
||||
foreach ( array( 'user', 'post', 'term', 'comment' ) as $entity_type ) {
|
||||
$new_mode = isset( $_POST[ 'wpdo_bridge_mode_' . $entity_type ] )
|
||||
? sanitize_key( wp_unslash( (string) $_POST[ 'wpdo_bridge_mode_' . $entity_type ] ) )
|
||||
: '';
|
||||
if ( TMDO_Mode_Manager::is_valid_mode( $new_mode ) ) {
|
||||
TMDO_Mode_Manager::set( $entity_type, $new_mode );
|
||||
}
|
||||
}
|
||||
TMDO_Mode_Manager::reset_cache();
|
||||
}
|
||||
self::remember_bridge_mode_refusals( self::apply_bridge_modes( self::posted_bridge_modes() ) );
|
||||
if ( class_exists( 'TMDO_Hook_Bus_Bridge' ) ) {
|
||||
TMDO_Hook_Bus_Bridge::reset_cache();
|
||||
}
|
||||
@@ -3144,6 +3134,9 @@ wpdo.getListings({ per_page: 3 }).then(r => console.log(r));'
|
||||
if ( isset( $_GET['wpdo_msg'] ) && 'settings_saved' === sanitize_key( wp_unslash( (string) $_GET['wpdo_msg'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( '設定已儲存。', '2meet-data-optimizer' ) . '</p></div>';
|
||||
}
|
||||
foreach ( self::pull_bridge_mode_refusals() as $refused ) {
|
||||
echo '<div class="notice notice-error"><p>' . esc_html__( '模式未變更:', '2meet-data-optimizer' ) . esc_html( $refused ) . '</p></div>';
|
||||
}
|
||||
$enabled = class_exists( 'TMDO_Email_Notifier' ) && TMDO_Email_Notifier::is_enabled();
|
||||
$email = class_exists( 'TMDO_Email_Notifier' ) ? TMDO_Email_Notifier::recipient() : '';
|
||||
$throttle = class_exists( 'TMDO_Email_Notifier' ) ? TMDO_Email_Notifier::throttle_hours() : 24;
|
||||
@@ -3743,22 +3736,33 @@ wpdo.getListings({ per_page: 3 }).then(r => console.log(r));'
|
||||
}
|
||||
}
|
||||
|
||||
private static function save_section_entity_bridge(): void {
|
||||
/**
|
||||
* Save the Entity Bridge section (Hook Bus switch and bridge modes).
|
||||
*
|
||||
* @return string[] Refused changes, "<entity>:<reason>".
|
||||
*/
|
||||
private static function save_section_entity_bridge(): array {
|
||||
update_option( 'wpdo_hook_bus_enabled', isset( $_POST['wpdo_hook_bus_enabled'] ) ? '1' : '0', false );
|
||||
if ( class_exists( 'TMDO_Mode_Manager' ) ) {
|
||||
foreach ( array( 'user', 'post', 'term', 'comment' ) as $entity_type ) {
|
||||
$new_mode = isset( $_POST[ 'wpdo_bridge_mode_' . $entity_type ] )
|
||||
? sanitize_key( wp_unslash( (string) $_POST[ 'wpdo_bridge_mode_' . $entity_type ] ) )
|
||||
: '';
|
||||
if ( TMDO_Mode_Manager::is_valid_mode( $new_mode ) ) {
|
||||
TMDO_Mode_Manager::set( $entity_type, $new_mode );
|
||||
}
|
||||
}
|
||||
TMDO_Mode_Manager::reset_cache();
|
||||
}
|
||||
$refused = self::apply_bridge_modes( self::posted_bridge_modes() );
|
||||
if ( class_exists( 'TMDO_Hook_Bus_Bridge' ) ) {
|
||||
TMDO_Hook_Bus_Bridge::reset_cache();
|
||||
}
|
||||
return $refused;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bridge modes posted by the settings form, keyed by entity type ('' when absent).
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private static function posted_bridge_modes(): array {
|
||||
$posted = array();
|
||||
foreach ( array( 'user', 'post', 'term', 'comment' ) as $entity_type ) {
|
||||
$posted[ $entity_type ] = isset( $_POST[ 'wpdo_bridge_mode_' . $entity_type ] )
|
||||
? sanitize_key( wp_unslash( (string) $_POST[ 'wpdo_bridge_mode_' . $entity_type ] ) )
|
||||
: '';
|
||||
}
|
||||
return $posted;
|
||||
}
|
||||
|
||||
private static function save_section_hp_transient(): void {
|
||||
@@ -3786,6 +3790,56 @@ wpdo.getListings({ per_page: 3 }).then(r => console.log(r));'
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing,Squiz.Commenting.FunctionComment.Missing,Squiz.Commenting.FunctionComment.WrongStyle
|
||||
|
||||
/**
|
||||
* Apply posted bridge modes through TMDO_Mode_Manager::set(). Empty or
|
||||
* unknown modes are skipped; refused changes (unsafe jump, Promotion Guard)
|
||||
* are returned instead of being dropped silently (2.4.2 PR-135).
|
||||
*
|
||||
* @param array<string, string> $posted Mode per entity type.
|
||||
* @return string[] Refused changes, "<entity>:<reason>".
|
||||
*/
|
||||
public static function apply_bridge_modes( array $posted ): array {
|
||||
if ( ! class_exists( 'TMDO_Mode_Manager' ) ) {
|
||||
return array();
|
||||
}
|
||||
$refused = array();
|
||||
foreach ( $posted as $entity_type => $new_mode ) {
|
||||
if ( ! TMDO_Mode_Manager::is_valid_mode( (string) $new_mode ) ) {
|
||||
continue;
|
||||
}
|
||||
$result = TMDO_Mode_Manager::set( (string) $entity_type, (string) $new_mode );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
$refused[] = $entity_type . ':' . $result->get_error_message();
|
||||
}
|
||||
}
|
||||
TMDO_Mode_Manager::reset_cache();
|
||||
return $refused;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep refused bridge-mode changes for the page the settings form redirects to.
|
||||
*
|
||||
* @param string[] $refused Refused changes.
|
||||
* @return void
|
||||
*/
|
||||
public static function remember_bridge_mode_refusals( array $refused ): void {
|
||||
if ( $refused ) {
|
||||
set_transient( 'wpdo_mode_refused_' . get_current_user_id(), $refused, 5 * MINUTE_IN_SECONDS );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refused bridge-mode changes remembered for this user, once.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function pull_bridge_mode_refusals(): array {
|
||||
$key = 'wpdo_mode_refused_' . get_current_user_id();
|
||||
$refused = get_transient( $key );
|
||||
delete_transient( $key );
|
||||
return is_array( $refused ) ? $refused : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: save a single settings section without a full page reload.
|
||||
*/
|
||||
@@ -3807,7 +3861,10 @@ wpdo.getListings({ per_page: 3 }).then(r => console.log(r));'
|
||||
if ( ! isset( $handlers[ $section ] ) ) {
|
||||
wp_send_json_error( 'invalid_section', 400 );
|
||||
}
|
||||
self::{$handlers[ $section ]}();
|
||||
$refused = self::{$handlers[ $section ]}();
|
||||
if ( is_array( $refused ) && $refused ) {
|
||||
wp_send_json_error( array( 'message' => __( '部分模式未變更:', '2meet-data-optimizer' ) . implode( ';', $refused ) ) );
|
||||
}
|
||||
wp_send_json_success( array( 'message' => __( '設定已儲存。', '2meet-data-optimizer' ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* The admin settings form saves bridge modes through TMDO_Mode_Manager::set().
|
||||
* A refused change (unsafe jump, Promotion Guard) used to be dropped silently:
|
||||
* the page said "saved" while the mode stayed put (2.4.2 PR-135, manual F-2).
|
||||
*/
|
||||
class AdminBridgeModeSaveTest extends TestCase {
|
||||
|
||||
public static function setUpBeforeClass(): void {
|
||||
require_once TMDO_PATH . 'admin/class-tmdo-admin.php';
|
||||
}
|
||||
|
||||
protected function setUp(): void {
|
||||
ini_set( 'error_log', '/dev/null' );
|
||||
$GLOBALS['_wp_options'] = array();
|
||||
$GLOBALS['_wp_filter_callbacks'] = array();
|
||||
$GLOBALS['_wp_current_user_can'] = array( 'manage_options' => true );
|
||||
$GLOBALS['_wp_current_user_id'] = 7;
|
||||
TMDO_Mode_Manager::reset_cache();
|
||||
}
|
||||
|
||||
private function modes( array $modes ): void {
|
||||
$GLOBALS['_wp_options']['wpdo_bridge_modes'] = $modes;
|
||||
TMDO_Mode_Manager::reset_cache();
|
||||
}
|
||||
|
||||
public function test_an_accepted_change_is_applied_without_refusals(): void {
|
||||
$this->modes( array( 'user' => 'disabled' ) );
|
||||
|
||||
$refused = TMDO_Admin::apply_bridge_modes( array( 'user' => 'dual_write' ) );
|
||||
|
||||
$this->assertSame( array(), $refused );
|
||||
$this->assertSame( 'dual_write', TMDO_Mode_Manager::get( 'user' ) );
|
||||
}
|
||||
|
||||
public function test_an_unsafe_jump_is_reported_and_the_mode_kept(): void {
|
||||
$this->modes( array( 'post' => 'disabled' ) );
|
||||
|
||||
$refused = TMDO_Admin::apply_bridge_modes( array( 'post' => 'shadow_read' ) );
|
||||
|
||||
$this->assertCount( 1, $refused );
|
||||
$this->assertStringStartsWith( 'post', $refused[0] );
|
||||
$this->assertSame( 'disabled', TMDO_Mode_Manager::get( 'post' ) );
|
||||
}
|
||||
|
||||
public function test_empty_or_unknown_modes_are_ignored(): void {
|
||||
$this->modes( array( 'term' => 'disabled' ) );
|
||||
|
||||
$this->assertSame( array(), TMDO_Admin::apply_bridge_modes( array( 'post' => '', 'term' => 'bogus' ) ) );
|
||||
$this->assertSame( 'disabled', TMDO_Mode_Manager::get( 'term' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
* @preserveGlobalState disabled
|
||||
*/
|
||||
public function test_a_promotion_refused_by_the_guard_is_reported(): void {
|
||||
eval( 'namespace HivePress; class Core {}' );
|
||||
$this->modes( array( 'user' => 'shadow_read' ) );
|
||||
|
||||
$refused = TMDO_Admin::apply_bridge_modes( array( 'user' => 'aeav_only' ) );
|
||||
|
||||
$this->assertCount( 1, $refused );
|
||||
$this->assertSame( 'shadow_read', TMDO_Mode_Manager::get( 'user' ) );
|
||||
}
|
||||
|
||||
/** The full-page form redirects after saving: refusals wait in a per-user transient, shown once. */
|
||||
public function test_refusals_are_kept_for_the_next_page_view_only(): void {
|
||||
TMDO_Admin::remember_bridge_mode_refusals( array( 'post:refused' ) );
|
||||
|
||||
$this->assertSame( array( 'post:refused' ), TMDO_Admin::pull_bridge_mode_refusals() );
|
||||
$this->assertSame( array(), TMDO_Admin::pull_bridge_mode_refusals() );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user