✏️ 编辑:class-data.php
路径:
/home/forge/kingkrunch.com/wp/wp-content/plugins/wpmudev-updates/includes/endpoints/class-data.php
大小:21.9 KB · 修改:2026-10-01 14:16:23 · 权限:0644 · 可写
← 返回目录
👁 查看
⬇ 下载
<?php /** * Data functionality REST endpoint. * * @link http://wpmudev.com * @since 4.12.0 * @author Joel James <joel@incsub.com> * @package WPMUDEV\Dashboard\Endpoints */ namespace WPMUDEV\Dashboard\Endpoints; // If this file is called directly, abort. defined( 'WPINC' ) || die; use WP_REST_Request; use WP_REST_Response; use WP_REST_Server; use WPMUDEV_Dashboard; /** * Class Data */ class Data extends Endpoint { /** * API endpoint for the current endpoint. * * @since 5.0.0 * @var string $endpoint */ private string $endpoint = '/data'; /** * Transient holding the cached HTTP section of System Info. * * A cache, not a setting: no default, not cleared on reset/logout, expires on its own ("Check again" for sooner). * Holds only local machine data about the site's own server, so no PII/GDPR concern. * * @since 5.0.3 * @var string */ private const HTTP_CACHE_KEY = 'system_info_http'; /** * Transient marking a recent lookup, used to throttle forced refreshes. * * @since 5.0.3 * @var string */ private const HTTP_LOCK_KEY = 'system_info_http_lock'; /** * How long a successful lookup is cached, in seconds. * * @since 5.0.3 * @var int */ private const HTTP_CACHE_TTL = 12 * HOUR_IN_SECONDS; /** * How long a failed lookup is cached, in seconds. * * @since 5.0.3 * @var int */ private const HTTP_CACHE_TTL_FAILED = 5 * MINUTE_IN_SECONDS; /** * Minimum delay between two forced refreshes, in seconds. * * @since 5.0.3 * @var int */ private const HTTP_REFRESH_THROTTLE = MINUTE_IN_SECONDS; /** * Register the routes for handling data functionality. * * @since 5.0.0 * * @return void */ public function register_routes() { // Routes to get user list. register_rest_route( $this->get_namespace(), $this->endpoint . '/available-users', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_users' ), 'permission_callback' => array( $this, 'permissions_check' ), ), ) ); // Routes to get site list. register_rest_route( $this->get_namespace(), $this->endpoint . '/sites', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_sites' ), 'permission_callback' => array( $this, 'permissions_check' ), 'args' => array( 'search' => array( 'required' => false, 'description' => __( 'Search term.', 'wpmudev' ), 'type' => 'string', ), 'page' => array( 'required' => false, 'description' => __( 'Page number.', 'wpmudev' ), 'type' => 'integer', 'default' => 1, 'minimum' => 1, ), 'per_page' => array( 'required' => false, 'description' => __( 'No. of sites per page.', 'wpmudev' ), 'type' => 'integer', 'default' => 10, 'minimum' => 1, ), ), ), ) ); // Routes to get site list. register_rest_route( $this->get_namespace(), $this->endpoint . '/sites/details', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_sites_details' ), 'permission_callback' => array( $this, 'permissions_check' ), 'args' => array( 'ids' => array( 'required' => true, 'maxItems' => 100, 'description' => __( 'Site ids.', 'wpmudev' ), 'type' => 'array', 'items' => array( 'type' => 'integer', 'minimum' => 1, ), ), ), ), ) ); // Routes to get service list. register_rest_route( $this->get_namespace(), $this->endpoint . '/services', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_services' ), 'permission_callback' => array( $this, 'permissions_check' ), ), ) ); // Routes to get system info. // Admin only until connected, full Dashboard check after - see permissions_check_system_info(). register_rest_route( $this->get_namespace(), $this->endpoint . '/system-info', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_system_info' ), 'permission_callback' => array( $this, 'permissions_check_system_info' ), 'args' => array( 'refresh' => array( 'required' => false, 'description' => __( 'Re-run the outbound HTTP checks instead of using the cached result.', 'wpmudev' ), 'type' => 'boolean', 'default' => false, ), ), ), ) ); } /** * Permission check for the system-info route. * * - Admin only while the site is disconnected: * - System info is what support/user asks for when the connection itself is the problem, and the allowed_users list is typically empty or stale * - Default Dashboard permissions_check, once the site Connected ( has API key ): * - The allowed_users list is authoritative at this point, so honour it * * @since 5.0.3 * * @return bool */ public function permissions_check_system_info(): bool { if ( ! WPMUDEV_Dashboard::$api->has_key() ) { return $this->permissions_check_admin_only(); } return $this->permissions_check(); } /** * Get list of available users. * * @since 5.0.0 * * @param WP_REST_Request $request Request object. * * @return WP_REST_Response */ public function get_users( $request ) { $users = array(); // Get available users. $available_users = WPMUDEV_Dashboard::$site->get_available_users( 100, false ); $allowed = WPMUDEV_Dashboard::$site->get_allowed_users( true ); foreach ( $available_users as $user ) { $user->ID = (int) $user->ID; if ( isset( $user->id ) ) { $user->id = $user->ID; } // Add avatar URL. $user->avatar_url = get_avatar_url( $user->ID ); $user->is_allowed = in_array( $user->ID, array_map( 'intval', $allowed ), true ); $users[] = $user; } return $this->get_response( $users ); } /** * Get list of sites using search query. * * @since 5.0.0 * * @param WP_REST_Request $request Request object. * * @return WP_REST_Response */ public function get_sites( $request ) { $search = $request->get_param( 'search' ); $page = $request->get_param( 'page' ); $per_page = $request->get_param( 'per_page' ); if ( ! is_multisite() ) { return $this->get_error_response( 'not_multisite', __( 'Not a multisite to search sites.', 'wpmudev' ) ); } $items = array(); $args = array( 'search' => sanitize_text_field( $search ), 'fields' => 'ids', 'number' => $per_page ?? 10, 'offset' => ( $page - 1 ) * $per_page, 'no_found_rows' => false, ); // Get site ids. $query = new \WP_Site_Query(); $site_ids = $query->query( $args ); $total = $query->found_sites; if ( ! empty( $site_ids ) ) { foreach ( $site_ids as $site_id ) { $items[] = array( 'id' => $site_id, 'text' => str_replace( array( 'https://', 'http://' ), '', get_home_url( $site_id ) ), ); } } return $this->get_response_with_pagination( $per_page, $total, $items ); } /** * Get sites details given its id * * @since 5.0.0 * * @param WP_REST_Request $request Request object. * * @return WP_REST_Response */ public function get_sites_details( $request ) { $site_ids = $request->get_param( 'ids' ); if ( ! is_multisite() ) { return $this->get_error_response( 'not_multisite', __( 'Not a multisite.', 'wpmudev' ) ); } $items = array(); foreach ( $site_ids as $site_id ) { $text = str_replace( array( 'https://', 'http://' ), '', get_home_url( $site_id ) ); // Add to the array only if the text is not empty. if ( ! empty( $text ) ) { $items[] = array( 'id' => $site_id, 'text' => $text, ); } } return $this->get_response( $items ); } /** * Get list of services. * * @since 5.0.0 * * @param WP_REST_Request $request Request object. * * @return WP_REST_Response */ public function get_services( $request ) { $urls = WPMUDEV_Dashboard::$ui->page_urls; $membership = WPMUDEV_Dashboard::$settings->get( 'membership_data' ); $site_id = WPMUDEV_Dashboard::$api->get_site_id(); $url_base = sprintf( '%s/site/%s', $urls->hub_url, $site_id ); $services = array( array( 'title' => __( 'Uptime', 'wpmudev' ), 'url' => esc_url( sprintf( '%s/uptime', $url_base ) ), 'icon' => 'hummingbird', 'is_activate' => ! empty( $membership['services']['uptime'] ), ), array( 'title' => __( 'Automate', 'wpmudev' ), 'url' => esc_url( sprintf( '%s/pluginsThemes/automate', $url_base ) ), 'icon' => 'defender', 'is_activate' => ! empty( $membership['services']['automate'] ), ), array( 'title' => __( 'Reports', 'wpmudev' ), 'url' => esc_url( sprintf( '%s/reports', $url_base ) ), 'icon' => 'smart-crawl', 'is_activate' => ! empty( $membership['services']['reports'] ), ), ); return $this->get_response( $services ); } /** * Get the system info data. * * @since 5.0.0 * * @param WP_REST_Request $request Request object. * * @return WP_REST_Response */ public function get_system_info( $request ) { if ( function_exists( 'wp_raise_memory_limit' ) ) { wp_raise_memory_limit( 'admin' ); // to trigger WP_MAX_MEMORY_LIMIT ( which isn't triggered in WP REST ). } $info = array( 'php' => $this->get_php_vars(), 'mysql' => $this->get_mysql_vars(), 'server' => $this->get_server_vars(), 'wp' => $this->get_wp_vars(), 'http' => $this->get_http_vars( (bool) $request->get_param( 'refresh' ) ), ); return $this->get_response( $info ); } /** * Get the list of PHP vars. * * @since 5.0.0 * * @return array */ private function get_php_vars() { return WPMUDEV_Dashboard::$ui->get_php_vars(); } /** * Get list of MySQL vars. * * @since 5.0.0 * * @return array */ private function get_mysql_vars() { global $wpdb; $vars = array(); $mysql_vars = array( 'key_buffer_size' => true, // Key cache size limit. 'max_allowed_packet' => false, // Individual query size limit. 'max_connections' => false, // Max number of client connections. 'query_cache_limit' => true, // Individual query cache size limit. 'query_cache_size' => true, // Total cache size limit. 'query_cache_type' => 'ON', // Query cache on or off. ); $extra_info = array(); // requires real time data + raw direct call. // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared $variables = $wpdb->get_results( "SHOW VARIABLES WHERE Variable_name IN ( '" . implode( "', '", array_keys( $mysql_vars ) ) . "' )" ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery // phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared $dbh = $wpdb->dbh; if ( is_object( $dbh ) ) { $driver = get_class( $dbh ); if ( method_exists( $dbh, 'db_version' ) ) { $version = $dbh->db_version(); } elseif ( isset( $dbh->server_info ) ) { $version = $dbh->server_info; } elseif ( isset( $dbh->server_version ) ) { $version = $dbh->server_version; } else { $version = __( 'Unknown', 'wpmudev' ); } if ( isset( $dbh->client_info ) ) { $extra_info['Driver version'] = $dbh->client_info; } if ( isset( $dbh->host_info ) ) { $extra_info['Connection info'] = $dbh->host_info; } } else { $version = __( 'Unknown', 'wpmudev' ); $driver = __( 'Unknown', 'wpmudev' ); } $extra_info['Database'] = $wpdb->dbname; $extra_info['Charset'] = $wpdb->charset; $extra_info['Collate'] = $wpdb->collate; $extra_info['Table Prefix'] = $wpdb->prefix; $vars['Server Version'] = $version; $vars['Driver'] = $driver; foreach ( $extra_info as $key => $val ) { $vars[ $key ] = $val; } foreach ( $mysql_vars as $key => $val ) { $vars[ $key ] = $val; } foreach ( $variables as $item ) { // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase $vars[ $item->Variable_name ] = $this->value_format( $item->Value ); // phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase } return $vars; } /** * Get list of WordPress vars. * * @since 5.0.0 * * @return array */ private function get_wp_vars() { global $wp_version; $vars = array(); $wp_constants = array( 'ABSPATH', 'WP_CONTENT_DIR', 'WP_PLUGIN_DIR', 'WPINC', 'WP_LANG_DIR', 'UPLOADBLOGSDIR', 'UPLOADS', 'WP_TEMP_DIR', 'SUNRISE', 'WP_ALLOW_MULTISITE', 'MULTISITE', 'SUBDOMAIN_INSTALL', 'DOMAIN_CURRENT_SITE', 'PATH_CURRENT_SITE', 'SITE_ID_CURRENT_SITE', 'BLOGID_CURRENT_SITE', 'BLOG_ID_CURRENT_SITE', 'COOKIE_DOMAIN', 'COOKIEPATH', 'SITECOOKIEPATH', 'DISABLE_WP_CRON', 'ALTERNATE_WP_CRON', 'DISALLOW_FILE_MODS', 'WP_HTTP_BLOCK_EXTERNAL', 'WP_ACCESSIBLE_HOSTS', 'WP_DEBUG', 'WP_DEBUG_LOG', 'WP_DEBUG_DISPLAY', 'ERRORLOGFILE', 'SCRIPT_DEBUG', 'WP_LANG', 'WP_MAX_MEMORY_LIMIT', 'WP_MEMORY_LIMIT', 'WPMU_ACCEL_REDIRECT', 'WPMU_SENDFILE', ); $vars['WordPress Version'] = $wp_version; foreach ( $wp_constants as $constant ) { $vars[ $constant ] = $this->const_format( $constant ); } return $vars; } /** * Get list of server vars. * * @since 5.0.0 * * @return array */ private function get_server_vars() { $vars = array(); // not user input. // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash $server = explode( ' ', $_SERVER['SERVER_SOFTWARE'] ?? '' ); $server = explode( '/', reset( $server ) ); $vars['Software Name'] = $server[0] ?? ''; $vars['Software Version'] = $server[1] ?? 'Unknown'; $vars['Server IP'] = $_SERVER['SERVER_ADDR'] ?? ''; $vars['External IP'] = $_SERVER['SERVER_ADDR'] ?? ''; $vars['Server Hostname'] = $_SERVER['SERVER_NAME'] ?? ''; $vars['Server Admin'] = $_SERVER['SERVER_ADMIN'] ?? ''; // phpcs:disable WordPress.DateTime.RestrictedFunctions.date_date $vars['Server local time'] = date( 'Y-m-d H:i:s (\U\T\C P)' ); // phpcs:enable WordPress.DateTime.RestrictedFunctions.date_date $vars['Operating System'] = function_exists( 'php_uname' ) ? php_uname( 's' ) : ''; $vars['OS Hostname'] = function_exists( 'php_uname' ) ? php_uname( 'n' ) : ''; $vars['OS Version'] = function_exists( 'php_uname' ) ? php_uname( 'v' ) : ''; // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized // phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash return $vars; } /** * Get list of HTTP vars, from cache when possible. * * The only expensive part of System Info: so it is cached while the rest of the data stays live. The refresh/force is throttled. * * @since 5.0.0 * @since 5.0.3 Cached, with an optional throttled refresh. * * @param bool $force Re-run the checks instead of using the cached result. * * @return array */ private function get_http_vars( bool $force = false ) { $cached = WPMUDEV_Dashboard::$settings->get_transient( self::HTTP_CACHE_KEY ); $cached = ( is_array( $cached ) && isset( $cached['vars'], $cached['time'] ) ) ? $cached : false; // Throttle only when there is something to show: a site without a cached result must get one. if ( $force && $cached && WPMUDEV_Dashboard::$settings->get_transient( self::HTTP_LOCK_KEY ) ) { $force = false; } if ( ! $force && $cached ) { return $this->with_checked_at( $cached['vars'], (int) $cached['time'] ); } $result = $this->fetch_http_vars(); $time = time(); // Failures cached too -- but shorter. WPMUDEV_Dashboard::$settings->set_transient( self::HTTP_CACHE_KEY, array( 'vars' => $result['vars'], 'time' => $time, ), $result['success'] ? self::HTTP_CACHE_TTL : self::HTTP_CACHE_TTL_FAILED ); WPMUDEV_Dashboard::$settings->set_transient( self::HTTP_LOCK_KEY, $time, self::HTTP_REFRESH_THROTTLE ); return $this->with_checked_at( $result['vars'], $time ); } /** * Add the age of the values to the HTTP section, for visitiblity on cached result * * @since 5.0.3 * * @param array $vars HTTP vars. * @param int $time When the values were resolved. * * @return array */ private function with_checked_at( array $vars, int $time ): array { $vars['Last Checked'] = $time; return $vars; } /** * Run the outbound checks of the HTTP section. * * @since 5.0.3 * * @return array */ private function fetch_http_vars(): array { $vars = array(); $options = array(); if ( defined( 'WPMUDEV_API_UNCOMPRESSED' ) && \WPMUDEV_API_UNCOMPRESSED ) { $options['decompress'] = false; } $remote_url = WPMUDEV_Dashboard::$api->get_test_url(); $url = wp_parse_url( $remote_url ); $remote_get = apply_filters( 'wpmudev_api_call_response', null, 'test', array(), 'GET', $options ); if ( is_null( $remote_get ) ) { $remote_get = wp_remote_get( $remote_url, $options ); } $remote_post = apply_filters( 'wpmudev_api_call_response', null, 'test', array(), 'POST', $options ); if ( is_null( $remote_post ) ) { $remote_post = wp_remote_post( $remote_url, $options ); } /** * Filter the external IP lookup, before any outbound request is made. * * Return non-`null` to skip the third party request: Any value still passes IP validation, so no markup can be injected. * * @since 5.0.0 * * @param array|string|\WP_Error|null $remote_ip Pre-resolved value. Null to run the default lookup (https://ipinfo.io/ip). * @param array $options Request args for `wp_remote_get()`. */ $remote_ip = apply_filters( 'wpmudev_http_remote_ip_api_call_response', null, $options ); if ( is_null( $remote_ip ) ) { $remote_ip = $this->get_external_ip( $options ); } if ( is_wp_error( $remote_get ) ) { $remote_get = $remote_get->get_error_message(); } else { $remote_get = wp_remote_retrieve_response_message( $remote_get ); } if ( is_wp_error( $remote_post ) ) { $remote_post = $remote_post->get_error_message(); } else { $remote_post = wp_remote_retrieve_response_message( $remote_post ); } if ( is_wp_error( $remote_ip ) ) { $remote_ip = $remote_ip->get_error_message(); } elseif ( is_array( $remote_ip ) ) { // Filtered value can be a raw HTTP response array. $remote_ip = $this->sanitize_ip( wp_remote_retrieve_body( $remote_ip ) ); } else { $remote_ip = $this->sanitize_ip( $remote_ip ); } $vars['WPMU DEV API Server'] = $url['scheme'] . '://' . $url['host']; $vars['WPMU DEV: GET'] = $remote_get; $vars['WPMU DEV: POST'] = $remote_post; $vars['External IP'] = $remote_ip; // Partial result counts as failure, so it gets the short TTL and is retried soon. $success = ( 'OK' === $remote_get ) && ( 'OK' === $remote_post ) && '' !== $remote_ip; return array( 'vars' => $vars, 'success' => $success, ); } /** * Resolve the server's external IP using a third party echo service. * * Informational only and remote body is validated before display. * * @since 5.0.3 * * @param array $options Request args for `wp_remote_get()`. * * @return string|\WP_Error */ private function get_external_ip( array $options ) { $url = 'https://ipinfo.io/ip'; /** * Filter the timeout of the external IP lookup request. Default stays below * WP's 5s, as it runs inline next to two other blocking calls. * * @since 5.0.3 * * @param int $timeout Timeout in seconds. */ $options['timeout'] = (int) apply_filters( 'wpmudev_http_remote_ip_timeout', 3 ); $response = wp_remote_get( $url, $options ); if ( is_wp_error( $response ) ) { return $response; } return $this->sanitize_ip( wp_remote_retrieve_body( $response ) ); } /** * Validate a third party supplied IP address. * * Accepts IPv4 and IPv6, anything else becomes an empty string. * * @since 5.0.3 * * @param mixed $ip Raw value. * * @return string */ private function sanitize_ip( $ip ): string { if ( ! is_string( $ip ) ) { return ''; } $ip = filter_var( trim( $ip ), FILTER_VALIDATE_IP ); return false === $ip ? '' : $ip; } /** * Helper function for value formatting. * * @since 5.0.0 * * @param mixed $val Value to format. * * @return string */ private function value_format( $val ) { if ( is_numeric( $val ) && ( $val >= ( 1024 * 1024 ) ) ) { $val = size_format( $val ); } return $val; } /** * Helper function. * * @since 4.0.0 * * @param string $constant Name of a PHP const. * * @return string */ private function const_format( $constant ) { if ( ! defined( $constant ) ) { return '<em>undefined</em>'; } $val = constant( $constant ); if ( ! is_bool( $val ) ) { return $val; } elseif ( ! $val ) { return 'FALSE'; } else { return 'TRUE'; } } }
💾 保存
取消
保存为 UTF-8,换行统一为 LF