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