Completed
Push — update/use-docker-image-from-d... ( cc624f...9fbb5a )
by
unknown
39:30 queued 32:13
created

Jetpack_Carousel::carousel_display_exif_callback()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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_comments'                => $this->test_1or0_option( Jetpack_Options::get_option_and_ensure_autoload( 'carousel_display_comments', true ) ),
256
				'display_geo'                     => $this->test_1or0_option( Jetpack_Options::get_option_and_ensure_autoload( 'carousel_display_geo', true ) ),
257
				'single_image_gallery'            => $this->single_image_gallery_enabled,
258
				'single_image_gallery_media_file' => $this->single_image_gallery_enabled_media_file,
259
				'background_color'                => $this->carousel_background_color_sanitize( Jetpack_Options::get_option_and_ensure_autoload( 'carousel_background_color', '' ) ),
260
				'comment'                         => __( 'Comment', 'jetpack' ),
261
				'post_comment'                    => __( 'Post Comment', 'jetpack' ),
262
				'write_comment'                   => __( 'Write a Comment...', 'jetpack' ),
263
				'loading_comments'                => __( 'Loading Comments...', 'jetpack' ),
264
				'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}' ),
265
				'no_comment_text'                 => __( 'Please be sure to submit some text with your comment.', 'jetpack' ),
266
				'no_comment_email'                => __( 'Please provide an email address to comment.', 'jetpack' ),
267
				'no_comment_author'               => __( 'Please provide your name to comment.', 'jetpack' ),
268
				'comment_post_error'              => __( 'Sorry, but there was an error posting your comment. Please try again later.', 'jetpack' ),
269
				'comment_approved'                => __( 'Your comment was approved.', 'jetpack' ),
270
				'comment_unapproved'              => __( 'Your comment is in moderation.', 'jetpack' ),
271
				'camera'                          => __( 'Camera', 'jetpack' ),
272
				'aperture'                        => __( 'Aperture', 'jetpack' ),
273
				'shutter_speed'                   => __( 'Shutter Speed', 'jetpack' ),
274
				'focal_length'                    => __( 'Focal Length', 'jetpack' ),
275
				'copyright'                       => __( 'Copyright', 'jetpack' ),
276
				'comment_registration'            => $comment_registration,
277
				'require_name_email'              => $require_name_email,
278
				/** This action is documented in core/src/wp-includes/link-template.php */
279
				'login_url'                       => wp_login_url( apply_filters( 'the_permalink', get_permalink() ) ),
280
				'blog_id'                         => (int) get_current_blog_id(),
281
				'meta_data'                       => array( 'camera', 'aperture', 'shutter_speed', 'focal_length', 'copyright' ),
282
			);
283
284
			if ( ! isset( $localize_strings['jetpack_comments_iframe_src'] ) || empty( $localize_strings['jetpack_comments_iframe_src'] ) ) {
285
				// We're not using Comments after all, so fallback to standard local comments.
286
287
				if ( $is_logged_in ) {
288
					$localize_strings['local_comments_commenting_as'] = '<p id="jp-carousel-commenting-as">' . sprintf( __( 'Commenting as %s', 'jetpack' ), $current_user->data->display_name ) . '</p>';
289
				} else {
290
					if ( $comment_registration ) {
291
						$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>';
292
					} else {
293
						$required = ( $require_name_email ) ? __( '%s (Required)', 'jetpack' ) : '%s';
294
						$localize_strings['local_comments_commenting_as'] = ''
295
							. '<fieldset><label for="email">' . sprintf( $required, __( 'Email', 'jetpack' ) ) . '</label> '
296
							. '<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>'
297
							. '<fieldset><label for="author">' . sprintf( $required, __( 'Name', 'jetpack' ) ) . '</label> '
298
							. '<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>'
299
							. '<fieldset><label for="url">' . __( 'Website', 'jetpack' ) . '</label> '
300
							. '<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>';
301
					}
302
				}
303
			}
304
305
			/**
306
			 * Handle WP stats for images in full-screen.
307
			 * Build string with tracking info.
308
			 */
309
310
			/**
311
			 * Filter if Jetpack should enable stats collection on carousel views
312
			 *
313
			 * @module carousel
314
			 *
315
			 * @since 4.3.2
316
			 *
317
			 * @param bool Enable Jetpack Carousel stat collection. Default false.
318
			 */
