Nav apraksta

class.jetpack-json-api-plugins-modify-endpoint.php 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. <?php
  2. use Automattic\Jetpack\Constants;
  3. new Jetpack_JSON_API_Plugins_Modify_Endpoint(
  4. array(
  5. 'description' => 'Activate/Deactivate a Plugin on your Jetpack Site, or set automatic updates',
  6. 'min_version' => '1',
  7. 'max_version' => '1.1',
  8. 'method' => 'POST',
  9. 'path' => '/sites/%s/plugins/%s',
  10. 'stat' => 'plugins:1:modify',
  11. 'path_labels' => array(
  12. '$site' => '(int|string) The site ID, The site domain',
  13. '$plugin' => '(string) The plugin ID',
  14. ),
  15. 'allow_jetpack_site_auth' => true,
  16. 'request_format' => array(
  17. 'action' => '(string) Possible values are \'update\'',
  18. 'autoupdate' => '(bool) Whether or not to automatically update the plugin',
  19. 'active' => '(bool) Activate or deactivate the plugin',
  20. 'network_wide' => '(bool) Do action network wide (default value: false)',
  21. ),
  22. 'query_parameters' => array(
  23. 'autoupdate' => '(bool=false) If the update is happening as a result of autoupdate event',
  24. ),
  25. 'response_format' => Jetpack_JSON_API_Plugins_Endpoint::$_response_format,
  26. 'example_request_data' => array(
  27. 'headers' => array(
  28. 'authorization' => 'Bearer YOUR_API_TOKEN',
  29. ),
  30. 'body' => array(
  31. 'action' => 'update',
  32. ),
  33. ),
  34. 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/plugins/hello-dolly%20hello',
  35. )
  36. );
  37. new Jetpack_JSON_API_Plugins_Modify_Endpoint(
  38. array(
  39. 'description' => 'Activate/Deactivate a list of plugins on your Jetpack Site, or set automatic updates',
  40. 'min_version' => '1',
  41. 'max_version' => '1.1',
  42. 'method' => 'POST',
  43. 'path' => '/sites/%s/plugins',
  44. 'stat' => 'plugins:modify',
  45. 'path_labels' => array(
  46. '$site' => '(int|string) The site ID, The site domain',
  47. ),
  48. 'request_format' => array(
  49. 'action' => '(string) Possible values are \'update\'',
  50. 'autoupdate' => '(bool) Whether or not to automatically update the plugin',
  51. 'active' => '(bool) Activate or deactivate the plugin',
  52. 'network_wide' => '(bool) Do action network wide (default value: false)',
  53. 'plugins' => '(array) A list of plugin ids to modify',
  54. ),
  55. 'allow_jetpack_site_auth' => true,
  56. 'query_parameters' => array(
  57. 'autoupdate' => '(bool=false) If the update is happening as a result of autoupdate event',
  58. ),
  59. 'response_format' => array(
  60. 'plugins' => '(array:plugin) An array of plugin objects.',
  61. 'updated' => '(array) A list of plugin ids that were updated. Only present if action is update.',
  62. 'not_updated' => '(array) A list of plugin ids that were not updated. Only present if action is update.',
  63. 'log' => '(array) Update log. Only present if action is update.',
  64. ),
  65. 'example_request_data' => array(
  66. 'headers' => array(
  67. 'authorization' => 'Bearer YOUR_API_TOKEN',
  68. ),
  69. 'body' => array(
  70. 'active' => true,
  71. 'plugins' => array(
  72. 'jetpack/jetpack',
  73. 'akismet/akismet',
  74. ),
  75. ),
  76. ),
  77. 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/plugins',
  78. )
  79. );
  80. new Jetpack_JSON_API_Plugins_Modify_Endpoint(
  81. array(
  82. 'description' => 'Update a Plugin on your Jetpack Site',
  83. 'min_version' => '1',
  84. 'max_version' => '1.1',
  85. 'method' => 'POST',
  86. 'path' => '/sites/%s/plugins/%s/update/',
  87. 'stat' => 'plugins:1:update',
  88. 'path_labels' => array(
  89. '$site' => '(int|string) The site ID, The site domain',
  90. '$plugin' => '(string) The plugin ID',
  91. ),
  92. 'allow_jetpack_site_auth' => true,
  93. 'query_parameters' => array(
  94. 'autoupdate' => '(bool=false) If the update is happening as a result of autoupdate event',
  95. ),
  96. 'response_format' => Jetpack_JSON_API_Plugins_Endpoint::$_response_format,
  97. 'example_request_data' => array(
  98. 'headers' => array(
  99. 'authorization' => 'Bearer YOUR_API_TOKEN',
  100. ),
  101. ),
  102. 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/example.wordpress.org/plugins/hello-dolly%20hello/update',
  103. )
  104. );
  105. class Jetpack_JSON_API_Plugins_Modify_Endpoint extends Jetpack_JSON_API_Plugins_Endpoint {
  106. // POST /sites/%s/plugins/%s
  107. // POST /sites/%s/plugins
  108. protected $slug = null;
  109. protected $needed_capabilities = 'activate_plugins';
  110. protected $action = 'default_action';
  111. protected $expected_actions = array( 'update', 'install', 'delete', 'update_translations' );
  112. public function callback( $path = '', $blog_id = 0, $object = null ) {
  113. Jetpack_JSON_API_Endpoint::validate_input( $object );
  114. switch ( $this->action ) {
  115. case 'delete':
  116. $this->needed_capabilities = 'delete_plugins';
  117. case 'update_translations':
  118. case 'update' :
  119. $this->needed_capabilities = 'update_plugins';
  120. break;
  121. case 'install' :
  122. $this->needed_capabilities = 'install_plugins';
  123. break;
  124. }
  125. if ( isset( $args['autoupdate'] ) || isset( $args['autoupdate_translations'] ) ) {
  126. $this->needed_capabilities = 'update_plugins';
  127. }
  128. return parent::callback( $path, $blog_id, $object );
  129. }
  130. public function default_action() {
  131. $args = $this->input();
  132. if ( isset( $args['autoupdate'] ) && is_bool( $args['autoupdate'] ) ) {
  133. if ( $args['autoupdate'] ) {
  134. $this->autoupdate_on();
  135. } else {
  136. $this->autoupdate_off();
  137. }
  138. }
  139. if ( isset( $args['active'] ) && is_bool( $args['active'] ) ) {
  140. if ( $args['active'] ) {
  141. // We don't have to check for activate_plugins permissions since we assume that the user has those
  142. // Since we set them via $needed_capabilities.
  143. return $this->activate();
  144. } else {
  145. if ( $this->current_user_can( 'deactivate_plugins' ) ) {
  146. return $this->deactivate();
  147. } else {
  148. return new WP_Error( 'unauthorized_error', __( 'Plugin deactivation is not allowed', 'jetpack' ), '403' );
  149. }
  150. }
  151. }
  152. if ( isset( $args['autoupdate_translations'] ) && is_bool( $args['autoupdate_translations'] ) ) {
  153. if ( $args['autoupdate_translations'] ) {
  154. $this->autoupdate_translations_on();
  155. } else {
  156. $this->autoupdate_translations_off();
  157. }
  158. }
  159. return true;
  160. }
  161. protected function autoupdate_on() {
  162. $autoupdate_plugins = (array) get_site_option( 'auto_update_plugins', array() );
  163. $autoupdate_plugins = array_unique( array_merge( $autoupdate_plugins, $this->plugins ) );
  164. update_site_option( 'auto_update_plugins', $autoupdate_plugins );
  165. }
  166. protected function autoupdate_off() {
  167. $autoupdate_plugins = (array) get_site_option( 'auto_update_plugins', array() );
  168. $autoupdate_plugins = array_diff( $autoupdate_plugins, $this->plugins );
  169. update_site_option( 'auto_update_plugins', $autoupdate_plugins );
  170. }
  171. protected function autoupdate_translations_on() {
  172. $autoupdate_plugins = Jetpack_Options::get_option( 'autoupdate_plugins_translations', array() );
  173. $autoupdate_plugins = array_unique( array_merge( $autoupdate_plugins, $this->plugins ) );
  174. Jetpack_Options::update_option( 'autoupdate_plugins_translations', $autoupdate_plugins );
  175. }
  176. protected function autoupdate_translations_off() {
  177. $autoupdate_plugins = Jetpack_Options::get_option( 'autoupdate_plugins_translations', array() );
  178. $autoupdate_plugins = array_diff( $autoupdate_plugins, $this->plugins );
  179. Jetpack_Options::update_option( 'autoupdate_plugins_translations', $autoupdate_plugins );
  180. }
  181. protected function activate() {
  182. $permission_error = false;
  183. foreach ( $this->plugins as $plugin ) {
  184. if ( ! $this->current_user_can( 'activate_plugin', $plugin ) ) {
  185. $this->log[$plugin]['error'] = __( 'Sorry, you are not allowed to activate this plugin.' );
  186. $has_errors = true;
  187. $permission_error = true;
  188. continue;
  189. }
  190. if ( ( ! $this->network_wide && Jetpack::is_plugin_active( $plugin ) ) || is_plugin_active_for_network( $plugin ) ) {
  191. $this->log[$plugin]['error'] = __( 'The Plugin is already active.', 'jetpack' );
  192. $has_errors = true;
  193. continue;
  194. }
  195. if ( ! $this->network_wide && is_network_only_plugin( $plugin ) && is_multisite() ) {
  196. $this->log[$plugin]['error'] = __( 'Plugin can only be Network Activated', 'jetpack' );
  197. $has_errors = true;
  198. continue;
  199. }
  200. $result = activate_plugin( $plugin, '', $this->network_wide );
  201. if ( is_wp_error( $result ) ) {
  202. $this->log[$plugin]['error'] = $result->get_error_messages();
  203. $has_errors = true;
  204. continue;
  205. }
  206. $success = Jetpack::is_plugin_active( $plugin );
  207. if ( $success && $this->network_wide ) {
  208. $success &= is_plugin_active_for_network( $plugin );
  209. }
  210. if ( ! $success ) {
  211. $this->log[$plugin]['error'] = $result->get_error_messages;
  212. $has_errors = true;
  213. continue;
  214. }
  215. $this->log[$plugin][] = __( 'Plugin activated.', 'jetpack' );
  216. }
  217. if ( ! $this->bulk && isset( $has_errors ) ) {
  218. $plugin = $this->plugins[0];
  219. if ( $permission_error ) {
  220. return new WP_Error( 'unauthorized_error', $this->log[$plugin]['error'], 403 );
  221. }
  222. return new WP_Error( 'activation_error', $this->log[$plugin]['error'] );
  223. }
  224. }
  225. protected function current_user_can( $capability, $plugin = null ) {
  226. // If this endpoint accepts site based authentication and a blog token is used, skip capabilities check.
  227. if ( $this->accepts_site_based_authentication() ) {
  228. return true;
  229. }
  230. if ( $plugin ) {
  231. return current_user_can( $capability, $plugin );
  232. }
  233. return current_user_can( $capability );
  234. }
  235. protected function deactivate() {
  236. $permission_error = false;
  237. foreach ( $this->plugins as $plugin ) {
  238. if ( ! $this->current_user_can( 'deactivate_plugin', $plugin ) ) {
  239. $error = $this->log[$plugin]['error'] = __( 'Sorry, you are not allowed to deactivate this plugin.', 'jetpack' );
  240. $permission_error = true;
  241. continue;
  242. }
  243. if ( ! Jetpack::is_plugin_active( $plugin ) ) {
  244. $error = $this->log[$plugin]['error'] = __( 'The Plugin is already deactivated.', 'jetpack' );
  245. continue;
  246. }
  247. deactivate_plugins( $plugin, false, $this->network_wide );
  248. $success = ! Jetpack::is_plugin_active( $plugin );
  249. if ( $success && $this->network_wide ) {
  250. $success &= ! is_plugin_active_for_network( $plugin );
  251. }
  252. if ( ! $success ) {
  253. $error = $this->log[$plugin]['error'] = __( 'There was an error deactivating your plugin', 'jetpack' );
  254. continue;
  255. }
  256. $this->log[$plugin][] = __( 'Plugin deactivated.', 'jetpack' );
  257. }
  258. if ( ! $this->bulk && isset( $error ) ) {
  259. if ( $permission_error ) {
  260. return new WP_Error( 'unauthorized_error', $error, 403 );
  261. }
  262. return new WP_Error( 'deactivation_error', $error );
  263. }
  264. }
  265. protected function update() {
  266. $query_args = $this->query_args();
  267. if ( isset( $query_args['autoupdate'] ) && $query_args['autoupdate'] ) {
  268. Constants::set_constant( 'JETPACK_PLUGIN_AUTOUPDATE', true );
  269. }
  270. wp_clean_plugins_cache();
  271. ob_start();
  272. wp_update_plugins(); // Check for Plugin updates
  273. ob_end_clean();
  274. $update_plugins = get_site_transient( 'update_plugins' );
  275. if ( isset( $update_plugins->response ) ) {
  276. $plugin_updates_needed = array_keys( $update_plugins->response );
  277. } else {
  278. $plugin_updates_needed = array();
  279. }
  280. $update_attempted = false;
  281. include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
  282. // unhook this functions that output things before we send our response header.
  283. remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
  284. remove_action( 'upgrader_process_complete', 'wp_version_check' );
  285. remove_action( 'upgrader_process_complete', 'wp_update_themes' );
  286. // Early return if unable to obtain auto_updater lock.
  287. // @see https://github.com/WordPress/wordpress-develop/blob/66469efa99e7978c8824e287834135aa9842e84f/src/wp-admin/includes/class-wp-automatic-updater.php#L453.
  288. if ( Constants::get_constant( 'JETPACK_PLUGIN_AUTOUPDATE' ) && ! WP_Upgrader::create_lock( 'auto_updater' ) ) {
  289. return new WP_Error( 'update_fail', __( 'Updates are already in progress.', 'jetpack' ), 400 );
  290. }
  291. $result = false;
  292. foreach ( $this->plugins as $plugin ) {
  293. if ( ! in_array( $plugin, $plugin_updates_needed ) ) {
  294. $this->log[$plugin][] = __( 'No update needed', 'jetpack' );
  295. continue;
  296. }
  297. // Rely on WP_Automatic_Updater class to check if a plugin item should be updated if it is a Jetpack autoupdate request.
  298. if ( Constants::get_constant( 'JETPACK_PLUGIN_AUTOUPDATE' ) && ! ( new WP_Automatic_Updater() )->should_update( 'plugin', $update_plugins->response[ $plugin ], WP_PLUGIN_DIR ) ) {
  299. continue;
  300. }
  301. // Establish per plugin lock.
  302. $plugin_slug = Jetpack_Autoupdate::get_plugin_slug( $plugin );
  303. if ( ! WP_Upgrader::create_lock( 'jetpack_' . $plugin_slug ) ) {
  304. continue;
  305. }
  306. /**
  307. * Pre-upgrade action
  308. *
  309. * @since 3.9.3
  310. *
  311. * @param array $plugin Plugin data
  312. * @param array $plugin Array of plugin objects
  313. * @param bool $updated_attempted false for the first update, true subsequently
  314. */
  315. do_action( 'jetpack_pre_plugin_upgrade', $plugin, $this->plugins, $update_attempted );
  316. $update_attempted = true;
  317. // Object created inside the for loop to clean the messages for each plugin
  318. $skin = new WP_Ajax_Upgrader_Skin();
  319. // The Automatic_Upgrader_Skin skin shouldn't output anything.
  320. $upgrader = new Plugin_Upgrader( $skin );
  321. $upgrader->init();
  322. // This avoids the plugin to be deactivated.
  323. // Using bulk upgrade puts the site into maintenance mode during the upgrades
  324. $result = $upgrader->bulk_upgrade( array( $plugin ) );
  325. $errors = $upgrader->skin->get_errors();
  326. $this->log[$plugin] = $upgrader->skin->get_upgrade_messages();
  327. // release individual plugin lock.
  328. WP_Upgrader::release_lock( 'jetpack_' . $plugin_slug );
  329. if ( is_wp_error( $errors ) && $errors->get_error_code() ) {
  330. return $errors;
  331. }
  332. }
  333. // release auto_udpate lock.
  334. if ( Constants::get_constant( 'JETPACK_PLUGIN_AUTOUPDATE' ) ) {
  335. WP_Upgrader::release_lock( 'auto_updater' );
  336. }
  337. if ( ! $this->bulk && ! $result && $update_attempted ) {
  338. return new WP_Error( 'update_fail', __( 'There was an error updating your plugin', 'jetpack' ), 400 );
  339. }
  340. return $this->default_action();
  341. }
  342. function update_translations() {
  343. include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
  344. // Clear the cache.
  345. wp_clean_plugins_cache();
  346. ob_start();
  347. wp_update_plugins(); // Check for Plugin updates
  348. ob_end_clean();
  349. $available_updates = get_site_transient( 'update_plugins' );
  350. if ( ! isset( $available_updates->translations ) || empty( $available_updates->translations ) ) {
  351. return new WP_Error( 'nothing_to_translate' );
  352. }
  353. $update_attempted = false;
  354. $result = false;
  355. foreach ( $this->plugins as $plugin ) {
  356. $this->slug = Jetpack_Autoupdate::get_plugin_slug( $plugin );
  357. $translation = array_filter( $available_updates->translations, array( $this, 'get_translation' ) );
  358. if ( empty( $translation ) ) {
  359. $this->log[$plugin][] = __( 'No update needed', 'jetpack' );
  360. continue;
  361. }
  362. /**
  363. * Pre-upgrade action
  364. *
  365. * @since 4.4.0
  366. *
  367. * @param array $plugin Plugin data
  368. * @param array $plugin Array of plugin objects
  369. * @param bool $update_attempted false for the first update, true subsequently
  370. */
  371. do_action( 'jetpack_pre_plugin_upgrade_translations', $plugin, $this->plugins, $update_attempted );
  372. $update_attempted = true;
  373. $skin = new Automatic_Upgrader_Skin();
  374. $upgrader = new Language_Pack_Upgrader( $skin );
  375. $upgrader->init();
  376. $result = $upgrader->upgrade( (object) $translation[0] );
  377. $this->log[$plugin] = $upgrader->skin->get_upgrade_messages();
  378. }
  379. if ( ! $this->bulk && ! $result ) {
  380. return new WP_Error( 'update_fail', __( 'There was an error updating your plugin', 'jetpack' ), 400 );
  381. }
  382. return true;
  383. }
  384. protected function get_translation( $translation ) {
  385. return ( $translation['slug'] === $this->slug );
  386. }
  387. }