Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.22% covered (success)
97.22%
35 / 36
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Post_Types
97.22% covered (success)
97.22%
35 / 36
75.00% covered (warning)
75.00%
3 / 4
7
0.00% covered (danger)
0.00%
0 / 1
 get_supported_content_types
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_supported_content_type_options
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 get_supported_content_type_objects
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
2
 sort_post_type_objects
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
2.01
1<?php
2/**
3 * Supported content post type discovery for Jetpack SEO.
4 *
5 * @package automattic/jetpack-seo-package
6 */
7
8namespace Automattic\Jetpack\SEO;
9
10/**
11 * Discovers the post types Jetpack SEO can list and edit via core REST.
12 */
13class Post_Types {
14
15    /**
16     * Post types that are REST-visible but not content surfaces for SEO.
17     *
18     * @var string[]
19     */
20    const EXCLUDED_POST_TYPES = array(
21        'attachment',
22        'elementor_library',   // Elementor saved templates.
23        'et_pb_layout',        // Divi Library layouts.
24        'fl-builder-template', // Beaver Builder saved templates.
25        'fl-theme-layout',     // Beaver Themer header, footer and archive layouts.
26        'kadence_element',     // Kadence Elements, hooked header and footer pieces.
27    );
28
29    /**
30     * Return supported post type slugs.
31     *
32     * @return string[]
33     */
34    public static function get_supported_content_types() {
35        return array_keys( self::get_supported_content_type_objects() );
36    }
37
38    /**
39     * Return supported post type options for the Content tab.
40     *
41     * @return array<int,array{slug:string,label:string}>
42     */
43    public static function get_supported_content_type_options() {
44        $options = array();
45
46        foreach ( self::get_supported_content_type_objects() as $post_type ) {
47            $options[] = array(
48                'slug'  => $post_type->name,
49                'label' => wp_strip_all_tags( $post_type->label ),
50            );
51        }
52
53        return $options;
54    }
55
56    /**
57     * Return supported post type objects keyed by slug.
58     *
59     * Supported means public, UI-visible, and REST-enabled so the SEO dashboard
60     * can both display and save the post through core-data. Excluded types and
61     * types hidden from search are left out.
62     *
63     * @return \WP_Post_Type[]
64     */
65    public static function get_supported_content_type_objects() {
66        $post_types = get_post_types(
67            array(
68                'public'       => true,
69                'show_ui'      => true,
70                'show_in_rest' => true,
71            ),
72            'objects'
73        );
74
75        $hidden_from_search = wp_filter_object_list( $post_types, array( 'exclude_from_search' => true ) );
76        $hidden_from_search = array_keys( $hidden_from_search );
77        // Sites hide posts or pages from search to tidy results, so keep listing them.
78        $hidden_from_search = array_diff( $hidden_from_search, array( 'post', 'page' ) );
79
80        $excluded = array_merge( self::EXCLUDED_POST_TYPES, $hidden_from_search );
81        $excluded = array_unique( $excluded );
82
83        /**
84         * Filters the post types left out of llms.txt, the SEO Content tab, Content coverage and the SEO admin columns.
85         *
86         * @since $$next-version$$
87         *
88         * @param string[] $excluded Post type slugs to leave out. Remove a slug to include that type.
89         */
90        $excluded = (array) apply_filters( 'jetpack_seo_excluded_post_types', $excluded );
91
92        foreach ( $excluded as $post_type ) {
93            unset( $post_types[ $post_type ] );
94        }
95
96        $post_types = array_filter( $post_types, 'is_post_type_viewable' );
97
98        uasort( $post_types, array( __CLASS__, 'sort_post_type_objects' ) );
99
100        return $post_types;
101    }
102
103    /**
104     * Sort core content types first, then custom types alphabetically by label.
105     *
106     * @param \WP_Post_Type $a First post type object.
107     * @param \WP_Post_Type $b Second post type object.
108     * @return int
109     */
110    private static function sort_post_type_objects( $a, $b ) {
111        $preferred = array(
112            'post' => 0,
113            'page' => 1,
114        );
115
116        $a_rank = $preferred[ $a->name ] ?? 99;
117        $b_rank = $preferred[ $b->name ] ?? 99;
118
119        if ( $a_rank !== $b_rank ) {
120            return $a_rank - $b_rank;
121        }
122
123        return strcasecmp( $a->label, $b->label );
124    }
125}