PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.0-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.0-beta
16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / modules / sharedaddy / sharing-service.php
jetpack / modules / sharedaddy Last commit date
images 2 months ago services 7 months ago admin-sharing-rtl.css 4 months ago admin-sharing-rtl.min.css 1 month ago admin-sharing.css 4 months ago admin-sharing.js 2 months ago admin-sharing.min.css 1 month ago amp-sharing.css 4 months ago recaptcha.php 1 month ago sharedaddy.php 7 months ago sharing-service.php 1 month ago sharing-sources.php 1 month ago sharing.css 4 months ago sharing.js 3 years ago sharing.php 2 months ago
sharing-service.php
1286 lines
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 int $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();
1286