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