Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
21.05% covered (danger)
21.05%
12 / 57
30.00% covered (danger)
30.00%
3 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
Boost_Cache_Settings
21.05% covered (danger)
21.05%
12 / 57
30.00% covered (danger)
30.00%
3 / 10
413.77
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
 get_instance
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 create_settings_file
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 log_init_error
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
 init_settings
18.75% covered (danger)
18.75%
3 / 16
0.00% covered (danger)
0.00%
0 / 1
33.28
 get
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 get_enabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_bypass_patterns
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_logging
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 set
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2/*
3 * This file may be called before WordPress is fully initialized. See the README file for info.
4 */
5
6namespace Automattic\Jetpack_Boost\Modules\Optimizations\Page_Cache\Pre_WordPress;
7
8/**
9 * Cache settings class.
10 * Settings are stored in a file in the boost-cache directory.
11 */
12class Boost_Cache_Settings {
13    private static $instance = null;
14    private $settings        = array();
15    private $config_file_path;
16    private $config_file;
17
18    /**
19     * An uninitialized config holds these settings.
20     *
21     * @var array
22     */
23    private $default_settings = array(
24        'enabled'         => false,
25        'bypass_patterns' => array(),
26        'logging'         => false,
27    );
28
29    private function __construct() {
30        $this->config_file_path = WP_CONTENT_DIR . '/boost-cache/';
31        $this->config_file      = $this->config_file_path . 'config.php';
32    }
33
34    /**
35     * Gets the instance of the class.
36     *
37     * @return Boost_Cache_Settings The instance of the class.
38     */
39    public static function get_instance() {
40        if ( self::$instance === null ) {
41            self::$instance = new Boost_Cache_Settings();
42            self::$instance->init_settings();
43        }
44
45        return self::$instance;
46    }
47
48    /**
49     * Ensure a settings file exists, if one isn't there already.
50     *
51     * @return Boost_Cache_Error|bool - True if it was changed, or a Boost_Cache_Error on failure, false if it was already created.
52     */
53    public function create_settings_file() {
54        if ( file_exists( $this->config_file ) ) {
55            return false;
56        }
57
58        if ( ! file_exists( $this->config_file_path ) ) {
59            if ( ! Filesystem_Utils::create_directory( $this->config_file_path ) ) {
60                return new Boost_Cache_Error( 'failed-settings-write', 'Failed to create settings directory at ' . $this->config_file_path );
61            }
62        }
63
64        $write_result = $this->set( $this->default_settings );
65        if ( $write_result instanceof Boost_Cache_Error ) {
66            return $write_result;
67        }
68
69        return true;
70    }
71
72    /**
73     * If an error occurs while reading the options, it will be impossible to ever log this to the Boost Cache logs.
74     * So, if WP_DEBUG is enabled write it to the error_log instead.
75     *
76     * @param string $message - The message to log.
77     */
78    private function log_init_error( $message ) {
79        if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
80            error_log( $message ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
81        }
82    }
83
84    /**
85     * Load the settings from the config file, if available. Falls back to defaults if not.
86     */
87    private function init_settings() {
88        $this->settings = $this->default_settings;
89
90        // If no settings file exists yet, don't try to create one until we are writing a value.
91        if ( ! file_exists( $this->config_file ) ) {
92            return;
93        }
94
95        $lines = @file( $this->config_file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
96        if ( empty( $lines ) || count( $lines ) < 4 ) {
97            $this->log_init_error( 'Invalid config file at ' . $this->config_file );
98            return;
99        }
100
101        $file_settings = null;
102        foreach ( $lines as $line ) {
103            if ( strpos( $line, '{' ) !== false ) {
104                $file_settings = json_decode( $line, true );
105                break;
106            }
107        }
108
109        if ( ! is_array( $file_settings ) ) {
110            $this->log_init_error( 'Invalid config file at ' . $this->config_file );
111            return false;
112        }
113
114        $this->settings = $file_settings;
115    }
116
117    /**
118     * Returns the value of the given setting.
119     *
120     * @param string $setting - The setting to get.
121     * @return mixed - The value of the setting, or the default if the setting does not exist.
122     */
123    public function get( $setting, $default = false ) {
124        if ( ! isset( $this->settings[ $setting ] ) ) {
125            return $default;
126        }
127
128        return $this->settings[ $setting ];
129    }
130
131    /**
132     * Returns true if the cache is enabled.
133     *
134     * @return bool
135     */
136    public function get_enabled() {
137        return $this->get( 'enabled', false );
138    }
139
140    /**
141     * Returns an array of URLs that should not be cached.
142     *
143     * @return array
144     */
145    public function get_bypass_patterns() {
146        return $this->get( 'bypass_patterns', array() );
147    }
148
149    /**
150     * Returns whether logging is enabled or not.
151     *
152     * @return bool
153     */
154    public function get_logging() {
155        return $this->get( 'logging', false );
156    }
157
158    /**
159     * Sets the given settings, and saves them to the config file.
160     *
161     * @param array $settings - The settings to set in a key => value associative
162     * array. This will be merged with the existing settings.
163     * Example:
164     * $result = $this->set( array( 'enabled' => true ) );
165     *
166     * @return Boost_Cache_Error|true - true if the settings were saved, Boost_Cache_Error otherwise.
167     */
168    public function set( $settings ) {
169        // If the settings file does not exist, attempt to create one.
170        if ( ! file_exists( $this->config_file_path ) ) {
171            $result = $this->create_settings_file();
172            if ( $result instanceof Boost_Cache_Error ) {
173                return $result;
174            }
175        }
176
177        if ( ! is_writable( $this->config_file_path ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable
178            $error = new Boost_Cache_Error( 'failed-settings-write', 'Could not write to the config file at ' . $this->config_file_path );
179            Logger::debug( $error->get_error_message() );
180            return $error;
181        }
182
183        $this->settings = array_merge( $this->settings, $settings );
184
185        $contents = "<?php die();\n/*\n * Configuration data for Jetpack Boost Cache. Do not edit.\n" . json_encode( // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
186            $this->settings,
187            JSON_HEX_TAG // Need to escape slashes because this is, for some reason, going into a PHP comment and we need to guard against `*/`.
188        ) . "\n */";
189        $result   = Filesystem_Utils::write_to_file( $this->config_file, $contents );
190        if ( $result instanceof Boost_Cache_Error ) {
191            Logger::debug( $result->get_error_message() );
192            return new Boost_Cache_Error( 'failed-settings-write', 'Failed to write settings file: ' . $result->get_error_message() );
193        }
194
195        return true;
196    }
197}