Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.96% covered (warning)
85.96%
49 / 57
33.33% covered (danger)
33.33%
1 / 3
CRAP
n/a
0 / 0
Automattic\Jetpack\Extensions\AIChat\register_block
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
3
Automattic\Jetpack\Extensions\AIChat\load_assets
90.48% covered (success)
90.48%
19 / 21
0.00% covered (danger)
0.00%
0 / 1
10.09
Automattic\Jetpack\Extensions\AIChat\add_ai_chat_block_data
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
2.00
1<?php
2/**
3 * Jetpack AI Chat.
4 *
5 * @since 12.1
6 *
7 * @package automattic/jetpack
8 */
9
10namespace Automattic\Jetpack\Extensions\AIChat;
11
12use Automattic\Jetpack\Blocks;
13use Automattic\Jetpack\Search\Module_Control as Search_Module_Control;
14use Automattic\Jetpack\Search\Plan as Search_Plan;
15use Automattic\Jetpack\Search\Search_Blocks;
16use Jetpack_Gutenberg;
17
18if ( ! defined( 'ABSPATH' ) ) {
19    exit( 0 );
20}
21
22// Required directly rather than relying on the plugin bootstrap: on
23// WordPress.com Simple the extension files load through wpcom's own loader
24// and load-jetpack.php never runs. Without it the is_ai_enabled() master-gate
25// check below would fatal there.
26require_once __DIR__ . '/../../../_inc/lib/class-jetpack-ai-settings.php';
27
28/**
29 * Registers our block for use in Gutenberg
30 * This is done via an action so that we can disable
31 * registration if we need to.
32 */
33function register_block() {
34    if ( ! \Jetpack_AI_Settings::site_is_connected() ) {
35        return;
36    }
37
38    // The block stays registered while Jetpack AI is off so the render callback
39    // runs and can output nothing. Otherwise WordPress echoes the saved markup,
40    // an empty div, wherever the block sits.
41    Blocks::jetpack_register_block(
42        __DIR__,
43        array( 'render_callback' => __NAMESPACE__ . '\load_assets' )
44    );
45
46    // Registration queues its own "available" mark on this action, so run after it.
47    if ( ! \Jetpack_AI_Settings::is_ai_enabled() ) {
48        add_action(
49            'jetpack_register_gutenberg_extensions',
50            static function () {
51                Jetpack_Gutenberg::set_extension_unavailable( 'ai-chat', 'ai_disabled' );
52            },
53            20
54        );
55    }
56}
57add_action( 'init', __NAMESPACE__ . '\register_block' );
58
59/**
60 * Jetpack AI Paragraph block registration/dependency declaration.
61 *
62 * @param array $attr Array containing the Jetpack AI Chat block attributes.
63 *
64 * @return string
65 */
66function load_assets( $attr ) {
67    // With Jetpack AI off there is nothing to ask, so render nothing rather
68    // than an empty container.
69    if ( ! \Jetpack_AI_Settings::is_ai_enabled() ) {
70        return '';
71    }
72
73    /*
74     * Enqueue necessary scripts and styles.
75     */
76    Jetpack_Gutenberg::load_assets_as_required( __DIR__ );
77
78    $ask_button_label = $attr['askButtonLabel'] ?? __( 'Ask', 'jetpack' );
79    $placeholder      = $attr['placeholder'] ?? __( 'Ask a question about this site.', 'jetpack' );
80
81    if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
82        $blog_id = get_current_blog_id();
83        $type    = 'wpcom'; // WPCOM simple sites.
84    } else {
85        $blog_id = \Jetpack_Options::get_option( 'id' );
86        $type    = 'jetpack'; // Self-hosted (includes Atomic)
87    }
88
89    return sprintf(
90        '<div class="%1$s" data-ask-button-label="%2$s" id="jetpack-ai-chat" data-blog-id="%3$d" data-blog-type="%4$s" data-placeholder="%5$s" data-show-copy="%6$d" data-show-feedback="%7$d" data-show-sources="%8$d"></div>',
91        esc_attr( Blocks::classes( Blocks::get_block_feature( __DIR__ ), $attr ) ),
92        esc_attr( $ask_button_label ),
93        esc_attr( $blog_id ),
94        esc_attr( $type ),
95        esc_attr( $placeholder ),
96        esc_attr( isset( $attr['showCopy'] ) ? ( $attr['showCopy'] ? 1 : 0 ) : 1 ),
97        esc_attr( isset( $attr['showFeedback'] ) ? ( $attr['showFeedback'] ? 1 : 0 ) : 1 ),
98        esc_attr( isset( $attr['showSources'] ) ? ( $attr['showSources'] ? 1 : 0 ) : 1 )
99    );
100}
101
102/**
103 * Add the initial state for the AI Chat block.
104 */
105function add_ai_chat_block_data() {
106    // Only relevant to the editor right now.
107    if ( ! is_admin() ) {
108        return;
109    }
110    $search        = new Search_Module_Control();
111    $plan          = new Search_Plan();
112    $initial_state = array(
113        'jetpackSettings' => array(
114            // `module_active` reflects whether the Jetpack Search module is on, which is
115            // what controls site indexing â€” independent of the chosen front-end experience
116            // (Overlay / Theme / Inline / Embedded). The AI Chat block only needs the
117            // index, so it gates on this rather than on `instant_search_enabled`.
118            'module_active'          => $search->is_active(),
119            'instant_search_enabled' => $search->is_instant_search_enabled(),
120            'plan_supports_search'   => $plan->supports_instant_search(),
121            'supports_paid_search'   => Search_Blocks::supports_paid_search(),
122        ),
123    );
124    wp_add_inline_script(
125        'jetpack-blocks-editor',
126        'var Jetpack_AIChatBlock = ' . wp_json_encode( $initial_state, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ';',
127        'before'
128    );
129}
130add_action( 'enqueue_block_assets', __NAMESPACE__ . '\add_ai_chat_block_data', 11 );