Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 144
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
Performance
0.00% covered (danger)
0.00%
0 / 144
0.00% covered (danger)
0.00%
0 / 12
992
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
20
 configureContainer
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
 hooks
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
42
 remove_epc_settings
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 nfd_asr_default
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 nfd_as_cleanup_batch_size
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 admin_bar_menu
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
30
 add_management_page
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 add_nfd_subnav
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 render_performance_app
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 initialize_performance_app
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 register_performance_assets
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace NewfoldLabs\WP\Module\Performance;
4
5use NewfoldLabs\WP\ModuleLoader\Container;
6use NewfoldLabs\WP\Module\Performance\Images\ImageManager;
7use NewfoldLabs\WP\Module\Performance\RestApi\RestApi;
8use NewfoldLabs\WP\Module\Performance\Data\Constants;
9use NewfoldLabs\WP\Module\Performance\Services\I18nService;
10use NewfoldLabs\WP\Module\Performance\LinkPrefetch\LinkPrefetch;
11use NewfoldLabs\WP\Module\Performance\Cache\Cache;
12use NewfoldLabs\WP\Module\Performance\Cache\ResponseHeaderManager;
13use NewfoldLabs\WP\Module\Performance\Cloudflare\CloudflareFeaturesManager;
14use NewfoldLabs\WP\Module\Performance\Fonts\FontManager;
15use NewfoldLabs\WP\Module\Performance\Skip404\Skip404;
16use NewfoldLabs\WP\Module\Performance\JetpackBoost\JetpackBoost;
17
18use function NewfoldLabs\WP\Module\Performance\get_cache_level;
19
20/**
21 * Main class for the performance module.
22 */
23class Performance {
24
25    /**
26     * URL parameter used to purge the entire cache.
27     *
28     * @var string
29     */
30    const PURGE_ALL = 'nfd_purge_all';
31
32    /**
33     * URL parameter used to purge the cache for a specific URL.
34     *
35     * @var string
36     */
37    const PURGE_URL = 'nfd_purge_url';
38
39    /**
40    * Slug used for the Performance module's admin page.
41    *
42    * @var string
43    */
44    const PAGE_SLUG = 'nfd-performance';
45
46    /**
47     * Dependency injection container.
48     *
49     * @var Container
50     */
51    protected $container;
52
53    /**
54     * Constructor.
55     *
56     * @param Container $container the container
57     */
58    public function __construct( Container $container ) {
59
60        $this->container = $container;
61        $this->configureContainer( $container );
62
63        $this->hooks();
64
65        new Cache( $container );
66        new Skip404( $container );
67        new PerformanceWPCLI();
68        new Constants( $container );
69        new CloudflareFeaturesManager( $container );
70        new ImageManager( $container );
71        new FontManager( $container );
72        new HealthChecks( $container );
73
74        new LinkPrefetch( $container );
75
76        new JetpackBoost( $container );
77
78        if ( Permissions::is_authorized_admin() || Permissions::rest_is_authorized_admin() ) {
79            new RestAPI();
80        }
81
82        add_action( 'admin_bar_menu', array( $this, 'admin_bar_menu' ), 100 );
83        add_action( 'admin_menu', array( $this, 'add_management_page' ) );
84        add_action( 'load-tools_page_' . self::PAGE_SLUG, array( __CLASS__, 'initialize_performance_app' ) );
85        add_filter( 'nfd_plugin_subnav', array( $this, 'add_nfd_subnav' ) );
86
87        ! defined( 'NFD_PERFORMANCE_PLUGIN_LANGUAGES_DIR' ) && define( 'NFD_PERFORMANCE_PLUGIN_LANGUAGES_DIR', dirname( $container->plugin()->file ) . '/vendor/newfold-labs/wp-module-performance/languages' );
88        new I18nService( $container );
89    }
90
91    /**
92     * Constructor.
93     *
94     * @param Container $container the container.
95     */
96    public function configureContainer( Container $container ) {
97
98        $is_apache = false;
99
100        // Ensure $is_apache is properly set, with a fallback for WP-CLI environment
101        if ( NFD_WPCLI::is_executing_wp_cli() ) {
102            // Attempt to detect Apache based on the SERVER_SOFTWARE header
103            $is_apache = isset( $_SERVER['SERVER_SOFTWARE'] ) && stripos( $_SERVER['SERVER_SOFTWARE'], 'apache' ) !== false;
104
105            // Check for the existence of an .htaccess file (commonly used in Apache environments)
106            if ( ! $is_apache && file_exists( ABSPATH . '.htaccess' ) ) {
107                $is_apache = true;
108            }
109        } else {
110            global $is_apache;
111        }
112
113        $container->set( 'isApache', $is_apache );
114
115        $container->set(
116            'responseHeaderManager',
117            $container->service(
118                function () {
119                    return new ResponseHeaderManager();
120                }
121            )
122        );
123    }
124
125    /**
126     * Add hooks.
127     */
128    public function hooks() {
129
130        add_action( 'admin_init', array( $this, 'remove_epc_settings' ), 99 );
131
132        /**
133         * On CLI requests, mod_rewrite is unavailable, so it fails to update
134         * the .htaccess file when save_mod_rewrite_rules() is called. This
135         * forces that to be true so updates from WP CLI work.
136         */
137        if ( defined( 'WP_CLI' ) && WP_CLI ) {
138            add_filter( 'got_rewrite', '__return_true' );
139        }
140
141        if (
142            isset( $_REQUEST['action'], $_REQUEST['plugin'] ) &&
143            'activate' === $_REQUEST['action'] &&
144            $_REQUEST['plugin'] === $this->container->plugin()->basename
145        ) {
146            add_filter(
147                'mod_rewrite_rules',
148                function ( $content ) {
149                    add_action(
150                        'shutdown',
151                        function () {
152                            do_action( 'newfold_update_htaccess' );
153                        }
154                    );
155                    return $content;
156                }
157            );
158        }
159
160        add_filter( 'action_scheduler_retention_period', array( $this, 'nfd_asr_default' ) );
161        add_filter( 'action_scheduler_cleanup_batch_size', array( $this, 'nfd_as_cleanup_batch_size' ) );
162    }
163
164    /**
165     * Remove EPC Settings if needed
166     *
167     * @return void
168     */
169    public function remove_epc_settings() {
170        global $wp_settings_fields, $wp_settings_sections;
171        //phpcs:ignore
172        // Remove the setting from EPC if it exists - TODO: Remove when no longer using EPC
173        if ( $this->container->get( 'hasMustUsePlugin' ) ) {
174            unset( $wp_settings_fields['general']['epc_settings_section'] );
175            unset( $wp_settings_sections['general']['epc_settings_section'] );
176            unregister_setting( 'general', 'endurance_cache_level' );
177            unregister_setting( 'general', 'epc_skip_404_handling' );
178        }
179    }
180
181    /**
182     * Update the default action scheduler retention period to 5 days instead of 30.
183     * The actions scheduler table tends to grow to gigantic sizes and this should help.
184     *
185     * @hooked action_scheduler_retention_period
186     * @see ActionScheduler_QueueCleaner::delete_old_actions()
187     *
188     * @return int New retention period in seconds.
189     */
190    public function nfd_asr_default() {
191        return 5 * constant( 'DAY_IN_SECONDS' );
192    }
193
194    /**
195     * Increase the batch size for the cleanup process from default of 20 to 1000.
196     *
197     * @hooked action_scheduler_cleanup_batch_size
198     * @see ActionScheduler_QueueCleaner::get_batch_size()
199     *
200     * @param int $batch_size Existing batch size; default is 20.
201     *
202     * @return int 1000 when running the cleanup process, otherwise the existing batch size.
203     */
204    public function nfd_as_cleanup_batch_size( $batch_size ) {
205        /**
206         * Apply only to {@see ActionScheduler_QueueCleaner::delete_old_actions()} and not to
207         * {@see ActionScheduler_QueueCleaner::reset_timeouts()} or
208         * {@see ActionScheduler_QueueCleaner::mark_failures()} batch sizes.
209         */
210        if ( ! did_filter( 'action_scheduler_retention_period' ) ) {
211            return $batch_size;
212        }
213
214        return 1000;
215    }
216
217    /**
218     * Add options to the WordPress admin bar.
219     *
220     * @param \WP_Admin_Bar $wp_admin_bar the admin bar.
221     */
222    public function admin_bar_menu( \WP_Admin_Bar $wp_admin_bar ) {
223
224        // If the EPC MU plugin exists, remove its cache clearing options.
225        if ( $this->container->get( 'hasMustUsePlugin' ) ) {
226            $wp_admin_bar->remove_node( 'epc_purge_menu' );
227        }
228
229        if ( current_user_can( 'manage_options' ) ) {
230            $wp_admin_bar->add_node(
231                array(
232                    'id'    => 'nfd_purge_menu',
233                    'title' => __( 'Caching', 'wp-module-performance' ),
234                )
235            );
236
237            $cache_level = get_cache_level();
238            if ( $cache_level > 0 ) {
239                $wp_admin_bar->add_node(
240                    array(
241                        'id'     => 'nfd_purge_menu-purge_all',
242                        'title'  => __( 'Purge All', 'wp-module-performance' ),
243                        'parent' => 'nfd_purge_menu',
244                        'href'   => apply_filters( 'nfd_build_url', add_query_arg( array( self::PURGE_ALL => true ) ) ),
245                    )
246                );
247
248                if ( ! is_admin() ) {
249                    $wp_admin_bar->add_node(
250                        array(
251                            'id'     => 'nfd_purge_menu-purge_single',
252                            'title'  => __( 'Purge This Page', 'wp-module-performance' ),
253                            'parent' => 'nfd_purge_menu',
254                            'href'   => apply_filters( 'nfd_build_url', add_query_arg( array( self::PURGE_URL => true ) ) ),
255                        )
256                    );
257                }
258            }
259
260            $wp_admin_bar->add_node(
261                array(
262                    'id'     => 'nfd_purge_menu-cache_settings',
263                    'title'  => __( 'Cache Settings', 'wp-module-performance' ),
264                    'parent' => 'nfd_purge_menu',
265                    'href'   => apply_filters( 'nfd_build_url', admin_url( 'tools.php?page=' . self::PAGE_SLUG ) ),
266                )
267            );
268        }
269    }
270
271    /**
272     * Adds the Performance module to the WordPress Tools > Site Health menu.
273     *
274     * @return void
275     */
276    public function add_management_page() {
277
278        add_management_page(
279            __( 'Performance', 'wp-module-performance' ),
280            __( 'Performance', 'wp-module-performance' ),
281            'manage_options',
282            self::PAGE_SLUG,
283            array( __CLASS__, 'render_performance_app' )
284        );
285    }
286
287    /**
288     * Add to the Newfold subnav.
289     *
290     * @param array $subnav The nav array.
291     * @return array The filtered nav array
292     */
293    public function add_nfd_subnav( $subnav ) {
294        $performance = array(
295            'route'    => self::PAGE_SLUG,
296            'title'    => __( 'Performance', 'wp-module-performance' ),
297            'priority' => 30,
298        );
299        array_push( $subnav, $performance );
300        return $subnav;
301    }
302
303    /**
304     * Outputs the HTML container for the Performance module's React application.
305     *
306     * @return void
307     */
308    public static function render_performance_app() {
309        echo PHP_EOL;
310        echo '<!-- NFD:PERFORMANCE -->';
311        echo PHP_EOL;
312        echo '<div id="' . esc_attr( self::PAGE_SLUG ) . '" class="' . esc_attr( self::PAGE_SLUG ) . '-container"></div>';
313        echo PHP_EOL;
314        echo '<!-- /NFD:PERFORMANCE -->';
315        echo PHP_EOL;
316    }
317
318    /**
319     * Initializes the Performance module by registering and enqueuing its assets.
320     *
321     * @return void
322     */
323    public static function initialize_performance_app() {
324        self::register_performance_assets();
325    }
326
327    /**
328     * Registers and enqueues the JavaScript and CSS assets for the Performance module.
329     *
330     * @return void
331     */
332    public static function register_performance_assets() {
333        $build_dir  = NFD_PERFORMANCE_BUILD_DIR;
334        $build_url  = NFD_PERFORMANCE_BUILD_URL;
335        $asset_file = $build_dir . '/performance/performance.min.asset.php';
336
337        if ( is_readable( $asset_file ) ) {
338            $asset = include_once $asset_file;
339
340            wp_register_script(
341                self::PAGE_SLUG,
342                $build_url . '/performance/performance.min.js',
343                $asset['dependencies'],
344                $asset['version'],
345                true
346            );
347
348            wp_register_style(
349                self::PAGE_SLUG,
350                $build_url . '/performance/performance.min.css',
351                array(),
352                $asset['version']
353            );
354
355            wp_enqueue_script( self::PAGE_SLUG );
356            wp_enqueue_style( self::PAGE_SLUG );
357        }
358    }
359}