PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.15.1
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.15.1
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
1834 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_codeposition',
762 'track_api_endpoint',
763 'track_js_endpoint',
764 ];
765 foreach ( $global_settings_always_show as $key ) {
766 $rows[] = [
767 'name' => ucfirst( str_replace( '_', ' ', $key ) ),
768 'value' => $this->settings->get_global_option( $key ),
769 'comment' => '',
770 ];
771 }
772
773 // otherwise show only few customised settings
774 // mostly only numeric values and booleans to not eg accidentally show anything that would store a token etc
775 // like we don't want to show license key etc
776 foreach ( $this->settings->get_customised_global_settings() as $key => $val ) {
777 if ( is_numeric( $val ) || is_bool( $val ) || 'track_content' === $key || 'track_user_id' === $key || 'core_version' === $key || 'version_history' === $key || 'mail_history' === $key ) {
778 if ( is_array( $val ) ) {
779 $val = implode( ', ', $val );
780 }
781
782 $rows[] = [
783 'name' => ucfirst( str_replace( '_', ' ', $key ) ),
784 'value' => $val,
785 'comment' => '',
786 ];
787 }
788 }
789
790 $rows[] = [
791 'section' => 'Logs',
792 ];
793
794 $error_log_entries = $this->logger->get_last_logged_entries();
795
796 if ( ! empty( $error_log_entries ) ) {
797 foreach ( $error_log_entries as $error ) {
798 if ( ! empty( $install_time )
799 && is_numeric( $install_time )
800 && ! empty( $error['name'] )
801 && ! empty( $error['value'] )
802 && is_numeric( $error['value'] )
803 && 'cron_sync' === $error['name']
804 && $error['value'] < ( $install_time + 300 )
805 ) {
806 // the first sync might right after the installation
807 continue;
808 }
809
810 // we only consider plugin_updates as errors only if there are still outstanding updates
811 $is_plugin_update_error = ! empty( $error['name'] ) && 'plugin_update' === $error['name']
812 && ! empty( $outstanding_updates );
813
814 $skip_plugin_update = ! empty( $error['name'] ) && 'plugin_update' === $error['name']
815 && empty( $outstanding_updates );
816
817 if ( empty( $error['comment'] ) && '0' !== $error['comment'] ) {
818 $error['comment'] = '';
819 }
820
821 if ( strpos( $error['comment'], '<head>' ) ) {
822 $error['comment'] = esc_html( $error['comment'] );
823 $error['comment'] = $this->replace_hexadecimal_colors( $error['comment'] );
824 }
825
826 $error['value'] = $this->convert_time_to_date( $error['value'], true, false );
827 $error['is_warning'] = ! empty( $error['name'] ) && stripos( $error['name'], 'archiv' ) !== false && 'archive_boot' !== $error['name'];
828 $error['is_error'] = $is_plugin_update_error;
829 if ( $is_plugin_update_error ) {
830 $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'];
831 } elseif ( $skip_plugin_update ) {
832 $error['comment'] = esc_html__( 'As there are no outstanding plugin updates it looks like this log can be ignored.', 'matomo' ) . '<br><br>' . $error['comment'];
833 }
834 $error['comment'] = matomo_anonymize_value( $error['comment'] );
835 $rows[] = $error;
836 }
837
838 foreach ( $error_log_entries as $error ) {
839 if ( $suports_async
840 && ! empty( $error['value'] )
841 && is_string( $error['value'] )
842 && strpos( $error['value'], __( 'Your PHP installation appears to be missing the MySQL extension which is required by WordPress.', 'matomo' ) ) > 0
843 ) {
844 $rows[] = [
845 'name' => 'Cli has no MySQL',
846 'value' => true,
847 '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' ) ) ),
848 'is_error' => true,
849 ];
850 }
851 }
852 } else {
853 $rows[] = [
854 'name' => __( 'None', 'matomo' ),
855 'value' => '',
856 'comment' => '',
857 ];
858 }
859
860 if ( ! WpMatomo::is_safe_mode() ) {
861 Bootstrap::do_bootstrap();
862 $trackfailures = [];
863 try {
864 $tracking_failures = new Failures();
865 $trackfailures = $tracking_failures->getAllFailures();
866 // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
867 } catch ( Exception $e ) {
868 // ignored in case not set up yet etc.
869 }
870 if ( ! empty( $trackfailures ) ) {
871 $rows[] = [
872 'section' => 'Tracking failures',
873 ];
874 foreach ( $trackfailures as $failure ) {
875 $comment = sprintf(
876 'Solution: %s<br>More info: %s<br>Date: %s<br>Request URL: %s',
877 $failure['solution'],
878 $failure['solution_url'],
879 $failure['pretty_date_first_occurred'],
880 $failure['request_url']
881 );
882 // do not esc_html the comment: we want the br
883 $rows[] = [
884 'name' => $failure['problem'],
885 'is_warning' => true,
886 'value' => '',
887 'comment' => $comment,
888 ];
889 }
890 }
891 }
892
893 return $rows;
894 }
895
896 private function had_visits_in_last_days( $num_days ) {
897 global $wpdb;
898
899 if ( WpMatomo::is_safe_mode() ) {
900 return null;
901 }
902
903 $days_in_seconds = $num_days * 86400;
904
905 $prefix_table = $this->db_settings->prefix_table_name( 'log_visit' );
906
907 $suppress_errors = $wpdb->suppress_errors;
908 $wpdb->suppress_errors( true );// prevent any of this showing in logs just in case
909
910 try {
911 $time = gmdate( 'Y-m-d H:i:s', time() - $days_in_seconds );
912 $sql = $wpdb->prepare( 'SELECT idsite from ' . $prefix_table . ' where visit_last_action_time > %s LIMIT 1', $time );
913 $row = $wpdb->get_var( $sql );
914 } catch ( Exception $e ) {
915 $row = null;
916 }
917
918 $wpdb->suppress_errors( $suppress_errors );
919 // we need to differentiate between
920 // 0 === had no visit
921 // 1 === had visit
922 // null === sum error... eg table was not correctly installed
923 if ( null !== $row ) {
924 $row = ! empty( $row );
925 }
926
927 return $row;
928 }
929
930 private function convert_time_to_date( $time, $in_blog_timezone, $print_diff = false ) {
931 if ( empty( $time ) ) {
932 return esc_html__( 'Unknown', 'matomo' );
933 }
934
935 $date = gmdate( 'Y-m-d H:i:s', (int) $time );
936
937 if ( $in_blog_timezone ) {
938 $date = get_date_from_gmt( $date, 'Y-m-d H:i:s' );
939 }
940
941 if ( $print_diff && class_exists( '\Piwik\Metrics\Formatter' ) ) {
942 $formatter = new \Piwik\Metrics\Formatter();
943 $date .= ' (' . $formatter->getPrettyTimeFromSeconds( $time - time(), true, false ) . ')';
944 }
945
946 return $date;
947 }
948
949 private function add_diagnostic_results( $rows, $results ) {
950 foreach ( $results as $result ) {
951 $comment = '';
952 /** @var DiagnosticResult $result */
953 if ( $result->getStatus() !== DiagnosticResult::STATUS_OK ) {
954 foreach ( $result->getItems() as $item ) {
955 $item_comment = $item->getComment();
956 if ( ! empty( $item_comment ) && is_string( $item_comment ) ) {
957 if ( stripos( $item_comment, 'core:archive' ) > 0 ) {
958 // 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"
959 // but not anything that asks user to set up a cronjob
960 $item_comment = substr( $item_comment, 0, stripos( $item_comment, 'core:archive' ) );
961 if ( strpos( $item_comment, '.' ) > 0 ) {
962 $item_comment = substr( $item_comment, 0, strripos( $item_comment, '.' ) );
963 } else {
964 $item_comment = 'Archiving hasn\'t run in a while.';
965 }
966 }
967 $comment .= $item_comment . '<br/>';
968 }
969 }
970 }
971
972 $rows[] = [
973 'name' => $result->getLabel(),
974 'value' => $result->getStatus() . ' ' . $result->getLongErrorMessage(),
975 'comment' => $comment,
976 'is_warning' => $result->getStatus() === DiagnosticResult::STATUS_WARNING,
977 'is_error' => $result->getStatus() === DiagnosticResult::STATUS_ERROR,
978 ];
979 }
980
981 return $rows;
982 }
983
984 private function get_wordpress_info() {
985 $is_multi_site = is_multisite();
986 $num_blogs = 1;
987 $is_network_enabled = false;
988 $matomo_id_sites_number = 1;
989 if ( $is_multi_site ) {
990 if ( function_exists( 'get_blog_count' ) ) {
991 $num_blogs = get_blog_count();
992 }
993 $settings = new Settings();
994 $is_network_enabled = $settings->is_network_enabled();
995 } else {
996 $sites_manager_model = new Model();
997 $matomo_id_sites_number = count( $sites_manager_model->getSitesId() );
998 }
999
1000 $rows = [];
1001 $rows[] = [
1002 'name' => 'Home URL',
1003 'value' => home_url(),
1004 ];
1005 $rows[] = [
1006 'name' => 'Site URL',
1007 'value' => site_url(),
1008 ];
1009 $rows[] = [
1010 'name' => 'WordPress Version',
1011 'value' => get_bloginfo( 'version' ),
1012 ];
1013 $rows[] = [
1014 'name' => 'Number of blogs',
1015 'value' => $num_blogs,
1016 ];
1017 $rows[] = [
1018 'name' => 'Multisite Enabled',
1019 'value' => $is_multi_site,
1020 ];
1021 $rows[] = [
1022 'name' => 'Network Enabled',
1023 'value' => $is_network_enabled,
1024 ];
1025 $consts = [
1026 'WP_DEBUG',
1027 'WP_DEBUG_DISPLAY',
1028 'WP_DEBUG_LOG',
1029 'DISABLE_WP_CRON',
1030 'FORCE_SSL_ADMIN',
1031 'WP_CACHE',
1032 'CONCATENATE_SCRIPTS',
1033 'COMPRESS_SCRIPTS',
1034 'COMPRESS_CSS',
1035 'ENFORCE_GZIP',
1036 'WP_LOCAL_DEV',
1037 'WP_CONTENT_URL',
1038 'WP_CONTENT_DIR',
1039 'UPLOADS',
1040 'BLOGUPLOADDIR',
1041 'DIEONDBERROR',
1042 'WPLANG',
1043 'ALTERNATE_WP_CRON',
1044 'WP_CRON_LOCK_TIMEOUT',
1045 'WP_DISABLE_FATAL_ERROR_HANDLER',
1046 'MATOMO_SUPPORT_ASYNC_ARCHIVING',
1047 'MATOMO_TRIGGER_BROWSER_ARCHIVING',
1048 'MATOMO_ENABLE_TAG_MANAGER',
1049 'MATOMO_SUPPRESS_DB_ERRORS',
1050 'MATOMO_ENABLE_AUTO_UPGRADE',
1051 'MATOMO_DEBUG',
1052 'MATOMO_SAFE_MODE',
1053 'MATOMO_GLOBAL_UPLOAD_DIR',
1054 'MATOMO_LOGIN_REDIRECT',
1055 ];
1056 foreach ( $consts as $const ) {
1057 $rows[] = [
1058 'name' => $const,
1059 'value' => defined( $const ) ? constant( $const ) : '-',
1060 ];
1061 }
1062
1063 $rows[] = [
1064 'name' => 'Permalink Structure',
1065 'value' => get_option( 'permalink_structure' ) ? get_option( 'permalink_structure' ) : 'Default',
1066 ];
1067
1068 $rows[] = [
1069 'name' => 'Possibly uses symlink',
1070 'value' => strpos( __DIR__, ABSPATH ) === false && strpos( __DIR__, WP_CONTENT_DIR ) === false,
1071 ];
1072
1073 $upload_dir = wp_upload_dir();
1074 $rows[] = [
1075 'name' => 'Upload base url',
1076 'value' => $upload_dir['baseurl'],
1077 ];
1078
1079 $rows[] = [
1080 'name' => 'Upload base dir',
1081 'value' => $upload_dir['basedir'],
1082 ];
1083
1084 $rows[] = [
1085 'name' => 'Upload url',
1086 'value' => $upload_dir['url'],
1087 ];
1088
1089 foreach ( [ 'upload_path', 'upload_url_path' ] as $option_read ) {
1090 $rows[] = [
1091 'name' => 'Custom ' . $option_read,
1092 'value' => get_option( $option_read ),
1093 ];
1094 }
1095
1096 if ( is_plugin_active( 'wp-piwik/wp-piwik.php' ) ) {
1097 $rows[] = [
1098 'name' => 'WP-Matomo (WP-Piwik) activated',
1099 'value' => true,
1100 'is_warning' => true,
1101 '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' ) ) ),
1102 ];
1103
1104 $mode = get_option( 'wp-piwik_global-piwik_mode' );
1105 if ( function_exists( 'get_site_option' ) && is_plugin_active_for_network( 'wp-piwik/wp-piwik.php' ) ) {
1106 $mode = get_site_option( 'wp-piwik_global-piwik_mode' );
1107 }
1108 if ( ! empty( $mode ) ) {
1109 $rows[] = [
1110 'name' => 'WP-Matomo mode',
1111 'value' => $mode,
1112 'is_warning' => 'php' === $mode || 'PHP' === $mode,
1113 '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' ),
1114 ];
1115 }
1116 }
1117
1118 $compatible_content_dir = matomo_has_compatible_content_dir();
1119 if ( true === $compatible_content_dir ) {
1120 $rows[] = [
1121 'name' => 'Compatible content directory',
1122 'value' => true,
1123 ];
1124 } else {
1125 $rows[] = [
1126 'name' => 'Compatible content directory',
1127 'value' => $compatible_content_dir,
1128 'is_warning' => true,
1129 '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' ) ),
1130 ];
1131 }
1132
1133 return $rows;
1134 }
1135
1136 private function add_maxminddb_row( &$rows, $maxmind_db_loaded ) {
1137 $rows[] = [
1138 'name' => esc_html__( 'PHP Maxmind DB extension', 'matomo' ),
1139 'value' => $maxmind_db_loaded ? __( 'Loaded', 'matomo' ) : __( 'Not loaded', 'matomo' ),
1140 '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' ) ) ) : '',
1141 'is_warning' => $maxmind_db_loaded,
1142 ];
1143 }
1144
1145 private function get_server_info() {
1146 $rows = [];
1147
1148 if ( ! empty( $_SERVER['SERVER_SOFTWARE'] ) ) {
1149 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
1150 $server_software = sanitize_text_field( $_SERVER['SERVER_SOFTWARE'] );
1151 $rows[] = [
1152 'name' => 'Server Info',
1153 'value' => $server_software,
1154 ];
1155 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1156 if ( strpos( $server_software, 'Apache' ) !== false ) {
1157 $url = plugins_url( 'app', MATOMO_ANALYTICS_FILE ) . '/index.php';
1158 $result = wp_remote_post(
1159 $url,
1160 array(
1161 'method' => 'GET',
1162 'sslverify' => false,
1163 'timeout' => 2,
1164 )
1165 );
1166 if ( is_array( $result ) ) {
1167 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
1168 $file_content = file_get_contents( dirname( MATOMO_ANALYTICS_FILE ) . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . '.htaccess' );
1169 if ( strpos( $file_content, 'AddHandler' ) && ! strpos( $file_content, '# AddHandler' ) ) {
1170 switch ( (int) $result['response']['code'] ) {
1171 case 500:
1172 $value = __( 'To be confirmed', 'matomo' );
1173 $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' ) ) );
1174 $is_error = true;
1175 break;
1176 default:
1177 $value = __( 'Supported', 'matomo' );
1178 $comment = '';
1179 $is_error = false;
1180 }
1181 $rows[] = [
1182 'name' => 'Apache AddHandler support',
1183 'value' => $value,
1184 'comment' => $comment,
1185 'is_error' => $is_error,
1186 ];
1187 }
1188 }
1189 }
1190 }
1191
1192 if ( PHP_OS ) {
1193 $rows[] = [
1194 'name' => 'PHP OS',
1195 'value' => PHP_OS,
1196 ];
1197 }
1198 $rows[] = [
1199 'name' => 'PHP Version',
1200 'value' => phpversion(),
1201 ];
1202 $rows[] = [
1203 'name' => 'PHP SAPI',
1204 'value' => php_sapi_name(),
1205 ];
1206 if ( defined( 'PHP_BINARY' ) && PHP_BINARY ) {
1207 $rows[] = [
1208 'name' => 'PHP Binary Name',
1209 'value' => PHP_BINARY,
1210 ];
1211 }
1212
1213 $this->add_maxminddb_row( $rows, extension_loaded( 'maxminddb' ) );
1214
1215 // we report error reporting before matomo bootstraped and after to see if Matomo changed it successfully etc
1216 $rows[] = [
1217 'name' => 'PHP Error Reporting',
1218 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting
1219 'value' => $this->initial_error_reporting . ' After bootstrap: ' . @error_reporting(),
1220 ];
1221
1222 if ( ! empty( $this->binary ) ) {
1223 $rows[] = [
1224 'name' => 'PHP Found Binary',
1225 'value' => $this->binary,
1226 ];
1227 }
1228 $rows[] = [
1229 'name' => 'Timezone',
1230 'value' => date_default_timezone_get(),
1231 ];
1232 if ( function_exists( 'wp_timezone_string' ) ) {
1233 $rows[] = [
1234 'name' => 'WP timezone',
1235 'value' => wp_timezone_string(),
1236 ];
1237 }
1238 $rows[] = [
1239 'name' => 'Locale',
1240 'value' => get_locale(),
1241 ];
1242 if ( function_exists( 'get_user_locale' ) ) {
1243 $rows[] = [
1244 'name' => 'User Locale',
1245 'value' => get_user_locale(),
1246 ];
1247 }
1248
1249 $rows[] = [
1250 'name' => 'Memory Limit',
1251 'value' => @ini_get( 'memory_limit' ),
1252 'comment' => sprintf( esc_html__( 'At least %1$dMB recommended. Depending on your traffic %2$dMB or more may be needed.', 'matomo' ), 128, 256 ),
1253 ];
1254
1255 $rows[] = [
1256 'name' => 'WP Memory Limit',
1257 'value' => defined( 'WP_MEMORY_LIMIT' ) ? WP_MEMORY_LIMIT : '',
1258 'comment' => '',
1259 ];
1260
1261 $rows[] = [
1262 'name' => 'WP Max Memory Limit',
1263 'value' => defined( 'WP_MAX_MEMORY_LIMIT' ) ? WP_MAX_MEMORY_LIMIT : '',
1264 'comment' => '',
1265 ];
1266
1267 if ( function_exists( 'timezone_version_get' ) ) {
1268 $rows[] = [
1269 'name' => 'Timezone version',
1270 'value' => timezone_version_get(),
1271 ];
1272 }
1273
1274 $rows[] = [
1275 'name' => 'Time',
1276 'value' => time(),
1277 ];
1278
1279 $rows[] = [
1280 'name' => 'Max Execution Time',
1281 'value' => ini_get( 'max_execution_time' ),
1282 ];
1283 $rows[] = [
1284 'name' => 'Max Post Size',
1285 'value' => ini_get( 'post_max_size' ),
1286 ];
1287 $rows[] = [
1288 'name' => 'Max Upload Size',
1289 'value' => wp_max_upload_size(),
1290 ];
1291 $rows[] = [
1292 'name' => 'Max Input Vars',
1293 'value' => ini_get( 'max_input_vars' ),
1294 ];
1295
1296 $disabled_functions = ini_get( 'disable_functions' );
1297 $rows[] = [
1298 'name' => 'Disabled PHP functions',
1299 'value' => ! empty( $disabled_functions ),
1300 'comment' => ! empty( $disabled_functions ) ? $disabled_functions : '',
1301 ];
1302
1303 $zlib_compression = ini_get( 'zlib.output_compression' );
1304 $row = [
1305 'name' => 'zlib.output_compression is off',
1306 'value' => '1' !== $zlib_compression,
1307 ];
1308
1309 if ( '1' === $zlib_compression ) {
1310 $row['is_error'] = true;
1311 $row['comment'] = esc_html__( 'You need to set "zlib.output_compression" in your php.ini to "Off".', 'matomo' );
1312 }
1313 $rows[] = $row;
1314
1315 if ( function_exists( 'curl_version' ) ) {
1316 $curl_version = curl_version();
1317 $curl_version = $curl_version['version'] . ', ' . $curl_version['ssl_version'];
1318 $rows[] = [
1319 'name' => 'Curl Version',
1320 'value' => $curl_version,
1321 ];
1322 }
1323
1324 $suhosin_installed = ( extension_loaded( 'suhosin' ) || ( defined( 'SUHOSIN_PATCH' ) && constant( 'SUHOSIN_PATCH' ) ) );
1325 $rows[] = [
1326 'name' => 'Suhosin installed',
1327 'value' => ! empty( $suhosin_installed ),
1328 'comment' => '',
1329 ];
1330
1331 return $rows;
1332 }
1333
1334 private function get_browser_info() {
1335 $rows = [];
1336
1337 if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
1338 $rows[] = [
1339 'name' => 'Browser',
1340 'value' => '',
1341 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
1342 'comment' => sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ),
1343 ];
1344 }
1345 if ( ! WpMatomo::is_safe_mode() ) {
1346 Bootstrap::do_bootstrap();
1347 try {
1348 if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
1349 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
1350 $detector = StaticContainer::get( DeviceDetectorFactory::class )->makeInstance( sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ) );
1351 $client = $detector->getClient();
1352 if ( ! empty( $client['name'] ) && 'Microsoft Edge' === $client['name'] && (int) $client['version'] >= 85 ) {
1353 $rows[] = [
1354 'name' => 'Browser Compatibility',
1355 'is_warning' => true,
1356 'value' => 'Yes',
1357 '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' ),
1358 ];
1359 }
1360 }
1361 } catch ( Exception $e ) {
1362 $this->logger->log( $e->getMessage() );
1363 }
1364
1365 $rows[] = [
1366 'name' => 'Language',
1367 'value' => Common::getBrowserLanguage(),
1368 'comment' => '',
1369 ];
1370 }
1371
1372 return $rows;
1373 }
1374
1375 private function get_db_info() {
1376 global $wpdb;
1377 $rows = [];
1378
1379 $rows[] = [
1380 'name' => 'MySQL Version',
1381 'value' => ! empty( $wpdb->is_mysql ) ? $wpdb->db_version() : '',
1382 'comment' => '',
1383 ];
1384
1385 $rows[] = [
1386 'name' => 'Mysqli Connect',
1387 'value' => function_exists( 'mysqli_connect' ),
1388 'comment' => '',
1389 ];
1390 $rows[] = [
1391 'name' => 'Force MySQL over Mysqli',
1392 'value' => defined( 'WP_USE_EXT_MYSQL' ) && WP_USE_EXT_MYSQL,
1393 'comment' => '',
1394 ];
1395
1396 $rows[] = [
1397 'name' => 'DB Prefix',
1398 'value' => $wpdb->prefix,
1399 ];
1400
1401 $rows[] = [
1402 'name' => 'DB CHARSET',
1403 'value' => defined( 'DB_CHARSET' ) ? DB_CHARSET : '',
1404 ];
1405
1406 $rows[] = [
1407 'name' => 'DB COLLATE',
1408 'value' => defined( 'DB_COLLATE' ) ? DB_COLLATE : '',
1409 ];
1410
1411 $rows[] = [
1412 'name' => 'SHOW ERRORS',
1413 'value' => ! empty( $wpdb->show_errors ),
1414 ];
1415
1416 $rows[] = [
1417 'name' => 'SUPPRESS ERRORS',
1418 'value' => ! empty( $wpdb->suppress_errors ),
1419 ];
1420
1421 if ( method_exists( $wpdb, 'parse_db_host' ) ) {
1422 $host_data = $wpdb->parse_db_host( DB_HOST );
1423 if ( $host_data ) {
1424 list( $host, $port, $socket, $is_ipv6 ) = $host_data;
1425 }
1426
1427 $rows[] = [
1428 'name' => 'Uses Socket',
1429 'value' => ! empty( $socket ),
1430 ];
1431 $rows[] = [
1432 'name' => 'Uses IPv6',
1433 'value' => ! empty( $is_ipv6 ),
1434 ];
1435 }
1436
1437 $rows[] = [
1438 'name' => 'Matomo tables found',
1439 'value' => $this->get_num_matomo_tables(),
1440 ];
1441
1442 $missing_tables = $this->get_missing_tables();
1443 $has_missing_tables = ( count( $missing_tables ) > 0 );
1444 $rows[] = [
1445 'name' => 'DB tables exist',
1446 'value' => ( ! $has_missing_tables ),
1447 'comment' => $has_missing_tables ? sprintf( esc_html__( 'Some tables may be missing: %s', 'matomo' ), implode( ', ', $missing_tables ) ) : '',
1448 'is_error' => $has_missing_tables,
1449 ];
1450
1451 foreach ( [ 'user', 'site' ] as $table ) {
1452 $row = [
1453 'name' => 'Matomo ' . $table . 's found',
1454 'value' => $this->get_num_entries_in_table( $table ),
1455 ];
1456 if ( 'site' === $table ) {
1457 if ( ( ! is_multisite() ) && ( $row['value'] > 1 ) ) {
1458 $row['is_warning'] = true;
1459 $row['comment'] = esc_html__( 'There is an error in your Matomo records. Please contact wordpress@matomo.org', 'matomo' );
1460 }
1461 }
1462 $rows[] = $row;
1463 }
1464
1465 $grants = $this->get_db_grants();
1466
1467 // we only show these grants for security reasons as only they are needed and we don't need to know any other ones
1468 $needed_grants = [
1469 'SELECT',
1470 'INSERT',
1471 'UPDATE',
1472 'INDEX',
1473 'DELETE',
1474 'CREATE',
1475 'DROP',
1476 'ALTER',
1477 'CREATE TEMPORARY TABLES',
1478 'LOCK TABLES',
1479 ];
1480 if ( in_array( 'ALL PRIVILEGES', $grants, true ) ) {
1481 // ALL PRIVILEGES may be used pre MySQL 8.0
1482 $grants = $needed_grants;
1483 }
1484
1485 $grants_missing = array_diff( $needed_grants, $grants );
1486
1487 if ( empty( $grants )
1488 || ! is_array( $grants )
1489 || count( $grants_missing ) === count( $needed_grants )
1490 ) {
1491 $rows[] = [
1492 'name' => esc_html__( 'Required permissions', 'matomo' ),
1493 'value' => esc_html__( 'Failed to detect granted permissions', 'matomo' ),
1494 'comment' => esc_html__( 'Please check your MySQL user has these permissions (grants):', 'matomo' ) . '<br />' . implode( ', ', $needed_grants ),
1495 'is_warning' => false,
1496 ];
1497 } else {
1498 if ( ! empty( $grants_missing ) ) {
1499 $rows[] = [
1500 'name' => esc_html__( 'Required permissions', 'matomo' ),
1501 'value' => esc_html__( 'Error', 'matomo' ),
1502 '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' ) ),
1503 'is_warning' => true,
1504 ];
1505 } else {
1506 $rows[] = [
1507 'name' => esc_html__( 'Required permissions', 'matomo' ),
1508 'value' => esc_html__( 'OK', 'matomo' ),
1509 'comment' => '',
1510 'is_warning' => false,
1511 ];
1512 }
1513 }
1514
1515 return $rows;
1516 }
1517
1518 /**
1519 * @return string[]
1520 */
1521 public function get_missing_tables() {
1522 global $wpdb;
1523
1524 $required_matomo_tables = $this->db_settings->get_matomo_tables();
1525 $required_matomo_tables = array_map( [ $this->db_settings, 'prefix_table_name' ], $required_matomo_tables );
1526
1527 $existing_tables = [];
1528 try {
1529 $prefix = $this->db_settings->prefix_table_name( '' );
1530 $existing_tables = $wpdb->get_col( 'SHOW TABLES LIKE "' . $prefix . '%"' );
1531 } catch ( Exception $e ) {
1532 $this->logger->log( 'no show tables: ' . $e->getMessage() );
1533 }
1534
1535 return array_diff( $required_matomo_tables, $existing_tables );
1536 }
1537
1538 private function get_num_entries_in_table( $table ) {
1539 global $wpdb;
1540
1541 $prefix = $this->db_settings->prefix_table_name( $table );
1542
1543 $results = null;
1544 try {
1545 $results = $wpdb->get_var( 'select count(*) from ' . $prefix );
1546 } catch ( Exception $e ) {
1547 $this->logger->log( 'no count(*): ' . $e->getMessage() );
1548 }
1549
1550 if ( isset( $results ) && is_numeric( $results ) ) {
1551 return $results;
1552 }
1553
1554 return 'table not exists';
1555 }
1556
1557 private function get_num_matomo_tables() {
1558 global $wpdb;
1559
1560 $prefix = $this->db_settings->prefix_table_name( '' );
1561
1562 $results = null;
1563 try {
1564 $results = $wpdb->get_results( 'show tables like "' . $prefix . '%"' );
1565 } catch ( Exception $e ) {
1566 $this->logger->log( 'no show tables: ' . $e->getMessage() );
1567 }
1568
1569 if ( is_array( $results ) ) {
1570 return count( $results );
1571 }
1572
1573 return 'show tables not working';
1574 }
1575
1576 private function get_db_grants() {
1577 global $wpdb;
1578
1579 $suppress_errors = $wpdb->suppress_errors;
1580 $wpdb->suppress_errors( true );// prevent any of this showing in logs just in case
1581
1582 try {
1583 $values = $wpdb->get_results( 'SHOW GRANTS', ARRAY_N );
1584 } catch ( Exception $e ) {
1585 // We ignore any possible error in case of permission or not supported etc.
1586 $values = [];
1587 }
1588
1589 $wpdb->suppress_errors( $suppress_errors );
1590
1591 $grants = [];
1592 foreach ( $values as $index => $value ) {
1593 if ( empty( $value[0] ) || ! is_string( $value[0] ) ) {
1594 continue;
1595 }
1596
1597 if ( stripos( $value[0], 'ALL PRIVILEGES' ) !== false ) {
1598 return [ 'ALL PRIVILEGES' ]; // the split on empty string wouldn't work otherwise
1599 }
1600
1601 foreach ( [ ' ON ', ' TO ', ' IDENTIFIED ', ' BY ' ] as $keyword ) {
1602 if ( stripos( $values[ $index ][0], $keyword ) !== false ) {
1603 // make sure to never show by any accident a db user or password by cutting anything after on/to
1604 $values[ $index ][0] = substr( $values[ $index ][0], 0, stripos( $values[ $index ][0], $keyword ) );
1605 }
1606 if ( stripos( $values[ $index ][0], 'GRANT' ) !== false ) {
1607 // otherwise we end up having "grant select"... instead of just "select"
1608 $values[ $index ][0] = substr( $values[ $index ][0], stripos( $values[ $index ][0], 'GRANT' ) + 5 );
1609 }
1610 }
1611 // make sure to never show by any accident a db user or password
1612 $values[ $index ][0] = str_replace(
1613 [ DB_USER, DB_PASSWORD ],
1614 [
1615 'DB_USER',
1616 'DB_PASS',
1617 ],
1618 $values[ $index ][0]
1619 );
1620
1621 $grants = array_merge( $grants, explode( ',', $values[ $index ][0] ) );
1622 }
1623 $grants = array_map( 'trim', $grants );
1624 $grants = array_map( 'strtoupper', $grants );
1625 $grants = array_unique( $grants );
1626
1627 return $grants;
1628 }
1629
1630 /**
1631 * @return string[]
1632 */
1633 private function get_actives_plugins() {
1634 $active_plugins = get_option( 'active_plugins', [] );
1635 if ( ! empty( $active_plugins ) && is_array( $active_plugins ) ) {
1636 $active_plugins = array_map(
1637 function ( $active_plugin ) {
1638 $parts = explode( '/', trim( $active_plugin ) );
1639
1640 return trim( $parts[0] );
1641 },
1642 $active_plugins
1643 );
1644 }
1645
1646 return $active_plugins;
1647 }
1648
1649 private function get_plugins_info() {
1650 $rows = [];
1651 $mu_plugins = get_mu_plugins();
1652
1653 if ( ! empty( $mu_plugins ) ) {
1654 $rows[] = [
1655 'section' => 'MU Plugins',
1656 ];
1657
1658 foreach ( $mu_plugins as $mu_pin ) {
1659 $comment = '';
1660 if ( ! empty( $plugin['Network'] ) ) {
1661 $comment = esc_html__( 'Network enabled', 'matomo' );
1662 }
1663 $rows[] = [
1664 'name' => $mu_pin['Name'],
1665 'value' => $mu_pin['Version'],
1666 'comment' => $comment,
1667 ];
1668 }
1669
1670 $rows[] = [
1671 'section' => 'Plugins',
1672 ];
1673 }
1674
1675 $plugins = get_plugins();
1676
1677 foreach ( $plugins as $plugin ) {
1678 $comment = '';
1679 if ( ! empty( $plugin['Network'] ) ) {
1680 $comment = esc_html__( 'Network enabled', 'matomo' );
1681 }
1682 $rows[] = [
1683 'name' => $plugin['Name'],
1684 'value' => $plugin['Version'],
1685 'comment' => $comment,
1686 ];
1687 }
1688
1689 $active_plugins = $this->get_actives_plugins();
1690
1691 if ( ! empty( $active_plugins ) && is_array( $active_plugins ) ) {
1692 $rows[] = [
1693 'name' => 'Active Plugins',
1694 'value' => count( $active_plugins ),
1695 'comment' => implode( ' ', $active_plugins ),
1696 ];
1697
1698 $used_not_compatible = array_intersect( $active_plugins, $this->not_compatible_plugins );
1699 if ( in_array( 'wp-rocket', $used_not_compatible, true ) ) {
1700 if ( defined( 'WP_ROCKET_VERSION' ) && ( version_compare( WP_ROCKET_VERSION, '3.11.5' ) <= 0 ) ) {
1701 unset( $used_not_compatible[ array_search( 'wp-rocket', $used_not_compatible, true ) ] );
1702 }
1703 }
1704
1705 if ( ! empty( $used_not_compatible ) ) {
1706 $additional_comment = '';
1707 if ( in_array( 'tweet-old-post-pro', $used_not_compatible, true ) ) {
1708 $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>.';
1709 }
1710 if ( in_array( 'post-smtp', $used_not_compatible, true ) ) {
1711 $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' );
1712 }
1713 if ( in_array( 'wp-rocket', $used_not_compatible, true ) ) {
1714 $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' ) ) );
1715 }
1716 $is_warning = false;
1717 $is_error = false;
1718 if ( count( $used_not_compatible ) ) {
1719 $is_warning = true;
1720 $is_error = false;
1721 if ( in_array( 'cookiebot', $used_not_compatible, true ) ) {
1722 $is_warning = false;
1723 $is_error = true;
1724 }
1725
1726 $rows[] = [
1727 'name' => __( 'Not compatible plugins', 'matomo' ),
1728 'value' => count( $used_not_compatible ),
1729 '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,
1730 'is_warning' => $is_warning,
1731 'is_error' => $is_error,
1732 ];
1733 }
1734 }
1735 }
1736
1737 $rows[] = [
1738 'name' => 'Theme',
1739 'value' => function_exists( 'get_template' ) ? get_template() : '',
1740 'comment' => get_option( 'stylesheet' ),
1741 ];
1742
1743 if ( is_plugin_active( 'better-wp-security/better-wp-security.php' ) ) {
1744 if ( class_exists( 'ITSEC_Modules' ) ) {
1745 if ( method_exists( '\ITSEC_Modules', 'get_setting' ) ) {
1746 $input = ITSEC_Modules::get_settings( 'system-tweaks' );
1747 // old plugin versions
1748 $long_url_strings_options = [ 'long_url_strings', 'st_longurl' ];
1749 $long_url_strings_enabled = false;
1750 foreach ( $long_url_strings_options as $option ) {
1751 if ( isset( $input[ $option ] ) && $input[ $option ] ) {
1752 $long_url_strings_enabled = true;
1753 }
1754 }
1755 if ( $long_url_strings_enabled ) {
1756 $rows[] = [
1757 'name' => "iThemes Security 'Long URLs' Enabled",
1758 'value' => true,
1759 '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' ),
1760 'is_error' => true,
1761 ];
1762 }
1763 if ( $input['plugins_php'] ) {
1764 $rows[] = [
1765 'name' => "iThemes Security 'Disable PHP in plugins' Enabled",
1766 'value' => true,
1767 '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' ),
1768 'is_error' => true,
1769 ];
1770 }
1771 }
1772 }
1773 }
1774
1775 if ( is_plugin_active( 'secupress/secupress.php' ) ) {
1776 if ( function_exists( 'secupress_is_submodule_active' ) ) {
1777 $blocked_methods = (int) secupress_is_submodule_active( 'firewall', 'request-methods-header' );
1778 if ( $blocked_methods ) {
1779 if ( ! defined( 'MATOMO_SUPPORT_ASYNC_ARCHIVING' ) || MATOMO_SUPPORT_ASYNC_ARCHIVING ) {
1780 $rows[] = [
1781 'name' => "Secupress 'Block Bad Request Methods' Enabled",
1782 'value' => true,
1783 '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>.",
1784 'is_error' => true,
1785 ];
1786 }
1787 }
1788 }
1789 }
1790
1791 return $rows;
1792 }
1793
1794 /**
1795 * Convert the hexadecimal colors in the content into their rgb values
1796 *
1797 * @param string $content
1798 *
1799 * @return string
1800 */
1801 private function replace_hexadecimal_colors( $content ) {
1802 $matches = array();
1803 if ( preg_match_all( '/ (#(([a-f0-9]{8})|([a-f0-9]{4}[ ;])))/i', $content, $matches ) ) {
1804 foreach ( $matches[1] as $hexadecimal_color ) {
1805 switch ( strlen( $hexadecimal_color ) ) {
1806 case 9:
1807 list( $r, $g, $b, $a ) = sscanf( $hexadecimal_color, '#%02x%02x%02x%02x' );
1808 break;
1809 case 6:
1810 $hexadecimal_color = substr( $hexadecimal_color, 0, 5 );
1811 list( $r, $g, $b, $a ) = sscanf( $hexadecimal_color, '#%01x%01x%01x%01x' );
1812 break;
1813 }
1814 $content = str_replace( $hexadecimal_color, 'rgb(' . $r . ',' . $g . ',' . $b . ',' . $a . ')', $content );
1815 }
1816 }
1817 if ( preg_match_all( '/ (#(([a-f0-9]{6})|([a-f0-9]{3})))/i', $content, $matches ) ) {
1818 foreach ( $matches[1] as $hexadecimal_color ) {
1819 switch ( strlen( $hexadecimal_color ) ) {
1820 case 7:
1821 list( $r, $g, $b ) = sscanf( $hexadecimal_color, '#%02x%02x%02x' );
1822 break;
1823 case 4:
1824 list( $r, $g, $b ) = sscanf( $hexadecimal_color, '#%01x%01x%01x' );
1825 break;
1826 }
1827 $content = str_replace( $hexadecimal_color, 'rgb(' . $r . ',' . $g . ',' . $b . ')', $content );
1828 }
1829 }
1830
1831 return $content;
1832 }
1833 }
1834