Нема описа

class.jetpack-admin.php 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. <?php
  2. use Automattic\Jetpack\Status;
  3. use Automattic\Jetpack\Assets\Logo as Jetpack_Logo;
  4. // Build the Jetpack admin menu as a whole
  5. class Jetpack_Admin {
  6. /**
  7. * @var Jetpack_Admin
  8. **/
  9. private static $instance = null;
  10. static function init() {
  11. if ( isset( $_GET['page'] ) && $_GET['page'] === 'jetpack' ) {
  12. add_filter( 'nocache_headers', array( 'Jetpack_Admin', 'add_no_store_header' ), 100 );
  13. }
  14. if ( is_null( self::$instance ) ) {
  15. self::$instance = new Jetpack_Admin();
  16. }
  17. return self::$instance;
  18. }
  19. static function add_no_store_header( $headers ) {
  20. $headers['Cache-Control'] .= ', no-store';
  21. return $headers;
  22. }
  23. private function __construct() {
  24. jetpack_require_lib( 'admin-pages/class.jetpack-react-page' );
  25. $this->jetpack_react = new Jetpack_React_Page();
  26. jetpack_require_lib( 'admin-pages/class.jetpack-settings-page' );
  27. $this->fallback_page = new Jetpack_Settings_Page();
  28. jetpack_require_lib( 'admin-pages/class-jetpack-about-page' );
  29. $this->jetpack_about = new Jetpack_About_Page();
  30. jetpack_require_lib( 'admin-pages/class-jetpack-search-dashboard-page' );
  31. $this->jetpack_search = new Jetpack_Search_Dashboard_Page();
  32. add_action( 'admin_init', array( $this->jetpack_react, 'react_redirects' ), 0 );
  33. add_action( 'admin_menu', array( $this->jetpack_react, 'add_actions' ), 998 );
  34. add_action( 'admin_menu', array( $this->jetpack_search, 'add_actions' ), 999 );
  35. add_action( 'jetpack_admin_menu', array( $this->jetpack_react, 'jetpack_add_dashboard_sub_nav_item' ) );
  36. add_action( 'jetpack_admin_menu', array( $this->jetpack_react, 'jetpack_add_settings_sub_nav_item' ) );
  37. add_action( 'jetpack_admin_menu', array( $this, 'admin_menu_debugger' ) );
  38. add_action( 'jetpack_admin_menu', array( $this->fallback_page, 'add_actions' ) );
  39. add_action( 'jetpack_admin_menu', array( $this->jetpack_about, 'add_actions' ) );
  40. // Add redirect to current page for activation/deactivation of modules
  41. add_action( 'jetpack_pre_activate_module', array( $this, 'fix_redirect' ), 10, 2 );
  42. add_action( 'jetpack_pre_deactivate_module', array( $this, 'fix_redirect' ) );
  43. // Add module bulk actions handler
  44. add_action( 'jetpack_unrecognized_action', array( $this, 'handle_unrecognized_action' ) );
  45. if ( class_exists( 'Akismet_Admin' ) ) {
  46. // If the site has Jetpack Anti-Spam, change the Akismet menu label accordingly.
  47. $site_products = Jetpack_Plan::get_products();
  48. $anti_spam_products = array( 'jetpack_anti_spam_monthly', 'jetpack_anti_spam' );
  49. if ( ! empty( array_intersect( $anti_spam_products, array_column( $site_products, 'product_slug' ) ) ) ) {
  50. // Prevent Akismet from adding a menu item.
  51. add_action(
  52. 'admin_menu',
  53. function () {
  54. remove_action( 'admin_menu', array( 'Akismet_Admin', 'admin_menu' ), 5 );
  55. },
  56. 4
  57. );
  58. // Add an Anti-spam menu item for Jetpack.
  59. add_action(
  60. 'jetpack_admin_menu',
  61. function () {
  62. add_submenu_page( 'jetpack', __( 'Anti-Spam', 'jetpack' ), __( 'Anti-Spam', 'jetpack' ), 'manage_options', 'akismet-key-config', array( 'Akismet_Admin', 'display_page' ) );
  63. }
  64. );
  65. add_action( 'admin_enqueue_scripts', array( $this, 'akismet_logo_replacement_styles' ) );
  66. }
  67. }
  68. add_filter( 'jetpack_display_jitms_on_screen', array( $this, 'should_display_jitms_on_screen' ), 10, 2 );
  69. }
  70. /**
  71. * Generate styles to replace Akismet logo for the Jetpack logo. It's a workaround until we create a proper settings page for
  72. * Jetpack Anti-Spam. Without this, we would have to change the logo from Akismet codebase and we want to avoid that.
  73. */
  74. public function akismet_logo_replacement_styles() {
  75. $logo = new Jetpack_Logo();
  76. $logo_base64 = base64_encode( $logo->get_jp_emblem_larger() );
  77. $logo_base64_url = "data:image/svg+xml;base64,{$logo_base64}";
  78. $style = ".akismet-masthead__logo-container { background: url({$logo_base64_url}) no-repeat .25rem; height: 1.8125rem; } .akismet-masthead__logo { display: none; }";
  79. wp_add_inline_style( 'admin-bar', $style );
  80. }
  81. static function sort_requires_connection_last( $module1, $module2 ) {
  82. if ( $module1['requires_connection'] == $module2['requires_connection'] ) {
  83. return 0;
  84. } elseif ( $module1['requires_connection'] ) {
  85. return 1;
  86. } elseif ( $module2['requires_connection'] ) {
  87. return -1;
  88. }
  89. return 0;
  90. }
  91. // Produce JS understandable objects of modules containing information for
  92. // presentation like description, name, configuration url, etc.
  93. function get_modules() {
  94. include_once JETPACK__PLUGIN_DIR . 'modules/module-info.php';
  95. $available_modules = Jetpack::get_available_modules();
  96. $active_modules = Jetpack::get_active_modules();
  97. $modules = array();
  98. $jetpack_active = Jetpack::is_connection_ready() || ( new Status() )->is_offline_mode();
  99. $overrides = Jetpack_Modules_Overrides::instance();
  100. foreach ( $available_modules as $module ) {
  101. if ( $module_array = Jetpack::get_module( $module ) ) {
  102. /**
  103. * Filters each module's short description.
  104. *
  105. * @since 3.0.0
  106. *
  107. * @param string $module_array['description'] Module description.
  108. * @param string $module Module slug.
  109. */
  110. $short_desc = apply_filters( 'jetpack_short_module_description', $module_array['description'], $module );
  111. // Fix: correct multibyte strings truncate with checking for mbstring extension
  112. $short_desc_trunc = ( function_exists( 'mb_strlen' ) )
  113. ? ( ( mb_strlen( $short_desc ) > 143 )
  114. ? mb_substr( $short_desc, 0, 140 ) . '...'
  115. : $short_desc )
  116. : ( ( strlen( $short_desc ) > 143 )
  117. ? substr( $short_desc, 0, 140 ) . '...'
  118. : $short_desc );
  119. $module_array['module'] = $module;
  120. $is_available = self::is_module_available( $module_array );
  121. $module_array['activated'] = ( $jetpack_active ? in_array( $module, $active_modules, true ) : false );
  122. $module_array['deactivate_nonce'] = wp_create_nonce( 'jetpack_deactivate-' . $module );
  123. $module_array['activate_nonce'] = wp_create_nonce( 'jetpack_activate-' . $module );
  124. $module_array['available'] = $is_available;
  125. $module_array['unavailable_reason'] = $is_available ? false : self::get_module_unavailable_reason( $module_array );
  126. $module_array['short_description'] = $short_desc_trunc;
  127. $module_array['configure_url'] = Jetpack::module_configuration_url( $module );
  128. $module_array['override'] = $overrides->get_module_override( $module );
  129. ob_start();
  130. /**
  131. * Allow the display of a "Learn More" button.
  132. * The dynamic part of the action, $module, is the module slug.
  133. *
  134. * @since 3.0.0
  135. */
  136. do_action( 'jetpack_learn_more_button_' . $module );
  137. $module_array['learn_more_button'] = ob_get_clean();
  138. ob_start();
  139. /**
  140. * Allow the display of information text when Jetpack is connected to WordPress.com.
  141. * The dynamic part of the action, $module, is the module slug.
  142. *
  143. * @since 3.0.0
  144. */
  145. do_action( 'jetpack_module_more_info_' . $module );
  146. /**
  147. * Filter the long description of a module.
  148. *
  149. * @since 3.5.0
  150. *
  151. * @param string ob_get_clean() The module long description.
  152. * @param string $module The module name.
  153. */
  154. $module_array['long_description'] = apply_filters( 'jetpack_long_module_description', ob_get_clean(), $module );
  155. ob_start();
  156. /**
  157. * Filter the search terms for a module
  158. *
  159. * Search terms are typically added to the module headers, under "Additional Search Queries".
  160. *
  161. * Use syntax:
  162. * function jetpack_$module_search_terms( $terms ) {
  163. * $terms = _x( 'term 1, term 2', 'search terms', 'jetpack' );
  164. * return $terms;
  165. * }
  166. * add_filter( 'jetpack_search_terms_$module', 'jetpack_$module_search_terms' );
  167. *
  168. * @since 3.5.0
  169. *
  170. * @param string The search terms (comma separated).
  171. */
  172. echo apply_filters( 'jetpack_search_terms_' . $module, $module_array['additional_search_queries'] );
  173. $module_array['search_terms'] = ob_get_clean();
  174. $module_array['configurable'] = false;
  175. if (
  176. current_user_can( 'manage_options' ) &&
  177. /**
  178. * Allow the display of a configuration link in the Jetpack Settings screen.
  179. *
  180. * @since 3.0.0
  181. *
  182. * @param string $module Module name.
  183. * @param bool false Should the Configure module link be displayed? Default to false.
  184. */
  185. apply_filters( 'jetpack_module_configurable_' . $module, false )
  186. ) {
  187. $module_array['configurable'] = sprintf( '<a href="%1$s">%2$s</a>', esc_url( $module_array['configure_url'] ), __( 'Configure', 'jetpack' ) );
  188. }
  189. $modules[ $module ] = $module_array;
  190. }
  191. }
  192. uasort( $modules, array( 'Jetpack', 'sort_modules' ) );
  193. if ( ! Jetpack::is_connection_ready() ) {
  194. uasort( $modules, array( __CLASS__, 'sort_requires_connection_last' ) );
  195. }
  196. return $modules;
  197. }
  198. static function is_module_available( $module ) {
  199. if ( ! is_array( $module ) || empty( $module ) ) {
  200. return false;
  201. }
  202. /**
  203. * We never want to show VaultPress as activatable through Jetpack.
  204. */
  205. if ( 'vaultpress' === $module['module'] ) {
  206. return false;
  207. }
  208. /*
  209. * WooCommerce Analytics should only be available
  210. * when running WooCommerce 3+
  211. */
  212. if (
  213. 'woocommerce-analytics' === $module['module']
  214. && (
  215. ! class_exists( 'WooCommerce' )
  216. || version_compare( WC_VERSION, '3.0', '<' )
  217. )
  218. ) {
  219. return false;
  220. }
  221. /*
  222. * In Offline mode, modules that require a site or user
  223. * level connection should be unavailable.
  224. */
  225. if ( ( new Status() )->is_offline_mode() ) {
  226. return ! ( $module['requires_connection'] || $module['requires_user_connection'] );
  227. }
  228. /*
  229. * Jetpack not connected.
  230. */
  231. if ( ! Jetpack::is_connection_ready() ) {
  232. return false;
  233. }
  234. /*
  235. * Jetpack connected at a site level only. Make sure to make
  236. * modules that require a user connection unavailable.
  237. */
  238. if ( ! Jetpack::connection()->has_connected_owner() && $module['requires_user_connection'] ) {
  239. return false;
  240. }
  241. return Jetpack_Plan::supports( $module['module'] );
  242. }
  243. /**
  244. * Returns why a module is unavailable.
  245. *
  246. * @param array $module The module.
  247. * @return string|false A string stating why the module is not available or false if the module is available.
  248. */
  249. public static function get_module_unavailable_reason( $module ) {
  250. if ( ! is_array( $module ) || empty( $module ) ) {
  251. return false;
  252. }
  253. if ( self::is_module_available( $module ) ) {
  254. return false;
  255. }
  256. /**
  257. * We never want to show VaultPress as activatable through Jetpack so return an empty string.
  258. */
  259. if ( 'vaultpress' === $module['module'] ) {
  260. return '';
  261. }
  262. /*
  263. * WooCommerce Analytics should only be available
  264. * when running WooCommerce 3+
  265. */
  266. if (
  267. 'woocommerce-analytics' === $module['module']
  268. && (
  269. ! class_exists( 'WooCommerce' )
  270. || version_compare( WC_VERSION, '3.0', '<' )
  271. )
  272. ) {
  273. return __( 'Requires WooCommerce 3+ plugin', 'jetpack' );
  274. }
  275. /*
  276. * In Offline mode, modules that require a site or user
  277. * level connection should be unavailable.
  278. */
  279. if ( ( new Status() )->is_offline_mode() ) {
  280. if ( $module['requires_connection'] || $module['requires_user_connection'] ) {
  281. return __( 'Offline mode', 'jetpack' );
  282. }
  283. }
  284. /*
  285. * Jetpack not connected.
  286. */
  287. if ( ! Jetpack::is_connection_ready() ) {
  288. return __( 'Jetpack is not connected', 'jetpack' );
  289. }
  290. /*
  291. * Jetpack connected at a site level only and module requires a user connection.
  292. */
  293. if ( ! Jetpack::connection()->has_connected_owner() && $module['requires_user_connection'] ) {
  294. return __( 'Requires a connected WordPress.com account', 'jetpack' );
  295. }
  296. /*
  297. * Plan restrictions.
  298. */
  299. if ( ! Jetpack_Plan::supports( $module['module'] ) ) {
  300. return __( 'Not supported by current plan', 'jetpack' );
  301. }
  302. return '';
  303. }
  304. function handle_unrecognized_action( $action ) {
  305. switch ( $action ) {
  306. case 'bulk-activate':
  307. if ( ! current_user_can( 'jetpack_activate_modules' ) ) {
  308. break;
  309. }
  310. $modules = (array) $_GET['modules'];
  311. $modules = array_map( 'sanitize_key', $modules );
  312. check_admin_referer( 'bulk-jetpack_page_jetpack_modules' );
  313. foreach ( $modules as $module ) {
  314. Jetpack::log( 'activate', $module );
  315. Jetpack::activate_module( $module, false );
  316. }
  317. // The following two lines will rarely happen, as Jetpack::activate_module normally exits at the end.
  318. wp_safe_redirect( wp_get_referer() );
  319. exit;
  320. case 'bulk-deactivate':
  321. if ( ! current_user_can( 'jetpack_deactivate_modules' ) ) {
  322. break;
  323. }
  324. $modules = (array) $_GET['modules'];
  325. $modules = array_map( 'sanitize_key', $modules );
  326. check_admin_referer( 'bulk-jetpack_page_jetpack_modules' );
  327. foreach ( $modules as $module ) {
  328. Jetpack::log( 'deactivate', $module );
  329. Jetpack::deactivate_module( $module );
  330. Jetpack::state( 'message', 'module_deactivated' );
  331. }
  332. Jetpack::state( 'module', $modules );
  333. wp_safe_redirect( wp_get_referer() );
  334. exit;
  335. default:
  336. return;
  337. }
  338. }
  339. function fix_redirect( $module, $redirect = true ) {
  340. if ( ! $redirect ) {
  341. return;
  342. }
  343. if ( wp_get_referer() ) {
  344. add_filter( 'wp_redirect', 'wp_get_referer' );
  345. }
  346. }
  347. function admin_menu_debugger() {
  348. jetpack_require_lib( 'debugger' );
  349. Jetpack_Debugger::disconnect_and_redirect();
  350. $debugger_hook = add_submenu_page(
  351. null,
  352. __( 'Debugging Center', 'jetpack' ),
  353. '',
  354. 'manage_options',
  355. 'jetpack-debugger',
  356. array( $this, 'wrap_debugger_page' )
  357. );
  358. add_action( "admin_head-$debugger_hook", array( 'Jetpack_Debugger', 'jetpack_debug_admin_head' ) );
  359. }
  360. function wrap_debugger_page() {
  361. nocache_headers();
  362. if ( ! current_user_can( 'manage_options' ) ) {
  363. die( '-1' );
  364. }
  365. Jetpack_Admin_Page::wrap_ui( array( $this, 'debugger_page' ) );
  366. }
  367. function debugger_page() {
  368. jetpack_require_lib( 'debugger' );
  369. Jetpack_Debugger::jetpack_debug_display_handler();
  370. }
  371. /**
  372. * Determines if JITMs should display on a particular screen.
  373. *
  374. * @param bool $value The default value of the filter.
  375. * @param string $screen_id The ID of the screen being tested for JITM display.
  376. *
  377. * @return bool True if JITMs should display, false otherwise.
  378. */
  379. public function should_display_jitms_on_screen( $value, $screen_id ) {
  380. // Disable all JITMs on these pages.
  381. if (
  382. in_array(
  383. $screen_id,
  384. array(
  385. 'jetpack_page_akismet-key-config',
  386. 'admin_page_jetpack_modules',
  387. ),
  388. true
  389. ) ) {
  390. return false;
  391. }
  392. // Disable all JITMs on pages where the recommendations banner is displaying.
  393. if (
  394. in_array(
  395. $screen_id,
  396. array(
  397. 'dashboard',
  398. 'plugins',
  399. 'jetpack_page_stats',
  400. ),
  401. true
  402. )
  403. && \Jetpack_Recommendations_Banner::can_be_displayed()
  404. ) {
  405. return false;
  406. }
  407. return $value;
  408. }
  409. }
  410. Jetpack_Admin::init();