Completed
Push — add/redirect_everything_url_su... ( b37637 )
by
unknown
07:12 queued 22s
created

modules/carousel/jetpack-carousel.php (1 issue)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
use Automattic\Jetpack\Assets;
3
use Automattic\Jetpack\Status;
4
/*
5
Plugin Name: Jetpack Carousel
6
Plugin URL: https://wordpress.com/
7
Description: Transform your standard image galleries into an immersive full-screen experience.
8
Version: 0.1
9
Author: Automattic
10
11
Released under the GPL v.2 license.
12
13
This program is distributed in the hope that it will be useful,
14
but WITHOUT ANY WARRANTY; without even the implied warranty of
15
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
GNU General Public License for more details.
17
*/
18
class Jetpack_Carousel {
19
20
	public $prebuilt_widths = array( 370, 700, 1000, 1200, 1400, 2000 );
21
22
	public $first_run = true;
23
24
	public $in_gallery = false;
25
26
	public $in_jetpack = true;
27
28
	public $single_image_gallery_enabled = false;
29
30
	public $single_image_gallery_enabled_media_file = false;
31
32
	function __construct() {
33
		add_action( 'init', array( $this, 'init' ) );
34
	}
35
36
	function init() {
37
		if ( $this->maybe_disable_jp_carousel() ) {
38
			return;
39
		}
40
41
		$this->in_jetpack = ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'enable_module_configurable' ) ) ? true : false;
42
43
		$this->single_image_gallery_enabled            = ! $this->maybe_disable_jp_carousel_single_images();
44
		$this->single_image_gallery_enabled_media_file = $this->maybe_enable_jp_carousel_single_images_media_file();
45
46
		if ( is_admin() ) {
47
			// Register the Carousel-related related settings
48
			add_action( 'admin_init', array( $this, 'register_settings' ), 5 );
49
			if ( ! $this->in_jetpack ) {
50
				if ( 0 == $this->test_1or0_option( get_option( 'carousel_enable_it' ), true ) ) {
51
					return; // Carousel disabled, abort early, but still register setting so user can switch it back on
52
				}
53
			}
54
			// If in admin, register the ajax endpoints.
55
			add_action( 'wp_ajax_get_attachment_comments', array( $this, 'get_attachment_comments' ) );
56
			add_action( 'wp_ajax_nopriv_get_attachment_comments', array( $this, 'get_attachment_comments' ) );
57
			add_action( 'wp_ajax_post_attachment_comment', array( $this, 'post_attachment_comment' ) );
58
			add_action( 'wp_ajax_nopriv_post_attachment_comment', array( $this, 'post_attachment_comment' ) );
59
		} else {
60
			if ( ! $this->in_jetpack ) {
61
				if ( 0 == $this->test_1or0_option( get_option( 'carousel_enable_it' ), true ) ) {
62
					return; // Carousel disabled, abort early
63
				}
64
			}
65
			// If on front-end, do the Carousel thang.
66
			/**
67
			 * Filter the array of default prebuilt widths used in Carousel.
68
			 *
69
			 * @module carousel
70
			 *
71
			 * @since 1.6.0
72
			 *
73
			 * @param array $this->prebuilt_widths Array of default widths.
74
			 */
75
			$this->prebuilt_widths = apply_filters( 'jp_carousel_widths', $this->prebuilt_widths );
76
			// below: load later than other callbacks hooked it (e.g. 3rd party plugins handling gallery shortcode)
77
			add_filter( 'post_gallery', array( $this, 'check_if_shortcode_processed_and_enqueue_assets' ), 1000, 2 );
78
			add_filter( 'post_gallery', array( $this, 'set_in_gallery' ), -1000 );
79
			add_filter( 'gallery_style', array( $this, 'add_data_to_container' ) );
80
			add_filter( 'wp_get_attachment_image_attributes', array( $this, 'add_data_to_images' ), 10, 2 );
81
			add_filter( 'the_content', array( $this, 'check_content_for_blocks' ), 1 );
82
			add_filter( 'jetpack_tiled_galleries_block_content', array( $this, 'add_data_img_tags_and_enqueue_assets' ) );
83
			if ( $this->single_image_gallery_enabled ) {
84
				add_filter( 'the_content', array( $this, 'add_data_img_tags_and_enqueue_assets' ) );
85
			}
86
		}
87
88
		if ( $this->in_jetpack ) {
89
			Jetpack::enable_module_configurable( dirname( dirname( __FILE__ ) ) . '/carousel.php' );
90
		}
91
	}
92
93
	function maybe_disable_jp_carousel() {
94
		/**
95
		 * Allow third-party plugins or themes to disable Carousel.
96
		 *
97
		 * @module carousel
98
		 *
99
		 * @since 1.6.0
100
		 *
101
		 * @param bool false Should Carousel be disabled? Default to false.
102
		 */
103
		return apply_filters( 'jp_carousel_maybe_disable', false );
104
	}
