From 56d534619ece509b227e2d66693db7c7e8ec9c2b Mon Sep 17 00:00:00 2001 From: wpdev Date: Fri, 31 Jul 2026 06:07:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(interceptors):=20=E6=A0=B8=E5=BF=83?= =?UTF-8?q?=E5=8A=A0=E5=85=A5=20Standard=5FPost=5FInterceptor=20=E6=8A=BD?= =?UTF-8?q?=E8=B1=A1=E5=9F=BA=E5=BA=95=EF=BC=88F1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Template Method 基底(A v3.0.1),收斂 action_delete_post / filter_update_meta / action_insert_post 三件套。子類只需宣告 public const FIELD_MAP + get_post_type() + get_table_key() + build_insert_data()。 HP AddOn 的 4 個 interceptor 隨即改為繼承它(573 → 344 行);未來 LatePoint / bookings / quotation 等 post↔自訂表 1:1 鏡射也可直接用。 back-compat 補 WPDO_Standard_Post_Interceptor alias。 unit 451 / integration 398 GREEN Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY --- 2meet-data-optimizer.php | 1 + includes/class-tmdo-back-compat.php | 1 + .../class-tmdo-standard-post-interceptor.php | 149 ++++++++++++++++++ tests/bootstrap.php | 1 + tests/integration/bootstrap.php | 1 + 5 files changed, 153 insertions(+) create mode 100644 includes/interceptors/class-tmdo-standard-post-interceptor.php diff --git a/2meet-data-optimizer.php b/2meet-data-optimizer.php index a77c02d..1e41e67 100644 --- a/2meet-data-optimizer.php +++ b/2meet-data-optimizer.php @@ -133,6 +133,7 @@ require_once TMDO_PATH . 'includes/class-tmdo-comment-stress-tester.php'; // ── Interceptor base + Sync Bridge(核心唯一保留 interceptor)───────────── require_once TMDO_PATH . 'includes/interceptors/class-tmdo-interceptor-base.php'; +require_once TMDO_PATH . 'includes/interceptors/class-tmdo-standard-post-interceptor.php'; require_once TMDO_PATH . 'includes/interceptors/class-tmdo-sync-bridge.php'; // ── Zone handlers ───────────────────────────────────────────────────────── diff --git a/includes/class-tmdo-back-compat.php b/includes/class-tmdo-back-compat.php index e32a644..e1513f0 100644 --- a/includes/class-tmdo-back-compat.php +++ b/includes/class-tmdo-back-compat.php @@ -127,6 +127,7 @@ $tmdo_class_aliases = array( 'TMDO_Phase_Completed' => 'WPDO_Phase_Completed', 'TMDO_Zone_Archive' => 'WPDO_Zone_Archive', 'TMDO_Interceptor_Base' => 'WPDO_Interceptor_Base', + 'TMDO_Standard_Post_Interceptor' => 'WPDO_Standard_Post_Interceptor', 'TMDO_Query_Interceptor_Base' => 'WPDO_Query_Interceptor_Base', 'TMDO_Abstract_Notifier' => 'WPDO_Abstract_Notifier', ); diff --git a/includes/interceptors/class-tmdo-standard-post-interceptor.php b/includes/interceptors/class-tmdo-standard-post-interceptor.php new file mode 100644 index 0000000..af21486 --- /dev/null +++ b/includes/interceptors/class-tmdo-standard-post-interceptor.php @@ -0,0 +1,149 @@ + 'column', ... ]; + * + * @package TMDO + * @since 3.0.1 + */ + +// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from TMDO_DB::table(); column validated by subclass FIELD_MAP constant. + +if ( ! defined( 'ABSPATH' ) ) { + exit; +} + +/** + * Abstract base for standard post→custom-table interceptors. + * + * @since 3.0.1 + */ +abstract class TMDO_Standard_Post_Interceptor extends TMDO_Interceptor_Base { + + // ── Subclass contracts ──────────────────────────────────────────────────── + + /** WordPress post_type slug this interceptor handles. */ + abstract protected function get_post_type(): string; + + /** Key passed to TMDO_DB::table() to resolve the custom table name. */ + abstract protected function get_table_key(): string; + + /** + * Build the INSERT row for a newly created post. + * + * @param int $post_id Post ID. + * @param \WP_Post $post Post object. + * @param string $now Formatted datetime string from TMDO_DB::now(). + * @return array{values: array, formats: array} + * 'values' — associative array of column => value. + * 'formats' — ordered format string array (%d/%s) for $wpdb->insert. + */ + abstract protected function build_insert_data( int $post_id, \WP_Post $post, string $now ): array; + + // ── Shared implementation ───────────────────────────────────────────────── + + /** + * Register the three standard hooks. Subclasses that need additional + * hooks (e.g. get_post_metadata) should call parent::register_hooks() + * first and then add their own. + */ + public function register_hooks(): void { + add_filter( 'update_post_metadata', array( $this, 'filter_update_meta' ), 10, 5 ); + add_action( 'wp_insert_post', array( $this, 'action_insert_post' ), 10, 3 ); + add_action( 'before_delete_post', array( $this, 'action_delete_post' ), 10, 2 ); + } + + /** + * Deletes the custom-table record when the post is deleted. + * + * @param int $post_id Post ID. + * @param \WP_Post $post Post object. + * @return void + */ + final public function action_delete_post( int $post_id, \WP_Post $post ): void { + if ( $this->get_post_type() !== $post->post_type || ! $this->is_active() ) { + return; + } + + try { + global $wpdb; + $wpdb->delete( TMDO_DB::table( $this->get_table_key() ), array( 'post_id' => $post_id ), array( '%d' ) ); + } catch ( \Throwable $e ) { + TMDO_Logger::error( $this->module, 'before_delete_post', $e->getMessage() ); + } + } + + /** + * Syncs a meta update to the custom table column. + * + * @param mixed $check Short-circuit flag. + * @param int $post_id Post ID. + * @param string $meta_key Meta key. + * @param mixed $meta_value Meta value. + * @param mixed $prev_value Previous value (unused). + * @return mixed Unmodified $check. + */ + final public function filter_update_meta( $check, int $post_id, string $meta_key, $meta_value, $prev_value ) { + $field_map = static::FIELD_MAP; // late static binding — picks up subclass const. + if ( $this->get_post_type() !== get_post_type( $post_id ) || ! isset( $field_map[ $meta_key ] ) ) { + return $check; + } + + if ( ! $this->is_active() ) { + return $check; + } + + try { + global $wpdb; + $col = $field_map[ $meta_key ]; + $table = TMDO_DB::table( $this->get_table_key() ); + $wpdb->query( + $wpdb->prepare( + "UPDATE `{$table}` SET `{$col}` = %s, updated_at = %s WHERE post_id = %d", + $meta_value, + TMDO_DB::now(), + $post_id + ) + ); + } catch ( \Throwable $e ) { + TMDO_Logger::error( $this->module, 'update_post_metadata', $e->getMessage() ); + } + + return $check; + } + + /** + * Inserts the custom-table record when a new post is created. + * + * @param int $post_id Post ID. + * @param \WP_Post $post Post object. + * @param bool $update Whether this is an update (not a new post). + * @return void + */ + final public function action_insert_post( int $post_id, \WP_Post $post, bool $update ): void { + if ( $update || $this->get_post_type() !== $post->post_type || ! $this->is_active() ) { + return; + } + + try { + $data = $this->build_insert_data( $post_id, $post, TMDO_DB::now() ); + global $wpdb; + $wpdb->insert( + TMDO_DB::table( $this->get_table_key() ), + $data['values'], + $data['formats'] + ); + } catch ( \Throwable $e ) { + TMDO_Logger::error( $this->module, 'wp_insert_post', $e->getMessage() ); + } + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 1c4a5a2..383a423 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -510,6 +510,7 @@ require_once TMDO_PATH . 'includes/class-tmdo-compatibility.php'; // Interceptors. require_once TMDO_PATH . 'includes/interceptors/class-tmdo-interceptor-base.php'; +require_once TMDO_PATH . 'includes/interceptors/class-tmdo-standard-post-interceptor.php'; require_once TMDO_PATH . 'includes/interceptors/class-tmdo-sync-bridge.php'; // Zones. diff --git a/tests/integration/bootstrap.php b/tests/integration/bootstrap.php index 6ed4d1b..a44ddd7 100644 --- a/tests/integration/bootstrap.php +++ b/tests/integration/bootstrap.php @@ -526,6 +526,7 @@ require_once TMDO_PATH . 'includes/class-tmdo-hook-bus-bridge.php'; require_once TMDO_PATH . 'includes/class-tmdo-conflict-monitor.php'; require_once TMDO_PATH . 'includes/class-tmdo-compatibility.php'; require_once TMDO_PATH . 'includes/interceptors/class-tmdo-interceptor-base.php'; +require_once TMDO_PATH . 'includes/interceptors/class-tmdo-standard-post-interceptor.php'; require_once TMDO_PATH . 'includes/interceptors/class-tmdo-sync-bridge.php'; require_once TMDO_PATH . 'includes/zones/class-tmdo-zone-router.php'; require_once TMDO_PATH . 'includes/class-tmdo-routing-predicate.php';