prefix . 'wpdo_term_misc'; } /** * Comment-side fully-qualified misc table name. * * @return string */ public static function comment_table(): string { global $wpdb; return $wpdb->prefix . 'wpdo_comment_misc'; } // ── Term metadata callbacks ─────────────────────────────────────────────── /** * Filter callback: get_term_metadata. * * @param mixed $pre Filter accumulator. * @param int $object_id Term ID. * @param string $meta_key Meta key being read. * @param bool $single Whether single value was requested. * @return mixed */ public static function on_term_read( $pre, $object_id, $meta_key, $single ) { unset( $single ); // Only handle when no earlier filter has resolved this read. if ( null !== $pre ) { return $pre; } if ( ! is_string( $meta_key ) || '' === $meta_key ) { return $pre; } $value = self::read( self::term_table(), 'term_id', (int) $object_id, $meta_key ); if ( null === $value ) { return $pre; } return array( $value ); } /** * Filter callback: add_term_metadata. * * @param mixed $check Filter accumulator. * @param int $object_id Term ID. * @param string $meta_key Meta key. * @param mixed $meta_value Value. * @param bool $unique Unique flag (unused). * @return mixed */ public static function on_term_add( $check, $object_id, $meta_key, $meta_value, $unique ) { unset( $unique ); if ( null !== $check ) { return $check; } if ( ! is_string( $meta_key ) || '' === $meta_key ) { return $check; } self::write( self::term_table(), 'term_id', (int) $object_id, $meta_key, $meta_value ); return true; } /** * Filter callback: update_term_metadata. * * @param mixed $check Filter accumulator. * @param int $object_id Term ID. * @param string $meta_key Meta key. * @param mixed $meta_value Value. * @param mixed $prev_value Previous value (unused). * @return mixed */ public static function on_term_update( $check, $object_id, $meta_key, $meta_value, $prev_value ) { unset( $prev_value ); if ( null !== $check ) { return $check; } if ( ! is_string( $meta_key ) || '' === $meta_key ) { return $check; } self::write( self::term_table(), 'term_id', (int) $object_id, $meta_key, $meta_value ); return true; } /** * Filter callback: delete_term_metadata. * * @param mixed $check Filter accumulator. * @param int $object_id Term ID. * @param string $meta_key Meta key. * @param mixed $meta_value Value-scoped delete (unused). * @param bool $delete_all Delete-all flag (unused). * @return mixed */ public static function on_term_delete( $check, $object_id, $meta_key, $meta_value, $delete_all ) { unset( $meta_value, $delete_all ); if ( null !== $check ) { return $check; } if ( ! is_string( $meta_key ) || '' === $meta_key ) { return $check; } self::delete_row( self::term_table(), 'term_id', (int) $object_id, $meta_key ); return true; } // ── Comment metadata callbacks ──────────────────────────────────────────── /** * Filter callback: get_comment_metadata. * * @param mixed $pre Filter accumulator. * @param int $object_id Comment ID. * @param string $meta_key Meta key. * @param bool $single Single flag (unused). * @return mixed */ public static function on_comment_read( $pre, $object_id, $meta_key, $single ) { unset( $single ); if ( null !== $pre ) { return $pre; } if ( ! is_string( $meta_key ) || '' === $meta_key ) { return $pre; } $value = self::read( self::comment_table(), 'comment_id', (int) $object_id, $meta_key ); if ( null === $value ) { return $pre; } return array( $value ); } /** * Filter callback: add_comment_metadata. * * @param mixed $check Filter accumulator. * @param int $object_id Comment ID. * @param string $meta_key Meta key. * @param mixed $meta_value Value. * @param bool $unique Unique flag (unused). * @return mixed */ public static function on_comment_add( $check, $object_id, $meta_key, $meta_value, $unique ) { unset( $unique ); if ( null !== $check ) { return $check; } if ( ! is_string( $meta_key ) || '' === $meta_key ) { return $check; } self::write( self::comment_table(), 'comment_id', (int) $object_id, $meta_key, $meta_value ); return true; } /** * Filter callback: update_comment_metadata. * * @param mixed $check Filter accumulator. * @param int $object_id Comment ID. * @param string $meta_key Meta key. * @param mixed $meta_value Value. * @param mixed $prev_value Previous value (unused). * @return mixed */ public static function on_comment_update( $check, $object_id, $meta_key, $meta_value, $prev_value ) { unset( $prev_value ); if ( null !== $check ) { return $check; } if ( ! is_string( $meta_key ) || '' === $meta_key ) { return $check; } self::write( self::comment_table(), 'comment_id', (int) $object_id, $meta_key, $meta_value ); return true; } /** * Filter callback: delete_comment_metadata. * * @param mixed $check Filter accumulator. * @param int $object_id Comment ID. * @param string $meta_key Meta key. * @param mixed $meta_value Value-scoped (unused). * @param bool $delete_all Delete-all flag (unused). * @return mixed */ public static function on_comment_delete( $check, $object_id, $meta_key, $meta_value, $delete_all ) { unset( $meta_value, $delete_all ); if ( null !== $check ) { return $check; } if ( ! is_string( $meta_key ) || '' === $meta_key ) { return $check; } self::delete_row( self::comment_table(), 'comment_id', (int) $object_id, $meta_key ); return true; } // ── Internal storage helpers ────────────────────────────────────────────── /** * Read a value from a misc bucket table. * * Returns the raw stored value (string), or null when no row exists. * Caller must wrap in array for the get_*_metadata filter contract. * * @param string $table Fully-qualified table name. * @param string $id_column 'term_id' or 'comment_id'. * @param int $object_id Entity ID. * @param string $meta_key Meta key. * @return string|null */ private static function read( string $table, string $id_column, int $object_id, string $meta_key ): ?string { global $wpdb; // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $value = $wpdb->get_var( $wpdb->prepare( "SELECT meta_value FROM `{$table}` WHERE `{$id_column}` = %d AND meta_key = %s LIMIT 1", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $object_id, $meta_key ) ); // phpcs:enable return ( null === $value ) ? null : (string) $value; } /** * Write (INSERT or UPDATE) a value to a misc bucket table. * * Uses ON DUPLICATE KEY UPDATE — the (id, meta_key) primary key * means each (entity, meta_key) pair has a single canonical row. * * @param string $table Fully-qualified table name. * @param string $id_column 'term_id' or 'comment_id'. * @param int $object_id Entity ID. * @param string $meta_key Meta key. * @param mixed $meta_value Value to store. Non-scalar types are serialized. * @return void */ private static function write( string $table, string $id_column, int $object_id, string $meta_key, $meta_value ): void { global $wpdb; // Match WP convention: serialize arrays/objects, scalar values stored as-is. $serialized = is_scalar( $meta_value ) || null === $meta_value ? (string) $meta_value : maybe_serialize( $meta_value ); // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared $wpdb->query( $wpdb->prepare( "INSERT INTO `{$table}` (`{$id_column}`, meta_key, meta_value) VALUES (%d, %s, %s) ON DUPLICATE KEY UPDATE meta_value = VALUES(meta_value)", $object_id, $meta_key, $serialized ) ); // phpcs:enable } /** * Delete the row matching (object_id, meta_key) from a misc bucket table. * * @param string $table Fully-qualified table name. * @param string $id_column 'term_id' or 'comment_id'. * @param int $object_id Entity ID. * @param string $meta_key Meta key. * @return void */ private static function delete_row( string $table, string $id_column, int $object_id, string $meta_key ): void { global $wpdb; // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( $wpdb->prepare( "DELETE FROM `{$table}` WHERE `{$id_column}` = %d AND meta_key = %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $object_id, $meta_key ) ); // phpcs:enable } /** * Count rows in a misc bucket table (for admin status panel). * * @param string $entity_type 'term' or 'comment'. * @return int */ public static function count_rows( string $entity_type ): int { global $wpdb; $table = 'comment' === $entity_type ? self::comment_table() : self::term_table(); // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared $exists = (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ); if ( ! $exists ) { return 0; } return (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}`" ); // phpcs:enable } }