/** * Install a WP-CLI package. * * Packages are required to be a valid Composer package, and can be * specified as: * * * Package name from WP-CLI's package index. * * Git URL accessible by the current shell user. * * Path to a directory on the local machine. * * Local or remote .zip file. * * When installing a local directory, WP-CLI simply registers a * reference to the directory. If you move or delete the directory, WP-CLI's * reference breaks. * * When installing a .zip file, WP-CLI extracts the package to * `~/.wp-cli/packages/local/<package-name>`. * * ## OPTIONS * * <name|git|path|zip> * : Name, git URL, directory path, or .zip file for the package to install. * Names can optionally include a version constraint * (e.g. wp-cli/server-command:@stable). * * ## EXAMPLES * * # Install the latest development version from the package index. * $ wp package install wp-cli/server-command * Installing package wp-cli/server-command (dev-master) * Updating /home/person/.wp-cli/packages/composer.json to require the package... * Using Composer to install the package... * --- * Loading composer repositories with package information * Updating dependencies * Resolving dependencies through SAT * Dependency resolution completed in 0.005 seconds * Analyzed 732 packages to resolve dependencies * Analyzed 1034 rules to resolve dependencies * - Installing package * Writing lock file * Generating autoload files * --- * Success: Package installed. * * # Install the latest stable version. * $ wp package install wp-cli/server-command:@stable * * # Install a package hosted at a git URL. * $ wp package install git@github.com:runcommand/hook.git * * # Install a package in a .zip file. * $ wp package install google-sitemap-generator-cli.zip */ public function install($args, $assoc_args) { list($package_name) = $args; $git_package = $dir_package = false; $version = 'dev-master'; if ('.git' === strtolower(substr($package_name, -4, 4))) { $git_package = $package_name; preg_match('#([^:\\/]+\\/[^\\/]+)\\.git#', $package_name, $matches); if (!empty($matches[1])) { $package_name = $matches[1]; } else { WP_CLI::error("Couldn't parse package name from expected path '<name>/<package>'."); } } else { if (false !== strpos($package_name, '://') && false !== stripos($package_name, '.zip') || pathinfo($package_name, PATHINFO_EXTENSION) === 'zip' && is_file($package_name)) { // Download the remote ZIP file to a temp directory if (false !== strpos($package_name, '://')) { $temp = Utils\get_temp_dir() . uniqid('package_') . ".zip"; $options = array('timeout' => 600, 'filename' => $temp); $response = Utils\http_request('GET', $package_name, null, array(), $options); if (20 != substr($response->status_code, 0, 2)) { WP_CLI::error("Couldn't download package."); } $package_name = $temp; } $dir_package = Utils\get_temp_dir() . uniqid('package_'); try { // Extract the package to get the package name Extractor::extract($package_name, $dir_package); list($package_name, $version) = self::get_package_name_and_version_from_dir_package($dir_package); // Move to a location based on the package name $local_dir = rtrim(WP_CLI::get_runner()->get_packages_dir_path(), '/') . '/local/'; $actual_dir_package = $local_dir . str_replace('/', '-', $package_name); Extractor::copy_overwrite_files($dir_package, $actual_dir_package); Extractor::rmdir($dir_package); // Behold, the extracted package $dir_package = $actual_dir_package; } catch (Exception $e) { WP_CLI::error($e->getMessage()); } } else { if (is_dir($package_name) && file_exists($package_name . '/composer.json')) { $dir_package = $package_name; if (!Utils\is_path_absolute($dir_package)) { $dir_package = getcwd() . DIRECTORY_SEPARATOR . $dir_package; } list($package_name, $version) = self::get_package_name_and_version_from_dir_package($dir_package); } else { if (false !== strpos($package_name, ':')) { list($package_name, $version) = explode(':', $package_name); } $package = $this->get_community_package_by_name($package_name); if (!$package) { WP_CLI::error("Invalid package."); } } } } WP_CLI::log(sprintf("Installing package %s (%s)", $package_name, $version)); $composer_json_obj = $this->get_composer_json(); // Add the 'require' to composer.json WP_CLI::log(sprintf("Updating %s to require the package...", $composer_json_obj->getPath())); $composer_backup = file_get_contents($composer_json_obj->getPath()); $json_manipulator = new JsonManipulator($composer_backup); $json_manipulator->addMainKey('name', 'wp-cli/wp-cli'); $json_manipulator->addMainKey('version', self::get_wp_cli_version_composer()); $json_manipulator->addLink('require', $package_name, $version); $json_manipulator->addConfigSetting('secure-http', true); if ($git_package) { WP_CLI::log(sprintf('Registering %s as a VCS repository...', $git_package)); $json_manipulator->addRepository($package_name, array('type' => 'vcs', 'url' => $git_package)); } else { if ($dir_package) { WP_CLI::log(sprintf('Registering %s as a path repository...', $dir_package)); $json_manipulator->addRepository($package_name, array('type' => 'path', 'url' => $dir_package)); } } $composer_backup_decoded = json_decode($composer_backup, true); // If the composer file does not contain the current package index repository, refresh the repository definition. if (empty($composer_backup_decoded['repositories']['wp-cli']['url']) || self::PACKAGE_INDEX_URL != $composer_backup_decoded['repositories']['wp-cli']['url']) { WP_CLI::log('Updating package index repository url...'); $json_manipulator->addRepository('wp-cli', array('type' => 'composer', 'url' => self::PACKAGE_INDEX_URL)); } file_put_contents($composer_json_obj->getPath(), $json_manipulator->getContents()); try { $composer = $this->get_composer(); } catch (Exception $e) { WP_CLI::error($e->getMessage()); } // Set up the EventSubscriber $event_subscriber = new \WP_CLI\PackageManagerEventSubscriber(); $composer->getEventDispatcher()->addSubscriber($event_subscriber); // Set up the installer $install = Installer::create(new ComposerIO(), $composer); $install->setUpdate(true); // Installer class will only override composer.lock with this flag $install->setPreferSource(true); // Use VCS when VCS for easier contributions. // Try running the installer, but revert composer.json if failed WP_CLI::log('Using Composer to install the package...'); WP_CLI::log('---'); $res = false; try { $res = $install->run(); } catch (Exception $e) { WP_CLI::warning($e->getMessage()); } WP_CLI::log('---'); if (0 === $res) { WP_CLI::success("Package installed."); } else { file_put_contents($composer_json_obj->getPath(), $composer_backup); WP_CLI::error("Package installation failed (Composer return code {$res}). Reverted composer.json"); } }
/** * Security copy of the core function with Requests - Gets the checksums for the given version of WordPress. * * @param string $version Version string to query. * @param string $locale Locale to query. * @return bool|array False on failure. An array of checksums on success. */ private static function get_core_checksums($version, $locale) { $url = $http_url = 'http://api.wordpress.org/core/checksums/1.0/?' . http_build_query(compact('version', 'locale'), null, '&'); if ($ssl = wp_http_supports(array('ssl'))) { $url = 'https' . substr($url, 4); } $options = array('timeout' => 30); $headers = array('Accept' => 'application/json'); $response = Utils\http_request('GET', $url, null, $headers, $options); if ($ssl && !$response->success) { WP_CLI::warning('wp-cli could not establish a secure connection to WordPress.org. Please contact your server administrator.'); $response = Utils\http_request('GET', $http_url, null, $headers, $options); } if (!$response->success || 200 != $response->status_code) { return false; } $body = trim($response->body); $body = json_decode($body, true); if (!is_array($body) || !isset($body['checksums']) || !is_array($body['checksums'])) { return false; } return $body['checksums']; }
/** * Returns update information. */ private function get_updates($assoc_args) { $url = 'https://api.github.com/repos/wp-cli/wp-cli/releases'; $options = array('timeout' => 30); $headers = array('Accept' => 'application/json'); $response = Utils\http_request('GET', $url, $headers, $options); if (!$response->success || 200 !== $response->status_code) { WP_CLI::error(sprintf("Failed to get latest version (HTTP code %d).", $response->status_code)); } $release_data = json_decode($response->body); $updates = array('major' => false, 'minor' => false, 'patch' => false); foreach ($release_data as $release) { // Get rid of leading "v" if there is one set. $release_version = $release->tag_name; if ('v' === substr($release_version, 0, 1)) { $release_version = ltrim($release_version, 'v'); } $update_type = Utils\get_named_sem_ver($release_version, WP_CLI_VERSION); if (!$update_type) { continue; } if (!empty($updates[$update_type]) && !Comparator::greaterThan($release_version, $updates[$update_type]['version'])) { continue; } $updates[$update_type] = array('version' => $release_version, 'update_type' => $update_type, 'package_url' => $release->assets[0]->browser_download_url); } foreach ($updates as $type => $value) { if (empty($value)) { unset($updates[$type]); } } foreach (array('major', 'minor', 'patch') as $type) { if (true === \WP_CLI\Utils\get_flag_value($assoc_args, $type)) { return !empty($updates[$type]) ? array($updates[$type]) : false; } } if (empty($updates) && preg_match('#-alpha-(.+)$#', WP_CLI_VERSION, $matches)) { $version_url = 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/NIGHTLY_VERSION'; $response = Utils\http_request('GET', $version_url); if (!$response->success || 200 !== $response->status_code) { WP_CLI::error(sprintf("Failed to get current nightly version (HTTP code %d)", $response->status_code)); } $nightly_version = trim($response->body); if (WP_CLI_VERSION != $nightly_version) { $updates['nightly'] = array('version' => $nightly_version, 'update_type' => 'nightly', 'package_url' => 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar'); } } return array_values($updates); }
/** * Returns update information */ private function get_updates($assoc_args) { $url = 'https://api.github.com/repos/wp-cli/wp-cli/releases'; $options = array('timeout' => 30); $headers = array('Accept' => 'application/json'); $response = Utils\http_request('GET', $url, $headers, $options); if (!$response->success || 200 !== $response->status_code) { WP_CLI::error(sprintf("Failed to get latest version (HTTP code %d)", $response->status_code)); } $release_data = json_decode($response->body); $current_parts = explode('.', WP_CLI_VERSION); $updates = array(); foreach ($release_data as $release) { $release_version = $release->tag_name; // get rid of leading "v" if ('v' === substr($release_version, 0, 1)) { $release_version = ltrim($release_version, 'v'); } // don't list earlier releases if (version_compare($release_version, WP_CLI_VERSION, '<=')) { continue; } $release_parts = explode('.', $release_version); $update_type = 'minor'; if ($release_parts[0] === $current_parts[0] && $release_parts[1] === $current_parts[1]) { $update_type = 'patch'; } if (!(\WP_CLI\Utils\get_flag_value($assoc_args, 'patch') && 'patch' !== $update_type) && !(\WP_CLI\Utils\get_flag_value($assoc_args, 'patch') === false && 'patch' === $update_type) && !(\WP_CLI\Utils\get_flag_value($assoc_args, 'minor') && 'minor' !== $update_type) && !(\WP_CLI\Utils\get_flag_value($assoc_args, 'minor') === false && 'minor' === $update_type) && !$this->same_minor_release($release_parts, $updates)) { $updates[] = array('version' => $release_version, 'update_type' => $update_type, 'package_url' => $release->assets[0]->browser_download_url); } } return $updates; }
/** * Returns update information */ private function get_updates($assoc_args) { global $wp_version; $versions_path = ABSPATH . 'wp-includes/version.php'; include $versions_path; $url = 'https://api.wordpress.org/core/stable-check/1.0/'; $options = array('timeout' => 30); $headers = array('Accept' => 'application/json'); $response = Utils\http_request('GET', $url, $headers, $options); if (!$response->success || 200 !== $response->status_code) { WP_CLI::error("Failed to get latest version list."); } $release_data = json_decode($response->body); $release_versions = array_keys((array) $release_data); usort($release_versions, function ($a, $b) { return 1 === version_compare($a, $b); }); $locale = get_locale(); $compare_version = str_replace('-src', '', $GLOBALS['wp_version']); $updates = array('major' => false, 'minor' => false); foreach ($release_versions as $release_version) { $update_type = Utils\get_named_sem_ver($release_version, $compare_version); if (!$update_type) { continue; } // WordPress follow its own versioning which is roughly equivalent to semver if ('minor' === $update_type) { $update_type = 'major'; } else { if ('patch' === $update_type) { $update_type = 'minor'; } } if (!empty($updates[$update_type]) && !Comparator::greaterThan($release_version, $updates[$update_type]['version'])) { continue; } $updates[$update_type] = array('version' => $release_version, 'update_type' => $update_type, 'package_url' => $this->get_download_url($release_version, $locale)); } foreach ($updates as $type => $value) { if (empty($value)) { unset($updates[$type]); } } foreach (array('major', 'minor') as $type) { if (true === \WP_CLI\Utils\get_flag_value($assoc_args, $type)) { return !empty($updates[$type]) ? array($updates[$type]) : false; } } return array_values($updates); }
/** * Security copy of the core function with Requests - Gets the checksums for the given version of WordPress. * * @param string $version Version string to query. * @param string $locale Locale to query. * @return bool|array False on failure. An array of checksums on success. */ private static function get_core_checksums($version, $locale) { $url = 'https://api.wordpress.org/core/checksums/1.0/?' . http_build_query(compact('version', 'locale'), null, '&'); $options = array('timeout' => 30); $headers = array('Accept' => 'application/json'); $response = Utils\http_request('GET', $url, null, $headers, $options); if (!$response->success || 200 != $response->status_code) { return false; } $body = trim($response->body); $body = json_decode($body, true); if (!is_array($body) || !isset($body['checksums']) || !is_array($body['checksums'])) { return false; } return $body['checksums']; }
/** * Get the index data from an API url * * @param string $api_url * @return array|false */ private static function get_api_index($api_url) { $query_char = false !== strpos($api_url, '?') ? '&' : '?'; $api_url .= $query_char . 'context=help'; $response = Utils\http_request('GET', $api_url); if (empty($response->body)) { return false; } return json_decode($response->body, true); }
/** * Do a REST Request * * @param string $method * */ private function do_request($method, $route, $assoc_args) { if ('internal' === $this->scope) { $request = new \WP_REST_Request($method, $route); if (in_array($method, array('POST', 'PUT'))) { $request->set_body_params($assoc_args); } else { foreach ($assoc_args as $key => $value) { $request->set_param($key, $value); } } $response = rest_do_request($request); if ($error = $response->as_error()) { WP_CLI::error($error); } return array($response->get_status(), $response->get_data(), $response->get_headers()); } else { if ('http' === $this->scope) { $response = Utils\http_request($method, rtrim($this->api_url, '/') . $route, $assoc_args); $body = json_decode($response->body, true); if ($response->status_code >= 400) { if (!empty($body['message'])) { WP_CLI::error($body['message'] . ' ' . json_encode(array('status' => $response->status_code))); } else { switch ($response->status_code) { case 404: WP_CLI::error("No {$this->name} found."); break; default: WP_CLI::error('Could not complete request.'); break; } } } return array($response->status_code, json_decode($response->body, true), $response->headers); } } WP_CLI::error('Invalid scope for REST command.'); }
/** * Check for update via Github API. Returns the available versions if there are updates, or empty if no update available. * * ## OPTIONS * * [--patch] * : Compare only the first two parts of the version number. * * [--minor] * : Compare only the first part of the version number. * * [--field=<field>] * : Prints the value of a single field for each update. * * [--fields=<fields>] * : Limit the output to specific object fields. Defaults to version,update_type,package_url. * * [--format=<format>] * : Accepted values: table, csv, json, count. Default: table * * @subcommand check-update */ function check_update($_, $assoc_args) { $url = 'https://api.github.com/repos/wp-cli/wp-cli/releases'; $options = array('timeout' => 30); $headers = array('Accept' => 'application/json'); $response = Utils\http_request('GET', $url, $headers, $options); if (!$response->success || 200 !== $response->status_code) { WP_CLI::error("Failed to get latest version."); } $release_data = json_decode($response->body); $current_parts = explode('.', WP_CLI_VERSION); $updates = array(); foreach ($release_data as $release) { $release_version = $release->tag_name; // get rid of leading "v" if ('v' === substr($release_version, 0, 1)) { $release_version = ltrim($release_version, 'v'); } // don't list earlier releases if (version_compare($release_version, WP_CLI_VERSION, '<=')) { continue; } $release_parts = explode('.', $release_version); $update_type = 'minor'; if ($release_parts[0] === $current_parts[0] && $release_parts[1] === $current_parts[1]) { $update_type = 'patch'; } if (!(isset($assoc_args['patch']) && 'patch' !== $update_type) && !(isset($assoc_args['minor']) && 'minor' !== $update_type) && !$this->same_minor_release($release_parts, $updates)) { $updates[] = array('version' => $release_version, 'update_type' => $update_type, 'package_url' => $release->assets[0]->browser_download_url); } } if ($updates) { $formatter = new \WP_CLI\Formatter($assoc_args, array('version', 'update_type', 'package_url')); $formatter->display_items($updates); } else { if (empty($assoc_args['format']) || 'table' == $assoc_args['format']) { WP_CLI::success("WP-CLI is at the latest version."); } } }