Source: includes/classes/Screen/StatusReport.php

  1. <?php
  2. /**
  3. * ElasticPress Status Report class
  4. *
  5. * @since 4.4.0
  6. * @package elasticpress
  7. */
  8. namespace ElasticPress\Screen;
  9. use ElasticPress\Utils;
  10. defined( 'ABSPATH' ) || exit;
  11. /**
  12. * Status Report class
  13. *
  14. * @package ElasticPress
  15. */
  16. class StatusReport {
  17. /**
  18. * The formatted/processed reports.
  19. *
  20. * @since 4.5.0
  21. * @var array
  22. */
  23. protected $formatted_reports = [];
  24. /**
  25. * Initialize class
  26. */
  27. public function setup() {
  28. add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] );
  29. add_action( 'admin_head', array( $this, 'admin_menu_count' ), 11 );
  30. add_action( 'wp_ajax_ep_load_groups', array( $this, 'action_wp_ajax_ep_load_groups' ) );
  31. }
  32. /**
  33. * Enqueue script.
  34. *
  35. * @return void
  36. */
  37. public function admin_enqueue_scripts() {
  38. if ( 'status-report' !== \ElasticPress\Screen::factory()->get_current_screen() ) {
  39. return;
  40. }
  41. wp_enqueue_script(
  42. 'ep_admin_status_report_scripts',
  43. EP_URL . 'dist/js/status-report-script.js',
  44. Utils\get_asset_info( 'status-report-script', 'dependencies' ),
  45. Utils\get_asset_info( 'status-report-script', 'version' ),
  46. true
  47. );
  48. $reports = $this->get_formatted_reports();
  49. $plain_text_reports = [];
  50. foreach ( $reports as $report ) {
  51. $title = $report['title'];
  52. $groups = $report['groups'];
  53. $plain_text_reports[] = $this->render_copy_paste_report( $title, $groups, $report['isAjaxReport'] );
  54. }
  55. $plain_text_report = implode( "\n\n", $plain_text_reports );
  56. wp_localize_script(
  57. 'ep_admin_status_report_scripts',
  58. 'epStatusReport',
  59. [
  60. 'plainTextReport' => $plain_text_report,
  61. 'reports' => $reports,
  62. 'nonce' => wp_create_nonce( 'ep-status-report-nonce' ),
  63. ]
  64. );
  65. wp_enqueue_style(
  66. 'ep_status_report_styles',
  67. EP_URL . 'dist/css/status-report-script.css',
  68. [ 'wp-components', 'wp-edit-post' ],
  69. Utils\get_asset_info( 'status-report-script', 'version' )
  70. );
  71. }
  72. /**
  73. * AJAX action to load an individual report group.
  74. *
  75. * @since 5.2.0
  76. *
  77. * @return void
  78. */
  79. public function action_wp_ajax_ep_load_groups(): void {
  80. if ( ! isset( $_POST['ep-status-report-nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['ep-status-report-nonce'] ) ), 'ep-status-report-nonce' ) ) {
  81. wp_send_json_error( [ 'message' => __( 'Nonce is not present.', 'elasticpress' ) ], 403 );
  82. }
  83. if ( empty( $this->formatted_reports ) ) {
  84. $this->formatted_reports = $this->get_reports();
  85. }
  86. $post = wp_unslash( $_POST );
  87. if ( empty( $this->formatted_reports[ $post['report'] ] ) ) {
  88. wp_send_json_error( [ 'message' => __( 'Status report not found.', 'elasticpress' ) ], 404 );
  89. }
  90. $report = $this->formatted_reports[ $post['report'] ];
  91. if ( ! $report instanceof \ElasticPress\StatusReport\AjaxReport ) {
  92. wp_send_json_error( [ 'message' => __( 'Report is not an AJAX report.', 'elasticpress' ) ], 403 );
  93. }
  94. wp_send_json_success(
  95. [
  96. 'groups' => $report->get_groups_ajax(),
  97. 'messages' => $report->get_messages(),
  98. ],
  99. 200
  100. );
  101. }
  102. /**
  103. * Return all reports available
  104. *
  105. * @return array
  106. */
  107. public function get_reports(): array {
  108. $reports = [];
  109. $query_logger = \ElasticPress\get_container()->get( '\ElasticPress\QueryLogger' );
  110. if ( $query_logger ) {
  111. $reports['failed-queries'] = new \ElasticPress\StatusReport\FailedQueries( $query_logger );
  112. }
  113. if ( Utils\is_epio() ) {
  114. $reports['autosuggest'] = new \ElasticPress\StatusReport\ElasticPressIo();
  115. }
  116. $reports['wordpress'] = new \ElasticPress\StatusReport\WordPress();
  117. $reports['indexable'] = new \ElasticPress\StatusReport\IndexableContent();
  118. $reports['elasticpress'] = new \ElasticPress\StatusReport\ElasticPress();
  119. $reports['indices'] = new \ElasticPress\StatusReport\Indices();
  120. $reports['last-sync'] = new \ElasticPress\StatusReport\LastSync();
  121. $reports['features'] = new \ElasticPress\StatusReport\Features();
  122. /**
  123. * Filter the reports executed in the Status Report page.
  124. *
  125. * @since 4.4.0
  126. * @hook ep_status_report_reports
  127. * @param {array<Report>} $reports Array of reports
  128. * @return {array<Report>} New array of reports
  129. */
  130. $filtered_reports = apply_filters( 'ep_status_report_reports', $reports );
  131. // phpcs:disable WordPress.Security.NonceVerification
  132. $skipped_reports = isset( $_GET['ep-skip-reports'] ) ?
  133. array_map( 'sanitize_text_field', (array) wp_unslash( $_GET['ep-skip-reports'] ) ) :
  134. [];
  135. // phpcs:enable WordPress.Security.NonceVerification
  136. $filtered_reports = array_filter(
  137. $filtered_reports,
  138. function ( $report_slug ) use ( $skipped_reports ) {
  139. return ! in_array( $report_slug, $skipped_reports, true );
  140. },
  141. ARRAY_FILTER_USE_KEY
  142. );
  143. return $filtered_reports;
  144. }
  145. /**
  146. * Process and format the reports, then store them in the `formatted_reports` attribute.
  147. *
  148. * @since 4.5.0
  149. * @return array
  150. */
  151. protected function get_formatted_reports(): array {
  152. if ( empty( $this->formatted_reports ) ) {
  153. $reports = $this->get_reports();
  154. $this->formatted_reports = array_map(
  155. function ( $report ) {
  156. return [
  157. 'actions' => $report->get_actions(),
  158. 'groups' => $report->get_groups(),
  159. 'messages' => $report->get_messages(),
  160. 'title' => $report->get_title(),
  161. 'isAjaxReport' => $report instanceof \ElasticPress\StatusReport\AjaxReport,
  162. ];
  163. },
  164. $reports
  165. );
  166. }
  167. return $this->formatted_reports;
  168. }
  169. /**
  170. * Render the copy & paste report
  171. *
  172. * @param string $title Report title
  173. * @param array $groups Report groups
  174. * @param bool $is_ajax_report Whether the report is an AJAX report
  175. *
  176. * @return string
  177. */
  178. protected function render_copy_paste_report( string $title, array $groups, bool $is_ajax_report = false ): string {
  179. $output = "## {$title} ##\n\n";
  180. if ( $is_ajax_report ) {
  181. $output .= $this->render_pending_generation();
  182. return $output;
  183. }
  184. foreach ( $groups as $group ) {
  185. $output .= "### {$group['title']} ###\n";
  186. foreach ( $group['fields'] as $slug => $field ) {
  187. $value = $field['value'] ?? '';
  188. $output .= "{$slug}: ";
  189. $output .= $this->render_value( $value );
  190. $output .= "\n";
  191. }
  192. $output .= "\n";
  193. }
  194. return $output;
  195. }
  196. /**
  197. * Render a value based on its type
  198. *
  199. * @param mixed $value The value
  200. * @return string
  201. */
  202. protected function render_value( $value ) {
  203. if ( is_array( $value ) || is_object( $value ) ) {
  204. return var_export( $value, true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions
  205. }
  206. if ( is_bool( $value ) ) {
  207. return $value ? 'true' : 'false';
  208. }
  209. return (string) $value;
  210. }
  211. /**
  212. * Render a message when the report is pending generation
  213. *
  214. * @return string
  215. */
  216. protected function render_pending_generation() {
  217. return __( 'Please generate a full report to see the content of this group.', 'elasticpress' );
  218. }
  219. /**
  220. * Display a badge in the admin menu if there's admin notices from
  221. * ElasticPress.io.
  222. *
  223. * @return void
  224. */
  225. public function admin_menu_count() {
  226. global $menu, $submenu;
  227. $messages = \ElasticPress\ElasticPressIo::factory()->get_endpoint_messages();
  228. if ( empty( $messages ) ) {
  229. return;
  230. }
  231. $count = count( $messages );
  232. $title = sprintf(
  233. /* translators: %d: Number of messages. */
  234. _n( '%s message from ElasticPress.io', '%s messages from ElasticPress.io', $count, 'elasticpress' ),
  235. $count
  236. );
  237. foreach ( $menu as $key => $value ) {
  238. if ( 'elasticpress' === $value[2] ) {
  239. $menu[ $key ][0] .= sprintf(
  240. ' <span class="update-plugins"><span aria-hidden="true">%1$s</span><span class="screen-reader-text">%2$s</span></span>',
  241. esc_html( $count ),
  242. esc_attr( $title )
  243. );
  244. }
  245. }
  246. if ( ! isset( $submenu['elasticpress'] ) ) {
  247. return;
  248. }
  249. foreach ( $submenu['elasticpress'] as $key => $value ) {
  250. if ( 'elasticpress-status-report' === $value[2] ) {
  251. $submenu['elasticpress'][ $key ][0] .= sprintf(
  252. ' <span class="menu-counter"><span aria-hidden="true">%1$s</span><span class="screen-reader-text">%2$s</span></span>',
  253. esc_html( $count ),
  254. esc_attr( $title )
  255. );
  256. }
  257. }
  258. }
  259. }