No Description

class.jetpack-network.php 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  1. <?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFilename
  2. /**
  3. * Jetpack Network Manager class file.
  4. *
  5. * @package automattic/jetpack
  6. */
  7. use Automattic\Jetpack\Connection\Manager;
  8. use Automattic\Jetpack\Connection\Tokens;
  9. use Automattic\Jetpack\Status;
  10. /**
  11. * Used to manage Jetpack installation on Multisite Network installs
  12. *
  13. * SINGLETON: To use call Jetpack_Network::init()
  14. *
  15. * DO NOT USE ANY STATIC METHODS IN THIS CLASS!!!!!!
  16. *
  17. * @since 2.9
  18. */
  19. class Jetpack_Network {
  20. /**
  21. * Holds a static copy of Jetpack_Network for the singleton
  22. *
  23. * @since 2.9
  24. * @var Jetpack_Network
  25. */
  26. private static $instance = null;
  27. /**
  28. * An instance of the connection manager object.
  29. *
  30. * @since 7.7
  31. * @var Automattic\Jetpack\Connection\Manager
  32. */
  33. private $connection;
  34. /**
  35. * Name of the network wide settings
  36. *
  37. * @since 2.9
  38. * @var string
  39. */
  40. private $settings_name = 'jetpack-network-settings';
  41. /**
  42. * Defaults for settings found on the Jetpack > Settings page
  43. *
  44. * @since 2.9
  45. * @var array
  46. */
  47. private $setting_defaults = array(
  48. 'auto-connect' => 0,
  49. 'sub-site-connection-override' => 1,
  50. );
  51. /**
  52. * Constructor
  53. *
  54. * @since 2.9
  55. */
  56. private function __construct() {
  57. require_once ABSPATH . '/wp-admin/includes/plugin.php'; // For the is_plugin... check.
  58. require_once JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php'; // For managing the global whitelist.
  59. /**
  60. * Sanity check to ensure the install is Multisite and we
  61. * are in Network Admin
  62. */
  63. if ( is_multisite() && is_network_admin() ) {
  64. add_action( 'network_admin_menu', array( $this, 'add_network_admin_menu' ) );
  65. add_action( 'network_admin_edit_jetpack-network-settings', array( $this, 'save_network_settings_page' ), 10, 0 );
  66. add_filter( 'admin_body_class', array( $this, 'body_class' ) );
  67. if ( isset( $_GET['page'] ) && 'jetpack' == $_GET['page'] ) {
  68. add_action( 'admin_init', array( $this, 'jetpack_sites_list' ) );
  69. }
  70. }
  71. /*
  72. * Things that should only run on multisite
  73. */
  74. if ( is_multisite() && is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
  75. add_action( 'wp_before_admin_bar_render', array( $this, 'add_to_menubar' ) );
  76. add_filter( 'jetpack_disconnect_cap', array( $this, 'set_multisite_disconnect_cap' ) );
  77. /*
  78. * If admin wants to automagically register new sites set the hook here
  79. *
  80. * This is a hacky way because xmlrpc is not available on wp_initialize_site
  81. */
  82. if ( 1 === $this->get_option( 'auto-connect' ) ) {
  83. add_action( 'wp_initialize_site', array( $this, 'do_automatically_add_new_site' ) );
  84. }
  85. }
  86. }
  87. /**
  88. * Sets a connection object.
  89. *
  90. * @param Automattic\Jetpack\Connection\Manager $connection the connection manager object.
  91. */
  92. public function set_connection( Manager $connection ) {
  93. $this->connection = $connection;
  94. }
  95. /**
  96. * Sets which modules get activated by default on subsite connection.
  97. * Modules can be set in Network Admin > Jetpack > Settings
  98. *
  99. * @since 2.9
  100. * @deprecated since 7.7.0
  101. *
  102. * @param array $modules List of modules.
  103. */
  104. public function set_auto_activated_modules( $modules ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
  105. _deprecated_function( __METHOD__, 'jetpack-7.7' );
  106. }
  107. /**
  108. * Registers new sites upon creation
  109. *
  110. * @since 2.9
  111. * @since 7.4.0 Uses a WP_Site object.
  112. * @uses wp_initialize_site
  113. *
  114. * @param WP_Site $site the WordPress site object.
  115. **/
  116. public function do_automatically_add_new_site( $site ) {
  117. if ( is_a( $site, 'WP_Site' ) ) {
  118. $this->do_subsiteregister( $site->id );
  119. }
  120. }
  121. /**
  122. * Adds .network-admin class to the body tag
  123. * Helps distinguish network admin JP styles from regular site JP styles
  124. *
  125. * @since 2.9
  126. *
  127. * @param String $classes current assigned body classes.
  128. * @return String amended class string.
  129. */
  130. public function body_class( $classes ) {
  131. return trim( $classes ) . ' network-admin ';
  132. }
  133. /**
  134. * Provides access to an instance of Jetpack_Network
  135. *
  136. * This is how the Jetpack_Network object should *always* be accessed
  137. *
  138. * @since 2.9
  139. * @return Jetpack_Network
  140. */
  141. public static function init() {
  142. if ( ! self::$instance || ! is_a( self::$instance, 'Jetpack_Network' ) ) {
  143. self::$instance = new Jetpack_Network();
  144. }
  145. return self::$instance;
  146. }
  147. /**
  148. * Registers the Multisite admin bar menu item shortcut.
  149. * This shortcut helps users quickly and easily navigate to the Jetpack Network Admin
  150. * menu from anywhere in their network.
  151. *
  152. * @since 2.9
  153. */
  154. public function register_menubar() {
  155. add_action( 'wp_before_admin_bar_render', array( $this, 'add_to_menubar' ) );
  156. }
  157. /**
  158. * Runs when Jetpack is deactivated from the network admin plugins menu.
  159. * Each individual site will need to have Jetpack::disconnect called on it.
  160. * Site that had Jetpack individually enabled will not be disconnected as
  161. * on Multisite individually activated plugins are still activated when
  162. * a plugin is deactivated network wide.
  163. *
  164. * @since 2.9
  165. **/
  166. public function deactivate() {
  167. // Only fire if in network admin.
  168. if ( ! is_network_admin() ) {
  169. return;
  170. }
  171. $sites = get_sites();
  172. foreach ( $sites as $s ) {
  173. switch_to_blog( $s->blog_id );
  174. $active_plugins = get_option( 'active_plugins' );
  175. /*
  176. * If this plugin was activated in the subsite individually
  177. * we do not want to call disconnect. Plugins activated
  178. * individually (before network activation) stay activated
  179. * when the network deactivation occurs
  180. */
  181. if ( ! in_array( 'jetpack/jetpack.php', $active_plugins, true ) ) {
  182. Jetpack::disconnect();
  183. }
  184. restore_current_blog();
  185. }
  186. }
  187. /**
  188. * Adds a link to the Jetpack Network Admin page in the network admin menu bar.
  189. *
  190. * @since 2.9
  191. **/
  192. public function add_to_menubar() {
  193. global $wp_admin_bar;
  194. // Don't show for logged out users or single site mode.
  195. if ( ! is_user_logged_in() || ! is_multisite() ) {
  196. return;
  197. }
  198. $wp_admin_bar->add_node(
  199. array(
  200. 'parent' => 'network-admin',
  201. 'id' => 'network-admin-jetpack',
  202. 'title' => 'Jetpack',
  203. 'href' => $this->get_url( 'network_admin_page' ),
  204. )
  205. );
  206. }
  207. /**
  208. * Returns various URL strings. Factory like
  209. *
  210. * $args can be a string or an array.
  211. * If $args is an array there must be an element called name for the switch statement
  212. *
  213. * Currently supports:
  214. * - subsiteregister: Pass array( 'name' => 'subsiteregister', 'site_id' => SITE_ID )
  215. * - network_admin_page: Provides link to /wp-admin/network/JETPACK
  216. * - subsitedisconnect: Pass array( 'name' => 'subsitedisconnect', 'site_id' => SITE_ID )
  217. *
  218. * @since 2.9
  219. *
  220. * @param Mixed $args URL parameters.
  221. *
  222. * @return String
  223. **/
  224. public function get_url( $args ) {
  225. $url = null; // Default url value.
  226. if ( is_string( $args ) ) {
  227. $name = $args;
  228. } else if ( is_array( $args ) ) {
  229. $name = $args['name'];
  230. } else {
  231. return $url;
  232. }
  233. switch ( $name ) {
  234. case 'subsiteregister':
  235. if ( ! isset( $args['site_id'] ) ) {
  236. break; // If there is not a site id present we cannot go further.
  237. }
  238. $url = network_admin_url(
  239. 'admin.php?page=jetpack&action=subsiteregister&site_id='
  240. . $args['site_id']
  241. );
  242. break;
  243. case 'network_admin_page':
  244. $url = network_admin_url( 'admin.php?page=jetpack' );
  245. break;
  246. case 'subsitedisconnect':
  247. if ( ! isset( $args['site_id'] ) ) {
  248. break; // If there is not a site id present we cannot go further.
  249. }
  250. $url = network_admin_url(
  251. 'admin.php?page=jetpack&action=subsitedisconnect&site_id='
  252. . $args['site_id']
  253. );
  254. break;
  255. }
  256. return $url;
  257. }
  258. /**
  259. * Adds the Jetpack menu item to the Network Admin area
  260. *
  261. * @since 2.9
  262. */
  263. public function add_network_admin_menu() {
  264. add_menu_page( 'Jetpack', 'Jetpack', 'jetpack_network_admin_page', 'jetpack', array( $this, 'wrap_network_admin_page' ), 'div', 3 );
  265. $jetpack_sites_page_hook = add_submenu_page( 'jetpack', __( 'Jetpack Sites', 'jetpack' ), __( 'Sites', 'jetpack' ), 'jetpack_network_sites_page', 'jetpack', array( $this, 'wrap_network_admin_page' ) );
  266. $jetpack_settings_page_hook = add_submenu_page( 'jetpack', __( 'Settings', 'jetpack' ), __( 'Settings', 'jetpack' ), 'jetpack_network_settings_page', 'jetpack-settings', array( $this, 'wrap_render_network_admin_settings_page' ) );
  267. add_action( "admin_print_styles-$jetpack_sites_page_hook", array( 'Jetpack_Admin_Page', 'load_wrapper_styles' ) );
  268. add_action( "admin_print_styles-$jetpack_settings_page_hook", array( 'Jetpack_Admin_Page', 'load_wrapper_styles' ) );
  269. /**
  270. * As jetpack_register_genericons is by default fired off a hook,
  271. * the hook may have already fired by this point.
  272. * So, let's just trigger it manually.
  273. */
  274. require_once JETPACK__PLUGIN_DIR . '_inc/genericons.php';
  275. jetpack_register_genericons();
  276. if ( ! wp_style_is( 'jetpack-icons', 'registered' ) ) {
  277. wp_register_style( 'jetpack-icons', plugins_url( 'css/jetpack-icons.min.css', JETPACK__PLUGIN_FILE ), false, JETPACK__VERSION );
  278. }
  279. add_action( 'admin_enqueue_scripts', array( $this, 'admin_menu_css' ) );
  280. }
  281. /**
  282. * Adds JP menu icon
  283. *
  284. * @since 2.9
  285. **/
  286. public function admin_menu_css() {
  287. wp_enqueue_style( 'jetpack-icons' );
  288. }
  289. /**
  290. * Provides functionality for the Jetpack > Sites page.
  291. * Does not do the display!
  292. *
  293. * @since 2.9
  294. */
  295. public function jetpack_sites_list() {
  296. Jetpack::init();
  297. if ( isset( $_GET['action'] ) ) {
  298. switch ( $_GET['action'] ) {
  299. case 'subsiteregister':
  300. /**
  301. * Add actual referrer checking.
  302. *
  303. * @todo check_admin_referer( 'jetpack-subsite-register' );
  304. */
  305. Jetpack::log( 'subsiteregister' );
  306. // If !$_GET['site_id'] stop registration and error.
  307. if ( ! isset( $_GET['site_id'] ) || empty( $_GET['site_id'] ) ) {
  308. /**
  309. * Log error to state cookie for display later.
  310. *
  311. * @todo Make state messages show on Jetpack NA pages
  312. */
  313. Jetpack::state( 'missing_site_id', esc_html__( 'Site ID must be provided to register a sub-site.', 'jetpack' ) );
  314. break;
  315. }
  316. // Send data to register endpoint and retrieve shadow blog details.
  317. $result = $this->do_subsiteregister();
  318. $url = $this->get_url( 'network_admin_page' );
  319. if ( is_wp_error( $result ) ) {
  320. $url = add_query_arg( 'action', 'connection_failed', $url );
  321. } else {
  322. $url = add_query_arg( 'action', 'connected', $url );
  323. }
  324. wp_safe_redirect( $url );
  325. exit;
  326. case 'subsitedisconnect':
  327. Jetpack::log( 'subsitedisconnect' );
  328. if ( ! isset( $_GET['site_id'] ) || empty( $_GET['site_id'] ) ) {
  329. Jetpack::state( 'missing_site_id', esc_html__( 'Site ID must be provided to disconnect a sub-site.', 'jetpack' ) );
  330. break;
  331. }
  332. $this->do_subsitedisconnect();
  333. break;
  334. case 'connected':
  335. case 'connection_failed':
  336. add_action( 'jetpack_notices', array( $this, 'show_jetpack_notice' ) );
  337. break;
  338. }
  339. }
  340. }
  341. /**
  342. * Set the disconnect capability for multisite.
  343. *
  344. * @param array $caps The capabilities array.
  345. */
  346. public function set_multisite_disconnect_cap( $caps ) {
  347. // Can individual site admins manage their own connection?
  348. if ( ! is_super_admin() && ! $this->get_option( 'sub-site-connection-override' ) ) {
  349. /*
  350. * We need to update the option name -- it's terribly unclear which
  351. * direction the override goes.
  352. *
  353. * @todo: Update the option name to `sub-sites-can-manage-own-connections`
  354. */
  355. return array( 'do_not_allow' );
  356. }
  357. return $caps;
  358. }
  359. /**
  360. * Shows the Jetpack plugin notices.
  361. */
  362. public function show_jetpack_notice() {
  363. if ( isset( $_GET['action'] ) && 'connected' == $_GET['action'] ) {
  364. $notice = __( 'Site successfully connected.', 'jetpack' );
  365. $classname = 'updated';
  366. } elseif ( isset( $_GET['action'] ) && 'connection_failed' == $_GET['action'] ) {
  367. $notice = __( 'Site connection failed!', 'jetpack' );
  368. $classname = 'error';
  369. }
  370. ?>
  371. <div id="message" class="<?php echo esc_attr( $classname ); ?> jetpack-message jp-connect" style="display:block !important;">
  372. <p><?php echo esc_html( $notice ); ?></p>
  373. </div>
  374. <?php
  375. }
  376. /**
  377. * Disconnect functionality for an individual site
  378. *
  379. * @since 2.9
  380. * @see Jetpack_Network::jetpack_sites_list()
  381. *
  382. * @param int $site_id the site identifier.
  383. */
  384. public function do_subsitedisconnect( $site_id = null ) {
  385. if ( ! current_user_can( 'jetpack_disconnect' ) ) {
  386. return;
  387. }
  388. $site_id = ( is_null( $site_id ) ) ? $_GET['site_id'] : $site_id;
  389. switch_to_blog( $site_id );
  390. Jetpack::disconnect();
  391. restore_current_blog();
  392. }
  393. /**
  394. * Registers a subsite with the Jetpack servers
  395. *
  396. * @since 2.9
  397. * @todo Break apart into easier to manage chunks that can be unit tested
  398. * @see Jetpack_Network::jetpack_sites_list();
  399. *
  400. * @param int $site_id the site identifier.
  401. */
  402. public function do_subsiteregister( $site_id = null ) {
  403. if ( ! current_user_can( 'jetpack_disconnect' ) ) {
  404. return;
  405. }
  406. if ( ( new Status() )->is_offline_mode() ) {
  407. return;
  408. }
  409. // Figure out what site we are working on.
  410. $site_id = ( is_null( $site_id ) ) ? $_GET['site_id'] : $site_id;
  411. /*
  412. * Here we need to switch to the subsite
  413. * For the registration process we really only hijack how it
  414. * works for an individual site and pass in some extra data here
  415. */
  416. switch_to_blog( $site_id );
  417. add_filter( 'jetpack_register_request_body', array( $this, 'filter_register_request_body' ) );
  418. add_action( 'jetpack_site_registered_user_token', array( $this, 'filter_register_user_token' ) );
  419. // Save the secrets in the subsite so when the wpcom server does a pingback it
  420. // will be able to validate the connection.
  421. $result = $this->connection->register( 'subsiteregister' );
  422. if ( is_wp_error( $result ) || ! $result ) {
  423. restore_current_blog();
  424. return $result;
  425. }
  426. Jetpack::activate_default_modules( false, false, array(), false );
  427. restore_current_blog();
  428. }
  429. /**
  430. * Receives the registration response token.
  431. *
  432. * @param Object $token the received token.
  433. */
  434. public function filter_register_user_token( $token ) {
  435. $is_connection_owner = ! $this->connection->has_connected_owner();
  436. ( new Tokens() )->update_user_token(
  437. get_current_user_id(),
  438. sprintf( '%s.%d', $token->secret, get_current_user_id() ),
  439. $is_connection_owner
  440. );
  441. }
  442. /**
  443. * Filters the registration request body to include additional properties.
  444. *
  445. * @param array $properties standard register request body properties.
  446. * @return array amended properties.
  447. */
  448. public function filter_register_request_body( $properties ) {
  449. $blog_details = get_blog_details();
  450. $network = get_network();
  451. switch_to_blog( $network->blog_id );
  452. // The blog id on WordPress.com of the primary network site.
  453. $network_wpcom_blog_id = Jetpack_Options::get_option( 'id' );
  454. restore_current_blog();
  455. /**
  456. * Both `state` and `user_id` need to be sent in the request, even though they are the same value.
  457. * Connecting via the network admin combines `register()` and `authorize()` methods into one step,
  458. * because we assume the main site is already authorized. `state` is used to verify the `register()`
  459. * request, while `user_id()` is used to create the token in the `authorize()` request.
  460. */
  461. return array_merge(
  462. $properties,
  463. array(
  464. 'network_url' => $this->get_url( 'network_admin_page' ),
  465. 'network_wpcom_blog_id' => $network_wpcom_blog_id,
  466. 'user_id' => get_current_user_id(),
  467. /*
  468. * Use the subsite's registration date as the site creation date.
  469. *
  470. * This is in contrast to regular standalone sites, where we use the helper
  471. * `Jetpack::get_assumed_site_creation_date()` to assume the site's creation date.
  472. */
  473. 'site_created' => $blog_details->registered,
  474. )
  475. );
  476. }
  477. /**
  478. * A hook handler for adding admin pages and subpages.
  479. */
  480. public function wrap_network_admin_page() {
  481. Jetpack_Admin_Page::wrap_ui( array( $this, 'network_admin_page' ) );
  482. }
  483. /**
  484. * Handles the displaying of all sites on the network that are
  485. * dis/connected to Jetpack
  486. *
  487. * @since 2.9
  488. * @see Jetpack_Network::jetpack_sites_list()
  489. */
  490. public function network_admin_page() {
  491. global $current_site;
  492. $this->network_admin_page_header();
  493. $jp = Jetpack::init();
  494. // We should be, but ensure we are on the main blog.
  495. switch_to_blog( $current_site->blog_id );
  496. $main_active = $jp->is_connection_ready();
  497. restore_current_blog();
  498. // If we are in dev mode, just show the notice and bail.
  499. if ( ( new Status() )->is_offline_mode() ) {
  500. Jetpack::show_development_mode_notice();
  501. return;
  502. }
  503. /*
  504. * Ensure the main blog is connected as all other subsite blog
  505. * connections will feed off this one
  506. */
  507. if ( ! $main_active ) {
  508. $url = $this->get_url(
  509. array(
  510. 'name' => 'subsiteregister',
  511. 'site_id' => 1,
  512. )
  513. );
  514. $data = array( 'url' => $jp->build_connect_url() );
  515. Jetpack::init()->load_view( 'admin/must-connect-main-blog.php', $data );
  516. return;
  517. }
  518. require_once 'class.jetpack-network-sites-list-table.php';
  519. $network_sites_table = new Jetpack_Network_Sites_List_Table();
  520. echo '<div class="wrap"><h2>' . esc_html__( 'Sites', 'jetpack' ) . '</h2>';
  521. echo '<form method="post">';
  522. $network_sites_table->prepare_items();
  523. $network_sites_table->display();
  524. echo '</form></div>';
  525. }
  526. /**
  527. * Stylized JP header formatting
  528. *
  529. * @since 2.9
  530. */
  531. public function network_admin_page_header() {
  532. $is_connected = Jetpack::is_connection_ready();
  533. $data = array(
  534. 'is_connected' => $is_connected,
  535. );
  536. Jetpack::init()->load_view( 'admin/network-admin-header.php', $data );
  537. }
  538. /**
  539. * Fires when the Jetpack > Settings page is saved.
  540. *
  541. * @since 2.9
  542. */
  543. public function save_network_settings_page() {
  544. if ( ! wp_verify_nonce( $_POST['_wpnonce'], 'jetpack-network-settings' ) ) {
  545. // No nonce, push back to settings page.
  546. wp_safe_redirect(
  547. add_query_arg(
  548. array( 'page' => 'jetpack-settings' ),
  549. network_admin_url( 'admin.php' )
  550. )
  551. );
  552. exit();
  553. }
  554. // Try to save the Protect whitelist before anything else, since that action can result in errors.
  555. $whitelist = str_replace( ' ', '', $_POST['global-whitelist'] );
  556. $whitelist = explode( PHP_EOL, $whitelist );
  557. $result = jetpack_protect_save_whitelist( $whitelist, true );
  558. if ( is_wp_error( $result ) ) {
  559. wp_safe_redirect(
  560. add_query_arg(
  561. array(
  562. 'page' => 'jetpack-settings',
  563. 'error' => 'jetpack_protect_whitelist',
  564. ),
  565. network_admin_url( 'admin.php' )
  566. )
  567. );
  568. exit();
  569. }
  570. /*
  571. * Fields
  572. *
  573. * auto-connect - Checkbox for global Jetpack connection
  574. * sub-site-connection-override - Allow sub-site admins to (dis)reconnect with their own Jetpack account
  575. */
  576. $auto_connect = 0;
  577. if ( isset( $_POST['auto-connect'] ) ) {
  578. $auto_connect = 1;
  579. }
  580. $sub_site_connection_override = 0;
  581. if ( isset( $_POST['sub-site-connection-override'] ) ) {
  582. $sub_site_connection_override = 1;
  583. }
  584. $data = array(
  585. 'auto-connect' => $auto_connect,
  586. 'sub-site-connection-override' => $sub_site_connection_override,
  587. );
  588. update_site_option( $this->settings_name, $data );
  589. wp_safe_redirect(
  590. add_query_arg(
  591. array(
  592. 'page' => 'jetpack-settings',
  593. 'updated' => 'true',
  594. ),
  595. network_admin_url( 'admin.php' )
  596. )
  597. );
  598. exit();
  599. }
  600. /**
  601. * A hook handler for adding admin pages and subpages.
  602. */
  603. public function wrap_render_network_admin_settings_page() {
  604. Jetpack_Admin_Page::wrap_ui( array( $this, 'render_network_admin_settings_page' ) );
  605. }
  606. /**
  607. * A hook rendering the admin settings page.
  608. */
  609. public function render_network_admin_settings_page() {
  610. $this->network_admin_page_header();
  611. $options = wp_parse_args( get_site_option( $this->settings_name ), $this->setting_defaults );
  612. $modules = array();
  613. $module_slugs = Jetpack::get_available_modules();
  614. foreach ( $module_slugs as $slug ) {
  615. $module = Jetpack::get_module( $slug );
  616. $module['module'] = $slug;
  617. $modules[] = $module;
  618. }
  619. usort( $modules, array( 'Jetpack', 'sort_modules' ) );
  620. if ( ! isset( $options['modules'] ) ) {
  621. $options['modules'] = $modules;
  622. }
  623. $data = array(
  624. 'modules' => $modules,
  625. 'options' => $options,
  626. 'jetpack_protect_whitelist' => jetpack_protect_format_whitelist(),
  627. );
  628. Jetpack::init()->load_view( 'admin/network-settings.php', $data );
  629. }
  630. /**
  631. * Updates a site wide option
  632. *
  633. * @since 2.9
  634. *
  635. * @param string $key option name.
  636. * @param mixed $value option value.
  637. *
  638. * @return boolean
  639. **/
  640. public function update_option( $key, $value ) {
  641. $options = get_site_option( $this->settings_name, $this->setting_defaults );
  642. $options[ $key ] = $value;
  643. return update_site_option( $this->settings_name, $options );
  644. }
  645. /**
  646. * Retrieves a site wide option
  647. *
  648. * @since 2.9
  649. *
  650. * @param string $name - Name of the option in the database.
  651. **/
  652. public function get_option( $name ) {
  653. $options = get_site_option( $this->settings_name, $this->setting_defaults );
  654. $options = wp_parse_args( $options, $this->setting_defaults );
  655. if ( ! isset( $options[ $name ] ) ) {
  656. $options[ $name ] = null;
  657. }
  658. return $options[ $name ];
  659. }
  660. }