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