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