105
106
	function maybe_disable_jp_carousel_single_images() {
107
		/**
108
		 * Allow third-party plugins or themes to disable Carousel for single images.
109
		 *
110
		 * @module carousel
111
		 *
112
		 * @since 4.5.0
113
		 *
114
		 * @param bool false Should Carousel be disabled for single images? Default to false.
115
		 */
116
		return apply_filters( 'jp_carousel_maybe_disable_single_images', false );
117
	}
118
119
	function maybe_enable_jp_carousel_single_images_media_file() {
120
		/**
121
		 * Allow third-party plugins or themes to enable Carousel
122
		 * for single images linking to 'Media File' (full size image).
123
		 *
124
		 * @module carousel
125
		 *
126
		 * @since 4.5.0
127
		 *
128
		 * @param bool false Should Carousel be enabled for single images linking to 'Media File'? Default to false.
129
		 */
130
		return apply_filters( 'jp_carousel_load_for_images_linked_to_file', false );
131
	}
132
133
	function asset_version( $version ) {
134
		/**
135
		 * Filter the version string used when enqueuing Carousel assets.
136
		 *
137
		 * @module carousel
138
		 *
139
		 * @since 1.6.0
140
		 *
141
		 * @param string $version Asset version.
142
		 */
143
		return apply_filters( 'jp_carousel_asset_version', $version );
144
	}
145
146
	function display_bail_message( $output = '' ) {
147
		// Displays a message on top of gallery if carousel has bailed
148
		$message  = '<div class="jp-carousel-msg"><p>';
149
		$message .= __( 'Jetpack\'s Carousel has been disabled, because another plugin or your theme is overriding the [gallery] shortcode.', 'jetpack' );
150
		$message .= '</p></div>';
151
		// put before gallery output
152
		$output = $message . $output;
153
		return $output;
154
	}
155
156
	function check_if_shortcode_processed_and_enqueue_assets( $output ) {
157
		if (
158
			class_exists( 'Jetpack_AMP_Support' )
159
			&& Jetpack_AMP_Support::is_amp_request()
160
		) {
161
			return $output;
162
		}
163
164
		if (
165
			! empty( $output ) &&
166
			/**
167
			 * Allow third-party plugins or themes to force-enable Carousel.
168
			 *
169
			 * @module carousel
170
			 *
171
			 * @since 1.9.0
172
			 *
173
			 * @param bool false Should we force enable Carousel? Default to false.
174
			 */
175
			! apply_filters( 'jp_carousel_force_enable', false )
176
		) {
177
			// Bail because someone is overriding the [gallery] shortcode.
178
			remove_filter( 'gallery_style', array( $this, 'add_data_to_container' ) );
179
			remove_filter( 'wp_get_attachment_image_attributes', array( $this, 'add_data_to_images' ) );
180
			remove_filter( 'the_content', array( $this, 'add_data_img_tags_and_enqueue_assets' ) );
181
			// Display message that carousel has bailed, if user is super_admin, and if we're not on WordPress.com.
182
			if (
183
				is_super_admin() &&
184
				! ( defined( 'IS_WPCOM' ) && IS_WPCOM )
185
			) {
186
				add_filter( 'post_gallery', array( $this, 'display_bail_message' ) );
187
			}
188
			return $output;
189
		}
190
191
		/**
192
		 * Fires when thumbnails are shown in Carousel.
193
		 *
194
		 * @module carousel
195
		 *
196
		 * @since 1.6.0
197
		 **/
198
		do_action( 'jp_carousel_thumbnails_shown' );
199
200
		$this->enqueue_assets();
201
202
		return $output;
203
	}
204
205
	/**
206
	 * Check if the content of a post uses gallery blocks. To be used by 'the_content' filter.
207
	 *
208
	 * @since 6.8.0
209
	 *
210
	 * @param string $content Post content.
211
	 *
212
	 * @return string $content Post content.
213
	 */
214
	function check_content_for_blocks( $content ) {
215
		if (
216
			class_exists( 'Jetpack_AMP_Support' )
217
			&& Jetpack_AMP_Support::is_amp_request()
218
		) {
219
			return $content;
220
		}
221
222
		if ( has_block( 'gallery', $content ) || has_block( 'jetpack/tiled-gallery', $content ) ) {
223
			$this->enqueue_assets();
224
			$content = $this->add_data_to_container( $content );
225
		}
226
		return $content;
227
	}
