PluginProbe
Plausible Analytics / trunk
Plausible Analytics vtrunk
2.6.1 2.6.0 trunk 1.0.0 1.0.1 1.1.0 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 All 52 releases
plausible-analytics / src / Admin / Module.php

Module.php in Plausible Analytics trunk, at src/Admin/Module.php

287 lines 7.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plausible Analytics | Module.
4 *
5 * @since 1.3.0
6 * @package WordPress
7 * @subpackage Plausible Analytics
8 */
9
10 namespace Plausible\Analytics\WP\Admin;
11
12 use Exception;
13 use Plausible\Analytics\WP\Helpers;
14 use Plausible\Analytics\WP\Proxy;
15
16 class Module {
17 /**
18 * Build properties.
19 *
20 * @return void
21 */
22 public function __construct() {
23 $this->init();
24 }
25
26 /**
27 * Filters & Actions.
28 *
29 * @return void
30 */
31 private function init() {
32 add_action( 'update_option_plausible_analytics_settings', [ $this, 'maybe_install_module' ], 9, 2 );
33 add_filter( 'pre_update_option_plausible_analytics_settings', [ $this, 'maybe_enable_proxy' ], 10, 2 );
34 }
35
36 /**
37 * Decide whether we should install the module, or not.
38 *
39 * @param array $settings Current settings, already written to the DB.
40 *
41 * @return void
42 * @since 1.3.0
43 *
44 */
45 public function maybe_install_module( $old_settings, $settings ) {
46 $settings_proxy = ( is_array( $settings ) && isset( $settings['proxy_enabled'] ) ) ? $settings['proxy_enabled'] : '';
47 $old_proxy = ( is_array( $old_settings ) && isset( $old_settings['proxy_enabled'] ) ) ? $old_settings['proxy_enabled'] : '';
48
49 if ( $settings_proxy === 'on' && $old_proxy !== 'on' ) {
50 $this->install();
51 } elseif ( $settings_proxy === '' && $old_proxy === 'on' ) {
52 $this->uninstall();
53 }
54 }
55
56 /**
57 * Takes care of installing the M(ust)U(se) plugin when the Proxy is enabled.
58 *
59 * @return void.
60 * @since 1.3.0
61 */
62 public function install() {
63 if ( ! current_user_can( 'install_plugins' ) ) {
64 return; // @codeCoverageIgnore
65 }
66
67 if ( ! function_exists( 'WP_Filesystem' ) ) {
68 require_once( ABSPATH . 'wp-admin/includes/file.php' ); // @codeCoverageIgnore
69 }
70
71 WP_Filesystem();
72
73 if ( ! is_dir( WPMU_PLUGIN_DIR ) ) {
74 wp_mkdir_p( WPMU_PLUGIN_DIR );
75 add_option( 'plausible_analytics_created_mu_plugins_dir', true );
76 }
77
78 if ( ! is_dir( WPMU_PLUGIN_DIR ) ) {
79 $this->show_module_not_installed_error(); // @codeCoverageIgnore
80 }
81
82 $results = copy_dir( PLAUSIBLE_ANALYTICS_PLUGIN_DIR . 'mu-plugin', WPMU_PLUGIN_DIR );
83
84 if ( is_wp_error( $results ) ) {
85 $this->show_module_not_installed_error(); // @codeCoverageIgnore
86 }
87
88 add_option( 'plausible_analytics_proxy_speed_module_installed', true );
89 }
90
91 /**
92 * @return void
93 *
94 * @codeCoverageIgnore
95 * @since 1.3.0
96 */
97 private function show_module_not_installed_error() {
98 $message = sprintf(
99 wp_kses(
100 // translators: %s: URL to manual proxy speed module installation instructions.
101 __(
102 'The proxy is enabled, but the proxy\'s speed module failed to install. Try <a href="%s" target="_blank">installing it manually</a>.',
103 'plausible-analytics'
104 ),
105 'post'
106 ),
107 'https://plausible.io/docs/troubleshoot-integration#proxy-script-is-slow'
108 );
109
110 Messages::set_error( $message );
111 }
112
113 /**
114 * Uninstall the Speed Module, generates JS files and all related settings when the proxy is disabled.
115 *
116 * @return void.
117 * @throws Exception
118 * @since 1.3.0
119 */
120 public function uninstall() {
121 if ( ! current_user_can( 'install_plugins' ) ) {
122 return; // @codeCoverageIgnore
123 }
124
125 /**
126 * Clean up MU plugin.
127 */
128 $file_path = WP_CONTENT_DIR . '/mu-plugins/plausible-proxy-speed-module.php';
129
130 if ( file_exists( $file_path ) ) {
131 wp_delete_file( $file_path );
132 }
133
134 if ( get_option( 'plausible_analytics_created_mu_plugins_dir' ) && $this->dir_is_empty( WPMU_PLUGIN_DIR ) ) {
135 rmdir( WPMU_PLUGIN_DIR );
136 }
137
138 /**
139 * Clean up generated JS files in /uploads dir.
140 */
141 $cache_dir = Helpers::get_proxy_resource( 'cache_dir' );
142 $js_file = $this->get_filename();
143
144 if ( file_exists( $cache_dir . $js_file . '.js' ) ) {
145 wp_delete_file( $cache_dir . $js_file . '.js' ); // @codeCoverageIgnore
146 }
147
148 if ( $this->dir_is_empty( $cache_dir ) ) {
149 rmdir( $cache_dir );
150 }
151
152 /**
153 * Clean up related DB entries.
154 */
155 delete_option( 'plausible_analytics_created_mu_plugins_dir' );
156 delete_option( 'plausible_analytics_proxy_speed_module_installed' );
157 delete_option( 'plausible_analytics_proxy_resources' );
158 }
159
160 /**
161 * Check if a directory is empty.
162 * This works because a new FilesystemIterator will initially point to the first file in the folder -
163 * if there are no files in the folder, valid() will return false.
164 *
165 * @see https://www.php.net/manual/en/directoryiterator.valid.php
166 * @since 1.3.0
167 *
168 * @param mixed $dir
169 *
170 * @return bool
171 *
172 * @codeCoverageIgnore Because we don't want to test the filesystem.
173 */
174 protected function dir_is_empty( $dir ) {
175 $iterator = new \FilesystemIterator( $dir );
176
177 return ! $iterator->valid();
178 }
179
180 /**
181 * @throws Exception
182 *
183 * @codeCoverageIgnore because Helpers are tested elsewhere.
184 */
185 protected function get_filename() {
186 return Helpers::get_filename();
187 }
188
189 /**
190 * Test the proxy before enabling the option.
191 *
192 * @param mixed $settings
193 *
194 * @return mixed
195 * @throws Exception
196 * @since 1.3.0
197 *
198 */
199 public function maybe_enable_proxy( $settings, $old_settings ) {
200 /**
201 * No need to run this on each update run, or when the proxy is disabled.
202 */
203 $new_proxy_setting = ( is_array( $settings ) && isset( $settings['proxy_enabled'] ) ) ? $settings['proxy_enabled'] : '';
204 $old_proxy_setting = ( is_array( $old_settings ) && isset( $old_settings['proxy_enabled'] ) ) ? $old_settings['proxy_enabled'] : '';
205
206 if ( empty( $new_proxy_setting ) || ( $new_proxy_setting === 'on' && $old_proxy_setting === 'on' ) ) {
207 return $settings;
208 }
209
210 $is_ssl = $this->is_ssl();
211
212 if ( ! $is_ssl ) {
213 Messages::set_notice(
214 sprintf(
215 // translators: %s: URL to list of potential proxy solutions.
216 __(
217 'Please check that your proxy is functioning correctly. If you encounter any issues with tracking, <a href="%s" target="_blank">click here</a> for a list of potential solutions',
218 'plausible-analytics'
219 ),
220 'https://plausible.io/docs/troubleshoot-integration#proxy-not-recording-stats-after-setup'
221 )
222 );
223 }
224
225 $test_succeeded = $this->test_proxy( Helpers::proxy_enabled( $settings ) && wp_doing_ajax() );
226
227 if ( ! $test_succeeded ) {
228 // @codeCoverageIgnoreStart
229 Messages::set_error(
230 sprintf(
231 wp_kses(
232 // translators: 1: Proxy endpoint URL, 2: URL to Plausible support.
233 __(
234 'Plausible\'s proxy couldn\'t be enabled, because the WordPress API is inaccessible. This might be due to a conflicting setting in a (security) plugin or server firewall. Make sure you whitelist requests to the Proxy\'s endpoint: <code>%1$s</code>. <a href="%2$s" target="_blank">Contact support</a> if you need help locating the issue.',
235 'plausible-analytics'
236 ),
237 'post'
238 ),
239 Helpers::get_rest_endpoint( false ),
240 'https://plausible.io/contact'
241 )
242 );
243
244 // Disable the proxy.
245 return $old_settings;
246 // @codeCoverageIgnoreEnd
247 }
248
249 return $settings;
250 }
251
252 /**
253 * is_ssl() only checks the current scheme that is used, which fails in a Nginx Reverse Proxy configuration (where the scheme is HTTP behind the
254 * proxy), this function is a custom wrapper which also checks the WordPress configuration for the presence of "https" in the configured Home
255 * URL.
256 *
257 * @return bool
258 */
259 private function is_ssl() {
260 return strpos( get_home_url(), 'https' ) !== false || is_ssl();
261 }
262
263 /**
264 * Runs a quick internal call to the WordPress API to make sure it's accessible.
265 *
266 * @return bool
267 * @throws Exception
268 * @since 1.3.0
269 */
270 private function test_proxy( $run = true ) {
271 // Always succeed if this is a CI environment.
272 if ( defined( 'PLAUSIBLE_CI' ) ) {
273 return true;
274 }
275
276 // Should we run the test?
277 if ( ! apply_filters( 'plausible_analytics_module_run_test_proxy', $run ) ) {
278 return false; // @codeCoverageIgnore
279 }
280
281 $proxy = new Proxy( false );
282 $result = $proxy->do_request( 'pageview', 'plausible.test', 'https://plausible.test/test' );
283
284 return wp_remote_retrieve_response_code( $result ) === 202;
285 }
286 }
287