Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
74.32% covered (warning)
74.32%
55 / 74
66.67% covered (warning)
66.67%
4 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Settings_Page
74.32% covered (warning)
74.32%
55 / 74
66.67% covered (warning)
66.67%
4 / 6
21.89
0.00% covered (danger)
0.00%
0 / 1
 init
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 register_menu
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 render_sections
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
7
 render_saved_notice
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 render_multibyte_warning
10.53% covered (danger)
10.53%
2 / 19
0.00% covered (danger)
0.00%
0 / 1
4.87
 render
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * The Settings > Sharing screen.
4 *
5 * @package automattic/jetpack-sharing-likes
6 */
7
8declare( strict_types = 1 );
9
10namespace Automattic\Jetpack\Sharing_Likes\Settings;
11
12use Automattic\Jetpack\Sharing_Likes\Settings_App\Settings_App;
13
14/**
15 * Registers Settings > Sharing and renders its sections.
16 *
17 * Registration does not depend on any module being active, so the screen and
18 * every section on it exist whatever the site is running, as long as it can
19 * run them at all: see `Environment::settings_screen_supported()`.
20 */
21final class Settings_Page {
22
23    /**
24     * Submenu slug. Unchanged from when sharedaddy registered this screen, so
25     * existing links and bookmarks keep working.
26     */
27    public const SLUG = 'sharing';
28
29    /**
30     * Query argument a save adds when the Comment Likes switch did not take.
31     */
32    public const COMMENT_LIKES_UNCHANGED = 'comment-likes-unchanged';
33
34    /**
35     * Hook the screen up.
36     */
37    public static function init(): void {
38        add_action( 'admin_menu', array( __CLASS__, 'register_menu' ) );
39        Settings_App::init();
40    }
41
42    /**
43     * Add the submenu entry under Settings.
44     */
45    public static function register_menu(): void {
46        if ( ! Environment::settings_screen_supported() ) {
47            return;
48        }
49
50        add_submenu_page(
51            'options-general.php',
52            __( 'Sharing Settings', 'jetpack-sharing-likes' ),
53            __( 'Sharing', 'jetpack-sharing-likes' ),
54            'manage_options',
55            self::SLUG,
56            Settings_App::render_callback() ?? array( __CLASS__, 'render' )
57        );
58    }
59
60    /**
61     * Render whichever sections apply, ruled off from one another, then the
62     * one Save button that covers them all.
63     */
64    private static function render_sections(): void {
65        // The submenu registers against this too. Repeated here because the sections
66        // mint the nonces that authorise every save, and the screen is public API.
67        if ( ! current_user_can( 'manage_options' ) ) {
68            return;
69        }
70
71        $sections = array(
72            array( Sharing_Section::class, 'render' ),
73            array( Likes_Section::class, 'render' ),
74            array( Comment_Likes_Section::class, 'render' ),
75        );
76
77        $sharing_state = Sharing_Section::state();
78
79        if ( Section_State::shows_placement( $sharing_state, Likes_Section::state(), Environment::comment_likes_follow_likes_settings() ) ) {
80            $sections[] = array( Placement_Section::class, 'render' );
81        }
82
83        if ( ! Section_State::configures( $sharing_state ) ) {
84            $sections[] = array( Extras_Section::class, 'render' );
85        }
86
87        $rendered = 0;
88
89        foreach ( $sections as $section ) {
90            ob_start();
91            call_user_func( $section );
92            $markup = trim( (string) ob_get_clean() );
93
94            // A section can decline to render, and a rule with nothing after it is worse than no rule.
95            if ( '' === $markup ) {
96                continue;
97            }
98
99            if ( $rendered > 0 ) {
100                echo '<hr />';
101            }
102
103            echo $markup; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- each section escapes its own output.
104            ++$rendered;
105        }
106
107        Settings_Form::render();
108    }
109
110    /**
111     * Confirm a save, when one just happened.
112     */
113    private static function render_saved_notice(): void {
114        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- only decides whether to print a confirmation.
115        if ( ! isset( $_GET['update'] ) || 'saved' !== $_GET['update'] ) {
116            return;
117        }
118
119        printf(
120            '<div class="updated"><p>%s</p></div>',
121            esc_html__( 'Settings have been saved', 'jetpack-sharing-likes' )
122        );
123
124        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- only decides whether to print a warning.
125        if ( isset( $_GET[ self::COMMENT_LIKES_UNCHANGED ] ) ) {
126            printf(
127                '<div class="error"><p>%s</p></div>',
128                esc_html__( 'Comment Likes could not be switched on or off on this site.', 'jetpack-sharing-likes' )
129            );
130        }
131    }
132
133    /**
134     * Warn when PHP has no mbstring extension.
135     *
136     * Sharing sources fall back to byte-wise string functions without it, which
137     * can cut multibyte text mid-character when a service truncates it.
138     */
139    private static function render_multibyte_warning(): void {
140        if ( function_exists( 'mb_stripos' ) ) {
141            return;
142        }
143
144        printf(
145            '<div class="notice notice-warning"><p><strong>%1$s</strong></p><p>%2$s</p></div>',
146            esc_html__( 'Warning! Multibyte support missing!', 'jetpack-sharing-likes' ),
147            wp_kses(
148                sprintf(
149                    /* translators: placeholder is a link to a PHP support document. */
150                    __( 'This plugin will work without it, but multibyte support is used <a href="%s" rel="noopener noreferrer" target="_blank">if available</a>. You may see minor problems with Tweets and other sharing services.', 'jetpack-sharing-likes' ),
151                    'https://www.php.net/manual/en/mbstring.installation.php'
152                ),
153                array(
154                    'a' => array(
155                        'href'   => array(),
156                        'rel'    => array(),
157                        'target' => array(),
158                    ),
159                )
160            )
161        );
162    }
163
164    /**
165     * Render the screen.
166     */
167    public static function render(): void {
168        ?>
169        <div class="wrap">
170            <h1><?php esc_html_e( 'Sharing Settings', 'jetpack-sharing-likes' ); ?></h1>
171            <?php
172            self::render_multibyte_warning();
173            self::render_saved_notice();
174
175            /**
176             * Fires at the top of the admin sharing settings screen.
177             *
178             * @module sharedaddy
179             *
180             * @since jetpack-1.6.0
181             */
182            do_action( 'pre_admin_screen_sharing' );
183
184            self::render_sections();
185            ?>
186        </div>
187        <?php
188    }
189}