228
229
	function enqueue_assets() {
230
		if ( $this->first_run ) {
231
			wp_enqueue_script(
232
				'jetpack-carousel',
233
				Assets::get_file_url_for_environment(
234
					'_inc/build/carousel/jetpack-carousel.min.js',
235
					'modules/carousel/jetpack-carousel.js'
236
				),
237
				array( 'jquery.spin' ),
238
				$this->asset_version( '20190102' ),
239
				true
240
			);
241
242
			// Note: using  home_url() instead of admin_url() for ajaxurl to be sure  to get same domain on wpcom when using mapped domains (also works on self-hosted)
243
			// Also: not hardcoding path since there is no guarantee site is running on site root in self-hosted context.
244
			$is_logged_in         = is_user_logged_in();
245
			$current_user         = wp_get_current_user();
246
			$comment_registration = intval( get_option( 'comment_registration' ) );
247
			$require_name_email   = intval( get_option( 'require_name_email' ) );
248
			$localize_strings     = array(
249
				'widths'                          => $this->prebuilt_widths,
250
				'is_logged_in'                    => $is_logged_in,
251
				'lang'                            => strtolower( substr( get_locale(), 0, 2 ) ),
252
				'ajaxurl'                         => set_url_scheme( admin_url( 'admin-ajax.php' ) ),
253
				'nonce'                           => wp_create_nonce( 'carousel_nonce' ),
254
				'display_exif'                    => $this->test_1or0_option( Jetpack_Options::get_option_and_ensure_autoload( 'carousel_display_exif', true ) ),
255
				'display_geo'                     => $this->test_1or0_option( Jetpack_Options::get_option_and_ensure_autoload( 'carousel_display_geo', true ) ),
256
				'single_image_gallery'            => $this->single_image_gallery_enabled,
257
				'single_image_gallery_media_file' => $this->single_image_gallery_enabled_media_file,
258
				'background_color'                => $this->carousel_background_color_sanitize( Jetpack_Options::get_option_and_ensure_autoload( 'carousel_background_color', '' ) ),
259
				'comment'                         => __( 'Comment', 'jetpack' ),
260
				'post_comment'                    => __( 'Post Comment', 'jetpack' ),
261
				'write_comment'                   => __( 'Write a Comment...', 'jetpack' ),
262
				'loading_comments'                => __( 'Loading Comments...', 'jetpack' ),
263
				'download_original'               => sprintf( __( 'View full size <span class="photo-size">%1$s<span class="photo-size-times">&times;</span>%2$s</span>', 'jetpack' ), '{0}', '{1}' ),
264
				'no_comment_text'                 => __( 'Please be sure to submit some text with your comment.', 'jetpack' ),
265
				'no_comment_email'                => __( 'Please provide an email address to comment.', 'jetpack' ),
266
				'no_comment_author'               => __( 'Please provide your name to comment.', 'jetpack' ),
267
				'comment_post_error'              => __( 'Sorry, but there was an error posting your comment. Please try again later.', 'jetpack' ),
268
				'comment_approved'                => __( 'Your comment was approved.', 'jetpack' ),
269
				'comment_unapproved'              => __( 'Your comment is in moderation.', 'jetpack' ),
270
				'camera'                          => __( 'Camera', 'jetpack' ),
271
				'aperture'                        => __( 'Aperture', 'jetpack' ),
272
				'shutter_speed'                   => __( 'Shutter Speed', 'jetpack' ),
273
				'focal_length'                    => __( 'Focal Length', 'jetpack' ),
274
				'copyright'                       => __( 'Copyright', 'jetpack' ),
275
				'comment_registration'            => $comment_registration,
276
				'require_name_email'              => $require_name_email,
277
				/** This action is documented in core/src/wp-includes/link-template.php */
278
				'login_url'                       => wp_login_url( apply_filters( 'the_permalink', get_permalink() ) ),
279
				'blog_id'                         => (int) get_current_blog_id(),
280
				'meta_data'                       => array( 'camera', 'aperture', 'shutter_speed', 'focal_length', 'copyright' ),
281
			);
282
283
			if ( ! isset( $localize_strings['jetpack_comments_iframe_src'] ) || empty( $localize_strings['jetpack_comments_iframe_src'] ) ) {
284
				// We're not using Comments after all, so fallback to standard local comments.
285
286
				if ( $is_logged_in ) {
287
					$localize_strings['local_comments_commenting_as'] = '<p id="jp-carousel-commenting-as">' . sprintf( __( 'Commenting as %s', 'jetpack' ), $current_user->data->display_name ) . '</p>';
288
				} else {
289
					if ( $comment_registration ) {
290
						$localize_strings['local_comments_commenting_as'] = '<p id="jp-carousel-commenting-as">' . __( 'You must be <a href="#" class="jp-carousel-comment-login">logged in</a> to post a comment.', 'jetpack' ) . '</p>';
291
					} else {
292
						$required = ( $require_name_email ) ? __( '%s (Required)', 'jetpack' ) : '%s';
293
						$localize_strings['local_comments_commenting_as'] = ''
294
							. '<fieldset><label for="email">' . sprintf( $required, __( 'Email', 'jetpack' ) ) . '</label> '
295
							. '<input type="text" name="email" class="jp-carousel-comment-form-field jp-carousel-comment-form-text-field" id="jp-carousel-comment-form-email-field" /></fieldset>'
296
							. '<fieldset><label for="author">' . sprintf( $required, __( 'Name', 'jetpack' ) ) . '</label> '
297
							. '<input type="text" name="author" class="jp-carousel-comment-form-field jp-carousel-comment-form-text-field" id="jp-carousel-comment-form-author-field" /></fieldset>'
298
							. '<fieldset><label for="url">' . __( 'Website', 'jetpack' ) . '</label> '
299
							. '<input type="text" name="url" class="jp-carousel-comment-form-field jp-carousel-comment-form-text-field" id="jp-carousel-comment-form-url-field" /></fieldset>';
300
					}
301
				}
302
			}
303
304
			/**
305
			 * Handle WP stats for images in full-screen.
306
			 * Build string with tracking info.
307
			 */
308
309
			/**
310
			 * Filter if Jetpack should enable stats collection on carousel views
311
			 *
312
			 * @module carousel
313
			 *
314
			 * @since 4.3.2
315
			 *
316
			 * @param bool Enable Jetpack Carousel stat collection. Default false.
317
			 */
318
			if ( apply_filters( 'jetpack_enable_carousel_stats', false ) && in_array( 'stats', Jetpack::get_active_modules() ) && ! ( new Status() )->is_development_mode() ) {
319
				$localize_strings['stats'] = 'blog=' . Jetpack_Options::get_option( 'id' ) . '&host=' . wp_parse_url( get_option( 'home' ), PHP_URL_HOST ) . '&v=ext&j=' . JETPACK__API_VERSION . ':' . JETPACK__VERSION;
0 ignored issues
show
The call to wp_parse_url() has too many arguments starting with PHP_URL_HOST.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
320
321
				// Set the stats as empty if user is logged in but logged-in users shouldn't be tracked.
322 View Code Duplication
				if ( is_user_logged_in() && function_exists( 'stats_get_options' ) ) {
323
					$stats_options        = stats_get_options();
324
					$track_loggedin_users = isset( $stats_options['reg_users'] ) ? (bool) $stats_options['reg_users'] : false;
325
326
					if ( ! $track_loggedin_users ) {
327
						$localize_strings['stats'] = '';
328
					}
329
				}
330
			}
331
332
			/**
333
			 * Filter the strings passed to the Carousel's js file.
334
			 *
335
			 * @module carousel
336
			 *
337
			 * @since 1.6.0
338
			 *
339
			 * @param array $localize_strings Array of strings passed to the Jetpack js file.
340
			 */
341
			$localize_strings = apply_filters( 'jp_carousel_localize_strings', $localize_strings );
342
			wp_localize_script( 'jetpack-carousel', 'jetpackCarouselStrings', $localize_strings );
343
			wp_enqueue_style( 'jetpack-carousel', plugins_url( 'jetpack-carousel.css', __FILE__ ), array(), $this->asset_version( '20120629' ) );
344
			wp_style_add_data( 'jetpack-carousel', 'rtl', 'replace' );
345
346
			/**
347
			 * Fires after carousel assets are enqueued for the first time.
348
			 * Allows for adding additional assets to the carousel page.
349
			 *
350
			 * @module carousel
351
			 *
352
			 * @since 1.6.0
353
			 *
354
			 * @param bool $first_run First load if Carousel on the page.
355
			 * @param array $localized_strings Array of strings passed to the Jetpack js file.
356
			 */
357
			do_action( 'jp_carousel_enqueue_assets', $this->first_run, $localize_strings );
358
359
			$this->first_run = false;
360
		}
361
	}
