'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() ); } } }