Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
44.09% covered (danger)
44.09%
41 / 93
42.86% covered (danger)
42.86%
3 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
Placement_Section
44.09% covered (danger)
44.09%
41 / 93
42.86% covered (danger)
42.86%
3 / 7
67.52
0.00% covered (danger)
0.00%
0 / 1
 render_summary
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
2
 render
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
6
 heading
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
2
 selected_post_types
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 normalize_show
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 default_post_types
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
 label_for
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
1<?php
2/**
3 * The shared placement section of Settings > Sharing.
4 *
5 * @package automattic/jetpack-sharing-likes
6 */
7
8declare( strict_types = 1 );
9
10namespace Automattic\Jetpack\Sharing_Likes\Settings;
11
12/**
13 * Renders "Show buttons on", which governs where sharing buttons, Like buttons,
14 * and Jetpack's Comment Likes appear. Its own section because it belongs to no feature alone.
15 */
16final class Placement_Section {
17
18    /**
19     * Anchor the feature sections link to.
20     */
21    public const ANCHOR = 'jetpack-sharing-placement';
22
23    /**
24     * Identifies the Sharing buttons section to `render_summary()`.
25     */
26    public const FEATURE_SHARING = 'sharing';
27
28    /**
29     * Identifies the Like buttons section to `render_summary()`.
30     */
31    public const FEATURE_LIKES = 'likes';
32
33    /**
34     * Identifies the Comment Likes section to `render_summary()`.
35     */
36    public const FEATURE_COMMENT_LIKES = 'comment-likes';
37
38    /**
39     * Where a feature's buttons currently appear, stated inside that feature's
40     * own section.
41     *
42     * This setting governs several features but lives in none, so each section
43     * says what it means for that feature and links here to change it. It also
44     * surfaces a placement that hides the buttons entirely, which is otherwise
45     * only visible on this section further down the page.
46     *
47     * Each feature gets a complete sentence: interpolating the feature name into
48     * a shared one does not translate.
49     *
50     * @param string $feature One of the FEATURE_* constants.
51     */
52    public static function render_summary( string $feature ): void {
53        $labels = array_map( array( __CLASS__, 'label_for' ), self::selected_post_types() );
54
55        if ( $labels === array() ) {
56            $nowhere = array(
57                self::FEATURE_SHARING       => __( 'Sharing buttons are currently not shown anywhere.', 'jetpack-sharing-likes' ),
58                self::FEATURE_LIKES         => __( 'Like buttons are currently not shown anywhere.', 'jetpack-sharing-likes' ),
59                self::FEATURE_COMMENT_LIKES => __( 'Comment Likes are currently not shown anywhere.', 'jetpack-sharing-likes' ),
60            );
61            $summary = $nowhere[ $feature ] ?? $nowhere[ self::FEATURE_SHARING ];
62        } else {
63            $somewhere = array(
64                /* translators: %s: comma-separated list of places, for example "Posts, Pages". */
65                self::FEATURE_SHARING       => __( 'Sharing buttons currently appear on: %s.', 'jetpack-sharing-likes' ),
66                /* translators: %s: comma-separated list of places, for example "Posts, Pages". */
67                self::FEATURE_LIKES         => __( 'Like buttons currently appear on: %s.', 'jetpack-sharing-likes' ),
68                /* translators: %s: comma-separated list of places, for example "Posts, Pages". */
69                self::FEATURE_COMMENT_LIKES => __( 'Comment Likes currently appear on comments on: %s.', 'jetpack-sharing-likes' ),
70            );
71            $summary   = sprintf( $somewhere[ $feature ] ?? $somewhere[ self::FEATURE_SHARING ], implode( ', ', $labels ) );
72        }
73
74        printf(
75            '<p class="description">%1$s <a href="#%2$s">%3$s</a></p>',
76            esc_html( $summary ),
77            esc_attr( self::ANCHOR ),
78            esc_html__( 'Change where they appear', 'jetpack-sharing-likes' )
79        );
80    }
81
82    /**
83     * Render the section.
84     */
85    public static function render(): void {
86        $shown = self::selected_post_types();
87
88        $choices = array_values( get_post_types( array( 'public' => true ) ) );
89        array_unshift( $choices, 'index' );
90        ?>
91        <div class="jetpack-sharing-settings__section" id="<?php echo esc_attr( self::ANCHOR ); ?>">
92            <h2><?php echo esc_html( self::heading() ); ?></h2>
93            <?php ob_start(); ?>
94                <table class="form-table">
95                    <tbody>
96                    <?php
97                    /**
98                     * Filters the HTML at the beginning of the "Show button on" row.
99                     *
100                     * @module sharedaddy
101                     *
102                     * @since jetpack-2.1.0
103                     *
104                     * @param string $var Opening HTML tag at the beginning of the "Show button on" row.
105                     */
106                    echo apply_filters( 'sharing_show_buttons_on_row_start', '<tr valign="top">' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
107                    ?>
108                        <th scope="row"></th>
109                        <td>
110                            <fieldset>
111                                <legend class="screen-reader-text"><span><?php echo esc_html( self::heading() ); ?></span></legend>
112                                <?php foreach ( $choices as $choice ) : ?>
113                                    <label>
114                                        <input type="checkbox" name="show[]" value="<?php echo esc_attr( $choice ); ?>" <?php checked( in_array( $choice, $shown, true ) ); ?> />
115                                        <?php echo esc_html( self::label_for( $choice ) ); ?>
116                                    </label><br />
117                                <?php endforeach; ?>
118                            </fieldset>
119                        </td>
120                    <?php
121                    /**
122                     * Filters the HTML at the end of the "Show button on" row.
123                     *
124                     * @module sharedaddy
125                     *
126                     * @since jetpack-2.1.0
127                     *
128                     * @param string $var Closing HTML tag at the end of the "Show button on" row.
129                     */
130                    echo apply_filters( 'sharing_show_buttons_on_row_end', '</tr>' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
131                    ?>
132                    </tbody>
133                </table>
134            <?php Settings_Form::render_fields( Settings_Form::SECTION_PLACEMENT, (string) ob_get_clean() ); ?>
135        </div>
136        <?php
137    }
138
139    /**
140     * Heading naming the features this section governs.
141     *
142     * "Show buttons on" sat directly below the Like buttons section and read as
143     * though it belonged to it. Naming the features makes the shared scope
144     * legible without a second line of copy.
145     */
146    private static function heading(): string {
147        $key = implode(
148            '+',
149            array_keys(
150                array_filter(
151                    array(
152                        'sharing'  => Section_State::configures( Sharing_Section::state() ),
153                        'likes'    => Section_State::configures( Likes_Section::state() ),
154                        'comments' => Environment::comment_likes_follow_likes_settings(),
155                    )
156                )
157            )
158        );
159
160        $headings = array(
161            'sharing+likes+comments' => __( 'Where sharing buttons, Like buttons, and Comment Likes appear', 'jetpack-sharing-likes' ),
162            'sharing+likes'          => __( 'Where sharing and Like buttons appear', 'jetpack-sharing-likes' ),
163            'sharing+comments'       => __( 'Where sharing buttons and Comment Likes appear', 'jetpack-sharing-likes' ),
164            'likes+comments'         => __( 'Where Like buttons and Comment Likes appear', 'jetpack-sharing-likes' ),
165            'likes'                  => __( 'Where Like buttons appear', 'jetpack-sharing-likes' ),
166            'comments'               => __( 'Where Comment Likes appear', 'jetpack-sharing-likes' ),
167        );
168
169        return $headings[ $key ] ?? __( 'Where sharing buttons appear', 'jetpack-sharing-likes' );
170    }
171
172    /**
173     * Post types currently set to show buttons.
174     *
175     * Falls back to the same defaults the features themselves apply when the
176     * option has never been saved. Reading it raw would render every checkbox
177     * unchecked on a site where the buttons are in fact live, and the next save
178     * would then write that back and turn them off.
179     *
180     * @return string[]
181     */
182    public static function selected_post_types(): array {
183        $sharing = get_option( 'sharing-options', array() );
184
185        if ( ! is_array( $sharing ) || ! isset( $sharing['global']['show'] ) ) {
186            return self::default_post_types();
187        }
188
189        return self::normalize_show( $sharing['global']['show'] );
190    }
191
192    /**
193     * A stored `show` value as a list of post types.
194     *
195     * Older sites stored a single keyword rather than a list, and both
196     * `Sharing_Service::get_global_options()` and `Jetpack_Likes_Settings::get_options()`
197     * still map it, so it is live data rather than a historical curiosity.
198     *
199     * @param mixed $shown Stored `sharing-options['global']['show']` value.
200     * @return string[]
201     */
202    public static function normalize_show( $shown ): array {
203        if ( is_scalar( $shown ) ) {
204            $legacy = array(
205                'posts'       => array( 'post', 'page' ),
206                'index'       => array( 'index' ),
207                'posts-index' => array( 'post', 'page', 'index' ),
208            );
209            $shown  = $legacy[ $shown ] ?? array();
210        }
211
212        return array_values( array_filter( (array) $shown, 'is_string' ) );
213    }
214
215    /**
216     * Where buttons appear on a site that has never saved this section.
217     *
218     * The two features apply different defaults:
219     * - sharing: posts and pages;
220     * - Likes: those, plus public post types that support comments.
221     *
222     * Reporting the narrower set would understate where Like buttons are, so
223     * defer to Likes whenever it is the feature running.
224     *
225     * @return string[]
226     */
227    private static function default_post_types(): array {
228        $defaults = array( 'post', 'page' );
229
230        if ( ! Environment::likes_settings_in_use() ) {
231            return $defaults;
232        }
233
234        if ( ! class_exists( 'Jetpack_Likes_Settings' ) ) {
235            return $defaults;
236        }
237
238        $options = ( new \Jetpack_Likes_Settings() )->get_options();
239
240        return isset( $options['show'] ) ? self::normalize_show( $options['show'] ) : $defaults;
241    }
242
243    /**
244     * Human-readable label for a post type choice.
245     *
246     * @param string $choice Post type slug, or 'index' for the archive pages.
247     */
248    private static function label_for( string $choice ): string {
249        if ( 'index' === $choice ) {
250            return __( 'Front Page, Archive Pages, and Search Results', 'jetpack-sharing-likes' );
251        }
252
253        $post_type = get_post_type_object( $choice );
254
255        return $post_type instanceof \WP_Post_Type ? $post_type->labels->name : $choice;
256    }
257}