362
363
	function set_in_gallery( $output ) {
364
		if (
365
			class_exists( 'Jetpack_AMP_Support' )
366
			&& Jetpack_AMP_Support::is_amp_request()
367
		) {
368
			return $output;
369
		}
370
		$this->in_gallery = true;
371
		return $output;
372
	}
373
374
	/**
375
	 * Adds data-* attributes required by carousel to img tags in post HTML
376
	 * content. To be used by 'the_content' filter.
377
	 *
378
	 * @see add_data_to_images()
379
	 * @see wp_make_content_images_responsive() in wp-includes/media.php
380
	 *
381
	 * @param string $content HTML content of the post
382
	 * @return string Modified HTML content of the post
383
	 */
384
	function add_data_img_tags_and_enqueue_assets( $content ) {
385
		if (
386
			class_exists( 'Jetpack_AMP_Support' )
387
			&& Jetpack_AMP_Support::is_amp_request()
388
		) {
389
			return $content;
390
		}
391
392
		if ( ! preg_match_all( '/<img [^>]+>/', $content, $matches ) ) {
393
			return $content;
394
		}
395
		$selected_images = array();
396
		foreach ( $matches[0] as $image_html ) {
397
			if ( preg_match( '/(wp-image-|data-id=)\"?([0-9]+)\"?/i', $image_html, $class_id ) &&
398
				! preg_match( '/wp-block-jetpack-slideshow_image/', $image_html ) ) {
399
				$attachment_id = absint( $class_id[2] );
400
				/**
401
				 * If exactly the same image tag is used more than once, overwrite it.
402
				 * All identical tags will be replaced later with 'str_replace()'.
403
				 */
404
				$selected_images[ $attachment_id  ] = $image_html;
405
			}
406
		}
407
408
		$find    = array();
409
		$replace = array();
410
		if ( empty( $selected_images ) ) {
411
			return $content;
412
		}
413
414
		$attachments = get_posts(
415
			array(
416
				'include'          => array_keys( $selected_images ),
417
				'post_type'        => 'any',
418
				'post_status'      => 'any',
419
				'suppress_filters' => false,
420
			)
421
		);
422
423
		foreach ( $attachments as $attachment ) {
424
			$image_html = $selected_images[ $attachment->ID ];
425
426
			$attributes      = $this->add_data_to_images( array(), $attachment );
427
			$attributes_html = '';
428
			foreach ( $attributes as $k => $v ) {
429
				$attributes_html .= esc_attr( $k ) . '="' . esc_attr( $v ) . '" ';
430
			}
431
432
			$find[]    = $image_html;
433
			$replace[] = str_replace( '<img ', "<img $attributes_html", $image_html );
434
		}
435
436
		$content = str_replace( $find, $replace, $content );
437
		$this->enqueue_assets();
438
		return $content;
439
	}