319
			if ( apply_filters( 'jetpack_enable_carousel_stats', false ) && in_array( 'stats', Jetpack::get_active_modules() ) && ! ( new Status() )->is_development_mode() ) {
320
				$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
Unused Code introduced by
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...
321
322
				// Set the stats as empty if user is logged in but logged-in users shouldn't be tracked.
323 View Code Duplication
				if ( is_user_logged_in() && function_exists( 'stats_get_options' ) ) {
324
					$stats_options        = stats_get_options();
325
					$track_loggedin_users = isset( $stats_options['reg_users'] ) ? (bool) $stats_options['reg_users'] : false;
326
327
					if ( ! $track_loggedin_users ) {
328
						$localize_strings['stats'] = '';
329
					}
330
				}
331
			}
332
333
			/**
334
			 * Filter the strings passed to the Carousel's js file.
335
			 *
336
			 * @module carousel
337
			 *
338
			 * @since 1.6.0
339
			 *
340
			 * @param array $localize_strings Array of strings passed to the Jetpack js file.
341
			 */
342
			$localize_strings = apply_filters( 'jp_carousel_localize_strings', $localize_strings );
343
			wp_localize_script( 'jetpack-carousel', 'jetpackCarouselStrings', $localize_strings );
344
			wp_enqueue_style( 'jetpack-carousel', plugins_url( 'jetpack-carousel.css', __FILE__ ), array(), $this->asset_version( '20120629' ) );
345
			wp_style_add_data( 'jetpack-carousel', 'rtl', 'replace' );
346
347
			/**
348
			 * Fires after carousel assets are enqueued for the first time.
349
			 * Allows for adding additional assets to the carousel page.
350
			 *
351
			 * @module carousel
352
			 *
353
			 * @since 1.6.0
354
			 *
355
			 * @param bool $first_run First load if Carousel on the page.
356
			 * @param array $localized_strings Array of strings passed to the Jetpack js file.
357
			 */
358
			do_action( 'jp_carousel_enqueue_assets', $this->first_run, $localize_strings );
359
360
			$this->first_run = false;
361
		}
362
	}
363
364
	function set_in_gallery( $output ) {
365
		if (
366
			class_exists( 'Jetpack_AMP_Support' )
367
			&& Jetpack_AMP_Support::is_amp_request()
368
		) {
369
			return $output;
370
		}
371
		$this->in_gallery = true;
372
		return $output;
373
	}
374
375
	/**
376
	 * Adds data-* attributes required by carousel to img tags in post HTML
377
	 * content. To be used by 'the_content' filter.
378
	 *
379
	 * @see add_data_to_images()
380
	 * @see wp_make_content_images_responsive() in wp-includes/media.php
381
	 *
382
	 * @param string $content HTML content of the post
383
	 * @return string Modified HTML content of the post
384
	 */
385
	function add_data_img_tags_and_enqueue_assets( $content ) {
386
		if (
387
			class_exists( 'Jetpack_AMP_Support' )
388
			&& Jetpack_AMP_Support::is_amp_request()
389
		) {
390
			return $this->maybe_add_amp_lightbox( $content );
391
		}
392
393
		if ( ! preg_match_all( '/<img [^>]+>/', $content, $matches ) ) {
394
			return $content;
395
		}
396
		$selected_images = array();
397
		foreach ( $matches[0] as $image_html ) {
398
			if ( preg_match( '/(wp-image-|data-id=)\"?([0-9]+)\"?/i', $image_html, $class_id ) &&
399
				! preg_match( '/wp-block-jetpack-slideshow_image/', $image_html ) ) {
400
				$attachment_id = absint( $class_id[2] );
401
				/**
402
				 * If exactly the same image tag is used more than once, overwrite it.
403
				 * All identical tags will be replaced later with 'str_replace()'.
404
				 */
405
				$selected_images[ $attachment_id  ] = $image_html;
406
			}
407
		}
408
409
		$find    = array();
410
		$replace = array();
411
		if ( empty( $selected_images ) ) {
412
			return $content;
413
		}
414
415
		$attachments = get_posts(
416
			array(
417
				'include'          => array_keys( $selected_images ),
418
				'post_type'        => 'any',
419
				'post_status'      => 'any',
420
				'suppress_filters' => false,
421
			)
422
		);
423
424
		foreach ( $attachments as $attachment ) {
425
			$image_html = $selected_images[ $attachment->ID ];
426
427
			$attributes      = $this->add_data_to_images( array(), $attachment );
428
			$attributes_html = '';
429
			foreach ( $attributes as $k => $v ) {
430
				$attributes_html .= esc_attr( $k ) . '="' . esc_attr( $v ) . '" ';
431
			}
432
433
			$find[]    = $image_html;
434
			$replace[] = str_replace( '<img ', "<img $attributes_html", $image_html );
435
		}
436
437
		$content = str_replace( $find, $replace, $content );
438
		$this->enqueue_assets();
439
		return $content;
440
	}
