is_post_type( $roadmap_id, Identifiers::POST_TYPE_ROADMAP ) ) { throw new RoadmapException( esc_html__( 'The roadmap entry does not exist.', 'senti-feedback' ) ); } $stage = get_term( $stage_id, Identifiers::TAXONOMY_ROADMAP_STAGE ); if ( $stage === null || is_wp_error( $stage ) ) { throw new RoadmapException( esc_html__( 'The roadmap stage does not exist.', 'senti-feedback' ) ); } wp_set_object_terms( $roadmap_id, [ $stage_id ], Identifiers::TAXONOMY_ROADMAP_STAGE, false ); update_post_meta( $roadmap_id, Identifiers::META_ROADMAP_POSITION, max( 0, $position ) ); return true; } /** * Links a feedback item to a roadmap entry. * * @throws RoadmapException When the roadmap entry or feedback item is invalid, or the relationship cannot be saved. */ public function relate_feedback( int $roadmap_id, int $feedback_id ): bool { if ( ! $this->is_post_type( $roadmap_id, Identifiers::POST_TYPE_ROADMAP ) || ! $this->is_post_type( $feedback_id, Identifiers::POST_TYPE_FEEDBACK ) ) { throw new RoadmapException( esc_html__( 'Roadmap entries can only link to existing feedback.', 'senti-feedback' ) ); } if ( ! $this->relationships->relate( $feedback_id, $roadmap_id, 'feedback_roadmap' ) ) { throw new RoadmapException( esc_html__( 'The feedback relationship could not be saved.', 'senti-feedback' ) ); } return true; } /** * Replaces all linked feedback items for a roadmap entry. * * @param int[] $feedback_ids * @throws RoadmapException When the roadmap entry or any feedback item is invalid, or saving fails. */ public function replace_feedback( int $roadmap_id, array $feedback_ids ): bool { if ( ! $this->is_post_type( $roadmap_id, Identifiers::POST_TYPE_ROADMAP ) ) { throw new RoadmapException( esc_html__( 'The roadmap entry does not exist.', 'senti-feedback' ) ); } $feedback_ids = array_values( array_unique( array_filter( array_map( 'absint', $feedback_ids ), static fn ( int $id ): bool => $id > 0 ) ) ); foreach ( $feedback_ids as $feedback_id ) { if ( ! $this->is_post_type( $feedback_id, Identifiers::POST_TYPE_FEEDBACK ) ) { throw new RoadmapException( esc_html__( 'Roadmap entries can only link to existing feedback.', 'senti-feedback' ) ); } } $this->relationships->delete_by_target( $roadmap_id, 'feedback_roadmap' ); foreach ( $feedback_ids as $feedback_id ) { $this->relate_feedback( $roadmap_id, $feedback_id ); } return true; } /** * Returns the feedback IDs linked to a roadmap entry. * * @return int[] */ public function feedback_ids( int $roadmap_id ): array { return $this->relationships->find_source_ids( $roadmap_id, 'feedback_roadmap', Identifiers::POST_TYPE_FEEDBACK ); } /** * Returns the roadmap IDs linked to a feedback item. * * @return int[] */ public function roadmap_ids( int $feedback_id ): array { return $this->relationships->find_target_ids( $feedback_id, 'feedback_roadmap', Identifiers::POST_TYPE_ROADMAP ); } private function is_post_type( int $post_id, string $post_type ): bool { return get_post_type( $post_id ) === $post_type; } }