Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
88.89% covered (warning)
88.89%
48 / 54
75.00% covered (warning)
75.00%
6 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Waf_Standalone_Bootstrap
88.89% covered (warning)
88.89%
48 / 54
75.00% covered (warning)
75.00%
6 / 8
26.93
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 guard_against_missing_abspath
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 initialize_constants
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 initialize_filesystem
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 locate_autoloader_file
72.22% covered (warning)
72.22%
13 / 18
0.00% covered (danger)
0.00%
0 / 1
12.14
 get_bootstrap_file_path
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_entrypoint
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 generate
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
7
1<?php
2/**
3 * Handles generation and deletion of the bootstrap for the standalone WAF mode.
4 *
5 * @package automattic/jetpack-waf
6 */
7
8namespace Automattic\Jetpack\Waf;
9
10use Composer\InstalledVersions;
11
12/**
13 * Handles the bootstrap.
14 *
15 * @phan-constructor-used-for-side-effects
16 */
17class Waf_Standalone_Bootstrap {
18
19    /**
20     * Ensures that constants are initialized if this class is used.
21     *
22     * @return void
23     */
24    public function __construct() {
25        $this->guard_against_missing_abspath();
26        $this->initialize_constants();
27    }
28
29    /**
30     * Ensures that this class is not used unless we are in the right context.
31     *
32     * @throws Waf_Exception If we are outside of WordPress.
33     *
34     * @return void
35     */
36    private function guard_against_missing_abspath() {
37
38        if ( ! defined( 'ABSPATH' ) ) {
39            throw new Waf_Exception( 'Cannot generate the WAF bootstrap if we are not running in WordPress context.' );
40        }
41    }
42
43    /**
44     * Initializes the constants required for generating the bootstrap, if they have not been initialized yet.
45     *
46     * @return void
47     */
48    private function initialize_constants() {
49        Waf_Constants::initialize_constants();
50    }
51
52    /**
53     * Initialized the WP filesystem and serves as a mocking hook for tests.
54     *
55     * Should only be implemented after the wp_loaded action hook:
56     *
57     * @link https://developer.wordpress.org/reference/functions/wp_filesystem/#more-information
58     *
59     * @return void
60     */
61    protected function initialize_filesystem() {
62        if ( ! function_exists( '\\WP_Filesystem' ) ) {
63            require_once ABSPATH . 'wp-admin/includes/file.php';
64        }
65
66        WP_Filesystem();
67    }
68
69    /**
70     * Finds the path to the autoloader, which can then be used to require the autoloader in the generated boostrap file.
71     *
72     * @throws Waf_Exception In case the autoloader file cannot be found.
73     *
74     * @return string|null
75     */
76    private function locate_autoloader_file() {
77        global $jetpack_autoloader_loader;
78
79        $autoload_file = null;
80
81        // Try the Jetpack autoloader.
82        if ( isset( $jetpack_autoloader_loader ) ) {
83            $class_file = $jetpack_autoloader_loader->find_class_file( Waf_Runner::class );
84            if ( $class_file ) {
85                $autoload_file = dirname( $class_file, 5 ) . '/vendor/autoload.php';
86            }
87        }
88
89        // Try Composer's autoloader.
90        if ( null === $autoload_file
91            && is_callable( array( InstalledVersions::class, 'getInstallPath' ) )
92            && InstalledVersions::isInstalled( 'automattic/jetpack-waf' )
93        ) {
94            $package_file = InstalledVersions::getInstallPath( 'automattic/jetpack-waf' );
95            if ( substr( $package_file, -23 ) === '/automattic/jetpack-waf' ) {
96                $autoload_file = dirname( $package_file, 3 ) . '/vendor/autoload.php';
97            }
98        }
99
100        // Guess. First look for being in a `vendor/automattic/jetpack-waf/src/', then see if we're standalone with our own vendor dir.
101        if ( null === $autoload_file ) {
102            $autoload_file = dirname( __DIR__, 4 ) . '/vendor/autoload.php';
103            if ( ! file_exists( $autoload_file ) ) {
104                $autoload_file = dirname( __DIR__ ) . '/vendor/autoload.php';
105            }
106        }
107
108        // Check that the determined file actually exists.
109        if ( ! file_exists( $autoload_file ) ) {
110            throw new Waf_Exception( 'Cannot find autoloader, and the WAF standalone boostrap will not work without it.' );
111        }
112
113        return $autoload_file;
114    }
115
116    /**
117     * Gets the path to the bootstrap.php file.
118     *
119     * @return string The bootstrap.php file path.
120     */
121    public function get_bootstrap_file_path() {
122        return trailingslashit( JETPACK_WAF_DIR ) . 'bootstrap.php';
123    }
124
125    /**
126     * Gets the entrypoint file.
127     *
128     * @return string The entrypoint file.
129     */
130    private function get_entrypoint() {
131        return defined( 'JETPACK_WAF_ENTRYPOINT' ) ? JETPACK_WAF_ENTRYPOINT : 'rules/rules.php';
132    }
133
134    /**
135     * Generates the bootstrap file.
136     *
137     * @throws File_System_Exception If the filesystem is not available.
138     * @throws File_System_Exception If the WAF directory cannot be created.
139     * @throws File_System_Exception If the bootstrap file cannot be created.
140     *
141     * @return string Absolute path to the bootstrap file.
142     */
143    public function generate() {
144
145        $this->initialize_filesystem();
146
147        global $wp_filesystem;
148        if ( ! $wp_filesystem ) {
149            throw new File_System_Exception( 'Cannot work without the file system being initialized.' );
150        }
151
152        $autoloader_file = $this->locate_autoloader_file();
153
154        $bootstrap_file          = $this->get_bootstrap_file_path();
155        $entrypoint              = $this->get_entrypoint();
156        $mode_option             = get_option( Waf_Runner::MODE_OPTION_NAME, false );
157        $share_data_option       = get_option( Waf_Runner::SHARE_DATA_OPTION_NAME, false );
158        $share_debug_data_option = get_option( Waf_Runner::SHARE_DEBUG_DATA_OPTION_NAME, false );
159
160        // phpcs:disable WordPress.PHP.DevelopmentFunctions
161        $code = "<?php\n"
162            . sprintf( "define( 'DISABLE_JETPACK_WAF', %s );\n", var_export( defined( 'DISABLE_JETPACK_WAF' ) && DISABLE_JETPACK_WAF, true ) )
163            . "if ( defined( 'DISABLE_JETPACK_WAF' ) && DISABLE_JETPACK_WAF ) return;\n"
164            . sprintf( "define( 'JETPACK_WAF_MODE', %s );\n", var_export( $mode_option ? $mode_option : 'silent', true ) )
165            . sprintf( "define( 'JETPACK_WAF_SHARE_DATA', %s );\n", var_export( $share_data_option, true ) )
166            . sprintf( "define( 'JETPACK_WAF_SHARE_DEBUG_DATA', %s );\n", var_export( $share_debug_data_option, true ) )
167            . sprintf( "define( 'JETPACK_WAF_DIR', %s );\n", var_export( JETPACK_WAF_DIR, true ) )
168            . sprintf( "define( 'JETPACK_WAF_WPCONFIG', %s );\n", var_export( JETPACK_WAF_WPCONFIG, true ) )
169            . sprintf( "define( 'JETPACK_WAF_ENTRYPOINT', %s );\n", var_export( $entrypoint, true ) )
170            . 'require_once ' . var_export( $autoloader_file, true ) . ";\n"
171            . "Automattic\Jetpack\Waf\Waf_Runner::initialize();\n";
172        // phpcs:enable
173
174        if ( ! $wp_filesystem->is_dir( JETPACK_WAF_DIR ) ) {
175            if ( ! $wp_filesystem->mkdir( JETPACK_WAF_DIR ) ) {
176                throw new File_System_Exception( 'Failed creating WAF standalone bootstrap file directory: ' . JETPACK_WAF_DIR );
177            }
178        }
179
180        if ( ! $wp_filesystem->put_contents( $bootstrap_file, $code ) ) {
181            throw new File_System_Exception( 'Failed writing WAF standalone bootstrap file to: ' . $bootstrap_file );
182        }
183
184        return $bootstrap_file;
185    }
186}