Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
74.29% covered (warning)
74.29%
26 / 35
54.55% covered (warning)
54.55%
6 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
Cornerstone_Utils
74.29% covered (warning)
74.29%
26 / 35
54.55% covered (warning)
54.55%
6 / 11
23.51
0.00% covered (danger)
0.00%
0 / 1
 get_list
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 dedupe_by_provider_key
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
4.01
 get_custom_list
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 get_predefined_list
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_cornerstone_page_by_url
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 sanitize_url
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_provider_key
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 prepare_provider_data
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 is_current_page_cornerstone
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 is_cornerstone_page
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 maybe_trailing_slash_urls
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace Automattic\Jetpack_Boost\Lib\Cornerstone;
4
5use Automattic\Jetpack_Boost\Lib\Critical_CSS\Source_Providers\Providers\Cornerstone_Provider;
6
7class Cornerstone_Utils {
8
9    /**
10     * Get the list of cornerstone pages.
11     *
12     * @return string[] The relative URLs of all the cornerstone pages.
13     */
14    public static function get_list() {
15        /**
16         * Filters the list of cornerstone pages. This list includes the predefined and custom pages.
17         * If you want to change the list of custom pages, use `jetpack_boost_cornerstone_pages_list` instead.
18         *
19         * @since 4.4.0-beta1
20         *
21         * @param string[] $urls The absolute URLs of all the cornerstone pages.
22         */
23        $urls = apply_filters( 'jetpack_boost_cornerstone_pages_list_complete', array_merge( self::get_predefined_list(), self::get_custom_list() ) );
24
25        return self::dedupe_by_provider_key( $urls );
26    }
27
28    /**
29     * Dedupe a list of cornerstone URLs by their provider key.
30     *
31     * Different URL forms of the same page collapse to one provider key: the predefined
32     * home_url() and a "/" (or "") homepage entry in the custom list both hash to
33     * `cornerstone_d41d8cd9`. Keeping both produces two page entries with the same key, and
34     * LCP_State::set_pending_pages() only marks the first — leaving the duplicate without a
35     * `status`, which fails the lcp_state schema on write and silently stores the empty
36     * not_analyzed fallback. The first occurrence wins, so the predefined entry is kept.
37     *
38     * @param string[] $urls The URLs to dedupe.
39     * @return string[] The URLs with provider-key duplicates removed, re-indexed.
40     */
41    private static function dedupe_by_provider_key( $urls ) {
42        if ( ! is_array( $urls ) ) {
43            return array();
44        }
45
46        $seen    = array();
47        $deduped = array();
48        foreach ( $urls as $url ) {
49            $key = self::get_provider_key( $url );
50            if ( isset( $seen[ $key ] ) ) {
51                continue;
52            }
53            $seen[ $key ] = true;
54            $deduped[]    = $url;
55        }
56
57        return $deduped;
58    }
59
60    /**
61     * Gets the list of Cornerstone Pages that the user has added to the custom list.
62     *
63     * @return string[] The absolute URLs of the cornerstone pages.
64     *
65     * @since 4.2.0
66     */
67    public static function get_custom_list() {
68        $pages = jetpack_boost_ds_get( 'cornerstone_pages_list' );
69
70        // Bail early if no pages are found.
71        if ( empty( $pages ) ) {
72            return array();
73        }
74
75        return self::maybe_trailing_slash_urls( $pages );
76    }
77
78    /**
79     * Gets the list of Cornerstone Pages that the user cannot remove.
80     *
81     * @return string[] The absolute URLs of the cornerstone pages.
82     *
83     * @since 4.2.0
84     */
85    public static function get_predefined_list() {
86        return self::maybe_trailing_slash_urls( array( home_url() ) );
87    }
88
89    /**
90     * Checks if a URL is a cornerstone page.
91     *
92     * @param string $url The URL to check.
93     * @return bool True if the URL is a cornerstone page, false otherwise.
94     */
95    public static function is_cornerstone_page_by_url( $url ) {
96        $cornerstone_pages = self::get_list();
97        if ( empty( $cornerstone_pages ) ) {
98            return false;
99        }
100
101        $cornerstone_pages = array_map( 'untrailingslashit', $cornerstone_pages );
102        return in_array( self::sanitize_url( $url ), $cornerstone_pages, true );
103    }
104
105    /**
106     * Sanitize a URL to make it a compatible cornerstone page URL.
107     *
108     * @param string $url The URL to sanitize.
109     * @return string The sanitized URL.
110     */
111    public static function sanitize_url( $url ) {
112        return untrailingslashit( $url );
113    }
114
115    /**
116     * Get the provider key for a given URL.
117     *
118     * @param string $url The URL to get the provider key for.
119     * @return string The provider key.
120     */
121    public static function get_provider_key( $url ) {
122        return Cornerstone_Provider::get_provider_key( self::sanitize_url( $url ) );
123    }
124
125    /**
126     * Prepare provider data for a given URL.
127     * This is usually sent to the Cloud API.
128     *
129     * @param string $url The URL to prepare provider data for.
130     * @return array The provider data.
131     */
132    public static function prepare_provider_data( $url ) {
133        return array(
134            'key' => self::get_provider_key( $url ),
135            // Send the URL verbatim (do not sanitize/untrailingslashit it). The cloud analyzer
136            // fetches this exact URL, so it must keep the site's canonical trailing-slash form to
137            // avoid edge/WAF 301s or 403s that break analysis. The `key` is intentionally still
138            // stripped so storage/retrieval stays slash-agnostic. Do not "simplify" url back to
139            // self::sanitize_url( $url ).
140            'url' => $url,
141        );
142    }
143
144    /**
145     * Checks if the current page is a cornerstone page.
146     *
147     * @return bool True if the current page is a cornerstone page, false otherwise.
148     *
149     * @since 3.13.1
150     */
151    public static function is_current_page_cornerstone() {
152        return self::is_cornerstone_page_by_url( Cornerstone_Provider::get_request_url() );
153    }
154
155    /**
156     * Check if a post ID is a cornerstone page.
157     *
158     * @param int $post_id The ID of the post to check.
159     * @return bool True if the post is a cornerstone page, false otherwise.
160     */
161    public static function is_cornerstone_page( $post_id ) {
162        return self::is_cornerstone_page_by_url( get_permalink( $post_id ) );
163    }
164
165    /**
166     * Adds trailing slashes to URLs if the current permalink structure requires it.
167     *
168     * @param string[] $urls The URLs to process.
169     * @return string[] The processed URLs.
170     */
171    public static function maybe_trailing_slash_urls( $urls ) {
172        $permalink_structure = \get_option( 'permalink_structure' );
173
174        // If permalink structure ends with slash, add trailing slashes.
175        if ( $permalink_structure && substr( $permalink_structure, -1 ) === '/' ) {
176            $urls = array_map( 'trailingslashit', $urls );
177        }
178
179        return $urls;
180    }
181}