PageRenderTime 41ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/php/WP_CLI/CommandWithUpgrade.php

https://gitlab.com/Blueprint-Marketing/wp-cli
PHP | 407 lines | 273 code | 90 blank | 44 comment | 51 complexity | f309e3b235c85197e6ed4ec68911a51a MD5 | raw file
  1. <?php
  2. namespace WP_CLI;
  3. abstract class CommandWithUpgrade extends \WP_CLI_Command {
  4. protected $item_type;
  5. protected $obj_fields;
  6. protected $upgrade_refresh;
  7. protected $upgrade_transient;
  8. function __construct() {
  9. // After updating plugins/themes also update translations by running the `core language update` command.
  10. add_action( 'upgrader_process_complete', function() {
  11. remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
  12. \WP_CLI::run_command( array( 'core', 'language', 'update' ), array( 'dry-run' => false ) );
  13. }, 1 );
  14. }
  15. abstract protected function get_upgrader_class( $force );
  16. abstract protected function get_item_list();
  17. /**
  18. * @param array List of update candidates
  19. * @param array List of item names
  20. * @return array List of update candidates
  21. */
  22. abstract protected function filter_item_list( $items, $args );
  23. abstract protected function get_all_items();
  24. abstract protected function get_status( $file );
  25. abstract protected function status_single( $args );
  26. abstract protected function install_from_repo( $slug, $assoc_args );
  27. function status( $args ) {
  28. // Force WordPress to check for updates
  29. call_user_func( $this->upgrade_refresh );
  30. if ( empty( $args ) ) {
  31. $this->status_all();
  32. } else {
  33. $this->status_single( $args );
  34. }
  35. }
  36. private function status_all() {
  37. $items = $this->get_all_items();
  38. $n = count( $items );
  39. // Not interested in the translation, just the number logic
  40. \WP_CLI::log( sprintf( _n(
  41. "%d installed {$this->item_type}:",
  42. "%d installed {$this->item_type}s:",
  43. $n ), $n ) );
  44. $padding = $this->get_padding($items);
  45. foreach ( $items as $file => $details ) {
  46. if ( $details['update'] ) {
  47. $line = ' %yU%n';
  48. } else {
  49. $line = ' ';
  50. }
  51. $line .= $this->format_status( $details['status'], 'short' );
  52. $line .= " " . str_pad( $details['name'], $padding ). "%n";
  53. if ( !empty( $details['version'] ) ) {
  54. $line .= " " . $details['version'];
  55. }
  56. \WP_CLI::line( \WP_CLI::colorize( $line ) );
  57. }
  58. \WP_CLI::line();
  59. $this->show_legend( $items );
  60. }
  61. private function get_padding( $items ) {
  62. $max_len = 0;
  63. foreach ( $items as $details ) {
  64. $len = strlen( $details['name'] );
  65. if ( $len > $max_len ) {
  66. $max_len = $len;
  67. }
  68. }
  69. return $max_len;
  70. }
  71. private function show_legend( $items ) {
  72. $statuses = array_unique( wp_list_pluck( $items, 'status' ) );
  73. $legend_line = array();
  74. foreach ( $statuses as $status ) {
  75. $legend_line[] = sprintf( '%s%s = %s%%n',
  76. $this->get_color( $status ),
  77. $this->map['short'][ $status ],
  78. $this->map['long'][ $status ]
  79. );
  80. }
  81. if ( in_array( true, wp_list_pluck( $items, 'update' ) ) )
  82. $legend_line[] = '%yU = Update Available%n';
  83. \WP_CLI::line( 'Legend: ' . \WP_CLI::colorize( implode( ', ', $legend_line ) ) );
  84. }
  85. function install( $args, $assoc_args ) {
  86. foreach ( $args as $slug ) {
  87. $local_or_remote_zip_file = false;
  88. $result = false;
  89. // Check if a URL to a remote zip file has been specified
  90. $url_path = parse_url( $slug, PHP_URL_PATH );
  91. if ( ! empty( $url_path ) && '.zip' === substr( $url_path, - 4 ) ) {
  92. $local_or_remote_zip_file = $slug;
  93. } else {
  94. // Check if a local zip file has been specified
  95. if ( 'zip' === pathinfo( $slug, PATHINFO_EXTENSION ) && file_exists( $slug ) ) {
  96. $local_or_remote_zip_file = $slug;
  97. }
  98. }
  99. if ( $local_or_remote_zip_file ) {
  100. // Install from local or remote zip file
  101. $file_upgrader = $this->get_upgrader( $assoc_args );
  102. if ( $file_upgrader->install( $local_or_remote_zip_file ) ) {
  103. $slug = $file_upgrader->result['destination_name'];
  104. $result = true;
  105. }
  106. } else {
  107. // Assume a plugin/theme slug from the WordPress.org repository has been specified
  108. $result = $this->install_from_repo( $slug, $assoc_args );
  109. if ( is_wp_error( $result ) ) {
  110. \WP_CLI::warning( "$slug: " . $result->get_error_message() );
  111. }
  112. }
  113. if ( $result ) {
  114. if ( isset( $assoc_args['activate-network'] ) ) {
  115. \WP_CLI::log( "Network-activating '$slug'..." );
  116. $this->activate( array( $slug ), array( 'network' => true ) );
  117. }
  118. if ( isset( $assoc_args['activate'] ) ) {
  119. \WP_CLI::log( "Activating '$slug'..." );
  120. $this->activate( array( $slug ) );
  121. }
  122. }
  123. }
  124. }
  125. /**
  126. * Prepare an API response for downloading a particular version of an item.
  127. *
  128. * @param object $response wordpress.org API response
  129. * @param string $version The desired version of the package
  130. */
  131. protected static function alter_api_response( $response, $version ) {
  132. if ( $response->version == $version )
  133. return;
  134. // WordPress.org forces https, but still sometimes returns http
  135. // See https://twitter.com/nacin/status/512362694205140992
  136. $response->download_link = str_replace( 'http://', 'https://', $response->download_link );
  137. list( $link ) = explode( $response->slug, $response->download_link );
  138. if ( false !== strpos( $response->download_link, 'theme' ) )
  139. $download_type = 'theme';
  140. else
  141. $download_type = 'plugin';
  142. if ( 'dev' == $version ) {
  143. $response->download_link = $link . $response->slug . '.zip';
  144. $response->version = 'Development Version';
  145. } else {
  146. $response->download_link = $link . $response->slug . '.' . $version .'.zip';
  147. $response->version = $version;
  148. // check if the requested version exists
  149. $response = wp_remote_head( $response->download_link );
  150. $response_code = wp_remote_retrieve_response_code( $response );
  151. if ( 200 !== $response_code ) {
  152. \WP_CLI::error( sprintf(
  153. "Can't find the requested %s's version %s in the WordPress.org %s repository (HTTP code %d).",
  154. $download_type, $version, $download_type, $response_code ) );
  155. }
  156. }
  157. }
  158. protected function get_upgrader( $assoc_args ) {
  159. $upgrader_class = $this->get_upgrader_class( isset( $assoc_args['force'] ) );
  160. return \WP_CLI\Utils\get_upgrader( $upgrader_class );
  161. }
  162. protected function update_many( $args, $assoc_args ) {
  163. call_user_func( $this->upgrade_refresh );
  164. if ( ! isset( $assoc_args['all'] ) && empty( $args ) ) {
  165. \WP_CLI::error( "Please specify one or more {$this->item_type}s, or use --all." );
  166. }
  167. $items = $this->get_item_list();
  168. if ( !isset( $assoc_args['all'] ) ) {
  169. $items = $this->filter_item_list( $items, $args );
  170. }
  171. $items_to_update = wp_list_filter( $items, array(
  172. 'update' => true
  173. ) );
  174. if ( isset( $assoc_args['dry-run'] ) ) {
  175. if ( empty( $items_to_update ) ) {
  176. \WP_CLI::line( "No {$this->item_type} updates available." );
  177. return;
  178. }
  179. \WP_CLI::line( "Available {$this->item_type} updates:" );
  180. \WP_CLI\Utils\format_items( 'table', $items_to_update,
  181. array( 'name', 'status', 'version', 'update_version' ) );
  182. return;
  183. }
  184. $result = array();
  185. // Only attempt to update if there is something to update
  186. if ( !empty( $items_to_update ) ) {
  187. $cache_manager = \WP_CLI::get_http_cache_manager();
  188. foreach ($items_to_update as $item) {
  189. $cache_manager->whitelist_package($item['update_package'], $this->item_type, $item['name'], $item['update_version']);
  190. }
  191. $upgrader = $this->get_upgrader( $assoc_args );
  192. $result = $upgrader->bulk_upgrade( wp_list_pluck( $items_to_update, 'update_id' ) );
  193. }
  194. // Let the user know the results.
  195. $num_to_update = count( $items_to_update );
  196. $num_updated = count( array_filter( $result ) );
  197. $line = "Updated $num_updated/$num_to_update {$this->item_type}s.";
  198. if ( $num_to_update == $num_updated ) {
  199. \WP_CLI::success( $line );
  200. } else if ( $num_updated > 0 ) {
  201. \WP_CLI::warning( $line );
  202. } else {
  203. \WP_CLI::error( $line );
  204. }
  205. }
  206. protected function _list( $_, $assoc_args ) {
  207. // Force WordPress to check for updates
  208. call_user_func( $this->upgrade_refresh );
  209. $all_items = $this->get_all_items();
  210. if ( !is_array( $all_items ) )
  211. \WP_CLI::error( "No {$this->item_type}s found." );
  212. foreach ( $all_items as $key => &$item ) {
  213. if ( empty( $item['version'] ) )
  214. $item['version'] = '';
  215. foreach ( $item as $field => &$value ) {
  216. if ( $value === true ) {
  217. $value = 'available';
  218. } else if ( $value === false ) {
  219. $value = 'none';
  220. }
  221. }
  222. foreach ( $this->obj_fields as $field ) {
  223. if ( isset( $assoc_args[$field] )
  224. && $assoc_args[$field] != $item[$field] )
  225. unset( $all_items[$key] );
  226. }
  227. }
  228. $formatter = $this->get_formatter( $assoc_args );
  229. $formatter->display_items( $all_items );
  230. }
  231. /**
  232. * Check whether an item has an update available or not.
  233. *
  234. * @param string $slug The plugin/theme slug
  235. *
  236. * @return bool
  237. */
  238. protected function has_update( $slug ) {
  239. $update_list = get_site_transient( $this->upgrade_transient );
  240. return isset( $update_list->response[ $slug ] );
  241. }
  242. /**
  243. * Get the available update info
  244. *
  245. * @param string $slug The plugin/theme slug
  246. *
  247. * @return array|null
  248. */
  249. protected function get_update_info( $slug ) {
  250. $update_list = get_site_transient( $this->upgrade_transient );
  251. if ( !isset( $update_list->response[ $slug ] ) )
  252. return null;
  253. return (array) $update_list->response[ $slug ];
  254. }
  255. private $map = array(
  256. 'short' => array(
  257. 'inactive' => 'I',
  258. 'active' => 'A',
  259. 'active-network' => 'N',
  260. 'must-use' => 'M',
  261. ),
  262. 'long' => array(
  263. 'inactive' => 'Inactive',
  264. 'active' => 'Active',
  265. 'active-network' => 'Network Active',
  266. 'must-use' => 'Must Use',
  267. )
  268. );
  269. protected function format_status( $status, $format ) {
  270. return $this->get_color( $status ) . $this->map[ $format ][ $status ];
  271. }
  272. private function get_color( $status ) {
  273. static $colors = array(
  274. 'inactive' => '',
  275. 'active' => '%g',
  276. 'active-network' => '%g',
  277. 'must-use' => '%c',
  278. );
  279. return $colors[ $status ];
  280. }
  281. /**
  282. * Search wordpress.org repo.
  283. *
  284. * @param array $args A arguments array containing the search term in the first element.
  285. * @param array $assoc_args Data passed in from command.
  286. */
  287. protected function _search( $args, $assoc_args ) {
  288. $term = $args[0];
  289. $defaults = array(
  290. 'per-page' => 10,
  291. 'fields' => array( 'name', 'slug', 'rating' )
  292. );
  293. $assoc_args = array_merge( $defaults, $assoc_args );
  294. $formatter = $this->get_formatter( $assoc_args );
  295. $api_args = array(
  296. 'per_page' => (int) $assoc_args['per-page'],
  297. 'search' => $term,
  298. );
  299. if ( 'plugin' == $this->item_type ) {
  300. $api = plugins_api( 'query_plugins', $api_args );
  301. } else {
  302. $api = themes_api( 'query_themes', $api_args );
  303. }
  304. if ( is_wp_error( $api ) )
  305. \WP_CLI::error( $api->get_error_message() . __( ' Try again' ) );
  306. $plural = $this->item_type . 's';
  307. if ( ! isset( $api->$plural ) )
  308. \WP_CLI::error( __( 'API error. Try Again.' ) );
  309. $items = $api->$plural;
  310. $count = isset( $api->info['results'] ) ? $api->info['results'] : 'unknown';
  311. \WP_CLI::success( sprintf( 'Showing %s of %s %s.', count( $items ), $count, $plural ) );
  312. $formatter->display_items( $items );
  313. }
  314. protected function get_formatter( &$assoc_args ) {
  315. return new \WP_CLI\Formatter( $assoc_args, $this->obj_fields, $this->item_type );
  316. }
  317. }