440
441
	function add_data_to_images( $attr, $attachment = null ) {
442
		if (
443
			class_exists( 'Jetpack_AMP_Support' )
444
			&& Jetpack_AMP_Support::is_amp_request()
445
		) {
446
			return $attr;
447
		}
448
449
		$attachment_id = intval( $attachment->ID );
450
		if ( ! wp_attachment_is_image( $attachment_id ) ) {
451
			return $attr;
452
		}
453
454
		$orig_file       = wp_get_attachment_image_src( $attachment_id, 'full' );
455
		$orig_file       = isset( $orig_file[0] ) ? $orig_file[0] : wp_get_attachment_url( $attachment_id );
456
		$meta            = wp_get_attachment_metadata( $attachment_id );
457
		$size            = isset( $meta['width'] ) ? intval( $meta['width'] ) . ',' . intval( $meta['height'] ) : '';
458
		$img_meta        = ( ! empty( $meta['image_meta'] ) ) ? (array) $meta['image_meta'] : array();
459
		$comments_opened = intval( comments_open( $attachment_id ) );
460
461
		/**
462
		 * Note: Cannot generate a filename from the width and height wp_get_attachment_image_src() returns because
463
		 * it takes the $content_width global variable themes can set in consideration, therefore returning sizes
464
		 * which when used to generate a filename will likely result in a 404 on the image.
465
		 * $content_width has no filter we could temporarily de-register, run wp_get_attachment_image_src(), then
466
		 * re-register. So using returned file URL instead, which we can define the sizes from through filename
467
		 * parsing in the JS, as this is a failsafe file reference.
468
		 *
469
		 * EG with Twenty Eleven activated:
470
		 * array(4) { [0]=> string(82) "http://vanillawpinstall.blah/wp-content/uploads/2012/06/IMG_3534-1024x764.jpg" [1]=> int(584) [2]=> int(435) [3]=> bool(true) }
471
		 *
472
		 * EG with Twenty Ten activated:
473
		 * array(4) { [0]=> string(82) "http://vanillawpinstall.blah/wp-content/uploads/2012/06/IMG_3534-1024x764.jpg" [1]=> int(640) [2]=> int(477) [3]=> bool(true) }
474
		 */
475
476
		$medium_file_info = wp_get_attachment_image_src( $attachment_id, 'medium' );
477
		$medium_file      = isset( $medium_file_info[0] ) ? $medium_file_info[0] : '';
478
479
		$large_file_info = wp_get_attachment_image_src( $attachment_id, 'large' );
480
		$large_file      = isset( $large_file_info[0] ) ? $large_file_info[0] : '';
481
482
		$attachment       = get_post( $attachment_id );
483
		$attachment_title = wptexturize( $attachment->post_title );
484
		$attachment_desc  = wpautop( wptexturize( $attachment->post_content ) );
485
		// Not yet providing geo-data, need to "fuzzify" for privacy
486 View Code Duplication
		if ( ! empty( $img_meta ) ) {
487
			foreach ( $img_meta as $k => $v ) {
488
				if ( 'latitude' == $k || 'longitude' == $k ) {
489
					unset( $img_meta[ $k ] );
490
				}
491
			}
492
		}
493
494
		// See https://github.com/Automattic/jetpack/issues/2765
495
		if ( isset( $img_meta['keywords'] ) ) {
496
			unset( $img_meta['keywords'] );
497
		}
498
499
		$img_meta = json_encode( array_map( 'strval', array_filter( $img_meta, 'is_scalar' ) ) );
500
501
		$attr['data-attachment-id']     = $attachment_id;
502
		$attr['data-permalink']         = esc_attr( get_permalink( $attachment->ID ) );
503
		$attr['data-orig-file']         = esc_attr( $orig_file );
504
		$attr['data-orig-size']         = $size;
505
		$attr['data-comments-opened']   = $comments_opened;
506
		$attr['data-image-meta']        = esc_attr( $img_meta );
507
		$attr['data-image-title']       = esc_attr( htmlspecialchars( $attachment_title ) );
508
		$attr['data-image-description'] = esc_attr( htmlspecialchars( $attachment_desc ) );
509
		$attr['data-medium-file']       = esc_attr( $medium_file );
510
		$attr['data-large-file']        = esc_attr( $large_file );
511
512
		return $attr;
513
	}
