$columns * @return array */ public function columns( array $columns ): array { $columns['senti_roadmap_stage'] = __( 'Stage', 'senti-feedback' ); $columns['senti_roadmap_position'] = __( 'Position', 'senti-feedback' ); return $columns; } /** * @internal */ public function render_column( string $column, int $post_id ): void { if ( $column === 'senti_roadmap_stage' ) { $terms = wp_get_object_terms( $post_id, Identifiers::TAXONOMY_ROADMAP_STAGE, [ 'fields' => 'names' ] ); echo esc_html( is_wp_error( $terms ) ? '' : implode( ', ', $terms ) ); } if ( $column === 'senti_roadmap_position' ) { echo esc_html( (string) get_post_meta( $post_id, Identifiers::META_ROADMAP_POSITION, true ) ); } } /** * @internal * @param array $columns * @return array */ public function sortable_columns( array $columns ): array { $columns['senti_roadmap_position'] = 'senti_roadmap_position'; return $columns; } /** * @internal */ public function sort_collection( WP_Query $query ): void { if ( ! is_admin() || ! $query->is_main_query() || $query->get( 'post_type' ) !== Identifiers::POST_TYPE_ROADMAP || $query->get( 'orderby' ) !== 'senti_roadmap_position' ) { return; } $query->set( 'meta_key', Identifiers::META_ROADMAP_POSITION ); $query->set( 'orderby', 'meta_value_num' ); } /** * @internal */ public function add_meta_boxes(): void { remove_meta_box( 'tagsdiv-' . Identifiers::TAXONOMY_ROADMAP_STAGE, Identifiers::POST_TYPE_ROADMAP, 'side' ); add_meta_box( 'senti-roadmap-order', __( 'Roadmap placement', 'senti-feedback' ), [ $this, 'render_placement' ], Identifiers::POST_TYPE_ROADMAP, 'side' ); add_meta_box( 'senti-roadmap-feedback', __( 'Linked feedback', 'senti-feedback' ), [ $this, 'render_feedback' ], Identifiers::POST_TYPE_ROADMAP, 'side' ); } /** * @internal */ public function render_placement( WP_Post $post ): void { $stages = get_terms( [ 'taxonomy' => Identifiers::TAXONOMY_ROADMAP_STAGE, 'hide_empty' => false, 'meta_key' => StageProvisioner::position_meta_key(), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- The small administrator-defined stage collection is ordered by explicit position. 'orderby' => 'meta_value_num', 'order' => 'ASC', ] ); $current_terms = wp_get_object_terms( $post->ID, Identifiers::TAXONOMY_ROADMAP_STAGE, [ 'fields' => 'ids' ] ); $current_stage = ( ! is_wp_error( $current_terms ) && ! empty( $current_terms ) ) ? (int) $current_terms[0] : 0; $position = (int) get_post_meta( $post->ID, Identifiers::META_ROADMAP_POSITION, true ); include dirname( __DIR__, 2 ) . '/templates/admin/metaboxes/roadmap-placement.php'; } /** * @internal */ public function render_feedback( WP_Post $post ): void { $selected = $this->roadmap->feedback_ids( $post->ID ); $feedback = get_posts( [ 'post_type' => Identifiers::POST_TYPE_FEEDBACK, 'post_status' => [ 'publish', 'pending', 'draft' ], 'numberposts' => 100, 'orderby' => 'title', 'order' => 'ASC', ] ); $shown = wp_list_pluck( $feedback, 'ID' ); foreach ( array_diff( $selected, array_map( 'intval', $shown ) ) as $missing_id ) { $missing = get_post( $missing_id ); if ( $missing instanceof WP_Post ) { $feedback[] = $missing; } } include dirname( __DIR__, 2 ) . '/templates/admin/metaboxes/roadmap-feedback.php'; } /** * @internal */ public function save( int $post_id, WP_Post $post ): void { $nonce = isset( $_POST['senti_roadmap_nonce'] ) && is_string( $_POST['senti_roadmap_nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['senti_roadmap_nonce'] ) ) : ''; if ( $post->post_type !== Identifiers::POST_TYPE_ROADMAP || ! wp_verify_nonce( $nonce, 'senti_roadmap_save' ) || ! current_user_can( 'edit_post', $post_id ) || wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) { return; } $stage = isset( $_POST['senti_roadmap_stage'] ) ? absint( $_POST['senti_roadmap_stage'] ) : 0; $position = isset( $_POST['senti_roadmap_position'] ) ? absint( $_POST['senti_roadmap_position'] ) : 0; if ( $stage === 0 ) { return; } try { $this->roadmap->set_stage_and_position( $post_id, $stage, $position ); $feedback = isset( $_POST['senti_roadmap_feedback'] ) && is_array( $_POST['senti_roadmap_feedback'] ) ? array_map( 'absint', wp_unslash( $_POST['senti_roadmap_feedback'] ) ) : []; $this->roadmap->replace_feedback( $post_id, $feedback ); } catch ( RoadmapException ) { return; } } }