PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.12.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.12.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 3 years ago AccessSettings.php 4 years ago Admin.php 4 years ago AdminSettings.php 4 years ago AdminSettingsInterface.php 6 years ago AdvancedSettings.php 4 years ago Chart.php 4 years ago CookieConsent.php 4 years ago Dashboard.php 4 years ago ExclusionSettings.php 4 years ago GeolocationSettings.php 4 years ago GetStarted.php 4 years ago ImportWpStatistics.php 4 years ago Info.php 4 years ago InvalidIpException.php 4 years ago Marketplace.php 4 years ago Menu.php 4 years ago PrivacySettings.php 4 years ago SafeModeMenu.php 4 years ago Summary.php 4 years ago SystemReport.php 3 years ago TrackingSettings.php 4 years ago
SystemReport.php
1568 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\Common;
16 use Piwik\Config;
17 use Piwik\Container\StaticContainer;
18 use Piwik\DeviceDetector\DeviceDetectorFactory;
19 use Piwik\Filesystem;
20 use Piwik\Plugin;
21 use Piwik\Plugins\CoreAdminHome\API;
22 use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult;
23 use Piwik\Plugins\Diagnostics\DiagnosticService;
24 use Piwik\Plugins\UserCountry\LocationProvider;
25 use Piwik\SettingsPiwik;
26 use Piwik\Tracker\Failures;
27 use Piwik\Version;
28 use WpMatomo;
29 use WpMatomo\Bootstrap;
30 use WpMatomo\Capabilities;
31 use WpMatomo\Installer;
32 use WpMatomo\Logger;
33 use WpMatomo\Paths;
34 use WpMatomo\ScheduledTasks;
35 use WpMatomo\Settings;
36 use WpMatomo\Site;
37 use WpMatomo\Site\Sync as SiteSync;
38 use WpMatomo\Updater;
39 use WpMatomo\User\Sync as UserSync;
40
41 if ( ! defined( 'ABSPATH' ) ) {
42 exit; // if accessed directly
43 }
44
45 /**
46 * error_reporting is required for this page
47 * phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_error_reporting
48 *
49 * We want a real data, not something coming from cache
50 * phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
51 *
52 * This is a report error, so silent the possible errors
53 * phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
54 *
55 * We cannot use parameters of statements as this is the table names we build
56 * phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
57 * phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
58 */
59 class SystemReport {
60 const NONCE_NAME = 'matomo_troubleshooting';
61 const TROUBLESHOOT_SYNC_USERS = 'matomo_troubleshooting_action_site_users';
62 const TROUBLESHOOT_SYNC_ALL_USERS = 'matomo_troubleshooting_action_all_users';
63 const TROUBLESHOOT_SYNC_SITE = 'matomo_troubleshooting_action_site';
64 const TROUBLESHOOT_SYNC_ALL_SITES = 'matomo_troubleshooting_action_all_sites';
65 const TROUBLESHOOT_CLEAR_MATOMO_CACHE = 'matomo_troubleshooting_action_clear_matomo_cache';
66 const TROUBLESHOOT_ARCHIVE_NOW = 'matomo_troubleshooting_action_archive_now';
67 const TROUBLESHOOT_UPDATE_GEOIP_DB = 'matomo_troubleshooting_action_update_geoipdb';
68 const TROUBLESHOOT_CLEAR_LOGS = 'matomo_troubleshooting_action_clear_logs';
69 const TROUBLESHOOT_RUN_UPDATER = 'matomo_troubleshooting_action_run_updater';
70
71 private $not_compatible_plugins = [
72 'background-manager',
73 // Uses an old version of Twig and plugin is no longer maintained.
74 'all-in-one-event-calendar',
75 // Uses an old version of Twig
76 'tweet-old-post-pro',
77 // uses a newer version of monolog
78 'wp-rss-aggregator',
79 // twig conflict
80 'age-verification-for-woocommerce',
81 // see https://github.com/matomo-org/wp-matomo/issues/428
82 'minify-html-markup',
83 // see https://wordpress.org/support/topic/graphs-are-not-displayed-in-the-visits-overview-widget/#post-14298068
84 'bigbuy-wc-dropshipping-connector',
85 // see https://wordpress.org/support/topic/20-total-errors-during-this-script-execution/
86 'google-listings-and-ads',
87 // see https://wordpress.org/support/topic/20-total-errors-during-this-script-execution/
88 'post-smtp',
89 // see https://wordpress.org/support/topic/activation-of-another-plugin-breaks-matomo/#post-15045079
90 'adshares',
91 // see https://github.com/matomo-org/matomo-for-wordpress/issues/618
92 'bluehost-wordpress-plugin',
93 // see https://wordpress.org/support/topic/archive-error-with-wp-rocket/
94 'wp-rocket',
95 ];
96
97 private $valid_tabs = [ 'troubleshooting' ];
98
99 /**
100 * @var Settings
101 */
102 private $settings;
103
104 /**
105 * @var Logger
106 */
107 private $logger;
108
109 private $initial_error_reporting = null;
110 /**
111 * @var \WpMatomo\Db\Settings
112 */
113 public $db_settings;
114
115 public function __construct( Settings $settings ) {
116 $this->settings = $settings;
117 $this->logger = new Logger();
118 $this->db_settings = new \WpMatomo\Db\Settings();
119 }
120
121 public function get_not_compatible_plugins() {
122 return $this->not_compatible_plugins;
123 }
124
125 private function execute_troubleshoot_if_needed() {
126 if ( ! empty( $_POST )
127 && is_admin()
128 && check_admin_referer( self::NONCE_NAME )
129 && current_user_can( Capabilities::KEY_SUPERUSER ) ) {
130 if ( ! empty( $_POST[ self::TROUBLESHOOT_ARCHIVE_NOW ] ) ) {
131 Bootstrap::do_bootstrap();
132 $scheduled_tasks = new ScheduledTasks( $this->settings );
133
134 if ( ! defined( 'PIWIK_ARCHIVE_NO_TRUNCATE' ) ) {
135 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
136 define( 'PIWIK_ARCHIVE_NO_TRUNCATE', 1 ); // when triggering it manually, we prefer the full error message
137 }
138
139 try {
140 // force invalidation of archive to ensure it actually will rearchive the data
141 $site = new Site();
142 $idsite = $site->get_current_matomo_site_id();
143 if ( $idsite ) {
144 $timezone = \Piwik\Site::getTimezoneFor( $idsite );
145 $now_string = \Piwik\Date::factory( 'now', $timezone )->toString();
146 foreach ( [ 'day' ] as $period ) {
147 API::getInstance()->invalidateArchivedReports( $idsite, $now_string, $period, false, false );
148 }
149 }
150 } catch ( Exception $e ) {
151 $this->logger->log_exception( 'archive_invalidate', $e );
152 }
153
154 try {
155 $errors = $scheduled_tasks->archive( true, false );
156 } catch ( Exception $e ) {
157 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>';
158 throw $e;
159 }
160
161 if ( ! empty( $errors ) ) {
162 echo '<div class="notice notice-warning"><p>Matomo Archive Warnings: ';
163 foreach ( $errors as $error ) {
164 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export
165 echo nl2br( esc_html( matomo_anonymize_value( var_export( $error, 1 ) ) ) );
166 echo '<br/>';
167 }
168 echo '</p></div>';
169 }
170 }
171
172 if ( ! empty( $_POST[ self::TROUBLESHOOT_CLEAR_MATOMO_CACHE ] ) ) {
173 $paths = new Paths();
174 $paths->clear_cache_dir();
175 // we first delete the cache dir manually just in case there's something
176 // going wrong with matomo and bootstrapping would not even be possible.
177 Bootstrap::do_bootstrap();
178 Filesystem::deleteAllCacheOnUpdate();
179 Updater::unlock();
180 }
181
182 if ( ! empty( $_POST[ self::TROUBLESHOOT_UPDATE_GEOIP_DB ] ) ) {
183 $scheduled_tasks = new ScheduledTasks( $this->settings );
184 $scheduled_tasks->update_geo_ip2_db();
185 }
186
187 if ( ! empty( $_POST[ self::TROUBLESHOOT_CLEAR_LOGS ] ) ) {
188 $this->logger->clear_logged_exceptions();
189 }
190
191 if ( ! $this->settings->is_network_enabled() || ! is_network_admin() ) {
192 if ( ! empty( $_POST[ self::TROUBLESHOOT_SYNC_USERS ] ) ) {
193 $sync = new UserSync();
194 $sync->sync_current_users();
195 }
196 if ( ! empty( $_POST[ self::TROUBLESHOOT_SYNC_SITE ] ) ) {
197 $sync = new SiteSync( $this->settings );
198 $sync->sync_current_site();
199 }
200 if ( ! empty( $_POST[ self::TROUBLESHOOT_RUN_UPDATER ] ) ) {
201 Updater::unlock();
202 $sync = new Updater( $this->settings );
203 $sync->update();
204 }
205 }
206 if ( $this->settings->is_network_enabled() ) {
207 if ( ! empty( $_POST[ self::TROUBLESHOOT_SYNC_ALL_SITES ] ) ) {
208 $sync = new SiteSync( $this->settings );
209 $sync->sync_all();
210 }
211 if ( ! empty( $_POST[ self::TROUBLESHOOT_SYNC_ALL_USERS ] ) ) {
212 $sync = new UserSync();
213 $sync->sync_all();
214 }
215 }
216 }
217 }
218
219 public function show() {
220 $this->execute_troubleshoot_if_needed();
221
222 $settings = $this->settings;
223
224 $matomo_active_tab = '';
225
226 if ( isset( $_GET['tab'] ) ) {
227 $tab = sanitize_text_field( wp_unslash( $_GET['tab'] ) );
228 if ( in_array( $tab, $this->valid_tabs, true ) ) {
229 $matomo_active_tab = $tab;
230 }
231 }
232
233 $matomo_tables = [];
234 if ( empty( $matomo_active_tab ) ) {
235 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting
236 $this->initial_error_reporting = @error_reporting();
237 $matomo_tables = [
238 [
239 'title' => 'Matomo',
240 'rows' => $this->get_matomo_info(),
241 'has_comments' => true,
242 ],
243 [
244 'title' => 'WordPress',
245 'rows' => $this->get_wordpress_info(),
246 'has_comments' => true,
247 ],
248 [
249 'title' => 'WordPress Plugins',
250 'rows' => $this->get_plugins_info(),
251 'has_comments' => true,
252 ],
253 [
254 'title' => 'Server',
255 'rows' => $this->get_server_info(),
256 'has_comments' => true,
257 ],
258 [
259 'title' => 'Database',
260 'rows' => $this->get_db_info(),
261 'has_comments' => true,
262 ],
263 [
264 'title' => 'Browser',
265 'rows' => $this->get_browser_info(),
266 'has_comments' => true,
267 ],
268 ];
269 }
270 $matomo_tables = apply_filters( 'matomo_systemreport_tables', $matomo_tables );
271 $matomo_tables = $this->add_errors_first( $matomo_tables );
272 $matomo_has_warning_and_no_errors = $this->has_only_warnings_no_error( $matomo_tables );
273
274 $matomo_has_exception_logs = $this->logger->get_last_logged_entries();
275
276 include dirname( __FILE__ ) . '/views/systemreport.php';
277 }
278
279 private function has_only_warnings_no_error( $report_tables ) {
280 $has_warning = false;
281 $has_error = false;
282 foreach ( $report_tables as $report_table ) {
283 foreach ( $report_table['rows'] as $row ) {
284 if ( ! empty( $row['is_error'] ) ) {
285 $has_error = true;
286 }
287 if ( ! empty( $row['is_warning'] ) ) {
288 $has_warning = true;
289 }
290 }
291 }
292
293 return $has_warning && ! $has_error;
294 }
295
296 private function add_errors_first( $report_tables ) {
297 $errors = [
298 'title' => 'Errors',
299 'rows' => [],
300 'has_comments' => true,
301 ];
302 foreach ( $report_tables as $report_table ) {
303 foreach ( $report_table['rows'] as $row ) {
304 if ( ! empty( $row['is_error'] ) ) {
305 $errors['rows'][] = $row;
306 }
307 }
308 }
309
310 if ( ! empty( $errors['rows'] ) ) {
311 array_unshift( $report_tables, $errors );
312 }
313
314 return $report_tables;
315 }
316
317 private function check_file_exists_and_writable( $rows, $path_to_check, $title, $required ) {
318 $file_exists = file_exists( $path_to_check );
319 $file_readable = is_readable( $path_to_check );
320 $file_writable = is_writable( $path_to_check );
321 $comment = '"' . $path_to_check . '" ';
322 if ( ! $file_exists ) {
323 $comment .= sprintf( esc_html__( '%s does not exist. ', 'matomo' ), $title );
324 }
325 if ( ! $file_readable ) {
326 $comment .= sprintf( esc_html__( '%s is not readable. ', 'matomo' ), $title );
327 }
328 if ( ! $file_writable ) {
329 $comment .= sprintf( esc_html__( '%s is not writable. ', 'matomo' ), $title );
330 }
331
332 $rows[] = [
333 'name' => sprintf( esc_html__( '%s exists and is writable.', 'matomo' ), $title ),
334 'value' => $file_exists && $file_readable && $file_writable ? esc_html__( 'Yes', 'matomo' ) : esc_html__( 'No', 'matomo' ),
335 'comment' => $comment,
336 'is_error' => $required && ( ! $file_exists || ! $file_readable ),
337 'is_warning' => ! $required && ( ! $file_exists || ! $file_readable ),
338 ];
339
340 return $rows;
341 }
342
343 private function get_matomo_info() {
344 $rows = [];
345
346 $plugin_data = get_plugin_data( MATOMO_ANALYTICS_FILE, $markup = false, $translate = false );
347 $install_time = get_option( Installer::OPTION_NAME_INSTALL_DATE );
348
349 $rows[] = [
350 'name' => esc_html__( 'Matomo Plugin Version', 'matomo' ),
351 'value' => $plugin_data['Version'],
352 'comment' => '',
353 ];
354
355 $paths = new Paths();
356 $path_config_file = $paths->get_config_ini_path();
357 $rows = $this->check_file_exists_and_writable( $rows, $path_config_file, 'Config', true );
358
359 $path_tracker_file = $paths->get_matomo_js_upload_path();
360 $rows = $this->check_file_exists_and_writable( $rows, $path_tracker_file, 'JS Tracker', false );
361
362 $rows[] = [
363 'name' => esc_html__( 'Plugin directories', 'matomo' ),
364 'value' => ! empty( $GLOBALS['MATOMO_PLUGIN_DIRS'] ) ? 'Yes' : 'No',
365 'comment' => ! empty( $GLOBALS['MATOMO_PLUGIN_DIRS'] ) ? wp_json_encode( $GLOBALS['MATOMO_PLUGIN_DIRS'] ) : '',
366 ];
367
368 $tmp_dir = $paths->get_tmp_dir();
369
370 $rows[] = [
371 'name' => esc_html__( 'Tmp directory writable', 'matomo' ),
372 'value' => is_writable( $tmp_dir ),
373 'comment' => $tmp_dir,
374 ];
375
376 if ( ! empty( $_SERVER['MATOMO_WP_ROOT_PATH'] ) ) {
377 // we can have / in this value
378 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
379 $custom_path = rtrim( $_SERVER['MATOMO_WP_ROOT_PATH'], '/' ) . '/wp-load.php';
380 $path_exists = file_exists( $custom_path );
381 $comment = '';
382 if ( ! $path_exists ) {
383 $comment = 'It seems the path does not point to the WP root directory.';
384 }
385
386 $rows[] = [
387 'name' => 'Custom MATOMO_WP_ROOT_PATH',
388 'value' => $path_exists,
389 'is_error' => ! $path_exists,
390 'comment' => $comment,
391 ];
392 }
393
394 $report = null;
395
396 if ( ! WpMatomo::is_safe_mode() ) {
397 try {
398 Bootstrap::do_bootstrap();
399 /** @var DiagnosticService $service */
400 $service = StaticContainer::get( DiagnosticService::class );
401 $report = $service->runDiagnostics();
402
403 $rows[] = [
404 'name' => esc_html__( 'Matomo Version', 'matomo' ),
405 'value' => \Piwik\Version::VERSION,
406 'comment' => '',
407 ];
408 } catch ( Exception $e ) {
409 $rows[] = [
410 'name' => esc_html__( 'Matomo System Check', 'matomo' ),
411 'value' => 'Failed to run Matomo system check.',
412 'comment' => $e->getMessage(),
413 ];
414 }
415 }
416
417 $site = new Site();
418 $idsite = $site->get_current_matomo_site_id();
419
420 $rows[] = [
421 'name' => esc_html__( 'Matomo Blog idSite', 'matomo' ),
422 'value' => $idsite,
423 'comment' => '',
424 ];
425
426 $install_date = '';
427 if ( ! empty( $install_time ) ) {
428 $install_date = 'Install date: ' . $this->convert_time_to_date( $install_time, true, false );
429 }
430
431 $rows[] = [
432 'name' => esc_html__( 'Matomo Install Version', 'matomo' ),
433 'value' => get_option( Installer::OPTION_NAME_INSTALL_VERSION ),
434 'comment' => $install_date,
435 ];
436
437 $wpmatomo_updater = new \WpMatomo\Updater( $this->settings );
438 if ( ! WpMatomo::is_safe_mode() ) {
439 $outstanding_updates = $wpmatomo_updater->get_plugins_requiring_update();
440 $upgrade_in_progress = $wpmatomo_updater->is_upgrade_in_progress();
441 $rows[] = [
442 'name' => 'Upgrades outstanding',
443 'value' => ! empty( $outstanding_updates ),
444 'comment' => ! empty( $outstanding_updates ) ? wp_json_encode( $outstanding_updates ) : '',
445 ];
446 $rows[] = [
447 'name' => 'Upgrade in progress',
448 'value' => $upgrade_in_progress,
449 'comment' => '',
450 ];
451 }
452
453 if ( ! $wpmatomo_updater->load_plugin_functions() ) {
454 // this should actually never happen...
455 $rows[] = [
456 'name' => 'Matomo Upgrade Plugin Functions',
457 'is_warning' => true,
458 'value' => false,
459 'comment' => 'Function "get_plugin_data" not available. There may be an issue with upgrades not being executed. Please reach out to us.',
460 ];
461 }
462
463 $rows[] = [
464 'section' => 'Endpoints',
465 ];
466
467 $rows[] = [
468 'name' => 'Matomo JavaScript Tracker URL',
469 'value' => '',
470 'comment' => $paths->get_js_tracker_url_in_matomo_dir(),
471 ];
472
473 $rows[] = [
474 'name' => 'Matomo JavaScript Tracker - WP Rest API',
475 'value' => '',
476 'comment' => $paths->get_js_tracker_rest_api_endpoint(),
477 ];
478
479 $rows[] = [
480 'name' => 'Matomo HTTP Tracking API',
481 'value' => '',
482 'comment' => $paths->get_tracker_api_url_in_matomo_dir(),
483 ];
484
485 $rows[] = [
486 'name' => 'Matomo HTTP Tracking API - WP Rest API',
487 'value' => '',
488 'comment' => $paths->get_tracker_api_rest_api_endpoint(),
489 ];
490
491 $matomo_plugin_dir_name = basename( dirname( MATOMO_ANALYTICS_FILE ) );
492 if ( 'matomo' !== $matomo_plugin_dir_name ) {
493 $rows[] = [
494 'name' => 'Matomo Plugin Name is correct',
495 'value' => false,
496 'is_error' => true,
497 '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.',
498 ];
499 } elseif ( ! is_plugin_active( 'matomo/matomo.php' ) ) {
500 $rows[] = [
501 'name' => 'Matomo Plugin not active',
502 'value' => false,
503 'is_error' => true,
504 '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.',
505 ];
506 }
507
508 $rows[] = [
509 'section' => 'Crons',
510 ];
511
512 $scheduled_tasks = new ScheduledTasks( $this->settings );
513 $all_events = $scheduled_tasks->get_all_events();
514
515 $rows[] = [
516 'name' => esc_html__( 'Server time', 'matomo' ),
517 'value' => $this->convert_time_to_date( time(), false ),
518 'comment' => '',
519 ];
520
521 $rows[] = [
522 'name' => esc_html__( 'Blog time', 'matomo' ),
523 'value' => $this->convert_time_to_date( time(), true ),
524 'comment' => esc_html__( 'Below dates are shown in blog timezone', 'matomo' ),
525 ];
526
527 foreach ( $all_events as $event_name => $event_config ) {
528 $last_run_before = $scheduled_tasks->get_last_time_before_cron( $event_name );
529 $last_run_after = $scheduled_tasks->get_last_time_after_cron( $event_name );
530
531 $next_scheduled = wp_next_scheduled( $event_name );
532
533 $comment = ' Last started: ' . $this->convert_time_to_date( $last_run_before, true, true ) . '.';
534 $comment .= ' Last ended: ' . $this->convert_time_to_date( $last_run_after, true, true ) . '.';
535 $comment .= ' Interval: ' . $event_config['interval'];
536
537 $rows[] = [
538 'name' => $event_config['name'],
539 'value' => 'Next run: ' . $this->convert_time_to_date( $next_scheduled, true, true ),
540 'comment' => $comment,
541 ];
542 }
543
544 $suports_async = false;
545 if ( ! WpMatomo::is_safe_mode() && $report ) {
546 $rows[] = [
547 'section' => esc_html__( 'Mandatory checks', 'matomo' ),
548 ];
549
550 $rows = $this->add_diagnostic_results( $rows, $report->getMandatoryDiagnosticResults() );
551
552 $rows[] = [
553 'section' => esc_html__( 'Optional checks', 'matomo' ),
554 ];
555 $rows = $this->add_diagnostic_results( $rows, $report->getOptionalDiagnosticResults() );
556
557 $cli_multi = new CliMulti();
558 $suports_async = $cli_multi->supportsAsync();
559
560 $rows[] = [
561 'name' => 'Supports Async Archiving',
562 'value' => $suports_async,
563 'comment' => '',
564 ];
565
566 $location_provider = LocationProvider::getCurrentProvider();
567 if ( $location_provider ) {
568 $rows[] = [
569 'name' => 'Location provider ID',
570 'value' => $location_provider->getId(),
571 'comment' => '',
572 ];
573 $rows[] = [
574 'name' => 'Location provider available',
575 'value' => $location_provider->isAvailable(),
576 'comment' => '',
577 ];
578 $rows[] = [
579 'name' => 'Location provider working',
580 'value' => $location_provider->isWorking(),
581 'comment' => '',
582 ];
583 }
584
585 if ( ! WpMatomo::is_safe_mode() ) {
586 Bootstrap::do_bootstrap();
587 $general = Config::getInstance()->General;
588
589 if ( empty( $general['proxy_client_headers'] ) ) {
590 foreach ( AdvancedSettings::$valid_host_headers as $header ) {
591 if ( ! empty( $_SERVER[ $header ] ) ) {
592 $rows[] = [
593 'name' => 'Proxy header',
594 'value' => $header,
595 'is_warning' => true,
596 'comment' => '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. Learn more: https://matomo.org/faq/wordpress/how-do-i-fix-the-proxy-header-warning-in-the-matomo-for-wordpress-system-report/',
597 ];
598 }
599 }
600 }
601 $incompatible_plugins = Plugin\Manager::getInstance()->getIncompatiblePlugins( Version::VERSION );
602 if ( ! empty( $incompatible_plugins ) ) {
603 $rows[] = [
604 'section' => esc_html__( 'Incompatible Matomo plugins', 'matomo' ),
605 ];
606 foreach ( $incompatible_plugins as $plugin ) {
607 $rows[] = [
608 'name' => 'Plugin has missing dependencies',
609 'value' => $plugin->getPluginName(),
610 'is_error' => true,
611 'comment' => $plugin->getMissingDependenciesAsString( Version::VERSION ) . ' If the plugin requires a different Matomo version you may need to update it. If you no longer use it consider uninstalling it.',
612 ];
613 }
614 }
615 }
616
617 $num_days_check_visits = 5;
618 $had_visits = $this->had_visits_in_last_days( $num_days_check_visits );
619 if ( false === $had_visits || true === $had_visits ) {
620 // do not show info if we could not detect it (had_visits === null)
621 $comment = '';
622 if ( ! $had_visits ) {
623 $comment = 'It looks like there were no visits in the last ' . $num_days_check_visits . ' 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.';
624 }
625
626 $rows[] = [
627 'name' => 'Had visit in last ' . $num_days_check_visits . ' days',
628 'value' => $had_visits,
629 'is_warning' => ! $had_visits && $this->settings->is_tracking_enabled(),
630 'comment' => $comment,
631 ];
632 }
633
634 if ( ! WpMatomo::is_safe_mode() ) {
635 Bootstrap::do_bootstrap();
636 $matomo_url = SettingsPiwik::getPiwikUrl();
637 $rows[] = [
638 'name' => 'Matomo URL',
639 'comment' => $matomo_url,
640 'value' => ! empty( $matomo_url ),
641 ];
642 }
643 }
644
645 $rows[] = [
646 'section' => 'Matomo Settings',
647 ];
648
649 // always show these settings
650 $global_settings_always_show = [
651 'track_mode',
652 'track_codeposition',
653 'track_api_endpoint',
654 'track_js_endpoint',
655 ];
656 foreach ( $global_settings_always_show as $key ) {
657 $rows[] = [
658 'name' => ucfirst( str_replace( '_', ' ', $key ) ),
659 'value' => $this->settings->get_global_option( $key ),
660 'comment' => '',
661 ];
662 }
663
664 // otherwise show only few customised settings
665 // mostly only numeric values and booleans to not eg accidentally show anything that would store a token etc
666 // like we don't want to show license key etc
667 foreach ( $this->settings->get_customised_global_settings() as $key => $val ) {
668 if ( is_numeric( $val ) || is_bool( $val ) || 'track_content' === $key || 'track_user_id' === $key || 'core_version' === $key || 'version_history' === $key || 'mail_history' === $key ) {
669 if ( is_array( $val ) ) {
670 $val = implode( ', ', $val );
671 }
672
673 $rows[] = [
674 'name' => ucfirst( str_replace( '_', ' ', $key ) ),
675 'value' => $val,
676 'comment' => '',
677 ];
678 }
679 }
680
681 $rows[] = [
682 'section' => 'Logs',
683 ];
684
685 $error_log_entries = $this->logger->get_last_logged_entries();
686
687 if ( ! empty( $error_log_entries ) ) {
688 foreach ( $error_log_entries as $error ) {
689 if ( ! empty( $install_time )
690 && is_numeric( $install_time )
691 && ! empty( $error['name'] )
692 && ! empty( $error['value'] )
693 && is_numeric( $error['value'] )
694 && 'cron_sync' === $error['name']
695 && $error['value'] < ( $install_time + 300 ) ) {
696 // the first sync might right after the installation
697 continue;
698 }
699
700 // we only consider plugin_updates as errors only if there are still outstanding updates
701 $is_plugin_update_error = ! empty( $error['name'] ) && 'plugin_update' === $error['name']
702 && ! empty( $outstanding_updates );
703
704 $skip_plugin_update = ! empty( $error['name'] ) && 'plugin_update' === $error['name']
705 && empty( $outstanding_updates );
706
707 if ( empty( $error['comment'] ) && '0' !== $error['comment'] ) {
708 $error['comment'] = '';
709 }
710
711 $error['value'] = $this->convert_time_to_date( $error['value'], true, false );
712 $error['is_warning'] = ! empty( $error['name'] ) && stripos( $error['name'], 'archiv' ) !== false && 'archive_boot' !== $error['name'];
713 $error['is_error'] = $is_plugin_update_error;
714 if ( $is_plugin_update_error ) {
715 $error['comment'] = 'Please reach out to us and include the copied system report (see https://matomo.org/faq/wordpress/how-do-i-troubleshoot-a-failed-database-upgrade-in-matomo-for-wordpress/ for more info)<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.' . $error['comment'];
716 } elseif ( $skip_plugin_update ) {
717 $error['comment'] = 'As there are no outstanding plugin updates it looks like this log can be ignored.<br><br>' . $error['comment'];
718 }
719 $error['comment'] = matomo_anonymize_value( $error['comment'] );
720 $rows[] = $error;
721 }
722
723 foreach ( $error_log_entries as $error ) {
724 if ( $suports_async
725 && ! empty( $error['value'] ) && is_string( $error['value'] )
726 && strpos( $error['value'], __( 'Your PHP installation appears to be missing the MySQL extension which is required by WordPress.', 'matomo' ) ) > 0 ) {
727 $rows[] = [
728 'name' => 'Cli has no MySQL',
729 'value' => true,
730 'comment' => 'It looks like MySQL is not available on CLI. Please read our FAQ on how to fix this issue: 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/ ',
731 'is_error' => true,
732 ];
733 }
734 }
735 } else {
736 $rows[] = [
737 'name' => __( 'None', 'matomo' ),
738 'value' => '',
739 'comment' => '',
740 ];
741 }
742
743 if ( ! WpMatomo::is_safe_mode() ) {
744 Bootstrap::do_bootstrap();
745 $trackfailures = [];
746 try {
747 $tracking_failures = new Failures();
748 $trackfailures = $tracking_failures->getAllFailures();
749 // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
750 } catch ( Exception $e ) {
751 // ignored in case not set up yet etc.
752 }
753 if ( ! empty( $trackfailures ) ) {
754 $rows[] = [
755 'section' => 'Tracking failures',
756 ];
757 foreach ( $trackfailures as $failure ) {
758 $comment = sprintf(
759 'Solution: %s<br>More info: %s<br>Date: %s<br>Request URL: %s',
760 $failure['solution'],
761 $failure['solution_url'],
762 $failure['pretty_date_first_occurred'],
763 $failure['request_url']
764 );
765 $rows[] = [
766 'name' => $failure['problem'],
767 'is_warning' => true,
768 'value' => '',
769 'comment' => $comment,
770 ];
771 }
772 }
773 }
774
775 return $rows;
776 }
777
778 private function had_visits_in_last_days( $num_days ) {
779 global $wpdb;
780
781 if ( WpMatomo::is_safe_mode() ) {
782 return null;
783 }
784
785 $days_in_seconds = $num_days * 86400;
786
787 $prefix_table = $this->db_settings->prefix_table_name( 'log_visit' );
788
789 $suppress_errors = $wpdb->suppress_errors;
790 $wpdb->suppress_errors( true );// prevent any of this showing in logs just in case
791
792 try {
793 $time = gmdate( 'Y-m-d H:i:s', time() - $days_in_seconds );
794 $sql = $wpdb->prepare( 'SELECT idsite from ' . $prefix_table . ' where visit_last_action_time > %s LIMIT 1', $time );
795 $row = $wpdb->get_var( $sql );
796 } catch ( Exception $e ) {
797 $row = null;
798 }
799
800 $wpdb->suppress_errors( $suppress_errors );
801 // we need to differentiate between
802 // 0 === had no visit
803 // 1 === had visit
804 // null === sum error... eg table was not correctly installed
805 if ( null !== $row ) {
806 $row = ! empty( $row );
807 }
808
809 return $row;
810 }
811
812 private function convert_time_to_date( $time, $in_blog_timezone, $print_diff = false ) {
813 if ( empty( $time ) ) {
814 return esc_html__( 'Unknown', 'matomo' );
815 }
816
817 $date = gmdate( 'Y-m-d H:i:s', (int) $time );
818
819 if ( $in_blog_timezone ) {
820 $date = get_date_from_gmt( $date, 'Y-m-d H:i:s' );
821 }
822
823 if ( $print_diff && class_exists( '\Piwik\Metrics\Formatter' ) ) {
824 $formatter = new \Piwik\Metrics\Formatter();
825 $date .= ' (' . $formatter->getPrettyTimeFromSeconds( $time - time(), true, false ) . ')';
826 }
827
828 return $date;
829 }
830
831 private function add_diagnostic_results( $rows, $results ) {
832 foreach ( $results as $result ) {
833 $comment = '';
834 /** @var DiagnosticResult $result */
835 if ( $result->getStatus() !== DiagnosticResult::STATUS_OK ) {
836 foreach ( $result->getItems() as $item ) {
837 $item_comment = $item->getComment();
838 if ( ! empty( $item_comment ) && is_string( $item_comment ) ) {
839 if ( stripos( $item_comment, 'core:archive' ) > 0 ) {
840 // 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"
841 // but not anything that asks user to set up a cronjob
842 $item_comment = substr( $item_comment, 0, stripos( $item_comment, 'core:archive' ) );
843 if ( strpos( $item_comment, '.' ) > 0 ) {
844 $item_comment = substr( $item_comment, 0, strripos( $item_comment, '.' ) );
845 } else {
846 $item_comment = 'Archiving hasn\'t run in a while.';
847 }
848 }
849 $comment .= $item_comment . '<br/>';
850 }
851 }
852 }
853
854 $rows[] = [
855 'name' => $result->getLabel(),
856 'value' => $result->getStatus() . ' ' . $result->getLongErrorMessage(),
857 'comment' => $comment,
858 'is_warning' => $result->getStatus() === DiagnosticResult::STATUS_WARNING,
859 'is_error' => $result->getStatus() === DiagnosticResult::STATUS_ERROR,
860 ];
861 }
862
863 return $rows;
864 }
865
866 private function get_wordpress_info() {
867 $is_multi_site = is_multisite();
868 $num_blogs = 1;
869 $is_network_enabled = false;
870 if ( $is_multi_site ) {
871 if ( function_exists( 'get_blog_count' ) ) {
872 $num_blogs = get_blog_count();
873 }
874 $settings = new Settings();
875 $is_network_enabled = $settings->is_network_enabled();
876 }
877
878 $rows = [];
879 $rows[] = [
880 'name' => 'Home URL',
881 'value' => home_url(),
882 ];
883 $rows[] = [
884 'name' => 'Site URL',
885 'value' => site_url(),
886 ];
887 $rows[] = [
888 'name' => 'WordPress Version',
889 'value' => get_bloginfo( 'version' ),
890 ];
891 $rows[] = [
892 'name' => 'Number of blogs',
893 'value' => $num_blogs,
894 ];
895 $rows[] = [
896 'name' => 'Multisite Enabled',
897 'value' => $is_multi_site,
898 ];
899 $rows[] = [
900 'name' => 'Network Enabled',
901 'value' => $is_network_enabled,
902 ];
903 $consts = [
904 'WP_DEBUG',
905 'WP_DEBUG_DISPLAY',
906 'WP_DEBUG_LOG',
907 'DISABLE_WP_CRON',
908 'FORCE_SSL_ADMIN',
909 'WP_CACHE',
910 'CONCATENATE_SCRIPTS',
911 'COMPRESS_SCRIPTS',
912 'COMPRESS_CSS',
913 'ENFORCE_GZIP',
914 'WP_LOCAL_DEV',
915 'WP_CONTENT_URL',
916 'WP_CONTENT_DIR',
917 'UPLOADS',
918 'BLOGUPLOADDIR',
919 'DIEONDBERROR',
920 'WPLANG',
921 'ALTERNATE_WP_CRON',
922 'WP_CRON_LOCK_TIMEOUT',
923 'WP_DISABLE_FATAL_ERROR_HANDLER',
924 'MATOMO_SUPPORT_ASYNC_ARCHIVING',
925 'MATOMO_TRIGGER_BROWSER_ARCHIVING',
926 'MATOMO_ENABLE_TAG_MANAGER',
927 'MATOMO_SUPPRESS_DB_ERRORS',
928 'MATOMO_ENABLE_AUTO_UPGRADE',
929 'MATOMO_DEBUG',
930 'MATOMO_SAFE_MODE',
931 'MATOMO_GLOBAL_UPLOAD_DIR',
932 'MATOMO_LOGIN_REDIRECT',
933 ];
934 foreach ( $consts as $const ) {
935 $rows[] = [
936 'name' => $const,
937 'value' => defined( $const ) ? constant( $const ) : '-',
938 ];
939 }
940
941 $rows[] = [
942 'name' => 'Permalink Structure',
943 'value' => get_option( 'permalink_structure' ) ? get_option( 'permalink_structure' ) : 'Default',
944 ];
945
946 $rows[] = [
947 'name' => 'Possibly uses symlink',
948 'value' => strpos( __DIR__, ABSPATH ) === false && strpos( __DIR__, WP_CONTENT_DIR ) === false,
949 ];
950
951 $upload_dir = wp_upload_dir();
952 $rows[] = [
953 'name' => 'Upload base url',
954 'value' => $upload_dir['baseurl'],
955 ];
956
957 $rows[] = [
958 'name' => 'Upload base dir',
959 'value' => $upload_dir['basedir'],
960 ];
961
962 $rows[] = [
963 'name' => 'Upload url',
964 'value' => $upload_dir['url'],
965 ];
966
967 foreach ( [ 'upload_path', 'upload_url_path' ] as $option_read ) {
968 $rows[] = [
969 'name' => 'Custom ' . $option_read,
970 'value' => get_option( $option_read ),
971 ];
972 }
973
974 if ( is_plugin_active( 'wp-piwik/wp-piwik.php' ) ) {
975 $rows[] = [
976 'name' => 'WP-Matomo (WP-Piwik) activated',
977 'value' => true,
978 'is_warning' => true,
979 'comment' => 'It is usually not recommended or needed to run Matomo for WordPress and WP-Matomo at the same time. To learn more about the differences between the two plugins view this URL: https://matomo.org/faq/wordpress/why-are-there-two-different-matomo-for-wordpress-plugins-what-is-the-difference-to-wp-matomo-integration-plugin/',
980 ];
981
982 $mode = get_option( 'wp-piwik_global-piwik_mode' );
983 if ( function_exists( 'get_site_option' ) && is_plugin_active_for_network( 'wp-piwik/wp-piwik.php' ) ) {
984 $mode = get_site_option( 'wp-piwik_global-piwik_mode' );
985 }
986 if ( ! empty( $mode ) ) {
987 $rows[] = [
988 'name' => 'WP-Matomo mode',
989 'value' => $mode,
990 'is_warning' => 'php' === $mode || 'PHP' === $mode,
991 'comment' => 'WP-Matomo is configured in "PHP mode". This is known to cause issues with Matomo for WordPress. We recommend you either deactivate WP-Matomo or you go "Settings => WP-Matomo" and change the "Matomo Mode" in the "Connect to Matomo" section to "Self-hosted HTTP API".',
992 ];
993 }
994 }
995
996 $compatible_content_dir = matomo_has_compatible_content_dir();
997 if ( true === $compatible_content_dir ) {
998 $rows[] = [
999 'name' => 'Compatible content directory',
1000 'value' => true,
1001 ];
1002 } else {
1003 $rows[] = [
1004 'name' => 'Compatible content directory',
1005 'value' => $compatible_content_dir,
1006 'is_warning' => true,
1007 'comment' => __( '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' ) . ' ' . __( 'Learn more', 'matomo' ) . ': https://matomo.org/faq/wordpress/how-do-i-make-matomo-for-wordpress-work-when-i-have-a-custom-content-directory/',
1008 ];
1009 }
1010
1011 return $rows;
1012 }
1013
1014 private function get_server_info() {
1015 $rows = [];
1016
1017 if ( ! empty( $_SERVER['SERVER_SOFTWARE'] ) ) {
1018 $rows[] = [
1019 'name' => 'Server Info',
1020 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
1021 'value' => sanitize_text_field( $_SERVER['SERVER_SOFTWARE'] ),
1022 ];
1023 }
1024 if ( PHP_OS ) {
1025 $rows[] = [
1026 'name' => 'PHP OS',
1027 'value' => PHP_OS,
1028 ];
1029 }
1030 $rows[] = [
1031 'name' => 'PHP Version',
1032 'value' => phpversion(),
1033 ];
1034 $rows[] = [
1035 'name' => 'PHP SAPI',
1036 'value' => php_sapi_name(),
1037 ];
1038 if ( defined( 'PHP_BINARY' ) && PHP_BINARY ) {
1039 $rows[] = [
1040 'name' => 'PHP Binary Name',
1041 'value' => @basename( PHP_BINARY ),
1042 ];
1043 }
1044 // we report error reporting before matomo bootstraped and after to see if Matomo changed it successfully etc
1045 $rows[] = [
1046 'name' => 'PHP Error Reporting',
1047 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting
1048 'value' => $this->initial_error_reporting . ' After bootstrap: ' . @error_reporting(),
1049 ];
1050 if ( ! WpMatomo::is_safe_mode() ) {
1051 Bootstrap::do_bootstrap();
1052 $cli_php = new CliMulti\CliPhp();
1053 $binary = $cli_php->findPhpBinary();
1054 if ( ! empty( $binary ) ) {
1055 $binary = basename( $binary );
1056 $rows[] = [
1057 'name' => 'PHP Found Binary',
1058 'value' => $binary,
1059 ];
1060 }
1061 }
1062 $rows[] = [
1063 'name' => 'Timezone',
1064 'value' => date_default_timezone_get(),
1065 ];
1066 if ( function_exists( 'wp_timezone_string' ) ) {
1067 $rows[] = [
1068 'name' => 'WP timezone',
1069 'value' => wp_timezone_string(),
1070 ];
1071 }
1072 $rows[] = [
1073 'name' => 'Locale',
1074 'value' => get_locale(),
1075 ];
1076 if ( function_exists( 'get_user_locale' ) ) {
1077 $rows[] = [
1078 'name' => 'User Locale',
1079 'value' => get_user_locale(),
1080 ];
1081 }
1082
1083 $rows[] = [
1084 'name' => 'Memory Limit',
1085 'value' => @ini_get( 'memory_limit' ),
1086 'comment' => 'At least 128MB recommended. Depending on your traffic 256MB or more may be needed.',
1087 ];
1088
1089 $rows[] = [
1090 'name' => 'WP Memory Limit',
1091 'value' => defined( 'WP_MEMORY_LIMIT' ) ? WP_MEMORY_LIMIT : '',
1092 'comment' => '',
1093 ];
1094
1095 $rows[] = [
1096 'name' => 'WP Max Memory Limit',
1097 'value' => defined( 'WP_MAX_MEMORY_LIMIT' ) ? WP_MAX_MEMORY_LIMIT : '',
1098 'comment' => '',
1099 ];
1100
1101 if ( function_exists( 'timezone_version_get' ) ) {
1102 $rows[] = [
1103 'name' => 'Timezone version',
1104 'value' => timezone_version_get(),
1105 ];
1106 }
1107
1108 $rows[] = [
1109 'name' => 'Time',
1110 'value' => time(),
1111 ];
1112
1113 $rows[] = [
1114 'name' => 'Max Execution Time',
1115 'value' => ini_get( 'max_execution_time' ),
1116 ];
1117 $rows[] = [
1118 'name' => 'Max Post Size',
1119 'value' => ini_get( 'post_max_size' ),
1120 ];
1121 $rows[] = [
1122 'name' => 'Max Upload Size',
1123 'value' => wp_max_upload_size(),
1124 ];
1125 $rows[] = [
1126 'name' => 'Max Input Vars',
1127 'value' => ini_get( 'max_input_vars' ),
1128 ];
1129
1130 $disabled_functions = ini_get( 'disable_functions' );
1131 $rows[] = [
1132 'name' => 'Disabled PHP functions',
1133 'value' => ! empty( $disabled_functions ),
1134 'comment' => ! empty( $disabled_functions ) ? $disabled_functions : '',
1135 ];
1136
1137 $zlib_compression = ini_get( 'zlib.output_compression' );
1138 $row = [
1139 'name' => 'zlib.output_compression is off',
1140 'value' => '1' !== $zlib_compression,
1141 ];
1142
1143 if ( '1' === $zlib_compression ) {
1144 $row['is_error'] = true;
1145 $row['comment'] = 'You need to set "zlib.output_compression" in your php.ini to "Off".';
1146 }
1147 $rows[] = $row;
1148
1149 if ( function_exists( 'curl_version' ) ) {
1150 $curl_version = curl_version();
1151 $curl_version = $curl_version['version'] . ', ' . $curl_version['ssl_version'];
1152 $rows[] = [
1153 'name' => 'Curl Version',
1154 'value' => $curl_version,
1155 ];
1156 }
1157
1158 $suhosin_installed = ( extension_loaded( 'suhosin' ) || ( defined( 'SUHOSIN_PATCH' ) && constant( 'SUHOSIN_PATCH' ) ) );
1159 $rows[] = [
1160 'name' => 'Suhosin installed',
1161 'value' => ! empty( $suhosin_installed ),
1162 'comment' => '',
1163 ];
1164
1165 return $rows;
1166 }
1167
1168 private function get_browser_info() {
1169 $rows = [];
1170
1171 if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
1172 $rows[] = [
1173 'name' => 'Browser',
1174 'value' => '',
1175 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
1176 'comment' => sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ),
1177 ];
1178 }
1179 if ( ! WpMatomo::is_safe_mode() ) {
1180 Bootstrap::do_bootstrap();
1181 try {
1182 if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
1183 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
1184 $detector = StaticContainer::get( DeviceDetectorFactory::class )->makeInstance( sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ) );
1185 $client = $detector->getClient();
1186 if ( ! empty( $client['name'] ) && 'Microsoft Edge' === $client['name'] && (int) $client['version'] >= 85 ) {
1187 $rows[] = [
1188 'name' => 'Browser Compatibility',
1189 'is_warning' => true,
1190 'value' => 'Yes',
1191 'comment' => '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.',
1192 ];
1193 }
1194 }
1195 } catch ( Exception $e ) {
1196 $this->logger->log( $e->getMessage() );
1197 }
1198
1199 $rows[] = [
1200 'name' => 'Language',
1201 'value' => Common::getBrowserLanguage(),
1202 'comment' => '',
1203 ];
1204 }
1205
1206 return $rows;
1207 }
1208
1209 private function get_db_info() {
1210 global $wpdb;
1211 $rows = [];
1212
1213 $rows[] = [
1214 'name' => 'MySQL Version',
1215 'value' => ! empty( $wpdb->is_mysql ) ? $wpdb->db_version() : '',
1216 'comment' => '',
1217 ];
1218
1219 $rows[] = [
1220 'name' => 'Mysqli Connect',
1221 'value' => function_exists( 'mysqli_connect' ),
1222 'comment' => '',
1223 ];
1224 $rows[] = [
1225 'name' => 'Force MySQL over Mysqli',
1226 'value' => defined( 'WP_USE_EXT_MYSQL' ) && WP_USE_EXT_MYSQL,
1227 'comment' => '',
1228 ];
1229
1230 $rows[] = [
1231 'name' => 'DB Prefix',
1232 'value' => $wpdb->prefix,
1233 ];
1234
1235 $rows[] = [
1236 'name' => 'DB CHARSET',
1237 'value' => defined( 'DB_CHARSET' ) ? DB_CHARSET : '',
1238 ];
1239
1240 $rows[] = [
1241 'name' => 'DB COLLATE',
1242 'value' => defined( 'DB_COLLATE' ) ? DB_COLLATE : '',
1243 ];
1244
1245 $rows[] = [
1246 'name' => 'SHOW ERRORS',
1247 'value' => ! empty( $wpdb->show_errors ),
1248 ];
1249
1250 $rows[] = [
1251 'name' => 'SUPPRESS ERRORS',
1252 'value' => ! empty( $wpdb->suppress_errors ),
1253 ];
1254
1255 if ( method_exists( $wpdb, 'parse_db_host' ) ) {
1256 $host_data = $wpdb->parse_db_host( DB_HOST );
1257 if ( $host_data ) {
1258 list( $host, $port, $socket, $is_ipv6 ) = $host_data;
1259 }
1260
1261 $rows[] = [
1262 'name' => 'Uses Socket',
1263 'value' => ! empty( $socket ),
1264 ];
1265 $rows[] = [
1266 'name' => 'Uses IPv6',
1267 'value' => ! empty( $is_ipv6 ),
1268 ];
1269 }
1270
1271 $rows[] = [
1272 'name' => 'Matomo tables found',
1273 'value' => $this->get_num_matomo_tables(),
1274 ];
1275
1276 $missing_tables = $this->get_missing_tables();
1277 $has_missing_tables = ( count( $missing_tables ) > 0 );
1278 $rows[] = [
1279 'name' => 'DB tables exist',
1280 'value' => ( ! $has_missing_tables ),
1281 'comment' => $has_missing_tables ? sprintf( __( 'Some tables may be missing: %s', 'matomo' ), implode( ', ', $missing_tables ) ) : '',
1282 'is_error' => $has_missing_tables,
1283 ];
1284
1285 foreach ( [ 'user', 'site' ] as $table ) {
1286 $rows[] = [
1287 'name' => 'Matomo ' . $table . 's found',
1288 'value' => $this->get_num_entries_in_table( $table ),
1289 ];
1290 }
1291
1292 $grants = $this->get_db_grants();
1293
1294 // we only show these grants for security reasons as only they are needed and we don't need to know any other ones
1295 $needed_grants = [
1296 'SELECT',
1297 'INSERT',
1298 'UPDATE',
1299 'INDEX',
1300 'DELETE',
1301 'CREATE',
1302 'DROP',
1303 'ALTER',
1304 'CREATE TEMPORARY TABLES',
1305 'LOCK TABLES',
1306 ];
1307 if ( in_array( 'ALL PRIVILEGES', $grants, true ) ) {
1308 // ALL PRIVILEGES may be used pre MySQL 8.0
1309 $grants = $needed_grants;
1310 }
1311
1312 $grants_missing = array_diff( $needed_grants, $grants );
1313
1314 if ( empty( $grants )
1315 || ! is_array( $grants )
1316 || count( $grants_missing ) === count( $needed_grants ) ) {
1317 $rows[] = [
1318 'name' => esc_html__( 'Required permissions', 'matomo' ),
1319 'value' => esc_html__( 'Failed to detect granted permissions', 'matomo' ),
1320 'comment' => esc_html__( 'Please check your MySQL user has these permissions (grants):', 'matomo' ) . '<br />' . implode( ', ', $needed_grants ),
1321 'is_warning' => false,
1322 ];
1323 } else {
1324 if ( ! empty( $grants_missing ) ) {
1325 $rows[] = [
1326 'name' => esc_html__( 'Required permissions', 'matomo' ),
1327 'value' => esc_html__( 'Error', 'matomo' ),
1328 'comment' => esc_html__( 'Missing permissions', 'matomo' ) . ': ' . implode( ', ', $grants_missing ) . '. ' . __( 'Please check if any of these MySQL permission (grants) are missing and add them if needed.', 'matomo' ) . ' ' . __( 'Learn more', 'matomo' ) . ': https://matomo.org/faq/troubleshooting/how-do-i-check-if-my-mysql-user-has-all-required-grants/',
1329 'is_warning' => true,
1330 ];
1331 } else {
1332 $rows[] = [
1333 'name' => esc_html__( 'Required permissions', 'matomo' ),
1334 'value' => esc_html__( 'OK', 'matomo' ),
1335 'comment' => '',
1336 'is_warning' => false,
1337 ];
1338 }
1339 }
1340
1341 return $rows;
1342 }
1343
1344 /**
1345 * @return string[]
1346 */
1347 public function get_missing_tables() {
1348 global $wpdb;
1349
1350 $required_matomo_tables = $this->db_settings->get_matomo_tables();
1351 $required_matomo_tables = array_map( [ $this->db_settings, 'prefix_table_name' ], $required_matomo_tables );
1352
1353 $existing_tables = [];
1354 try {
1355 $prefix = $this->db_settings->prefix_table_name( '' );
1356 $existing_tables = $wpdb->get_col( 'SHOW TABLES LIKE "' . $prefix . '%"' );
1357 } catch ( Exception $e ) {
1358 $this->logger->log( 'no show tables: ' . $e->getMessage() );
1359 }
1360
1361 return array_diff( $required_matomo_tables, $existing_tables );
1362 }
1363
1364 private function get_num_entries_in_table( $table ) {
1365 global $wpdb;
1366
1367 $prefix = $this->db_settings->prefix_table_name( $table );
1368
1369 $results = null;
1370 try {
1371 $results = $wpdb->get_var( 'select count(*) from ' . $prefix );
1372 } catch ( Exception $e ) {
1373 $this->logger->log( 'no count(*): ' . $e->getMessage() );
1374 }
1375
1376 if ( isset( $results ) && is_numeric( $results ) ) {
1377 return $results;
1378 }
1379
1380 return 'table not exists';
1381 }
1382
1383 private function get_num_matomo_tables() {
1384 global $wpdb;
1385
1386 $prefix = $this->db_settings->prefix_table_name( '' );
1387
1388 $results = null;
1389 try {
1390 $results = $wpdb->get_results( 'show tables like "' . $prefix . '%"' );
1391 } catch ( Exception $e ) {
1392 $this->logger->log( 'no show tables: ' . $e->getMessage() );
1393 }
1394
1395 if ( is_array( $results ) ) {
1396 return count( $results );
1397 }
1398
1399 return 'show tables not working';
1400 }
1401
1402 private function get_db_grants() {
1403 global $wpdb;
1404
1405 $suppress_errors = $wpdb->suppress_errors;
1406 $wpdb->suppress_errors( true );// prevent any of this showing in logs just in case
1407
1408 try {
1409 $values = $wpdb->get_results( 'SHOW GRANTS', ARRAY_N );
1410 } catch ( Exception $e ) {
1411 // We ignore any possible error in case of permission or not supported etc.
1412 $values = [];
1413 }
1414
1415 $wpdb->suppress_errors( $suppress_errors );
1416
1417 $grants = [];
1418 foreach ( $values as $index => $value ) {
1419 if ( empty( $value[0] ) || ! is_string( $value[0] ) ) {
1420 continue;
1421 }
1422
1423 if ( stripos( $value[0], 'ALL PRIVILEGES' ) !== false ) {
1424 return [ 'ALL PRIVILEGES' ]; // the split on empty string wouldn't work otherwise
1425 }
1426
1427 foreach ( [ ' ON ', ' TO ', ' IDENTIFIED ', ' BY ' ] as $keyword ) {
1428 if ( stripos( $values[ $index ][0], $keyword ) !== false ) {
1429 // make sure to never show by any accident a db user or password by cutting anything after on/to
1430 $values[ $index ][0] = substr( $value[0], 0, stripos( $value[0], $keyword ) );
1431 }
1432 if ( stripos( $values[ $index ][0], 'GRANT' ) !== false ) {
1433 // otherwise we end up having "grant select"... instead of just "select"
1434 $values[ $index ][0] = substr( $value[0], stripos( $values[ $index ][0], 'GRANT' ) + 5 );
1435 }
1436 }
1437 // make sure to never show by any accident a db user or password
1438 $values[ $index ][0] = str_replace(
1439 [ DB_USER, DB_PASSWORD ],
1440 [
1441 'DB_USER',
1442 'DB_PASS',
1443 ],
1444 $values[ $index ][0]
1445 );
1446
1447 $grants = array_merge( $grants, explode( ',', $values[ $index ][0] ) );
1448 }
1449 $grants = array_map( 'trim', $grants );
1450 $grants = array_map( 'strtoupper', $grants );
1451 $grants = array_unique( $grants );
1452
1453 return $grants;
1454 }
1455
1456 private function get_plugins_info() {
1457 $rows = [];
1458 $mu_plugins = get_mu_plugins();
1459
1460 if ( ! empty( $mu_plugins ) ) {
1461 $rows[] = [
1462 'section' => 'MU Plugins',
1463 ];
1464
1465 foreach ( $mu_plugins as $mu_pin ) {
1466 $comment = '';
1467 if ( ! empty( $plugin['Network'] ) ) {
1468 $comment = 'Network enabled';
1469 }
1470 $rows[] = [
1471 'name' => $mu_pin['Name'],
1472 'value' => $mu_pin['Version'],
1473 'comment' => $comment,
1474 ];
1475 }
1476
1477 $rows[] = [
1478 'section' => 'Plugins',
1479 ];
1480 }
1481
1482 $plugins = get_plugins();
1483
1484 foreach ( $plugins as $plugin ) {
1485 $comment = '';
1486 if ( ! empty( $plugin['Network'] ) ) {
1487 $comment = 'Network enabled';
1488 }
1489 $rows[] = [
1490 'name' => $plugin['Name'],
1491 'value' => $plugin['Version'],
1492 'comment' => $comment,
1493 ];
1494 }
1495
1496 $active_plugins = get_option( 'active_plugins', [] );
1497
1498 if ( ! empty( $active_plugins ) && is_array( $active_plugins ) ) {
1499 $active_plugins = array_map(
1500 function ( $active_plugin ) {
1501 $parts = explode( '/', trim( $active_plugin ) );
1502
1503 return trim( $parts[0] );
1504 },
1505 $active_plugins
1506 );
1507
1508 $rows[] = [
1509 'name' => 'Active Plugins',
1510 'value' => count( $active_plugins ),
1511 'comment' => implode( ' ', $active_plugins ),
1512 ];
1513
1514 $used_not_compatible = array_intersect( $active_plugins, $this->not_compatible_plugins );
1515 if ( ! empty( $used_not_compatible ) ) {
1516 $additional_comment = '';
1517 if ( in_array( 'tweet-old-post-pro', $used_not_compatible, true ) ) {
1518 $additional_comment .= '<br><br>A workaround for Revive Old Posts Pro may be to add the following line to your "wp-config.php". <br><code>define( \'MATOMO_SUPPORT_ASYNC_ARCHIVING\', false );</code>.';
1519 }
1520 if ( in_array( 'secupress', $used_not_compatible, true ) ) {
1521 $additional_comment .= '<br><br>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": <br><code>define( \'MATOMO_SUPPORT_ASYNC_ARCHIVING\', false );</code>.';
1522 }
1523 if ( in_array( 'post-smtp', $used_not_compatible, true ) ) {
1524 $additional_comment .= '<br><br>The PDF report files from the email reports will be missing when the PostSMTP mode is selected but it works when the PHPMailer mode is selected.';
1525 }
1526 if ( in_array( 'wp-rocket', $used_not_compatible, true ) ) {
1527 $additional_comment .= '<br><br>WP-Rocket is incompatible from version 3.12. Until fixes, please reinstall version 3.11.5 if you have a newer version. For more information please visit https://github.com/matomo-org/matomo-for-wordpress/wiki/Downgrade-wp-rocket-to-a-version-compatible-with-the-Matomo-plugin';
1528 }
1529
1530 $is_warning = true;
1531 $is_error = false;
1532 if ( in_array( 'cookiebot', $used_not_compatible, true ) ) {
1533 $is_warning = false;
1534 $is_error = true;
1535 }
1536
1537 $rows[] = [
1538 'name' => __( 'Not compatible plugins', 'matomo' ),
1539 'value' => count( $used_not_compatible ),
1540 'comment' => implode( ', ', $used_not_compatible ) . '<br><br> Matomo may work fine when using these plugins but there may be some issues. For more information see<br>https://matomo.org/faq/wordpress/which-plugins-is-matomo-for-wordpress-known-to-be-not-compatible-with/ ' . $additional_comment,
1541 'is_warning' => $is_warning,
1542 'is_error' => $is_error,
1543 ];
1544 }
1545 }
1546
1547 $rows[] = [
1548 'name' => 'Theme',
1549 'value' => function_exists( 'get_template' ) ? get_template() : '',
1550 'comment' => get_option( 'stylesheet' ),
1551 ];
1552
1553 if ( is_plugin_active( 'better-wp-security/better-wp-security.php' ) ) {
1554 if ( method_exists( '\ITSEC_Modules', 'get_setting' )
1555 && ITSEC_Modules::get_setting( 'system-tweaks', 'long_url_strings' ) ) {
1556 $rows[] = [
1557 'name' => 'iThemes Security Long URLs Enabled',
1558 'value' => true,
1559 'comment' => 'Tracking might not work because it looks like you have Long URLs disabled in iThemes Security. To fix this please go to "Security -> Settings -> System Tweaks" and disable the setting "Long URL Strings".',
1560 'is_error' => true,
1561 ];
1562 }
1563 }
1564
1565 return $rows;
1566 }
1567 }
1568