514
515
	function add_data_to_container( $html ) {
516
		global $post;
517
		if (
518
			class_exists( 'Jetpack_AMP_Support' )
519
			&& Jetpack_AMP_Support::is_amp_request()
520
		) {
521
			return $html;
522
		}
523
524
		if ( isset( $post ) ) {
525
			$blog_id = (int) get_current_blog_id();
526
527
			$extra_data = array(
528
				'data-carousel-extra' => array(
529
					'blog_id'   => $blog_id,
530
					'permalink' => get_permalink( $post->ID ),
531
				),
532
			);
533
534
			/**
535
			 * Filter the data added to the Gallery container.
536
			 *
537
			 * @module carousel
538
			 *
539
			 * @since 1.6.0
540
			 *
541
			 * @param array $extra_data Array of data about the site and the post.
542
			 */
543
			$extra_data = apply_filters( 'jp_carousel_add_data_to_container', $extra_data );
544
			foreach ( (array) $extra_data as $data_key => $data_values ) {
545
				$html = str_replace( '<div ', '<div ' . esc_attr( $data_key ) . "='" . json_encode( $data_values ) . "' ", $html );
546
				$html = str_replace( '<ul class="wp-block-gallery', '<ul ' . esc_attr( $data_key ) . "='" . json_encode( $data_values ) . "' class=\"wp-block-gallery", $html );
547
				$html = str_replace( '<ul class="blocks-gallery-grid', '<ul ' . esc_attr( $data_key ) . "='" . json_encode( $data_values ) . "' class=\"blocks-gallery-grid", $html );
548
			}
549
		}
550
551
		return $html;
552
	}
553
554
	function get_attachment_comments() {
555
		if ( ! headers_sent() ) {
556
			header( 'Content-type: text/javascript' );
557
		}
558
559
		/**
560
		 * Allows for the checking of privileges of the blog user before comments
561
		 * are packaged as JSON and sent back from the get_attachment_comments
562
		 * AJAX endpoint
563
		 *
564
		 * @module carousel
565
		 *
566
		 * @since 1.6.0
567
		 */
568
		do_action( 'jp_carousel_check_blog_user_privileges' );
569
570
		$attachment_id = ( isset( $_REQUEST['id'] ) ) ? (int) $_REQUEST['id'] : 0;
571
		$offset        = ( isset( $_REQUEST['offset'] ) ) ? (int) $_REQUEST['offset'] : 0;
572
573
		if ( ! $attachment_id ) {
574
			echo json_encode( __( 'Missing attachment ID.', 'jetpack' ) );
575
			die();
576
		}
577
578
		if ( $offset < 1 ) {
579
			$offset = 0;
580
		}
581
582
		$comments = get_comments(
583
			array(
584
				'status'  => 'approve',
585
				'order'   => ( 'asc' == get_option( 'comment_order' ) ) ? 'ASC' : 'DESC',
586
				'number'  => 10,
587
				'offset'  => $offset,
588
				'post_id' => $attachment_id,
589
			)
590
		);
591
592
		$out = array();
593
594
		// Can't just send the results, they contain the commenter's email address.
595
		foreach ( $comments as $comment ) {
596
			$avatar = get_avatar( $comment->comment_author_email, 64 );
597
			if ( ! $avatar ) {
598
				$avatar = '';
599
			}
600
			$out[] = array(
601
				'id'              => $comment->comment_ID,
602
				'parent_id'       => $comment->comment_parent,
603
				'author_markup'   => get_comment_author_link( $comment->comment_ID ),
604
				'gravatar_markup' => $avatar,
605
				'date_gmt'        => $comment->comment_date_gmt,
606
				'content'         => wpautop( $comment->comment_content ),
607
			);
608
		}
609
610
		die( json_encode( $out ) );
611
	}
