'array', 'sanitize_callback' => array( 'Hookbird\\Settings', 'sanitize' ), 'default' => Settings::defaults(), ) ); } /** * Enqueue assets on our screens only. * * @param string $hook Screen hook. */ public function assets( $hook ) { if ( in_array( $hook, array( 'index.php', 'plugins.php' ), true ) && get_option( Onboarding::WELCOME_OPTION ) ) { wp_enqueue_style( 'hookbird-admin', HOOKBIRD_URL . 'assets/admin.css', array(), self::asset_version( 'admin.css' ) ); return; } if ( false === strpos( (string) $hook, 'hookbird' ) ) { return; } wp_enqueue_style( 'hookbird-admin', HOOKBIRD_URL . 'assets/admin.css', array(), self::asset_version( 'admin.css' ) ); wp_enqueue_script( 'hookbird-admin', HOOKBIRD_URL . 'assets/admin.js', array(), self::asset_version( 'admin.js' ), true ); wp_localize_script( 'hookbird-admin', 'hookbirdAdmin', array( 'copied' => __( 'Copied', 'hookbird' ), 'sending' => __( 'Sending...', 'hookbird' ), 'confirm' => __( 'Delete this endpoint? Its queued deliveries will be cancelled.', 'hookbird' ), 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'tourNonce' => wp_create_nonce( 'hookbird_tour' ), 'tourAuto' => ( Onboarding::tour_pending() && 'hookbird' === self::query_var( 'page' ) ) || '1' === self::query_var( 'hookbird_tour' ), 'next' => __( 'Next', 'hookbird' ), 'finish' => __( 'Get started', 'hookbird' ), ) ); } /** * Asset version that changes whenever the file changes, so browsers never keep a stale copy. * * @param string $file File name in assets/. * @return string */ public static function asset_version( $file ) { $mtime = filemtime( HOOKBIRD_DIR . 'assets/' . $file ); return HOOKBIRD_VERSION . ( $mtime ? '.' . $mtime : '' ); } /** * Links on the Plugins screen. * * @param array $links Links. * @return array */ public function action_links( $links ) { array_unshift( $links, '' . esc_html__( 'Endpoints', 'hookbird' ) . '' ); return $links; } /** * Highlight "Endpoints" rather than "Add endpoint" while editing an existing endpoint. * * @param string|null $submenu_file Current submenu file. * @return string|null */ public function submenu_file( $submenu_file ) { if ( 'hookbird-edit' === self::query_var( 'page' ) && absint( self::query_var( 'id' ) ) ) { return 'hookbird'; } return $submenu_file; } // Helpers. /** * Stop unless the user may manage the plugin. */ private function guard() { if ( ! current_user_can( self::CAP ) ) { wp_die( esc_html__( 'You do not have permission to do this.', 'hookbird' ), '', array( 'response' => 403 ) ); } } /** * Store a notice for the next page load. * * @param string $message Message. * @param string $type success, error, warning or info. */ private function notice( $message, $type = 'success' ) { $key = 'hookbird_notices_' . get_current_user_id(); $notices = (array) get_transient( $key ); $notices[] = array( 'message' => (string) $message, 'type' => in_array( $type, array( 'success', 'error', 'warning', 'info' ), true ) ? $type : 'info', ); set_transient( $key, $notices, 5 * MINUTE_IN_SECONDS ); } /** * Print and clear stored notices. */ private function render_notices() { $key = 'hookbird_notices_' . get_current_user_id(); $notices = get_transient( $key ); if ( ! is_array( $notices ) ) { return; } delete_transient( $key ); foreach ( $notices as $notice ) { if ( empty( $notice['message'] ) ) { continue; } printf( '

%2$s

