d36bb954d1
Baseline before backporting wp-data-optimizer v3.0.1-v3.4.6. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
196 lines
5.1 KiB
PHP
196 lines
5.1 KiB
PHP
<?php
|
|
/**
|
|
* Base class for all WPDO interceptors.
|
|
*
|
|
* @package WP_Data_Optimizer
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Base class for all WPDO interceptors.
|
|
*
|
|
* Ported from HPCT_Interceptor_Base with WPDO enhancements:
|
|
* - Uses TMDO_Feature_Flags (7-state) instead of HPCT 4-state
|
|
* - Uses TMDO_Logger instead of HPCT_Logger
|
|
* - Adds zone awareness for future Zone-based interceptors
|
|
*/
|
|
abstract class TMDO_Interceptor_Base {
|
|
|
|
/**
|
|
* Module name — set in each subclass.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected string $module = '';
|
|
|
|
/**
|
|
* Whether to send admin email on first error.
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected bool $email_on_error = true;
|
|
|
|
/**
|
|
* Return true when the module is fully enabled (reads from custom table).
|
|
*
|
|
* @return bool True if module is in complete state.
|
|
*/
|
|
protected function is_enabled(): bool {
|
|
return TMDO_Feature_Flags::is_complete( $this->module );
|
|
}
|
|
|
|
/**
|
|
* Return true when dual-write is active (writes go to both native + custom).
|
|
*
|
|
* @return bool True if module is in a write-active state.
|
|
*/
|
|
protected function is_write_active(): bool {
|
|
return TMDO_Feature_Flags::is_write_active( $this->module );
|
|
}
|
|
|
|
/**
|
|
* Return true when the module should intercept reads or writes.
|
|
*
|
|
* @return bool True if module is enabled or write-active.
|
|
*/
|
|
protected function is_active(): bool {
|
|
return $this->is_enabled() || $this->is_write_active();
|
|
}
|
|
|
|
/**
|
|
* Execute a custom-table callable safely.
|
|
*
|
|
* If $custom throws, log and return $native_fallback().
|
|
* After 3 consecutive errors in a request, disable the module.
|
|
*
|
|
* @param callable $custom Custom table read/write callable.
|
|
* @param callable $native_fallback Original WordPress callable.
|
|
* @param string $hook Hook name for logging context.
|
|
* @return mixed
|
|
* @throws \RuntimeException When the custom callable returns a WP_Error.
|
|
*/
|
|
protected function intercept( callable $custom, callable $native_fallback, string $hook = '' ): mixed {
|
|
if ( ! $this->is_enabled() ) {
|
|
return $native_fallback();
|
|
}
|
|
|
|
try {
|
|
$result = $custom();
|
|
|
|
if ( is_wp_error( $result ) ) {
|
|
throw new \RuntimeException( $result->get_error_message() );
|
|
}
|
|
|
|
return $result;
|
|
|
|
} catch ( \Throwable $e ) {
|
|
$this->handle_error(
|
|
$hook ?: 'intercept',
|
|
$e->getMessage(),
|
|
array(
|
|
'exception' => get_class( $e ),
|
|
'file' => $e->getFile(),
|
|
'line' => $e->getLine(),
|
|
)
|
|
);
|
|
|
|
return $native_fallback();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dual-write: call native first, then sync to custom table.
|
|
* Custom failure is non-fatal.
|
|
*
|
|
* @param callable $native Original write callable (always executed).
|
|
* @param callable $custom Custom table write callable.
|
|
* @param string $hook Hook name for logging.
|
|
* @return mixed Return value of $native.
|
|
*/
|
|
protected function dual_write( callable $native, callable $custom, string $hook = '' ): mixed {
|
|
$result = $native();
|
|
|
|
if ( $this->is_write_active() || $this->is_enabled() ) {
|
|
try {
|
|
$custom( $result );
|
|
} catch ( \Throwable $e ) {
|
|
$this->handle_error(
|
|
$hook ?: 'dual_write',
|
|
$e->getMessage(),
|
|
array(
|
|
'exception' => get_class( $e ),
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Register all hooks. Called by TMDO_Core.
|
|
*
|
|
* @return void
|
|
*/
|
|
abstract public function register_hooks(): void;
|
|
|
|
// ── Private helpers ───────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Per-request consecutive error counter, keyed by module.
|
|
*
|
|
* @var array
|
|
*/
|
|
private static array $error_counts = array();
|
|
|
|
/**
|
|
* Handles an interceptor error and auto-disables the module after 3 consecutive errors.
|
|
*
|
|
* @param string $hook Hook name for logging context.
|
|
* @param string $message Error message.
|
|
* @param array $context Additional context data.
|
|
* @return void
|
|
*/
|
|
private function handle_error( string $hook, string $message, array $context = array() ): void {
|
|
TMDO_Logger::error( $this->module, $hook, $message, $context );
|
|
|
|
self::$error_counts[ $this->module ] = ( self::$error_counts[ $this->module ] ?? 0 ) + 1;
|
|
|
|
if ( self::$error_counts[ $this->module ] >= 3 ) {
|
|
TMDO_Feature_Flags::reset( $this->module );
|
|
TMDO_Logger::error( $this->module, $hook, 'Module auto-disabled after 3 consecutive errors.' );
|
|
|
|
if ( $this->email_on_error ) {
|
|
$this->notify_admin( $message );
|
|
$this->email_on_error = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sends an admin notification email when a module is auto-disabled.
|
|
*
|
|
* @param string $message Error message to include in the notification.
|
|
* @return void
|
|
*/
|
|
private function notify_admin( string $message ): void {
|
|
$admin_email = get_option( 'admin_email' );
|
|
if ( ! $admin_email ) {
|
|
return;
|
|
}
|
|
|
|
wp_mail(
|
|
$admin_email,
|
|
sprintf( '[WPDO] Module "%s" auto-disabled', $this->module ),
|
|
sprintf(
|
|
"The WPDO module \"%s\" has been automatically disabled due to repeated errors.\n\nLast error: %s\n\nPlease review the error log at Tools > WP Data Optimizer > Logs.",
|
|
$this->module,
|
|
$message
|
|
)
|
|
);
|
|
}
|
|
}
|