612
613
	function post_attachment_comment() {
614
		if ( ! headers_sent() ) {
615
			header( 'Content-type: text/javascript' );
616
		}
617
618
		if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'carousel_nonce' ) ) {
619
			die( json_encode( array( 'error' => __( 'Nonce verification failed.', 'jetpack' ) ) ) );
620
		}
621
622
		$_blog_id = (int) $_POST['blog_id'];
623
		$_post_id = (int) $_POST['id'];
624
		$comment  = $_POST['comment'];
625
626
		if ( empty( $_blog_id ) ) {
627
			die( json_encode( array( 'error' => __( 'Missing target blog ID.', 'jetpack' ) ) ) );
628
		}
629
630
		if ( empty( $_post_id ) ) {
631
			die( json_encode( array( 'error' => __( 'Missing target post ID.', 'jetpack' ) ) ) );
632
		}
633
634
		if ( empty( $comment ) ) {
635
			die( json_encode( array( 'error' => __( 'No comment text was submitted.', 'jetpack' ) ) ) );
636
		}
637
638
		// Used in context like NewDash
639
		$switched = false;
640
		if ( is_multisite() && $_blog_id != get_current_blog_id() ) {
641
			switch_to_blog( $_blog_id );
642
			$switched = true;
643
		}
644
645
		/** This action is documented in modules/carousel/jetpack-carousel.php */
646
		do_action( 'jp_carousel_check_blog_user_privileges' );
647
648
		if ( ! comments_open( $_post_id ) ) {
649
			die( json_encode( array( 'error' => __( 'Comments on this post are closed.', 'jetpack' ) ) ) );
650
		}
651
652
		if ( is_user_logged_in() ) {
653
			$user         = wp_get_current_user();
654
			$user_id      = $user->ID;
655
			$display_name = $user->display_name;
656
			$email        = $user->user_email;
657
			$url          = $user->user_url;
658
659
			if ( empty( $user_id ) ) {
660
				die( json_encode( array( 'error' => __( 'Sorry, but we could not authenticate your request.', 'jetpack' ) ) ) );
661
			}
662
		} else {
663
			$user_id      = 0;
664
			$display_name = $_POST['author'];
665
			$email        = $_POST['email'];
666
			$url          = $_POST['url'];
667
668
			if ( get_option( 'require_name_email' ) ) {
669
				if ( empty( $display_name ) ) {
670
					die( json_encode( array( 'error' => __( 'Please provide your name.', 'jetpack' ) ) ) );
671
				}
672
673
				if ( empty( $email ) ) {
674
					die( json_encode( array( 'error' => __( 'Please provide an email address.', 'jetpack' ) ) ) );
675
				}
676
677
				if ( ! is_email( $email ) ) {
678
					die( json_encode( array( 'error' => __( 'Please provide a valid email address.', 'jetpack' ) ) ) );
679
				}
680
			}
681
		}
682
683
		$comment_data = array(
684
			'comment_content'      => $comment,
685
			'comment_post_ID'      => $_post_id,
686
			'comment_author'       => $display_name,
687
			'comment_author_email' => $email,
688
			'comment_author_url'   => $url,
689
			'comment_approved'     => 0,
690
			'comment_type'         => 'comment',
691
		);
692
693
		if ( ! empty( $user_id ) ) {
694
			$comment_data['user_id'] = $user_id;
695
		}
696
697
		// Note: wp_new_comment() sanitizes and validates the values (too).
698
		$comment_id = wp_new_comment( $comment_data );
699
700
		/**
701
		 * Fires before adding a new comment to the database via the get_attachment_comments ajax endpoint.
702
		 *
703
		 * @module carousel
704
		 *
705
		 * @since 1.6.0
706
		 */
707
		do_action( 'jp_carousel_post_attachment_comment' );
708
		$comment_status = wp_get_comment_status( $comment_id );
709
710
		if ( true == $switched ) {
711
			restore_current_blog();
712
		}
713
714
		die(
715
			json_encode(
716
				array(
717
					'comment_id'     => $comment_id,
718
					'comment_status' => $comment_status,
719
				)
720
			)
721
		);
722
	}
