$args Extra query args. * @return string */ public static function url( $tab = 'connect', array $args = array() ) { return add_query_arg( array_merge( array( 'page' => self::SLUG, 'tab' => $tab ), $args ), admin_url( 'admin.php' ) ); } /** * Render. */ public function render() { if ( ! current_user_can( self::CAPABILITY ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only tab switch. $tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'connect'; $tabs = array( 'connect' => __( 'Connect', 'orderchirp' ), 'messages' => __( 'Messages', 'orderchirp' ), 'activity' => __( 'Activity', 'orderchirp' ), ); /** * Tabs on the Orderchirp page. Orderchirp Pro adds its channels here. * * @param array $tabs Slug → label. */ $tabs = (array) apply_filters( 'orderchirp_admin_tabs', $tabs ); if ( ! isset( $tabs[ $tab ] ) ) { $tab = 'connect'; } echo '

' . esc_html__( 'Orderchirp', 'orderchirp' ) . '

'; $this->notice(); echo ''; if ( 'connect' === $tab ) { $this->tab_connect(); } elseif ( 'messages' === $tab ) { $this->tab_messages(); } elseif ( 'activity' === $tab ) { $this->tab_activity(); } else { /** * Render a tab added with orderchirp_admin_tabs. */ do_action( 'orderchirp_admin_tab_' . $tab ); } echo '
'; } /** * The result of the last action, shown once. */ private function notice() { $key = 'orderchirp_notice_' . get_current_user_id(); $notice = get_transient( $key ); if ( ! is_array( $notice ) ) { return; } delete_transient( $key ); printf( '

%2$s

', 'error' === $notice['type'] ? 'error' : ( 'warning' === $notice['type'] ? 'warning' : 'success' ), esc_html( (string) $notice['text'] ) ); } /** * Remember a notice and go back to a tab. * * @param string $type success|warning|error. * @param string $text Text. * @param string $tab Tab. * @param array $args Extra query args. */ private function back( $type, $text, $tab, array $args = array() ) { set_transient( 'orderchirp_notice_' . get_current_user_id(), array( 'type' => $type, 'text' => $text, ), 60 ); wp_safe_redirect( self::url( $tab, $args ) ); exit; } /** * Capability check for a form; each handler checks its own nonce right after. */ private function guard() { if ( ! current_user_can( self::CAPABILITY ) ) { wp_die( esc_html__( 'You are not allowed to do this.', 'orderchirp' ), 403 ); } } /** * Connect tab. */ private function tab_connect() { $connected = Settings::connected(); echo '
'; echo '

' . esc_html__( '1. Your Telegram bot', 'orderchirp' ) . '

'; if ( $connected ) { $status = Connection::status(); echo '

' . sprintf( /* translators: %s: bot username */ esc_html__( 'Connected to %s.', 'orderchirp' ), '@' . esc_html( (string) Settings::get( 'bot_username' ) ) . '' ) . '

'; echo '

' . ( 'webhook' === $status['delivery'] ? esc_html__( 'Telegram delivers messages to this site instantly (webhook).', 'orderchirp' ) : esc_html__( 'This site checks Telegram for new messages once a minute (polling), because Telegram can only call sites that use HTTPS.', 'orderchirp' ) ) . '

'; if ( '' !== $status['last_error'] ) { /* translators: %s: error reported by Telegram */ echo '

' . esc_html( sprintf( __( 'Telegram reports a problem: %s', 'orderchirp' ), $status['last_error'] ) ) . '

'; } $this->post_form( 'disconnect', __( 'Disconnect the bot', 'orderchirp' ), 'secondary' ); } else { echo '
    '; echo '
  1. ' . wp_kses( __( 'In Telegram, open @BotFather and send /newbot.', 'orderchirp' ), array( 'a' => array( 'href' => true, 'target' => true, 'rel' => true ), 'code' => array() ) ) . '
  2. '; echo '
  3. ' . esc_html__( 'Give the bot your shop’s name, and a username that ends in "bot".', 'orderchirp' ) . '
  4. '; echo '
  5. ' . esc_html__( 'BotFather answers with a token. Paste it here.', 'orderchirp' ) . '
  6. '; echo '
'; echo '
'; wp_nonce_field( 'orderchirp_connect' ); echo ''; echo '


'; echo '

'; echo '

'; submit_button( __( 'Connect', 'orderchirp' ) ); echo '
'; } echo '
'; if ( ! $connected ) { return; } echo '
'; echo '

' . esc_html__( '2. Your alerts', 'orderchirp' ) . '

'; echo '

' . esc_html__( 'Chats that get new-order alerts, low-stock alerts and customer replies.', 'orderchirp' ) . '

'; $owners = Subscribers::owners(); if ( $owners ) { echo ''; foreach ( $owners as $chat => $label ) { echo ''; } echo '
' . esc_html( $label ) . ''; $this->post_form( 'remove_owner', __( 'Remove', 'orderchirp' ), 'secondary small', array( 'chat' => (string) $chat ) ); echo '
'; } else { echo '

' . esc_html__( 'No chat added yet, so nobody gets your alerts.', 'orderchirp' ) . '

'; } $invite = get_transient( 'orderchirp_invite_' . get_current_user_id() ); if ( is_string( $invite ) && '' !== $invite ) { delete_transient( 'orderchirp_invite_' . get_current_user_id() ); echo '

' . esc_html__( 'Open this link on the phone or computer where you use Telegram and press Start. It works once, for one hour:', 'orderchirp' ) . '

'; echo '

' . esc_html__( 'Open in Telegram', 'orderchirp' ) . '

'; } $this->post_form( 'invite', __( 'Add a chat', 'orderchirp' ), 'secondary' ); if ( $owners ) { $this->post_form( 'test', __( 'Send a test message', 'orderchirp' ), 'secondary' ); } echo '
'; } /** * A one-button form. * * @param string $action Action. * @param string $label Button label. * @param string $class Button class. * @param array $fields Hidden fields. */ private function post_form( $action, $label, $class, array $fields = array() ) { echo '
'; wp_nonce_field( 'orderchirp_' . $action ); echo ''; foreach ( $fields as $name => $value ) { echo ''; } submit_button( $label, $class, 'submit', false ); echo '
'; } /** * Messages tab. */ private function tab_messages() { $settings = Settings::all(); $messages = $settings['status_messages']; echo '
'; wp_nonce_field( 'orderchirp_save_messages' ); echo ''; echo '

' . esc_html__( 'Messages to customers', 'orderchirp' ) . '

'; echo '

' . esc_html__( 'Sent on Telegram when an order moves to a status, to customers who turned updates on. A line whose tracking or item placeholder has no value is left out.', 'orderchirp' ) . '

'; foreach ( wc_get_order_statuses() as $key => $label ) { $status = 0 === strpos( $key, 'wc-' ) ? substr( $key, 3 ) : $key; $message = isset( $messages[ $status ] ) ? $messages[ $status ] : array( 'enabled' => false, 'text' => '' ); $field = 'status_messages[' . $status . ']'; echo '
'; printf( '', esc_attr( $field ), checked( ! empty( $message['enabled'] ), true, false ), esc_html( $label ) ); printf( '', esc_attr( $field ), esc_textarea( (string) $message['text'] ) ); echo '
'; } echo '
' . esc_html__( 'Placeholders you can use', 'orderchirp' ) . '
    '; foreach ( Template::placeholders() as $name => $meaning ) { echo '
  • {' . esc_html( $name ) . '} ' . esc_html( $meaning ) . '
  • '; } echo '
'; echo '

' . esc_html__( 'Your alerts', 'orderchirp' ) . '

'; $this->checkbox( 'owner_new_order', __( 'New orders', 'orderchirp' ), $settings ); $this->checkbox( 'owner_low_stock', __( 'Low stock and out of stock', 'orderchirp' ), $settings ); $this->checkbox( 'owner_replies', __( 'Customer replies (also added to the order as a note)', 'orderchirp' ), $settings ); echo '
'; echo '

' . esc_html__( 'Where customers turn updates on', 'orderchirp' ) . '

'; $this->checkbox( 'optin_thankyou', __( 'Order received page', 'orderchirp' ), $settings ); $this->checkbox( 'optin_email', __( 'Order emails (received and on hold)', 'orderchirp' ), $settings ); $this->checkbox( 'optin_account', __( 'My Account → order', 'orderchirp' ), $settings ); echo '
'; submit_button( __( 'Save messages', 'orderchirp' ) ); echo '
'; } /** * A settings checkbox. * * @param string $key Setting. * @param string $label Label. * @param array $settings Settings. */ private function checkbox( $key, $label, array $settings ) { printf( '

', esc_attr( $key ), checked( ! empty( $settings[ $key ] ), true, false ), esc_html( $label ) ); } /** * Activity tab. */ private function tab_activity() { $rows = Message_Log::recent( 100 ); echo '

' . esc_html__( 'Last 100 messages', 'orderchirp' ) . '

'; if ( ! $rows ) { echo '

' . esc_html__( 'Nothing sent or received yet.', 'orderchirp' ) . '

'; return; } echo ''; foreach ( array( __( 'Time (UTC)', 'orderchirp' ), __( 'Order', 'orderchirp' ), __( 'To / from', 'orderchirp' ), __( 'Event', 'orderchirp' ), __( 'Status', 'orderchirp' ), __( 'Message', 'orderchirp' ) ) as $heading ) { echo ''; } echo ''; foreach ( $rows as $row ) { $order = (int) $row['order_id'] ? wc_get_order( (int) $row['order_id'] ) : null; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
' . esc_html( $heading ) . '
' . esc_html( (string) $row['created_at'] ) . '' . ( $order ? '#' . esc_html( $order->get_order_number() ) . '' : '–' ) . '' . esc_html( ( 'in' === $row['direction'] ? '← ' : '→ ' ) . $row['recipient'] ) . '' . esc_html( (string) $row['event'] ) . '' . esc_html( (string) $row['status'] ) . ( '' !== (string) $row['detail'] ? '
' . esc_html( (string) $row['detail'] ) . '' : '' ) . '
' . nl2br( esc_html( (string) $row['body'] ) ) . '
'; } /** * Connect. */ public function handle_connect() { $this->guard(); check_admin_referer( 'orderchirp_connect' ); $token = isset( $_POST['token'] ) ? sanitize_text_field( wp_unslash( $_POST['token'] ) ) : ''; $polling = ! empty( $_POST['polling'] ); $result = Connection::connect( $token, $polling ); if ( is_wp_error( $result ) ) { $this->back( Settings::connected() ? 'warning' : 'error', $result->get_error_message(), 'connect' ); } $this->back( 'success', __( 'Your bot is connected. Now add the chat where you want your alerts.', 'orderchirp' ), 'connect' ); } /** * Disconnect. */ public function handle_disconnect() { $this->guard(); check_admin_referer( 'orderchirp_disconnect' ); Connection::disconnect(); $this->back( 'success', __( 'The bot is disconnected.', 'orderchirp' ), 'connect' ); } /** * Owner invite. */ public function handle_invite() { $this->guard(); check_admin_referer( 'orderchirp_invite' ); if ( ! Settings::connected() ) { $this->back( 'error', __( 'Connect your bot first.', 'orderchirp' ), 'connect' ); } set_transient( 'orderchirp_invite_' . get_current_user_id(), Connection::deep_link( Updates_Handler::new_owner_invite() ), 600 ); $this->back( 'success', __( 'Invite created.', 'orderchirp' ), 'connect' ); } /** * Remove an owner chat. */ public function handle_remove_owner() { $this->guard(); check_admin_referer( 'orderchirp_remove_owner' ); $chat = isset( $_POST['chat'] ) ? sanitize_text_field( wp_unslash( $_POST['chat'] ) ) : ''; Subscribers::remove_owner( $chat ); $this->back( 'success', __( 'Chat removed.', 'orderchirp' ), 'connect' ); } /** * Test message to the owner chats. */ public function handle_test() { $this->guard(); check_admin_referer( 'orderchirp_test' ); $queued = 0; foreach ( array_keys( Subscribers::owners() ) as $chat ) { if ( Dispatcher::queue( array( 'recipient' => 'owner', 'event' => 'owner:test', 'chat_id' => (string) $chat, 'body' => __( 'This is a test message from Orderchirp. Your alerts work ✅', 'orderchirp' ), ) ) ) { ++$queued; } } /* translators: %d: number of chats */ $this->back( 'success', sprintf( _n( 'Test message queued for %d chat. It arrives within a minute.', 'Test message queued for %d chats. They arrive within a minute.', $queued, 'orderchirp' ), $queued ), 'connect' ); } /** * Save the Messages tab. */ public function handle_save_messages() { $this->guard(); check_admin_referer( 'orderchirp_save_messages' ); $input = isset( $_POST['status_messages'] ) && is_array( $_POST['status_messages'] ) ? wp_unslash( $_POST['status_messages'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- each field is sanitized below. $known = array(); foreach ( array_keys( wc_get_order_statuses() ) as $key ) { $known[] = 0 === strpos( $key, 'wc-' ) ? substr( $key, 3 ) : $key; } $messages = array(); foreach ( $known as $status ) { $row = isset( $input[ $status ] ) && is_array( $input[ $status ] ) ? $input[ $status ] : array(); $messages[ $status ] = array( 'enabled' => ! empty( $row['enabled'] ), 'text' => isset( $row['text'] ) ? sanitize_textarea_field( (string) $row['text'] ) : '', ); } $values = array( 'status_messages' => $messages ); foreach ( array( 'owner_new_order', 'owner_low_stock', 'owner_replies', 'optin_thankyou', 'optin_email', 'optin_account' ) as $key ) { $values[ $key ] = ! empty( $_POST[ $key ] ); } Settings::update( $values ); $this->back( 'success', __( 'Messages saved.', 'orderchirp' ), 'messages' ); } }