PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 1.0.5
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v1.0.5
5.13.0 5.12.1 5.12.0 5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / classes / WpMatomo / Admin / SystemReport.php
matomo / classes / WpMatomo / Admin Last commit date
TrackingSettings 6 years ago views 6 years ago AccessSettings.php 6 years ago Admin.php 6 years ago AdminSettings.php 6 years ago AdminSettingsInterface.php 6 years ago AdvancedSettings.php 6 years ago ExclusionSettings.php 6 years ago GeolocationSettings.php 6 years ago GetStarted.php 6 years ago Info.php 6 years ago Marketplace.php 6 years ago Menu.php 6 years ago PrivacySettings.php 6 years ago SafeModeMenu.php 6 years ago Summary.php 6 years ago SystemReport.php 6 years ago TrackingSettings.php 6 years ago
SystemReport.php
1057 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 Piwik\CliMulti;
13 use Piwik\Common;
14 use Piwik\Container\StaticContainer;
15 use Piwik\Filesystem;
16 use Piwik\MetricsFormatter;
17 use Piwik\Plugins\Diagnostics\Diagnostic\DiagnosticResult;
18 use Piwik\Plugins\Diagnostics\DiagnosticService;
19 use Piwik\Plugins\UserCountry\LocationProvider;
20 use WpMatomo\Bootstrap;
21 use WpMatomo\Capabilities;
22 use WpMatomo\Logger;
23 use WpMatomo\Paths;
24 use WpMatomo\ScheduledTasks;
25 use WpMatomo\Settings;
26 use WpMatomo\Site;
27 use WpMatomo\Site\Sync as SiteSync;
28 use WpMatomo\User\Sync as UserSync;
29
30 if ( ! defined( 'ABSPATH' ) ) {
31 exit; // if accessed directly
32 }
33
34 class SystemReport {
35 const NONCE_NAME = 'matomo_troubleshooting';
36 const TROUBLESHOOT_SYNC_USERS = 'matomo_troubleshooting_action_site_users';
37 const TROUBLESHOOT_SYNC_ALL_USERS = 'matomo_troubleshooting_action_all_users';
38 const TROUBLESHOOT_SYNC_SITE = 'matomo_troubleshooting_action_site';
39 const TROUBLESHOOT_SYNC_ALL_SITES = 'matomo_troubleshooting_action_all_sites';
40 const TROUBLESHOOT_CLEAR_MATOMO_CACHE = 'matomo_troubleshooting_action_clear_matomo_cache';
41 const TROUBLESHOOT_ARCHIVE_NOW = 'matomo_troubleshooting_action_archive_now';
42 const TROUBLESHOOT_UPDATE_GEOIP_DB = 'matomo_troubleshooting_action_update_geoipdb';
43 const TROUBLESHOOT_CLEAR_LOGS = 'matomo_troubleshooting_action_clear_logs';
44
45 private $not_compatible_plugins = array(
46 'background-manager/background-manager.php', // Uses an old version of Twig and plugin is no longer maintained.
47 );
48
49 private $valid_tabs = array( 'troubleshooting' );
50
51 /**
52 * @var Settings
53 */
54 private $settings;
55
56 /**
57 * @var Logger
58 */
59 private $logger;
60
61 public function __construct( Settings $settings ) {
62 $this->settings = $settings;
63 $this->logger = new Logger();
64 }
65
66 public function get_not_compatible_plugins() {
67 return $this->not_compatible_plugins;
68 }
69
70 private function execute_troubleshoot_if_needed() {
71 if ( ! empty( $_POST )
72 && is_admin()
73 && check_admin_referer( self::NONCE_NAME )
74 && current_user_can( Capabilities::KEY_SUPERUSER ) ) {
75 if ( ! empty( $_POST[ self::TROUBLESHOOT_ARCHIVE_NOW ] ) ) {
76 Bootstrap::do_bootstrap();
77 $scheduled_tasks = new ScheduledTasks( $this->settings );
78
79 try {
80 $errors = $scheduled_tasks->archive( $force = true, $throw_exception = false );
81 } catch (\Exception $e) {
82 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>';
83 throw $e;
84 }
85
86 if ( ! empty( $errors ) ) {
87 echo '<div class="notice notice-warning"><p>Matomo Archive Warnings: ';
88 foreach ($errors as $error) {
89 echo nl2br(esc_html(matomo_anonymize_value(var_export($error, 1))));
90 echo '<br/>';
91 }
92 echo '</p></div>';
93 }
94 }
95
96 if ( ! empty( $_POST[ self::TROUBLESHOOT_CLEAR_MATOMO_CACHE ] ) ) {
97 $paths = new Paths();
98 $paths->clear_cache_dir();
99 // we first delete the cache dir manually just in case there's something
100 // going wrong with matomo and bootstrapping would not even be possible.
101 Bootstrap::do_bootstrap();
102 Filesystem::deleteAllCacheOnUpdate();
103 }
104
105 if ( ! empty( $_POST[ self::TROUBLESHOOT_UPDATE_GEOIP_DB ] ) ) {
106 $scheduled_tasks = new ScheduledTasks( $this->settings );
107 $scheduled_tasks->update_geo_ip2_db();
108 }
109
110 if ( ! empty( $_POST[ self::TROUBLESHOOT_CLEAR_LOGS ] ) ) {
111 $this->logger->clear_logged_exceptions();
112 }
113
114 if ( ! $this->settings->is_network_enabled() || ! is_network_admin() ) {
115 if ( ! empty( $_POST[ self::TROUBLESHOOT_SYNC_USERS ] ) ) {
116 $sync = new UserSync();
117 $sync->sync_current_users();
118 }
119 if ( ! empty( $_POST[ self::TROUBLESHOOT_SYNC_SITE ] ) ) {
120 $sync = new SiteSync( $this->settings );
121 $sync->sync_current_site();
122 }
123 }
124 if ( $this->settings->is_network_enabled() ) {
125 if ( ! empty( $_POST[ self::TROUBLESHOOT_SYNC_ALL_SITES ] ) ) {
126 $sync = new SiteSync( $this->settings );
127 $sync->sync_all();
128 }
129 if ( ! empty( $_POST[ self::TROUBLESHOOT_SYNC_ALL_USERS ] ) ) {
130 $sync = new UserSync();
131 $sync->sync_all();
132 }
133 }
134 }
135 }
136
137 public function show() {
138 $this->execute_troubleshoot_if_needed();
139
140 $settings = $this->settings;
141
142 $matomo_active_tab = '';
143 if ( isset( $_GET['tab'] ) && in_array( $_GET['tab'], $this->valid_tabs, true ) ) {
144 $matomo_active_tab = $_GET['tab'];
145 }
146
147 $matomo_tables = array();
148 if ( empty( $matomo_active_tab ) ) {
149 $matomo_tables = array(
150 array(
151 'title' => 'Matomo',
152 'rows' => $this->get_matomo_info(),
153 'has_comments' => true,
154 ),
155 array(
156 'title' => 'WordPress',
157 'rows' => $this->get_wordpress_info(),
158 'has_comments' => true,
159 ),
160 array(
161 'title' => 'WordPress Plugins',
162 'rows' => $this->get_plugins_info(),
163 'has_comments' => true,
164 ),
165 array(
166 'title' => 'Server',
167 'rows' => $this->get_server_info(),
168 'has_comments' => true,
169 ),
170 array(
171 'title' => 'Database',
172 'rows' => $this->get_db_info(),
173 'has_comments' => true,
174 ),
175 array(
176 'title' => 'Browser',
177 'rows' => $this->get_browser_info(),
178 'has_comments' => false,
179 ),
180 );
181 }
182 $matomo_tables = apply_filters('matomo_systemreport_tables', $matomo_tables);
183 $matomo_tables = $this->add_errors_first( $matomo_tables );
184 $matomo_has_warning_and_no_errors = $this->has_only_warnings_no_error( $matomo_tables );
185
186 $matomo_has_exception_logs = $this->logger->get_last_logged_entries();
187
188 include dirname( __FILE__ ) . '/views/systemreport.php';
189 }
190
191 private function has_only_warnings_no_error( $report_tables ) {
192 $has_warning = false;
193 $has_error = false;
194 foreach ( $report_tables as $report_table ) {
195 foreach ( $report_table['rows'] as $row ) {
196 if ( ! empty( $row['is_error'] ) ) {
197 $has_error = true;
198 }
199 if ( ! empty( $row['is_warning'] ) ) {
200 $has_warning = true;
201 }
202 }
203 }
204
205 return $has_warning && ! $has_error;
206 }
207
208 private function add_errors_first( $report_tables ) {
209 $errors = array(
210 'title' => 'Errors',
211 'rows' => array(),
212 'has_comments' => true,
213 );
214 foreach ( $report_tables as $report_table ) {
215 foreach ( $report_table['rows'] as $row ) {
216 if ( ! empty( $row['is_error'] ) ) {
217 $errors['rows'][] = $row;
218 }
219 }
220 }
221
222 if ( ! empty( $errors['rows'] ) ) {
223 array_unshift( $report_tables, $errors );
224 }
225
226 return $report_tables;
227 }
228
229 private function check_file_exists_and_writable( $rows, $path_to_check, $title, $required ) {
230 $file_exists = file_exists( $path_to_check );
231 $file_readable = is_readable( $path_to_check );
232 $file_writable = is_writable( $path_to_check );
233 $comment = '"' . $path_to_check . '" ';
234 if ( ! $file_exists ) {
235 $comment .= sprintf( esc_html__( '%s does not exist. ', 'matomo' ), $title );
236 }
237 if ( ! $file_readable ) {
238 $comment .= sprintf( esc_html__( '%s is not readable. ', 'matomo' ), $title );
239 }
240 if ( ! $file_writable ) {
241 $comment .= sprintf( esc_html__( '%s is not writable. ', 'matomo' ), $title );
242 }
243
244 $rows[] = array(
245 'name' => sprintf( esc_html__( '%s exists and is writable.', 'matomo' ), $title ),
246 'value' => $file_exists && $file_readable && $file_writable ? esc_html__( 'Yes', 'matomo' ) : esc_html__( 'No', 'matomo' ),
247 'comment' => $comment,
248 'is_error' => $required && ( ! $file_exists || ! $file_readable ),
249 'is_warning' => ! $required && ( ! $file_exists || ! $file_readable ),
250 );
251
252 return $rows;
253 }
254
255 private function get_matomo_info() {
256 $rows = array();
257
258 $plugin_data = get_plugin_data( MATOMO_ANALYTICS_FILE, $markup = false, $translate = false );
259
260 $rows[] = array(
261 'name' => esc_html__( 'Matomo Plugin Version', 'matomo' ),
262 'value' => $plugin_data['Version'],
263 'comment' => '',
264 );
265
266 $paths = new Paths();
267 $path_config_file = $paths->get_config_ini_path();
268 $rows = $this->check_file_exists_and_writable( $rows, $path_config_file, 'Config', true );
269
270 $path_tracker_file = $paths->get_matomo_js_upload_path();
271 $rows = $this->check_file_exists_and_writable( $rows, $path_tracker_file, 'JS Tracker', false );
272
273 $rows[] = array(
274 'name' => esc_html__( 'Plugin directories', 'matomo' ),
275 'value' => ! empty( $GLOBALS['MATOMO_PLUGIN_DIRS'] ) ? 'Yes' : 'No',
276 'comment' => ! empty( $GLOBALS['MATOMO_PLUGIN_DIRS'] ) ? wp_json_encode( $GLOBALS['MATOMO_PLUGIN_DIRS'] ) : '',
277 );
278
279 $tmp_dir = $paths->get_tmp_dir();
280
281 $rows[] = array(
282 'name' => esc_html__( 'Tmp directory writable', 'matomo' ),
283 'value' => is_writable( $tmp_dir ),
284 'comment' => $tmp_dir,
285 );
286
287 if ( ! empty( $_SERVER['MATOMO_WP_ROOT_PATH'] ) ) {
288 $custom_path = rtrim( $_SERVER['MATOMO_WP_ROOT_PATH'], '/' ) . '/wp-load.php';
289 $path_exists = file_exists( $custom_path );
290 $comment = '';
291 if ( ! $path_exists ) {
292 $comment = 'It seems the path does not point to the WP root directory.';
293 }
294
295 $rows[] = array(
296 'name' => 'Custom MATOMO_WP_ROOT_PATH',
297 'value' => $path_exists,
298 'is_error' => ! $path_exists,
299 'comment' => $comment,
300 );
301 }
302
303 $report = null;
304
305 if ( ! \WpMatomo::is_safe_mode() ) {
306 try {
307 Bootstrap::do_bootstrap();
308 /** @var DiagnosticService $service */
309 $service = StaticContainer::get( DiagnosticService::class );
310 $report = $service->runDiagnostics();
311
312 $rows[] = array(
313 'name' => esc_html__( 'Matomo Version', 'matomo' ),
314 'value' => \Piwik\Version::VERSION,
315 'comment' => '',
316 );
317 } catch ( \Exception $e ) {
318 $rows[] = array(
319 'name' => esc_html__( 'Matomo System Check', 'matomo' ),
320 'value' => 'Failed to run, please open the system check in Matomo',
321 'comment' => $e->getMessage(),
322 );
323 }
324 }
325
326 $site = new Site();
327 $idsite = $site->get_current_matomo_site_id();
328
329 $rows[] = array(
330 'name' => esc_html__( 'Matomo Blog idSite', 'matomo' ),
331 'value' => $idsite,
332 'comment' => '',
333 );
334
335 $rows[] = array(
336 'section' => 'Endpoints',
337 );
338
339 $rows[] = array(
340 'name' => 'Matomo JavaScript Tracker URL',
341 'value' => '',
342 'comment' => $paths->get_js_tracker_url_in_matomo_dir(),
343 );
344
345 $rows[] = array(
346 'name' => 'Matomo JavaScript Tracker - WP Rest API',
347 'value' => '',
348 'comment' => $paths->get_js_tracker_rest_api_endpoint(),
349 );
350
351 $rows[] = array(
352 'name' => 'Matomo HTTP Tracking API',
353 'value' => '',
354 'comment' => $paths->get_tracker_api_url_in_matomo_dir(),
355 );
356
357 $rows[] = array(
358 'name' => 'Matomo HTTP Tracking API - WP Rest API',
359 'value' => '',
360 'comment' => $paths->get_tracker_api_rest_api_endpoint(),
361 );
362
363 $matomo_plugin_dir_name = basename(dirname(MATOMO_ANALYTICS_FILE));
364 if ($matomo_plugin_dir_name !== 'matomo') {
365 $rows[] = array(
366 'name' => 'Matomo Plugin Name is correct',
367 'value' => false,
368 'is_error' => true,
369 '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.',
370 );
371 } elseif (!is_plugin_active('matomo/matomo.php')) {
372 $rows[] = array(
373 'name' => 'Matomo Plugin not active',
374 'value' => false,
375 'is_error' => true,
376 '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.',
377 );
378 }
379
380 $rows[] = array(
381 'section' => 'Crons',
382 );
383
384 $scheduled_tasks = new ScheduledTasks( $this->settings );
385 $all_events = $scheduled_tasks->get_all_events();
386
387 $rows[] = array(
388 'name' => esc_html__( 'Server time', 'matomo' ),
389 'value' => $this->convert_time_to_date( time(), false ),
390 'comment' => '',
391 );
392
393 $rows[] = array(
394 'name' => esc_html__( 'Blog time', 'matomo' ),
395 'value' => $this->convert_time_to_date( time(), true ),
396 'comment' => esc_html__( 'Below dates are shown in blog timezone', 'matomo' ),
397 );
398
399 foreach ( $all_events as $event_name => $event_config ) {
400 $last_run_before = $scheduled_tasks->get_last_time_before_cron( $event_name );
401 $last_run_after = $scheduled_tasks->get_last_time_after_cron( $event_name );
402
403 $next_scheduled = wp_next_scheduled( $event_name );
404
405 $comment = ' Last started: ' . $this->convert_time_to_date( $last_run_before, true, true ) . '.';
406 $comment .= ' Last ended: ' . $this->convert_time_to_date( $last_run_after, true, true ) . '.';
407 $comment .= ' Interval: ' . $event_config['interval'];
408
409 $rows[] = array(
410 'name' => $event_config['name'],
411 'value' => 'Next run: ' . $this->convert_time_to_date( $next_scheduled, true, true ),
412 'comment' => $comment,
413 );
414 }
415
416 if ( ! \WpMatomo::is_safe_mode() && $report ) {
417 $rows[] = array(
418 'section' => esc_html__( 'Mandatory checks', 'matomo' ),
419 );
420
421 $rows = $this->add_diagnostic_results( $rows, $report->getMandatoryDiagnosticResults() );
422
423 $rows[] = array(
424 'section' => esc_html__( 'Optional checks', 'matomo' ),
425 );
426 $rows = $this->add_diagnostic_results( $rows, $report->getOptionalDiagnosticResults() );
427
428 $cli_multi = new CliMulti();
429
430 $rows[] = array(
431 'name' => 'Supports Async Archiving',
432 'value' => $cli_multi->supportsAsync(),
433 'comment' => '',
434 );
435
436 $location_provider = LocationProvider::getCurrentProvider();
437 if ($location_provider) {
438 $rows[] = array(
439 'name' => 'Location provider ID',
440 'value' => $location_provider->getId(),
441 'comment' => '',
442 );
443 $rows[] = array(
444 'name' => 'Location provider available',
445 'value' => $location_provider->isAvailable(),
446 'comment' => '',
447 );
448 $rows[] = array(
449 'name' => 'Location provider working',
450 'value' => $location_provider->isWorking(),
451 'comment' => '',
452 );
453 }
454
455 $num_days_check_visits = 5;
456 $had_visits = $this->had_visits_in_last_days($num_days_check_visits);
457 if ($had_visits === false || $had_visits === true) {
458 // do not show info if we could not detect it (had_visits === null)
459 $comment = '';
460 if (!$had_visits) {
461 $comment = 'It looks like there were no visits in the last ' . $num_days_check_visits . ' days. This may be expected if tracking is disabled, you have not added the tracking code, or your website does not have many visitors in general and you exclude your own visits.';
462 }
463
464 $rows[] = array(
465 'name' => 'Had visit in last ' . $num_days_check_visits . ' days',
466 'value' => $had_visits,
467 'is_warning' => !$had_visits && $this->settings->is_tracking_enabled(),
468 'comment' => $comment,
469 );
470 }
471
472 }
473
474 $rows[] = array(
475 'section' => 'Matomo Settings',
476 );
477
478 // always show these settings
479 $global_settings_always_show = array(
480 'track_mode',
481 'track_codeposition',
482 'track_api_endpoint',
483 'track_js_endpoint',
484 );
485 foreach ( $global_settings_always_show as $key ) {
486 $rows[] = array(
487 'name' => ucfirst( str_replace( '_', ' ', $key ) ),
488 'value' => $this->settings->get_global_option( $key ),
489 'comment' => '',
490 );
491 }
492
493 // otherwise show only few customised settings
494 // mostly only numeric values and booleans to not eg accidentally show anything that would store a token etc
495 // like we don't want to show license key etc
496 foreach ( $this->settings->get_customised_global_settings() as $key => $val ) {
497 if ( is_numeric( $val ) || is_bool( $val ) || 'track_content' === $key || 'track_user_id' === $key || 'core_version' === $key || 'version_history' === $key || 'mail_history' === $key ) {
498 if ( is_array( $val ) ) {
499 $val = implode( ', ', $val );
500 }
501
502 $rows[] = array(
503 'name' => ucfirst( str_replace( '_', ' ', $key ) ),
504 'value' => $val,
505 'comment' => '',
506 );
507 }
508 }
509
510 $rows[] = array(
511 'section' => 'Logs',
512 );
513
514 $error_log_entries = $this->logger->get_last_logged_entries();
515 if ( ! empty( $error_log_entries ) ) {
516 foreach ( $error_log_entries as $error ) {
517 $error['value'] = $this->convert_time_to_date( $error['value'], true, false );
518 $error['is_warning'] = !empty($error['name']) && stripos($error['name'], 'archiv') !== false && $error['name'] !== 'archive_boot';
519 $error['comment'] = matomo_anonymize_value($error['comment']);
520 $rows[] = $error;
521 }
522 } else {
523 $rows[] = array(
524 'name' => __('None', 'matomo'),
525 'value' => '',
526 'comment' => '',
527 );
528 }
529
530 return $rows;
531 }
532
533 private function had_visits_in_last_days($numDays)
534 {
535 global $wpdb;
536
537 if (\WpMatomo::is_safe_mode()) {
538 return null;
539 }
540
541 $days_in_seconds = $numDays * 86400;
542 $db = new \WpMatomo\Db\Settings();
543 $prefix_table = $db->prefix_table_name('log_visit');
544
545 $suppress_errors = $wpdb->suppress_errors;
546 $wpdb->suppress_errors( true );// prevent any of this showing in logs just in case
547
548 try {
549 $time = gmdate( 'Y-m-d H:i:s', time() - $days_in_seconds );
550 $sql = $wpdb->prepare('SELECT count(idsite) from ' . $prefix_table . ' where visit_last_action_time > %s LIMIT 1', $time );
551 $row = $wpdb->get_var( $sql );
552 } catch ( \Exception $e ) {
553 $row = null;
554 }
555
556 $wpdb->suppress_errors( $suppress_errors );
557 // we need to differentiate between
558 // 0 === had no visit
559 // 1 === had visit
560 // null === sum error... eg table was not correctly installed
561 if ($row !== null) {
562 $row = !empty($row);
563 }
564
565 return $row;
566 }
567
568 private function convert_time_to_date( $time, $in_blog_timezone, $print_diff = false ) {
569 if ( empty( $time ) ) {
570 return esc_html__( 'Unknown', 'matomo' );
571 }
572
573 $date = gmdate( 'Y-m-d H:i:s', $time );
574
575 if ( $in_blog_timezone ) {
576 $date = get_date_from_gmt( $date, 'Y-m-d H:i:s' );
577 }
578
579 if ( $print_diff && class_exists( '\Piwik\MetricsFormatter' ) ) {
580 $date .= ' (' . MetricsFormatter::getPrettyTimeFromSeconds( $time - time(), true, false, true ) . ')';
581 }
582
583 return $date;
584 }
585
586 private function add_diagnostic_results( $rows, $results ) {
587 foreach ( $results as $result ) {
588 $comment = '';
589 /** @var DiagnosticResult $result */
590 if ( $result->getStatus() !== DiagnosticResult::STATUS_OK ) {
591 foreach ( $result->getItems() as $item ) {
592 $item_comment = $item->getComment();
593 if ( ! empty( $item_comment ) && is_string( $item_comment ) ) {
594 if ( stripos( $item_comment, 'core:archive' ) > 0 ) {
595 // 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"
596 // but not anything that asks user to set up a cronjob
597 $item_comment = substr( $item_comment, 0, stripos( $item_comment, 'core:archive' ) );
598 if ( strpos( $item_comment, '.' ) > 0 ) {
599 $item_comment = substr( $item_comment, 0, strripos( $item_comment, '.' ) );
600 } else {
601 $item_comment = 'Archiving hasn\'t run in a while.';
602 }
603 }
604 $comment .= $item_comment . '<br/>';
605 }
606 }
607 }
608
609 $rows[] = array(
610 'name' => $result->getLabel(),
611 'value' => $result->getStatus() . ' ' . $result->getLongErrorMessage(),
612 'comment' => $comment,
613 'is_warning' => $result->getStatus() === DiagnosticResult::STATUS_WARNING,
614 'is_error' => $result->getStatus() === DiagnosticResult::STATUS_ERROR,
615 );
616 }
617
618 return $rows;
619 }
620
621 private function get_wordpress_info() {
622 $is_multi_site = is_multisite();
623 $num_blogs = 1;
624 $is_network_enabled = false;
625 if ( $is_multi_site ) {
626 if ( function_exists( 'get_blog_count' ) ) {
627 $num_blogs = get_blog_count();
628 }
629 $settings = new Settings();
630 $is_network_enabled = $settings->is_network_enabled();
631 }
632
633 $rows = array();
634 $rows[] = array(
635 'name' => 'Home URL',
636 'value' => home_url(),
637 );
638 $rows[] = array(
639 'name' => 'Site URL',
640 'value' => site_url(),
641 );
642 $rows[] = array(
643 'name' => 'WordPress Version',
644 'value' => get_bloginfo( 'version' ),
645 );
646 $rows[] = array(
647 'name' => 'Number of blogs',
648 'value' => $num_blogs,
649 );
650 $rows[] = array(
651 'name' => 'Multisite Enabled',
652 'value' => $is_multi_site,
653 );
654 $rows[] = array(
655 'name' => 'Network Enabled',
656 'value' => $is_network_enabled,
657 );
658 $rows[] = array(
659 'name' => 'Debug Mode Enabled',
660 'value' => defined( 'WP_DEBUG' ) && WP_DEBUG,
661 );
662 $rows[] = array(
663 'name' => 'Cron Enabled',
664 'value' => defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON,
665 );
666 $rows[] = array(
667 'name' => 'Force SSL Admin',
668 'value' => defined( 'FORCE_SSL_ADMIN' ) && FORCE_SSL_ADMIN,
669 );
670
671 $rows[] = array(
672 'name' => 'Language',
673 'value' => defined( 'WPLANG' ) && WPLANG ? WPLANG : 'en_US',
674 );
675
676 $rows[] = array(
677 'name' => 'Permalink Structure',
678 'value' => get_option( 'permalink_structure' ) ? get_option( 'permalink_structure' ) : 'Default',
679 );
680
681 $rows[] = array(
682 'name' => 'Possibly uses symlink',
683 'value' => strpos( __DIR__, ABSPATH ) === false && strpos( __DIR__, WP_CONTENT_DIR ) === false,
684 );
685
686 $rows[] = array(
687 'name' => 'WP Cache enabled',
688 'value' => defined( 'WP_CACHE' ) && WP_CACHE,
689 );
690
691 if (is_plugin_active('wp-piwik/wp-piwik.php')) {
692 $rows[] = array(
693 'name' => 'WP-Matomo (WP-Piwik) activated',
694 'value' => true,
695 'is_warning' => true,
696 'comment' => 'It is usually not recommended or needed to run Matomo for WordPress and WP-Matomo at the same time. To learn more about the differences between the two plugins view this URL: https://matomo.org/faq/wordpress/why-are-there-two-different-matomo-for-wordpress-plugins-what-is-the-difference-to-wp-matomo-integration-plugin/'
697 );
698
699 $mode = get_option ( 'wp-piwik_global-piwik_mode' );
700 if (function_exists('get_site_option') && is_plugin_active_for_network ( 'wp-piwik/wp-piwik.php' )) {
701 $mode = get_site_option ( 'wp-piwik_global-piwik_mode');
702 }
703 if (!empty($mode)) {
704 $rows[] = array(
705 'name' => 'WP-Matomo mode',
706 'value' => $mode,
707 'is_warning' => $mode === 'php' || $mode === 'PHP',
708 'comment' => 'WP-Matomo is configured in "PHP mode". This is known to cause issues with Matomo for WordPress. We recommend you either deactivate WP-Matomo or you go "Settings => WP-Matomo" and change the "Matomo Mode" in the "Connect to Matomo" section to "Self-hosted HTTP API".'
709 );
710 }
711 }
712
713 $compatible_content_dir = matomo_has_compatible_content_dir();
714 if ($compatible_content_dir === true) {
715 $rows[] = array(
716 'name' => 'Compatible content directory',
717 'value' => true,
718 );
719 } else {
720 $rows[] = array(
721 'name' => 'Compatible content directory',
722 'value' => $compatible_content_dir,
723 'is_warning' => true,
724 'comment' => __( 'It looks like you are maybe using a custom WordPress content directory. The Matomo reporting/admin pages might not work. You may be able to workaround this.', 'matomo' ) . ' ' . __( 'Learn more', 'matomo' ) . ': https://matomo.org/faq/wordpress/how-do-i-make-matomo-for-wordpress-work-when-i-have-a-custom-content-directory/'
725 );
726 }
727
728 return $rows;
729 }
730
731 private function get_server_info() {
732 $rows = array();
733
734 if ( ! empty( $_SERVER['SERVER_SOFTWARE'] ) ) {
735 $rows[] = array(
736 'name' => 'Server Info',
737 'value' => $_SERVER['SERVER_SOFTWARE'],
738 );
739 }
740 if ( PHP_OS ) {
741 $rows[] = array(
742 'name' => 'PHP OS',
743 'value' => PHP_OS,
744 );
745 }
746 $rows[] = array(
747 'name' => 'PHP Version',
748 'value' => phpversion(),
749 );
750 $rows[] = array(
751 'name' => 'PHP SAPI',
752 'value' => php_sapi_name(),
753 );
754 if (defined('PHP_BINARY') && PHP_BINARY) {
755 $rows[] = array(
756 'name' => 'PHP Binary Name',
757 'value' => @basename(PHP_BINARY),
758 );
759 }
760 $rows[] = array(
761 'name' => 'Timezone',
762 'value' => date_default_timezone_get(),
763 );
764 $rows[] = array(
765 'name' => 'Locale',
766 'value' => get_locale(),
767 );
768
769 $rows[] = array(
770 'name' => 'Memory Limit',
771 'value' => @ini_get( 'memory_limit' ),
772 'comment' => 'At least 128MB recommended. Depending on your traffic 256MB or more may be needed.',
773 );
774
775 $rows[] = array(
776 'name' => 'Max Memory Limit',
777 'value' => defined( 'WP_MAX_MEMORY_LIMIT' ) ? WP_MAX_MEMORY_LIMIT : '',
778 'comment' => '',
779 );
780
781 $rows[] = array(
782 'name' => 'Time',
783 'value' => time(),
784 );
785
786 $rows[] = array(
787 'name' => 'Max Execution Time',
788 'value' => ini_get( 'max_execution_time' ),
789 );
790 $rows[] = array(
791 'name' => 'Max Post Size',
792 'value' => ini_get( 'post_max_size' ),
793 );
794 $rows[] = array(
795 'name' => 'Max Upload Size',
796 'value' => wp_max_upload_size(),
797 );
798 $rows[] = array(
799 'name' => 'Max Input Vars',
800 'value' => ini_get( 'max_input_vars' ),
801 );
802
803 $disabled_functions = ini_get('disable_functions');
804 $rows[] = array(
805 'name' => 'Disabled PHP functions',
806 'value' => !empty($disabled_functions),
807 'comment' => !empty($disabled_functions) ? $disabled_functions : ''
808 );
809
810 $zlib_compression = ini_get( 'zlib.output_compression' );
811 $row = array(
812 'name' => 'zlib.output_compression is off',
813 'value' => $zlib_compression !== '1',
814 );
815
816 if ( $zlib_compression === '1' ) {
817 $row['is_error'] = true;
818 $row['comment'] = 'You need to set "zlib.output_compression" in your php.ini to "Off".';
819 }
820 $rows[] = $row;
821
822 if ( function_exists( 'curl_version' ) ) {
823 $curl_version = curl_version();
824 $curl_version = $curl_version['version'] . ', ' . $curl_version['ssl_version'];
825 $rows[] = array(
826 'name' => 'Curl Version',
827 'value' => $curl_version,
828 );
829 }
830
831 return $rows;
832 }
833
834 private function get_browser_info() {
835 $rows = array();
836
837 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
838 $rows[] = array(
839 'name' => 'Browser',
840 'value' => $_SERVER['HTTP_USER_AGENT'],
841 );
842 }
843 if (!\WpMatomo::is_safe_mode()) {
844 Bootstrap::do_bootstrap();
845 $rows[] = array(
846 'name' => 'Language',
847 'value' => Common::getBrowserLanguage()
848 );
849 }
850
851
852 return $rows;
853 }
854
855 private function get_db_info() {
856 global $wpdb;
857 $rows = array();
858
859 $rows[] = array(
860 'name' => 'MySQL Version',
861 'value' => ! empty( $wpdb->is_mysql ) ? $wpdb->db_version() : '',
862 'comment' => '',
863 );
864
865 $rows[] = array(
866 'name' => 'Mysqli Connect',
867 'value' => function_exists( 'mysqli_connect' ),
868 'comment' => '',
869 );
870 $rows[] = array(
871 'name' => 'Force MySQL over Mysqli',
872 'value' => defined( 'WP_USE_EXT_MYSQL' ) && WP_USE_EXT_MYSQL,
873 'comment' => '',
874 );
875
876 $rows[] = array(
877 'name' => 'DB Prefix',
878 'value' => $wpdb->prefix,
879 );
880
881 if ( method_exists( $wpdb, 'parse_db_host' ) ) {
882 $host_data = $wpdb->parse_db_host( DB_HOST );
883 if ( $host_data ) {
884 list( $host, $port, $socket, $is_ipv6 ) = $host_data;
885 }
886
887 $rows[] = array(
888 'name' => 'Uses Socket',
889 'value' => ! empty( $socket ),
890 );
891 $rows[] = array(
892 'name' => 'Uses IPv6',
893 'value' => ! empty( $is_ipv6 ),
894 );
895 }
896
897 $grants = $this->get_db_grants();
898
899 // we only show these grants for security reasons as only they are needed and we don't need to know any other ones
900 $needed_grants = array( 'SELECT', 'INSERT', 'UPDATE', 'INDEX', 'DELETE', 'CREATE', 'DROP', 'ALTER', 'CREATE TEMPORARY TABLES', 'LOCK TABLES' );
901 if ( in_array( 'ALL PRIVILEGES', $grants, true ) ) {
902 // ALL PRIVILEGES may be used pre MySQL 8.0
903 $grants = $needed_grants;
904 }
905
906 $grants_missing = array_diff( $needed_grants, $grants );
907
908 if ( empty( $grants )
909 || ! is_array( $grants )
910 || count( $grants_missing ) === count( $needed_grants ) ) {
911 $rows[] = array(
912 'name' => esc_html__( 'Required permissions', 'matomo' ),
913 'value' => esc_html__( 'Failed to detect granted permissions', 'matomo' ),
914 'comment' => esc_html__( 'Please check your MySQL user has these permissions (grants):', 'matomo' ) . '<br />' . implode( ', ', $needed_grants ),
915 'is_warning' => false,
916 );
917 } else {
918 if ( ! empty( $grants_missing ) ) {
919 $rows[] = array(
920 'name' => esc_html__( 'Required permissions', 'matomo' ),
921 'value' => esc_html__( 'Error', 'matomo' ),
922 'comment' => esc_html__( 'Missing permissions', 'matomo' ) . ': ' . implode( ', ', $grants_missing ) . '. ' . __( 'Please check if any of these MySQL permission (grants) are missing and add them if needed.', 'matomo' ) . ' ' . __( 'Learn more', 'matomo' ) . ': https://matomo.org/faq/troubleshooting/how-do-i-check-if-my-mysql-user-has-all-required-grants/',
923 'is_warning' => true,
924 );
925 } else {
926 $rows[] = array(
927 'name' => esc_html__( 'Required permissions', 'matomo' ),
928 'value' => esc_html__( 'OK', 'matomo' ),
929 'comment' => '',
930 'is_warning' => false,
931 );
932 }
933 }
934
935 return $rows;
936 }
937
938 private function get_db_grants() {
939 global $wpdb;
940
941 $suppress_errors = $wpdb->suppress_errors;
942 $wpdb->suppress_errors( true );// prevent any of this showing in logs just in case
943
944 try {
945 $values = $wpdb->get_results( 'SHOW GRANTS', ARRAY_N );
946 } catch ( \Exception $e ) {
947 // We ignore any possible error in case of permission or not supported etc.
948 $values = array();
949 }
950
951 $wpdb->suppress_errors( $suppress_errors );
952
953 $grants = array();
954 foreach ( $values as $index => $value ) {
955 if ( empty( $value[0] ) || ! is_string( $value[0] ) ) {
956 continue;
957 }
958
959 if ( stripos( $value[0], 'ALL PRIVILEGES' ) !== false ) {
960 return array( 'ALL PRIVILEGES' ); // the split on empty string wouldn't work otherwise
961 }
962
963 foreach ( array( ' ON ', ' TO ', ' IDENTIFIED ', ' BY ' ) as $keyword ) {
964 if ( stripos( $values[ $index ][0], $keyword ) !== false ) {
965 // make sure to never show by any accident a db user or password by cutting anything after on/to
966 $values[ $index ][0] = substr( $value[0], 0, stripos( $value[0], $keyword ) );
967 }
968 if ( stripos( $values[ $index ][0], 'GRANT' ) !== false ) {
969 // otherwise we end up having "grant select"... instead of just "select"
970 $values[ $index ][0] = substr( $value[0], stripos( $values[ $index ][0], 'GRANT' ) + 5 );
971 }
972 }
973 // make sure to never show by any accident a db user or password
974 $values[ $index ][0] = str_replace( array( DB_USER, DB_PASSWORD ), array( 'DB_USER', 'DB_PASS' ), $values[ $index ][0] );
975
976 $grants = array_merge( $grants, explode( ',', $values[ $index ][0] ) );
977 }
978 $grants = array_map( 'trim', $grants );
979 $grants = array_map( 'strtoupper', $grants );
980 $grants = array_unique( $grants );
981 return $grants;
982 }
983
984 private function get_plugins_info() {
985 $rows = array();
986 $mu_plugins = get_mu_plugins();
987
988 if ( ! empty( $mu_plugins ) ) {
989 $rows[] = array(
990 'section' => 'MU Plugins',
991 );
992
993 foreach ( $mu_plugins as $mu_pin ) {
994 $comment = '';
995 if ( ! empty( $plugin['Network'] ) ) {
996 $comment = 'Network enabled';
997 }
998 $rows[] = array(
999 'name' => $mu_pin['Name'],
1000 'value' => $mu_pin['Version'],
1001 'comment' => $comment,
1002 );
1003 }
1004
1005 $rows[] = array(
1006 'section' => 'Plugins',
1007 );
1008 }
1009
1010 $plugins = get_plugins();
1011
1012 foreach ( $plugins as $plugin ) {
1013 $comment = '';
1014 if ( ! empty( $plugin['Network'] ) ) {
1015 $comment = 'Network enabled';
1016 }
1017 $rows[] = array(
1018 'name' => $plugin['Name'],
1019 'value' => $plugin['Version'],
1020 'comment' => $comment,
1021 );
1022 }
1023
1024 $active_plugins = get_option( 'active_plugins', array() );
1025
1026 if ( ! empty( $active_plugins ) && is_array( $active_plugins ) ) {
1027 $active_plugins = array_map(
1028 function ( $active_plugin ) {
1029 $parts = explode( '/', trim( $active_plugin ) );
1030 return trim( $parts[0] );
1031 },
1032 $active_plugins
1033 );
1034
1035 $rows[] = array(
1036 'name' => 'Active Plugins',
1037 'value' => count( $active_plugins ),
1038 'comment' => implode( ' ', $active_plugins ),
1039 );
1040
1041 $used_not_compatible = array_intersect( $active_plugins, $this->not_compatible_plugins );
1042 if ( ! empty( $used_not_compatible ) ) {
1043 $rows[] = array(
1044 'name' => __( 'Not compatible plugins', 'matomo' ),
1045 'value' => count( $used_not_compatible ),
1046 'comment' => implode( ', ', $used_not_compatible ),
1047 'is_error' => true,
1048 );
1049 }
1050 }
1051
1052 return $rows;
1053 }
1054
1055
1056 }
1057