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