441
442
	function add_data_to_images( $attr, $attachment = null ) {
443
		if (
444
			class_exists( 'Jetpack_AMP_Support' )
445
			&& Jetpack_AMP_Support::is_amp_request()
446
		) {
447
			return $attr;
448
		}
449
450
		$attachment_id = intval( $attachment->ID );
451
		if ( ! wp_attachment_is_image( $attachment_id ) ) {
452
			return $attr;
453
		}
454
455
		$orig_file       = wp_get_attachment_image_src( $attachment_id, 'full' );
456
		$orig_file       = isset( $orig_file[0] ) ? $orig_file[0] : wp_get_attachment_url( $attachment_id );
457
		$meta            = wp_get_attachment_metadata( $attachment_id );
458
		$size            = isset( $meta['width'] ) ? intval( $meta['width'] ) . ',' . intval( $meta['height'] ) : '';
459
		$img_meta        = ( ! empty( $meta['image_meta'] ) ) ? (array) $meta['image_meta'] : array();
460
		$comments_opened = intval( comments_open( $attachment_id ) );
461
462
		/**
463
		 * Note: Cannot generate a filename from the width and height wp_get_attachment_image_src() returns because
464
		 * it takes the $content_width global variable themes can set in consideration, therefore returning sizes
465
		 * which when used to generate a filename will likely result in a 404 on the image.
466
		 * $content_width has no filter we could temporarily de-register, run wp_get_attachment_image_src(), then
467
		 * re-register. So using returned file URL instead, which we can define the sizes from through filename
468
		 * parsing in the JS, as this is a failsafe file reference.
469
		 *
470
		 * EG with Twenty Eleven activated:
471
		 * 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) }
472
		 *
473
		 * EG with Twenty Ten activated:
474
		 * 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) }
475
		 */
476
477
		$medium_file_info = wp_get_attachment_image_src( $attachment_id, 'medium' );
478
		$medium_file      = isset( $medium_file_info[0] ) ? $medium_file_info[0] : '';
479
480
		$large_file_info = wp_get_attachment_image_src( $attachment_id, 'large' );
481
		$large_file      = isset( $large_file_info[0] ) ? $large_file_info[0] : '';
482
483
		$attachment       = get_post( $attachment_id );
484
		$attachment_title = wptexturize( $attachment->post_title );
485
		$attachment_desc  = wpautop( wptexturize( $attachment->post_content ) );
486
		// Not yet providing geo-data, need to "fuzzify" for privacy
487 View Code Duplication
		if ( ! empty( $img_meta ) ) {
488
			foreach ( $img_meta as $k => $v ) {
489
				if ( 'latitude' == $k || 'longitude' == $k ) {
490
					unset( $img_meta[ $k ] );
491
				}
492
			}
493
		}
494
495
		// See https://github.com/Automattic/jetpack/issues/2765
496
		if ( isset( $img_meta['keywords'] ) ) {
497
			unset( $img_meta['keywords'] );
498
		}
499
500
		$img_meta = json_encode( array_map( 'strval', array_filter( $img_meta, 'is_scalar' ) ) );
501
502
		$attr['data-attachment-id']     = $attachment_id;
503
		$attr['data-permalink']         = esc_attr( get_permalink( $attachment->ID ) );
504
		$attr['data-orig-file']         = esc_attr( $orig_file );
505
		$attr['data-orig-size']         = $size;
506
		$attr['data-comments-opened']   = $comments_opened;
507
		$attr['data-image-meta']        = esc_attr( $img_meta );
508
		$attr['data-image-title']       = esc_attr( htmlspecialchars( $attachment_title ) );
509
		$attr['data-image-description'] = esc_attr( htmlspecialchars( $attachment_desc ) );
510
		$attr['data-medium-file']       = esc_attr( $medium_file );
511
		$attr['data-large-file']        = esc_attr( $large_file );
512
513
		return $attr;
514
	}