723
724
	function register_settings() {
725
		add_settings_section( 'carousel_section', __( 'Image Gallery Carousel', 'jetpack' ), array( $this, 'carousel_section_callback' ), 'media' );
726
727
		if ( ! $this->in_jetpack ) {
728
			add_settings_field( 'carousel_enable_it', __( 'Enable carousel', 'jetpack' ), array( $this, 'carousel_enable_it_callback' ), 'media', 'carousel_section' );
729
			register_setting( 'media', 'carousel_enable_it', array( $this, 'carousel_enable_it_sanitize' ) );
730
		}
731
732
		add_settings_field( 'carousel_background_color', __( 'Background color', 'jetpack' ), array( $this, 'carousel_background_color_callback' ), 'media', 'carousel_section' );
733
		register_setting( 'media', 'carousel_background_color', array( $this, 'carousel_background_color_sanitize' ) );
734
735
		add_settings_field( 'carousel_display_exif', __( 'Metadata', 'jetpack' ), array( $this, 'carousel_display_exif_callback' ), 'media', 'carousel_section' );
736
		register_setting( 'media', 'carousel_display_exif', array( $this, 'carousel_display_exif_sanitize' ) );
737
738
		// No geo setting yet, need to "fuzzify" data first, for privacy
739
		// add_settings_field('carousel_display_geo', __( 'Geolocation', 'jetpack' ), array( $this, 'carousel_display_geo_callback' ), 'media', 'carousel_section' );
740
		// register_setting( 'media', 'carousel_display_geo', array( $this, 'carousel_display_geo_sanitize' ) );
741
	}
742
743
	// Fulfill the settings section callback requirement by returning nothing
744
	function carousel_section_callback() {
745
		return;
746
	}
747
748
	function test_1or0_option( $value, $default_to_1 = true ) {
749
		if ( true == $default_to_1 ) {
750
			// Binary false (===) of $value means it has not yet been set, in which case we do want to default sites to 1
751
			if ( false === $value ) {
752
				$value = 1;
753
			}
754
		}
755
		return ( 1 == $value ) ? 1 : 0;
756
	}
757
758
	function sanitize_1or0_option( $value ) {
759
		return ( 1 == $value ) ? 1 : 0;
760
	}
761
762
	function settings_checkbox( $name, $label_text, $extra_text = '', $default_to_checked = true ) {
763
		if ( empty( $name ) ) {
764
			return;
765
		}
766
		$option = $this->test_1or0_option( get_option( $name ), $default_to_checked );
767
		echo '<fieldset>';
768
		echo '<input type="checkbox" name="' . esc_attr( $name ) . '" id="' . esc_attr( $name ) . '" value="1" ';
769
		checked( '1', $option );
770
		echo '/> <label for="' . esc_attr( $name ) . '">' . $label_text . '</label>';
771
		if ( ! empty( $extra_text ) ) {
772
			echo '<p class="description">' . $extra_text . '</p>';
773
		}
774
		echo '</fieldset>';
775
	}
776
777
	function settings_select( $name, $values, $extra_text = '' ) {
778
		if ( empty( $name ) || ! is_array( $values ) || empty( $values ) ) {
779
			return;
780
		}
781
		$option = get_option( $name );
782
		echo '<fieldset>';
783
		echo '<select name="' . esc_attr( $name ) . '" id="' . esc_attr( $name ) . '">';
784
		foreach ( $values as $key => $value ) {
785
			echo '<option value="' . esc_attr( $key ) . '" ';
786
			selected( $key, $option );
787
			echo '>' . esc_html( $value ) . '</option>';
788
		}
789
		echo '</select>';
790
		if ( ! empty( $extra_text ) ) {
791
			echo '<p class="description">' . $extra_text . '</p>';
792
		}
793
		echo '</fieldset>';
794
	}
795
796
	function carousel_display_exif_callback() {
797
		$this->settings_checkbox( 'carousel_display_exif', __( 'Show photo metadata (<a href="https://en.wikipedia.org/wiki/Exchangeable_image_file_format" rel="noopener noreferrer" target="_blank">Exif</a>) in carousel, when available.', 'jetpack' ) );
798
	}
799
800
	function carousel_display_exif_sanitize( $value ) {
801
		return $this->sanitize_1or0_option( $value );
802
	}
803
804
	function carousel_display_geo_callback() {
805
		$this->settings_checkbox( 'carousel_display_geo', __( 'Show map of photo location in carousel, when available.', 'jetpack' ) );
806
	}
807
808
	function carousel_display_geo_sanitize( $value ) {
809
		return $this->sanitize_1or0_option( $value );
810
	}
811
812
	function carousel_background_color_callback() {
813
		$this->settings_select(
814
			'carousel_background_color', array(
815
				'black' => __( 'Black', 'jetpack' ),
816
				'white' => __( 'White', 'jetpack' ),
817
			)
818
		);
819
	}
820
821
	function carousel_background_color_sanitize( $value ) {
822
		return ( 'white' == $value ) ? 'white' : 'black';
823
	}
824
825
	function carousel_enable_it_callback() {
826
		$this->settings_checkbox( 'carousel_enable_it', __( 'Display images in full-size carousel slideshow.', 'jetpack' ) );
827
	}
828
829
	function carousel_enable_it_sanitize( $value ) {
830
		return $this->sanitize_1or0_option( $value );
831
	}
832
}
833
834
new Jetpack_Carousel;
835