. * * @package Hookbird */ namespace Hookbird; defined( 'ABSPATH' ) || exit; /** * Manage Hookbird endpoints, deliveries and the queue. */ class CLI { /** * List endpoints. * * ## OPTIONS * * [--format=] * : table, json, csv or yaml. * --- * default: table * --- * * ## EXAMPLES * * wp hookbird endpoints * * @param array $args Positional args. * @param array $assoc_args Assoc args. */ public function endpoints( $args, $assoc_args ) { $rows = array(); foreach ( Endpoints::all() as $endpoint ) { $state = ! $endpoint['active'] ? 'disabled' : ( $endpoint['paused_at'] ? 'paused' : 'active' ); $rows[] = array( 'id' => $endpoint['id'], 'name' => $endpoint['name'], 'format' => $endpoint['format'], 'url' => Endpoints::mask_url( $endpoint['url'] ), 'events' => implode( ',', $endpoint['events'] ), 'state' => $state, ); } \WP_CLI\Utils\format_items( isset( $assoc_args['format'] ) ? $assoc_args['format'] : 'table', $rows, array( 'id', 'name', 'format', 'url', 'events', 'state' ) ); } /** * Create an endpoint. * * ## OPTIONS * * * : Destination URL. * * --events= * : Comma separated event keys or patterns, for example "order.*,user.registered" or "*". * * [--name=] * : Display name. * * [--type=] * : Destination: json, slack, discord, telegram, teams, googlechat, or a preset such as n8n, make, zapier. * --- * default: json * --- * * [--chat-id=] * : Telegram chat ID. * * ## EXAMPLES * * wp hookbird create https://hooks.slack.com/services/XXX --events="order.*" --type=slack --name="Shop alerts" * * @param array $args Positional args. * @param array $assoc_args Assoc args. */ public function create( $args, $assoc_args ) { $type = isset( $assoc_args['type'] ) ? sanitize_key( $assoc_args['type'] ) : 'json'; $presets = Formatter::presets(); $format = isset( $presets[ $type ] ) ? $presets[ $type ]['format'] : $type; if ( ! in_array( $format, Endpoints::FORMATS, true ) ) { \WP_CLI::error( sprintf( 'Unknown type "%s". Use one of: %s.', $type, implode( ', ', array_unique( array_merge( Endpoints::FORMATS, array_keys( $presets ) ) ) ) ) ); } $id = Endpoints::create( array( 'url' => $args[0], 'events' => isset( $assoc_args['events'] ) ? $assoc_args['events'] : '', 'name' => isset( $assoc_args['name'] ) ? $assoc_args['name'] : '', 'format' => $format, 'options' => array( 'chat_id' => isset( $assoc_args['chat-id'] ) ? $assoc_args['chat-id'] : '', 'preset' => isset( $presets[ $type ] ) ? $type : '', ), 'active' => true, ) ); if ( is_wp_error( $id ) ) { \WP_CLI::error( $id->get_error_message() ); } $endpoint = Endpoints::get( $id ); \WP_CLI::success( sprintf( 'Created endpoint %d. Signing secret: %s', $id, $endpoint['secret'] ) ); } /** * Delete an endpoint. * * * : Endpoint ID. * * @param array $args Positional args. */ public function delete( $args ) { $this->require_endpoint( $args[0] ); Endpoints::delete( (int) $args[0] ); \WP_CLI::success( 'Endpoint deleted.' ); } /** * Enable an endpoint (also resumes it after a circuit breaker pause). * * * : Endpoint ID. * * @param array $args Positional args. */ public function enable( $args ) { $this->require_endpoint( $args[0] ); Endpoints::set_active( (int) $args[0], true ); \WP_CLI::success( 'Endpoint enabled.' ); } /** * Disable an endpoint. * * * : Endpoint ID. * * @param array $args Positional args. */ public function disable( $args ) { $this->require_endpoint( $args[0] ); Endpoints::set_active( (int) $args[0], false ); \WP_CLI::success( 'Endpoint disabled.' ); } /** * Send a sample event to an endpoint. * * ## OPTIONS * * * : Endpoint ID. * * [--event=] * : Event key for the sample. Defaults to the first subscribed event. * * @param array $args Positional args. * @param array $assoc_args Assoc args. */ public function test( $args, $assoc_args ) { $result = Dispatcher::instance()->send_test( (int) $args[0], isset( $assoc_args['event'] ) ? $assoc_args['event'] : '' ); if ( is_wp_error( $result ) ) { \WP_CLI::error( $result->get_error_message() ); } if ( $result['success'] ) { \WP_CLI::success( sprintf( 'Sent "%s": HTTP %d in %d ms.', $result['event'], $result['code'], $result['duration_ms'] ) ); } else { \WP_CLI::error( sprintf( 'Test failed: %s', $result['error'] ) ); } } /** * List recent deliveries. * * ## OPTIONS * * [--status=] * : pending, processing, success, failed or cancelled. * * [--endpoint=] * : Endpoint ID. * * [--limit=] * : How many. * --- * default: 20 * --- * * [--format=] * : table, json, csv or yaml. * --- * default: table * --- * * @param array $args Positional args. * @param array $assoc_args Assoc args. */ public function deliveries( $args, $assoc_args ) { $result = Deliveries::query( array( 'status' => isset( $assoc_args['status'] ) ? $assoc_args['status'] : '', 'endpoint_id' => isset( $assoc_args['endpoint'] ) ? (int) $assoc_args['endpoint'] : 0, 'per_page' => isset( $assoc_args['limit'] ) ? (int) $assoc_args['limit'] : 20, ) ); $rows = array(); foreach ( $result['items'] as $delivery ) { $rows[] = array( 'id' => $delivery['id'], 'created' => $delivery['created_at'], 'event' => $delivery['event'], 'endpoint' => $delivery['endpoint_id'], 'status' => $delivery['status'], 'code' => $delivery['response_code'], 'attempts' => $delivery['attempts'], 'ms' => $delivery['duration_ms'], ); } \WP_CLI\Utils\format_items( isset( $assoc_args['format'] ) ? $assoc_args['format'] : 'table', $rows, array( 'id', 'created', 'event', 'endpoint', 'status', 'code', 'attempts', 'ms' ) ); } /** * Resend a delivery now. * * * : Delivery ID. * * @param array $args Positional args. */ public function retry( $args ) { $result = Dispatcher::instance()->resend( (int) $args[0] ); if ( is_wp_error( $result ) ) { \WP_CLI::error( $result->get_error_message() ); } if ( $result['success'] ) { \WP_CLI::success( sprintf( 'Delivered: HTTP %d.', $result['code'] ) ); } else { \WP_CLI::warning( sprintf( 'Failed: %s', $result['error'] ) ); } } /** * Process due deliveries now. Handy as a system cron job when WP-Cron is disabled. * * ## EXAMPLES * * * * * * * wp hookbird run --path=/var/www/html --quiet * * @subcommand run */ public function run_queue() { Deliveries::recover_stuck(); if ( Dispatcher::is_running() ) { \WP_CLI::warning( sprintf( 'Another run is already sending deliveries. %d pending.', Deliveries::count( 'pending' ) ) ); return; } $sent = Dispatcher::instance()->process_queue(); \WP_CLI::success( sprintf( 'Processed %d deliveries. %d still pending.', $sent, Deliveries::count( 'pending' ) ) ); } /** * Show queue status. */ public function status() { $next = wp_next_scheduled( Installer::CRON_PROCESS ); \WP_CLI::line( sprintf( 'Pending: %d', Deliveries::count( 'pending' ) ) ); \WP_CLI::line( sprintf( 'Failed: %d', Deliveries::count( 'failed' ) ) ); \WP_CLI::line( sprintf( 'Delivered: %d', Deliveries::count( 'success' ) ) ); \WP_CLI::line( sprintf( 'Next run: %s', $next ? gmdate( 'Y-m-d H:i:s', $next ) . ' UTC' : 'not scheduled' ) ); } /** * Export endpoints and settings as JSON. * * ## OPTIONS * * [--include-secrets] * : Include signing secrets so receivers keep working on the new site. * * ## EXAMPLES * * wp hookbird export > hookbird.json * * @param array $args Positional args. * @param array $assoc_args Assoc args. */ public function export( $args, $assoc_args ) { \WP_CLI::line( (string) wp_json_encode( Transfer::export( ! empty( $assoc_args['include-secrets'] ) ), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) ); } /** * Import endpoints and settings from a JSON export. * * ## OPTIONS * * * : Path to the export file. * * [--skip-settings] * : Only import endpoints. * * @param array $args Positional args. * @param array $assoc_args Assoc args. */ public function import( $args, $assoc_args ) { if ( ! is_readable( $args[0] ) ) { \WP_CLI::error( 'File not readable.' ); } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local file given on the command line. $data = json_decode( (string) file_get_contents( $args[0] ), true ); $result = Transfer::import( is_array( $data ) ? $data : array(), empty( $assoc_args['skip-settings'] ) ); if ( is_wp_error( $result ) ) { \WP_CLI::error( $result->get_error_message() ); } foreach ( $result['errors'] as $error ) { \WP_CLI::warning( $error ); } \WP_CLI::success( sprintf( 'Imported %d endpoints.', $result['created'] ) ); } /** * Exit with an error if the endpoint does not exist. * * @param int $id Endpoint ID. */ private function require_endpoint( $id ) { if ( ! Endpoints::get( (int) $id ) ) { \WP_CLI::error( 'Endpoint not found.' ); } } }