515
516
	function add_data_to_container( $html ) {
517
		global $post;
518
		if (
519
			class_exists( 'Jetpack_AMP_Support' )
520
			&& Jetpack_AMP_Support::is_amp_request()
521
		) {
522
			return $html;
523
		}
524
525
		if ( isset( $post ) ) {
526
			$blog_id = (int) get_current_blog_id();
527
528
			$extra_data = array(
529
				'data-carousel-extra' => array(
530
					'blog_id'   => $blog_id,
531
					'permalink' => get_permalink( $post->ID ),
532
				),
533
			);
534
535
			/**
536
			 * Filter the data added to the Gallery container.
537
			 *
538
			 * @module carousel
539
			 *
540
			 * @since 1.6.0
541
			 *
542
			 * @param array $extra_data Array of data about the site and the post.
543
			 */
544
			$extra_data = apply_filters( 'jp_carousel_add_data_to_container', $extra_data );
545
			foreach ( (array) $extra_data as $data_key => $data_values ) {
546
				$html = str_replace( '<div ', '<div ' . esc_attr( $data_key ) . "='" . json_encode( $data_values ) . "' ", $html );
547
				$html = str_replace( '<ul class="wp-block-gallery', '<ul ' . esc_attr( $data_key ) . "='" . json_encode( $data_values ) . "' class=\"wp-block-gallery", $html );
548
				$html = str_replace( '<ul class="blocks-gallery-grid', '<ul ' . esc_attr( $data_key ) . "='" . json_encode( $data_values ) . "' class=\"blocks-gallery-grid", $html );
549
			}
550
		}
551
552
		return $html;
553
	}
554
555
	/**
556
	 * Conditionally adds amp-lightbox to galleries and images.
557
	 *
558
	 * This applies to gallery blocks and shortcodes,
559
	 * in addition to images that are wrapped in a link to the page.
560
	 * Images wrapped in a link to the media file shouldn't get an amp-lightbox.
561
	 *
562
	 * @param string $content The content to possibly add amp-lightbox to.
563
	 * @return string The content, with amp-lightbox possibly added.
564
	 */
565
	public function maybe_add_amp_lightbox( $content ) {
566
		$content = preg_replace(
567
			array(
568
				'#(<figure)[^>]*(?=class=(["\']?)[^>]*wp-block-gallery[^>]*\2)#is', // Gallery block.
569
				'#(\[gallery)(?=\s+)#', // Gallery shortcode.
570
			),
571
			array(
572
				'\1 data-amp-lightbox="true" ', // https://github.com/ampproject/amp-wp/blob/1094ea03bd5dc92889405a47a8c41de1a88908de/includes/sanitizers/class-amp-gallery-block-sanitizer.php#L84.
573
				'\1 amp-lightbox="true"', // https://github.com/ampproject/amp-wp/blob/1094ea03bd5dc92889405a47a8c41de1a88908de/includes/embeds/class-amp-gallery-embed.php#L64.
574
			),
575
			$content
576
		);
577
578
		return preg_replace_callback(
579
			'#(<a[^>]* href=(["\']?)(\S+)\2>)\s*(<img[^>]*)(class=(["\']?)[^>]*wp-image-[0-9]+[^>]*\6.*>)\s*</a>#is',
580
			static function( $matches ) {
581 View Code Duplication
				if ( ! preg_match( '#\.\w+$#', $matches[3] ) ) {
582
					// The a[href] doesn't end in a file extension like .jpeg, so this is not a link to the media file, and should get a lightbox.
583
					return $matches[4] . ' data-amp-lightbox="true" lightbox="true" ' . $matches[5]; // https://github.com/ampproject/amp-wp/blob/1094ea03bd5dc92889405a47a8c41de1a88908de/includes/sanitizers/class-amp-img-sanitizer.php#L419.
584
				}
585
586
				return $matches[0];
587
			},
588
			$content
589
		);
590
	}
