Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
ExistingImageRedirectFragment
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 7
56
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 id
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 priority
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 exclusive
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 is_enabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 render
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
2
 patches
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * ExistingImageRedirectFragment
4 *
5 * Renders .htaccess rules that prefer serving a `.webp` variant when both the
6 * original image and the `.webp` version exist on disk.
7 * Example: If both `/images/hero.jpg` and `/images/hero.webp` exist, rewrite
8 * to serve `/images/hero.webp`.
9 *
10 * Uses the centralized Htaccess fragment system to ensure single, debounced writes.
11 *
12 * @package NewfoldLabs\WP\Module\Performance\Images\Fragments
13 * @since 1.0.0
14 */
15
16namespace NewfoldLabs\WP\Module\Performance\Images\Fragments;
17
18use NewfoldLabs\WP\Module\Htaccess\Fragment;
19use NewfoldLabs\WP\Module\Htaccess\Context;
20
21/**
22 * Fragment: WebP Existing Image Redirect
23 *
24 * If the requested original image exists and a `.webp` variant also exists,
25 * this fragment rewrites to the `.webp` file (prefer optimized).
26 *
27 * @since 1.0.0
28 */
29final class ExistingImageRedirectFragment implements Fragment {
30
31    /**
32     * Globally-unique fragment identifier used by the registry.
33     *
34     * @var string
35     */
36    private $id;
37
38    /**
39     * Human-friendly marker text printed in BEGIN/END comments.
40     *
41     * @var string
42     */
43    private $marker_label;
44
45    /**
46     * Constructor.
47     *
48     * @since 1.0.0
49     *
50     * @param string $id           Unique fragment ID used by the registry.
51     * @param string $marker_label Marker label shown in BEGIN/END comments.
52     */
53    public function __construct( $id, $marker_label ) {
54        $this->id           = (string) $id;
55        $this->marker_label = (string) $marker_label;
56    }
57
58    /**
59     * Get the unique fragment ID.
60     *
61     * @since 1.0.0
62     *
63     * @return string Fragment identifier.
64     */
65    public function id() {
66        return $this->id;
67    }
68
69    /**
70     * Get the execution priority relative to other fragments.
71     *
72     * Runs after the core WordPress rules.
73     *
74     * @since 1.0.0
75     *
76     * @return int Priority constant.
77     */
78    public function priority() {
79        return self::PRIORITY_POST_WP;
80    }
81
82    /**
83     * Whether this fragment is exclusive (only a single instance may render).
84     *
85     * @since 1.0.0
86     *
87     * @return bool True if exclusive.
88     */
89    public function exclusive() {
90        return true;
91    }
92
93    /**
94     * Whether this fragment is enabled for the given context.
95     *
96     * Upper-layer logic (register/unregister) controls enablement; once
97     * instantiated, this always returns true.
98     *
99     * @since 1.0.0
100     *
101     * @param Context $context Context snapshot (unused).
102     * @return bool True when enabled.
103     */
104    public function is_enabled( $context ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
105        return true;
106    }
107
108    /**
109     * Render the .htaccess rules for existing-image → prefer .webp redirects.
110     *
111     * @since 1.0.0
112     *
113     * @param Context $context Context snapshot (unused).
114     * @return string Rendered fragment including BEGIN/END comments.
115     */
116    public function render( $context ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
117        $lines   = array();
118        $lines[] = '# BEGIN ' . $this->marker_label;
119        $lines[] = '<IfModule mod_rewrite.c>';
120        $lines[] = "\tRewriteEngine On";
121        $lines[] = "\tRewriteCond %{REQUEST_FILENAME} -f";
122        $lines[] = "\tRewriteCond %{REQUEST_URI} (.+)\\.(gif|bmp|jpg|jpeg|png|tiff|svg|webp)$ [NC]";
123        $lines[] = "\tRewriteCond %{DOCUMENT_ROOT}%1.webp -f";
124        $lines[] = "\tRewriteRule ^(.+)\\.(gif|bmp|jpg|jpeg|png|tiff|svg|webp)$ $1.webp [T=image/webp,E=WEBP_REDIRECT:1,L]";
125        $lines[] = '</IfModule>';
126        $lines[] = '# END ' . $this->marker_label;
127
128        return implode( "\n", $lines );
129    }
130
131    /**
132     * Optional regex patches (none for this fragment).
133     *
134     * @param Context $context Context snapshot (unused).
135     * @return array
136     */
137    public function patches( $context ) {
138        return array();
139    }
140}