` died with an * uncaught exception and a stack trace instead of a message. * * @param SE_License_SDK_Client $client The client. * * @return SE_License_SDK_License */ protected function license_or_die( SE_License_SDK_Client $client ): SE_License_SDK_License { if ( $client->isFree() ) { WP_CLI::error( sprintf( '"%s" is a free product and has no license.', $client->getSlug() ) ); } return $client->license( false ); } /** * List all products registered with the SDK on this site. * * ## OPTIONS * * [--format=] * : Output format. * --- * default: table * options: * - table * - json * - csv * - yaml * --- * * ## EXAMPLES * * wp se-license list * * @param array $args Positional args. * @param array $assoc_args Associative args. */ public function list( $args, $assoc_args ) { $rows = []; foreach ( SE_License_SDK::get_all_registered() as $client ) { $rows[] = [ 'slug' => $client->getSlug(), 'name' => $client->getPackageName(), 'type' => $client->getType(), 'version' => $client->getProjectVersion(), 'free' => $client->isFree() ? 'yes' : 'no', ]; } if ( ! $rows ) { WP_CLI::warning( 'No SDK-registered products found.' ); return; } WP_CLI\Utils\format_items( $assoc_args['format'] ?? 'table', $rows, [ 'slug', 'name', 'type', 'version', 'free' ] ); } /** * Show the license + update status for a product. * * ## OPTIONS * * * : The product slug. * * [--format=] * : Output format (table, json, yaml). * * ## EXAMPLES * * wp se-license status storeengine-pro * * @param array $args Positional args. * @param array $assoc_args Associative args. */ public function status( $args, $assoc_args ) { $client = $this->client_or_die( $args ); // A free product has no license handler at all — report what we can // rather than blowing up on SE_License_SDK_Client::license(). if ( $client->isFree() ) { WP_CLI\Utils\format_items( $assoc_args['format'] ?? 'table', [ [ 'field' => 'slug', 'value' => $client->getSlug() ], [ 'field' => 'name', 'value' => $client->getPackageName() ], [ 'field' => 'installed_version', 'value' => $client->getProjectVersion() ], [ 'field' => 'license_status', 'value' => 'n/a (free product)' ], ], [ 'field', 'value' ] ); return; } $license = $this->license_or_die( $client ); $data = $license->get_license(); $rows = [ [ 'field' => 'slug', 'value' => $client->getSlug() ], [ 'field' => 'name', 'value' => $client->getPackageName() ], [ 'field' => 'installed_version', 'value' => $client->getProjectVersion() ], [ 'field' => 'license_status', 'value' => $data['status'] ?? 'inactive' ], [ 'field' => 'license_valid', 'value' => $license->is_valid() ? 'yes' : 'no' ], [ 'field' => 'in_offline_grace', 'value' => $license->is_in_grace() ? 'yes' : 'no' ], [ 'field' => 'expires', 'value' => ! empty( $data['expires'] ) ? gmdate( 'Y-m-d H:i:s', (int) $data['expires'] ) . ' UTC' : '—' ], [ 'field' => 'activations', 'value' => sprintf( '%s / %s', $data['activations'] ?? 0, ! empty( $data['unlimited'] ) ? '∞' : ( $data['limit'] ?? 0 ) ) ], ]; WP_CLI\Utils\format_items( $assoc_args['format'] ?? 'table', $rows, [ 'field', 'value' ] ); } /** * Activate a license key for a product. * * ## OPTIONS * * * : The product slug. * * --license= * : The license key to activate. * * ## EXAMPLES * * wp se-license activate storeengine-pro --license=XXXX-XXXX-XXXX * * @param array $args Positional args. * @param array $assoc_args Associative args. */ public function activate( $args, $assoc_args ) { $client = $this->client_or_die( $args ); $key = $assoc_args['license'] ?? ''; if ( ! $key ) { WP_CLI::error( 'The --license= option is required.' ); } $license = $this->license_or_die( $client ); $license->activate_client_license( [ 'license_key' => $key ] ); if ( $license->get_error() ) { WP_CLI::error( $license->get_error() ); } WP_CLI::success( $license->get_success() ?: 'License activated.' ); } /** * Deactivate the license for a product on this site. * * ## OPTIONS * * * : The product slug. * * ## EXAMPLES * * wp se-license deactivate storeengine-pro * * @param array $args Positional args. * @param array $assoc_args Associative args. */ public function deactivate( $args, $assoc_args ) { $client = $this->client_or_die( $args ); $license = $this->license_or_die( $client ); $license->deactivate_client_license(); if ( $license->get_error() ) { WP_CLI::error( $license->get_error() ); } WP_CLI::success( $license->get_success() ?: 'License deactivated.' ); } /** * Check whether an update is available for a product. * * ## OPTIONS * * * : The product slug. * * ## EXAMPLES * * wp se-license check-update storeengine-pro * * @param array $args Positional args. * @param array $assoc_args Associative args. */ public function check_update( $args, $assoc_args ) { $client = $this->client_or_die( $args ); if ( ! $client->maybe_init_update() ) { WP_CLI::warning( 'Updates are not enabled for this product.' ); return; } $info = $client->updater()->force_check(); $current = $client->getProjectVersion(); if ( ! $info || empty( $info->new_version ) ) { WP_CLI::success( sprintf( 'Up to date (v%s).', $current ) ); return; } if ( version_compare( $current, $info->new_version, '<' ) ) { WP_CLI::log( sprintf( 'Update available: v%s → v%s', $current, $info->new_version ) ); } else { WP_CLI::success( sprintf( 'Up to date (v%s).', $current ) ); } } /** * Install the latest (or a specific) version of a product via the SDK. * * Honours the core-dependency gate: for a pro product with `requires_core` * it updates the free/core plugin first and aborts with a message if it * still can't be satisfied. * * ## OPTIONS * * * : The product slug. * * [--version=] * : Specific version to install/roll back to. Defaults to latest. * * [--yes] * : Skip the confirmation prompt. * * ## EXAMPLES * * wp se-license install storeengine-pro --user=1 * wp se-license install storeengine-pro --version=2.1.0 --user=1 * * @param array $args Positional args. * @param array $assoc_args Associative args. */ public function install( $args, $assoc_args ) { $client = $this->client_or_die( $args ); if ( ! $client->maybe_init_update() ) { WP_CLI::error( 'Updates are not enabled for this product.' ); } // The install job enforces install_plugins + activate_plugins. WP-CLI // runs as user 0 unless `--user=` is given, so without this the command // downloads a package and only then fails on a permission check that // could never have passed. Fail fast with the actual remedy instead. if ( ! current_user_can( 'install_plugins' ) || ! current_user_can( 'activate_plugins' ) ) { WP_CLI::error( get_current_user_id() ? 'The current user cannot install or activate plugins on this site.' : 'Installing requires a user context. Re-run with `--user=` (e.g. --user=1).' ); } $version = $assoc_args['version'] ?? ''; $current = $client->getProjectVersion(); WP_CLI::confirm( $version ? sprintf( 'Install %s v%s (currently v%s)?', $client->getSlug(), $version, $current ) : sprintf( 'Install the latest %s (currently v%s)?', $client->getSlug(), $current ), $assoc_args ); // Delegate to the same handler the REST installer uses — this applies // the core-dependency gate, resolves the (signed) package URL, runs the // install job and records update-state, so CLI and UI stay identical. $request = new WP_REST_Request( 'POST', '' ); if ( $version ) { $request->set_param( 'version', $version ); } $result = $client->rest_api()->updates_install( $request ); if ( is_wp_error( $result ) ) { WP_CLI::error( $result->get_error_message() ); } $data = $result->get_data(); WP_CLI::success( sprintf( '%s %s v%s.', $client->getSlug(), ! empty( $data['is_rollback'] ) ? 'rolled back to' : 'installed', $data['target_version'] ?? $version ) ); } }