591
592
	function get_attachment_comments() {
593
		if ( ! headers_sent() ) {
594
			header( 'Content-type: text/javascript' );
595
		}
596
597
		/**
598
		 * Allows for the checking of privileges of the blog user before comments
599
		 * are packaged as JSON and sent back from the get_attachment_comments
600
		 * AJAX endpoint
601
		 *
602
		 * @module carousel
603
		 *
604
		 * @since 1.6.0
605
		 */
606
		do_action( 'jp_carousel_check_blog_user_privileges' );
607
608
		$attachment_id = ( isset( $_REQUEST['id'] ) ) ? (int) $_REQUEST['id'] : 0;
609
		$offset        = ( isset( $_REQUEST['offset'] ) ) ? (int) $_REQUEST['offset'] : 0;
610
611
		if ( ! $attachment_id ) {
612
			echo json_encode( __( 'Missing attachment ID.', 'jetpack' ) );
613
			die();
614
		}
615
616
		if ( $offset < 1 ) {
617
			$offset = 0;
618
		}
619
620
		$comments = get_comments(
621
			array(
622
				'status'  => 'approve',
623
				'order'   => ( 'asc' == get_option( 'comment_order' ) ) ? 'ASC' : 'DESC',
624
				'number'  => 10,
625
				'offset'  => $offset,
626
				'post_id' => $attachment_id,
627
			)
628
		);
629
630
		$out = array();
631
632
		// Can't just send the results, they contain the commenter's email address.
633
		foreach ( $comments as $comment ) {
634
			$avatar = get_avatar( $comment->comment_author_email, 64 );
635
			if ( ! $avatar ) {
636
				$avatar = '';
637
			}
638
			$out[] = array(
639
				'id'              => $comment->comment_ID,
640
				'parent_id'       => $comment->comment_parent,
641
				'author_markup'   => get_comment_author_link( $comment->comment_ID ),
642
				'gravatar_markup' => $avatar,
643
				'date_gmt'        => $comment->comment_date_gmt,
644
				'content'         => wpautop( $comment->comment_content ),
645
			);
646
		}
647
648
		die( json_encode( $out ) );
649
	}
650
651
	function post_attachment_comment() {
652
		if ( ! headers_sent() ) {
653
			header( 'Content-type: text/javascript' );
654
		}
655
656
		if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'carousel_nonce' ) ) {
657
			die( json_encode( array( 'error' => __( 'Nonce verification failed.', 'jetpack' ) ) ) );
658
		}
659
660
		$_blog_id = (int) $_POST['blog_id'];
661
		$_post_id = (int) $_POST['id'];
662
		$comment  = $_POST['comment'];
663
664
		if ( empty( $_blog_id ) ) {
665
			die( json_encode( array( 'error' => __( 'Missing target blog ID.', 'jetpack' ) ) ) );
666
		}
667
668
		if ( empty( $_post_id ) ) {
669
			die( json_encode( array( 'error' => __( 'Missing target post ID.', 'jetpack' ) ) ) );
670
		}
671
672
		if ( empty( $comment ) ) {
673
			die( json_encode( array( 'error' => __( 'No comment text was submitted.', 'jetpack' ) ) ) );
674
		}
675
676
		// Used in context like NewDash
677
		$switched = false;
678
		if ( is_multisite() && $_blog_id != get_current_blog_id() ) {
679
			switch_to_blog( $_blog_id );
680
			$switched = true;
681
		}
682
683
		/** This action is documented in modules/carousel/jetpack-carousel.php */
684
		do_action( 'jp_carousel_check_blog_user_privileges' );
685
686
		if ( ! comments_open( $_post_id ) ) {
687
			die( json_encode( array( 'error' => __( 'Comments on this post are closed.', 'jetpack' ) ) ) );
688
		}
