PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.1.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.1.0
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / classes / WpMatomo / Admin / SystemReport.php
matomo / classes / WpMatomo / Admin Last commit date
TrackingSettings 4 years ago views 2 years ago AccessSettings.php 4 years ago Admin.php 2 years ago AdminSettings.php 2 years ago AdminSettingsInterface.php 6 years ago AdvancedSettings.php 2 years ago Chart.php 4 years ago CookieConsent.php 4 years ago Dashboard.php 4 years ago ExclusionSettings.php 4 years ago GeolocationSettings.php 2 years ago GetStarted.php 4 years ago ImportWpStatistics.php 4 years ago Info.php 4 years ago InvalidIpException.php 4 years ago Marketplace.php 2 years ago MarketplaceSetupWizard.php 2 years ago Menu.php 2 years ago PluginMeasurableSettings.php 2 years ago PrivacySettings.php 4 years ago SafeModeMenu.php 4 years ago Summary.php 4 years ago SystemReport.php 2 years ago TrackingSettings.php 4 years ago
SystemReport.php
2002 lines
1 <?php
2 /**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 * @package matomo
8 */
9
10 namespace WpMatomo\Admin;
11
12 use Exception;
13 use ITSEC_Modules;
14 use Piwik\CliMulti;
15 use Piwik\CliMulti\CliPhp;
16 use Piwik\Common;
17 use Piwik\Config;
18 use Piwik\Container\StaticContainer;
19 use Piwik\DeviceDetector\DeviceDetectorFactory;
20 use Piwik\Filesystem;
21 use Piwik\Plugin;
22 use Piwik\Plugins\CoreAdminHome\API;
23 use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult;
24 use Piwik\Plugins\Diagnostics\DiagnosticService;
25 use Piwik\Plugins\SitesManager\Model;
26 use Piwik\Plugins\UserCountry\LocationProvider;
27 use Piwik\Plugins\WordPress\WordPress;
28 use Piwik\SettingsPiwik;
29 use Piwik\Tracker\Failures;
30 use Piwik\Version;
31 use WpMatomo;
32 use WpMatomo\Bootstrap;
33 use WpMatomo\Capabilities;
34 use WpMatomo\Installer;
35 use WpMatomo\Logger;
36 use WpMatomo\Paths;
37 use WpMatomo\ScheduledTasks;
38 use WpMatomo\Settings;
39 use WpMatomo\Site;
40 use WpMatomo\Site\Sync as SiteSync;
41 use WpMatomo\Updater;
42 use WpMatomo\User\Sync as UserSync;
43
44 if ( ! defined( 'ABSPATH' ) ) {
45 exit; // if accessed directly
46 }
47
48 /**
49 * error_reporting is required for this page
50 * phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_error_reporting
51 *
52 * We want a real data, not something coming from cache
53 * phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
54 *
55 * This is a report error, so silent the possible errors
56 * phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
57 *
58 * We cannot use parameters of statements as this is the table names we build
59 * phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
60 * phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
61 */
62 class SystemReport {
63 const NONCE_NAME = 'matomo_troubleshooting';
64 const TROUBLESHOOT_SYNC_USERS = 'matomo_troubleshooting_action_site_users';
65 const TROUBLESHOOT_SYNC_ALL_USERS = 'matomo_troubleshooting_action_all_users';
66 const TROUBLESHOOT_SYNC_SITE = 'matomo_troubleshooting_action_site';
67 const TROUBLESHOOT_SYNC_ALL_SITES = 'matomo_troubleshooting_action_all_sites';
68 const TROUBLESHOOT_CLEAR_MATOMO_CACHE = 'matomo_troubleshooting_action_clear_matomo_cache';
69 const TROUBLESHOOT_ARCHIVE_NOW = 'matomo_troubleshooting_action_archive_now';
70 const TROUBLESHOOT_UPDATE_GEOIP_DB = 'matomo_troubleshooting_action_update_geoipdb';
71 const TROUBLESHOOT_CLEAR_LOGS = 'matomo_troubleshooting_action_clear_logs';
72 const TROUBLESHOOT_RUN_UPDATER = 'matomo_troubleshooting_action_run_updater';
73
74 private $not_compatible_plugins = [
75 'minify-html-markup',
76 ];
77
78 private $valid_tabs = [ 'troubleshooting' ];
79
80 /**
81 * @var Settings
82 */
83 private $settings;
84
85 /**
86 * @var Logger
87 */
88 private $logger;
89
90 private $initial_error_reporting = null;
91
92 private $shell_exec_available;
93 /**
94 * @var \WpMatomo\Db\Settings
95 */
96 public $db_settings;
97 /**
98 * @var string the php binary used by Matomo (use get_php_binary() to access)
99 */
100 private $binary;
101
102 /**
103 * @var string
104 */
105 private $path_to_plugin;
106
107 private static $matomo_tables;
108
109 public function __construct( Settings $settings ) {
110 $this->settings = $settings;
111 $this->logger = new Logger();
112 $this->db_settings = new \WpMatomo\Db\Settings();
113 $this->shell_exec_available = function_exists( 'shell_exec' );
114 $this->path_to_plugin = $this->get_abs_path_to_plugin();
115 }
116
117 public function get_not_compatible_plugins() {
118 return $this->not_compatible_plugins;
119 }
120
121 private function execute_troubleshoot_if_needed() {
122 if ( ! empty( $_POST )
123 && is_admin()
124 && check_admin_referer( self::NONCE_NAME )
125 && current_user_can( Capabilities::KEY_SUPERUSER )
126 ) {
127 if ( ! empty( $_POST[ self::TROUBLESHOOT_ARCHIVE_NOW ] ) ) {
128 Bootstrap::do_bootstrap();
129 $scheduled_tasks = new ScheduledTasks( $this->settings );
130
131 if ( ! defined( 'PIWIK_ARCHIVE_NO_TRUNCATE' ) ) {
132 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
133 define( 'PIWIK_ARCHIVE_NO_TRUNCATE', 1 ); // when triggering it manually, we prefer the full error message
134 }
135
136 try {
137 // force invalidation of archive to ensure it actually will rearchive the data
138 $site = new Site();
139 $idsite = $site->get_current_matomo_site_id();
140 if ( $idsite ) {
141 $timezone = \Piwik\Site::getTimezoneFor( $idsite );
142 $now_string = \Piwik\Date::factory( 'now', $timezone )->toString();
143 foreach ( [ 'day' ] as $period ) {
144 API::getInstance()->invalidateArchivedReports( $idsite, $now_string, $period, false, false );
145 }
146 }
147 } catch ( Exception $e ) {
148 $this->logger->log_exception( 'archive_invalidate', $e );
149 }
150
151 try {
152 $errors = $scheduled_tasks->archive( true, false );
153 } catch ( Exception $e ) {
154 echo '<div class="error"><p>' . esc_html__( 'Matomo Archive Error', 'matomo' ) . ': ' . esc_html( matomo_anonymize_value( $e->getMessage() . ' =>' . $this->logger->get_readable_trace( $e ) ) ) . '</p></div>';
155 throw $e;
156 }
157
158 if ( ! empty( $errors ) ) {
159 echo '<div class="notice notice-warning"><p>Matomo Archive Warnings: ';
160 foreach ( $errors as $error ) {
161 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
162 echo nl2br( esc_html( matomo_anonymize_value( var_export( $error, 1 ) ) ) );
163 echo '<br/>';
164 }
165 echo '</p></div>';
166 } else {
167 echo '<div class="notice notice-success"><p>' . esc_html__( 'Matomo Archiving completed successfully!', 'matomo' ) . '</p></div>';
168 }
169 }
170
171 if ( ! empty( $_POST[ self::TROUBLESHOOT_CLEAR_MATOMO_CACHE ] ) ) {
172 $paths = new Paths();
173 $paths->clear_cache_dir();
174 // we first delete the cache dir manually just in case there's something
175 // going wrong with matomo and bootstrapping would not even be possible.
176 Bootstrap::do_bootstrap();
177 Filesystem::deleteAllCacheOnUpdate();
178 Updater::unlock();
179 }
180
181 if ( ! empty( $_POST[ self::TROUBLESHOOT_UPDATE_GEOIP_DB ] ) ) {
182 $scheduled_tasks = new ScheduledTasks( $this->settings );
183 $scheduled_tasks->update_geo_ip2_db();
184 }
185
186 if ( ! empty( $_POST[ self::TROUBLESHOOT_CLEAR_LOGS ] ) ) {
187 $this->logger->clear_logged_exceptions();
188 }
189
190 if ( ! $this->settings->is_network_enabled() || ! is_network_admin() ) {
191 if ( ! empty( $_POST[ self::TROUBLESHOOT_SYNC_USERS ] ) ) {
192 $sync = new UserSync();
193 $sync->sync_current_users();
194 }
195 if ( ! empty( $_POST[ self::TROUBLESHOOT_SYNC_SITE ] ) ) {
196 $sync = new SiteSync( $this->settings );
197 $sync->sync_current_site();
198 }
199 if ( ! empty( $_POST[ self::TROUBLESHOOT_RUN_UPDATER ] ) ) {
200 Updater::unlock();
201 $sync = new Updater( $this->settings );
202 $sync->update();
203 }
204 }
205 if ( $this->settings->is_network_enabled() ) {
206 if ( ! empty( $_POST[ self::TROUBLESHOOT_SYNC_ALL_SITES ] ) ) {
207 $sync = new SiteSync( $this->settings );
208 $sync->sync_all();
209 }
210 if ( ! empty( $_POST[ self::TROUBLESHOOT_SYNC_ALL_USERS ] ) ) {
211 $sync = new UserSync();
212 $sync->sync_all();
213 }
214 }
215 }
216 }
217
218 private function get_error_tables() {
219 $matomo_tables = self::$matomo_tables;
220
221 if ( ! $matomo_tables ) {
222 $matomo_tables = [
223 [
224 'title' => 'Matomo',
225 'rows' => $this->get_matomo_info(),
226 'has_comments' => true,
227 ],
228 [
229 'title' => 'WordPress',
230 'rows' => $this->get_wordpress_info(),
231 'has_comments' => true,
232 ],
233 [
234 'title' => 'WordPress Plugins',
235 'rows' => $this->get_plugins_info(),
236 'has_comments' => true,
237 ],
238 [
239 'title' => 'Server',
240 'rows' => $this->get_server_info(),
241 'has_comments' => true,
242 ],
243 [
244 'title' => 'PHP cli',
245 'rows' => $this->get_phpcli_info(),
246 'has_comments' => true,
247 ],
248 [
249 'title' => 'Database',
250 'rows' => $this->get_db_info(),
251 'has_comments' => true,
252 ],
253 [
254 'title' => 'Browser',
255 'rows' => $this->get_browser_info(),
256 'has_comments' => true,
257 ],
258 ];
259 self::$matomo_tables = $matomo_tables;
260 }
261
262 return $matomo_tables;
263 }
264
265 public function errors_present() {
266 $cache_key = 'matomo_system_report_has_errors';
267 $cache_value = get_transient( $cache_key );
268
269 if ( false === $cache_value ) {
270 // pre-record that there were no errors found. in case the system report fails to execute, this will
271 // allow the rest of Matomo for WordPress to continue to still be usable.
272 set_transient( $cache_key, 0, WEEK_IN_SECONDS );
273
274 $matomo_tables = $this->get_error_tables();
275
276 $matomo_tables = apply_filters( 'matomo_systemreport_tables', $matomo_tables );
277 $matomo_tables = $this->add_errors_first( $matomo_tables );
278
279 foreach ( $matomo_tables as $report_table ) {
280 foreach ( $report_table['rows'] as $row ) {
281 if ( ! empty( $row['is_error'] ) || ! empty( $row['is_warning'] ) ) {
282 $cache_value = true;
283 }
284 }
285 }
286
287 set_transient( $cache_key, (int) $cache_value, WEEK_IN_SECONDS );
288 }
289
290 return 1 === (int) $cache_value;
291 }
292
293 public function show() {
294 $this->execute_troubleshoot_if_needed();
295
296 $settings = $this->settings;
297
298 $matomo_active_tab = '';
299
300 if ( isset( $_GET['tab'] ) ) {
301 $tab = sanitize_text_field( wp_unslash( $_GET['tab'] ) );
302 if ( in_array( $tab, $this->valid_tabs, true ) ) {
303 $matomo_active_tab = $tab;
304 }
305 }
306
307 $matomo_tables = [];
308 if ( empty( $matomo_active_tab ) ) {
309 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting
310 $this->initial_error_reporting = @error_reporting();
311 $matomo_tables = $this->get_error_tables();
312 }
313 $matomo_tables = apply_filters( 'matomo_systemreport_tables', $matomo_tables );
314 $matomo_tables = $this->add_errors_first( $matomo_tables );
315 $matomo_has_warning_and_no_errors = $this->has_only_warnings_no_error( $matomo_tables );
316
317 $matomo_has_exception_logs = $this->logger->get_last_logged_entries();
318
319 include dirname( __FILE__ ) . '/views/systemreport.php';
320 }
321
322 private function has_only_warnings_no_error( $report_tables ) {
323 $has_warning = false;
324 $has_error = false;
325 foreach ( $report_tables as $report_table ) {
326 foreach ( $report_table['rows'] as $row ) {
327 if ( ! empty( $row['is_error'] ) ) {
328 $has_error = true;
329 }
330 if ( ! empty( $row['is_warning'] ) ) {
331 $has_warning = true;
332 }
333 }
334 }
335
336 return $has_warning && ! $has_error;
337 }
338
339 private function add_errors_first( $report_tables ) {
340 $errors = [
341 'title' => 'Errors',
342 'rows' => [],
343 'has_comments' => true,
344 ];
345 foreach ( $report_tables as $report_table ) {
346 foreach ( $report_table['rows'] as $row ) {
347 if ( ! empty( $row['is_error'] ) ) {
348 $errors['rows'][] = $row;
349 }
350 }
351 }
352
353 if ( ! empty( $errors['rows'] ) ) {
354 array_unshift( $report_tables, $errors );
355 }
356
357 return $report_tables;
358 }
359
360 private function check_file_exists_and_writable( $rows, $path_to_check, $title, $required ) {
361 $file_exists = file_exists( $path_to_check );
362 $file_readable = is_readable( $path_to_check );
363 $file_writable = is_writable( $path_to_check );
364 $comment = '"' . $path_to_check . '" ';
365 if ( ! $file_exists ) {
366 $comment .= sprintf( esc_html__( '%s does not exist. ', 'matomo' ), $title );
367 }
368 if ( ! $file_readable ) {
369 $comment .= sprintf( esc_html__( '%s is not readable. ', 'matomo' ), $title );
370 }
371 if ( ! $file_writable ) {
372 $comment .= sprintf( esc_html__( '%s is not writable. ', 'matomo' ), $title );
373 }
374
375 $rows[] = [
376 'name' => sprintf( esc_html__( '%s exists and is writable.', 'matomo' ), $title ),
377 'value' => $file_exists && $file_readable && $file_writable ? esc_html__( 'Yes', 'matomo' ) : esc_html__( 'No', 'matomo' ),
378 'comment' => $comment,
379 'is_error' => $required && ( ! $file_exists || ! $file_readable ),
380 'is_warning' => ! $required && ( ! $file_exists || ! $file_readable ),
381 ];
382
383 return $rows;
384 }
385
386 private function get_phpcli_info() {
387 $rows = [];
388
389 if ( $this->shell_exec_available ) {
390 try {
391 $cli_multi = new CliMulti();
392
393 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
394 $supports_async = $cli_multi->supportsAsync;
395 } catch ( \Exception $ex ) {
396 $rows[] = [
397 'name' => esc_html__( 'PHP CLI configuration', 'matomo' ),
398 'value' => esc_html__( 'Unexpected error', 'matomo' ),
399 'is_warning' => true,
400 'comment' => sprintf( esc_html__( 'Could not detect whether async archiving is enabled: %s', 'matomo' ), $ex->getMessage() ),
401 ];
402 }
403
404 $phpcli_version = $this->get_phpcli_output( '-r "echo phpversion();"' );
405
406 $is_warning = false;
407 $comment = '';
408
409 $advanced_settings_url = home_url( '/wp-admin/admin.php?page=matomo-settings&tab=advanced#matomo[disable_async_archiving]' );
410
411 $is_using_cli_archiving = ! \WpMatomo::is_async_archiving_manually_disabled() && $supports_async;
412
413 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
414 if ( version_compare( $phpcli_version, PHP_VERSION ) < 0 ) {
415 if ( $is_using_cli_archiving ) {
416 $is_warning = true;
417 }
418
419 $comment = sprintf(
420 esc_html__( 'The detected PHP CLI version does not match the PHP web version. To avoid archiving errors, %1$senable archiving via HTTP requests%2$s, or %3$smanually set the path to your PHP CLI executable%4$s to the one for PHP version %5$s.', 'matomo' ),
421 '<a href="' . $advanced_settings_url . '">',
422 '</a>',
423 '<a href="https://matomo.org/faq/how-to-solve-the-error-message-your-php-cli-version-is-not-compatible-with-the-matomo-requirements/" target="_blank" rel="noreferrer noopener">',
424 '</a>',
425 PHP_VERSION
426 );
427 }
428 $rows[] = [
429 'name' => esc_html__( 'PHP CLI Version', 'matomo' ),
430 'value' => $phpcli_version,
431 'comment' => $comment,
432 'is_warning' => $is_warning,
433 ];
434
435 $is_error = false;
436 $value = __( 'ok', 'matomo' );
437 $comment = '';
438 if ( ! intval( $this->get_phpcli_output( '-r "echo extension_loaded(\'mysqli\');"' ) ) ) {
439 $value = __( 'missing', 'matomo' );
440 $is_error = true;
441 $comment = esc_html__( 'Your PHP cli does not load the MySQLi extension. You might have archiving problems in Matomo but also others problems in your WordPress cron tasks. You should enable this extension', 'matomo' );
442 }
443
444 $rows[] = [
445 'name' => esc_html__( 'MySQLi support', 'matomo' ),
446 'value' => $value,
447 'comment' => $comment,
448 'is_error' => $is_using_cli_archiving ? $is_error : false,
449 ];
450
451 if ( $supports_async ) {
452 $this->check_wp_can_be_loaded_in_php_cli( $rows );
453 }
454 }
455
456 return $rows;
457 }
458
459 private function check_wp_can_be_loaded_in_php_cli( &$rows ) {
460 if ( ! $this->binary ) {
461 return;
462 }
463
464 $wp_load_path = $this->find_wp_load_path();
465 if ( ! $wp_load_path ) {
466 return;
467 }
468
469 $command = "-r 'require_once( \"$wp_load_path\" );'";
470 $output = $this->get_phpcli_output( $command );
471 if ( empty( $output ) ) {
472 $rows[] = [
473 'name' => esc_html__( 'PHP CLI configuration', 'matomo' ),
474 'value' => esc_html__( 'Configured correctly', 'matomo' ),
475 ];
476 } else {
477 $error_message = esc_html__(
478 'WordPress cannot be loaded via PHP CLI. Please ensure both WordPress and PHP CLI are correctly configured. Note: If you are using get_cfg_var() in your wp-config.php, you will need to make sure the php.ini file for PHP CLI has the correct values. You may need to contact your hosting provider for these changes to be made.',
479 'matomo'
480 );
481
482 $rows[] = [
483 'name' => esc_html__( 'PHP CLI configuration', 'matomo' ),
484 'value' => esc_html__( 'warning', 'matomo' ),
485 'comment' => $error_message,
486 ];
487 }
488 }
489
490 private function get_phpcli_output( $phpcli_params ) {
491 $output = '';
492 if ( $this->shell_exec_available && $this->get_php_cli_binary() ) {
493 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.system_calls_shell_exec
494 $output = trim( '' . @shell_exec( $this->get_php_cli_binary() . ' ' . $phpcli_params ) );
495 }
496
497 return $output;
498 }
499
500 private function get_matomo_info() {
501 $rows = [];
502
503 $plugin_data = get_plugin_data( MATOMO_ANALYTICS_FILE, $markup = false, $translate = false );
504 $install_time = get_option( Installer::OPTION_NAME_INSTALL_DATE );
505
506 $rows[] = [
507 'name' => esc_html__( 'Matomo Plugin Version', 'matomo' ),
508 'value' => $plugin_data['Version'],
509 'comment' => '',
510 ];
511
512 $paths = new Paths();
513 $path_config_file = $paths->get_config_ini_path();
514 $rows = $this->check_file_exists_and_writable( $rows, $path_config_file, 'Config', true );
515
516 $path_tracker_file = $paths->get_matomo_js_upload_path();
517 $rows = $this->check_file_exists_and_writable( $rows, $path_tracker_file, 'JS Tracker', false );
518
519 $rows[] = [
520 'name' => esc_html__( 'Plugin directories', 'matomo' ),
521 'value' => ! empty( $GLOBALS['MATOMO_PLUGIN_DIRS'] ) ? 'Yes' : 'No',
522 'comment' => ! empty( $GLOBALS['MATOMO_PLUGIN_DIRS'] ) ? wp_json_encode( $GLOBALS['MATOMO_PLUGIN_DIRS'] ) : '',
523 ];
524
525 $tmp_dir = $paths->get_tmp_dir();
526
527 $rows[] = [
528 'name' => esc_html__( 'Tmp directory writable', 'matomo' ),
529 'value' => is_writable( $tmp_dir ),
530 'comment' => $tmp_dir,
531 ];
532
533 if ( ! empty( $_SERVER['MATOMO_WP_ROOT_PATH'] ) ) {
534 // we can have / in this value
535 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
536 $custom_path = rtrim( $_SERVER['MATOMO_WP_ROOT_PATH'], '/' ) . '/wp-load.php';
537 $path_exists = file_exists( $custom_path );
538 $comment = '';
539 if ( ! $path_exists ) {
540 $comment = 'It seems the path does not point to the WP root directory.';
541 }
542
543 $rows[] = [
544 'name' => 'Custom MATOMO_WP_ROOT_PATH',
545 'value' => $path_exists,
546 'is_error' => ! $path_exists,
547 'comment' => $comment,
548 ];
549 }
550
551 $report = null;
552
553 if ( ! WpMatomo::is_safe_mode() ) {
554 try {
555 Bootstrap::do_bootstrap();
556 /** @var DiagnosticService $service */
557 $service = StaticContainer::get( DiagnosticService::class );
558 $report = $service->runDiagnostics();
559
560 $rows[] = [
561 'name' => esc_html__( 'Matomo Version', 'matomo' ),
562 'value' => \Piwik\Version::VERSION,
563 'comment' => '',
564 ];
565 } catch ( Exception $e ) {
566 $rows[] = [
567 'name' => esc_html__( 'Matomo System Check', 'matomo' ),
568 'value' => 'Failed to run Matomo system check.',
569 'comment' => $e->getMessage(),
570 'is_error' => true,
571 ];
572 }
573 }
574
575 $site = new Site();
576 $idsite = $site->get_current_matomo_site_id();
577
578 $rows[] = [
579 'name' => esc_html__( 'Matomo Blog idSite', 'matomo' ),
580 'value' => $idsite,
581 'comment' => '',
582 ];
583
584 $install_date = '';
585 if ( ! empty( $install_time ) ) {
586 $install_date = 'Install date: ' . $this->convert_time_to_date( $install_time, true, false );
587 }
588
589 $rows[] = [
590 'name' => esc_html__( 'Matomo Install Version', 'matomo' ),
591 'value' => get_option( Installer::OPTION_NAME_INSTALL_VERSION ),
592 'comment' => $install_date,
593 ];
594
595 $wpmatomo_updater = new \WpMatomo\Updater( $this->settings );
596 if ( ! WpMatomo::is_safe_mode() ) {
597 $outstanding_updates = $wpmatomo_updater->get_plugins_requiring_update();
598 $upgrade_in_progress = $wpmatomo_updater->is_upgrade_in_progress();
599 $rows[] = [
600 'name' => 'Upgrades outstanding',
601 'value' => ! empty( $outstanding_updates ),
602 'comment' => ! empty( $outstanding_updates ) ? wp_json_encode( $outstanding_updates ) : '',
603 ];
604 $rows[] = [
605 'name' => 'Upgrade in progress',
606 'value' => $upgrade_in_progress,
607 'comment' => '',
608 ];
609 }
610
611 if ( ! $wpmatomo_updater->load_plugin_functions() ) {
612 // this should actually never happen...
613 $rows[] = [
614 'name' => 'Matomo Upgrade Plugin Functions',
615 'is_warning' => true,
616 'value' => false,
617 'comment' => 'Function "get_plugin_data" not available. There may be an issue with upgrades not being executed. Please reach out to us.',
618 ];
619 }
620
621 $rows[] = [
622 'section' => 'Endpoints',
623 ];
624
625 $rows[] = [
626 'name' => 'Matomo JavaScript Tracker URL',
627 'value' => '',
628 'comment' => $paths->get_js_tracker_url_in_matomo_dir(),
629 ];
630
631 $rows[] = [
632 'name' => 'Matomo JavaScript Tracker - WP Rest API',
633 'value' => '',
634 'comment' => $paths->get_js_tracker_rest_api_endpoint(),
635 ];
636
637 $rows[] = [
638 'name' => 'Matomo HTTP Tracking API',
639 'value' => '',
640 'comment' => $paths->get_tracker_api_url_in_matomo_dir(),
641 ];
642
643 $rows[] = [
644 'name' => 'Matomo HTTP Tracking API - WP Rest API',
645 'value' => '',
646 'comment' => $paths->get_tracker_api_rest_api_endpoint(),
647 ];
648
649 $matomo_plugin_dir_name = basename( dirname( MATOMO_ANALYTICS_FILE ) );
650 if ( 'matomo' !== $matomo_plugin_dir_name ) {
651 $rows[] = [
652 'name' => 'Matomo Plugin Name is correct',
653 'value' => false,
654 'is_error' => true,
655 'comment' => 'The plugin name should be "matomo" but seems to be "' . $matomo_plugin_dir_name . '". As a result, admin pages and other features might not work. You might need to rename the directory name of this plugin and reactive the plugin.',
656 ];
657 } elseif ( ! is_plugin_active( 'matomo/matomo.php' ) ) {
658 $rows[] = [
659 'name' => 'Matomo Plugin not active',
660 'value' => false,
661 'is_error' => true,
662 'comment' => 'It seems WordPress thinks that `matomo/matomo.php` is not active. As a result Matomo reporting and admin pages may not work. You may be able to fix this by deactivating and activating the Matomo Analytics plugin. One of the reasons this could happen is that you used to have Matomo installed in the wrong folder.',
663 ];
664 }
665
666 $rows[] = [
667 'section' => 'Crons',
668 ];
669
670 $scheduled_tasks = new ScheduledTasks( $this->settings );
671 $all_events = $scheduled_tasks->get_all_events();
672
673 $rows[] = [
674 'name' => esc_html__( 'Server time', 'matomo' ),
675 'value' => $this->convert_time_to_date( time(), false ),
676 'comment' => '',
677 ];
678
679 $rows[] = [
680 'name' => esc_html__( 'Blog time', 'matomo' ),
681 'value' => $this->convert_time_to_date( time(), true ),
682 'comment' => esc_html__( 'Below dates are shown in blog timezone', 'matomo' ),
683 ];
684
685 foreach ( $all_events as $event_name => $event_config ) {
686 $last_run_before = $scheduled_tasks->get_last_time_before_cron( $event_name );
687 $last_run_after = $scheduled_tasks->get_last_time_after_cron( $event_name );
688
689 $next_scheduled = wp_next_scheduled( $event_name );
690
691 $comment = ' Last started: ' . $this->convert_time_to_date( $last_run_before, true, true ) . '.';
692 $comment .= ' Last ended: ' . $this->convert_time_to_date( $last_run_after, true, true ) . '.';
693 $comment .= ' Interval: ' . $event_config['interval'];
694
695 $rows[] = [
696 'name' => $event_config['name'],
697 'value' => 'Next run: ' . $this->convert_time_to_date( $next_scheduled, true, true ),
698 'comment' => $comment,
699 ];
700 }
701
702 $suports_async = false;
703 if ( ! WpMatomo::is_safe_mode() && $report ) {
704 $rows[] = [
705 'section' => esc_html__( 'Mandatory checks', 'matomo' ),
706 ];
707
708 $rows = $this->add_diagnostic_results( $rows, $report->getMandatoryDiagnosticResults() );
709
710 $rows[] = [
711 'section' => esc_html__( 'Optional checks', 'matomo' ),
712 ];
713 $rows = $this->add_diagnostic_results( $rows, $report->getOptionalDiagnosticResults() );
714
715 try {
716 $cli_multi = new CliMulti();
717 $suports_async = $cli_multi->supportsAsync();
718
719 $rows[] = [
720 'name' => esc_html__( 'Supports Async Archiving', 'matomo' ),
721 'value' => $suports_async,
722 'comment' => '',
723 ];
724 } catch ( \Exception $e ) {
725 $rows[] = [
726 'name' => esc_html__( 'Supports Async Archiving', 'matomo' ),
727 'value' => 'Could not check if async archiving is supported.',
728 'comment' => $e->getMessage(),
729 'is_error' => true,
730 ];
731 }
732
733 $rows[] = [
734 'name' => 'Async Archiving Disabled in Setting',
735 'value' => $this->settings->is_async_archiving_disabled_by_option(),
736 'comment' => '',
737 ];
738
739 $location_provider = LocationProvider::getCurrentProvider();
740 if ( $location_provider ) {
741 try {
742 $location_provider = LocationProvider::getCurrentProvider();
743 if ( $location_provider ) {
744 $rows[] = [
745 'name' => 'Location provider ID',
746 'value' => $location_provider->getId(),
747 'comment' => '',
748 ];
749 $rows[] = [
750 'name' => 'Location provider available',
751 'value' => $location_provider->isAvailable(),
752 'comment' => '',
753 ];
754 $rows[] = [
755 'name' => 'Location provider working',
756 'value' => $location_provider->isWorking(),
757 'comment' => '',
758 ];
759 }
760 } catch ( \Exception $e ) {
761 $rows[] = [
762 'name' => 'Location provider ID',
763 'value' => 'Failed to get the configured Location Provider.',
764 'comment' => $e->getMessage(),
765 'is_error' => true,
766 ];
767 }
768 }
769
770 if ( ! WpMatomo::is_safe_mode() ) {
771 Bootstrap::do_bootstrap();
772 $general = Config::getInstance()->General;
773
774 if ( empty( $general['proxy_client_headers'] ) ) {
775 foreach ( AdvancedSettings::$valid_host_headers as $header ) {
776 if ( ! empty( $_SERVER[ $header ] ) ) {
777 $rows[] = [
778 'name' => 'Proxy header',
779 'value' => $header,
780 'is_warning' => true,
781 'comment' => sprintf( esc_html__( 'A proxy header is set which means you maybe need to configure a proxy header in the Advanced settings to make location reporting work. If the location in your reports is detected correctly, you can ignore this warning. %s', 'matomo' ), sprintf( '<a href="%s" target="_blank">%s</a>', 'https://matomo.org/faq/wordpress/how-do-i-fix-the-proxy-header-warning-in-the-matomo-for-wordpress-system-report/', esc_html__( 'Learn more', 'matomo' ) ) ),
782 ];
783 }
784 }
785 }
786
787 try {
788 $incompatible_plugins = Plugin\Manager::getInstance()->getIncompatiblePlugins( Version::VERSION );
789 if ( ! empty( $incompatible_plugins ) ) {
790 $rows[] = [
791 'section' => esc_html__( 'Incompatible Matomo plugins', 'matomo' ),
792 ];
793 foreach ( $incompatible_plugins as $plugin ) {
794 $rows[] = [
795 'name' => 'Plugin has missing dependencies',
796 'value' => $plugin->getPluginName(),
797 'is_error' => true,
798 'comment' => sprintf( esc_html__( '%s If the plugin requires a different Matomo version you may need to update it. If you no longer use it consider uninstalling it.', 'matomo' ), $plugin->getMissingDependenciesAsString( Version::VERSION ) ),
799 ];
800 }
801 }
802 } catch ( \Exception $e ) {
803 $rows[] = [
804 'section' => esc_html__( 'Incompatible Matomo plugins', 'matomo' ),
805 ];
806 $rows[] = [
807 'name' => 'Incompatible Matomo plugin check',
808 'value' => 'Failed to check for incompatible Matomo plugins.',
809 'is_error' => true,
810 'comment' => $e->getMessage(),
811 ];
812 }
813 }
814
815 $num_days_check_visits = 5;
816 $had_visits = $this->had_visits_in_last_days( $num_days_check_visits );
817
818 if ( false === $had_visits || true === $had_visits ) {
819 // do not show info if we could not detect it (had_visits === null)
820 $comment = '';
821 if ( ! $had_visits ) {
822 $comment = sprintf( esc_html__( 'It looks like there were no visits in the last %s days. This may be expected if tracking is disabled, you have not added the tracking code, or your website does not have many visitors in general and you exclude your own visits.', 'matomo' ), $num_days_check_visits );
823 }
824
825 $rows[] = [
826 'name' => 'Had visit in last ' . $num_days_check_visits . ' days',
827 'value' => $had_visits,
828 'is_warning' => ! $had_visits && $this->settings->is_tracking_enabled(),
829 'comment' => $comment,
830 ];
831 }
832
833 if ( ! WpMatomo::is_safe_mode() ) {
834 Bootstrap::do_bootstrap();
835
836 try {
837 $matomo_url = SettingsPiwik::getPiwikUrl();
838 $rows[] = [
839 'name' => 'Matomo URL',
840 'comment' => $matomo_url,
841 'value' => ! empty( $matomo_url ),
842 ];
843 } catch ( \Exception $e ) {
844 $rows[] = [
845 'name' => 'Matomo URL',
846 'comment' => $e->getMessage(),
847 'value' => 'Failed to fetch the Matomo URL.',
848 'is_error' => true,
849 ];
850 }
851 }
852
853 // check that yml files are not accessible
854 $url = plugins_url( 'app', MATOMO_ANALYTICS_FILE ) . '/vendor/matomo/device-detector/regexes/bots.yml';
855 $result = wp_remote_post(
856 $url,
857 array(
858 'method' => 'GET',
859 'sslverify' => false,
860 'timeout' => 2,
861 )
862 );
863 if ( is_array( $result ) ) {
864 $response_code = (int) $result['response']['code'];
865 if ( $response_code >= 200 && $response_code < 300 ) {
866 $rows[] = [
867 'name' => __( 'YML files should not be accessible', 'matomo' ),
868 'value' => 'warning',
869 'comment' => __( 'The .yml files in the wp-content/plugins/matomo/app/vendor directory are accessible from the internet. This can cause some web security tools to flag your website as suspicious. If you are using Apache, it is probably due to your server configuration disabling the use of .htaccess files. If you are instead using nginx, it is due to your nginx configuration allowing .yml files. You may need to contact your hosting provider to fix this.', 'matomo' ),
870 'is_warning' => true,
871 ];
872 }
873 }
874 }
875
876 $rows[] = [
877 'section' => 'Matomo Settings',
878 ];
879
880 // always show these settings
881 $global_settings_always_show = [
882 'track_mode',
883 'track_ecommerce',
884 'track_codeposition',
885 'track_api_endpoint',
886 'track_js_endpoint',
887 ];
888 foreach ( $global_settings_always_show as $key ) {
889 $rows[] = [
890 'name' => ucfirst( str_replace( '_', ' ', $key ) ),
891 'value' => $this->settings->get_global_option( $key ),
892 'comment' => '',
893 ];
894 }
895
896 // otherwise show only few customised settings
897 // mostly only numeric values and booleans to not eg accidentally show anything that would store a token etc
898 // like we don't want to show license key etc
899 foreach ( $this->settings->get_customised_global_settings() as $key => $val ) {
900 if ( is_numeric( $val ) || is_bool( $val ) || 'track_content' === $key || 'track_user_id' === $key || 'core_version' === $key || 'version_history' === $key || 'mail_history' === $key ) {
901 if ( is_array( $val ) ) {
902 $val = implode( ', ', $val );
903 }
904
905 $rows[] = [
906 'name' => ucfirst( str_replace( '_', ' ', $key ) ),
907 'value' => $val,
908 'comment' => '',
909 ];
910 }
911 }
912
913 $rows[] = [
914 'section' => 'Logs',
915 ];
916
917 $error_log_entries = $this->logger->get_last_logged_entries();
918
919 if ( ! empty( $error_log_entries ) ) {
920 foreach ( $error_log_entries as $error ) {
921 if ( ! empty( $install_time )
922 && is_numeric( $install_time )
923 && ! empty( $error['name'] )
924 && ! empty( $error['value'] )
925 && is_numeric( $error['value'] )
926 && 'cron_sync' === $error['name']
927 && $error['value'] < ( $install_time + 300 )
928 ) {
929 // the first sync might right after the installation
930 continue;
931 }
932
933 // we only consider plugin_updates as errors only if there are still outstanding updates
934 $is_plugin_update_error = ! empty( $error['name'] ) && 'plugin_update' === $error['name']
935 && ! empty( $outstanding_updates );
936
937 $skip_plugin_update = ! empty( $error['name'] ) && 'plugin_update' === $error['name']
938 && empty( $outstanding_updates );
939
940 if ( empty( $error['comment'] ) && '0' !== $error['comment'] ) {
941 $error['comment'] = '';
942 }
943
944 if ( strpos( $error['comment'], '<head>' ) ) {
945 $error['comment'] = esc_html( $error['comment'] );
946 $error['comment'] = $this->replace_hexadecimal_colors( $error['comment'] );
947 }
948
949 $error['value'] = $this->convert_time_to_date( $error['value'], true, false );
950 $error['is_warning'] = ! empty( $error['name'] ) && stripos( $error['name'], 'archiv' ) !== false && 'archive_boot' !== $error['name'];
951 $error['is_error'] = $is_plugin_update_error;
952 if ( $is_plugin_update_error ) {
953 $error['comment'] = sprintf( esc_html__( 'Please reach out to us and include the copied system report (%s)<br><br>You can also retry the update manually by clicking in the top on the "Troubleshooting" tab and then clicking on the "Run updater" button.', 'matomo' ), sprintf( '<a href="%s" target="_blank">%s</a>', 'https://matomo.org/faq/wordpress/how-do-i-troubleshoot-a-failed-database-upgrade-in-matomo-for-wordpress/', esc_html__( 'more info', 'matomo' ) ) ) . $error['comment'];
954 } elseif ( $skip_plugin_update ) {
955 $error['comment'] = esc_html__( 'As there are no outstanding plugin updates it looks like this log can be ignored.', 'matomo' ) . '<br><br>' . $error['comment'];
956 }
957 $error['comment'] = matomo_anonymize_value( $error['comment'] );
958 $rows[] = $error;
959 }
960
961 foreach ( $error_log_entries as $error ) {
962 if ( $suports_async
963 && ! empty( $error['value'] )
964 && is_string( $error['value'] )
965 && strpos( $error['value'], __( 'Your PHP installation appears to be missing the MySQL extension which is required by WordPress.', 'matomo' ) ) > 0
966 ) {
967 $rows[] = [
968 'name' => 'Cli has no MySQL',
969 'value' => true,
970 'comment' => sprintf( esc_html__( 'It looks like MySQL is not available on CLI. Please read our FAQ on how to %s', 'matomo' ), sprintf( ' <a href="%s" target="_blank">%s</a>', 'https://matomo.org/faq/wordpress/how-do-i-fix-the-error-your-php-installation-appears-to-be-missing-the-mysql-extension-which-is-required-by-wordpress-in-matomo-system-report/', esc_html__( 'fix this issue', 'matomo' ) ) ),
971 'is_error' => true,
972 ];
973 }
974 }
975 } else {
976 $rows[] = [
977 'name' => __( 'None', 'matomo' ),
978 'value' => '',
979 'comment' => '',
980 ];
981 }
982
983 if ( ! WpMatomo::is_safe_mode() ) {
984 Bootstrap::do_bootstrap();
985 $trackfailures = [];
986 try {
987 $tracking_failures = new Failures();
988 $trackfailures = $tracking_failures->getAllFailures();
989 // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
990 } catch ( Exception $e ) {
991 // ignored in case not set up yet etc.
992 }
993 if ( ! empty( $trackfailures ) ) {
994 $rows[] = [
995 'section' => 'Tracking failures',
996 ];
997 foreach ( $trackfailures as $failure ) {
998 $comment = sprintf(
999 'Solution: %s<br>More info: %s<br>Date: %s<br>Request URL: %s',
1000 $failure['solution'],
1001 $failure['solution_url'],
1002 $failure['pretty_date_first_occurred'],
1003 $failure['request_url']
1004 );
1005 // do not esc_html the comment: we want the br
1006 $rows[] = [
1007 'name' => $failure['problem'],
1008 'is_warning' => true,
1009 'value' => '',
1010 'comment' => $comment,
1011 ];
1012 }
1013 }
1014 }
1015
1016 return $rows;
1017 }
1018
1019 private function had_visits_in_last_days( $num_days ) {
1020 global $wpdb;
1021
1022 if ( WpMatomo::is_safe_mode() ) {
1023 return null;
1024 }
1025
1026 $days_in_seconds = $num_days * 86400;
1027
1028 $prefix_table = $this->db_settings->prefix_table_name( 'log_visit' );
1029
1030 $suppress_errors = $wpdb->suppress_errors;
1031 $wpdb->suppress_errors( true );// prevent any of this showing in logs just in case
1032
1033 try {
1034 $time = gmdate( 'Y-m-d H:i:s', time() - $days_in_seconds );
1035 $sql = $wpdb->prepare( 'SELECT idsite from ' . $prefix_table . ' where visit_last_action_time > %s LIMIT 1', $time );
1036 $row = $wpdb->get_var( $sql );
1037 } catch ( Exception $e ) {
1038 $row = null;
1039 }
1040
1041 $wpdb->suppress_errors( $suppress_errors );
1042 // we need to differentiate between
1043 // 0 === had no visit
1044 // 1 === had visit
1045 // null === sum error... eg table was not correctly installed
1046 if ( null !== $row ) {
1047 $row = ! empty( $row );
1048 }
1049
1050 return $row;
1051 }
1052
1053 private function convert_time_to_date( $time, $in_blog_timezone, $print_diff = false ) {
1054 if ( empty( $time ) ) {
1055 return esc_html__( 'Unknown', 'matomo' );
1056 }
1057
1058 $date = gmdate( 'Y-m-d H:i:s', (int) $time );
1059
1060 if ( $in_blog_timezone ) {
1061 $date = get_date_from_gmt( $date, 'Y-m-d H:i:s' );
1062 }
1063
1064 if ( $print_diff && class_exists( '\Piwik\Metrics\Formatter' ) ) {
1065 try {
1066 $formatter = new \Piwik\Metrics\Formatter();
1067 $date .= ' (' . $formatter->getPrettyTimeFromSeconds( $time - time(), true, false ) . ')';
1068 } catch ( \Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
1069 // empty
1070 }
1071 }
1072
1073 return $date;
1074 }
1075
1076 private function add_diagnostic_results( $rows, $results ) {
1077 foreach ( $results as $result ) {
1078 $comment = '';
1079 /** @var DiagnosticResult $result */
1080 if ( $result->getStatus() !== DiagnosticResult::STATUS_OK ) {
1081 foreach ( $result->getItems() as $item ) {
1082 $item_comment = $item->getComment();
1083 if ( ! empty( $item_comment ) && is_string( $item_comment ) ) {
1084 if ( stripos( $item_comment, 'core:archive' ) > 0 ) {
1085 // we only want to keep the first sentence like " Archiving last ran successfully on Wednesday, January 2, 2019 00:00:00 which is 335 days 20:08:11 ago"
1086 // but not anything that asks user to set up a cronjob
1087 $item_comment = substr( $item_comment, 0, stripos( $item_comment, 'core:archive' ) );
1088 if ( strpos( $item_comment, '.' ) > 0 ) {
1089 $item_comment = substr( $item_comment, 0, strripos( $item_comment, '.' ) );
1090 } else {
1091 $item_comment = 'Archiving hasn\'t run in a while.';
1092 }
1093 }
1094 $comment .= $item_comment . '<br/>';
1095 }
1096 }
1097 }
1098
1099 $rows[] = [
1100 'name' => $result->getLabel(),
1101 'value' => $result->getStatus() . ' ' . $result->getLongErrorMessage(),
1102 'comment' => $comment,
1103 'is_warning' => $result->getStatus() === DiagnosticResult::STATUS_WARNING,
1104 'is_error' => $result->getStatus() === DiagnosticResult::STATUS_ERROR,
1105 ];
1106 }
1107
1108 return $rows;
1109 }
1110
1111 private function get_wordpress_info() {
1112 $is_multi_site = is_multisite();
1113 $num_blogs = 1;
1114 $is_network_enabled = false;
1115
1116 if ( $is_multi_site ) {
1117 if ( function_exists( 'get_blog_count' ) ) {
1118 $num_blogs = get_blog_count();
1119 }
1120 $settings = new Settings();
1121 $is_network_enabled = $settings->is_network_enabled();
1122 }
1123
1124 $rows = [];
1125 $rows[] = [
1126 'name' => 'Home URL',
1127 'value' => home_url(),
1128 ];
1129 $rows[] = [
1130 'name' => 'Site URL',
1131 'value' => site_url(),
1132 ];
1133 $rows[] = [
1134 'name' => 'WordPress Version',
1135 'value' => get_bloginfo( 'version' ),
1136 ];
1137 $rows[] = [
1138 'name' => 'Number of blogs',
1139 'value' => $num_blogs,
1140 ];
1141 $rows[] = [
1142 'name' => 'Multisite Enabled',
1143 'value' => $is_multi_site,
1144 ];
1145 $rows[] = [
1146 'name' => 'Network Enabled',
1147 'value' => $is_network_enabled,
1148 ];
1149 $consts = [
1150 'WP_DEBUG',
1151 'WP_DEBUG_DISPLAY',
1152 'WP_DEBUG_LOG',
1153 'DISABLE_WP_CRON',
1154 'FORCE_SSL_ADMIN',
1155 'WP_CACHE',
1156 'CONCATENATE_SCRIPTS',
1157 'COMPRESS_SCRIPTS',
1158 'COMPRESS_CSS',
1159 'ENFORCE_GZIP',
1160 'WP_LOCAL_DEV',
1161 'WP_CONTENT_URL',
1162 'WP_CONTENT_DIR',
1163 'UPLOADS',
1164 'BLOGUPLOADDIR',
1165 'DIEONDBERROR',
1166 'WPLANG',
1167 'ALTERNATE_WP_CRON',
1168 'WP_CRON_LOCK_TIMEOUT',
1169 'WP_DISABLE_FATAL_ERROR_HANDLER',
1170 'MATOMO_SUPPORT_ASYNC_ARCHIVING',
1171 'MATOMO_ENABLE_TAG_MANAGER',
1172 'MATOMO_SUPPRESS_DB_ERRORS',
1173 'MATOMO_ENABLE_AUTO_UPGRADE',
1174 'MATOMO_DEBUG',
1175 'MATOMO_SAFE_MODE',
1176 'MATOMO_GLOBAL_UPLOAD_DIR',
1177 'MATOMO_LOGIN_REDIRECT',
1178 ];
1179 foreach ( $consts as $const ) {
1180 $rows[] = [
1181 'name' => $const,
1182 'value' => defined( $const ) ? constant( $const ) : '-',
1183 ];
1184 }
1185
1186 $rows[] = [
1187 'name' => 'Permalink Structure',
1188 'value' => get_option( 'permalink_structure' ) ? get_option( 'permalink_structure' ) : 'Default',
1189 ];
1190
1191 $rows[] = [
1192 'name' => 'Possibly uses symlink',
1193 'value' => strpos( __DIR__, ABSPATH ) === false && strpos( __DIR__, WP_CONTENT_DIR ) === false,
1194 ];
1195
1196 $upload_dir = wp_upload_dir();
1197 $rows[] = [
1198 'name' => 'Upload base url',
1199 'value' => $upload_dir['baseurl'],
1200 ];
1201
1202 $rows[] = [
1203 'name' => 'Upload base dir',
1204 'value' => $upload_dir['basedir'],
1205 ];
1206
1207 $rows[] = [
1208 'name' => 'Upload url',
1209 'value' => $upload_dir['url'],
1210 ];
1211
1212 foreach ( [ 'upload_path', 'upload_url_path' ] as $option_read ) {
1213 $rows[] = [
1214 'name' => 'Custom ' . $option_read,
1215 'value' => get_option( $option_read ),
1216 ];
1217 }
1218
1219 if ( is_plugin_active( 'wp-piwik/wp-piwik.php' ) ) {
1220 $rows[] = [
1221 'name' => 'Connect Matomo (WP-Matomo) activated',
1222 'value' => true,
1223 'is_warning' => true,
1224 'comment' => sprintf( esc_html__( 'It is usually not recommended or needed to run Matomo for WordPress and Connect Matomo at the same time. To learn more about the differences between the two plugins view this %s', 'matomo' ), sprintf( '<a href="%s" target="_blank">%s</a>', 'https://matomo.org/faq/wordpress/why-are-there-two-different-matomo-for-wordpress-plugins-what-is-the-difference-to-wp-matomo-integration-plugin/', esc_html__( 'URL', 'matomo' ) ) ),
1225 ];
1226
1227 $mode = get_option( 'wp-piwik_global-piwik_mode' );
1228 if ( function_exists( 'get_site_option' ) && is_plugin_active_for_network( 'wp-piwik/wp-piwik.php' ) ) {
1229 $mode = get_site_option( 'wp-piwik_global-piwik_mode' );
1230 }
1231 if ( ! empty( $mode ) ) {
1232 $rows[] = [
1233 'name' => 'Connect Matomo mode',
1234 'value' => $mode,
1235 'is_warning' => 'php' === $mode || 'PHP' === $mode,
1236 'comment' => esc_html__( 'Connect Matomo is configured in "PHP mode". This is known to cause issues with Matomo for WordPress. We recommend you either deactivate Connect Matomo or you go "Settings => Connect Matomo" and change the "Matomo Mode" in the "Connect to Matomo" section to "Self-hosted HTTP API".', 'matomo' ),
1237 ];
1238 }
1239 }
1240
1241 $compatible_content_dir = matomo_has_compatible_content_dir();
1242 if ( true === $compatible_content_dir ) {
1243 $rows[] = [
1244 'name' => 'Compatible content directory',
1245 'value' => true,
1246 ];
1247 } else {
1248 $rows[] = [
1249 'name' => 'Compatible content directory',
1250 'value' => $compatible_content_dir,
1251 'is_warning' => true,
1252 'comment' => esc_html__( 'It looks like you are maybe using a custom WordPress content directory. The Matomo reporting/admin pages might not work. You may be able to workaround this.', 'matomo' ) . ' ' . sprintf( '<a href="%s" target="_blank">%s</a>', 'https://matomo.org/faq/wordpress/how-do-i-make-matomo-for-wordpress-work-when-i-have-a-custom-content-directory/', esc_html__( 'Learn more', 'matomo' ) ),
1253 ];
1254 }
1255
1256 $paths = new Paths();
1257 $paths->get_file_system();
1258
1259 $filesystem_init_succeeded = $paths->get_host_init_filesystem_succeeded();
1260 $rows[] = [
1261 'name' => 'WP_Filesystem Initialized',
1262 'value' => $filesystem_init_succeeded,
1263 'is_warning' => ! $filesystem_init_succeeded,
1264 'comment' => $filesystem_init_succeeded
1265 ? null
1266 : esc_html__( 'The WordPress Filesystem abstraction was not initialized correctly (WP_Filesystem() returned false). This indicates a WordPress or server configuration issue which may cause problems in Matomo and other plugins. To resolve it, contact your hosting provider.', 'matomo' ),
1267 ];
1268
1269 return $rows;
1270 }
1271
1272 private function add_maxminddb_row( &$rows, $maxmind_db_loaded ) {
1273 $rows[] = [
1274 'name' => esc_html__( 'PHP Maxmind DB extension', 'matomo' ),
1275 'value' => $maxmind_db_loaded ? __( 'Loaded', 'matomo' ) : __( 'Not loaded', 'matomo' ),
1276 'comment' => $maxmind_db_loaded ? sprintf( esc_html__( 'You may encounter %s', 'matomo' ), sprintf( '<a href="%s" target="_blank">%s</a>', 'https://matomo.org/faq/troubleshooting/how-do-i-fix-the-error-call-to-undefined-method-maxminddbreadergetwithprefixlen/', esc_html__( 'the following problem', 'matomo' ) ) ) : '',
1277 'is_warning' => $maxmind_db_loaded,
1278 ];
1279 }
1280
1281 private function get_server_info() {
1282 $rows = [];
1283
1284 if ( ! empty( $_SERVER['SERVER_SOFTWARE'] ) ) {
1285 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
1286 $server_software = sanitize_text_field( $_SERVER['SERVER_SOFTWARE'] );
1287 $rows[] = [
1288 'name' => 'Server Info',
1289 'value' => $server_software,
1290 ];
1291 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1292 if ( strpos( $server_software, 'Apache' ) !== false ) {
1293 $url = plugins_url( 'app', MATOMO_ANALYTICS_FILE ) . '/index.php';
1294 $result = wp_remote_post(
1295 $url,
1296 array(
1297 'method' => 'GET',
1298 'sslverify' => false,
1299 'timeout' => defined( 'MATOMO_LOCAL_ENVIRONMENT' ) && MATOMO_LOCAL_ENVIRONMENT ? 5 : 2,
1300 )
1301 );
1302 if ( is_array( $result ) ) {
1303 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
1304 $file_content = file_get_contents( dirname( MATOMO_ANALYTICS_FILE ) . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . '.htaccess' );
1305 if ( strpos( $file_content, 'AddHandler' ) && ! strpos( $file_content, '# AddHandler' ) ) {
1306 switch ( (int) $result['response']['code'] ) {
1307 case 500:
1308 $value = __( 'To be confirmed', 'matomo' );
1309 $comment = sprintf( esc_html__( 'The AddHandler Apache directive maybe disabled. If you get a 500 error code when accessing Matomo, please read this %s', 'matomo' ), sprintf( '<a href="%s" target="_blank">%s<a/>', 'https://matomo.org/faq/wordpress/how-do-i-fix-the-error-addhandler-not-allowed-here/', esc_html__( 'FAQ', 'matomo' ) ) );
1310 $is_error = true;
1311 break;
1312 default:
1313 $value = __( 'Supported', 'matomo' );
1314 $comment = '';
1315 $is_error = false;
1316 }
1317 $rows[] = [
1318 'name' => 'Apache AddHandler support',
1319 'value' => $value,
1320 'comment' => $comment,
1321 'is_error' => $is_error,
1322 ];
1323 }
1324 }
1325 }
1326 }
1327
1328 if ( PHP_OS ) {
1329 $rows[] = [
1330 'name' => 'PHP OS',
1331 'value' => PHP_OS,
1332 ];
1333 }
1334 $rows[] = [
1335 'name' => 'PHP Version',
1336 'value' => phpversion(),
1337 ];
1338 $rows[] = [
1339 'name' => 'PHP SAPI',
1340 'value' => php_sapi_name(),
1341 ];
1342 if ( defined( 'PHP_BINARY' ) && PHP_BINARY ) {
1343 $rows[] = [
1344 'name' => 'PHP Binary Name',
1345 'value' => PHP_BINARY,
1346 ];
1347 }
1348
1349 $this->add_maxminddb_row( $rows, extension_loaded( 'maxminddb' ) );
1350
1351 // we report error reporting before matomo bootstraped and after to see if Matomo changed it successfully etc
1352 $rows[] = [
1353 'name' => 'PHP Error Reporting',
1354 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting
1355 'value' => $this->initial_error_reporting . ' After bootstrap: ' . @error_reporting(),
1356 ];
1357
1358 if ( ! empty( $this->get_php_cli_binary() ) ) {
1359 $rows[] = [
1360 'name' => 'PHP Found Binary',
1361 'value' => $this->get_php_cli_binary(),
1362 ];
1363 }
1364 $rows[] = [
1365 'name' => 'Timezone',
1366 'value' => date_default_timezone_get(),
1367 ];
1368 if ( function_exists( 'wp_timezone_string' ) ) {
1369 $rows[] = [
1370 'name' => 'WP timezone',
1371 'value' => wp_timezone_string(),
1372 ];
1373 }
1374 $rows[] = [
1375 'name' => 'Locale',
1376 'value' => get_locale(),
1377 ];
1378 if ( function_exists( 'get_user_locale' ) ) {
1379 $rows[] = [
1380 'name' => 'User Locale',
1381 'value' => get_user_locale(),
1382 ];
1383 }
1384
1385 $rows[] = [
1386 'name' => 'Memory Limit',
1387 'value' => @ini_get( 'memory_limit' ),
1388 'comment' => sprintf( esc_html__( 'At least %1$dMB recommended. Depending on your traffic %2$dMB or more may be needed.', 'matomo' ), 128, 256 ),
1389 ];
1390
1391 $rows[] = [
1392 'name' => 'WP Memory Limit',
1393 'value' => defined( 'WP_MEMORY_LIMIT' ) ? WP_MEMORY_LIMIT : '',
1394 'comment' => '',
1395 ];
1396
1397 $rows[] = [
1398 'name' => 'WP Max Memory Limit',
1399 'value' => defined( 'WP_MAX_MEMORY_LIMIT' ) ? WP_MAX_MEMORY_LIMIT : '',
1400 'comment' => '',
1401 ];
1402
1403 if ( function_exists( 'timezone_version_get' ) ) {
1404 $rows[] = [
1405 'name' => 'Timezone version',
1406 'value' => timezone_version_get(),
1407 ];
1408 }
1409
1410 $rows[] = [
1411 'name' => 'Time',
1412 'value' => time(),
1413 ];
1414
1415 $rows[] = [
1416 'name' => 'Max Execution Time',
1417 'value' => ini_get( 'max_execution_time' ),
1418 ];
1419 $rows[] = [
1420 'name' => 'Max Post Size',
1421 'value' => ini_get( 'post_max_size' ),
1422 ];
1423 $rows[] = [
1424 'name' => 'Max Upload Size',
1425 'value' => wp_max_upload_size(),
1426 ];
1427 $rows[] = [
1428 'name' => 'Max Input Vars',
1429 'value' => ini_get( 'max_input_vars' ),
1430 ];
1431
1432 $disabled_functions = ini_get( 'disable_functions' );
1433 $rows[] = [
1434 'name' => 'Disabled PHP functions',
1435 'value' => ! empty( $disabled_functions ),
1436 'comment' => ! empty( $disabled_functions ) ? $disabled_functions : '',
1437 ];
1438
1439 $zlib_compression = ini_get( 'zlib.output_compression' );
1440 $row = [
1441 'name' => 'zlib.output_compression is off',
1442 'value' => '1' !== $zlib_compression,
1443 ];
1444
1445 if ( '1' === $zlib_compression ) {
1446 $row['is_error'] = true;
1447 $row['comment'] = esc_html__( 'You need to set "zlib.output_compression" in your php.ini to "Off".', 'matomo' );
1448 }
1449 $rows[] = $row;
1450
1451 if ( function_exists( 'curl_version' ) ) {
1452 $curl_version = curl_version();
1453 $curl_version = $curl_version['version'] . ', ' . $curl_version['ssl_version'];
1454 $rows[] = [
1455 'name' => 'Curl Version',
1456 'value' => $curl_version,
1457 ];
1458 }
1459
1460 $suhosin_installed = ( extension_loaded( 'suhosin' ) || ( defined( 'SUHOSIN_PATCH' ) && constant( 'SUHOSIN_PATCH' ) ) );
1461 $rows[] = [
1462 'name' => 'Suhosin installed',
1463 'value' => ! empty( $suhosin_installed ),
1464 'comment' => '',
1465 ];
1466
1467 return $rows;
1468 }
1469
1470 private function get_browser_info() {
1471 $rows = [];
1472
1473 if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
1474 $rows[] = [
1475 'name' => 'Browser',
1476 'value' => '',
1477 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
1478 'comment' => sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ),
1479 ];
1480 }
1481 if ( ! WpMatomo::is_safe_mode() ) {
1482 Bootstrap::do_bootstrap();
1483 try {
1484 if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
1485 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
1486 $detector = StaticContainer::get( DeviceDetectorFactory::class )->makeInstance( sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ) );
1487 $client = $detector->getClient();
1488 if ( ! empty( $client['name'] ) && 'Microsoft Edge' === $client['name'] && (int) $client['version'] >= 85 ) {
1489 $rows[] = [
1490 'name' => 'Browser Compatibility',
1491 'is_warning' => true,
1492 'value' => 'Yes',
1493 'comment' => esc_html__( 'Because you are using MS Edge browser, you may see a warning like "This site has been reported as unsafe" from "Microsoft Defender SmartScreen" when you view the Matomo Reporting, Admin or Tag Manager page. This is a false alert and you can safely ignore this warning by clicking on the icon next to the URL (in the address bar) and choosing either "Report as safe" (preferred) or "Show unsafe content". We are hoping to get this false warning removed in the future.', 'matomo' ),
1494 ];
1495 }
1496 }
1497 } catch ( Exception $e ) {
1498 $rows[] = [
1499 'name' => 'Browser Compatibility',
1500 'is_error' => true,
1501 'value' => 'Failed to use Device Detector.',
1502 'comment' => $e->getMessage(),
1503 ];
1504 }
1505
1506 $rows[] = [
1507 'name' => 'Language',
1508 'value' => Common::getBrowserLanguage(),
1509 'comment' => '',
1510 ];
1511 }
1512
1513 return $rows;
1514 }
1515
1516 private function get_db_info() {
1517 global $wpdb;
1518 $rows = [];
1519
1520 $rows[] = [
1521 'name' => 'MySQL Version',
1522 'value' => ! empty( $wpdb->is_mysql ) ? $wpdb->db_version() : '',
1523 'comment' => '',
1524 ];
1525
1526 $rows[] = [
1527 'name' => 'Mysqli Connect',
1528 'value' => function_exists( 'mysqli_connect' ),
1529 'comment' => '',
1530 ];
1531 $rows[] = [
1532 'name' => 'Force MySQL over Mysqli',
1533 'value' => defined( 'WP_USE_EXT_MYSQL' ) && WP_USE_EXT_MYSQL,
1534 'comment' => '',
1535 ];
1536
1537 $rows[] = [
1538 'name' => 'DB Prefix',
1539 'value' => $wpdb->prefix,
1540 ];
1541
1542 $rows[] = [
1543 'name' => 'DB CHARSET',
1544 'value' => defined( 'DB_CHARSET' ) ? DB_CHARSET : '',
1545 ];
1546
1547 $rows[] = [
1548 'name' => 'DB COLLATE',
1549 'value' => defined( 'DB_COLLATE' ) ? DB_COLLATE : '',
1550 ];
1551
1552 $rows[] = [
1553 'name' => 'SHOW ERRORS',
1554 'value' => ! empty( $wpdb->show_errors ),
1555 ];
1556
1557 $rows[] = [
1558 'name' => 'SUPPRESS ERRORS',
1559 'value' => ! empty( $wpdb->suppress_errors ),
1560 ];
1561
1562 if ( method_exists( $wpdb, 'parse_db_host' ) ) {
1563 $host_data = $wpdb->parse_db_host( DB_HOST );
1564 if ( $host_data ) {
1565 list( $host, $port, $socket, $is_ipv6 ) = $host_data;
1566 }
1567
1568 $rows[] = [
1569 'name' => 'Uses Socket',
1570 'value' => ! empty( $socket ),
1571 ];
1572 $rows[] = [
1573 'name' => 'Uses IPv6',
1574 'value' => ! empty( $is_ipv6 ),
1575 ];
1576 }
1577
1578 $rows[] = [
1579 'name' => 'Matomo tables found',
1580 'value' => $this->get_num_matomo_tables(),
1581 ];
1582
1583 $missing_tables = $this->get_missing_tables();
1584 $has_missing_tables = ( count( $missing_tables ) > 0 );
1585 $rows[] = [
1586 'name' => 'DB tables exist',
1587 'value' => ( ! $has_missing_tables ),
1588 'comment' => $has_missing_tables ? sprintf( esc_html__( 'Some tables may be missing: %s', 'matomo' ), implode( ', ', $missing_tables ) ) : '',
1589 'is_error' => $has_missing_tables,
1590 ];
1591
1592 foreach ( [ 'user', 'site' ] as $table ) {
1593 $row = [
1594 'name' => 'Matomo ' . $table . 's found',
1595 'value' => $this->get_num_entries_in_table( $table ),
1596 ];
1597 if ( 'site' === $table ) {
1598 if ( ( ! is_multisite() ) && ( $row['value'] > 1 ) ) {
1599 $row['is_warning'] = true;
1600 $row['comment'] = esc_html__( 'There is an error in your Matomo records. Please contact wordpress@matomo.org', 'matomo' );
1601 }
1602 }
1603 $rows[] = $row;
1604 }
1605
1606 $grants = $this->get_db_grants();
1607
1608 // we only show these grants for security reasons as only they are needed and we don't need to know any other ones
1609 $needed_grants = [
1610 'SELECT',
1611 'INSERT',
1612 'UPDATE',
1613 'INDEX',
1614 'DELETE',
1615 'CREATE',
1616 'DROP',
1617 'ALTER',
1618 'CREATE TEMPORARY TABLES',
1619 'LOCK TABLES',
1620 ];
1621 if ( in_array( 'ALL PRIVILEGES', $grants, true ) ) {
1622 // ALL PRIVILEGES may be used pre MySQL 8.0
1623 $grants = $needed_grants;
1624 }
1625
1626 $grants_missing = array_diff( $needed_grants, $grants );
1627
1628 if ( empty( $grants )
1629 || ! is_array( $grants )
1630 || count( $grants_missing ) === count( $needed_grants )
1631 ) {
1632 $rows[] = [
1633 'name' => esc_html__( 'Required permissions', 'matomo' ),
1634 'value' => esc_html__( 'Failed to detect granted permissions', 'matomo' ),
1635 'comment' => esc_html__( 'Please check your MySQL user has these permissions (grants):', 'matomo' ) . '<br />' . implode( ', ', $needed_grants ),
1636 'is_warning' => false,
1637 ];
1638 } else {
1639 if ( ! empty( $grants_missing ) ) {
1640 $rows[] = [
1641 'name' => esc_html__( 'Required permissions', 'matomo' ),
1642 'value' => esc_html__( 'Error', 'matomo' ),
1643 'comment' => esc_html__( 'Missing permissions', 'matomo' ) . ': ' . implode( ', ', $grants_missing ) . '. ' . esc_html__( 'Please check if any of these MySQL permission (grants) are missing and add them if needed.', 'matomo' ) . ' ' . sprintf( '<a href="https://matomo.org/faq/troubleshooting/how-do-i-check-if-my-mysql-user-has-all-required-grants/" target="_blank">%s</a>', __( 'Learn more', 'matomo' ) ),
1644 'is_warning' => true,
1645 ];
1646 } else {
1647 $rows[] = [
1648 'name' => esc_html__( 'Required permissions', 'matomo' ),
1649 'value' => esc_html__( 'OK', 'matomo' ),
1650 'comment' => '',
1651 'is_warning' => false,
1652 ];
1653 }
1654 }
1655
1656 return $rows;
1657 }
1658
1659 /**
1660 * @return string[]
1661 */
1662 public function get_missing_tables() {
1663 global $wpdb;
1664
1665 $required_matomo_tables = $this->db_settings->get_matomo_tables();
1666 $required_matomo_tables = array_map( [ $this->db_settings, 'prefix_table_name' ], $required_matomo_tables );
1667
1668 $existing_tables = [];
1669 try {
1670 $prefix = $this->db_settings->prefix_table_name( '' );
1671 $existing_tables = $wpdb->get_col( 'SHOW TABLES LIKE "' . $prefix . '%"' );
1672 } catch ( Exception $e ) {
1673 $this->logger->log( 'no show tables: ' . $e->getMessage() );
1674 }
1675
1676 return array_diff( $required_matomo_tables, $existing_tables );
1677 }
1678
1679 private function get_num_entries_in_table( $table ) {
1680 global $wpdb;
1681
1682 $prefix = $this->db_settings->prefix_table_name( $table );
1683
1684 $results = null;
1685 try {
1686 $results = $wpdb->get_var( 'select count(*) from ' . $prefix );
1687 } catch ( Exception $e ) {
1688 $this->logger->log( 'no count(*): ' . $e->getMessage() );
1689 }
1690
1691 if ( isset( $results ) && is_numeric( $results ) ) {
1692 return $results;
1693 }
1694
1695 return 'table not exists';
1696 }
1697
1698 private function get_num_matomo_tables() {
1699 global $wpdb;
1700
1701 $prefix = $this->db_settings->prefix_table_name( '' );
1702
1703 $results = null;
1704 try {
1705 $results = $wpdb->get_results( 'show tables like "' . $prefix . '%"' );
1706 } catch ( Exception $e ) {
1707 $this->logger->log( 'no show tables: ' . $e->getMessage() );
1708 }
1709
1710 if ( is_array( $results ) ) {
1711 return count( $results );
1712 }
1713
1714 return 'show tables not working';
1715 }
1716
1717 private function get_db_grants() {
1718 global $wpdb;
1719
1720 $suppress_errors = $wpdb->suppress_errors;
1721 $wpdb->suppress_errors( true );// prevent any of this showing in logs just in case
1722
1723 try {
1724 $values = $wpdb->get_results( 'SHOW GRANTS', ARRAY_N );
1725 } catch ( Exception $e ) {
1726 // We ignore any possible error in case of permission or not supported etc.
1727 $values = [];
1728 }
1729
1730 $wpdb->suppress_errors( $suppress_errors );
1731
1732 $grants = [];
1733 foreach ( $values as $index => $value ) {
1734 if ( empty( $value[0] ) || ! is_string( $value[0] ) ) {
1735 continue;
1736 }
1737
1738 if ( stripos( $value[0], 'ALL PRIVILEGES' ) !== false ) {
1739 return [ 'ALL PRIVILEGES' ]; // the split on empty string wouldn't work otherwise
1740 }
1741
1742 foreach ( [ ' ON ', ' TO ', ' IDENTIFIED ', ' BY ' ] as $keyword ) {
1743 if ( stripos( $values[ $index ][0], $keyword ) !== false ) {
1744 // make sure to never show by any accident a db user or password by cutting anything after on/to
1745 $values[ $index ][0] = substr( $values[ $index ][0], 0, stripos( $values[ $index ][0], $keyword ) );
1746 }
1747 if ( stripos( $values[ $index ][0], 'GRANT' ) !== false ) {
1748 // otherwise we end up having "grant select"... instead of just "select"
1749 $values[ $index ][0] = substr( $values[ $index ][0], stripos( $values[ $index ][0], 'GRANT' ) + 5 );
1750 }
1751 }
1752 // make sure to never show by any accident a db user or password
1753 $values[ $index ][0] = str_replace(
1754 [ DB_USER, DB_PASSWORD ],
1755 [
1756 'DB_USER',
1757 'DB_PASS',
1758 ],
1759 $values[ $index ][0]
1760 );
1761
1762 $grants = array_merge( $grants, explode( ',', $values[ $index ][0] ) );
1763 }
1764 $grants = array_map( 'trim', $grants );
1765 $grants = array_map( 'strtoupper', $grants );
1766 $grants = array_unique( $grants );
1767
1768 return $grants;
1769 }
1770
1771 /**
1772 * @return string[]
1773 */
1774 private function get_actives_plugins() {
1775 $active_plugins = get_option( 'active_plugins', [] );
1776 if ( ! empty( $active_plugins ) && is_array( $active_plugins ) ) {
1777 $plugins = get_plugins();
1778 $active_plugins = array_map(
1779 function ( $active_plugin ) use ( $plugins ) {
1780 $plugin_version = isset( $plugins[ $active_plugin ]['Version'] ) ? $plugins[ $active_plugin ]['Version'] : null;
1781 $result_suffix = $plugin_version ? ( ':' . $plugin_version ) : '';
1782 $parts = explode( '/', trim( $active_plugin ) );
1783 return trim( $parts[0] ) . $result_suffix;
1784 },
1785 $active_plugins
1786 );
1787 }
1788
1789 return $active_plugins;
1790 }
1791
1792 private function get_plugins_info() {
1793 $rows = [];
1794 $mu_plugins = get_mu_plugins();
1795
1796 if ( ! empty( $mu_plugins ) ) {
1797 $rows[] = [
1798 'section' => 'MU Plugins',
1799 ];
1800
1801 foreach ( $mu_plugins as $mu_pin ) {
1802 $comment = '';
1803 if ( ! empty( $plugin['Network'] ) ) {
1804 $comment = esc_html__( 'Network enabled', 'matomo' );
1805 }
1806 $rows[] = [
1807 'name' => $mu_pin['Name'],
1808 'value' => $mu_pin['Version'],
1809 'comment' => $comment,
1810 ];
1811 }
1812
1813 $rows[] = [
1814 'section' => 'Plugins',
1815 ];
1816 }
1817
1818 $plugins = get_plugins();
1819
1820 foreach ( $plugins as $plugin_file => $plugin ) {
1821 $comment = '';
1822 if ( ! empty( $plugin['Network'] ) ) {
1823 $comment = esc_html__( 'Network enabled', 'matomo' );
1824 }
1825
1826 if ( strpos( $plugin_file, 'ninjafirewall/' ) === 0 ) {
1827 $comment .= '<br/><br/>' . esc_html__( 'We noticed you are using Matomo with Ninja Firewall. This can result in Matomo cache file changes showing up in Ninja Firewall which likely undesired.', 'matomo' ) . '
1828 <a href="https://matomo.org/faq/wordpress/how-do-i-prevent-matomo-cache-file-changes-to-show-up-in-ninja-firewall/" rel="noreferrer noopener" target="_blank">' . esc_html__( 'Read our FAQ to learn how to prevent these entries.', 'matomo' ) . '</a>';
1829 }
1830
1831 $rows[] = [
1832 'name' => $plugin['Name'],
1833 'value' => $plugin['Version'],
1834 'comment' => $comment,
1835 ];
1836 }
1837
1838 $active_plugins_with_version = $this->get_actives_plugins();
1839
1840 if ( ! empty( $active_plugins_with_version ) && is_array( $active_plugins_with_version ) ) {
1841 $rows[] = [
1842 'name' => 'Active Plugins',
1843 'value' => count( $active_plugins_with_version ),
1844 'comment' => implode( ' ', $active_plugins_with_version ),
1845 ];
1846
1847 $active_plugins = array_map(
1848 function ( $plugin_with_version ) {
1849 $parts = explode( ':', $plugin_with_version );
1850 return $parts[0];
1851 },
1852 $active_plugins_with_version
1853 );
1854
1855 $used_not_compatible = array_intersect( $active_plugins, $this->not_compatible_plugins );
1856 if ( ! empty( $used_not_compatible ) ) {
1857 $additional_comment = '';
1858 if ( count( $used_not_compatible ) ) {
1859 $rows[] = [
1860 'name' => __( 'Not compatible plugins', 'matomo' ),
1861 'value' => count( $used_not_compatible ),
1862 'comment' => implode( ', ', $used_not_compatible ) . '<br><br>' . sprintf( esc_html__( 'Matomo may work fine when using these plugins but there may be some issues. For more information %1$sSee %2$s', 'matomo' ), '<br/>', sprintf( '<a href="%s" target="_blank">%s</a>', 'https://matomo.org/faq/wordpress/which-plugins-is-matomo-for-wordpress-known-to-be-not-compatible-with/', esc_html__( 'this FAQ', 'matomo' ) ) ) . $additional_comment,
1863 'is_warning' => true,
1864 'is_error' => false,
1865 ];
1866 }
1867 }
1868 }
1869
1870 $rows[] = [
1871 'name' => 'Theme',
1872 'value' => function_exists( 'get_template' ) ? get_template() : '',
1873 'comment' => get_option( 'stylesheet' ),
1874 ];
1875
1876 if ( is_plugin_active( 'better-wp-security/better-wp-security.php' ) ) {
1877 if ( class_exists( 'ITSEC_Modules' ) ) {
1878 if ( method_exists( '\ITSEC_Modules', 'get_setting' ) ) {
1879 $input = ITSEC_Modules::get_settings( 'system-tweaks' );
1880 // old plugin versions
1881 $long_url_strings_options = [ 'long_url_strings', 'st_longurl' ];
1882 $long_url_strings_enabled = false;
1883 foreach ( $long_url_strings_options as $option ) {
1884 if ( isset( $input[ $option ] ) && $input[ $option ] ) {
1885 $long_url_strings_enabled = true;
1886 }
1887 }
1888 if ( $long_url_strings_enabled ) {
1889 $rows[] = [
1890 'name' => "iThemes Security 'Long URLs' Enabled",
1891 'value' => true,
1892 'comment' => esc_html__( 'Tracking might not work because it looks like you have Long URLs disabled in iThemes Security. To fix this please contact ithemes security support.', 'matomo' ),
1893 'is_error' => true,
1894 ];
1895 }
1896 if ( $input['plugins_php'] ) {
1897 $rows[] = [
1898 'name' => "iThemes Security 'Disable PHP in plugins' Enabled",
1899 'value' => true,
1900 'comment' => esc_html__( 'You have disabled the PHP usage in the plugins folder from your ithemes security plugin. Matomo won\'t work in this configuration. You must uncheck the checkbox "Security > Settings > Advanced > System tweaks > Disable PHP in plugins."', 'matomo' ),
1901 'is_error' => true,
1902 ];
1903 }
1904 }
1905 }
1906 }
1907
1908 if ( is_plugin_active( 'secupress/secupress.php' ) ) {
1909 if ( function_exists( 'secupress_is_submodule_active' ) ) {
1910 $blocked_methods = (int) secupress_is_submodule_active( 'firewall', 'request-methods-header' );
1911 if ( $blocked_methods ) {
1912 if ( ! defined( 'MATOMO_SUPPORT_ASYNC_ARCHIVING' ) || MATOMO_SUPPORT_ASYNC_ARCHIVING ) {
1913 $rows[] = [
1914 'name' => "Secupress 'Block Bad Request Methods' Enabled",
1915 'value' => true,
1916 'comment' => esc_html__( "If reports aren't being generated then you may need to disable the feature \"Firewall -> Block Bad Request Methods\" in SecuPress (if it is enabled) or add the following line to your \"wp-config.php\"", 'matomo' ) . ": <br><code>define( 'MATOMO_SUPPORT_ASYNC_ARCHIVING', false );</code>.",
1917 'is_error' => true,
1918 ];
1919 }
1920 }
1921 }
1922 }
1923
1924 return $rows;
1925 }
1926
1927 /**
1928 * Convert the hexadecimal colors in the content into their rgb values
1929 *
1930 * @param string $content
1931 *
1932 * @return string
1933 */
1934 private function replace_hexadecimal_colors( $content ) {
1935 $matches = array();
1936 if ( preg_match_all( '/ (#(([a-f0-9]{8})|([a-f0-9]{4}[ ;])))/i', $content, $matches ) ) {
1937 foreach ( $matches[1] as $hexadecimal_color ) {
1938 switch ( strlen( $hexadecimal_color ) ) {
1939 case 9:
1940 list( $r, $g, $b, $a ) = sscanf( $hexadecimal_color, '#%02x%02x%02x%02x' );
1941 break;
1942 case 6:
1943 $hexadecimal_color = substr( $hexadecimal_color, 0, 5 );
1944 list( $r, $g, $b, $a ) = sscanf( $hexadecimal_color, '#%01x%01x%01x%01x' );
1945 break;
1946 }
1947 $content = str_replace( $hexadecimal_color, 'rgb(' . $r . ',' . $g . ',' . $b . ',' . $a . ')', $content );
1948 }
1949 }
1950 if ( preg_match_all( '/ (#(([a-f0-9]{6})|([a-f0-9]{3})))/i', $content, $matches ) ) {
1951 foreach ( $matches[1] as $hexadecimal_color ) {
1952 switch ( strlen( $hexadecimal_color ) ) {
1953 case 7:
1954 list( $r, $g, $b ) = sscanf( $hexadecimal_color, '#%02x%02x%02x' );
1955 break;
1956 case 4:
1957 list( $r, $g, $b ) = sscanf( $hexadecimal_color, '#%01x%01x%01x' );
1958 break;
1959 }
1960 $content = str_replace( $hexadecimal_color, 'rgb(' . $r . ',' . $g . ',' . $b . ')', $content );
1961 }
1962 }
1963
1964 return $content;
1965 }
1966
1967 private function get_abs_path_to_plugin() {
1968 if ( is_dir( ABSPATH . '/wp-content/plugins/matomo' ) ) {
1969 return ABSPATH . '/wp-content/plugins/matomo';
1970 } elseif ( is_dir( ABSPATH . '/wp-content/mu-plugins/matomo' ) ) {
1971 return ABSPATH . '/wp-content/mu-plugins/matomo';
1972 } else {
1973 return __DIR__ . '/../../..';
1974 }
1975 }
1976
1977 private function find_wp_load_path() {
1978 $abs_path = rtrim( ABSPATH, '/' );
1979 $search_dir = $this->get_abs_path_to_plugin();
1980
1981 while ( ! is_file( $search_dir . '/wp-load.php' ) && strpos( $search_dir, $abs_path ) === 0 ) {
1982 $search_dir = dirname( $search_dir );
1983 }
1984
1985 if ( strpos( $search_dir, $abs_path ) !== 0 ) {
1986 return null;
1987 }
1988
1989 return $search_dir . '/wp-load.php';
1990 }
1991
1992 private function get_php_cli_binary() {
1993 if ( ! $this->binary && ! WpMatomo::is_safe_mode() ) {
1994 Bootstrap::do_bootstrap();
1995 $cli_php = new CliPhp();
1996 $this->binary = $cli_php->findPhpBinary();
1997 }
1998
1999 return $this->binary;
2000 }
2001 }
2002