Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.59% covered (success)
98.59%
70 / 71
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Sharing_Buttons_Markup
98.59% covered (success)
98.59%
70 / 71
83.33% covered (warning)
83.33%
5 / 6
28
0.00% covered (danger)
0.00%
0 / 1
 fill
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
1 / 1
5
 services
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
8
 remember_legacy_services
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 block_services
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
5.03
 legacy_button_style
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 labels
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Builds the Sharing Buttons block that gets hooked into templates.
4 *
5 * @package automattic/jetpack-sharing-likes
6 */
7
8declare( strict_types = 1 );
9
10namespace Automattic\Jetpack\Sharing_Likes\Hooked_Blocks;
11
12use Automattic\Jetpack\Sharing_Likes\Block_Names;
13use Automattic\Jetpack\Sharing_Likes\Settings\Environment;
14
15/**
16 * A `jetpack/sharing-buttons` block with its buttons, carried over from the
17 * site's legacy sharing services where the block offers them.
18 *
19 * @since 0.4.0
20 */
21final class Sharing_Buttons_Markup {
22
23    /**
24     * The parent block's markup around its buttons, as `sharing-buttons/save.jsx` saves it.
25     * The misspelt id is that save output; changing it would invalidate every saved block.
26     */
27    private const LIST_OPEN  = '<ul class="wp-block-jetpack-sharing-buttons has-normal-icon-size jetpack-sharing-buttons__services-list" id="jetpack-sharing-serivces-list">';
28    private const LIST_CLOSE = '</ul>';
29
30    /**
31     * The legacy services Simple had when it removed them all to switch to the block.
32     */
33    public const SAVED_SERVICES_OPTION = 'jetpack_sharing_buttons_legacy_services';
34
35    /**
36     * Buttons when the site has no legacy service the block offers.
37     */
38    private const DEFAULT_SERVICES = array( 'x', 'facebook', 'mastodon' );
39
40    /**
41     * Buttons on WordPress.com Simple when the site has no legacy service the block offers.
42     */
43    private const SIMPLE_DEFAULT_SERVICES = array( 'wordpress', 'x', 'facebook' ); // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText -- A service ID.
44
45    /**
46     * Legacy service IDs the block knows under another name.
47     */
48    private const LEGACY_ALIASES = array(
49        'email'            => 'mail',
50        'jetpack-whatsapp' => 'whatsapp',
51        'press-this'       => 'wordpress', // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText -- A service ID.
52        'twitter'          => 'x',
53    );
54
55    /**
56     * Legacy button styles the block styles too. It accepts `official` but has no styles for it.
57     */
58    private const STYLE_TYPES = array( 'icon-text', 'icon', 'text' );
59
60    /**
61     * Fill a hooked `jetpack/sharing-buttons` block with its buttons and markup.
62     *
63     * @param array $block The hooked block, in parsed block format.
64     * @return array
65     */
66    public static function fill( array $block ): array {
67        $labels   = self::labels();
68        $children = array();
69
70        foreach ( self::services() as $service ) {
71            $children[] = array(
72                'blockName'    => Block_Names::SHARING_BUTTON,
73                'attrs'        => array(
74                    'service' => $service,
75                    'label'   => $labels[ $service ],
76                ),
77                'innerBlocks'  => array(),
78                'innerHTML'    => '',
79                'innerContent' => array(),
80            );
81        }
82
83        $block['attrs'] = isset( $block['attrs'] ) && is_array( $block['attrs'] ) ? $block['attrs'] : array();
84
85        $style = self::legacy_button_style();
86        if ( '' !== $style ) {
87            $block['attrs']['styleType'] = $style;
88        }
89
90        $block['innerBlocks']  = $children;
91        $block['innerHTML']    = self::LIST_OPEN . self::LIST_CLOSE;
92        $block['innerContent'] = array_merge(
93            array( self::LIST_OPEN ),
94            array_fill( 0, count( $children ), null ),
95            array( self::LIST_CLOSE )
96        );
97
98        return $block;
99    }
100
101    /**
102     * The block's services for the legacy buttons the site shows, or showed before Simple removed
103     * them all; those behind "More" when none are visible; or the defaults.
104     *
105     * Reads the option rather than `Sharing_Service`, which may not be loaded, and saves defaults on read.
106     *
107     * @return string[]
108     */
109    public static function services(): array {
110        foreach ( array( get_option( 'sharing-services' ), get_option( self::SAVED_SERVICES_OPTION ) ) as $stored ) {
111            foreach ( array( 'visible', 'hidden' ) as $list ) {
112                $services = is_array( $stored ) && isset( $stored[ $list ] ) && is_array( $stored[ $list ] ) ? self::block_services( $stored[ $list ] ) : array();
113
114                if ( $services ) {
115                    return $services;
116                }
117            }
118        }
119
120        return Environment::is_simple_site() ? self::SIMPLE_DEFAULT_SERVICES : self::DEFAULT_SERVICES;
121    }
122
123    /**
124     * Keep the legacy services for the block before they are all removed. The one writer for the copy.
125     *
126     * A site that never saved any keeps no copy, so the block gets its own defaults.
127     */
128    public static function remember_legacy_services(): void {
129        $stored = get_option( 'sharing-services' );
130
131        if ( ! is_array( $stored ) || ( empty( $stored['visible'] ) && empty( $stored['hidden'] ) ) ) {
132            return;
133        }
134
135        update_option(
136            self::SAVED_SERVICES_OPTION,
137            array(
138                'visible' => is_array( $stored['visible'] ?? null ) ? $stored['visible'] : array(),
139                'hidden'  => is_array( $stored['hidden'] ?? null ) ? $stored['hidden'] : array(),
140            ),
141            false
142        );
143    }
144
145    /**
146     * The block's services for some legacy service IDs, dropping those it does not offer.
147     *
148     * @param array $legacy_ids Legacy service IDs.
149     * @return string[]
150     */
151    private static function block_services( array $legacy_ids ): array {
152        $labels   = self::labels();
153        $services = array();
154
155        foreach ( $legacy_ids as $legacy_id ) {
156            if ( ! is_string( $legacy_id ) ) {
157                continue;
158            }
159
160            $service = self::LEGACY_ALIASES[ $legacy_id ] ?? $legacy_id;
161
162            if ( isset( $labels[ $service ] ) && ! in_array( $service, $services, true ) ) {
163                $services[] = $service;
164            }
165        }
166
167        return $services;
168    }
169
170    /**
171     * The legacy button style, when the block accepts it.
172     */
173    private static function legacy_button_style(): string {
174        $options = get_option( 'sharing-options' );
175        $style   = is_array( $options ) ? ( $options['global']['button_style'] ?? '' ) : '';
176
177        return in_array( $style, self::STYLE_TYPES, true ) ? $style : '';
178    }
179
180    /**
181     * Button labels for the services carried over, as the block's variations set them.
182     *
183     * The block prints the label, falling back to the raw service slug.
184     *
185     * @return array<string, string>
186     */
187    private static function labels(): array {
188        return array(
189            'bluesky'   => 'Bluesky',
190            // translators: option to print the content - a verb labelling a button.
191            'print'     => __( 'Print', 'jetpack-sharing-likes' ),
192            'facebook'  => 'Facebook',
193            'linkedin'  => 'LinkedIn',
194            // translators: option to share the content by email - a verb labelling a button.
195            'mail'      => __( 'Mail', 'jetpack-sharing-likes' ),
196            'mastodon'  => 'Mastodon',
197            'pinterest' => 'Pinterest',
198            'reddit'    => 'Reddit',
199            'telegram'  => 'Telegram',
200            'threads'   => 'Threads',
201            'tumblr'    => 'Tumblr',
202            'whatsapp'  => 'WhatsApp',
203            'wordpress' => 'WordPress', // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText -- A service ID.
204            'x'         => 'X',
205            'nextdoor'  => 'Nextdoor',
206        );
207    }
208}