Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
58.46% covered (warning)
58.46%
38 / 65
66.67% covered (warning)
66.67%
4 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Writing_Prompt_Widget
58.46% covered (warning)
58.46%
38 / 65
66.67% covered (warning)
66.67%
4 / 6
37.71
0.00% covered (danger)
0.00%
0 / 1
 init
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 register_widget
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
3
 should_load
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 enqueue_assets
17.86% covered (danger)
17.86%
5 / 28
0.00% covered (danger)
0.00%
0 / 1
18.86
 add_script_data
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 render_widget
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * A class that adds the Daily Writing Prompt dashboard widget to wp-admin.
4 *
5 * @package automattic/jetpack-newsletter
6 */
7
8namespace Automattic\Jetpack\Newsletter;
9
10use Automattic\Jetpack\Connection\Manager as Connection_Manager;
11use Automattic\Jetpack\Status;
12use Automattic\Jetpack\Status\Host;
13use Automattic\Jetpack\Tracking;
14use Jetpack_Tracks_Client;
15
16/**
17 * Register and render the Daily Writing Prompt dashboard widget.
18 */
19class Writing_Prompt_Widget {
20    /**
21     * Whether the class has been initialized.
22     *
23     * @var bool
24     */
25    private static $initialized = false;
26
27    /**
28     * Initialize the Daily Writing Prompt widget.
29     *
30     * Hooks the widget registration into `wp_dashboard_setup`. It can be called
31     * multiple times safely as it will only initialize once.
32     *
33     * @since 0.9.0
34     *
35     * @return void
36     */
37    public static function init() {
38        if ( self::$initialized ) {
39            return;
40        }
41
42        self::$initialized = true;
43
44        add_action( 'wp_dashboard_setup', array( __CLASS__, 'register_widget' ) );
45    }
46
47    /**
48     * Register the Daily Writing Prompt dashboard widget.
49     *
50     * @since 0.9.0
51     *
52     * @return void
53     */
54    public static function register_widget() {
55        if ( ! current_user_can( 'manage_options' ) ) {
56            return;
57        }
58
59        if ( ! self::should_load() ) {
60            return;
61        }
62
63        wp_add_dashboard_widget(
64            'wpcom_daily_writing_prompt',
65            __( 'Daily Writing Prompt', 'jetpack-newsletter' ),
66            array( __CLASS__, 'render_widget' ),
67            null, // @phan-suppress-current-line PhanTypeMismatchArgumentProbablyReal -- Core should ideally document null for no-callback arg. See https://core.trac.wordpress.org/ticket/52539.
68            array(),
69            'side',
70            'high'
71        );
72
73        // Enqueue here (on wp_dashboard_setup, which only fires on the dashboard
74        // screen and runs before the header is printed) rather than in the render
75        // callback, so the stylesheet lands in the head like it did previously.
76        self::enqueue_assets();
77    }
78
79    /**
80     * Whether the Daily Writing Prompt widget should load.
81     *
82     * The widget fetches prompts from WordPress.com through the Jetpack proxy, so
83     * it can only load where the site is able to reach WordPress.com: on Simple
84     * sites, or on online, connected Jetpack sites (including Atomic).
85     *
86     * This runs on `wp_dashboard_setup` rather than at plugin-load time so that
87     * the connection check happens after Core's pluggable functions are loaded.
88     * Checking the connection any earlier triggers a fatal error on Atomic.
89     *
90     * @since 0.9.1
91     *
92     * @return bool
93     */
94    private static function should_load() {
95        // Simple sites can always reach WordPress.com, with no Jetpack connection involved.
96        if ( ( new Host() )->is_wpcom_simple() ) {
97            return true;
98        }
99
100        // Everywhere else the widget needs an online, connected site to fetch prompts.
101        return ( new Connection_Manager() )->is_connected() && ! ( new Status() )->is_offline_mode();
102    }
103
104    /**
105     * Enqueue the assets used to display the Daily Writing Prompt widget.
106     *
107     * @since 0.9.0
108     *
109     * @return string The script/style handle.
110     */
111    public static function enqueue_assets() {
112        $handle    = 'jetpack-newsletter-writing-prompt';
113        $js_path   = dirname( __DIR__ ) . '/build/writing-prompt.js';
114        $asset_php = dirname( __DIR__ ) . '/build/writing-prompt.asset.php';
115
116        if ( ! file_exists( $js_path ) ) {
117            return $handle;
118        }
119
120        $asset_file   = file_exists( $asset_php ) ? include $asset_php : array();
121        $dependencies = $asset_file['dependencies'] ?? array();
122        $version      = $asset_file['version'] ?? filemtime( $js_path );
123
124        wp_enqueue_script(
125            $handle,
126            plugins_url( '../build/writing-prompt.js', __FILE__ ),
127            $dependencies,
128            $version,
129            true
130        );
131        wp_set_script_translations( $handle, 'jetpack-newsletter' );
132
133        // Load the Tracks transport so the widget's analytics events can fire.
134        Tracking::register_tracks_functions_scripts( true );
135
136        // Expose the connected user's Tracks identity to the widget script.
137        add_filter( 'jetpack_admin_js_script_data', array( __CLASS__, 'add_script_data' ) );
138
139        $css_ext  = is_rtl() ? 'rtl.css' : 'css';
140        $css_path = dirname( __DIR__ ) . "/build/writing-prompt.$css_ext";
141        if ( file_exists( $css_path ) ) {
142            wp_enqueue_style(
143                $handle,
144                plugins_url( "../build/writing-prompt.$css_ext", __FILE__ ),
145                array(),
146                $version
147            );
148        }
149
150        return $handle;
151    }
152
153    /**
154     * Add the connected user's Tracks identity to the admin script data.
155     *
156     * Mirrors the `newsletter.tracksUserData` shape provided by the Newsletter
157     * settings page so the widget can reuse the same `getNewsletterScriptData()`
158     * helper and `analytics.initialize()` flow.
159     *
160     * @since 0.9.1
161     *
162     * @param array $data The script data.
163     * @return array The filtered script data.
164     */
165    public static function add_script_data( $data ) {
166        if ( ! isset( $data['newsletter'] ) || ! is_array( $data['newsletter'] ) ) {
167            $data['newsletter'] = array();
168        }
169
170        $data['newsletter']['tracksUserData'] = Jetpack_Tracks_Client::get_connected_user_tracks_identity();
171
172        return $data;
173    }
174
175    /**
176     * Render the container of the Daily Writing Prompt widget.
177     *
178     * The widget is hydrated client-side by the `writing-prompt` JS entry, which
179     * mounts the React app into the container rendered here.
180     *
181     * @since 0.9.0
182     *
183     * @return void
184     */
185    public static function render_widget() {
186        $warning = __( 'Your Daily Writing Prompt widget requires JavaScript to function properly.', 'jetpack-newsletter' );
187
188        ?>
189        <div>
190            <div class="hide-if-js">
191                <?php echo esc_html( $warning ); ?>
192            </div>
193            <div
194                id="wpcom_daily_writing_prompt_main"
195                class="wpcom_daily_writing_prompt hide-if-no-js"
196                style="height: 100%">
197            </div>
198        </div>
199        <?php
200    }
201}