No Description

class-jetpack-debug-data.php 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. <?php
  2. /**
  3. * Jetpack Debug Data for the Site Health sections.
  4. *
  5. * @package automattic/jetpack
  6. */
  7. use Automattic\Jetpack\Connection\Tokens;
  8. use Automattic\Jetpack\Connection\Urls;
  9. use Automattic\Jetpack\Constants;
  10. use Automattic\Jetpack\Identity_Crisis;
  11. use Automattic\Jetpack\Redirect;
  12. use Automattic\Jetpack\Sync\Modules;
  13. use Automattic\Jetpack\Sync\Sender;
  14. /**
  15. * Class Jetpack_Debug_Data
  16. *
  17. * Collect and return debug data for Jetpack.
  18. *
  19. * @since 7.3.0
  20. */
  21. class Jetpack_Debug_Data {
  22. /**
  23. * Determine the active plan and normalize it for the debugger results.
  24. *
  25. * @since 7.3.0
  26. *
  27. * @return string The plan slug.
  28. */
  29. public static function what_jetpack_plan() {
  30. $plan = Jetpack_Plan::get();
  31. return ! empty( $plan['class'] ) ? $plan['class'] : 'undefined';
  32. }
  33. /**
  34. * Convert seconds to human readable time.
  35. *
  36. * A dedication function instead of using Core functionality to allow for output in seconds.
  37. *
  38. * @since 7.3.0
  39. *
  40. * @param int $seconds Number of seconds to convert to human time.
  41. *
  42. * @return string Human readable time.
  43. */
  44. public static function seconds_to_time( $seconds ) {
  45. $seconds = (int) $seconds;
  46. $units = array(
  47. 'week' => WEEK_IN_SECONDS,
  48. 'day' => DAY_IN_SECONDS,
  49. 'hour' => HOUR_IN_SECONDS,
  50. 'minute' => MINUTE_IN_SECONDS,
  51. 'second' => 1,
  52. );
  53. // specifically handle zero.
  54. if ( 0 === $seconds ) {
  55. return '0 seconds';
  56. }
  57. $human_readable = '';
  58. foreach ( $units as $name => $divisor ) {
  59. $quot = (int) ( $seconds / $divisor );
  60. if ( $quot ) {
  61. $human_readable .= "$quot $name";
  62. $human_readable .= ( abs( $quot ) > 1 ? 's' : '' ) . ', ';
  63. $seconds -= $quot * $divisor;
  64. }
  65. }
  66. return substr( $human_readable, 0, -2 );
  67. }
  68. /**
  69. * Return debug data in the format expected by Core's Site Health Info tab.
  70. *
  71. * @since 7.3.0
  72. *
  73. * @param array $debug {
  74. * The debug information already compiled by Core.
  75. *
  76. * @type string $label The title for this section of the debug output.
  77. * @type string $description Optional. A description for your information section which may contain basic HTML
  78. * markup: `em`, `strong` and `a` for linking to documentation or putting emphasis.
  79. * @type boolean $show_count Optional. If set to `true` the amount of fields will be included in the title for
  80. * this section.
  81. * @type boolean $private Optional. If set to `true` the section and all associated fields will be excluded
  82. * from the copy-paste text area.
  83. * @type array $fields {
  84. * An associative array containing the data to be displayed.
  85. *
  86. * @type string $label The label for this piece of information.
  87. * @type string $value The output that is of interest for this field.
  88. * @type boolean $private Optional. If set to `true` the field will not be included in the copy-paste text area
  89. * on top of the page, allowing you to show, for example, API keys here.
  90. * }
  91. * }
  92. *
  93. * @return array $args Debug information in the same format as the initial argument.
  94. */
  95. public static function core_debug_data( $debug ) {
  96. $support_url = Jetpack::is_development_version()
  97. ? Redirect::get_url( 'jetpack-contact-support-beta-group' )
  98. : Redirect::get_url( 'jetpack-contact-support' );
  99. $jetpack = array(
  100. 'jetpack' => array(
  101. 'label' => __( 'Jetpack', 'jetpack' ),
  102. 'description' => sprintf(
  103. /* translators: %1$s is URL to jetpack.com's contact support page. %2$s accessibility text */
  104. __(
  105. 'Diagnostic information helpful to <a href="%1$s" target="_blank" rel="noopener noreferrer">your Jetpack Happiness team<span class="screen-reader-text">%2$s</span></a>',
  106. 'jetpack'
  107. ),
  108. esc_url( $support_url ),
  109. __( '(opens in a new tab)', 'jetpack' )
  110. ),
  111. 'fields' => self::debug_data(),
  112. ),
  113. );
  114. $debug = array_merge( $debug, $jetpack );
  115. return $debug;
  116. }
  117. /**
  118. * Compile and return array of debug information.
  119. *
  120. * @since 7.3.0
  121. *
  122. * @return array $args {
  123. * Associated array of arrays with the following.
  124. * @type string $label The label for this piece of information.
  125. * @type string $value The output that is of interest for this field.
  126. * @type boolean $private Optional. Set to true if data is sensitive (API keys, etc).
  127. * }
  128. */
  129. public static function debug_data() {
  130. $debug_info = array();
  131. /* Add various important Jetpack options */
  132. $debug_info['site_id'] = array(
  133. 'label' => 'Jetpack Site ID',
  134. 'value' => Jetpack_Options::get_option( 'id' ),
  135. 'private' => false,
  136. );
  137. $debug_info['ssl_cert'] = array(
  138. 'label' => 'Jetpack SSL Verfication Bypass',
  139. 'value' => ( Jetpack_Options::get_option( 'fallback_no_verify_ssl_certs' ) ) ? 'Yes' : 'No',
  140. 'private' => false,
  141. );
  142. $debug_info['time_diff'] = array(
  143. 'label' => "Offset between Jetpack server's time and this server's time.",
  144. 'value' => Jetpack_Options::get_option( 'time_diff' ),
  145. 'private' => false,
  146. );
  147. $debug_info['version_option'] = array(
  148. 'label' => 'Current Jetpack Version Option',
  149. 'value' => Jetpack_Options::get_option( 'version' ),
  150. 'private' => false,
  151. );
  152. $debug_info['old_version'] = array(
  153. 'label' => 'Previous Jetpack Version',
  154. 'value' => Jetpack_Options::get_option( 'old_version' ),
  155. 'private' => false,
  156. );
  157. $debug_info['public'] = array(
  158. 'label' => 'Jetpack Site Public',
  159. 'value' => ( Jetpack_Options::get_option( 'public' ) ) ? 'Public' : 'Private',
  160. 'private' => false,
  161. );
  162. $debug_info['master_user'] = array(
  163. 'label' => 'Jetpack Master User',
  164. 'value' => self::human_readable_master_user(), // Only ID number and user name.
  165. 'private' => false,
  166. );
  167. /**
  168. * Token information is private, but awareness if there one is set is helpful.
  169. *
  170. * To balance out information vs privacy, we only display and include the "key",
  171. * which is a segment of the token prior to a period within the token and is
  172. * technically not private.
  173. *
  174. * If a token does not contain a period, then it is malformed and we report it as such.
  175. */
  176. $user_id = get_current_user_id();
  177. $blog_token = ( new Tokens() )->get_access_token();
  178. $user_token = ( new Tokens() )->get_access_token( $user_id );
  179. $tokenset = '';
  180. if ( $blog_token ) {
  181. $tokenset = 'Blog ';
  182. $blog_key = substr( $blog_token->secret, 0, strpos( $blog_token->secret, '.' ) );
  183. // Intentionally not translated since this is helpful when sent to Happiness.
  184. $blog_key = ( $blog_key ) ? $blog_key : 'Potentially Malformed Token.';
  185. }
  186. if ( $user_token ) {
  187. $tokenset .= 'User';
  188. $user_key = substr( $user_token->secret, 0, strpos( $user_token->secret, '.' ) );
  189. // Intentionally not translated since this is helpful when sent to Happiness.
  190. $user_key = ( $user_key ) ? $user_key : 'Potentially Malformed Token.';
  191. }
  192. if ( ! $tokenset ) {
  193. $tokenset = 'None';
  194. }
  195. $debug_info['current_user'] = array(
  196. 'label' => 'Current User',
  197. 'value' => self::human_readable_user( $user_id ),
  198. 'private' => false,
  199. );
  200. $debug_info['tokens_set'] = array(
  201. 'label' => 'Tokens defined',
  202. 'value' => $tokenset,
  203. 'private' => false,
  204. );
  205. $debug_info['blog_token'] = array(
  206. 'label' => 'Blog Public Key',
  207. 'value' => ( $blog_token ) ? $blog_key : 'Not set.',
  208. 'private' => false,
  209. );
  210. $debug_info['user_token'] = array(
  211. 'label' => 'User Public Key',
  212. 'value' => ( $user_token ) ? $user_key : 'Not set.',
  213. 'private' => false,
  214. );
  215. /** Jetpack Environmental Information */
  216. $debug_info['version'] = array(
  217. 'label' => 'Jetpack Version',
  218. 'value' => JETPACK__VERSION,
  219. 'private' => false,
  220. );
  221. $debug_info['jp_plugin_dir'] = array(
  222. 'label' => 'Jetpack Directory',
  223. 'value' => JETPACK__PLUGIN_DIR,
  224. 'private' => false,
  225. );
  226. $debug_info['plan'] = array(
  227. 'label' => 'Plan Type',
  228. 'value' => self::what_jetpack_plan(),
  229. 'private' => false,
  230. );
  231. foreach ( array(
  232. 'HTTP_HOST',
  233. 'SERVER_PORT',
  234. 'HTTPS',
  235. 'GD_PHP_HANDLER',
  236. 'HTTP_AKAMAI_ORIGIN_HOP',
  237. 'HTTP_CF_CONNECTING_IP',
  238. 'HTTP_CLIENT_IP',
  239. 'HTTP_FASTLY_CLIENT_IP',
  240. 'HTTP_FORWARDED',
  241. 'HTTP_FORWARDED_FOR',
  242. 'HTTP_INCAP_CLIENT_IP',
  243. 'HTTP_TRUE_CLIENT_IP',
  244. 'HTTP_X_CLIENTIP',
  245. 'HTTP_X_CLUSTER_CLIENT_IP',
  246. 'HTTP_X_FORWARDED',
  247. 'HTTP_X_FORWARDED_FOR',
  248. 'HTTP_X_IP_TRAIL',
  249. 'HTTP_X_REAL_IP',
  250. 'HTTP_X_VARNISH',
  251. 'REMOTE_ADDR',
  252. ) as $header ) {
  253. if ( isset( $_SERVER[ $header ] ) ) {
  254. $debug_info[ $header ] = array(
  255. 'label' => 'Server Variable ' . $header,
  256. 'value' => ( $_SERVER[ $header ] ) ? $_SERVER[ $header ] : 'false',
  257. 'private' => true, // This isn't really 'private' information, but we don't want folks to easily paste these into public forums.
  258. );
  259. }
  260. }
  261. $debug_info['protect_header'] = array(
  262. 'label' => 'Trusted IP',
  263. 'value' => wp_json_encode( get_site_option( 'trusted_ip_header' ) ),
  264. 'private' => false,
  265. );
  266. /** Sync Debug Information */
  267. $sync_module = Modules::get_module( 'full-sync' );
  268. if ( $sync_module ) {
  269. $sync_statuses = $sync_module->get_status();
  270. $human_readable_sync_status = array();
  271. foreach ( $sync_statuses as $sync_status => $sync_status_value ) {
  272. $human_readable_sync_status[ $sync_status ] =
  273. in_array( $sync_status, array( 'started', 'queue_finished', 'send_started', 'finished' ), true )
  274. ? gmdate( 'r', $sync_status_value ) : $sync_status_value;
  275. }
  276. $debug_info['full_sync'] = array(
  277. 'label' => 'Full Sync Status',
  278. 'value' => wp_json_encode( $human_readable_sync_status ),
  279. 'private' => false,
  280. );
  281. }
  282. $queue = Sender::get_instance()->get_sync_queue();
  283. $debug_info['sync_size'] = array(
  284. 'label' => 'Sync Queue Size',
  285. 'value' => $queue->size(),
  286. 'private' => false,
  287. );
  288. $debug_info['sync_lag'] = array(
  289. 'label' => 'Sync Queue Lag',
  290. 'value' => self::seconds_to_time( $queue->lag() ),
  291. 'private' => false,
  292. );
  293. $full_sync_queue = Sender::get_instance()->get_full_sync_queue();
  294. $debug_info['full_sync_size'] = array(
  295. 'label' => 'Full Sync Queue Size',
  296. 'value' => $full_sync_queue->size(),
  297. 'private' => false,
  298. );
  299. $debug_info['full_sync_lag'] = array(
  300. 'label' => 'Full Sync Queue Lag',
  301. 'value' => self::seconds_to_time( $full_sync_queue->lag() ),
  302. 'private' => false,
  303. );
  304. /**
  305. * IDC Information
  306. *
  307. * Must follow sync debug since it depends on sync functionality.
  308. */
  309. $idc_urls = array(
  310. 'home' => Urls::home_url(),
  311. 'siteurl' => Urls::site_url(),
  312. 'WP_HOME' => Constants::is_defined( 'WP_HOME' ) ? Constants::get_constant( 'WP_HOME' ) : '',
  313. 'WP_SITEURL' => Constants::is_defined( 'WP_SITEURL' ) ? Constants::get_constant( 'WP_SITEURL' ) : '',
  314. );
  315. $debug_info['idc_urls'] = array(
  316. 'label' => 'IDC URLs',
  317. 'value' => wp_json_encode( $idc_urls ),
  318. 'private' => false,
  319. );
  320. $debug_info['idc_error_option'] = array(
  321. 'label' => 'IDC Error Option',
  322. 'value' => wp_json_encode( Jetpack_Options::get_option( 'sync_error_idc' ) ),
  323. 'private' => false,
  324. );
  325. $debug_info['idc_optin'] = array(
  326. 'label' => 'IDC Opt-in',
  327. 'value' => Identity_Crisis::should_handle_idc(),
  328. 'private' => false,
  329. );
  330. // @todo -- Add testing results?
  331. $cxn_tests = new Jetpack_Cxn_Tests();
  332. $debug_info['cxn_tests'] = array(
  333. 'label' => 'Connection Tests',
  334. 'value' => '',
  335. 'private' => false,
  336. );
  337. if ( $cxn_tests->pass() ) {
  338. $debug_info['cxn_tests']['value'] = 'All Pass.';
  339. } else {
  340. $debug_info['cxn_tests']['value'] = wp_json_encode( $cxn_tests->list_fails() );
  341. }
  342. return $debug_info;
  343. }
  344. /**
  345. * Returns a human readable string for which user is the master user.
  346. *
  347. * @return string
  348. */
  349. private static function human_readable_master_user() {
  350. $master_user = Jetpack_Options::get_option( 'master_user' );
  351. if ( ! $master_user ) {
  352. return __( 'No master user set.', 'jetpack' );
  353. }
  354. $user = new WP_User( $master_user );
  355. if ( ! $user ) {
  356. return __( 'Master user no longer exists. Please disconnect and reconnect Jetpack.', 'jetpack' );
  357. }
  358. return self::human_readable_user( $user );
  359. }
  360. /**
  361. * Return human readable string for a given user object.
  362. *
  363. * @param WP_User|int $user Object or ID.
  364. *
  365. * @return string
  366. */
  367. private static function human_readable_user( $user ) {
  368. $user = new WP_User( $user );
  369. return sprintf( '#%1$d %2$s', $user->ID, $user->user_login ); // Format: "#1 username".
  370. }
  371. }