', esc_attr( $notice['type'] ), esc_html( $notice['message'] ) ); } } /** * Redirect and exit. * * @param string $page Page slug. * @param array $args Query args. */ private function go( $page, array $args = array() ) { wp_safe_redirect( add_query_arg( array_merge( array( 'page' => $page ), $args ), admin_url( 'admin.php' ) ) ); exit; } /** * Admin URL for an action with nonce. * * @param string $action Action. * @param array $args Args. * @return string */ private function action_url( $action, array $args ) { return wp_nonce_url( add_query_arg( array_merge( array( 'action' => $action ), $args ), admin_url( 'admin-post.php' ) ), $action ); } /** * Format a UTC MySQL date for display in the site timezone. * * @param string|null $date Date. * @return string */ public static function date( $date ) { if ( ! $date ) { return '—'; } $timestamp = strtotime( $date . ' UTC' ); if ( ! $timestamp ) { return '—'; } $diff = time() - $timestamp; if ( $diff >= 0 && $diff < DAY_IN_SECONDS ) { /* translators: %s: human time difference. */ return sprintf( __( '%s ago', 'hookbird' ), human_time_diff( $timestamp ) ); } if ( $diff < 0 && $diff > -DAY_IN_SECONDS ) { /* translators: %s: human time difference. */ return sprintf( __( 'in %s', 'hookbird' ), human_time_diff( $timestamp ) ); } return wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $timestamp ); } /** * Status badge. * * @param string $status Status key. * @param string $label Label. * @return string HTML. */ public static function badge( $status, $label = '' ) { $labels = array( 'success' => __( 'Delivered', 'hookbird' ), 'failed' => __( 'Failed', 'hookbird' ), 'pending' => __( 'Queued', 'hookbird' ), 'processing' => __( 'Sending', 'hookbird' ), 'cancelled' => __( 'Cancelled', 'hookbird' ), 'active' => __( 'Active', 'hookbird' ), 'paused' => __( 'Paused', 'hookbird' ), 'disabled' => __( 'Disabled', 'hookbird' ), ); $label = '' !== $label ? $label : ( isset( $labels[ $status ] ) ? $labels[ $status ] : $status ); return '' . esc_html( $label ) . ''; } /** * Endpoint state key. * * @param array $endpoint Endpoint. * @return string */ public static function state( array $endpoint ) { if ( ! $endpoint['active'] ) { return 'disabled'; } return $endpoint['paused_at'] ? 'paused' : 'active'; } /** * Preset key of an endpoint. * * @param array $endpoint Endpoint. * @return string */ public static function preset_of( array $endpoint ) { if ( ! empty( $endpoint['options']['preset'] ) ) { return $endpoint['options']['preset']; } return 'json' === $endpoint['format'] ? 'custom' : $endpoint['format']; } /** * Read a GET parameter for display purposes (no state change). * * @param string $key Key. * @return string */ private static function query_var( $key ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only screen parameters. return isset( $_GET[ $key ] ) ? sanitize_text_field( wp_unslash( $_GET[ $key ] ) ) : ''; } /** * The plugin's mark: a hook whose top curls into a bird's head, drawn as one line. * * @return string SVG markup. */ public static function logo_svg() { return '' . '' . ''; } /** * Original line icon for a destination (drawn for Hookbird, not the services' logos). * * @param string $key Preset key. * @return string SVG markup. */ public static function dest_icon( $key ) { $paths = array( // A channel: speech bubble holding a hash. 'slack' => '', // A community: two overlapping bubbles. 'discord' => '', // Sending: a paper plane. 'telegram' => '', // A team: two people. 'teams' => '', // A space: a bubble with typing dots. 'googlechat' => '', // A workflow: nodes joined left to right. 'n8n' => '', // A scenario: a loop that keeps running. 'make' => '', // A trigger: a lightning bolt. 'zapier' => '', // Code: braces. 'custom' => '', ); $d = isset( $paths[ $key ] ) ? $paths[ $key ] : $paths['custom']; return ''; } /** * Allowed tags for the plugin's inline SVG icons and logo. * * @return array */ public static function svg_kses() { $common = array_fill_keys( array( 'd', 'x', 'y', 'width', 'height', 'rx', 'cx', 'cy', 'r', 'stroke-width', 'class' ), true ); return array( 'svg' => array_fill_keys( array( 'viewbox', 'width', 'height', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'focusable', 'aria-hidden' ), true ), 'path' => $common, 'rect' => $common, 'circle' => array_merge( $common, array( 'fill' => true, 'stroke' => true, ) ), 'g' => array( 'class' => true ), ); } /** * One-line delivery health for the header. * * @return array state (ok|warn|idle), label, title, url. */ private static function health() { $paused = 0; foreach ( Endpoints::all() as $endpoint ) { if ( $endpoint['active'] && $endpoint['paused_at'] ) { ++$paused; } } $next = Deliveries::next_due_time(); $overdue = $next && $next < time() - 5 * MINUTE_IN_SECONDS; $pending = Deliveries::count( 'pending' ); if ( $paused || $overdue ) { $count = $paused + ( $overdue ? 1 : 0 ); return array( 'state' => 'warn', /* translators: %d: number of problems. */ 'label' => sprintf( _n( '%d item needs attention', '%d items need attention', $count, 'hookbird' ), $count ), 'title' => __( 'Open the system status check', 'hookbird' ), 'url' => admin_url( 'admin.php?page=hookbird-help#status' ), ); } if ( ! Endpoints::all() ) { return array( 'state' => 'idle', 'label' => __( 'Not connected yet', 'hookbird' ), 'title' => __( 'Add your first endpoint', 'hookbird' ), 'url' => admin_url( 'admin.php?page=hookbird-edit' ), ); } return array( 'state' => 'ok', 'label' => $pending /* translators: %d: number of deliveries. */ ? sprintf( _n( 'Delivering, %d in queue', 'Delivering, %d in queue', $pending, 'hookbird' ), $pending ) : __( 'All deliveries flowing', 'hookbird' ), 'title' => __( 'Open the delivery log', 'hookbird' ), 'url' => admin_url( 'admin.php?page=hookbird-log' ), ); } /** * Branded header, navigation and page title. * * @param string $current Current page slug. * @param string $title Page title. * @param string $subtitle One line under the title. */ private function header( $current, $title, $subtitle = '' ) { $tabs = array( 'hookbird' => array( __( 'Endpoints', 'hookbird' ), 'dashicons-networking' ), 'hookbird-log' => array( __( 'Delivery log', 'hookbird' ), 'dashicons-list-view' ), 'hookbird-settings' => array( __( 'Settings', 'hookbird' ), 'dashicons-admin-generic' ), 'hookbird-help' => array( __( 'Help & Setup', 'hookbird' ), 'dashicons-sos' ), ); $active = 'hookbird-edit' === $current ? 'hookbird' : $current; $health = self::health(); ?>


render_notices(); $this->cron_warning(); } /** * Close the wrap and print the tour. */ private function footer() { require HOOKBIRD_DIR . 'admin/views/tour.php'; echo '
'; } /** * Warn when deliveries are piling up because WP-Cron is not running. */ private function cron_warning() { $next = Deliveries::next_due_time(); if ( ! $next || $next > time() - 5 * MINUTE_IN_SECONDS ) { return; } echo '

'; if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) { esc_html_e( 'Some deliveries are overdue. WP-Cron is disabled on this site, so make sure a system cron job calls wp-cron.php every minute, or run "wp hookbird run" from cron.', 'hookbird' ); } else { esc_html_e( 'Some deliveries are overdue. WP-Cron runs when your site gets visits, so on quiet sites consider a real cron job. You can also process the queue now from Settings.', 'hookbird' ); } echo '

'; } // Pages. /** * Endpoints list. */ public function page_endpoints() { $this->guard(); $endpoints = Endpoints::all(); $stats = Deliveries::stats(); $presets = Formatter::presets(); $checklist = Onboarding::checklist(); $this->header( 'hookbird', __( 'Endpoints', 'hookbird' ), __( 'Where your site events go, and how each destination is doing.', 'hookbird' ) ); require HOOKBIRD_DIR . 'admin/views/endpoints.php'; $this->footer(); } /** * Add or edit an endpoint. */ public function page_edit() { $this->guard(); $id = absint( self::query_var( 'id' ) ); $endpoint = $id ? Endpoints::get( $id ) : null; if ( $id && ! $endpoint ) { wp_die( esc_html__( 'Endpoint not found.', 'hookbird' ) ); } $presets = Formatter::presets(); $preset = $endpoint ? self::preset_of( $endpoint ) : sanitize_key( self::query_var( 'preset' ) ); if ( ! isset( $presets[ $preset ] ) ) { $preset = 'slack'; } // Values from a failed save take priority so the form is not lost. $draft = get_transient( 'hookbird_draft_' . get_current_user_id() ); if ( is_array( $draft ) && (int) $draft['id'] === $id ) { delete_transient( 'hookbird_draft_' . get_current_user_id() ); $values = $draft['values']; $preset = isset( $presets[ $values['preset'] ] ) ? $values['preset'] : $preset; } elseif ( $endpoint ) { $values = array( 'name' => $endpoint['name'], 'url' => $endpoint['url'], 'events' => $endpoint['events'], 'headers' => $endpoint['headers'], 'conditions' => $endpoint['conditions'], 'chat_id' => isset( $endpoint['options']['chat_id'] ) ? $endpoint['options']['chat_id'] : '', 'active' => $endpoint['active'], ); } else { $values = array( 'name' => '', 'url' => '', 'events' => array(), 'headers' => array(), 'conditions' => array( 'match' => 'all', 'rules' => array(), ), 'chat_id' => '', 'active' => true, ); } $grouped = Registry::grouped(); $known = array_keys( Registry::events() ); $custom = array_values( array_filter( (array) $values['events'], static function ( $key ) use ( $known ) { return '*' !== $key && ! in_array( $key, $known, true ); } ) ); $all = in_array( '*', (array) $values['events'], true ); $operators = Conditions::operators(); $paths = Registry::all_paths(); $woo = class_exists( 'WooCommerce' ); $stats = $endpoint ? Deliveries::stats() : array(); $this->header( 'hookbird-edit', $endpoint ? $endpoint['name'] : __( 'Add endpoint', 'hookbird' ), $endpoint ? __( 'Change where events go, which events are sent and when.', 'hookbird' ) : __( 'Choose a destination, pick the events it should get, then send a test.', 'hookbird' ) ); require HOOKBIRD_DIR . 'admin/views/edit.php'; $this->footer(); } /** * Delivery log and delivery detail. */ public function page_log() { $this->guard(); $delivery_id = absint( self::query_var( 'delivery' ) ); $endpoints = array(); foreach ( Endpoints::all() as $endpoint ) { $endpoints[ $endpoint['id'] ] = $endpoint; } if ( $delivery_id ) { $delivery = Deliveries::get( $delivery_id ); if ( ! $delivery ) { wp_die( esc_html__( 'Delivery not found.', 'hookbird' ) ); } $endpoint = isset( $endpoints[ $delivery['endpoint_id'] ] ) ? $endpoints[ $delivery['endpoint_id'] ] : null; $this->header( 'hookbird-log', __( 'Delivery details', 'hookbird' ), __( 'The exact payload that was sent and what the destination answered.', 'hookbird' ) ); require HOOKBIRD_DIR . 'admin/views/delivery.php'; $this->footer(); return; } $filters = array( 'endpoint_id' => absint( self::query_var( 'endpoint_id' ) ), 'event' => Endpoints::sanitize_event_key( self::query_var( 'event' ) ), 'status' => in_array( self::query_var( 'status' ), Deliveries::STATUSES, true ) ? self::query_var( 'status' ) : '', 'search' => self::query_var( 's' ), ); $page = max( 1, absint( self::query_var( 'paged' ) ) ); $result = Deliveries::query( $filters + array( 'page' => $page, 'per_page' => 25, ) ); $events = Registry::events(); $this->header( 'hookbird-log', __( 'Delivery log', 'hookbird' ), __( 'Every event Hookbird sent, with the response. Failed deliveries can be resent with one click.', 'hookbird' ) ); require HOOKBIRD_DIR . 'admin/views/log.php'; $this->footer(); } /** * Settings and tools. */ public function page_settings() { $this->guard(); $settings = Settings::all(); $counts = array( 'pending' => Deliveries::count( 'pending' ), 'failed' => Deliveries::count( 'failed' ), 'success' => Deliveries::count( 'success' ), ); $next_run = wp_next_scheduled( Installer::CRON_PROCESS ); $sweep = wp_next_scheduled( Installer::CRON_SWEEP ); $this->header( 'hookbird-settings', __( 'Settings', 'hookbird' ), __( 'Retries, timeouts, alerts and tools for moving your setup between sites.', 'hookbird' ) ); require HOOKBIRD_DIR . 'admin/views/settings.php'; $this->footer(); } /** * Help & Setup: guides, troubleshooting and system status. */ public function page_help() { $this->guard(); $guides = Onboarding::guides(); $faq = Onboarding::troubleshooting(); $checks = Onboarding::status_checks(); $presets = Formatter::presets(); $links = Feedback::links(); $support = Feedback::support_email(); $report = Feedback::system_report(); $contact = $this->support_state(); $this->header( 'hookbird-help', __( 'Help & Setup', 'hookbird' ), __( 'Step-by-step guides for each destination, fixes for common problems, support and a health check of this site.', 'hookbird' ) ); require HOOKBIRD_DIR . 'admin/views/help.php'; $this->footer(); } /** * What the support form shows: the last send result and the values to fill in. * * @return array status (''|sent|error), message, name, email, text, report. */ private function support_state() { $key = 'hookbird_support_state_' . get_current_user_id(); $state = get_transient( $key ); delete_transient( $key ); $user = wp_get_current_user(); $defaults = array( 'status' => '', 'message' => '', 'name' => $user->display_name, 'email' => $user->user_email, 'text' => '', 'report' => false, ); $state = is_array( $state ) ? array_merge( $defaults, $state ) : $defaults; if ( 'sent' === $state['status'] ) { $state['text'] = ''; $state['report'] = false; } return $state; } // Handlers. /** * Send the Help & Setup support form to the developer by email. Only runs when the admin clicks Send. */ public function handle_support() { $this->guard(); check_admin_referer( 'hookbird_support' ); $to = Feedback::support_email(); $name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : ''; $name = mb_substr( trim( str_replace( array( '<', '>', '"' ), '', $name ) ), 0, 100 ); $email = isset( $_POST['email'] ) ? sanitize_email( wp_unslash( $_POST['email'] ) ) : ''; $text = isset( $_POST['text'] ) ? sanitize_textarea_field( wp_unslash( $_POST['text'] ) ) : ''; $text = mb_substr( trim( str_replace( array( "\r\n", "\r" ), "\n", $text ) ), 0, 5000 ); $report = ! empty( $_POST['report'] ); $state = array( 'name' => $name, 'email' => $email, 'text' => $text, 'report' => $report, ); $user = get_current_user_id(); $error = ''; if ( '' === $to ) { $error = __( 'Email support is not available in this version. Please ask on the support forum.', 'hookbird' ); } elseif ( '' === $name ) { $error = __( 'Please enter your name.', 'hookbird' ); } elseif ( ! is_email( $email ) ) { $error = __( 'Please enter a valid email address, so the reply can reach you.', 'hookbird' ); } elseif ( mb_strlen( $text ) < 10 ) { $error = __( 'Please describe your question in a few words.', 'hookbird' ); } elseif ( get_transient( 'hookbird_support_sent_' . $user ) ) { $error = __( 'Your last message was sent less than a minute ago. Please wait a moment before sending another.', 'hookbird' ); } if ( '' === $error ) { global $wp_version; $lines = array( 'Name: ' . $name, 'Email: ' . $email, 'Site: ' . home_url( '/' ), 'Hookbird ' . HOOKBIRD_VERSION . ', WordPress ' . $wp_version . ', PHP ' . PHP_VERSION, '', $text, ); if ( $report ) { $lines[] = ''; $lines[] = '--- System report ---'; $lines[] = Feedback::system_report(); } $first = strtok( $text, "\n" ); $subject = '[Hookbird support] ' . mb_substr( trim( false === $first ? $text : $first ), 0, 70 ); $sent = wp_mail( $to, $subject, implode( "\n", $lines ), array( 'Reply-To: ' . $name . ' <' . $email . '>' ) ); if ( $sent ) { set_transient( 'hookbird_support_sent_' . $user, 1, MINUTE_IN_SECONDS ); $state['status'] = 'sent'; /* translators: %s: the email address the reply will go to. */ $state['message'] = sprintf( __( 'Message sent. You will get a reply at %s.', 'hookbird' ), $email ); } else { $error = __( 'Your site could not send the email. Check that it can send mail (an SMTP plugin usually fixes this), or ask on the support forum.', 'hookbird' ); } } if ( '' !== $error ) { $state['status'] = 'error'; $state['message'] = $error; } set_transient( 'hookbird_support_state_' . $user, $state, 10 * MINUTE_IN_SECONDS ); wp_safe_redirect( admin_url( 'admin.php?page=hookbird-help' ) . '#support' ); exit; } /** * Save an endpoint from the editor form. */ public function handle_save_endpoint() { $this->guard(); check_admin_referer( 'hookbird_save_endpoint' ); $id = isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0; $presets = Formatter::presets(); $preset = isset( $_POST['preset'] ) ? sanitize_key( wp_unslash( $_POST['preset'] ) ) : 'custom'; $preset = isset( $presets[ $preset ] ) ? $preset : 'custom'; $events = isset( $_POST['events'] ) ? array_map( 'sanitize_text_field', wp_unslash( (array) $_POST['events'] ) ) : array(); if ( ! empty( $_POST['all_events'] ) ) { $events = array( '*' ); } if ( ! empty( $_POST['custom_events'] ) ) { $events = array_merge( $events, explode( ',', sanitize_text_field( wp_unslash( $_POST['custom_events'] ) ) ) ); } $conditions = array( 'match' => isset( $_POST['conditions_match'] ) && 'any' === $_POST['conditions_match'] ? 'any' : 'all', 'rules' => array(), ); if ( isset( $_POST['conditions'] ) && is_array( $_POST['conditions'] ) ) { // Each rule is sanitized field by field in Endpoints::sanitize_conditions(). $raw_rules = map_deep( wp_unslash( $_POST['conditions'] ), 'sanitize_text_field' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by map_deep. foreach ( (array) $raw_rules as $rule ) { if ( is_array( $rule ) && isset( $rule['field'] ) && '' !== trim( (string) $rule['field'] ) ) { $conditions['rules'][] = $rule; } } } $values = array( 'name' => isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '', 'url' => isset( $_POST['url'] ) ? esc_url_raw( wp_unslash( $_POST['url'] ) ) : '', 'events' => $events, 'format' => $presets[ $preset ]['format'], 'headers' => isset( $_POST['headers'] ) ? sanitize_textarea_field( wp_unslash( $_POST['headers'] ) ) : '', 'conditions' => $conditions, 'options' => array( 'preset' => $preset, 'chat_id' => isset( $_POST['chat_id'] ) ? sanitize_text_field( wp_unslash( $_POST['chat_id'] ) ) : '', ), 'active' => ! empty( $_POST['active'] ), ); $result = $id ? Endpoints::update( $id, $values ) : Endpoints::create( $values ); if ( is_wp_error( $result ) ) { set_transient( 'hookbird_draft_' . get_current_user_id(), array( 'id' => $id, 'values' => array( 'preset' => $preset, 'name' => $values['name'], 'url' => $values['url'], 'events' => $events, 'headers' => Endpoints::sanitize_headers( $values['headers'] ), 'conditions' => Endpoints::sanitize_conditions( $conditions ), 'chat_id' => $values['options']['chat_id'], 'active' => $values['active'], ), ), 5 * MINUTE_IN_SECONDS ); $this->notice( $result->get_error_message(), 'error' ); $this->go( 'hookbird-edit', $id ? array( 'id' => $id ) : array( 'preset' => $preset ) ); } $new_id = $id ? $id : (int) $result; $this->notice( $id ? __( 'Endpoint saved.', 'hookbird' ) : __( 'Endpoint created. Send a test event to check that it works.', 'hookbird' ) ); $this->go( 'hookbird-edit', array( 'id' => $new_id ) ); } /** * Delete, enable, disable, resume or rotate. */ public function handle_endpoint_action() { $this->guard(); check_admin_referer( 'hookbird_endpoint_action' ); $id = isset( $_GET['id'] ) ? absint( $_GET['id'] ) : 0; $do = isset( $_GET['do'] ) ? sanitize_key( wp_unslash( $_GET['do'] ) ) : ''; $back = isset( $_GET['back'] ) && 'edit' === $_GET['back'] ? 'hookbird-edit' : 'hookbird'; if ( ! Endpoints::get( $id ) ) { $this->notice( __( 'Endpoint not found.', 'hookbird' ), 'error' ); $this->go( 'hookbird' ); } switch ( $do ) { case 'delete': Endpoints::delete( $id ); $this->notice( __( 'Endpoint deleted.', 'hookbird' ) ); $this->go( 'hookbird' ); break; case 'enable': Endpoints::set_active( $id, true ); Dispatcher::instance()->schedule_run( time() ); $this->notice( __( 'Endpoint enabled. Queued events will be sent shortly.', 'hookbird' ) ); break; case 'disable': Endpoints::set_active( $id, false ); $this->notice( __( 'Endpoint disabled. New events will not be queued for it.', 'hookbird' ) ); break; case 'resume': Endpoints::resume( $id ); Dispatcher::instance()->schedule_run( time() ); $this->notice( __( 'Endpoint resumed. Queued events will be sent shortly.', 'hookbird' ) ); break; case 'rotate': Endpoints::rotate_secret( $id ); $this->notice( __( 'New signing secret generated. Update it in your receiver.', 'hookbird' ), 'warning' ); break; } $this->go( $back, 'hookbird-edit' === $back ? array( 'id' => $id ) : array() ); } /** * Send a test event. */ public function handle_test_endpoint() { $this->guard(); check_admin_referer( 'hookbird_test_endpoint' ); $id = isset( $_REQUEST['id'] ) ? absint( $_REQUEST['id'] ) : 0; $event = isset( $_REQUEST['event'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['event'] ) ) : ''; $back = isset( $_REQUEST['back'] ) && 'list' === $_REQUEST['back'] ? 'hookbird' : 'hookbird-edit'; $result = Dispatcher::instance()->send_test( $id, $event ); if ( is_wp_error( $result ) ) { $this->notice( $result->get_error_message(), 'error' ); } elseif ( $result['success'] ) { /* translators: 1: event key, 2: HTTP status code, 3: duration in milliseconds. */ $this->notice( sprintf( __( 'Test "%1$s" delivered: HTTP %2$d in %3$d ms.', 'hookbird' ), $result['event'], $result['code'], $result['duration_ms'] ) ); } else { /* translators: %s: error message. */ $this->notice( sprintf( __( 'Test failed: %s See the delivery log for the full response.', 'hookbird' ), $result['error'] ), 'error' ); } $this->go( $back, 'hookbird-edit' === $back ? array( 'id' => $id ) : array() ); } /** * Resend a delivery. */ public function handle_resend() { $this->guard(); check_admin_referer( 'hookbird_resend' ); $id = isset( $_GET['id'] ) ? absint( $_GET['id'] ) : 0; $result = Dispatcher::instance()->resend( $id ); if ( is_wp_error( $result ) ) { $this->notice( $result->get_error_message(), 'error' ); $this->go( 'hookbird-log' ); } if ( $result['success'] ) { /* translators: %d: HTTP status code. */ $this->notice( sprintf( __( 'Delivered: HTTP %d.', 'hookbird' ), $result['code'] ) ); } else { /* translators: %s: error message. */ $this->notice( sprintf( __( 'Delivery failed again: %s', 'hookbird' ), $result['error'] ), 'error' ); } $this->go( 'hookbird-log', array( 'delivery' => $result['delivery_id'] ) ); } /** * Queue tools. */ public function handle_tools() { $this->guard(); check_admin_referer( 'hookbird_tools' ); $do = isset( $_POST['do'] ) ? sanitize_key( wp_unslash( $_POST['do'] ) ) : ''; if ( 'run' === $do ) { Deliveries::recover_stuck(); if ( Dispatcher::is_running() ) { $this->notice( __( 'The queue is already being sent in the background. Check the delivery log again in a minute.', 'hookbird' ), 'info' ); $this->go( 'hookbird-settings' ); } $sent = Dispatcher::instance()->process_queue(); /* translators: %d: number of deliveries. */ $this->notice( sprintf( _n( 'Processed %d delivery.', 'Processed %d deliveries.', $sent, 'hookbird' ), $sent ) ); } elseif ( 'purge' === $do ) { $deleted = Deliveries::purge( 0 ); /* translators: %d: number of log entries. */ $this->notice( sprintf( _n( 'Deleted %d log entry.', 'Deleted %d log entries.', $deleted, 'hookbird' ), $deleted ) ); } $this->go( 'hookbird-settings' ); } /** * Download an export file. */ public function handle_export() { $this->guard(); check_admin_referer( 'hookbird_export' ); $secrets = ! empty( $_POST['include_secrets'] ); $filename = 'hookbird-' . sanitize_title( wp_parse_url( home_url(), PHP_URL_HOST ) ) . '-' . gmdate( 'Y-m-d' ) . '.json'; nocache_headers(); header( 'Content-Type: application/json; charset=utf-8' ); header( 'Content-Disposition: attachment; filename="' . $filename . '"' ); echo wp_json_encode( Transfer::export( $secrets ), JSON_PRETTY_PRINT ); exit; } /** * Import an export file. */ public function handle_import() { $this->guard(); check_admin_referer( 'hookbird_import' ); if ( empty( $_FILES['import_file']['tmp_name'] ) || ! is_uploaded_file( $_FILES['import_file']['tmp_name'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- Temporary upload path, validated by is_uploaded_file(). $this->notice( __( 'Please choose an export file.', 'hookbird' ), 'error' ); $this->go( 'hookbird-settings' ); } $name = isset( $_FILES['import_file']['name'] ) ? sanitize_file_name( wp_unslash( $_FILES['import_file']['name'] ) ) : ''; if ( 'json' !== strtolower( pathinfo( $name, PATHINFO_EXTENSION ) ) ) { $this->notice( __( 'Please choose a .json export file.', 'hookbird' ), 'error' ); $this->go( 'hookbird-settings' ); } if ( ! empty( $_FILES['import_file']['size'] ) && (int) $_FILES['import_file']['size'] > MB_IN_BYTES ) { $this->notice( __( 'The file is too large.', 'hookbird' ), 'error' ); $this->go( 'hookbird-settings' ); } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents, WordPress.Security.ValidatedSanitizedInput -- Reading the uploaded temporary file; contents are validated and sanitized by Transfer::import(). $data = json_decode( (string) file_get_contents( $_FILES['import_file']['tmp_name'] ), true ); $result = Transfer::import( is_array( $data ) ? $data : array(), ! empty( $_POST['import_settings'] ) ); if ( is_wp_error( $result ) ) { $this->notice( $result->get_error_message(), 'error' ); } else { /* translators: %d: number of endpoints. */ $this->notice( sprintf( _n( 'Imported %d endpoint.', 'Imported %d endpoints.', $result['created'], 'hookbird' ), $result['created'] ) ); foreach ( $result['errors'] as $error ) { $this->notice( $error, 'warning' ); } } $this->go( 'hookbird-settings' ); } }