Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
89.80% covered (success)
89.80%
44 / 49
66.67% covered (warning)
66.67%
4 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Column
89.80% covered (success)
89.80%
44 / 49
66.67% covered (warning)
66.67%
4 / 6
16.27
0.00% covered (danger)
0.00%
0 / 1
 init
85.71% covered (success)
85.71%
12 / 14
0.00% covered (danger)
0.00%
0 / 1
4.05
 render_columns_head
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 render_columns_content
78.57% covered (warning)
78.57%
11 / 14
0.00% covered (danger)
0.00%
0 / 1
6.35
 make_column_sortable
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 set_sort_query
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 get_sort_query_args
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * BeyondWords column on the posts list screen.
4 *
5 * @package BeyondWords\PostsList
6 * @since   3.0.0
7 * @since   7.0.0 Refactored to BeyondWords namespace with snake_case methods.
8 */
9
10declare( strict_types = 1 );
11
12namespace BeyondWords\PostsList;
13
14defined( 'ABSPATH' ) || exit;
15
16/**
17 * Sortable "BeyondWords" column on compatible post-type list tables.
18 *
19 * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods.
20 */
21class Column {
22
23    const ALLOWED_HTML = [
24        'span' => [
25            'class' => [],
26        ],
27    ];
28
29    const OUTPUT_YES          = '<span class="dashicons dashicons-yes"></span> ';
30    const OUTPUT_NO           = '—';
31    const OUTPUT_DISABLED     = ' <span class="beyondwords--disabled">Disabled</span>';
32    const OUTPUT_ERROR_PREFIX = '<span class="dashicons dashicons-warning"></span> ';
33
34    /**
35     * Register WordPress hooks.
36     */
37    public static function init(): void {
38        add_action(
39            'wp_loaded',
40            static function (): void {
41                $post_types = \BeyondWords\Settings\Utils::get_compatible_post_types();
42
43                if ( ! is_array( $post_types ) ) {
44                    return;
45                }
46
47                foreach ( $post_types as $post_type ) {
48                    add_filter( "manage_{$post_type}_posts_columns", [ self::class, 'render_columns_head' ] );
49                    add_action( "manage_{$post_type}_posts_custom_column", [ self::class, 'render_columns_content' ], 10, 2 );
50                    add_filter( "manage_edit-{$post_type}_sortable_columns", [ self::class, 'make_column_sortable' ] );
51                }
52            }
53        );
54
55        if ( \BeyondWords\Core\Utils::is_edit_screen() ) {
56            add_action( 'pre_get_posts', [ self::class, 'set_sort_query' ] );
57        }
58    }
59
60    /**
61     * Append the BeyondWords column to the columns header array.
62     *
63     * @param array<string,string> $columns Existing column headers.
64     *
65     * @return array<string,string>
66     */
67    public static function render_columns_head( $columns ) {
68        return array_merge(
69            $columns,
70            [ 'beyondwords' => __( 'BeyondWords', 'speechkit' ) ]
71        );
72    }
73
74    /**
75     * Render the cell contents for the BeyondWords column.
76     *
77     * @param string $column_name Column slug being rendered.
78     * @param int    $post_id     Post ID.
79     */
80    public static function render_columns_content( $column_name, $post_id ): void {
81        if ( 'beyondwords' !== $column_name ) {
82            return;
83        }
84
85        if ( empty( \BeyondWords\Settings\Utils::get_compatible_post_types() ) ) {
86            return;
87        }
88
89        $error_message = \BeyondWords\Post\Meta::get_error_message( $post_id );
90        $has_content   = \BeyondWords\Post\Meta::has_content( $post_id );
91
92        $disabled = \BeyondWords\Editor\Components\SettingsFields::is_player_disabled_for_post( $post_id );
93
94        if ( ! empty( $error_message ) ) {
95            echo wp_kses( self::OUTPUT_ERROR_PREFIX . $error_message, self::ALLOWED_HTML );
96        } elseif ( $has_content ) {
97            echo wp_kses( self::OUTPUT_YES, self::ALLOWED_HTML );
98        } else {
99            echo wp_kses( self::OUTPUT_NO, self::ALLOWED_HTML );
100        }
101
102        if ( ! empty( $disabled ) ) {
103            echo wp_kses( self::OUTPUT_DISABLED, self::ALLOWED_HTML );
104        }
105    }
106
107    /**
108     * Mark the BeyondWords column sortable.
109     *
110     * @param array<string,string> $sortable_columns Existing sortable columns.
111     *
112     * @return array<string,string>
113     */
114    public static function make_column_sortable( $sortable_columns ) {
115        $sortable_columns['beyondwords'] = 'beyondwords';
116        return $sortable_columns;
117    }
118
119    /**
120     * Apply the BeyondWords meta-query sort when ordering by the BeyondWords column.
121     *
122     * @param \WP_Query $query Query object, modified in place.
123     *
124     * @return \WP_Query
125     */
126    public static function set_sort_query( $query ) {
127        if ( 'beyondwords' === $query->get( 'orderby' ) && $query->is_main_query() ) {
128            $query->set( 'meta_query', self::get_sort_query_args() );
129            $query->set( 'orderby', 'meta_value_num date' );
130        }
131
132        return $query;
133    }
134
135    /**
136     * Meta query that orders posts with audio metadata before posts without.
137     *
138     * Two clauses with `relation: OR` so posts that have *never* had audio
139     * still appear in the list rather than being filtered out.
140     *
141     * @return array<string,mixed>
142     */
143    public static function get_sort_query_args(): array {
144        return [
145            'relation' => 'OR',
146            [
147                'key'     => 'beyondwords_generate_audio',
148                'compare' => 'NOT EXISTS',
149            ],
150            [
151                'key'     => 'beyondwords_generate_audio',
152                'compare' => 'EXISTS',
153            ],
154        ];
155    }
156}