Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
CloudflareFeaturesManager
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 5
156
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 on_image_optimization_change
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 on_fonts_optimization_change
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 on_site_capabilities_change
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 update_htaccess_header
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2/**
3 * CloudflareFeaturesManager
4 *
5 * Tracks Cloudflare-related optimization toggles (Polish, Mirage, Fonts) and
6 * manages a single .htaccess fragment that sets a sticky cookie indicating the
7 * current optimization tuple. Uses the centralized HtaccessApi to ensure safe,
8 * debounced writes and conflict-free composition with other fragments.
9 *
10 * @package NewfoldLabs\WP\Module\Performance\Cloudflare
11 * @since 1.0.0
12 */
13
14namespace NewfoldLabs\WP\Module\Performance\Cloudflare;
15
16use NewfoldLabs\WP\Module\Performance\Fonts\FontSettings;
17use NewfoldLabs\WP\Module\Performance\Images\ImageSettings;
18use NewfoldLabs\WP\Module\Htaccess\Api as HtaccessApi;
19use NewfoldLabs\WP\Module\Performance\Cloudflare\Fragments\CloudflareOptimizationHeaderFragment;
20
21/**
22 * Handles detection and tracking of Cloudflare Polish, Mirage, and Font Optimization.
23 *
24 * Listens to option/transient changes and registers/unregisters a single
25 * fragment that sets a cookie reflecting which CF features are enabled.
26 *
27 * @since 1.0.0
28 */
29class CloudflareFeaturesManager {
30
31    /**
32     * Human-friendly marker printed in BEGIN/END comments in .htaccess.
33     *
34     * @var string
35     */
36    private const MARKER = 'Newfold CF Optimization Header';
37
38    /**
39     * Globally-unique identifier for the Cloudflare optimization header fragment.
40     *
41     * @var string
42     */
43    private const FRAGMENT_ID = 'nfd.cloudflare.optimization.header';
44
45    /**
46     * Constructor to register hooks for settings changes.
47     *
48     * @since 1.0.0
49     */
50    public function __construct() {
51        add_action( 'update_option_nfd_image_optimization', array( $this, 'on_image_optimization_change' ), 10, 2 );
52        add_action( 'add_option_nfd_image_optimization', array( $this, 'on_image_optimization_change' ), 10, 2 );
53        add_action( 'update_option_nfd_fonts_optimization', array( $this, 'on_fonts_optimization_change' ), 10, 2 );
54        add_action( 'add_option_nfd_fonts_optimization', array( $this, 'on_fonts_optimization_change' ), 10, 2 );
55        add_action( 'set_transient_nfd_site_capabilities', array( $this, 'on_site_capabilities_change' ), 10, 2 );
56    }
57
58    /**
59     * Handles image optimization setting changes.
60     *
61     * @since 1.0.0
62     *
63     * @param mixed $old_value Previous value.
64     * @param mixed $new_value New value.
65     * @return void
66     */
67    public function on_image_optimization_change( $old_value, $new_value ): void {
68        $this->update_htaccess_header( $new_value, get_option( 'nfd_fonts_optimization', array() ) );
69    }
70
71    /**
72     * Handles font optimization setting changes.
73     *
74     * @since 1.0.0
75     *
76     * @param mixed $old_value Previous value.
77     * @param mixed $new_value New value.
78     * @return void
79     */
80    public function on_fonts_optimization_change( $old_value, $new_value ): void {
81        $this->update_htaccess_header( get_option( 'nfd_image_optimization', array() ), $new_value );
82    }
83
84    /**
85     * Callback for when the `nfd_site_capabilities` transient is set.
86     *
87     * Triggers a refresh of image and font optimization settings based on updated site capabilities.
88     *
89     * @since 1.0.0
90     *
91     * @param mixed $value      The value being set in the transient.
92     * @return void
93     */
94    public function on_site_capabilities_change( $value ): void {
95        if ( is_array( $value ) ) {
96            ImageSettings::maybe_refresh_with_capabilities( $value );
97            FontSettings::maybe_refresh_with_capabilities( $value );
98            $this->update_htaccess_header( get_option( 'nfd_image_optimization', array() ), get_option( 'nfd_fonts_optimization', array() ) );
99        }
100    }
101
102    /**
103     * Ensure the fragment reflects current optimization state.
104     *
105     * If no CF features are enabled, the fragment is unregistered. Otherwise,
106     * a deterministic cookie value is computed and a fragment is registered
107     * that sets the cookie when absent/mismatched.
108     *
109     * @since 1.0.0
110     *
111     * @param array $image_settings Array-like image optimization settings.
112     * @param array $fonts_settings Array-like font optimization settings.
113     * @return void
114     */
115    private function update_htaccess_header( $image_settings, $fonts_settings ): void {
116        $images_cloudflare = isset( $image_settings['cloudflare'] ) ? (array) $image_settings['cloudflare'] : array();
117        $fonts_cloudflare  = isset( $fonts_settings['cloudflare'] ) ? (array) $fonts_settings['cloudflare'] : array();
118
119        $mirage_enabled     = ! empty( $images_cloudflare['mirage']['value'] );
120        $polish_enabled     = ! empty( $images_cloudflare['polish']['value'] );
121        $fonts_enabled_flag = ! empty( $fonts_cloudflare['fonts']['value'] );
122
123        // Build a short, stable header/cookie value for the enabled set.
124        $mirage_hash  = $mirage_enabled ? substr( sha1( 'mirage' ), 0, 8 ) : '';
125        $polish_hash  = $polish_enabled ? substr( sha1( 'polish' ), 0, 8 ) : '';
126        $fonts_hash   = $fonts_enabled_flag ? substr( sha1( 'fonts' ), 0, 8 ) : '';
127        $header_value = "{$mirage_hash}{$polish_hash}{$fonts_hash}";
128
129        // Remove any existing fragment first to avoid duplicates.
130        HtaccessApi::unregister( self::FRAGMENT_ID );
131
132        // If no features enabled, return.
133        if ( '' === $header_value ) {
134            return;
135        }
136
137        // Register/replace the fragment with the current header value.
138        HtaccessApi::register(
139            new CloudflareOptimizationHeaderFragment(
140                self::FRAGMENT_ID,
141                self::MARKER,
142                $header_value
143            ),
144            true // queue apply to coalesce writes
145        );
146    }
147}