689
690
		if ( is_user_logged_in() ) {
691
			$user         = wp_get_current_user();
692
			$user_id      = $user->ID;
693
			$display_name = $user->display_name;
694
			$email        = $user->user_email;
695
			$url          = $user->user_url;
696
697
			if ( empty( $user_id ) ) {
698
				die( json_encode( array( 'error' => __( 'Sorry, but we could not authenticate your request.', 'jetpack' ) ) ) );
699
			}
700
		} else {
701
			$user_id      = 0;
702
			$display_name = $_POST['author'];
703
			$email        = $_POST['email'];
704
			$url          = $_POST['url'];
705
706
			if ( get_option( 'require_name_email' ) ) {
707
				if ( empty( $display_name ) ) {
708
					die( json_encode( array( 'error' => __( 'Please provide your name.', 'jetpack' ) ) ) );
709
				}
710
711
				if ( empty( $email ) ) {
712
					die( json_encode( array( 'error' => __( 'Please provide an email address.', 'jetpack' ) ) ) );
713
				}
714
715
				if ( ! is_email( $email ) ) {
716
					die( json_encode( array( 'error' => __( 'Please provide a valid email address.', 'jetpack' ) ) ) );
717
				}
718
			}
719
		}
720
721
		$comment_data = array(
722
			'comment_content'      => $comment,
723
			'comment_post_ID'      => $_post_id,
724
			'comment_author'       => $display_name,
725
			'comment_author_email' => $email,
726
			'comment_author_url'   => $url,
727
			'comment_approved'     => 0,
728
			'comment_type'         => 'comment',
729
		);
730
731
		if ( ! empty( $user_id ) ) {
732
			$comment_data['user_id'] = $user_id;
733
		}
734
735
		// Note: wp_new_comment() sanitizes and validates the values (too).
736
		$comment_id = wp_new_comment( $comment_data );
737
738
		/**
739
		 * Fires before adding a new comment to the database via the get_attachment_comments ajax endpoint.
740
		 *
741
		 * @module carousel
742
		 *
743
		 * @since 1.6.0
744
		 */
745
		do_action( 'jp_carousel_post_attachment_comment' );
746
		$comment_status = wp_get_comment_status( $comment_id );
747
748
		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...
749
			restore_current_blog();
750
		}
751
752
		die(
753
			json_encode(
754
				array(
755
					'comment_id'     => $comment_id,
756
					'comment_status' => $comment_status,
757
				)
758
			)
759
		);
760
	}
761
762
	function register_settings() {
763
		add_settings_section( 'carousel_section', __( 'Image Gallery Carousel', 'jetpack' ), array( $this, 'carousel_section_callback' ), 'media' );
764
765
		if ( ! $this->in_jetpack ) {
766
			add_settings_field( 'carousel_enable_it', __( 'Enable carousel', 'jetpack' ), array( $this, 'carousel_enable_it_callback' ), 'media', 'carousel_section' );
767
			register_setting( 'media', 'carousel_enable_it', array( $this, 'carousel_enable_it_sanitize' ) );
768
		}
769
770
		add_settings_field( 'carousel_background_color', __( 'Background color', 'jetpack' ), array( $this, 'carousel_background_color_callback' ), 'media', 'carousel_section' );
771
		register_setting( 'media', 'carousel_background_color', array( $this, 'carousel_background_color_sanitize' ) );
772
773
		add_settings_field( 'carousel_display_exif', __( 'Metadata', 'jetpack' ), array( $this, 'carousel_display_exif_callback' ), 'media', 'carousel_section' );
774
		register_setting( 'media', 'carousel_display_exif', array( $this, 'carousel_display_exif_sanitize' ) );
775
776
		add_settings_field( 'carousel_display_comments', __( 'Comments', 'jetpack' ), array( $this, 'carousel_display_comments_callback' ), 'media', 'carousel_section' );
777
		register_setting( 'media', 'carousel_display_comments', array( $this, 'carousel_display_comments_sanitize' ) );
778
779
		// No geo setting yet, need to "fuzzify" data first, for privacy
780
		// add_settings_field('carousel_display_geo', __( 'Geolocation', 'jetpack' ), array( $this, 'carousel_display_geo_callback' ), 'media', 'carousel_section' );
781
		// register_setting( 'media', 'carousel_display_geo', array( $this, 'carousel_display_geo_sanitize' ) );
782
	}
