PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
← All changes | modules/sharedaddy/sharing-service.php +1285 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,1285 @@
1 +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 +/**
3 + * Utilities to register and interact with a sharing service.
4 + *
5 + * Sharing_Service gets info about a service.
6 + * Sharing_Service_Total and Sharing_Post_Total get stats data.
7 + *
8 + * @package automattic/jetpack
9 + *
10 + * phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
11 + */
12 +
13 +// phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move classes to appropriately-named class files.
14 +
15 +use Automattic\Jetpack\Assets;
16 +use Automattic\Jetpack\Sync\Settings;
17 +
18 +if ( ! defined( 'ABSPATH' ) ) {
19 + exit( 0 );
20 +}
21 +
22 +require_once __DIR__ . '/sharing-sources.php';
23 +
24 +define( 'WP_SHARING_PLUGIN_VERSION', JETPACK__VERSION );
25 +
26 +/**
27 + * Interact with a sharing service.
28 + */
29 +class Sharing_Service {
30 + /**
31 + * Should the service be available globally?
32 + *
33 + * @var bool
34 + */
35 + private $global = false;
36 +
37 + /**
38 + * Default sharing label.
39 + *
40 + * @var string
41 + */
42 + public $default_sharing_label = '';
43 +
44 + /**
45 + * Initialize the sharing service.
46 + * Only run this method once upon module loading.
47 + *
48 + * @return void
49 + */
50 + public static function init() {
51 + add_filter( 'the_content', 'sharing_display', 19 );
52 + add_filter( 'the_excerpt', 'sharing_display', 19 );
53 + }
54 +
55 + /**
56 + * Constructor.
57 + */
58 + public function __construct() {
59 + $this->default_sharing_label = __( 'Share this:', 'jetpack' );
60 + }
61 +
62 + /**
63 + * Gets a generic list of all services, without any config
64 + *
65 + * @return array
66 + */
67 + public function get_all_services_blog() {
68 + $options = get_option( 'sharing-options' );
69 + $all = $this->get_all_services();
70 + $services = array();
71 +
72 + foreach ( $all as $id => $name ) {
73 + if ( isset( $all[ $id ] ) ) {
74 + $config = array();
75 +
76 + // Pre-load custom modules otherwise they won't know who they are
77 + if ( str_starts_with( $id, 'custom-' ) && is_array( $options[ $id ] ) ) {
78 + $config = $options[ $id ];
79 + }
80 +
81 + $services[ $id ] = new $all[ $id ]( $id, $config );
82 + }
83 + }
84 +
85 + return $services;
86 + }
87 +
88 + /**
89 + * Gets a list of all available service names and classes
90 + *
91 + * @param bool $include_custom Include custom sharing services.
92 + *
93 + * @return array
94 + */
95 + public function get_all_services( $include_custom = true ) {
96 + // Default services
97 + // if you update this list, please update the REST API tests
98 + // in bin/tests/api/suites/SharingTest.php
99 + $services = array(
100 + 'print' => 'Share_Print',
101 + 'email' => 'Share_Email',
102 + 'facebook' => 'Share_Facebook',
103 + 'linkedin' => 'Share_LinkedIn',
104 + 'reddit' => 'Share_Reddit',
105 + 'twitter' => 'Share_Twitter',
106 + 'tumblr' => 'Share_Tumblr',
107 + 'pinterest' => 'Share_Pinterest',
108 + 'telegram' => 'Share_Telegram',
109 + 'threads' => 'Share_Threads',
110 + 'jetpack-whatsapp' => 'Jetpack_Share_WhatsApp',
111 + 'mastodon' => 'Share_Mastodon',
112 + 'nextdoor' => 'Share_Nextdoor',
113 + 'x' => 'Share_X',
114 + 'bluesky' => 'Share_Bluesky',
115 + );
116 +
117 + if ( is_multisite() && is_plugin_active( 'press-this/press-this-plugin.php' ) ) {
118 + $services['press-this'] = 'Share_PressThis';
119 + }
120 +
121 + if ( $include_custom ) {
122 + // Add any custom services in
123 + $options = $this->get_global_options();
124 + if ( isset( $options['custom'] ) ) {
125 + foreach ( $options['custom'] as $custom_id ) {
126 + $services[ $custom_id ] = 'Share_Custom';
127 + }
128 + }
129 + }
130 +
131 + /**
132 + * Filters the list of available Sharing Services.
133 + *
134 + * @module sharedaddy
135 + *
136 + * @since 1.1.0
137 + *
138 + * @param array $services Array of all available Sharing Services.
139 + */
140 + return apply_filters( 'sharing_services', $services );
141 + }
142 +
143 + /**
144 + * Save a new custom sharing service.
145 + *
146 + * @param string $label Service name.
147 + * @param string $url Service sharing URL.
148 + * @param string $icon Service icon.
149 + *
150 + * @return bool|Share_Custom
151 + */
152 + public function new_service( $label, $url, $icon ) {
153 + // Validate.
154 + $label = trim( wp_html_excerpt( wp_kses( $label, array() ), 30 ) );
155 + $url = trim( esc_url_raw( $url ) );
156 + $icon = trim( esc_url_raw( $icon ) );
157 +
158 + if ( $label && $url && $icon ) {
159 + $options = get_option( 'sharing-options' );
160 + if ( ! is_array( $options ) ) {
161 + $options = array();
162 + }
163 +
164 + $service_id = 'custom-' . time();
165 +
166 + // Add a new custom service
167 + $options['global']['custom'][] = $service_id;
168 + if ( false !== $this->global ) {
169 + $this->global['custom'][] = $service_id;
170 + }
171 +
172 + update_option( 'sharing-options', $options );
173 +
174 + // Create a custom service and set the options for it
175 + $service = new Share_Custom(
176 + $service_id,
177 + array(
178 + 'name' => $label,
179 + 'url' => $url,
180 + 'icon' => $icon,
181 + )
182 + );
183 + $this->set_service( $service_id, $service );
184 +
185 + // Return the service
186 + return $service;
187 + }
188 +
189 + return false;
190 + }
191 +
192 + /**
193 + * Delete a sharing service.
194 + *
195 + * @param string $service_id Service ID.
196 + *
197 + * @return bool
198 + */
199 + public function delete_service( $service_id ) {
200 + $options = get_option( 'sharing-options' );
201 + if ( isset( $options[ $service_id ] ) ) {
202 + unset( $options[ $service_id ] );
203 + }
204 +
205 + $key = array_search( $service_id, $options['global']['custom'], true );
206 + if ( $key !== false ) {
207 + unset( $options['global']['custom'][ $key ] );
208 + }
209 +
210 + update_option( 'sharing-options', $options );
211 + return true;
212 + }
213 +
214 + /**
215 + * Save enabled sharing services.
216 + *
217 + * @param array $visible Visible sharing services.
218 + * @param array $hidden Hidden sharing services (available under a dropdown).
219 + *
220 + * @return bool
221 + */
222 + public function set_blog_services( array $visible, array $hidden ) {
223 + $services = $this->get_all_services();
224 + // Validate the services
225 + $available = array_keys( $services );
226 +
227 + // Only allow services that we have defined
228 + $hidden = array_intersect( $hidden, $available );
229 + $visible = array_intersect( $visible, $available );
230 +
231 + // Ensure we don't have the same ones in hidden and visible
232 + $hidden = array_diff( $hidden, $visible );
233 +
234 + /**
235 + * Control the state of the list of sharing services.
236 + *
237 + * @module sharedaddy
238 + *
239 + * @since 1.1.0
240 + *
241 + * @param array $args {
242 + * Array of options describing the state of the sharing services.
243 + *
244 + * @type array $services List of all available service names and classes.
245 + * @type array $available Validated list of all available service names and classes.
246 + * @type array $hidden List of services hidden behind a "More" button.
247 + * @type array $visible List of visible services.
248 + * @type array $this->get_blog_services() Array of Sharing Services currently enabled.
249 + * }
250 + */
251 + do_action(
252 + 'sharing_get_services_state',
253 + array(
254 + 'services' => $services,
255 + 'available' => $available,
256 + 'hidden' => $hidden,
257 + 'visible' => $visible,
258 + 'currently_enabled' => $this->get_blog_services(),
259 + )
260 + );
261 +
262 + return update_option(
263 + 'sharing-services',
264 + array(
265 + 'visible' => $visible,
266 + 'hidden' => $hidden,
267 + )
268 + );
269 + }
270 +
271 + /**
272 + * Get information about enabled sharing services on the site.
273 + *
274 + * @return array
275 + */
276 + public function get_blog_services() {
277 + $options = get_option( 'sharing-options' );
278 + $enabled = get_option( 'sharing-services' );
279 + $services = $this->get_all_services();
280 +
281 + /**
282 + * Check if options exist and are well formatted.
283 + * This avoids issues on sites with corrupted options.
284 + *
285 + * @see https://github.com/Automattic/jetpack/issues/6121
286 + */
287 + if ( ! is_array( $options ) || ! isset( $options['button_style'] ) || ! isset( $options['global'] ) ) {
288 + $global_options = array( 'global' => $this->get_global_options() );
289 + $options = is_array( $options )
290 + ? array_merge( $options, $global_options )
291 + : $global_options;
292 + }
293 +
294 + $global = $options['global'];
295 +
296 + // Default services
297 + if ( ! is_array( $enabled ) ) {
298 + $enabled = array(
299 + 'visible' => array(
300 + 'facebook',
301 + 'x',
302 + ),
303 + 'hidden' => array(),
304 + );
305 +
306 + /**
307 + * Filters the list of default Sharing Services.
308 + *
309 + * @module sharedaddy
310 + *
311 + * @since 1.1.0
312 + *
313 + * @param array $enabled Array of default Sharing Services.
314 + */
315 + $enabled = apply_filters( 'sharing_default_services', $enabled );
316 + }
317 +
318 + // Cleanup after any filters that may have produced duplicate services
319 + if ( isset( $enabled['visible'] ) && is_array( $enabled['visible'] ) ) {
320 + $enabled['visible'] = array_unique( $enabled['visible'] );
321 + } else {
322 + $enabled['visible'] = array();
323 + }
324 +
325 + if ( isset( $enabled['hidden'] ) && is_array( $enabled['hidden'] ) ) {
326 + $enabled['hidden'] = array_unique( $enabled['hidden'] );
327 + } else {
328 + $enabled['hidden'] = array();
329 + }
330 +
331 + // Form the enabled services
332 + $blog = array(
333 + 'visible' => array(),
334 + 'hidden' => array(),
335 + );
336 +
337 + foreach ( $blog as $area => $stuff ) {
338 + foreach ( (array) $enabled[ $area ] as $service ) {
339 + if ( isset( $services[ $service ] ) ) {
340 + if ( ! isset( $options[ $service ] ) || ! is_array( $options[ $service ] ) ) {
341 + $options[ $service ] = array();
342 + }
343 + $blog[ $area ][ $service ] = new $services[ $service ]( $service, array_merge( $global, $options[ $service ] ) );
344 + }
345 + }
346 + }
347 +
348 + /**
349 + * Filters the list of enabled Sharing Services.
350 + *
351 + * @module sharedaddy
352 + *
353 + * @since 1.1.0
354 + *
355 + * @param array $blog Array of enabled Sharing Services.
356 + */
357 + $blog = apply_filters( 'sharing_services_enabled', $blog );
358 +
359 + // Add CSS for NASCAR
360 + if ( ( is_countable( $blog['visible'] ) && count( $blog['visible'] ) ) || ( is_countable( $blog['hidden'] ) && count( $blog['hidden'] ) ) ) {
361 + add_filter( 'post_flair_block_css', 'post_flair_service_enabled_sharing' );
362 + }
363 +
364 + // Convenience for checking if a service is present
365 + $blog['all'] = array_flip( array_merge( array_keys( $blog['visible'] ), array_keys( $blog['hidden'] ) ) );
366 + return $blog;
367 + }
368 +
369 + /**
370 + * Get information about a specific enabled sharing service.
371 + *
372 + * @param string $service_name Service name.
373 + *
374 + * @return bool|Sharing_Source
375 + */
376 + public function get_service( $service_name ) {
377 + $services = $this->get_blog_services();
378 +
379 + if ( isset( $services['visible'][ $service_name ] ) ) {
380 + return $services['visible'][ $service_name ];
381 + }
382 +
383 + if ( isset( $services['hidden'][ $service_name ] ) ) {
384 + return $services['hidden'][ $service_name ];
385 + }
386 +
387 + return false;
388 + }
389 +
390 + /**
391 + * Update global sharing options.
392 + *
393 + * @param array $data Array of new sharing options to save.
394 + */
395 + public function set_global_options( $data ) {
396 + $options = get_option( 'sharing-options' );
397 +
398 + // No options yet.
399 + if ( ! is_array( $options ) ) {
400 + $options = array();
401 + }
402 +
403 + // Defaults.
404 + $options['global'] = array(
405 + 'button_style' => 'icon-text',
406 + 'sharing_label' => $this->default_sharing_label,
407 + 'open_links' => 'same',
408 + 'show' => ! isset( $options['global'] ) ? array( 'post', 'page' ) : array(),
409 + 'custom' => $options['global']['custom'] ?? array(),
410 + );
411 +
412 + /**
413 + * Filters global sharing settings.
414 + *
415 + * @module sharedaddy
416 + *
417 + * @since 1.1.0
418 + *
419 + * @param array $options['global'] Array of global sharing settings.
420 + */
421 + $options['global'] = apply_filters( 'sharing_default_global', $options['global'] );
422 +
423 + // Validate options and set from our data
424 + if (
425 + isset( $data['button_style'] )
426 + && in_array( $data['button_style'], array( 'icon-text', 'icon', 'text', 'official' ), true )
427 + ) {
428 + $options['global']['button_style'] = $data['button_style'];
429 + }
430 +
431 + if ( isset( $data['sharing_label'] ) ) {
432 + if ( $this->default_sharing_label === $data['sharing_label'] ) {
433 + $options['global']['sharing_label'] = false;
434 + } else {
435 + $options['global']['sharing_label'] = trim( wp_kses( stripslashes( $data['sharing_label'] ), array() ) );
436 + }
437 + }
438 +
439 + if (
440 + isset( $data['open_links'] )
441 + && in_array( $data['open_links'], array( 'new', 'same' ), true )
442 + ) {
443 + $options['global']['open_links'] = $data['open_links'];
444 + }
445 +
446 + $shows = array_values( get_post_types( array( 'public' => true ) ) );
447 + $shows[] = 'index';
448 + if ( isset( $data['show'] ) ) {
449 + if ( is_scalar( $data['show'] ) ) {
450 + switch ( $data['show'] ) {
451 + case 'posts':
452 + $data['show'] = array( 'post', 'page' );
453 + break;
454 + case 'index':
455 + $data['show'] = array( 'index' );
456 + break;
457 + case 'posts-index':
458 + $data['show'] = array( 'post', 'page', 'index' );
459 + break;
460 + }
461 + }
462 +
463 + $data['show'] = array_intersect( $data['show'], $shows );
464 + if ( $data['show'] ) {
465 + $options['global']['show'] = $data['show'];
466 + }
467 + }
468 +
469 + update_option( 'sharing-options', $options );
470 + return $options['global'];
471 + }
472 +
473 + /**
474 + * Get global sharing options for the site.
475 + *
476 + * @return array
477 + */
478 + public function get_global_options() {
479 + if ( $this->global === false ) {
480 + $options = get_option( 'sharing-options' );
481 +
482 + if ( is_array( $options ) && isset( $options['global'] ) && is_array( $options['global'] ) ) {
483 + $this->global = $options['global'];
484 + } else {
485 + $this->global = $this->set_global_options( $options );
486 + }
487 + }
488 +
489 + if ( ! isset( $this->global['show'] ) ) {
490 + $this->global['show'] = array( 'post', 'page' );
491 + } elseif ( is_scalar( $this->global['show'] ) ) {
492 + switch ( $this->global['show'] ) {
493 + case 'posts':
494 + $this->global['show'] = array( 'post', 'page' );
495 + break;
496 + case 'index':
497 + $this->global['show'] = array( 'index' );
498 + break;
499 + case 'posts-index':
500 + $this->global['show'] = array( 'post', 'page', 'index' );
501 + break;
502 + }
503 + }
504 +
505 + if ( ! isset( $this->global['sharing_label'] ) || false === $this->global['sharing_label'] || $this->global['sharing_label'] === 'Share this:' ) {
506 + $this->global['sharing_label'] = $this->default_sharing_label;
507 + }
508 +
509 + return $this->global;
510 + }
511 +
512 + /**
513 + * Save a sharing service for use.
514 + *
515 + * @param string $id Sharing unique ID.
516 + * @param Sharing_Advanced_Source $service Sharing service.
517 + *
518 + * @return void
519 + */
520 + public function set_service( $id, Sharing_Advanced_Source $service ) {
521 + // Update the options for this service
522 + $options = get_option( 'sharing-options' );
523 +
524 + // No options yet
525 + if ( ! is_array( $options ) ) {
526 + $options = array();
527 + }
528 +
529 + /**
530 + * Get the state of a sharing button.
531 + *
532 + * @module sharedaddy
533 + *
534 + * @since 1.1.0
535 + *
536 + * @param array $args {
537 + * State of a sharing button.
538 + *
539 + * @type string $id Service ID.
540 + * @type array $options Array of all sharing options.
541 + * @type array $service Details about a service.
542 + * }
543 + */
544 + do_action(
545 + 'sharing_get_button_state',
546 + array(
547 + 'id' => $id,
548 + 'options' => $options,
549 + 'service' => $service,
550 + )
551 + );
552 +
553 + $options[ $id ] = $service->get_options();
554 +
555 + update_option( 'sharing-options', array_filter( $options ) );
556 + }
557 +
558 + /**
559 + * Get stats for a site, a post, or a sharing service.
560 + * Soon to come to a .org plugin near you!
561 + *
562 + * @param string|bool $service_name Service name.
563 + * @param int|bool $post_id Post ID.
564 + * @param int|bool $_blog_id Blog ID.
565 + *
566 + * @return int
567 + */
568 + public function get_total( $service_name = false, $post_id = false, $_blog_id = false ) {
569 + global $wpdb, $blog_id;
570 + if ( ! $_blog_id ) {
571 + $_blog_id = $blog_id;
572 + }
573 + if ( $service_name === false ) {
574 + if ( $post_id > 0 ) {
575 + // total number of shares for this post
576 + $sql = $wpdb->prepare( 'SELECT SUM( count ) FROM sharing_stats WHERE blog_id = %d AND post_id = %d', $_blog_id, $post_id );
577 + $cache_key = "sharing_service_get_total_b{$_blog_id}_p{$post_id}";
578 + } else {
579 + // total number of shares for this blog
580 + $sql = $wpdb->prepare( 'SELECT SUM( count ) FROM sharing_stats WHERE blog_id = %d', $_blog_id );
581 + $cache_key = "sharing_service_get_total_b{$_blog_id}";
582 + }
583 + } elseif ( $post_id > 0 ) {
584 + $sql = $wpdb->prepare( 'SELECT SUM( count ) FROM sharing_stats WHERE blog_id = %d AND post_id = %d AND share_service = %s', $_blog_id, $post_id, $service_name );
585 + $cache_key = "sharing_service_get_total_b{$_blog_id}_p{$post_id}_s{$service_name}";
586 + } else {
587 + $sql = $wpdb->prepare( 'SELECT SUM( count ) FROM sharing_stats WHERE blog_id = %d AND share_service = %s', $_blog_id, $service_name );
588 + $cache_key = "sharing_service_get_total_b{$_blog_id}_s{$service_name}";
589 + }
590 +
591 + $ret = wp_cache_get( $cache_key, 'sharing' );
592 + if ( $ret === false ) {
593 + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery -- Prepared above.
594 + $ret = (int) $wpdb->get_var( $sql );
595 + wp_cache_set( $cache_key, $ret, 'sharing', 5 * MINUTE_IN_SECONDS );
596 + }
597 + return $ret;
598 + }
599 +
600 + /**
601 + * Get total stats for a site, for all sharing services.
602 + *
603 + * @param int|bool $post_id Post ID.
604 + *
605 + * @return array
606 + */
607 + public function get_services_total( $post_id = false ) {
608 + $totals = array();
609 + $services = $this->get_blog_services();
610 +
611 + if ( ! empty( $services ) && isset( $services['all'] ) ) {
612 + foreach ( $services['all'] as $key => $value ) {
613 + $totals[ $key ] = new Sharing_Service_Total( $key, $this->get_total( $key, $post_id ) );
614 + }
615 + }
616 + usort( $totals, array( 'Sharing_Service_Total', 'cmp' ) );
617 +
618 + return $totals;
619 + }
620 +
621 + /**
622 + * Get sharing stats for all posts on the site.
623 + *
624 + * @return array
625 + */
626 + public function get_posts_total() {
627 + $totals = array();
628 + global $wpdb, $blog_id;
629 +
630 + $cache_key = "sharing_service_get_posts_total_{$blog_id}";
631 + $my_data = wp_cache_get( $cache_key, 'sharing' );
632 + if ( $my_data === false ) {
633 + $my_data = $wpdb->get_results( $wpdb->prepare( 'SELECT post_id as id, SUM( count ) as total FROM sharing_stats WHERE blog_id = %d GROUP BY post_id ORDER BY count DESC ', $blog_id ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
634 + wp_cache_set( $cache_key, $my_data, 'sharing', 5 * MINUTE_IN_SECONDS );
635 + }
636 +
637 + if ( ! empty( $my_data ) ) {
638 + foreach ( $my_data as $row ) {
639 + $totals[] = new Sharing_Post_Total( $row->id, $row->total );
640 + }
641 + }
642 +
643 + usort( $totals, array( 'Sharing_Post_Total', 'cmp' ) );
644 +
645 + return $totals;
646 + }
647 +}
648 +
649 +/**
650 + * Get stats for a specific sharing service.
651 + */
652 +class Sharing_Service_Total {
653 + /**
654 + * Sharing service ID.
655 + *
656 + * @var int
657 + */
658 + public $id = '';
659 +
660 + /**
661 + * Service name.
662 + *
663 + * @var string
664 + */
665 + public $name = '';
666 +
667 + /**
668 + * Sharing service name.
669 + *
670 + * @var string
671 + */
672 + public $service = '';
673 +
674 + /**
675 + * Total number of shares for this service.
676 + *
677 + * @var string
678 + */
679 + public $total = 0;
680 +
681 + /**
682 + * Constructor.
683 + *
684 + * @param int $id Service ID.
685 + * @param int $total Total shares.
686 + */
687 + public function __construct( $id, $total ) {
688 + $services = new Sharing_Service();
689 + $this->id = esc_html( $id );
690 + $this->service = $services->get_service( $id );
691 + $this->total = (int) $total;
692 +
693 + if ( $this->service instanceof Sharing_Source ) {
694 + $this->name = $this->service->get_name();
695 + }
696 + }
697 +
698 + /**
699 + * Compare total shares between 2 posts.
700 + *
701 + * @param object $a Sharing_Service_Total object.
702 + * @param object $b Sharing_Service_Total object.
703 + *
704 + * @return int -1, 0, or 1 if $a is <, =, or > $b
705 + */
706 + public static function cmp( $a, $b ) {
707 + if ( $a->total === $b->total ) {
708 + return $b->name <=> $a->name;
709 + }
710 + return $b->total <=> $a->total;
711 + }
712 +}
713 +
714 +/**
715 + * Get sharing stats for a specific post.
716 + */
717 +class Sharing_Post_Total {
718 + /**
719 + * Sharing service ID.
720 + *
721 + * @var int
722 + */
723 + public $id = 0;
724 +
725 + /**
726 + * Total shares.
727 + *
728 + * @var int
729 + */
730 + public $total = 0;
731 +
732 + /**
733 + * Post title.
734 + *
735 + * @var string
736 + */
737 + public $title = '';
738 +
739 + /**
740 + * Post permalink.
741 + *
742 + * @var string
743 + */
744 + public $url = '';
745 +
746 + /**
747 + * Constructor.
748 + *
749 + * @param int $id Service ID.
750 + * @param int $total Total shares.
751 + */
752 + public function __construct( $id, $total ) {
753 + $this->id = (int) $id;
754 + $this->total = (int) $total;
755 + $this->title = get_the_title( $this->id );
756 + $this->url = get_permalink( $this->id );
757 + }
758 +
759 + /**
760 + * Compare total shares between 2 posts.
761 + *
762 + * @param object $a Sharing_Post_Total object.
763 + * @param object $b Sharing_Post_Total object.
764 + *
765 + * @return int -1, 0, or 1 if $a is <, =, or > $b
766 + */
767 + public static function cmp( $a, $b ) {
768 + if ( $a->total === $b->total ) {
769 + return $b->id <=> $a->id;
770 + }
771 + return $b->total <=> $a->total;
772 + }
773 +}
774 +
775 +/**
776 + * Populate sharing counts global with a post we want to count shares for.
777 + *
778 + * @param int $post_id Post ID.
779 + *
780 + * @return void
781 + */
782 +function sharing_register_post_for_share_counts( $post_id ) {
783 + global $jetpack_sharing_counts;
784 +
785 + if ( ! isset( $jetpack_sharing_counts ) || ! is_array( $jetpack_sharing_counts ) ) {
786 + $jetpack_sharing_counts = array();
787 + }
788 +
789 + $jetpack_sharing_counts[ (int) $post_id ] = get_permalink( $post_id );
790 +}
791 +
792 +/**
793 + * Determine whether we should load sharing scripts or not.
794 + *
795 + * @return bool
796 + */
797 +function sharing_maybe_enqueue_scripts() {
798 + $sharer = new Sharing_Service();
799 + $global_options = $sharer->get_global_options();
800 +
801 + $enqueue = false;
802 + if ( is_singular() && in_array( get_post_type(), $global_options['show'], true ) ) {
803 + $enqueue = true;
804 + } elseif (
805 + in_array( 'index', $global_options['show'], true )
806 + && (
807 + is_home()
808 + || is_front_page()
809 + || is_archive()
810 + || is_search()
811 + || in_array( get_post_type(), $global_options['show'], true )
812 + )
813 + ) {
814 + $enqueue = true;
815 + }
816 +
817 + /**
818 + * Filter to decide when sharing scripts should be enqueued.
819 + *
820 + * @module sharedaddy
821 + *
822 + * @since 3.2.0
823 + *
824 + * @param bool $enqueue Decide if the sharing scripts should be enqueued.
825 + */
826 + return (bool) apply_filters( 'sharing_enqueue_scripts', $enqueue );
827 +}
828 +
829 +/**
830 + * Add sharing JavaScript to the footer of a page.
831 + *
832 + * @return void
833 + */
834 +function sharing_add_footer() {
835 + if (
836 + class_exists( 'Jetpack_AMP_Support' )
837 + && Jetpack_AMP_Support::is_amp_request()
838 + ) {
839 + return;
840 + }
841 +
842 + global $jetpack_sharing_counts;
843 +
844 + if (
845 + /**
846 + * Filter all JavaScript output by the sharing module.
847 + *
848 + * @module sharedaddy
849 + *
850 + * @since 1.1.0
851 + *
852 + * @param bool true Control whether the sharing module should add any JavaScript to the site. Default to true.
853 + */
854 + apply_filters( 'sharing_js', true )
855 + && sharing_maybe_enqueue_scripts()
856 + ) {
857 + if (
858 + /**
859 + * Filter the display of sharing counts next to the sharing buttons.
860 + *
861 + * @module sharedaddy
862 + *
863 + * @since 3.2.0
864 + *
865 + * @param bool true Control the display of counters next to the sharing buttons. Default to true.
866 + */
867 + apply_filters( 'jetpack_sharing_counts', true )
868 + && is_array( $jetpack_sharing_counts )
869 + && count( $jetpack_sharing_counts )
870 + ) :
871 + $sharing_post_urls = array_filter( $jetpack_sharing_counts );
872 + if ( $sharing_post_urls ) :
873 + ?>
874 +
875 + <script type="text/javascript">
876 + window.WPCOM_sharing_counts = <?php echo wp_json_encode( array_flip( $sharing_post_urls ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?>;
877 + </script>
878 + <?php
879 + endif;
880 + endif;
881 +
882 + wp_enqueue_script( 'sharing-js' );
883 + $sharing_js_options = array(
884 + 'lang' => get_base_recaptcha_lang_code(),
885 + /** This filter is documented in modules/sharedaddy/sharing-service.php */
886 + 'counts' => apply_filters( 'jetpack_sharing_counts', true ),
887 + 'is_stats_active' => Jetpack::is_module_active( 'stats' ),
888 + );
889 + wp_localize_script( 'sharing-js', 'sharing_js_options', $sharing_js_options );
890 + }
891 + $sharer = new Sharing_Service();
892 + $enabled = $sharer->get_blog_services();
893 + foreach ( array_merge( $enabled['visible'], $enabled['hidden'] ) as $service ) {
894 + $service->display_footer();
895 + }
896 +}
897 +
898 +/**
899 + * Enqueue sharing CSS in head.
900 + *
901 + * @return void
902 + */
903 +function sharing_add_header() {
904 + $sharer = new Sharing_Service();
905 + $enabled = $sharer->get_blog_services();
906 +
907 + foreach ( array_merge( $enabled['visible'], $enabled['hidden'] ) as $service ) {
908 + $service->display_header();
909 + }
910 +
911 + if ( is_countable( $enabled['all'] ) && ( count( $enabled['all'] ) > 0 ) && sharing_maybe_enqueue_scripts() ) {
912 + wp_enqueue_style( 'sharedaddy', plugin_dir_url( __FILE__ ) . 'sharing.css', array(), JETPACK__VERSION );
913 + wp_enqueue_style( 'social-logos' );
914 + }
915 +}
916 +add_action( 'wp_head', 'sharing_add_header', 1 );
917 +
918 +/**
919 + * Launch sharing requests on page load when a specific query string is used.
920 + *
921 + * @return void
922 + */
923 +function sharing_process_requests() {
924 + global $post;
925 +
926 + // Only process if: single post and share=X defined
927 + if ( ( is_page() || is_single() ) && isset( $_GET['share'] ) && is_string( $_GET['share'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
928 + $sharer = new Sharing_Service();
929 +
930 + $service = $sharer->get_service( sanitize_text_field( wp_unslash( $_GET['share'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
931 + if ( $service ) {
932 + $service->process_request( $post, $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
933 + }
934 + }
935 +}
936 +
937 +// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only checking for the data being present.
938 +if ( isset( $_GET['share'] ) ) {
939 + add_action( 'template_redirect', 'sharing_process_requests', 9 );
940 +}
941 +
942 +/**
943 + * Gets the url to customise the sharing buttons in WP-Admin.
944 + *
945 + * @return string the customisation URL.
946 + */
947 +function get_sharing_buttons_customisation_url() {
948 + return admin_url( 'options-general.php?page=sharing' );
949 +}
950 +
951 +/**
952 + * Append sharing links to text.
953 + *
954 + * @param string $text The original text to append sharing links onto.
955 + * @param bool $echo Where to echo the text or return.
956 + *
957 + * @return string The original $text with, if conditions are met, the sharing links.
958 + */
959 +function sharing_display( $text = '', $echo = false ) {
960 + global $post, $wp_current_filter;
961 +
962 + if ( Settings::is_syncing() ) {
963 + return $text;
964 + }
965 +
966 + // We require the post to not be empty and be an actual WordPress post object. If it's not - we just return.
967 + if ( empty( $post ) || ! $post instanceof \WP_Post ) {
968 + return $text;
969 + }
970 +
971 + if ( ( is_preview() || is_admin() ) && ! ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
972 + return $text;
973 + }
974 +
975 + // Prevent from rendering sharing buttons in block which is fetched from REST endpoint by editor
976 + if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
977 + return $text;
978 + }
979 +
980 + // Do not output sharing buttons for ActivityPub requests.
981 + if (
982 + function_exists( '\Activitypub\is_activitypub_request' )
983 + && \Activitypub\is_activitypub_request()
984 + ) {
985 + return $text;
986 + }
987 +
988 + // Don't output flair on excerpts.
989 + if ( in_array( 'get_the_excerpt', (array) $wp_current_filter, true ) ) {
990 + return $text;
991 + }
992 +
993 + // Ensure we don't display sharing buttons on post excerpts that are hooked inside the post content
994 + if ( in_array( 'the_excerpt', (array) $wp_current_filter, true ) &&
995 + in_array( 'the_content', (array) $wp_current_filter, true ) ) {
996 + return $text;
997 + }
998 +
999 + // Don't allow flair to be added to the_content more than once (prevent infinite loops).
1000 + $done = false;
1001 + foreach ( $wp_current_filter as $filter ) {
1002 + if ( 'the_content' === $filter ) {
1003 + if ( $done ) {
1004 + return $text;
1005 + } else {
1006 + $done = true;
1007 + }
1008 + }
1009 + }
1010 +
1011 + // check whether we are viewing the front page and whether the front page option is checked.
1012 + $options = get_option( 'sharing-options' );
1013 + $display_options = null;
1014 +
1015 + if ( is_array( $options ) ) {
1016 + $display_options = $options['global']['show'];
1017 + }
1018 +
1019 + if ( is_front_page() && ( is_array( $display_options ) && ! in_array( 'index', $display_options, true ) ) ) {
1020 + return $text;
1021 + }
1022 +
1023 + if ( is_attachment() && in_array( 'the_excerpt', (array) $wp_current_filter, true ) ) {
1024 + // Many themes run the_excerpt() conditionally on an attachment page, then run the_content().
1025 + // We only want to output the sharing buttons once. Let's stick with the_content().
1026 + return $text;
1027 + }
1028 +
1029 + $sharer = new Sharing_Service();
1030 + $global = $sharer->get_global_options();
1031 +
1032 + $show = false;
1033 + if ( ! is_feed() ) {
1034 + if ( is_singular() && in_array( get_post_type(), $global['show'], true ) ) {
1035 + $show = true;
1036 + } elseif ( in_array( 'index', $global['show'], true ) && ( is_home() || is_front_page() || is_archive() || is_search() || in_array( get_post_type(), $global['show'], true ) ) ) {
1037 + $show = true;
1038 + }
1039 + }
1040 +
1041 + /**
1042 + * Filter to decide if sharing buttons should be displayed.
1043 + *
1044 + * @module sharedaddy
1045 + *
1046 + * @since 1.1.0
1047 + *
1048 + * @param bool $show Should the sharing buttons be displayed.
1049 + * @param WP_Post $post The post to share.
1050 + */
1051 + $show = apply_filters( 'sharing_show', $show, $post );
1052 +
1053 + // Disabled for this post?
1054 + $switched_status = get_post_meta( $post->ID, 'sharing_disabled', false );
1055 +
1056 + if ( ! empty( $switched_status ) ) {
1057 + $show = false;
1058 + }
1059 +
1060 + // Is the post private?
1061 + $post_status = get_post_status( $post->ID );
1062 +
1063 + if ( 'private' === $post_status ) {
1064 + $show = false;
1065 + }
1066 +
1067 + // Hide on password protected posts unless password is provided.
1068 + if ( post_password_required( $post->ID ) ) {
1069 + $show = false;
1070 + }
1071 +
1072 + /**
1073 + * Filter the Sharing buttons' Ajax action name Jetpack checks for.
1074 + * This allows the use of the buttons with your own Ajax implementation.
1075 + *
1076 + * @module sharedaddy
1077 + *
1078 + * @since 7.3.0
1079 + *
1080 + * @param string $sharing_ajax_action_name Name of the Sharing buttons' Ajax action.
1081 + */
1082 + $ajax_action = apply_filters( 'sharing_ajax_action', 'get_latest_posts' );
1083 +
1084 + // Allow to be used in ajax requests for latest posts.
1085 + if (
1086 + defined( 'DOING_AJAX' )
1087 + && DOING_AJAX
1088 + && isset( $_REQUEST['action'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce handling happens within each custom implementation.
1089 + && $ajax_action === $_REQUEST['action'] // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce handling happens within each custom implementation.
1090 + ) {
1091 + $show = true;
1092 + }
1093 +
1094 + $sharing_content = '';
1095 + $enabled = false;
1096 +
1097 + if ( $show ) {
1098 + /**
1099 + * Filters the list of enabled Sharing Services.
1100 + *
1101 + * @module sharedaddy
1102 + *
1103 + * @since 2.2.3
1104 + *
1105 + * @param array $sharer->get_blog_services() Array of Sharing Services currently enabled.
1106 + */
1107 + $enabled = apply_filters( 'sharing_enabled', $sharer->get_blog_services() );
1108 +
1109 + if ( is_countable( $enabled['all'] ) && ( count( $enabled['all'] ) > 0 ) ) {
1110 + $dir = get_option( 'text_direction' );
1111 +
1112 + // Wrapper.
1113 + $sharing_content .= '<div class="sharedaddy sd-sharing-enabled"><div class="robots-nocontent sd-block sd-social sd-social-' . ( $global['button_style'] ?? 'icon-text' ) . ' sd-sharing">';
1114 + if ( '' !== $global['sharing_label'] ) {
1115 + $sharing_content .= sprintf(
1116 + /**
1117 + * Filter the sharing buttons' headline structure.
1118 + *
1119 + * @module sharedaddy
1120 + *
1121 + * @since 4.4.0
1122 + *
1123 + * @param string $sharing_headline Sharing headline structure.
1124 + * @param string $global['sharing_label'] Sharing title.
1125 + * @param string $sharing Module name.
1126 + */
1127 + apply_filters( 'jetpack_sharing_headline_html', '<h3 class="sd-title">%s</h3>', $global['sharing_label'], 'sharing' ),
1128 + esc_html( $global['sharing_label'] )
1129 + );
1130 + }
1131 + $sharing_content .= '<div class="sd-content"><ul>';
1132 +
1133 + // Visible items.
1134 + $visible = '';
1135 + foreach ( $enabled['visible'] as $service ) {
1136 + $klasses = array( 'share-' . $service->get_class() );
1137 + if ( $service->is_deprecated() ) {
1138 + if ( ! current_user_can( 'manage_options' ) ) {
1139 + continue;
1140 + }
1141 + $klasses[] = 'share-deprecated';
1142 + }
1143 + // Individual HTML for sharing service.
1144 + $visible .= '<li class="' . implode( ' ', $klasses ) . '">' . $service->get_display( $post ) . '</li>';
1145 + }
1146 +
1147 + $parts = array();
1148 + $parts[] = $visible;
1149 + $count_hidden = is_countable( $enabled['hidden'] ) ? count( $enabled['hidden'] ) : 0;
1150 + $count_visible = is_countable( $enabled['visible'] ) ? count( $enabled['visible'] ) : 0;
1151 + if ( $count_hidden > 0 ) {
1152 + if ( $count_visible > 0 ) {
1153 + $expand = __( 'More', 'jetpack' );
1154 + } else {
1155 + $expand = __( 'Share', 'jetpack' );
1156 + }
1157 + $parts[] = '<li><a href="#" class="sharing-anchor sd-button share-more"><span>' . $expand . '</span></a></li>';
1158 + }
1159 +
1160 + if ( 'rtl' === $dir ) {
1161 + $parts = array_reverse( $parts );
1162 + }
1163 +
1164 + $sharing_content .= implode( '', $parts );
1165 + $sharing_content .= '<li class="share-end"></li></ul>';
1166 +
1167 + // Link to customization options if user can manage them.
1168 + if ( current_user_can( 'manage_options' ) ) {
1169 + $link_url = get_sharing_buttons_customisation_url();
1170 + if ( ! empty( $link_url ) ) {
1171 + $link_text = __( 'Customize buttons', 'jetpack' );
1172 + $sharing_content .= '<p class="share-customize-link"><a href="' . esc_url( $link_url ) . '" target="_blank" rel="noopener noreferrer">' . esc_html( $link_text ) . '</a></p>';
1173 + }
1174 + }
1175 +
1176 + if ( $count_hidden > 0 ) {
1177 + $sharing_content .= '<div class="sharing-hidden"><div class="inner" style="display: none;';
1178 +
1179 + if ( $count_hidden === 1 ) {
1180 + $sharing_content .= 'width:150px;';
1181 + }
1182 +
1183 + $sharing_content .= '">';
1184 +
1185 + if ( $count_hidden === 1 ) {
1186 + $sharing_content .= '<ul style="background-image:none;">';
1187 + } else {
1188 + $sharing_content .= '<ul>';
1189 + }
1190 +
1191 + foreach ( $enabled['hidden'] as $service ) {
1192 + // Individual HTML for sharing service.
1193 + $klasses = array( 'share-' . $service->get_class() );
1194 + if ( $service->is_deprecated() ) {
1195 + if ( ! current_user_can( 'manage_options' ) ) {
1196 + continue;
1197 + }
1198 + $klasses[] = 'share-deprecated';
1199 + }
1200 + $sharing_content .= '<li class="' . implode( ' ', $klasses ) . '">';
1201 + $sharing_content .= $service->get_display( $post );
1202 + $sharing_content .= '</li>';
1203 + }
1204 +
1205 + // End of wrapper.
1206 + $sharing_content .= '<li class="share-end"></li></ul></div></div>';
1207 + }
1208 +
1209 + $sharing_content .= '</div></div></div>';
1210 +
1211 + // Register our JS.
1212 + if ( defined( 'JETPACK__VERSION' ) ) {
1213 + $ver = JETPACK__VERSION;
1214 + } else {
1215 + $ver = '20211226';
1216 + }
1217 +
1218 + // @todo: Investigate if we can load this JS in the footer instead.
1219 + wp_register_script(
1220 + 'sharing-js',
1221 + Assets::get_file_url_for_environment(
1222 + '_inc/build/sharedaddy/sharing.min.js',
1223 + 'modules/sharedaddy/sharing.js'
1224 + ),
1225 + array(),
1226 + $ver,
1227 + false
1228 + );
1229 +
1230 + // Enqueue scripts for the footer.
1231 + add_action( 'wp_footer', 'sharing_add_footer' );
1232 + }
1233 + }
1234 +
1235 + /**
1236 + * Filters the content markup of the Jetpack sharing links
1237 + *
1238 + * @module sharedaddy
1239 + *
1240 + * @since 3.8.0
1241 + * @since 6.2.0 Started sending $enabled as a second parameter.
1242 + *
1243 + * @param string $sharing_content Content markup of the Jetpack sharing links
1244 + * @param array $enabled Array of Sharing Services currently enabled.
1245 + */
1246 + $sharing_markup = apply_filters( 'jetpack_sharing_display_markup', $sharing_content, $enabled );
1247 +
1248 + if ( $echo ) {
1249 + echo $text . $sharing_markup; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1250 + } else {
1251 + return $text . $sharing_markup;
1252 + }
1253 +}
1254 +
1255 +/**
1256 + * Get reCAPTCHA language code based off the language code of the site.
1257 + *
1258 + * @return string
1259 + */
1260 +function get_base_recaptcha_lang_code() {
1261 + $base_recaptcha_lang_code_mapping = array(
1262 + 'en' => 'en',
1263 + 'nl' => 'nl',
1264 + 'fr' => 'fr',
1265 + 'fr-be' => 'fr',
1266 + 'fr-ca' => 'fr',
1267 + 'fr-ch' => 'fr',
1268 + 'de' => 'de',
1269 + 'pt' => 'pt',
1270 + 'pt-br' => 'pt',
1271 + 'ru' => 'ru',
1272 + 'es' => 'es',
1273 + 'tr' => 'tr',
1274 + );
1275 +
1276 + $blog_lang_code = get_bloginfo( 'language' );
1277 + if ( isset( $base_recaptcha_lang_code_mapping[ $blog_lang_code ] ) ) {
1278 + return $base_recaptcha_lang_code_mapping[ $blog_lang_code ];
1279 + }
1280 +
1281 + // if no base mapping is found return default 'en'
1282 + return 'en';
1283 +}
1284 +
1285 +Sharing_Service::init();