server_info_util = new WP_Optimize_Server_Information(); } /** * Returns singleton instance * * @return WP_Optimize_System_Status_Report */ public static function get_instance() { static $_instance = null; if (null === $_instance) { $_instance = new self(); } return $_instance; } /** * Fill the report with actual data * * @return array */ private function generate_wpo_report() { $report_format = $this->get_report_format(); $report_data = array(); foreach ($report_format as $block) { $piece = array( 'title' => $block['title'], 'items' => array() ); foreach ($block['items'] as $part_index => $part) { $piece['items'][$part_index]['title'] = $part['title']; $piece['items'][$part_index]['value'] = $part['value'] ?? ''; if (isset($part['markdown_value'])) { $piece['items'][$part_index]['markdown_value'] = $part['markdown_value']; } } $report_data[] = $piece; } return $report_data; } /** * Define the report format * * @return array */ private function get_report_format() { return array( array( 'title' => esc_html__('WP-Optimize information', 'wp-optimize'), 'items' => array( array( 'title' => esc_html__('Report generation time', 'wp-optimize'), 'value' => esc_html($this->generation_time()) ), array( 'title' => esc_html__('Cache size', 'wp-optimize'), 'value' => $this->cache_size(), ), array( 'title' => esc_html__('Minify size', 'wp-optimize'), 'value' => $this->minify_size(), ), array( 'title' => esc_html__('Logs', 'wp-optimize'), 'value' => $this->logs(), 'markdown_value' => $this->plain_log_debug() ), array( 'title' => esc_html__('WebP redirection rules', 'wp-optimize'), 'value' => $this->webp_redirect_status(), ), array( 'title' => esc_html__('Plugin settings', 'wp-optimize'), 'value' => $this->settings_container(), 'markdown_value' => '{{[wpo-general-settings]}}' ), array( 'title' => esc_html__('Cache settings', 'wp-optimize'), 'value' => $this->cache_settings(), 'markdown_value' => $this->cache_settings(false) ), array( 'title' => esc_html__('Webroot .htaccess', 'wp-optimize'), 'value' => $this->webroot_htaccess(), 'markdown_value' => $this->webroot_htaccess(false), ), array( 'title' => esc_html__('Uploads .htaccess', 'wp-optimize'), 'value' => $this->uploads_htaccess(), 'markdown_value' => $this->uploads_htaccess(false), ), array( 'title' => esc_html__('Folder permissions', 'wp-optimize'), 'value' => $this->path_perms(), 'markdown_value' => $this->path_perms(false), ), ) ), ); } /** * Some WordPress Core Health report fields are loaded via AJAX after page render (ex: directory sizes), these field names are used as tags in JS to replace inside the plaintext report. * This function is a getter for the list that has been set while parsing WP core health report * * @return array */ public function get_replaceable_md_tags() { return $this->replaceable_md_tags; } /** * Generate WPO report and merge with WP code report * * @return array */ public function generate_report() { return array_merge($this->generate_wpo_report(), $this->get_wp_health_report()); } /** * Get debug information from WordPress core * * @return array */ private function get_wp_health_report() { $report = array(); $core_data = $this->server_info_util->get_wp_debug_data(); foreach ($core_data as $data_idx => $data) { $block = array( 'title' => $data['label'], 'items' => array() ); if (!isset($data['fields'])) { $report[] = $block; continue; } foreach ($data['fields'] as $fieldname => $info) { $path_size_field_id = 'wp-paths-sizes' === $data_idx ? $data_idx . '-' . $fieldname : null; $row = array(); $ajax_field_class = null; if (isset($info['debug']) && ('loading...' === $info['debug']) && !is_null($path_size_field_id)) { $replaceable_tag = '{{[' . $path_size_field_id . ']}}'; // Collect the tags that will need to be replaced after page render via Ajax requests to WP api $this->replaceable_md_tags[$fieldname] = $replaceable_tag; $row['markdown_value'] = $replaceable_tag; $ajax_field_class = (string) $data_idx; } $row['title'] = $this->fix_title_case($info['label']); $row['value'] = $this->ajax_value_container($info['value'], $path_size_field_id, $ajax_field_class); $block['items'][] = $row; } $report[] = $block; } return $report; } /** * Hard fix title issue * * @param string $title Original title * @return string */ private function fix_title_case($title) { return str_replace('Site Language', 'Site language', $title); } /** * Give format to cache size information * * @return string */ private function cache_size() { $data = $this->server_info_util->get_cache_size(); $size = size_format($data['size']); if (false === $size) { return esc_html__('Not available', 'wp-optimize'); } // translators: %d represents the number of files. return esc_html($size) . ' ' . sprintf(esc_html__('(%d files)', 'wp-optimize'), $data['file_count']); } /** * Grab minify size from information object * * @return string */ private function minify_size() { $data = $this->server_info_util->get_minify_size(); if (empty($data)) { return esc_html__('Not available', 'wp-optimize'); } return esc_html($data); } /** * Give link for user to download logs in a zip file * * @return string */ private function logs() { $data = $this->server_info_util->get_logs_summary(); if (0 === count($data)) { return esc_html__('No log files found', 'wp-optimize'); } return '' . $this->pre_container(implode("\n", $data)); } /** * Just show log data, no button * * @return string */ private function plain_log_debug() { $data = $this->server_info_util->get_logs_summary(); if (0 < count($data)) { return "\n" . ' - ' . implode("\n - ", $data); } return esc_html__('No log files found', 'wp-optimize'); } /** * Empty placeholder for plugin settings, that will be filled using `WP_Optimize().build_settings()` * * @return string */ private function settings_container() { return $this->pre_container('', 'wpo-general-settings'); } /** * Cache settings into preformatted HTML container * * @param bool $preformat_container Enclose the value inside a
tag
* @return string
*/
private function cache_settings($preformat_container = true) {
$config = $this->server_info_util->get_cache_config();
if (isset($config['wp_salt_auth'])) {
$config['wp_salt_auth'] = '*****';
}
if (isset($config['wp_salt_logged_in'])) {
$config['wp_salt_logged_in'] = '*****';
}
$json = wp_json_encode($config, JSON_PRETTY_PRINT);
return (string) $preformat_container ? $this->pre_container($json) : $json;
}
/**
* Grab .htaccess contents in webroot
*
* @param bool $preformat_container Enclose the value inside a tag
* @return string
*/
private function webroot_htaccess($preformat_container = true) {
$content = $this->server_info_util->get_webroot_htaccess();
if ('' !== $content) {
return $preformat_container ? $this->pre_container($content) : $content;
}
return esc_html__('Not found or not readable', 'wp-optimize');
}
/**
* Grab .htaccess contents in uploads folder
*
* @param bool $preformat_container Enclose the value inside a tag
* @return string
*/
private function uploads_htaccess($preformat_container = true) {
$content = $this->server_info_util->get_upload_dir_htaccess();
if ('' !== $content) {
return $preformat_container ? $this->pre_container($content) : $content;
}
return esc_html__('Not found or not readable', 'wp-optimize');
}
/**
* Helper to generate a preformatted HTML tag with contents and optional id
*
* @param string $content The innerHTML for the tag
* @param string $id Optional DOM id attribute
* @return string
*/
private function pre_container($content, $id = '') {
return '' . esc_html($content) . '
';
}
/**
* Show information about directories permissions
*
* @param bool $preformat_container Enclose the value inside a tag
* @return string
*/
private function path_perms($preformat_container = true) {
$json = wp_json_encode($this->server_info_util->get_uploads_dir_permissions(), JSON_PRETTY_PRINT);
return (string) $preformat_container ? $this->pre_container($json) : $json;
}
/**
* Information about WebP image redirection status
*
* @return string
*/
private function webp_redirect_status() {
$result = $this->server_info_util->webp_redirection_status();
if (null === $result) {
return esc_html__('Redirection is not available', 'wp-optimize');
} elseif (false === $result) {
return esc_html__('Redirection is disabled', 'wp-optimize');
}
return esc_html__('Redirection is enabled', 'wp-optimize');
}
/**
* Prepare complex item (async loading) as a label HTML tag that will be replaced with JS code (ex. directories sizes from core-site-health API call)
*
* @param string|array $value If an array, it will be JSON encoded inside a preformatted HTML tag
* @param string|null $id If not null, it is used as the label id property. If null, there will be no label, just value
* @param string|null $dynamic_field_type If not null, a css class is used to be able to catch these fields when the call is done
* @return string
*/
private function ajax_value_container($value, $id, $dynamic_field_type) {
if (is_array($value)) {
$value = '' . wp_json_encode($value, JSON_PRETTY_PRINT) . '
';
} else {
$value = esc_html($value);
}
if (is_null($id)) {
return $value;
}
$css_class = null !== $dynamic_field_type ? 'wpo-ajax-field-' . esc_attr($dynamic_field_type) : '';
return '';
}
/**
* Add timestamp to report information
*
* @return string
*/
private function generation_time() {
$timezone = $this->wp_timezone_string();
return date_i18n('Y-m-d H:i:s', $this->current_datetime()->getTimestamp()) . ('' !== $timezone ? ' (' . $timezone . ')' : '');
}
/**
* Retrieves the site's timezone string in a backward-compatible way.
*
* If the `wp_timezone_string()` function is available (WordPress 5.3+), it will use it.
* Otherwise, it falls back to a custom implementation that uses `timezone_string` or `gmt_offset`.
*
* Example return values:
* - 'Europe/London'
* - 'UTC'
* - '+01:00'
* - '-06:30'
*
* @return string PHP timezone identifier or a ±HH:MM offset.
*/
private function wp_timezone_string() {
if (function_exists('wp_timezone_string')) {
return wp_timezone_string();
}
$timezone_string = get_option('timezone_string');
if ($timezone_string) {
return $timezone_string;
}
$offset = (float) get_option( 'gmt_offset' );
$hours = (int) $offset;
$minutes = ($offset - $hours);
$sign = ($offset < 0) ? '-' : '+';
$abs_hour = abs($hours);
$abs_mins = abs($minutes * 60);
$tz_offset = sprintf('%s%02d:%02d', $sign, $abs_hour, $abs_mins);
return $tz_offset;
}
/**
* Retrieves the current site datetime object in a backwards-compatible way.
*
* @return DateTimeImmutable
*/
private function current_datetime() {
if (function_exists('current_datetime')) {
return current_datetime();
}
$timezone = new DateTimeZone($this->wp_timezone_string());
return new DateTimeImmutable('now', $timezone);
}
}
endif;