783
784
	// Fulfill the settings section callback requirement by returning nothing
785
	function carousel_section_callback() {
786
		return;
787
	}
788
789
	function test_1or0_option( $value, $default_to_1 = true ) {
790
		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...
791
			// Binary false (===) of $value means it has not yet been set, in which case we do want to default sites to 1
792
			if ( false === $value ) {
793
				$value = 1;
794
			}
795
		}
796
		return ( 1 == $value ) ? 1 : 0;
797
	}
798
799
	function sanitize_1or0_option( $value ) {
800
		return ( 1 == $value ) ? 1 : 0;
801
	}
802
803
	function settings_checkbox( $name, $label_text, $extra_text = '', $default_to_checked = true ) {
804
		if ( empty( $name ) ) {
805
			return;
806
		}
807
		$option = $this->test_1or0_option( get_option( $name ), $default_to_checked );
808
		echo '<fieldset>';
809
		echo '<input type="checkbox" name="' . esc_attr( $name ) . '" id="' . esc_attr( $name ) . '" value="1" ';
810
		checked( '1', $option );
811
		echo '/> <label for="' . esc_attr( $name ) . '">' . $label_text . '</label>';
812
		if ( ! empty( $extra_text ) ) {
813
			echo '<p class="description">' . $extra_text . '</p>';
814
		}
815
		echo '</fieldset>';
816
	}
817
818
	function settings_select( $name, $values, $extra_text = '' ) {
819
		if ( empty( $name ) || ! is_array( $values ) || empty( $values ) ) {
820
			return;
821
		}
822
		$option = get_option( $name );
823
		echo '<fieldset>';
824
		echo '<select name="' . esc_attr( $name ) . '" id="' . esc_attr( $name ) . '">';
825
		foreach ( $values as $key => $value ) {
826
			echo '<option value="' . esc_attr( $key ) . '" ';
827
			selected( $key, $option );
828
			echo '>' . esc_html( $value ) . '</option>';
829
		}
830
		echo '</select>';
831
		if ( ! empty( $extra_text ) ) {
832
			echo '<p class="description">' . $extra_text . '</p>';
833
		}
834
		echo '</fieldset>';
835
	}
836
837
	function carousel_display_exif_callback() {
838
		$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' ) );
839
	}
840
841
	/**
842
	 * Callback for checkbox and label of field that allows to toggle comments.
843
	 */
844
	public function carousel_display_comments_callback() {
845
		$this->settings_checkbox( 'carousel_display_comments', esc_html__( 'Show comments area in carousel', 'jetpack' ) );
846
	}
847
848
	function carousel_display_exif_sanitize( $value ) {
849
		return $this->sanitize_1or0_option( $value );
850
	}
851
852
	/**
853
	 * Return sanitized option for value that controls whether comments will be hidden or not.
854
	 *
855
	 * @param number $value Value to sanitize.
856
	 *
857
	 * @return number Sanitized value, only 1 or 0.
858
	 */
859
	public function carousel_display_comments_sanitize( $value ) {
860
		return $this->sanitize_1or0_option( $value );
861
	}
862
863
	function carousel_display_geo_callback() {
864
		$this->settings_checkbox( 'carousel_display_geo', __( 'Show map of photo location in carousel, when available.', 'jetpack' ) );
865
	}
866
867
	function carousel_display_geo_sanitize( $value ) {
868
		return $this->sanitize_1or0_option( $value );
869
	}
870
871
	function carousel_background_color_callback() {
872
		$this->settings_select(
873
			'carousel_background_color', array(
874
				'black' => __( 'Black', 'jetpack' ),
875
				'white' => __( 'White', 'jetpack' ),
876
			)
877
		);
878
	}
879
880
	function carousel_background_color_sanitize( $value ) {
881
		return ( 'white' == $value ) ? 'white' : 'black';
882
	}
883
884
	function carousel_enable_it_callback() {
885
		$this->settings_checkbox( 'carousel_enable_it', __( 'Display images in full-size carousel slideshow.', 'jetpack' ) );
886
	}
887
888
	function carousel_enable_it_sanitize( $value ) {
889
		return $this->sanitize_1or0_option( $value );
890
	}
891
}
892
893
new Jetpack_Carousel;
894