Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 179
0.00% covered (danger)
0.00%
0 / 12
CRAP
n/a
0 / 0
wpcom_normalize_site_logo_id
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
30
wpcom_site_logo_edit_link
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
42
wpcom_site_logo_edit_description
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
12
wpcom_fiverr_logo
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
wpcom_fiverr_cta_button
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
wpcom_fiverr_cta
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 1
42
wpcom_fiverr
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
wpcom_site_management_panel_link
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
wpcom_site_management_panel
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
wpcom_options_general_get_site_domains
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
132
wpcom_options_general_get_primary_domain_candidates
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
156
wpcom_enqueue_options_general_assets
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Override /wp-admin/options-general.php
4 *
5 * @package automattic/jetpack-mu-wpcom
6 */
7
8use Automattic\Jetpack\Connection\Client;
9use Automattic\Jetpack\Constants;
10use Automattic\Jetpack\Jetpack_Mu_Wpcom;
11
12/**
13 * Normalize a site logo value to a positive attachment ID.
14 *
15 * The value may hold an attachment ID, a legacy `array( 'id' => ... )`, or a WP_Error,
16 * so anything that is not a positive scalar resolves to 0.
17 *
18 * @param mixed $value The raw site_logo option or custom_logo theme mod value.
19 * @return int A positive attachment ID, or 0 when none is usable.
20 */
21function wpcom_normalize_site_logo_id( $value ) {
22    if ( is_array( $value ) && isset( $value['id'] ) ) {
23        $value = $value['id'];
24    }
25    return ( is_scalar( $value ) && (int) $value > 0 ) ? (int) $value : 0;
26}
27
28/**
29 * Resolve the URL for editing the current site logo.
30 *
31 * Block themes use the Site Editor's Identity screen; classic themes use the
32 * Customizer control matching their logo support (mirroring the logo-tool feature).
33 * The URL is empty when no reliable destination exists, so callers can omit the link.
34 *
35 * @return array{url:string, is_block:bool} The edit URL and block-theme flag.
36 */
37function wpcom_site_logo_edit_link() {
38    if ( wp_is_block_theme() ) {
39        // The Identity screen shipped in Gutenberg 22.8 / WordPress 7.1; omit the
40        // link when it is unavailable.
41        $has_identity = defined( 'GUTENBERG_VERSION' )
42            ? version_compare( GUTENBERG_VERSION, '22.8', '>=' )
43            : version_compare( get_bloginfo( 'version' ), '7.1', '>=' );
44
45        return array(
46            'url'      => $has_identity ? admin_url( 'site-editor.php?p=%2Fidentity' ) : '',
47            'is_block' => true,
48        );
49    }
50
51    if ( current_theme_supports( 'custom-logo' ) ) {
52        $url = admin_url( 'customize.php?autofocus[control]=custom_logo' );
53    } elseif ( current_theme_supports( 'site-logo' ) ) {
54        $url = admin_url( 'customize.php?autofocus[control]=site_logo' );
55    } else {
56        $url = '';
57    }
58
59    return array(
60        'url'      => $url,
61        'is_block' => false,
62    );
63}
64
65/**
66 * The description pointing at where the site logo can be set.
67 *
68 * Rendered whether or not a logo exists, so the empty state still offers a way
69 * to upload one. That is the case where someone is most likely looking for it.
70 * The wording says "set" so it reads correctly for both adding and replacing.
71 */
72function wpcom_site_logo_edit_description() {
73    $logo_edit_link = wpcom_site_logo_edit_link();
74
75    // Only promise an edit path when a known destination exists.
76    if ( ! $logo_edit_link['url'] ) {
77        return;
78    }
79    ?>
80    <p class="description wpcom-site-logo-description">
81        <?php
82        if ( $logo_edit_link['is_block'] ) {
83            printf(
84                /* translators: %1$s: opening link tag to the Site Editor, %2$s: closing link tag. */
85                esc_html__( 'Displays in your site\'s layout via the Site Logo block. %1$sYou can set your site logo in the site editor%2$s.', 'jetpack-mu-wpcom' ),
86                '<a href="' . esc_url( $logo_edit_link['url'] ) . '">',
87                '</a>'
88            );
89        } else {
90            printf(
91                /* translators: %1$s: opening link tag to the Customizer, %2$s: closing link tag. */
92                esc_html__( '%1$sYou can set your site logo in the Customizer%2$s.', 'jetpack-mu-wpcom' ),
93                '<a href="' . esc_url( $logo_edit_link['url'] ) . '">',
94                '</a>'
95            );
96        }
97        ?>
98    </p>
99    <?php
100}
101
102/**
103 * The Fiverr logo mark.
104 *
105 * Decorative in every current use, since the adjacent text always names Fiverr.
106 */
107function wpcom_fiverr_logo() {
108    ?>
109    <svg class="wpcom-fiverr-cta-logo" width="20" height="20" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
110        <circle cx="250" cy="250" r="177" fill="white"/>
111        <path d="M500 250C500 111.93 388.07 0 250 0C111.93 0 0 111.93 0 250C0 388.07 111.93 500 250 500C388.07 500 500 388.07 500 250ZM360.42 382.5H294.77V237.2H231.94V382.5H165.9V237.2H128.45V183.45H165.9V167.13C165.9 124.54 198.12 95.48 246.05 95.48H294.78V149.22H256.93C241.62 149.22 231.95 157.58 231.95 171.12V183.45H360.43V382.5H360.42Z" fill="#1DBF73"/>
112    </svg>
113    <?php
114}
115
116/**
117 * The Fiverr logo-maker CTA button's DOM.
118 */
119function wpcom_fiverr_cta_button() {
120    ?>
121    <button class="wpcom-fiverr-cta-button button" type="button">
122        <?php wpcom_fiverr_logo(); ?>
123        <?php esc_html_e( 'Try Fiverr Logo Maker', 'jetpack-mu-wpcom' ); ?>
124    </button>
125    <?php
126}
127
128/**
129 * The fiverr cta's DOM.
130 */
131function wpcom_fiverr_cta() {
132    // Core keeps the site_logo option and custom_logo theme mod in sync; read either.
133    $logo_id = wpcom_normalize_site_logo_id( get_option( 'site_logo' ) );
134    if ( ! $logo_id ) {
135        $logo_id = wpcom_normalize_site_logo_id( get_theme_mod( 'custom_logo' ) );
136    }
137
138    $logo_img = '';
139    if ( $logo_id ) {
140        // Fall back to the site title when the logo has no alt text, so the preview
141        // is still announced to assistive technology.
142        $logo_alt = trim( wp_strip_all_tags( (string) get_post_meta( $logo_id, '_wp_attachment_image_alt', true ) ) );
143        if ( '' === $logo_alt ) {
144            $site_name = get_bloginfo( 'name' );
145            $logo_alt  = $site_name
146                /* translators: %s: The site title. */
147                ? sprintf( __( 'Current site logo for %s', 'jetpack-mu-wpcom' ), $site_name )
148                : __( 'Current site logo', 'jetpack-mu-wpcom' );
149        }
150        // Request a proportional size, not a square, so wide logos aren't shown as a
151        // center-cropped thumbnail; CSS caps the dimensions.
152        $logo_img = wp_get_attachment_image(
153            $logo_id,
154            'medium',
155            false,
156            array(
157                'class' => 'wpcom-site-logo-preview-image',
158                'alt'   => $logo_alt,
159            )
160        );
161    }
162    ?>
163    <div id="wpcom-fiverr-cta">
164        <?php if ( $logo_img ) : ?>
165            <div class="wpcom-site-logo">
166                <div class="wpcom-site-logo-preview">
167                    <?php echo $logo_img; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_get_attachment_image() returns escaped markup. ?>
168                </div>
169                <?php wpcom_fiverr_cta_button(); ?>
170            </div>
171        <?php else : ?>
172            <div class="wpcom-fiverr-cta-box">
173                <?php wpcom_fiverr_logo(); ?>
174                <div class="wpcom-fiverr-cta-description">
175                    <p>
176                        <?php
177                        printf(
178                            /* translators: %1$s: opening link tag to the Fiverr logo maker, %2$s: closing link tag. */
179                            esc_html__( 'No logo yet? Make one in minutes with %1$sFiverr Logo Maker%2$s.', 'jetpack-mu-wpcom' ),
180                            '<a class="wpcom-fiverr-cta-link" href="' . esc_url( 'https://wp.me/logo-maker/?utm_campaign=general_settings' ) . '" target="_blank" rel="noopener noreferrer">',
181                            '</a>'
182                        );
183                        ?>
184                    </p>
185                    <p><?php esc_html_e( 'Pre-designed by top talent. Just add your touch.', 'jetpack-mu-wpcom' ); ?></p>
186                </div>
187            </div>
188        <?php endif; ?>
189    </div>
190    <?php
191    // Sits outside the Fiverr CTA: it describes the Site Logo setting itself,
192    // not the upsell.
193    wpcom_site_logo_edit_description();
194}
195
196/**
197 * Add the fiverr cta to the general settings page.
198 */
199function wpcom_fiverr() {
200    add_settings_field( 'wpcom_fiverr_cta', __( 'Site Logo', 'jetpack-mu-wpcom' ), 'wpcom_fiverr_cta', 'general', 'default' );
201}
202add_action( 'load-options-general.php', 'wpcom_fiverr' );
203
204/**
205 * DOM for the link to the Site Management Panel on WordPress.com.
206 */
207function wpcom_site_management_panel_link() {
208    ?>
209    <a href="https://my.wordpress.com/sites/<?php echo esc_attr( wpcom_get_site_slug() ); ?>">
210        <?php esc_html_e( 'Manage site ownership and other site tools on WordPress.com ↗', 'jetpack-mu-wpcom' ); ?>
211    </a>
212    <?php
213}
214
215/**
216 * Add the link to the Site Management Panel on WordPress.com to the general settings page.
217 */
218function wpcom_site_management_panel() {
219    $current_screen = wpcom_admin_get_current_screen();
220    if ( in_array( $current_screen, WPCOM_DUPLICATED_VIEW, true ) ) {
221        add_settings_field( 'wpcom_site_management_panel_link', __( 'WordPress.com Site Settings', 'jetpack-mu-wpcom' ), 'wpcom_site_management_panel_link', 'general' );
222    }
223}
224add_action( 'load-options-general.php', 'wpcom_site_management_panel' );
225
226/**
227 * Get the site's domains, from the domains table on Simple and from the WordPress.com API on Atomic.
228 *
229 * @return array<int,object>|null Domain objects shaped like /sites/{id}/domains, or null if the lookup failed.
230 */
231function wpcom_options_general_get_site_domains() {
232    if ( Constants::is_true( 'IS_WPCOM' ) ) {
233        if ( ! class_exists( 'Domain_Management' ) && file_exists( WP_CONTENT_DIR . '/admin-plugins/domains/management.php' ) ) {
234            require_once WP_CONTENT_DIR . '/admin-plugins/domains/management.php';
235        }
236        if ( ! class_exists( 'Domain_Management' ) ) {
237            return null;
238        }
239        return \Domain_Management::get_domains_for_current_blog_light_load();
240    }
241
242    $site_id = \Jetpack_Options::get_option( 'id' );
243    if ( ! $site_id ) {
244        return null;
245    }
246
247    $response = Client::wpcom_json_api_request_as_blog(
248        sprintf( '/sites/%d/domains', (int) $site_id ),
249        '1.2',
250        array(
251            'method'  => 'GET',
252            // A slow answer must not become a slow settings page.
253            'timeout' => 5,
254        ),
255        null,
256        'rest'
257    );
258    if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
259        return null;
260    }
261
262    $body = json_decode( wp_remote_retrieve_body( $response ) );
263    if ( ! is_object( $body ) || ! isset( $body->domains ) || ! is_array( $body->domains ) ) {
264        return null;
265    }
266
267    return $body->domains;
268}
269
270/**
271 * Get the custom domains that could become the site address of a site still on its WordPress.com address.
272 *
273 * Covers a domain bought during setup that never became primary. A site that already
274 * uses a custom domain gets an empty list, so it keeps the "Add custom address" button.
275 *
276 * @return string[] Domain names.
277 */
278function wpcom_options_general_get_primary_domain_candidates() {
279    $domains = wpcom_options_general_get_site_domains();
280    if ( ! is_array( $domains ) ) {
281        return array();
282    }
283
284    $candidates = array();
285    foreach ( $domains as $domain ) {
286        if ( empty( $domain->domain ) || ! is_string( $domain->domain ) ) {
287            continue;
288        }
289
290        $is_wpcom_provided = ! empty( $domain->wpcom_domain ) || ! empty( $domain->is_wpcom_staging_domain );
291
292        if ( ! empty( $domain->primary_domain ) ) {
293            if ( ! $is_wpcom_provided ) {
294                return array();
295            }
296            continue;
297        }
298
299        if (
300            $is_wpcom_provided ||
301            empty( $domain->can_set_as_primary ) ||
302            ! empty( $domain->expired ) ||
303            ! empty( $domain->set_primary_domain_pending )
304        ) {
305            continue;
306        }
307
308        $candidates[] = $domain->domain;
309    }
310
311    return $candidates;
312}
313
314/**
315 * Display the site URL in General Settings on Simple Classic sites.
316 *
317 * @param string $hook_suffix The current admin page.
318 */
319function wpcom_enqueue_options_general_assets( $hook_suffix = '' ) {
320    $asset_file = include Jetpack_Mu_Wpcom::BASE_DIR . 'build/wpcom-options-general/wpcom-options-general.asset.php';
321
322    wp_enqueue_style(
323        'wpcom-options-general',
324        plugins_url( 'build/wpcom-options-general/wpcom-options-general.css', Jetpack_Mu_Wpcom::BASE_FILE ),
325        array(),
326        $asset_file['version'] ?? filemtime( Jetpack_Mu_Wpcom::BASE_DIR . 'build/wpcom-options-general/wpcom-options-general.css' )
327    );
328
329    wp_enqueue_script(
330        'wpcom-options-general',
331        plugins_url( 'build/wpcom-options-general/wpcom-options-general.js', Jetpack_Mu_Wpcom::BASE_FILE ),
332        $asset_file['dependencies'] ?? array(),
333        $asset_file['version'] ?? filemtime( Jetpack_Mu_Wpcom::BASE_DIR . 'build/wpcom-options-general/wpcom-options-general.js' ),
334        true
335    );
336    wp_set_script_translations( 'wpcom-options-general', 'jetpack-mu-wpcom' );
337    $site_slug           = wpcom_get_site_slug();
338    $options_general_url = admin_url( 'options-general.php' );
339    wp_add_inline_script(
340        'wpcom-options-general',
341        'window.wpcomSiteUrl = ' . wp_json_encode(
342            array(
343                'siteUrl'                 => get_site_url(),
344                'homeUrl'                 => home_url(),
345                'siteSlug'                => $site_slug,
346                'optionsGeneralUrl'       => $options_general_url,
347                // Only looked up on Settings > General, the one page that uses it.
348                'primaryDomainCandidates' => 'options-general.php' === $hook_suffix ? wpcom_options_general_get_primary_domain_candidates() : array(),
349            ),
350            JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP
351        ) . ';',
352        'before'
353    );
354}
355add_action( 'admin_enqueue_scripts', 'wpcom_enqueue_options_general_assets' );