Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
64.04% covered (warning)
64.04%
73 / 114
25.00% covered (danger)
25.00%
1 / 4
CRAP
n/a
0 / 0
Automattic\Jetpack\Extensions\Sharing_Button_Block\register_block
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
Automattic\Jetpack\Extensions\Sharing_Button_Block\render_block
89.33% covered (warning)
89.33%
67 / 75
0.00% covered (danger)
0.00%
0 / 1
11.15
Automattic\Jetpack\Extensions\Sharing_Button_Block\get_services
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
2
Automattic\Jetpack\Extensions\Sharing_Button_Block\sharing_process_requests
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2/**
3 * Sharing Buttons Block.
4 *
5 * @since 13.1
6 *
7 * @package automattic/jetpack
8 */
9
10namespace Automattic\Jetpack\Extensions\Sharing_Button_Block;
11
12use Automattic\Jetpack\Blocks;
13use Automattic\Jetpack\Sharing_Likes\Hooked_Blocks\Hooked_Blocks;
14use Automattic\Jetpack\Status\Host;
15use Jetpack_Gutenberg;
16
17if ( ! defined( 'ABSPATH' ) ) {
18    exit( 0 );
19}
20
21require_once __DIR__ . '/class-sharing-source-block.php';
22require_once __DIR__ . '/components/social-icons.php';
23
24const PARENT_BLOCK_NAME = 'jetpack/sharing-buttons';
25const INNER_BLOCK_NAME  = 'jetpack/sharing-button';
26
27/**
28 * Registers the block for use in Gutenberg
29 * This is done via an action so that we can disable
30 * registration if we need to.
31 */
32function register_block() {
33    Blocks::jetpack_register_block(
34        __DIR__,
35        array( 'render_callback' => __NAMESPACE__ . '\render_block' )
36    );
37
38    // load-jetpack.php sets the package up everywhere else, but Simple's front end loads neither it nor sharing.php.
39    if ( ( new Host() )->is_wpcom_simple() && class_exists( Hooked_Blocks::class ) ) {
40        Hooked_Blocks::init();
41    }
42}
43add_action( 'init', __NAMESPACE__ . '\register_block' );
44
45/**
46 * Sharing Buttons block registration/dependency declaration.
47 *
48 * @param array  $attr    Array containing the Sharing Buttons block attributes.
49 * @param string $content String containing the Sharing Buttons block content.
50 * @param object $block   Object containing block data.
51 *
52 * @return string
53 */
54function render_block( $attr, $content, $block ) {
55    $services = get_services();
56    if ( ! isset( $attr['service'] ) || ! array_key_exists( $attr['service'], $services ) ) {
57        return $content;
58    }
59
60    $service_name = $attr['service'];
61    $title        = $attr['label'] ?? $service_name;
62    $icon         = get_social_logo( $service_name );
63    $style_type   = $block->context['styleType'] ?? 'icon-text';
64    $post_id      = $block->context['postId'] ?? 0;
65    $data_shared  = sprintf(
66        'sharing-%1$s-%2$d',
67        $service_name,
68        $post_id
69    );
70    // data-shared stays per post for sharing.js; the label id must be unique per instance.
71    $label_id = wp_unique_id( $data_shared . '-' );
72
73    $service      = new $services[ $service_name ]( $service_name, array() );
74    $link_props   = $service->get_link(
75        $post_id,
76        'share=' . esc_attr( $service_name ) . '&nb=1',
77        esc_attr( $data_shared )
78    );
79    $link_url     = $link_props['url'];
80    $link_classes = sprintf(
81        'jetpack-sharing-button__button style-%1$s share-%2$s',
82        $style_type,
83        $service_name
84    );
85
86    $accessible_name = sprintf(
87        /* translators: %s refers to a string representation of sharing service, e.g. Facebook */
88        esc_html__( 'Share on %s', 'jetpack' ),
89        esc_html( $title )
90    );
91
92    $accessible_name .= __( ' (Opens in new window)', 'jetpack' );
93
94    $block_class_name = 'jetpack-sharing-button__list-item';
95
96    if ( $service_name === 'share' ) {
97        $block_class_name .= ' tooltip';
98        /* translators: aria label for SMS sharing button */
99        $accessible_name = esc_attr__( 'Share using Native tools', 'jetpack' );
100        $link_props      = $service->get_link( $post_id );
101    }
102
103    $link_url     = $link_props['url'];
104    $link_classes = sprintf(
105        'jetpack-sharing-button__button style-%1$s share-%2$s',
106        $style_type,
107        $service_name
108    );
109
110    $styles = array();
111    if (
112        array_key_exists( 'iconColorValue', $block->context )
113        && ! empty( $block->context['iconColorValue'] )
114    ) {
115        $styles['color'] = $block->context['iconColorValue'];
116    }
117    if (
118        array_key_exists( 'iconBackgroundColorValue', $block->context )
119        && ! empty( $block->context['iconBackgroundColorValue'] )
120    ) {
121        $styles['background-color'] = $block->context['iconBackgroundColorValue'];
122    }
123    $link_styles = '';
124    foreach ( $styles as $property => $value ) {
125        $link_styles .= $property . ':' . $value . ';';
126    }
127
128    Jetpack_Gutenberg::load_assets_as_required( __DIR__ );
129
130    $component  = '<li class="' . esc_attr( $block_class_name ) . '">';
131    $component .= sprintf(
132        '<a href="%1$s" target="_blank" rel="nofollow noopener noreferrer" class="%2$s" style="%3$s" data-service="%4$s" data-shared="%5$s" aria-labelledby="%6$s">',
133        esc_url( $link_url ),
134        esc_attr( $link_classes ),
135        esc_attr( $link_styles ),
136        esc_attr( $service_name ),
137        esc_attr( $data_shared ),
138        esc_attr( $label_id )
139    );
140
141    // Add hidden span with accessible name
142    $component .= sprintf(
143        '<span id="%s" hidden>%s</span>',
144        esc_attr( $label_id ),
145        esc_html( $accessible_name )
146    );
147
148    $component .= $style_type !== 'text' ? $icon : '';
149    $component .= '<span class="jetpack-sharing-button__service-label" aria-hidden="true">' . esc_html( $title ) . '</span>';
150    if ( $service_name === 'share' ) {
151        $component .= '<span class="tooltiptext" aria-live="assertive">' . esc_html__( 'Copied to clipboard', 'jetpack' ) . '</span>';
152    }
153    $component .= '</a>';
154    $component .= '</li>';
155
156    return $component;
157}
158
159/**
160 * Get services for the Sharing Buttons block.
161 *
162 * @return array Array of services.
163 */
164function get_services() {
165    $services = array(
166        'print'     => Share_Print_Block::class,
167        'mail'      => Share_Email_Block::class,
168        'facebook'  => Share_Facebook_Block::class,
169        'linkedin'  => Share_LinkedIn_Block::class,
170        'reddit'    => Share_Reddit_Block::class,
171        'twitter'   => Share_Twitter_Block::class,
172        'tumblr'    => Share_Tumblr_Block::class,
173        'pinterest' => Share_Pinterest_Block::class,
174        'telegram'  => Share_Telegram_Block::class,
175        'threads'   => Share_Threads_Block::class,
176        'whatsapp'  => Jetpack_Share_WhatsApp_Block::class,
177        'mastodon'  => Share_Mastodon_Block::class,
178        'nextdoor'  => Share_Nextdoor_Block::class,
179        'bluesky'   => Share_Bluesky_Block::class,
180        'x'         => Share_X_Block::class,
181        'share'     => Share_Native_Block::class,
182    );
183
184    return $services;
185}
186
187/**
188 * Launch sharing requests on page load when a specific query string is used.
189 *
190 * @return void
191 */
192function sharing_process_requests() {
193    global $post;
194
195    // Only process if: single post and share={service} defined
196    if ( ( is_page() || is_single() ) && isset( $_GET['share'] ) && is_string( $_GET['share'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
197        $services     = get_services();
198        $service_name = sanitize_text_field( wp_unslash( $_GET['share'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
199
200        // Only allow services that have been defined in get_services().
201        if ( ! array_key_exists( $service_name, $services ) ) {
202            return;
203        }
204
205        $service = new $services[ ( $service_name ) ]( $service_name, array() );
206        $service->process_request( $post, $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
207    }
208}
209
210// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only checking for the data being present.
211if ( isset( $_GET['share'] ) ) {
212    add_action( 'template_redirect', __NAMESPACE__ . '\sharing_process_requests', 9 );
213}