Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
100 / 100
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
Status_Controller
100.00% covered (success)
100.00%
100 / 100
100.00% covered (success)
100.00%
6 / 6
17
100.00% covered (success)
100.00%
1 / 1
 register_routes
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
1 / 1
1
 get_status
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
2
 managed_templates
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 switch_to_block
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 activate
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 refuse_unless
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2/**
3 * The section states behind Settings > Sharing, and the actions that change them.
4 *
5 * @package automattic/jetpack-sharing-likes
6 */
7
8declare( strict_types = 1 );
9
10namespace Automattic\Jetpack\Sharing_Likes\REST;
11
12use Automattic\Jetpack\Sharing_Likes\Block_Names;
13use Automattic\Jetpack\Sharing_Likes\Hooked_Blocks\Hooked_Blocks;
14use Automattic\Jetpack\Sharing_Likes\Settings\Environment;
15use Automattic\Jetpack\Sharing_Likes\Settings\Feature_Actions;
16use Automattic\Jetpack\Sharing_Likes\Settings\Likes_Section;
17use Automattic\Jetpack\Sharing_Likes\Settings\Placement_Section;
18use Automattic\Jetpack\Sharing_Likes\Settings\Section_State;
19use Automattic\Jetpack\Sharing_Likes\Settings\Sharing_Section;
20use WP_Error;
21use WP_REST_Request;
22use WP_REST_Response;
23use WP_REST_Server;
24
25/**
26 * Which variant each section renders, plus "Switch to the … block" and "Turn on".
27 *
28 * Each action is only accepted from the variant that offers it, and answers with
29 * the new status so the screen can re-render without asking again.
30 */
31final class Status_Controller extends Controller {
32
33    /**
34     * Register the routes.
35     */
36    public function register_routes() {
37        $base     = '/' . Endpoints::BASE;
38        $features = array( Placement_Section::FEATURE_SHARING, Placement_Section::FEATURE_LIKES );
39        $feature  = '(?P<feature>' . implode( '|', $features ) . ')';
40        // Route matching ignores case, and a body or query `feature` would outrank the URL's.
41        $args = array(
42            'feature' => array(
43                'type' => 'string',
44                'enum' => $features,
45            ),
46        );
47
48        register_rest_route(
49            $this->namespace,
50            $base . '/status',
51            array(
52                'methods'             => WP_REST_Server::READABLE,
53                'callback'            => array( $this, 'get_status' ),
54                'permission_callback' => array( $this, 'permission_check' ),
55            )
56        );
57
58        register_rest_route(
59            $this->namespace,
60            $base . '/' . $feature . '/switch-to-block',
61            array(
62                'methods'             => WP_REST_Server::CREATABLE,
63                'callback'            => array( $this, 'switch_to_block' ),
64                'permission_callback' => array( $this, 'permission_check' ),
65                'args'                => $args,
66            )
67        );
68
69        register_rest_route(
70            $this->namespace,
71            $base . '/' . $feature . '/activate',
72            array(
73                'methods'             => WP_REST_Server::CREATABLE,
74                'callback'            => array( $this, 'activate' ),
75                'permission_callback' => array( $this, 'permission_check' ),
76                'args'                => $args,
77            )
78        );
79    }
80
81    /**
82     * The variant each section renders.
83     *
84     * @return WP_REST_Response
85     */
86    public function get_status() {
87        $sharing_state        = Sharing_Section::state();
88        $likes_state          = Likes_Section::state();
89        $comment_likes_follow = Environment::comment_likes_follow_likes_settings();
90        $saved_templates      = array();
91
92        // Only the block route offers the template placements.
93        if ( in_array( Section_State::BLOCK_CALL_TO_ACTION, array( $sharing_state, $likes_state ), true ) ) {
94            $saved_templates = array(
95                'single' => Environment::saved_template_content( 'single' ),
96                'page'   => Environment::saved_template_content( 'page' ),
97            );
98        }
99
100        return rest_ensure_response(
101            array(
102                'sharing'                  => array(
103                    'state'             => $sharing_state,
104                    'managed_templates' => self::managed_templates( Block_Names::SHARING_BUTTONS, $saved_templates ),
105                ),
106                'likes'                    => array(
107                    'state'             => $likes_state,
108                    'supported'         => Environment::likes_supported(),
109                    'managed_templates' => self::managed_templates( Block_Names::LIKE, $saved_templates ),
110                ),
111                // No variants: comments have no block to move to.
112                'comment_likes'            => array(
113                    'supported'              => Environment::likes_supported(),
114                    'follows_likes_settings' => $comment_likes_follow,
115                ),
116                'placement'                => Section_State::shows_placement( $sharing_state, $likes_state, $comment_likes_follow ),
117                'site_editor_url'          => Environment::single_template_editor_url(),
118                'page_template_editor_url' => Environment::page_template_editor_url(),
119            )
120        );
121    }
122
123    /**
124     * The templates whose saved copy decides where a block goes, which the template placements no longer change.
125     *
126     * @param string                     $block           Block type.
127     * @param array<string, string|null> $saved_templates Saved content by template slug, null where the theme's file serves it.
128     * @return string[] Template slugs.
129     */
130    private static function managed_templates( string $block, array $saved_templates ): array {
131        $managed = array();
132
133        foreach ( $saved_templates as $slug => $content ) {
134            if ( null !== $content && Hooked_Blocks::is_managed_by_template( $content, $block ) ) {
135                $managed[] = $slug;
136            }
137        }
138
139        return $managed;
140    }
141
142    /**
143     * Hand a feature over to its block.
144     *
145     * @param WP_REST_Request $request Request.
146     * @return WP_REST_Response|WP_Error
147     */
148    public function switch_to_block( $request ) {
149        $feature = $request->get_url_params()['feature'];
150        $refused = self::refuse_unless( $feature, Section_State::CONFIGURE_WITH_BLOCK_NUDGE );
151
152        if ( $refused ) {
153            return $refused;
154        }
155
156        Feature_Actions::switch_to_block( $feature );
157
158        return $this->get_status();
159    }
160
161    /**
162     * Turn a feature's module back on.
163     *
164     * @param WP_REST_Request $request Request.
165     * @return WP_REST_Response|WP_Error
166     */
167    public function activate( $request ) {
168        $feature = $request->get_url_params()['feature'];
169        $refused = self::refuse_unless( $feature, Section_State::OFF );
170
171        if ( $refused ) {
172            return $refused;
173        }
174
175        if ( ! Feature_Actions::activate( $feature ) ) {
176            return new WP_Error(
177                'rest_sharing_likes_activation_failed',
178                __( 'The feature could not be turned on.', 'jetpack-sharing-likes' ),
179                array( 'status' => 409 )
180            );
181        }
182
183        return $this->get_status();
184    }
185
186    /**
187     * An error unless the feature's section renders the variant that offers the action.
188     *
189     * @param string $feature One of the `Placement_Section::FEATURE_*` constants.
190     * @param string $state   The `Section_State` constant that offers the action.
191     * @return WP_Error|null
192     */
193    private static function refuse_unless( string $feature, string $state ): ?WP_Error {
194        $is_likes = Placement_Section::FEATURE_LIKES === $feature;
195
196        // Offline mode keeps the screen, but loads no Likes module for the section to act on.
197        if ( $is_likes && ! Environment::likes_supported() ) {
198            $current = null;
199        } else {
200            $current = $is_likes ? Likes_Section::state() : Sharing_Section::state();
201        }
202
203        if ( $state === $current ) {
204            return null;
205        }
206
207        return new WP_Error(
208            'rest_sharing_likes_action_unavailable',
209            __( 'This action is not available on this site right now.', 'jetpack-sharing-likes' ),
210            array( 'status' => 409 )
211        );
212    }
213}