Keine Beschreibung

class.jetpack-react-page.php 7.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. <?php
  2. use Automattic\Jetpack\Connection\Manager as Connection_Manager;
  3. use Automattic\Jetpack\Status;
  4. include_once( 'class.jetpack-admin-page.php' );
  5. require_once __DIR__ . '/class-jetpack-redux-state-helper.php';
  6. // Builds the landing page and its menu
  7. class Jetpack_React_Page extends Jetpack_Admin_Page {
  8. protected $dont_show_if_not_active = false;
  9. protected $is_redirecting = false;
  10. function get_page_hook() {
  11. // Add the main admin Jetpack menu
  12. return add_menu_page( 'Jetpack', 'Jetpack', 'jetpack_admin_page', 'jetpack', array( $this, 'render' ), 'div', 3 );
  13. }
  14. function add_page_actions( $hook ) {
  15. /** This action is documented in class.jetpack.php */
  16. do_action( 'jetpack_admin_menu', $hook );
  17. if ( ! isset( $_GET['page'] ) || 'jetpack' !== $_GET['page'] ) {
  18. return; // No need to handle the fallback redirection if we are not on the Jetpack page
  19. }
  20. // Adding a redirect meta tag if the REST API is disabled
  21. if ( ! $this->is_rest_api_enabled() ) {
  22. $this->is_redirecting = true;
  23. add_action( 'admin_head', array( $this, 'add_fallback_head_meta' ) );
  24. }
  25. // Adding a redirect meta tag wrapped in noscript tags for all browsers in case they have JavaScript disabled
  26. add_action( 'admin_head', array( $this, 'add_noscript_head_meta' ) );
  27. // If this is the first time the user is viewing the admin, don't show JITMs.
  28. // This filter is added just in time because this function is called on admin_menu
  29. // and JITMs are initialized on admin_init
  30. if ( Jetpack::is_connection_ready() && ! Jetpack_Options::get_option( 'first_admin_view', false ) ) {
  31. Jetpack_Options::update_option( 'first_admin_view', true );
  32. add_filter( 'jetpack_just_in_time_msgs', '__return_false' );
  33. }
  34. }
  35. /**
  36. * Add Jetpack Dashboard sub-link and point it to AAG if the user can view stats, manage modules or if Protect is active.
  37. *
  38. * Works in Dev Mode or when user is connected.
  39. *
  40. * @since 4.3.0
  41. */
  42. function jetpack_add_dashboard_sub_nav_item() {
  43. if ( ( new Status() )->is_offline_mode() || Jetpack::is_connection_ready() ) {
  44. add_submenu_page( 'jetpack', __( 'Dashboard', 'jetpack' ), __( 'Dashboard', 'jetpack' ), 'jetpack_admin_page', 'jetpack#/dashboard', '__return_null' );
  45. remove_submenu_page( 'jetpack', 'jetpack' );
  46. }
  47. }
  48. /**
  49. * Determine whether a user can access the Jetpack Settings page.
  50. *
  51. * Rules are:
  52. * - user is allowed to see the Jetpack Admin
  53. * - site is connected or in offline mode
  54. * - non-admins only need access to the settings when there are modules they can manage.
  55. *
  56. * @return bool $can_access_settings Can the user access settings.
  57. */
  58. private function can_access_settings() {
  59. $connection = new Connection_Manager( 'jetpack' );
  60. $status = new Status();
  61. // User must have the necessary permissions to see the Jetpack settings pages.
  62. if ( ! current_user_can( 'edit_posts' ) ) {
  63. return false;
  64. }
  65. // In offline mode, allow access to admins.
  66. if ( $status->is_offline_mode() && current_user_can( 'manage_options' ) ) {
  67. return true;
  68. }
  69. // If not in offline mode but site is not connected, bail.
  70. if ( ! Jetpack::is_connection_ready() ) {
  71. return false;
  72. }
  73. /*
  74. * Additional checks for non-admins.
  75. */
  76. if ( ! current_user_can( 'manage_options' ) ) {
  77. // If the site isn't connected at all, bail.
  78. if ( ! $connection->has_connected_owner() ) {
  79. return false;
  80. }
  81. /*
  82. * If they haven't connected their own account yet,
  83. * they have no use for the settings page.
  84. * They will not be able to manage any settings.
  85. */
  86. if ( ! $connection->is_user_connected() ) {
  87. return false;
  88. }
  89. /*
  90. * Non-admins only have access to settings
  91. * for the following modules:
  92. * - Publicize
  93. * - Post By Email
  94. * If those modules are not available, bail.
  95. */
  96. if (
  97. ! Jetpack::is_module_active( 'post-by-email' )
  98. && ! Jetpack::is_module_active( 'publicize' )
  99. ) {
  100. return false;
  101. }
  102. }
  103. // fallback.
  104. return true;
  105. }
  106. /**
  107. * Jetpack Settings sub-link.
  108. *
  109. * @since 4.3.0
  110. * @since 9.7.0 If Connection does not have an owner, restrict it to admins
  111. */
  112. function jetpack_add_settings_sub_nav_item() {
  113. if ( $this->can_access_settings() ) {
  114. add_submenu_page( 'jetpack', __( 'Settings', 'jetpack' ), __( 'Settings', 'jetpack' ), 'jetpack_admin_page', 'jetpack#/settings', '__return_null' );
  115. }
  116. }
  117. function add_fallback_head_meta() {
  118. echo '<meta http-equiv="refresh" content="0; url=?page=jetpack_modules">';
  119. }
  120. function add_noscript_head_meta() {
  121. echo '<noscript>';
  122. $this->add_fallback_head_meta();
  123. echo '</noscript>';
  124. }
  125. /**
  126. * Custom menu order.
  127. *
  128. * @deprecated since 9.2.0
  129. * @param array $menu_order Menu order.
  130. * @return array
  131. */
  132. function jetpack_menu_order( $menu_order ) {
  133. _deprecated_function( __METHOD__, 'jetpack-9.2' );
  134. return $menu_order;
  135. }
  136. function page_render() {
  137. /** This action is already documented in views/admin/admin-page.php */
  138. do_action( 'jetpack_notices' );
  139. // Try fetching by patch
  140. $static_html = @file_get_contents( JETPACK__PLUGIN_DIR . '_inc/build/static.html' );
  141. if ( false === $static_html ) {
  142. // If we still have nothing, display an error
  143. echo '<p>';
  144. esc_html_e( 'Error fetching static.html. Try running: ', 'jetpack' );
  145. echo '<code>pnpm run distclean && pnpx jetpack build plugins/jetpack</code>';
  146. echo '</p>';
  147. } else {
  148. // We got the static.html so let's display it
  149. echo $static_html;
  150. }
  151. }
  152. /**
  153. * Allow robust deep links to React.
  154. *
  155. * The Jetpack dashboard requires fragments/hash values to make
  156. * a deep link to it but passing fragments as part of a return URL
  157. * will most often be discarded throughout the process.
  158. * This logic aims to bridge this gap and reduce the chance of React
  159. * specific links being broken while passing them along.
  160. */
  161. public function react_redirects() {
  162. global $pagenow;
  163. // phpcs:ignore WordPress.Security.NonceVerification.Recommended
  164. if ( 'admin.php' !== $pagenow || ! isset( $_GET['jp-react-redirect'] ) ) {
  165. return;
  166. }
  167. $allowed_paths = array(
  168. 'product-purchased' => admin_url( '/admin.php?page=jetpack#/recommendations/product-purchased' ),
  169. );
  170. // phpcs:ignore WordPress.Security.NonceVerification.Recommended
  171. $target = sanitize_text_field( (string) $_GET['jp-react-redirect'] );
  172. if ( isset( $allowed_paths[ $target ] ) ) {
  173. wp_safe_redirect( $allowed_paths[ $target ] );
  174. exit;
  175. }
  176. }
  177. function additional_styles() {
  178. Jetpack_Admin_Page::load_wrapper_styles();
  179. }
  180. function page_admin_scripts() {
  181. if ( $this->is_redirecting ) {
  182. return; // No need for scripts on a fallback page
  183. }
  184. $status = new Status();
  185. $is_offline_mode = $status->is_offline_mode();
  186. $site_suffix = $status->get_site_suffix();
  187. $script_deps_path = JETPACK__PLUGIN_DIR . '_inc/build/admin.asset.php';
  188. $script_dependencies = array( 'wp-polyfill' );
  189. if ( file_exists( $script_deps_path ) ) {
  190. $asset_manifest = include $script_deps_path;
  191. $script_dependencies = $asset_manifest['dependencies'];
  192. }
  193. wp_enqueue_script(
  194. 'react-plugin',
  195. plugins_url( '_inc/build/admin.js', JETPACK__PLUGIN_FILE ),
  196. $script_dependencies,
  197. JETPACK__VERSION,
  198. true
  199. );
  200. if ( ! $is_offline_mode && Jetpack::is_connection_ready() ) {
  201. // Required for Analytics.
  202. wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
  203. }
  204. wp_set_script_translations( 'react-plugin', 'jetpack' );
  205. // Add objects to be passed to the initial state of the app.
  206. // Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
  207. wp_add_inline_script( 'react-plugin', 'var Initial_State=JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( Jetpack_Redux_State_Helper::get_initial_state() ) ) . '"));', 'before' );
  208. // This will set the default URL of the jp_redirects lib.
  209. wp_add_inline_script( 'react-plugin', 'var jetpack_redirects = { currentSiteRawUrl: "' . $site_suffix . '" };', 'before' );
  210. }
  211. }