':red_circle:', 'major' => ':large_orange_circle:', 'minor' => ':large_yellow_circle:', 'nice' => ':large_purple_circle:', ); $labels = pinora_labels()['severities']; $escape = static function ( $text ) { return str_replace( array( '&', '<', '>' ), array( '&', '<', '>' ), (string) $text ); }; $author = $note['author'] ?? __( 'Anonymous', 'pinora' ); $severity = $note['severity'] ?? 'major'; $page = pinora_page_url( $note['url'] ?? '/' ); $view = add_query_arg( array( 'pinora' => '1', 'pinora_note' => $note['id'], ), $page ); $context = array_filter( array( '*' . $escape( $author ) . '*', '<' . esc_url_raw( $page ) . '|' . $escape( $note['url'] ?? '/' ) . '>', /* translators: %s: name of the page section, e.g. "Footer". */ isset( $note['sectionLabel'] ) ? sprintf( __( 'section “%s”', 'pinora' ), $escape( $note['sectionLabel'] ) ) : '', /* translators: %d: browser window width in pixels. */ isset( $note['viewportWidth'] ) ? sprintf( __( '%d px wide', 'pinora' ), (int) $note['viewportWidth'] ) : '', ) ); $blocks = array( array( 'type' => 'section', 'text' => array( 'type' => 'mrkdwn', 'text' => ( $icons[ $severity ] ?? '' ) . ' ' . ( $labels[ $severity ] ?? $severity ) /* translators: %d: note number. */ . ' · ' . sprintf( __( 'new note #%d', 'pinora' ), (int) $note['id'] ) . "\n" . $escape( mb_substr( $note['description'] ?? '', 0, 2800 ) ), ), ), array( 'type' => 'context', 'elements' => array( array( 'type' => 'mrkdwn', 'text' => implode( ' · ', $context ), ), ), ), ); if ( ! empty( $note['screenshot'] ) ) { $blocks[] = array( 'type' => 'image', 'image_url' => $note['screenshot'], 'alt_text' => __( 'Screenshot taken with the note', 'pinora' ), ); } $blocks[] = array( 'type' => 'actions', 'elements' => array( array( 'type' => 'button', 'text' => array( 'type' => 'plain_text', 'text' => __( 'View on the page', 'pinora' ), ), 'url' => esc_url_raw( $view ), ), array( 'type' => 'button', 'text' => array( 'type' => 'plain_text', 'text' => __( 'Open the board', 'pinora' ), ), 'url' => esc_url_raw( pinora_board_url() ), ), ), ); $message = array( // Fallback text: mobile notifications and clients without Block Kit. /* translators: 1: author name, 2: page path. */ 'text' => sprintf( __( 'New feedback from %1$s on %2$s', 'pinora' ), $author, $note['url'] ?? '/' ), 'blocks' => $blocks, ); /** * Filters the Slack payload before it is sent. * * @param array $message Slack payload (`text` and `blocks`). * @param array $note Formatted note. */ return apply_filters( 'pinora_slack_message', $message, $note ); } /** * Posts a note to Slack. A failure is logged, never blocking. * * @param array $note Formatted note. * @return true|WP_Error */ function pinora_slack_send( $note ) { $webhook = pinora_slack_webhook(); if ( '' === $webhook ) { return new WP_Error( 'pinora_slack', __( 'No Slack webhook configured.', 'pinora' ) ); } $response = wp_remote_post( $webhook, array( 'timeout' => 5, 'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ), 'body' => wp_json_encode( pinora_slack_message( $note ) ), ) ); if ( is_wp_error( $response ) ) { pinora_log( 'Slack unreachable: ' . $response->get_error_message() ); return $response; } $code = (int) wp_remote_retrieve_response_code( $response ); if ( 200 !== $code ) { /* translators: 1: HTTP status code, 2: Slack error message. */ $error = sprintf( __( 'Slack answered %1$d: %2$s', 'pinora' ), $code, wp_remote_retrieve_body( $response ) ); pinora_log( $error ); return new WP_Error( 'pinora_slack', $error ); } return true; }