Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
7.19% covered (danger)
7.19%
11 / 153
25.00% covered (danger)
25.00%
2 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_Subscription_Site
7.19% covered (danger)
7.19%
11 / 153
25.00% covered (danger)
25.00%
2 / 8
1384.87
0.00% covered (danger)
0.00%
0 / 1
 init
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 handle_subscribe_block_placements
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 user_can_view_post
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 get_post_end_placement_block_attributes
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
12
 is_header_context
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
30
 is_single_post_context
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 handle_subscribe_block_navigation_placement
9.38% covered (danger)
9.38%
3 / 32
0.00% covered (danger)
0.00%
0 / 1
84.43
 handle_subscribe_block_post_end_placement
3.49% covered (danger)
3.49%
3 / 86
0.00% covered (danger)
0.00%
0 / 1
217.27
1<?php
2/**
3 * Adds support for Jetpack Subscription Site feature.
4 *
5 * @package automattic/jetpack
6 * @since 13.2
7 */
8
9namespace Automattic\Jetpack\Extensions\Subscriptions;
10
11use Jetpack_Memberships;
12use WP_Block_Template;
13use WP_Post;
14
15/**
16 * Jetpack_Subscription_Site class.
17 */
18class Jetpack_Subscription_Site {
19    /**
20     * Jetpack_Subscription_Site singleton instance.
21     *
22     * @var Jetpack_Subscription_Site|null
23     */
24    private static $instance;
25
26    /**
27     * Jetpack_Subscription_Site instance init.
28     */
29    public static function init() {
30        if ( self::$instance === null ) {
31            self::$instance = new Jetpack_Subscription_Site();
32        }
33
34        return self::$instance;
35    }
36
37    /**
38     * Handles Subscribe block placements.
39     *
40     * @return void
41     */
42    public function handle_subscribe_block_placements() {
43        $this->handle_subscribe_block_post_end_placement();
44        $this->handle_subscribe_block_navigation_placement();
45    }
46
47    /**
48     * Returns true if current user can view the post.
49     *
50     * @return bool
51     */
52    protected function user_can_view_post() {
53        if ( ! class_exists( 'Jetpack_Memberships' ) ) {
54            return true;
55        }
56
57        return Jetpack_Memberships::user_can_view_post();
58    }
59
60    /**
61     * Returns post end placement hooked block attributes.
62     *
63     * @param array $default_attrs Deafult attributes.
64     * @param array $anchor_block The anchor block, in parsed block array format.
65     *
66     * @return array
67     */
68    protected function get_post_end_placement_block_attributes( $default_attrs, $anchor_block ) {
69        if ( ! empty( $anchor_block['attrs']['layout']['type'] ) ) {
70            return array_merge(
71                $default_attrs,
72                array(
73                    'layout' => array(
74                        'type' => $anchor_block['attrs']['layout']['type'],
75                    ),
76                )
77            );
78        }
79
80        if ( ! empty( $anchor_block['attrs']['layout']['inherit'] ) ) {
81            return array_merge(
82                $default_attrs,
83                array(
84                    'layout' => array(
85                        'inherit' => $anchor_block['attrs']['layout']['inherit'],
86                    ),
87                )
88            );
89        }
90
91        return $default_attrs;
92    }
93
94    /**
95     * Returns true if context is recognized as a header element.
96     *
97     * @param WP_Block_Template|WP_Post|array $context The block template, template part, or pattern the anchor block belongs to.
98     *
99     * @return bool
100     */
101    protected function is_header_context( $context ) {
102        if ( $context instanceof WP_Post && $context->post_type === 'wp_navigation' ) {
103            return true;
104        }
105
106        if ( $context instanceof WP_Block_Template && $context->area === 'header' ) {
107            return true;
108        }
109
110        return false;
111    }
112
113    /**
114     * Returns true if context is recognized as a single post element.
115     *
116     * @param WP_Block_Template|WP_Post|array $context The block template, template part, or pattern the anchor block belongs to.
117     *
118     * @return bool
119     */
120    protected function is_single_post_context( $context ) {
121        /**
122         * Some themes are not returning a WP_Block_Template. In that case, we need to check with is_singular function.
123         */
124        if ( is_array( $context ) ) {
125            return is_singular( 'post' );
126        }
127
128        return $context instanceof WP_Block_Template && $context->slug === 'single';
129    }
130
131    /**
132     * Handles Subscription block navigation placement.
133     *
134     * @return void
135     */
136    protected function handle_subscribe_block_navigation_placement() {
137        $is_enabled = get_option( 'jetpack_subscriptions_subscribe_navigation_enabled', false );
138        if ( ! $is_enabled ) {
139            return;
140        }
141
142        if ( ! wp_is_block_theme() ) { // TODO Fallback for classic themes.
143            return;
144        }
145
146        add_filter(
147            'hooked_block_types',
148            function ( $hooked_blocks, $relative_position, $anchor_block, $context ) {
149                if (
150                    $anchor_block === 'core/navigation' &&
151                    $relative_position === 'last_child' &&
152                    self::is_header_context( $context )
153                ) {
154                    $hooked_blocks[] = 'jetpack/subscriptions';
155                }
156
157                return $hooked_blocks;
158            },
159            10,
160            4
161        );
162
163        add_filter(
164            'hooked_block_jetpack/subscriptions',
165            function ( $hooked_block, $hooked_block_type, $relative_position, $anchor_block ) {
166                $is_navigation_anchor_block = isset( $anchor_block['blockName'] ) && $anchor_block['blockName'] === 'core/navigation';
167
168                if ( $is_navigation_anchor_block ) {
169                    $class_name = ( ! empty( $hooked_block['attrs'] ) && ! empty( $hooked_block['attrs']['className'] ) )
170                        ? $hooked_block['attrs']['className'] . ' is-style-button'
171                        : 'is-style-button';
172
173                    $hooked_block['attrs']['className'] = $class_name;
174                    $hooked_block['attrs']['appSource'] = 'subscribe-block-navigation';
175                }
176
177                return $hooked_block;
178            },
179            10,
180            4
181        );
182    }
183
184    /**
185     * Handles Subscribe block placement at the end of each post.
186     *
187     * @return void
188     */
189    protected function handle_subscribe_block_post_end_placement() {
190        $subscribe_post_end_enabled = get_option( 'jetpack_subscriptions_subscribe_post_end_enabled', false );
191        if ( ! $subscribe_post_end_enabled ) {
192            return;
193        }
194
195        if ( ! wp_is_block_theme() ) { // Fallback for classic themes.
196            add_filter(
197                'the_content',
198                function ( $content ) {
199                    // Check if we're inside the main loop in a single Post.
200                    if (
201                        is_single() &&
202                        in_the_loop() &&
203                        is_main_query() &&
204                        $this->user_can_view_post()
205                    ) {
206                        // translators: %s is the name of the site.
207                        $discover_more_from_text = sprintf( __( 'Discover more from %s', 'jetpack' ), get_bloginfo( 'name' ) );
208                        $subscribe_text          = __( 'Subscribe to get the latest posts sent to your email.', 'jetpack' );
209
210                        return $content . do_blocks(
211                            <<<HTML
212<!-- wp:group {"style":{"spacing":{"padding":{"top":"0px","bottom":"0px","left":"0px","right":"0px"},"margin":{"top":"32px","bottom":"32px"}},"border":{"width":"0px","style":"none"}},"className":"has-border-color","layout":{"type":"default"}} -->
213<div class="wp-block-group has-border-color" style="border-style:none;border-width:0px;margin-top:32px;margin-bottom:32px;padding-top:0px;padding-right:0px;padding-bottom:0px;padding-left:0px">
214    <!-- wp:separator {"style":{"spacing":{"margin":{"bottom":"24px"}}},"className":"is-style-wide"} -->
215    <hr class="wp-block-separator has-alpha-channel-opacity is-style-wide" style="margin-bottom:24px"/>
216    <!-- /wp:separator -->
217
218    <!-- wp:heading {"textAlign":"center","level":3,"style":{"layout":{"selfStretch":"fit","flexSize":null},"spacing":{"margin":{"top":"4px","bottom":"10px"}}}} -->
219    <h3 class="wp-block-heading has-text-align-center" style="margin-top:4px;margin-bottom:10px">$discover_more_from_text</h3>
220    <!-- /wp:heading -->
221
222    <!-- wp:paragraph {"align":"center","style":{"typography":{"fontSize":"15px"},"spacing":{"margin":{"top":"10px","bottom":"10px"}}}} -->
223    <p class="has-text-align-center" style="margin-top:10px;margin-bottom:10px;font-size:15px">$subscribe_text</p>
224    <!-- /wp:paragraph -->
225
226    <!-- wp:group {"layout":{"type":"constrained","contentSize":"480px"}} -->
227    <div class="wp-block-group">
228        <!-- wp:jetpack/subscriptions {"appSource":"subscribe-block-post-end"} /-->
229    </div>
230    <!-- /wp:group -->
231</div>
232<!-- /wp:group -->
233HTML
234                        );
235                    }
236
237                    return $content;
238                },
239                100, // To insert it after the sharing blocks.
240                1
241            );
242
243            return;
244        }
245
246        add_filter(
247            'hooked_block_types',
248            function ( $hooked_blocks, $relative_position, $anchor_block, $context ) {
249                if (
250                    $anchor_block === 'core/post-content' &&
251                    $relative_position === 'after' &&
252                    self::is_single_post_context( $context ) &&
253                    $this->user_can_view_post()
254                ) {
255                    $hooked_blocks[] = 'jetpack/subscriptions';
256                }
257
258                return $hooked_blocks;
259            },
260            10,
261            4
262        );
263
264        add_filter(
265            'hooked_block_jetpack/subscriptions',
266            function ( $hooked_block, $hooked_block_type, $relative_position, $anchor_block ) {
267                $is_post_content_anchor_block = isset( $anchor_block['blockName'] ) && $anchor_block['blockName'] === 'core/post-content';
268                if ( $is_post_content_anchor_block && ( $relative_position === 'after' || $relative_position === 'before' ) ) {
269                    $attrs = $this->get_post_end_placement_block_attributes(
270                        array(
271                            'style' => array(
272                                'spacing' => array(
273                                    'margin'  => array(
274                                        'top'    => '48px',
275                                        'bottom' => '48px',
276                                    ),
277                                    'padding' => array(
278                                        'top'    => '5px',
279                                        'bottom' => '5px',
280                                    ),
281                                ),
282                            ),
283                        ),
284                        $anchor_block
285                    );
286
287                    // translators: %s is the name of the site.
288                    $discover_more_from_text = sprintf( __( 'Discover more from %s', 'jetpack' ), get_bloginfo( 'name' ) );
289                    $subscribe_text          = __( 'Subscribe to get the latest posts sent to your email.', 'jetpack' );
290                    $inner_content_begin     = <<<HTML
291<div class="wp-block-group" style="margin-top:48px;margin-bottom:48px;padding-top:5px;padding-bottom:5px">
292    <!-- wp:separator {"style":{"spacing":{"margin":{"bottom":"36px"}}},"className":"is-style-wide"} -->
293    <hr class="wp-block-separator has-alpha-channel-opacity is-style-wide" style="margin-bottom:36px"/>
294    <!-- /wp:separator -->
295
296    <!-- wp:heading {"textAlign":"center","level":3,"style":{"layout":{"selfStretch":"fit","flexSize":null},"spacing":{"margin":{"top":"4px","bottom":"10px"}}}} -->
297    <h3 class="wp-block-heading has-text-align-center" style="margin-top:4px;margin-bottom:10px">$discover_more_from_text</h3>
298    <!-- /wp:heading -->
299
300    <!-- wp:paragraph {"align":"center","style":{"typography":{"fontSize":"15px"},"spacing":{"margin":{"top":"4px","bottom":"0px"}}}} -->
301    <p class="has-text-align-center" style="margin-top:4px;margin-bottom:0px;font-size:15px">$subscribe_text</p>
302    <!-- /wp:paragraph -->
303
304    <!-- wp:group {"layout":{"type":"constrained","contentSize":"480px"}} -->
305    <div class="wp-block-group">
306HTML;
307                    $inner_content_end       = <<<'HTML'
308    </div>
309    <!-- /wp:group -->
310</div>
311HTML;
312
313                    $hooked_block['attrs']['appSource'] = 'subscribe-block-post-end';
314
315                    return array(
316                        'blockName'    => 'core/group',
317                        'attrs'        => $attrs,
318                        'innerBlocks'  => array( $hooked_block ),
319                        'innerContent' => array(
320                            $inner_content_begin,
321                            null,
322                            $inner_content_end,
323                        ),
324                    );
325                }
326
327                return $hooked_block;
328            },
329            10,
330            4
331        );
332    }
333}