is_active() ) { return $check; } try { $this->sync_favorites( $user_id, $meta_value ); } catch ( \Throwable $e ) { TMDO_Logger::error( $this->module, 'update_user_metadata', $e->getMessage() ); } return $check; } /** * Deletes favorites records when a listing post is deleted. * * @param int $post_id Post ID. * @param \WP_Post $post Post object. * @return void */ public function action_delete_post( int $post_id, \WP_Post $post ): void { if ( 'hp_listing' !== $post->post_type || ! $this->is_active() ) { return; } try { global $wpdb; $wpdb->delete( TMDO_DB::table( 'hpct_favorites' ), array( 'listing_id' => $post_id ), array( '%d' ) ); } catch ( \Throwable $e ) { TMDO_Logger::error( $this->module, 'before_delete_post', $e->getMessage() ); } } /** * Syncs user favorites to the hpct_favorites table. * * @param int $user_id User ID. * @param mixed $meta_value Favorites meta value (array or serialized). * @return void */ private function sync_favorites( int $user_id, $meta_value ): void { global $wpdb; $table = TMDO_DB::table( 'hpct_favorites' ); $now = TMDO_DB::now(); // v2.13.3: object-injection-safe unserialize (fixes L-DESER-1). // Input is user-controlled wp_usermeta value via update_user_meta hook. $listing_ids = is_array( $meta_value ) ? $meta_value : (array) TMDO_Safe_Unserialize::run( $meta_value ); $listing_ids = array_filter( array_map( 'absint', $listing_ids ) ); // Delete all existing and re-insert. $wpdb->delete( $table, array( 'user_id' => $user_id ), array( '%d' ) ); $insert_sql = TMDO_IS_SQLITE ? 'INSERT OR IGNORE' : 'INSERT IGNORE'; // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- insert_sql is 'INSERT IGNORE'/'INSERT OR IGNORE'; table name from TMDO_DB::table(). foreach ( $listing_ids as $listing_id ) { $wpdb->query( $wpdb->prepare( "{$insert_sql} INTO `{$table}` (user_id, listing_id, created_at) VALUES (%d, %d, %s)", $user_id, $listing_id, $now ) ); } // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared } }