Completed
Push — fix/13531-carousel-markup ( 2df577 )
by
unknown
06:27
created

Jetpack_Carousel::add_data_to_container()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
use Automattic\Jetpack\Assets;
3
/*
4
Plugin Name: Jetpack Carousel
5
Plugin URL: https://wordpress.com/
6
Description: Transform your standard image galleries into an immersive full-screen experience.
7
Version: 0.1
8
Author: Automattic
9
10
Released under the GPL v.2 license.
11
12
This program is distributed in the hope that it will be useful,
13
but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
GNU General Public License for more details.
16
*/
17
class Jetpack_Carousel {
18
19
	public $prebuilt_widths = array( 370, 700, 1000, 1200, 1400, 2000 );
20
21
	public $first_run = true;
22
23
	public $in_gallery = false;
24
25
	public $in_jetpack = true;
26
27
	public $single_image_gallery_enabled = false;
28
29
	public $single_image_gallery_enabled_media_file = false;
30
31
	function __construct() {
32
		add_action( 'init', array( $this, 'init' ) );
33
	}
34
35
	function init() {
36
		if ( $this->maybe_disable_jp_carousel() ) {
37
			return;
38
		}
39
40
		$this->in_jetpack = ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'enable_module_configurable' ) ) ? true : false;
41
42
		$this->single_image_gallery_enabled            = ! $this->maybe_disable_jp_carousel_single_images();
43
		$this->single_image_gallery_enabled_media_file = $this->maybe_enable_jp_carousel_single_images_media_file();
44
45
		if ( is_admin() ) {
46
			// Register the Carousel-related related settings
47
			add_action( 'admin_init', array( $this, 'register_settings' ), 5 );
48
			if ( ! $this->in_jetpack ) {
49
				if ( 0 == $this->test_1or0_option( get_option( 'carousel_enable_it' ), true ) ) {
50
					return; // Carousel disabled, abort early, but still register setting so user can switch it back on
51
				}
52
			}
53
			// If in admin, register the ajax endpoints.
54
			add_action( 'wp_ajax_get_attachment_comments', array( $this, 'get_attachment_comments' ) );
55
			add_action( 'wp_ajax_nopriv_get_attachment_comments', array( $this, 'get_attachment_comments' ) );
56
			add_action( 'wp_ajax_post_attachment_comment', array( $this, 'post_attachment_comment' ) );
57
			add_action( 'wp_ajax_nopriv_post_attachment_comment', array( $this, 'post_attachment_comment' ) );
58
		} else {
59
			if ( ! $this->in_jetpack ) {
60
				if ( 0 == $this->test_1or0_option( get_option( 'carousel_enable_it' ), true ) ) {
61
					return; // Carousel disabled, abort early
62
				}
63
			}
64
			// If on front-end, do the Carousel thang.
65
			/**
66
			 * Filter the array of default prebuilt widths used in Carousel.
67
			 *
68
			 * @module carousel
69
			 *
70
			 * @since 1.6.0
71
			 *
72
			 * @param array $this->prebuilt_widths Array of default widths.
73
			 */
74
			$this->prebuilt_widths = apply_filters( 'jp_carousel_widths', $this->prebuilt_widths );
75
			// below: load later than other callbacks hooked it (e.g. 3rd party plugins handling gallery shortcode)
76
			add_filter( 'post_gallery', array( $this, 'check_if_shortcode_processed_and_enqueue_assets' ), 1000, 2 );
77
			add_filter( 'post_gallery', array( $this, 'set_in_gallery' ), -1000 );
78
			add_filter( 'gallery_style', array( $this, 'add_data_to_container' ) );
79
			add_filter( 'wp_get_attachment_image_attributes', array( $this, 'add_data_to_images' ), 10, 2 );
80
			add_filter( 'the_content', array( $this, 'check_content_for_blocks' ), 1 );
81
			add_filter( 'jetpack_tiled_galleries_block_content', array( $this, 'add_data_img_tags_and_enqueue_assets' ) );
82
			if ( $this->single_image_gallery_enabled ) {
83
				add_filter( 'the_content', array( $this, 'add_data_img_tags_and_enqueue_assets' ) );
84
			}
85
		}
86
87
		if ( $this->in_jetpack ) {
88
			Jetpack::enable_module_configurable( dirname( dirname( __FILE__ ) ) . '/carousel.php' );
89
		}
90
	}
91
92
	function maybe_disable_jp_carousel() {
93
		/**
94
		 * Allow third-party plugins or themes to disable Carousel.
95
		 *
96
		 * @module carousel
97
		 *
98
		 * @since 1.6.0
99
		 *
100
		 * @param bool false Should Carousel be disabled? Default to false.
101
		 */
102
		return apply_filters( 'jp_carousel_maybe_disable', false );
103
	}
104
105
	function maybe_disable_jp_carousel_single_images() {
106
		/**
107
		 * Allow third-party plugins or themes to disable Carousel for single images.
108
		 *
109
		 * @module carousel
110
		 *
111
		 * @since 4.5.0
112
		 *
113
		 * @param bool false Should Carousel be disabled for single images? Default to false.
114
		 */
115
		return apply_filters( 'jp_carousel_maybe_disable_single_images', false );
116
	}
117
118
	function maybe_enable_jp_carousel_single_images_media_file() {
119
		/**
120
		 * Allow third-party plugins or themes to enable Carousel
121
		 * for single images linking to 'Media File' (full size image).
122
		 *
123
		 * @module carousel
124
		 *
125
		 * @since 4.5.0
126
		 *
127
		 * @param bool false Should Carousel be enabled for single images linking to 'Media File'? Default to false.
128
		 */
129
		return apply_filters( 'jp_carousel_load_for_images_linked_to_file', false );
130
	}
131
132
	function asset_version( $version ) {
133
		/**
134
		 * Filter the version string used when enqueuing Carousel assets.
135
		 *
136
		 * @module carousel
137
		 *
138
		 * @since 1.6.0
139
		 *
140
		 * @param string $version Asset version.
141
		 */
142
		return apply_filters( 'jp_carousel_asset_version', $version );
143
	}
144
145
	function display_bail_message( $output = '' ) {
146
		// Displays a message on top of gallery if carousel has bailed
147
		$message  = '<div class="jp-carousel-msg"><p>';
148
		$message .= __( 'Jetpack\'s Carousel has been disabled, because another plugin or your theme is overriding the [gallery] shortcode.', 'jetpack' );
149
		$message .= '</p></div>';
150
		// put before gallery output
151
		$output = $message . $output;
152
		return $output;
153
	}
154
155
	function check_if_shortcode_processed_and_enqueue_assets( $output ) {
156
		if (
157
			class_exists( 'Jetpack_AMP_Support' )
158
			&& Jetpack_AMP_Support::is_amp_request()
159
		) {
160
			return $output;
161
		}
162
163
		if (
164
			! empty( $output ) &&
165
			/**
166
			 * Allow third-party plugins or themes to force-enable Carousel.
167
			 *
168
			 * @module carousel
169
			 *
170
			 * @since 1.9.0
171
			 *
172
			 * @param bool false Should we force enable Carousel? Default to false.
173
			 */
174
			! apply_filters( 'jp_carousel_force_enable', false )
175
		) {
176
			// Bail because someone is overriding the [gallery] shortcode.
177
			remove_filter( 'gallery_style', array( $this, 'add_data_to_container' ) );
178
			remove_filter( 'wp_get_attachment_image_attributes', array( $this, 'add_data_to_images' ) );
179
			remove_filter( 'the_content', array( $this, 'add_data_img_tags_and_enqueue_assets' ) );
180
			// Display message that carousel has bailed, if user is super_admin, and if we're not on WordPress.com.
181
			if (
182
				is_super_admin() &&
183
				! ( defined( 'IS_WPCOM' ) && IS_WPCOM )
184
			) {
185
				add_filter( 'post_gallery', array( $this, 'display_bail_message' ) );
186
			}
187
			return $output;
188
		}
189
190
		/**
191
		 * Fires when thumbnails are shown in Carousel.
192
		 *
193
		 * @module carousel
194
		 *
195
		 * @since 1.6.0
196
		 **/
197
		do_action( 'jp_carousel_thumbnails_shown' );
198
199
		$this->enqueue_assets();
200
201
		return $output;
202
	}
203
204
	/**
205
	 * Check if the content of a post uses gallery blocks. To be used by 'the_content' filter.
206
	 *
207
	 * @since 6.8.0
208
	 *
209
	 * @param string $content Post content.
210
	 *
211
	 * @return string $content Post content.
212
	 */
213
	function check_content_for_blocks( $content ) {
214
		if (
215
			class_exists( 'Jetpack_AMP_Support' )
216
			&& Jetpack_AMP_Support::is_amp_request()
217
		) {
218
			return $content;
219
		}
220
221
		if ( has_block( 'gallery', $content ) || has_block( 'jetpack/tiled-gallery', $content ) ) {
222
			$this->enqueue_assets();
223
			$content = $this->add_data_to_html( $content );
224
		}
225
		return $content;
226
	}
227
228
	function enqueue_assets() {
229
		if ( $this->first_run ) {
230
			wp_enqueue_script(
231
				'jetpack-carousel',
232
				Assets::get_file_url_for_environment(
233
					'_inc/build/carousel/jetpack-carousel.min.js',
234
					'modules/carousel/jetpack-carousel.js'
235
				),
236
				array( 'jquery.spin' ),
237
				$this->asset_version( '20190102' ),
238
				true
239
			);
240
241
			// 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)
242
			// Also: not hardcoding path since there is no guarantee site is running on site root in self-hosted context.
243
			$is_logged_in         = is_user_logged_in();
244
			$current_user         = wp_get_current_user();
245
			$comment_registration = intval( get_option( 'comment_registration' ) );
246
			$require_name_email   = intval( get_option( 'require_name_email' ) );
247
			$localize_strings     = array(
248
				'widths'                          => $this->prebuilt_widths,
249
				'is_logged_in'                    => $is_logged_in,
250
				'lang'                            => strtolower( substr( get_locale(), 0, 2 ) ),
251
				'ajaxurl'                         => set_url_scheme( admin_url( 'admin-ajax.php' ) ),
252
				'nonce'                           => wp_create_nonce( 'carousel_nonce' ),
253
				'display_exif'                    => $this->test_1or0_option( Jetpack_Options::get_option_and_ensure_autoload( 'carousel_display_exif', true ) ),
254
				'display_geo'                     => $this->test_1or0_option( Jetpack_Options::get_option_and_ensure_autoload( 'carousel_display_geo', true ) ),
255
				'single_image_gallery'            => $this->single_image_gallery_enabled,
256
				'single_image_gallery_media_file' => $this->single_image_gallery_enabled_media_file,
257
				'background_color'                => $this->carousel_background_color_sanitize( Jetpack_Options::get_option_and_ensure_autoload( 'carousel_background_color', '' ) ),
258
				'comment'                         => __( 'Comment', 'jetpack' ),
259
				'post_comment'                    => __( 'Post Comment', 'jetpack' ),
260
				'write_comment'                   => __( 'Write a Comment...', 'jetpack' ),
261
				'loading_comments'                => __( 'Loading Comments...', 'jetpack' ),
262
				'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}' ),
263
				'no_comment_text'                 => __( 'Please be sure to submit some text with your comment.', 'jetpack' ),
264
				'no_comment_email'                => __( 'Please provide an email address to comment.', 'jetpack' ),
265
				'no_comment_author'               => __( 'Please provide your name to comment.', 'jetpack' ),
266
				'comment_post_error'              => __( 'Sorry, but there was an error posting your comment. Please try again later.', 'jetpack' ),
267
				'comment_approved'                => __( 'Your comment was approved.', 'jetpack' ),
268
				'comment_unapproved'              => __( 'Your comment is in moderation.', 'jetpack' ),
269
				'camera'                          => __( 'Camera', 'jetpack' ),
270
				'aperture'                        => __( 'Aperture', 'jetpack' ),
271
				'shutter_speed'                   => __( 'Shutter Speed', 'jetpack' ),
272
				'focal_length'                    => __( 'Focal Length', 'jetpack' ),
273
				'copyright'                       => __( 'Copyright', 'jetpack' ),
274
				'comment_registration'            => $comment_registration,
275
				'require_name_email'              => $require_name_email,
276
				/** This action is documented in core/src/wp-includes/link-template.php */
277
				'login_url'                       => wp_login_url( apply_filters( 'the_permalink', get_permalink() ) ),
278
				'blog_id'                         => (int) get_current_blog_id(),
279
				'meta_data'                       => array( 'camera', 'aperture', 'shutter_speed', 'focal_length', 'copyright' ),
280
			);
281
282
			if ( ! isset( $localize_strings['jetpack_comments_iframe_src'] ) || empty( $localize_strings['jetpack_comments_iframe_src'] ) ) {
283
				// We're not using Comments after all, so fallback to standard local comments.
284
285
				if ( $is_logged_in ) {
286
					$localize_strings['local_comments_commenting_as'] = '<p id="jp-carousel-commenting-as">' . sprintf( __( 'Commenting as %s', 'jetpack' ), $current_user->data->display_name ) . '</p>';
287
				} else {
288
					if ( $comment_registration ) {
289
						$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>';
290
					} else {
291
						$required = ( $require_name_email ) ? __( '%s (Required)', 'jetpack' ) : '%s';
292
						$localize_strings['local_comments_commenting_as'] = ''
293
							. '<fieldset><label for="email">' . sprintf( $required, __( 'Email', 'jetpack' ) ) . '</label> '
294
							. '<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>'
295
							. '<fieldset><label for="author">' . sprintf( $required, __( 'Name', 'jetpack' ) ) . '</label> '
296
							. '<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>'
297
							. '<fieldset><label for="url">' . __( 'Website', 'jetpack' ) . '</label> '
298
							. '<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>';
299
					}
300
				}
301
			}
302
303
			/**
304
			 * Handle WP stats for images in full-screen.
305
			 * Build string with tracking info.
306
			 */
307
308
			/**
309
			 * Filter if Jetpack should enable stats collection on carousel views
310
			 *
311
			 * @module carousel
312
			 *
313
			 * @since 4.3.2
314
			 *
315
			 * @param bool Enable Jetpack Carousel stat collection. Default false.
316
			 */
317
			if ( apply_filters( 'jetpack_enable_carousel_stats', false ) && in_array( 'stats', Jetpack::get_active_modules() ) && ! Jetpack::is_development_mode() ) {
318
				$localize_strings['stats'] = 'blog=' . Jetpack_Options::get_option( 'id' ) . '&host=' . parse_url( get_option( 'home' ), PHP_URL_HOST ) . '&v=ext&j=' . JETPACK__API_VERSION . ':' . JETPACK__VERSION;
319
320
				// Set the stats as empty if user is logged in but logged-in users shouldn't be tracked.
321 View Code Duplication
				if ( is_user_logged_in() && function_exists( 'stats_get_options' ) ) {
322
					$stats_options        = stats_get_options();
323
					$track_loggedin_users = isset( $stats_options['reg_users'] ) ? (bool) $stats_options['reg_users'] : false;
324
325
					if ( ! $track_loggedin_users ) {
326
						$localize_strings['stats'] = '';
327
					}
328
				}
329
			}
330
331
			/**
332
			 * Filter the strings passed to the Carousel's js file.
333
			 *
334
			 * @module carousel
335
			 *
336
			 * @since 1.6.0
337
			 *
338
			 * @param array $localize_strings Array of strings passed to the Jetpack js file.
339
			 */
340
			$localize_strings = apply_filters( 'jp_carousel_localize_strings', $localize_strings );
341
			wp_localize_script( 'jetpack-carousel', 'jetpackCarouselStrings', $localize_strings );
342
			wp_enqueue_style( 'jetpack-carousel', plugins_url( 'jetpack-carousel.css', __FILE__ ), array(), $this->asset_version( '20120629' ) );
343
			wp_style_add_data( 'jetpack-carousel', 'rtl', 'replace' );
344
345
			/**
346
			 * Fires after carousel assets are enqueued for the first time.
347
			 * Allows for adding additional assets to the carousel page.
348
			 *
349
			 * @module carousel
350
			 *
351
			 * @since 1.6.0
352
			 *
353
			 * @param bool $first_run First load if Carousel on the page.
354
			 * @param array $localized_strings Array of strings passed to the Jetpack js file.
355
			 */
356
			do_action( 'jp_carousel_enqueue_assets', $this->first_run, $localize_strings );
357
358
			$this->first_run = false;
359
		}
360
	}
361
362
	function set_in_gallery( $output ) {
363
		if (
364
			class_exists( 'Jetpack_AMP_Support' )
365
			&& Jetpack_AMP_Support::is_amp_request()
366
		) {
367
			return $output;
368
		}
369
		$this->in_gallery = true;
370
		return $output;
371
	}
372
373
	/**
374
	 * Adds data-* attributes required by carousel to img tags in post HTML
375
	 * content. To be used by 'the_content' filter.
376
	 *
377
	 * @see add_data_to_images()
378
	 * @see wp_make_content_images_responsive() in wp-includes/media.php
379
	 *
380
	 * @param string $content HTML content of the post
381
	 * @return string Modified HTML content of the post
382
	 */
383
	function add_data_img_tags_and_enqueue_assets( $content ) {
384
		if (
385
			class_exists( 'Jetpack_AMP_Support' )
386
			&& Jetpack_AMP_Support::is_amp_request()
387
		) {
388
			return $content;
389
		}
390
391
		if ( ! preg_match_all( '/<img [^>]+>/', $content, $matches ) ) {
392
			return $content;
393
		}
394
		$selected_images = array();
395
		foreach ( $matches[0] as $image_html ) {
396
			if ( preg_match( '/(wp-image-|data-id=)\"?([0-9]+)\"?/i', $image_html, $class_id ) &&
397
				! preg_match( '/wp-block-jetpack-slideshow_image/', $image_html ) ) {
398
				$attachment_id = absint( $class_id[2] );
399
				/**
400
				 * If exactly the same image tag is used more than once, overwrite it.
401
				 * All identical tags will be replaced later with 'str_replace()'.
402
				 */
403
				$selected_images[ $attachment_id  ] = $image_html;
404
			}
405
		}
406
407
		$find    = array();
408
		$replace = array();
409
		if ( empty( $selected_images ) ) {
410
			return $content;
411
		}
412
413
		$attachments = get_posts(
414
			array(
415
				'include'          => array_keys( $selected_images ),
416
				'post_type'        => 'any',
417
				'post_status'      => 'any',
418
				'suppress_filters' => false,
419
			)
420
		);
421
422
		foreach ( $attachments as $attachment ) {
423
			$image_html = $selected_images[ $attachment->ID ];
424
425
			$attributes      = $this->add_data_to_images( array(), $attachment );
426
			$attributes_html = '';
427
			foreach ( $attributes as $k => $v ) {
428
				$attributes_html .= esc_attr( $k ) . '="' . esc_attr( $v ) . '" ';
429
			}
430
431
			$find[]    = $image_html;
432
			$replace[] = str_replace( '<img ', "<img $attributes_html", $image_html );
433
		}
434
435
		$content = str_replace( $find, $replace, $content );
436
		$this->enqueue_assets();
437
		return $content;
438
	}
439
440
	function add_data_to_images( $attr, $attachment = null ) {
441
		if (
442
			class_exists( 'Jetpack_AMP_Support' )
443
			&& Jetpack_AMP_Support::is_amp_request()
444
		) {
445
			return $attr;
446
		}
447
448
		$attachment_id = intval( $attachment->ID );
449
		if ( ! wp_attachment_is_image( $attachment_id ) ) {
450
			return $attr;
451
		}
452
453
		$orig_file       = wp_get_attachment_image_src( $attachment_id, 'full' );
454
		$orig_file       = isset( $orig_file[0] ) ? $orig_file[0] : wp_get_attachment_url( $attachment_id );
455
		$meta            = wp_get_attachment_metadata( $attachment_id );
456
		$size            = isset( $meta['width'] ) ? intval( $meta['width'] ) . ',' . intval( $meta['height'] ) : '';
457
		$img_meta        = ( ! empty( $meta['image_meta'] ) ) ? (array) $meta['image_meta'] : array();
458
		$comments_opened = intval( comments_open( $attachment_id ) );
459
460
		/**
461
		 * Note: Cannot generate a filename from the width and height wp_get_attachment_image_src() returns because
462
		 * it takes the $content_width global variable themes can set in consideration, therefore returning sizes
463
		 * which when used to generate a filename will likely result in a 404 on the image.
464
		 * $content_width has no filter we could temporarily de-register, run wp_get_attachment_image_src(), then
465
		 * re-register. So using returned file URL instead, which we can define the sizes from through filename
466
		 * parsing in the JS, as this is a failsafe file reference.
467
		 *
468
		 * EG with Twenty Eleven activated:
469
		 * 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) }
470
		 *
471
		 * EG with Twenty Ten activated:
472
		 * 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) }
473
		 */
474
475
		$medium_file_info = wp_get_attachment_image_src( $attachment_id, 'medium' );
476
		$medium_file      = isset( $medium_file_info[0] ) ? $medium_file_info[0] : '';
477
478
		$large_file_info = wp_get_attachment_image_src( $attachment_id, 'large' );
479
		$large_file      = isset( $large_file_info[0] ) ? $large_file_info[0] : '';
480
481
		$attachment       = get_post( $attachment_id );
482
		$attachment_title = wptexturize( $attachment->post_title );
483
		$attachment_desc  = wpautop( wptexturize( $attachment->post_content ) );
484
		// Not yet providing geo-data, need to "fuzzify" for privacy
485 View Code Duplication
		if ( ! empty( $img_meta ) ) {
486
			foreach ( $img_meta as $k => $v ) {
487
				if ( 'latitude' == $k || 'longitude' == $k ) {
488
					unset( $img_meta[ $k ] );
489
				}
490
			}
491
		}
492
493
		// See https://github.com/Automattic/jetpack/issues/2765
494
		if ( isset( $img_meta['keywords'] ) ) {
495
			unset( $img_meta['keywords'] );
496
		}
497
498
		$img_meta = json_encode( array_map( 'strval', array_filter( $img_meta, 'is_scalar' ) ) );
499
500
		$attr['data-attachment-id']     = $attachment_id;
501
		$attr['data-permalink']         = esc_attr( get_permalink( $attachment->ID ) );
502
		$attr['data-orig-file']         = esc_attr( $orig_file );
503
		$attr['data-orig-size']         = $size;
504
		$attr['data-comments-opened']   = $comments_opened;
505
		$attr['data-image-meta']        = esc_attr( $img_meta );
506
		$attr['data-image-title']       = esc_attr( htmlspecialchars( $attachment_title ) );
507
		$attr['data-image-description'] = esc_attr( htmlspecialchars( $attachment_desc ) );
508
		$attr['data-medium-file']       = esc_attr( $medium_file );
509
		$attr['data-large-file']        = esc_attr( $large_file );
510
511
		return $attr;
512
	}
513
514
	function add_data_to_container( $gallery_style ) {
515
		$gallery_style = $this->add_data_to_html( $gallery_style );
516
		return preg_replace( '#</div>\s*+$#', '', $gallery_style );
517
	}
518
519
	function add_data_to_html( $html ) {
520
		global $post;
521
		if (
522
			class_exists( 'Jetpack_AMP_Support' )
523
			&& Jetpack_AMP_Support::is_amp_request()
524
		) {
525
			return $html;
526
		}
527
528
		if ( isset( $post ) ) {
529
			$blog_id = (int) get_current_blog_id();
530
531
			$extra_data = array(
532
				'data-carousel-extra' => array(
533
					'blog_id'   => $blog_id,
534
					'permalink' => get_permalink( $post->ID ),
535
				),
536
			);
537
538
			/**
539
			 * Filter the data added to the Gallery container.
540
			 *
541
			 * @module carousel
542
			 *
543
			 * @since 1.6.0
544
			 *
545
			 * @param array $extra_data Array of data about the site and the post.
546
			 */
547
			$extra_data = apply_filters( 'jp_carousel_add_data_to_container', $extra_data );
548
549
			foreach ( (array) $extra_data as $data_key => $data_values ) {
550
				// Do not go any further if DOMDocument is disabled on the server.
551
				if ( ! class_exists( 'DOMDocument' ) ) {
552
					return $html;
553
				}
554
555
				$fake_root_tag = 'jetpack' . mt_rand( 10000, 99999 );
556
				$charset = get_option( 'blog_charset', 'utf-8' );
557
558
				// Let's grab all containers from the HTML.
559
				$dom_doc = new DOMDocument();
560
				$dom_doc->encoding = $charset;
561
562
				/*
563
				 * The @ is not enough to suppress errors when dealing with libxml,
564
				 * we have to tell it directly how we want to handle errors.
565
				 */
566
				$old_libxml_disable_entity_loader = libxml_disable_entity_loader( true );
567
				$old_libxml_use_internal_errors   = libxml_use_internal_errors( true );
568
				@$dom_doc->loadHTML( // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
569
					$a = sprintf(
570
						'<head><meta http-equiv="Content-Type" content="text/html; charset=%1$s"/></head><%2$s>%3$s</%2$s>',
571
						esc_attr( $charset ),
572
						tag_escape( $fake_root_tag ),
573
						$html
574
					)
575
				);
576
577
				libxml_use_internal_errors( $old_libxml_use_internal_errors );
578
				libxml_disable_entity_loader( $old_libxml_disable_entity_loader );
579
580
				// Let's look for lists and divs.
581
				$ul_tags  = $dom_doc->getElementsByTagName( 'ul' );
582
				$div_tags = $dom_doc->getElementsByTagName( 'div' );
583
584
				// Loop through each ul, and add the data to it if it is a gallery block.
585
				foreach ( $ul_tags as $ul_tag ) {
586
					if ( false !== strpos( $ul_tag->getAttribute( 'class' ), 'wp-block-gallery' ) ) {
587
						$ul_tag->setAttribute(
588
							$data_key,
589
							wp_json_encode( $data_values )
590
						);
591
					}
592
				}
593
594
				/*
595
				 * Loop through each div and add the data, only when it's a gallery block div.
596
				 * We want to avoid adding data to divs like wp-block-columns.
597
				 * We do however want data on divs like wp-block-jetpack-tiled-gallery.
598
				 */
599
				foreach ( $div_tags as $div_tag ) {
600
					if (
601
						false === strpos( $div_tag->getAttribute( 'class' ), 'wp-block-' )
602
						|| false !== strpos( $div_tag->getAttribute( 'class' ), 'gallery' )
603
					) {
604
						$div_tag->setAttribute(
605
							$data_key,
606
							wp_json_encode( $data_values )
607
						);
608
					}
609
				}
610
611
				// Save our updated HTML.
612
				$fake_root_tag_length = strlen( $fake_root_tag ) + 2;
613
				$html = substr(
614
					$dom_doc->saveHTML( $dom_doc->getElementsByTagName( $fake_root_tag )->item( 0 ) ),
615
					$fake_root_tag_length,
616
					-1 * ( $fake_root_tag_length + 1 )
617
				);
618
			}
619
		}
620
621
		return $html;
622
	}
623
624
	function get_attachment_comments() {
625
		if ( ! headers_sent() ) {
626
			header( 'Content-type: text/javascript' );
627
		}
628
629
		/**
630
		 * Allows for the checking of privileges of the blog user before comments
631
		 * are packaged as JSON and sent back from the get_attachment_comments
632
		 * AJAX endpoint
633
		 *
634
		 * @module carousel
635
		 *
636
		 * @since 1.6.0
637
		 */
638
		do_action( 'jp_carousel_check_blog_user_privileges' );
639
640
		$attachment_id = ( isset( $_REQUEST['id'] ) ) ? (int) $_REQUEST['id'] : 0;
641
		$offset        = ( isset( $_REQUEST['offset'] ) ) ? (int) $_REQUEST['offset'] : 0;
642
643
		if ( ! $attachment_id ) {
644
			echo json_encode( __( 'Missing attachment ID.', 'jetpack' ) );
645
			die();
646
		}
647
648
		if ( $offset < 1 ) {
649
			$offset = 0;
650
		}
651
652
		$comments = get_comments(
653
			array(
654
				'status'  => 'approve',
655
				'order'   => ( 'asc' == get_option( 'comment_order' ) ) ? 'ASC' : 'DESC',
656
				'number'  => 10,
657
				'offset'  => $offset,
658
				'post_id' => $attachment_id,
659
			)
660
		);
661
662
		$out = array();
663
664
		// Can't just send the results, they contain the commenter's email address.
665
		foreach ( $comments as $comment ) {
666
			$avatar = get_avatar( $comment->comment_author_email, 64 );
667
			if ( ! $avatar ) {
668
				$avatar = '';
669
			}
670
			$out[] = array(
671
				'id'              => $comment->comment_ID,
672
				'parent_id'       => $comment->comment_parent,
673
				'author_markup'   => get_comment_author_link( $comment->comment_ID ),
674
				'gravatar_markup' => $avatar,
675
				'date_gmt'        => $comment->comment_date_gmt,
676
				'content'         => wpautop( $comment->comment_content ),
677
			);
678
		}
679
680
		die( json_encode( $out ) );
681
	}
682
683
	function post_attachment_comment() {
684
		if ( ! headers_sent() ) {
685
			header( 'Content-type: text/javascript' );
686
		}
687
688
		if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'carousel_nonce' ) ) {
689
			die( json_encode( array( 'error' => __( 'Nonce verification failed.', 'jetpack' ) ) ) );
690
		}
691
692
		$_blog_id = (int) $_POST['blog_id'];
693
		$_post_id = (int) $_POST['id'];
694
		$comment  = $_POST['comment'];
695
696
		if ( empty( $_blog_id ) ) {
697
			die( json_encode( array( 'error' => __( 'Missing target blog ID.', 'jetpack' ) ) ) );
698
		}
699
700
		if ( empty( $_post_id ) ) {
701
			die( json_encode( array( 'error' => __( 'Missing target post ID.', 'jetpack' ) ) ) );
702
		}
703
704
		if ( empty( $comment ) ) {
705
			die( json_encode( array( 'error' => __( 'No comment text was submitted.', 'jetpack' ) ) ) );
706
		}
707
708
		// Used in context like NewDash
709
		$switched = false;
710
		if ( is_multisite() && $_blog_id != get_current_blog_id() ) {
711
			switch_to_blog( $_blog_id );
712
			$switched = true;
713
		}
714
715
		/** This action is documented in modules/carousel/jetpack-carousel.php */
716
		do_action( 'jp_carousel_check_blog_user_privileges' );
717
718
		if ( ! comments_open( $_post_id ) ) {
719
			die( json_encode( array( 'error' => __( 'Comments on this post are closed.', 'jetpack' ) ) ) );
720
		}
721
722
		if ( is_user_logged_in() ) {
723
			$user         = wp_get_current_user();
724
			$user_id      = $user->ID;
725
			$display_name = $user->display_name;
726
			$email        = $user->user_email;
727
			$url          = $user->user_url;
728
729
			if ( empty( $user_id ) ) {
730
				die( json_encode( array( 'error' => __( 'Sorry, but we could not authenticate your request.', 'jetpack' ) ) ) );
731
			}
732
		} else {
733
			$user_id      = 0;
734
			$display_name = $_POST['author'];
735
			$email        = $_POST['email'];
736
			$url          = $_POST['url'];
737
738
			if ( get_option( 'require_name_email' ) ) {
739
				if ( empty( $display_name ) ) {
740
					die( json_encode( array( 'error' => __( 'Please provide your name.', 'jetpack' ) ) ) );
741
				}
742
743
				if ( empty( $email ) ) {
744
					die( json_encode( array( 'error' => __( 'Please provide an email address.', 'jetpack' ) ) ) );
745
				}
746
747
				if ( ! is_email( $email ) ) {
748
					die( json_encode( array( 'error' => __( 'Please provide a valid email address.', 'jetpack' ) ) ) );
749
				}
750
			}
751
		}
752
753
		$comment_data = array(
754
			'comment_content'      => $comment,
755
			'comment_post_ID'      => $_post_id,
756
			'comment_author'       => $display_name,
757
			'comment_author_email' => $email,
758
			'comment_author_url'   => $url,
759
			'comment_approved'     => 0,
760
			'comment_type'         => '',
761
		);
762
763
		if ( ! empty( $user_id ) ) {
764
			$comment_data['user_id'] = $user_id;
765
		}
766
767
		// Note: wp_new_comment() sanitizes and validates the values (too).
768
		$comment_id = wp_new_comment( $comment_data );
769
770
		/**
771
		 * Fires before adding a new comment to the database via the get_attachment_comments ajax endpoint.
772
		 *
773
		 * @module carousel
774
		 *
775
		 * @since 1.6.0
776
		 */
777
		do_action( 'jp_carousel_post_attachment_comment' );
778
		$comment_status = wp_get_comment_status( $comment_id );
779
780
		if ( true == $switched ) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
781
			restore_current_blog();
782
		}
783
784
		die(
785
			json_encode(
786
				array(
787
					'comment_id'     => $comment_id,
788
					'comment_status' => $comment_status,
789
				)
790
			)
791
		);
792
	}
793
794
	function register_settings() {
795
		add_settings_section( 'carousel_section', __( 'Image Gallery Carousel', 'jetpack' ), array( $this, 'carousel_section_callback' ), 'media' );
796
797
		if ( ! $this->in_jetpack ) {
798
			add_settings_field( 'carousel_enable_it', __( 'Enable carousel', 'jetpack' ), array( $this, 'carousel_enable_it_callback' ), 'media', 'carousel_section' );
799
			register_setting( 'media', 'carousel_enable_it', array( $this, 'carousel_enable_it_sanitize' ) );
800
		}
801
802
		add_settings_field( 'carousel_background_color', __( 'Background color', 'jetpack' ), array( $this, 'carousel_background_color_callback' ), 'media', 'carousel_section' );
803
		register_setting( 'media', 'carousel_background_color', array( $this, 'carousel_background_color_sanitize' ) );
804
805
		add_settings_field( 'carousel_display_exif', __( 'Metadata', 'jetpack' ), array( $this, 'carousel_display_exif_callback' ), 'media', 'carousel_section' );
806
		register_setting( 'media', 'carousel_display_exif', array( $this, 'carousel_display_exif_sanitize' ) );
807
808
		// No geo setting yet, need to "fuzzify" data first, for privacy
809
		// add_settings_field('carousel_display_geo', __( 'Geolocation', 'jetpack' ), array( $this, 'carousel_display_geo_callback' ), 'media', 'carousel_section' );
810
		// register_setting( 'media', 'carousel_display_geo', array( $this, 'carousel_display_geo_sanitize' ) );
811
	}
812
813
	// Fulfill the settings section callback requirement by returning nothing
814
	function carousel_section_callback() {
815
		return;
816
	}
817
818
	function test_1or0_option( $value, $default_to_1 = true ) {
819
		if ( true == $default_to_1 ) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
820
			// Binary false (===) of $value means it has not yet been set, in which case we do want to default sites to 1
821
			if ( false === $value ) {
822
				$value = 1;
823
			}
824
		}
825
		return ( 1 == $value ) ? 1 : 0;
826
	}
827
828
	function sanitize_1or0_option( $value ) {
829
		return ( 1 == $value ) ? 1 : 0;
830
	}
831
832
	function settings_checkbox( $name, $label_text, $extra_text = '', $default_to_checked = true ) {
833
		if ( empty( $name ) ) {
834
			return;
835
		}
836
		$option = $this->test_1or0_option( get_option( $name ), $default_to_checked );
837
		echo '<fieldset>';
838
		echo '<input type="checkbox" name="' . esc_attr( $name ) . '" id="' . esc_attr( $name ) . '" value="1" ';
839
		checked( '1', $option );
840
		echo '/> <label for="' . esc_attr( $name ) . '">' . $label_text . '</label>';
841
		if ( ! empty( $extra_text ) ) {
842
			echo '<p class="description">' . $extra_text . '</p>';
843
		}
844
		echo '</fieldset>';
845
	}
846
847
	function settings_select( $name, $values, $extra_text = '' ) {
848
		if ( empty( $name ) || ! is_array( $values ) || empty( $values ) ) {
849
			return;
850
		}
851
		$option = get_option( $name );
852
		echo '<fieldset>';
853
		echo '<select name="' . esc_attr( $name ) . '" id="' . esc_attr( $name ) . '">';
854
		foreach ( $values as $key => $value ) {
855
			echo '<option value="' . esc_attr( $key ) . '" ';
856
			selected( $key, $option );
857
			echo '>' . esc_html( $value ) . '</option>';
858
		}
859
		echo '</select>';
860
		if ( ! empty( $extra_text ) ) {
861
			echo '<p class="description">' . $extra_text . '</p>';
862
		}
863
		echo '</fieldset>';
864
	}
865
866
	function carousel_display_exif_callback() {
867
		$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' ) );
868
	}
869
870
	function carousel_display_exif_sanitize( $value ) {
871
		return $this->sanitize_1or0_option( $value );
872
	}
873
874
	function carousel_display_geo_callback() {
875
		$this->settings_checkbox( 'carousel_display_geo', __( 'Show map of photo location in carousel, when available.', 'jetpack' ) );
876
	}
877
878
	function carousel_display_geo_sanitize( $value ) {
879
		return $this->sanitize_1or0_option( $value );
880
	}
881
882
	function carousel_background_color_callback() {
883
		$this->settings_select(
884
			'carousel_background_color', array(
885
				'black' => __( 'Black', 'jetpack' ),
886
				'white' => __( 'White', 'jetpack' ),
887
			)
888
		);
889
	}
890
891
	function carousel_background_color_sanitize( $value ) {
892
		return ( 'white' == $value ) ? 'white' : 'black';
893
	}
894
895
	function carousel_enable_it_callback() {
896
		$this->settings_checkbox( 'carousel_enable_it', __( 'Display images in full-size carousel slideshow.', 'jetpack' ) );
897
	}
898
899
	function carousel_enable_it_sanitize( $value ) {
900
		return $this->sanitize_1or0_option( $value );
901
	}
902
}
903
904
new Jetpack_Carousel;
905