| @@ -7,10 +7,18 @@ | ||
| 7 | 7 | |
| 8 | 8 | use Automattic\Jetpack\Assets; |
| 9 | 9 | use Automattic\Jetpack\Stats\Options as Stats_Options; |
| 10 | 10 | use Automattic\Jetpack\Status; |
| 11 | +use Automattic\Jetpack\Status\Host; | |
| 12 | + | |
| 13 | +if ( ! defined( 'ABSPATH' ) ) { | |
| 14 | + exit( 0 ); | |
| 15 | +} | |
| 16 | + | |
| 11 | 17 | /** |
| 12 | 18 | * Jetpack_Carousel class. |
| 19 | + * | |
| 20 | + * @phan-constructor-used-for-side-effects | |
| 13 | 21 | */ |
| 14 | 22 | class Jetpack_Carousel { |
| 15 | 23 | /** |
| 16 | 24 | * Defines Carousel pre-built widths |
| @@ -42,9 +50,9 @@ | ||
| 42 | 50 | */ |
| 43 | 51 | public $in_gallery = false; |
| 44 | 52 | |
| 45 | 53 | /** |
| 46 | - * Determines whether the Jetpack class and method exists. Default is true. | |
| 54 | + * Determines whether the module runs in the Jetpack plugin, as opposed to WP.com Simple site environment | |
| 47 | 55 | * |
| 48 | 56 | * @var bool |
| 49 | 57 | */ |
| 50 | 58 | public $in_jetpack = true; |
| @@ -77,9 +85,9 @@ | ||
| 77 | 85 | if ( $this->maybe_disable_jp_carousel() ) { |
| 78 | 86 | return; |
| 79 | 87 | } |
| 80 | 88 | |
| 81 | - $this->in_jetpack = ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'enable_module_configurable' ) ) ? true : false; | |
| 89 | + $this->in_jetpack = ! ( new Host() )->is_wpcom_simple(); | |
| 82 | 90 | |
| 83 | 91 | $this->single_image_gallery_enabled = ! $this->maybe_disable_jp_carousel_single_images(); |
| 84 | 92 | $this->single_image_gallery_enabled_media_file = $this->maybe_enable_jp_carousel_single_images_media_file(); |
| 85 | 93 | |
| @@ -122,15 +130,12 @@ | ||
| 122 | 130 | if ( $this->single_image_gallery_enabled ) { |
| 123 | 131 | add_filter( 'the_content', array( $this, 'add_data_img_tags_and_enqueue_assets' ) ); |
| 124 | 132 | } |
| 125 | 133 | |
| 126 | - if ( | |
| 127 | - ! class_exists( 'Jetpack_AMP_Support' ) | |
| 128 | - || ! Jetpack_AMP_Support::is_amp_request() | |
| 129 | - ) { | |
| 130 | - add_filter( 'render_block_core/gallery', array( $this, 'filter_gallery_block_render' ), 10, 2 ); | |
| 131 | - add_filter( 'render_block_jetpack/tiled-gallery', array( $this, 'filter_gallery_block_render' ), 10, 2 ); | |
| 132 | - } | |
| 134 | + add_filter( 'render_block_data', array( $this, 'remove_core_lightbox_in_gallery' ), 10, 3 ); | |
| 135 | + | |
| 136 | + // `is_amp_request()` can't be called until the 'wp' filter. | |
| 137 | + add_action( 'wp', array( $this, 'check_amp_support' ) ); | |
| 133 | 138 | } |
| 134 | 139 | |
| 135 | 140 | if ( $this->in_jetpack ) { |
| 136 | 141 | Jetpack::enable_module_configurable( dirname( __DIR__ ) . '/carousel.php' ); |
| @@ -137,8 +142,21 @@ | ||
| 137 | 142 | } |
| 138 | 143 | } |
| 139 | 144 | |
| 140 | 145 | /** |
| 146 | + * Check AMP and add filters. | |
| 147 | + */ | |
| 148 | + public function check_amp_support() { | |
| 149 | + if ( | |
| 150 | + ! class_exists( 'Jetpack_AMP_Support' ) | |
| 151 | + || ! Jetpack_AMP_Support::is_amp_request() | |
| 152 | + ) { | |
| 153 | + add_filter( 'render_block_core/gallery', array( $this, 'filter_gallery_block_render' ), 10, 2 ); | |
| 154 | + add_filter( 'render_block_jetpack/tiled-gallery', array( $this, 'filter_gallery_block_render' ), 10, 2 ); | |
| 155 | + } | |
| 156 | + } | |
| 157 | + | |
| 158 | + /** | |
| 141 | 159 | * Returns the value of the applied jp_carousel_maybe_disable filter |
| 142 | 160 | * |
| 143 | 161 | * @since 1.6.0 |
| 144 | 162 | * |
| @@ -326,8 +344,28 @@ | ||
| 326 | 344 | return $content; |
| 327 | 345 | } |
| 328 | 346 | |
| 329 | 347 | /** |
| 348 | + * Remove core lightbox settings from images in a gallery, if Carousel is enabled. | |
| 349 | + * | |
| 350 | + * @param array $parsed_block An associative array of the block being rendered. | |
| 351 | + * @param array $source_block An un-modified copy of `$parsed_block`, as it appeared in the source content. | |
| 352 | + * @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block. | |
| 353 | + * @return array The modified block data. | |
| 354 | + */ | |
| 355 | + public function remove_core_lightbox_in_gallery( $parsed_block, $source_block, $parent_block ) { | |
| 356 | + if ( | |
| 357 | + ! empty( $parsed_block['blockName'] ) && | |
| 358 | + 'core/image' === $parsed_block['blockName'] && | |
| 359 | + ! empty( $parent_block->name ) && | |
| 360 | + 'core/gallery' === $parent_block->name | |
| 361 | + ) { | |
| 362 | + unset( $parsed_block['attrs']['lightbox'] ); | |
| 363 | + } | |
| 364 | + return $parsed_block; | |
| 365 | + } | |
| 366 | + | |
| 367 | + /** | |
| 330 | 368 | * Enrich the gallery block content using the render_block_{$this->name} filter. |
| 331 | 369 | * This function is triggered after block render to make sure we track galleries within |
| 332 | 370 | * reusable blocks. |
| 333 | 371 | * |
| @@ -347,9 +385,9 @@ | ||
| 347 | 385 | } |
| 348 | 386 | |
| 349 | 387 | $this->enqueue_assets(); |
| 350 | 388 | |
| 351 | - if ( ! isset( $post ) ) { | |
| 389 | + if ( ! $post instanceof WP_Post ) { | |
| 352 | 390 | return $block_content; |
| 353 | 391 | } |
| 354 | 392 | |
| 355 | 393 | $blog_id = (int) get_current_blog_id(); |
| @@ -380,9 +418,9 @@ | ||
| 380 | 418 | $extra_attributes = implode( |
| 381 | 419 | ' ', |
| 382 | 420 | array_map( |
| 383 | 421 | function ( $data_key, $data_values ) { |
| 384 | - return esc_attr( $data_key ) . "='" . wp_json_encode( $data_values ) . "'"; | |
| 422 | + return esc_attr( $data_key ) . "='" . esc_attr( wp_json_encode( $data_values, JSON_UNESCAPED_SLASHES | JSON_HEX_AMP ) ) . "'"; | |
| 385 | 423 | }, |
| 386 | 424 | array_keys( $extra_data ), |
| 387 | 425 | array_values( $extra_data ) |
| 388 | 426 | ) |
| @@ -413,14 +451,12 @@ | ||
| 413 | 451 | true |
| 414 | 452 | ); |
| 415 | 453 | |
| 416 | 454 | $swiper_library_path = array( |
| 417 | - 'url' => Assets::get_file_url_for_environment( | |
| 418 | - '_inc/build/carousel/swiper-bundle.min.js', | |
| 419 | - 'modules/carousel/swiper-bundle.js' | |
| 420 | - ), | |
| 455 | + 'url' => plugins_url( '_inc/blocks/swiper.js', JETPACK__PLUGIN_FILE ), | |
| 421 | 456 | ); |
| 422 | 457 | wp_localize_script( 'jetpack-carousel', 'jetpackSwiperLibraryPath', $swiper_library_path ); |
| 458 | + add_action( 'wp_footer', array( $this, 'prefetch_swiper_library' ) ); | |
| 423 | 459 | |
| 424 | 460 | // 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). |
| 425 | 461 | // Also: not hardcoding path since there is no guarantee site is running on site root in self-hosted context. |
| 426 | 462 | $is_logged_in = is_user_logged_in(); |
| @@ -440,8 +476,9 @@ | ||
| 440 | 476 | 'comment' => __( 'Comment', 'jetpack' ), |
| 441 | 477 | 'post_comment' => __( 'Post Comment', 'jetpack' ), |
| 442 | 478 | 'write_comment' => __( 'Write a Comment...', 'jetpack' ), |
| 443 | 479 | 'loading_comments' => __( 'Loading Comments...', 'jetpack' ), |
| 480 | + 'image_label' => __( 'Open image in full-screen.', 'jetpack' ), | |
| 444 | 481 | 'download_original' => sprintf( |
| 445 | 482 | /* translators: %1s is the full-size image width, and %2s is the height. */ |
| 446 | 483 | __( 'View full size <span class="photo-size">%1$s<span class="photo-size-times">×</span>%2$s</span>', 'jetpack' ), |
| 447 | 484 | '{0}', |
| @@ -505,12 +542,12 @@ | ||
| 505 | 542 | */ |
| 506 | 543 | $localize_strings = apply_filters( 'jp_carousel_localize_strings', $localize_strings ); |
| 507 | 544 | wp_localize_script( 'jetpack-carousel', 'jetpackCarouselStrings', $localize_strings ); |
| 508 | 545 | wp_enqueue_style( |
| 509 | - 'jetpack-carousel-swiper-css', | |
| 510 | - plugins_url( 'swiper-bundle.css', __FILE__ ), | |
| 546 | + 'jetpack-swiper-library', | |
| 547 | + plugins_url( '_inc/blocks/swiper.css', JETPACK__PLUGIN_FILE ), | |
| 511 | 548 | array(), |
| 512 | - $this->asset_version( JETPACK__VERSION ) | |
| 549 | + JETPACK__VERSION | |
| 513 | 550 | ); |
| 514 | 551 | wp_enqueue_style( 'jetpack-carousel', plugins_url( 'jetpack-carousel.css', __FILE__ ), array(), $this->asset_version( JETPACK__VERSION ) ); |
| 515 | 552 | wp_style_add_data( 'jetpack-carousel', 'rtl', 'replace' ); |
| 516 | 553 | |
| @@ -535,8 +572,24 @@ | ||
| 535 | 572 | } |
| 536 | 573 | } |
| 537 | 574 | |
| 538 | 575 | /** |
| 576 | + * Hint the browser to fetch the Swiper library while it is idle. | |
| 577 | + * | |
| 578 | + * Swiper is only requested when the lightbox is first opened, which puts a network | |
| 579 | + * round trip in front of that first click. This is deliberately `prefetch` rather than | |
| 580 | + * `preload`: most visitors never open the lightbox, so the fetch must stay at low | |
| 581 | + * priority and out of the way of the page's own images. `loadSwiper()` still loads the | |
| 582 | + * library on demand, since a prefetch is a hint the browser is free to ignore. | |
| 583 | + */ | |
| 584 | + public function prefetch_swiper_library() { | |
| 585 | + printf( | |
| 586 | + '<link rel="prefetch" href="%s" as="script" />' . "\n", | |
| 587 | + esc_url( plugins_url( '_inc/blocks/swiper.js', JETPACK__PLUGIN_FILE ) ) | |
| 588 | + ); | |
| 589 | + } | |
| 590 | + | |
| 591 | + /** | |
| 539 | 592 | * Generate the HTML skeleton that will be picked up by the Carousel JS and used for showing the carousel. |
| 540 | 593 | */ |
| 541 | 594 | public function add_carousel_skeleton() { |
| 542 | 595 | $localize_strings = $this->localize_strings; |
| @@ -546,12 +599,13 @@ | ||
| 546 | 599 | $current_user = wp_get_current_user(); |
| 547 | 600 | $require_name_email = (int) get_option( 'require_name_email' ); |
| 548 | 601 | /* translators: %s is replaced with a field name in the form, e.g. "Email" */ |
| 549 | 602 | $required = ( $require_name_email ) ? __( '%s (Required)', 'jetpack' ) : '%s'; |
| 603 | + require_once JETPACK__PLUGIN_DIR . '_inc/lib/class-jetpack-spinner.php'; | |
| 550 | 604 | ?> |
| 551 | - <div id="jp-carousel-loading-overlay"> | |
| 605 | + <div id="jp-carousel-loading-overlay" style="display: none;"> | |
| 552 | 606 | <div id="jp-carousel-loading-wrapper"> |
| 553 | - <span id="jp-carousel-library-loading"> </span> | |
| 607 | + <span id="jp-carousel-library-loading"><?php echo Jetpack_Spinner::render( 40 ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static SVG markup. ?></span> | |
| 554 | 608 | </div> |
| 555 | 609 | </div> |
| 556 | 610 | <div class="jp-carousel-overlay<?php echo( $is_light ? ' jp-carousel-light' : '' ); ?>" style="display: none;"> |
| 557 | 611 | |
| @@ -557,9 +611,9 @@ | ||
| 557 | 611 | |
| 558 | 612 | <div class="jp-carousel-container<?php echo( $is_light ? ' jp-carousel-light' : '' ); ?>"> |
| 559 | 613 | <!-- The Carousel Swiper --> |
| 560 | 614 | <div |
| 561 | - class="jp-carousel-wrap swiper-container jp-carousel-swiper-container jp-carousel-transitions" | |
| 615 | + class="jp-carousel-wrap swiper jp-carousel-swiper-container jp-carousel-transitions" | |
| 562 | 616 | itemscope |
| 563 | 617 | itemtype="https://schema.org/ImageGallery"> |
| 564 | 618 | <div class="jp-carousel swiper-wrapper"></div> |
| 565 | 619 | <div class="jp-swiper-button-prev swiper-button-prev"> |
| @@ -601,9 +655,9 @@ | ||
| 601 | 655 | <div class="jp-swiper-pagination swiper-pagination"></div> |
| 602 | 656 | <div class="jp-carousel-pagination"></div> |
| 603 | 657 | </div> |
| 604 | 658 | <div class="jp-carousel-photo-title-container"> |
| 605 | - <h2 class="jp-carousel-photo-caption"></h2> | |
| 659 | + <div class="jp-carousel-photo-caption"></div> | |
| 606 | 660 | </div> |
| 607 | 661 | <div class="jp-carousel-photo-icons-container"> |
| 608 | 662 | <a href="#" class="jp-carousel-icon-btn jp-carousel-icon-info" aria-label="<?php esc_attr_e( 'Toggle photo metadata visibility', 'jetpack' ); ?>"> |
| 609 | 663 | <span class="jp-carousel-icon"> |
| @@ -637,9 +691,9 @@ | ||
| 637 | 691 | </div> |
| 638 | 692 | <div class="jp-carousel-info-extra"> |
| 639 | 693 | <div class="jp-carousel-info-content-wrapper"> |
| 640 | 694 | <div class="jp-carousel-photo-title-container"> |
| 641 | - <h2 class="jp-carousel-photo-title"></h2> | |
| 695 | + <div class="jp-carousel-photo-title"></div> | |
| 642 | 696 | </div> |
| 643 | 697 | <div class="jp-carousel-comments-wrapper"> |
| 644 | 698 | <?php if ( $localize_strings['display_comments'] ) : ?> |
| 645 | 699 | <div id="jp-carousel-comments-loading"> |
| @@ -646,9 +700,9 @@ | ||
| 646 | 700 | <span><?php echo esc_html( $localize_strings['loading_comments'] ); ?></span> |
| 647 | 701 | </div> |
| 648 | 702 | <div class="jp-carousel-comments"></div> |
| 649 | 703 | <div id="jp-carousel-comment-form-container"> |
| 650 | - <span id="jp-carousel-comment-form-spinner"> </span> | |
| 704 | + <span id="jp-carousel-comment-form-spinner"><?php echo Jetpack_Spinner::render( 20 ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static SVG markup. ?></span> | |
| 651 | 705 | <div id="jp-carousel-comment-post-results"></div> |
| 652 | 706 | <?php if ( $use_local_comments ) : ?> |
| 653 | 707 | <?php if ( ! $localize_strings['is_logged_in'] && $localize_strings['comment_registration'] ) : ?> |
| 654 | 708 | <div id="jp-carousel-comment-form-commenting-as"> |
| @@ -717,15 +771,14 @@ | ||
| 717 | 771 | </div> |
| 718 | 772 | <div class="jp-carousel-image-meta"> |
| 719 | 773 | <div class="jp-carousel-title-and-caption"> |
| 720 | 774 | <div class="jp-carousel-photo-info"> |
| 721 | - <h3 class="jp-carousel-caption" itemprop="caption description"></h3> | |
| 775 | + <div class="jp-carousel-caption" itemprop="caption description"></div> | |
| 722 | 776 | </div> |
| 723 | 777 | |
| 724 | 778 | <div class="jp-carousel-photo-description"></div> |
| 725 | 779 | </div> |
| 726 | - <ul class="jp-carousel-image-exif" style="display: none;"></ul> | |
| 727 | - <a class="jp-carousel-image-download" target="_blank" style="display: none;"> | |
| 780 | + <a class="jp-carousel-image-download" href="#" aria-label="<?php esc_attr_e( 'Download image', 'jetpack' ); ?>" target="_blank" style="display: none;"> | |
| 728 | 781 | <svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg"> |
| 729 | 782 | <mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="3" y="3" width="19" height="18"> |
| 730 | 783 | <path fill-rule="evenodd" clip-rule="evenodd" d="M5.84615 5V19H19.7775V12H21.7677V19C21.7677 20.1 20.8721 21 19.7775 21H5.84615C4.74159 21 3.85596 20.1 3.85596 19V5C3.85596 3.9 4.74159 3 5.84615 3H12.8118V5H5.84615ZM14.802 5V3H21.7677V10H19.7775V6.41L9.99569 16.24L8.59261 14.83L18.3744 5H14.802Z" fill="white"/> |
| 731 | 784 | </mask> |
| @@ -776,8 +829,11 @@ | ||
| 776 | 829 | * @param string $content HTML content of the post. |
| 777 | 830 | * @return string |
| 778 | 831 | */ |
| 779 | 832 | public function add_data_img_tags_and_enqueue_assets( $content ) { |
| 833 | + if ( ! is_string( $content ) || $content === '' ) { | |
| 834 | + return ''; | |
| 835 | + } | |
| 780 | 836 | if ( |
| 781 | 837 | class_exists( 'Jetpack_AMP_Support' ) |
| 782 | 838 | && Jetpack_AMP_Support::is_amp_request() |
| 783 | 839 | ) { |
| @@ -788,11 +844,19 @@ | ||
| 788 | 844 | return $content; |
| 789 | 845 | } |
| 790 | 846 | $selected_images = array(); |
| 791 | 847 | foreach ( $matches[0] as $image_html ) { |
| 848 | + // This image already carries the attributes this method adds, so adding | |
| 849 | + // them again would emit every one of them twice. Tiled Gallery output | |
| 850 | + // reaches this filter twice: once as 'jetpack_tiled_galleries_block_content' | |
| 851 | + // from inside the block's render callback, and again as 'the_content' when | |
| 852 | + // single image galleries are enabled. See JETPACK-1990. | |
| 853 | + if ( str_contains( $image_html, 'data-attachment-id=' ) ) { | |
| 854 | + continue; | |
| 855 | + } | |
| 792 | 856 | if ( |
| 793 | 857 | preg_match( '/(wp-image-|data-id=)\"?([0-9]+)\"?/i', $image_html, $class_id ) |
| 794 | - && ! preg_match( '/wp-block-jetpack-slideshow_image/', $image_html ) | |
| 858 | + && ! str_contains( $image_html, 'wp-block-jetpack-slideshow_image' ) | |
| 795 | 859 | ) { |
| 796 | 860 | /** |
| 797 | 861 | * Allow filtering the attachment ID used to fetch and populate metadata about an image in a gallery. |
| 798 | 862 | * |
| @@ -844,9 +908,13 @@ | ||
| 844 | 908 | * |
| 845 | 909 | * This is meant as a relatively quick fix, as a better fix is likely to update the get_posts call above to only |
| 846 | 910 | * include attachments. |
| 847 | 911 | */ |
| 848 | - if ( ! isset( $attachment->ID ) || ! wp_attachment_is_image( $attachment->ID ) ) { | |
| 912 | + if ( | |
| 913 | + ! isset( $attachment->ID ) | |
| 914 | + || ! wp_attachment_is_image( $attachment->ID ) | |
| 915 | + || ! isset( $selected_images[ $attachment->ID ] ) | |
| 916 | + ) { | |
| 849 | 917 | continue; |
| 850 | 918 | } |
| 851 | 919 | $image_elements = $selected_images[ $attachment->ID ]; |
| 852 | 920 | |
| @@ -875,10 +943,10 @@ | ||
| 875 | 943 | * |
| 876 | 944 | * @see add_data_img_tags_and_enqueue_assets() |
| 877 | 945 | * @see https://developer.wordpress.org/reference/functions/wp_get_attachment_image/ Documentation about wp_get_attachment_image |
| 878 | 946 | * |
| 879 | - * @param string[] $attr Array of attribute values for the image markup, keyed by attribute name. | |
| 880 | - * @param WP_Post $attachment Image attachment post. | |
| 947 | + * @param string[] $attr Array of attribute values for the image markup, keyed by attribute name. | |
| 948 | + * @param null|WP_Post $attachment Image attachment post. | |
| 881 | 949 | * |
| 882 | 950 | * @return string[] Modified image attributes. |
| 883 | 951 | */ |
| 884 | 952 | public function add_data_to_images( $attr, $attachment = null ) { |
| @@ -888,19 +956,24 @@ | ||
| 888 | 956 | ) { |
| 889 | 957 | return $attr; |
| 890 | 958 | } |
| 891 | 959 | |
| 892 | - $attachment_id = (int) $attachment->ID; | |
| 893 | - if ( ! wp_attachment_is_image( $attachment_id ) ) { | |
| 960 | + if ( | |
| 961 | + ! $attachment instanceof WP_Post | |
| 962 | + || ! isset( $attachment->ID ) | |
| 963 | + || ! wp_attachment_is_image( $attachment ) | |
| 964 | + ) { | |
| 894 | 965 | return $attr; |
| 895 | 966 | } |
| 896 | 967 | |
| 968 | + $attachment_id = (int) $attachment->ID; | |
| 897 | 969 | $orig_file = wp_get_attachment_image_src( $attachment_id, 'full' ); |
| 898 | - $orig_file = isset( $orig_file[0] ) ? $orig_file[0] : wp_get_attachment_url( $attachment_id ); | |
| 970 | + $orig_file = $orig_file[0] ?? wp_get_attachment_url( $attachment_id ); | |
| 899 | 971 | $meta = wp_get_attachment_metadata( $attachment_id ); |
| 900 | 972 | $size = isset( $meta['width'] ) ? (int) $meta['width'] . ',' . (int) $meta['height'] : ''; |
| 901 | 973 | $img_meta = ( ! empty( $meta['image_meta'] ) ) ? (array) $meta['image_meta'] : array(); |
| 902 | 974 | $comments_opened = (int) comments_open( $attachment_id ); |
| 975 | + $display_exif = $this->test_1or0_option( Jetpack_Options::get_option_and_ensure_autoload( 'carousel_display_exif', true ) ); | |
| 903 | 976 | |
| 904 | 977 | /** |
| 905 | 978 | * Note: Cannot generate a filename from the width and height wp_get_attachment_image_src() returns because |
| 906 | 979 | * it takes the $content_width global variable themes can set in consideration, therefore returning sizes |
| @@ -915,39 +988,62 @@ | ||
| 915 | 988 | * EG with Twenty Ten activated: |
| 916 | 989 | * 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) } |
| 917 | 990 | */ |
| 918 | 991 | |
| 919 | - $medium_file_info = wp_get_attachment_image_src( $attachment_id, 'medium' ); | |
| 920 | - $medium_file = isset( $medium_file_info[0] ) ? $medium_file_info[0] : ''; | |
| 921 | - | |
| 922 | 992 | $large_file_info = wp_get_attachment_image_src( $attachment_id, 'large' ); |
| 923 | - $large_file = isset( $large_file_info[0] ) ? $large_file_info[0] : ''; | |
| 993 | + $large_file = $large_file_info[0] ?? ''; | |
| 924 | 994 | |
| 925 | - $attachment = get_post( $attachment_id ); | |
| 926 | - $attachment_title = ! empty( $attachment ) ? wptexturize( $attachment->post_title ) : ''; | |
| 927 | - $attachment_desc = ! empty( $attachment ) ? wpautop( wptexturize( $attachment->post_content ) ) : ''; | |
| 928 | - $attachment_caption = ! empty( $attachment ) ? wpautop( wptexturize( $attachment->post_excerpt ) ) : ''; | |
| 995 | + $attachment_title = wptexturize( $attachment->post_title ); | |
| 996 | + $attachment_desc = wpautop( wptexturize( $attachment->post_content ) ); | |
| 997 | + $attachment_caption = wpautop( wptexturize( $attachment->post_excerpt ) ); | |
| 929 | 998 | |
| 930 | - // See https://github.com/Automattic/jetpack/issues/2765. | |
| 931 | - if ( isset( $img_meta['keywords'] ) ) { | |
| 932 | - unset( $img_meta['keywords'] ); | |
| 933 | - } | |
| 934 | - | |
| 935 | - $img_meta = wp_json_encode( array_map( 'strval', array_filter( $img_meta, 'is_scalar' ) ) ); | |
| 936 | - | |
| 937 | 999 | $attr['data-attachment-id'] = $attachment_id; |
| 938 | 1000 | $attr['data-permalink'] = esc_attr( get_permalink( $attachment_id ) ); |
| 939 | 1001 | $attr['data-orig-file'] = esc_attr( $orig_file ); |
| 940 | 1002 | $attr['data-orig-size'] = $size; |
| 941 | 1003 | $attr['data-comments-opened'] = $comments_opened; |
| 942 | - $attr['data-image-meta'] = esc_attr( $img_meta ); | |
| 1004 | + | |
| 1005 | + /* | |
| 1006 | + Lets the Carousel show its "has comments" badge without fetching the comments | |
| 1007 | + themselves. Omitted when there are none, which is the common case, so galleries | |
| 1008 | + without comments pay nothing for it. | |
| 1009 | + */ | |
| 1010 | + $comments_count = (int) $attachment->comment_count; | |
| 1011 | + if ( $comments_count > 0 ) { | |
| 1012 | + $attr['data-comments-count'] = $comments_count; | |
| 1013 | + } | |
| 1014 | + | |
| 1015 | + if ( $display_exif ) { | |
| 1016 | + // See https://github.com/Automattic/jetpack/issues/2765. | |
| 1017 | + if ( isset( $img_meta['keywords'] ) ) { | |
| 1018 | + unset( $img_meta['keywords'] ); | |
| 1019 | + } | |
| 1020 | + | |
| 1021 | + /* | |
| 1022 | + Filtering on `is_scalar` alone kept every "" and "0" in the metadata array, which | |
| 1023 | + on a typical photo is most of it. The carousel skips those values when it renders | |
| 1024 | + the EXIF panel anyway, so serialising them only inflates the page. Mirror that | |
| 1025 | + check here: drop empties and numeric zeroes, but keep text that merely casts to | |
| 1026 | + zero, such as a camera name. | |
| 1027 | + */ | |
| 1028 | + $img_meta = array_filter( | |
| 1029 | + array_map( 'strval', array_filter( $img_meta, 'is_scalar' ) ), | |
| 1030 | + function ( $value ) { | |
| 1031 | + return '' !== $value && ! ( is_numeric( $value ) && 0.0 === (float) $value ); | |
| 1032 | + } | |
| 1033 | + ); | |
| 1034 | + | |
| 1035 | + // With nothing left to show, the attribute itself is dead weight. | |
| 1036 | + if ( ! empty( $img_meta ) ) { | |
| 1037 | + $attr['data-image-meta'] = esc_attr( wp_json_encode( $img_meta, JSON_UNESCAPED_SLASHES | JSON_HEX_AMP ) ); | |
| 1038 | + } | |
| 1039 | + } | |
| 1040 | + | |
| 943 | 1041 | // The lines below use `esc_attr( htmlspecialchars( ) )` because esc_attr tries to be too smart and won't double-encode, and we need that here. |
| 944 | 1042 | $attr['data-image-title'] = esc_attr( htmlspecialchars( $attachment_title, ENT_COMPAT ) ); |
| 945 | 1043 | $attr['data-image-description'] = esc_attr( htmlspecialchars( $attachment_desc, ENT_COMPAT ) ); |
| 946 | 1044 | $attr['data-image-caption'] = esc_attr( htmlspecialchars( $attachment_caption, ENT_COMPAT ) ); |
| 947 | - $attr['data-medium-file'] = esc_attr( $medium_file ); | |
| 948 | 1045 | $attr['data-large-file'] = esc_attr( $large_file ); |
| 949 | - | |
| 950 | 1046 | return $attr; |
| 951 | 1047 | } |
| 952 | 1048 | |
| 953 | 1049 | /** |
| @@ -986,12 +1082,12 @@ | ||
| 986 | 1082 | * @param array $extra_data Array of data about the site and the post. |
| 987 | 1083 | */ |
| 988 | 1084 | $extra_data = apply_filters( 'jp_carousel_add_data_to_container', $extra_data ); |
| 989 | 1085 | foreach ( (array) $extra_data as $data_key => $data_values ) { |
| 990 | - $html = str_replace( '<div ', '<div ' . esc_attr( $data_key ) . "='" . wp_json_encode( $data_values ) . "' ", $html ); | |
| 991 | - $html = str_replace( '<ul class="wp-block-gallery', '<ul ' . esc_attr( $data_key ) . "='" . wp_json_encode( $data_values ) . "' class=\"wp-block-gallery", $html ); | |
| 992 | - $html = str_replace( '<ul class="blocks-gallery-grid', '<ul ' . esc_attr( $data_key ) . "='" . wp_json_encode( $data_values ) . "' class=\"blocks-gallery-grid", $html ); | |
| 993 | - $html = preg_replace( '/\<figure([^>]*)class="(wp-block-gallery[^"]*?has-nested-images.*?)"/', '<figure ' . esc_attr( $data_key ) . "='" . wp_json_encode( $data_values ) . "' $1 class=\"$2\"", $html ); | |
| 1086 | + $html = str_replace( '<div ', '<div ' . esc_attr( $data_key ) . "='" . esc_attr( wp_json_encode( $data_values, JSON_HEX_AMP | JSON_UNESCAPED_SLASHES ) ) . "' ", $html ); | |
| 1087 | + $html = str_replace( '<ul class="wp-block-gallery', '<ul ' . esc_attr( $data_key ) . "='" . esc_attr( wp_json_encode( $data_values, JSON_HEX_AMP | JSON_UNESCAPED_SLASHES ) ) . "' class=\"wp-block-gallery", $html ); | |
| 1088 | + $html = str_replace( '<ul class="blocks-gallery-grid', '<ul ' . esc_attr( $data_key ) . "='" . esc_attr( wp_json_encode( $data_values, JSON_HEX_AMP | JSON_UNESCAPED_SLASHES ) ) . "' class=\"blocks-gallery-grid", $html ); | |
| 1089 | + $html = preg_replace( '/\<figure([^>]*)class="(wp-block-gallery[^"]*?has-nested-images.*?)"/', '<figure ' . esc_attr( $data_key ) . "='" . esc_attr( wp_json_encode( $data_values, JSON_HEX_AMP | JSON_UNESCAPED_SLASHES ) ) . "' $1 class=\"$2\"", $html ); | |
| 994 | 1090 | } |
| 995 | 1091 | } |
| 996 | 1092 | |
| 997 | 1093 | return $html; |
| @@ -1036,9 +1132,9 @@ | ||
| 1036 | 1132 | |
| 1037 | 1133 | /** |
| 1038 | 1134 | * Retrieves comment information |
| 1039 | 1135 | * |
| 1040 | - * @return string | |
| 1136 | + * @return never | |
| 1041 | 1137 | */ |
| 1042 | 1138 | public function get_attachment_comments() { |
| 1043 | 1139 | if ( ! headers_sent() ) { |
| 1044 | 1140 | header( 'Content-type: text/javascript' ); |
| @@ -1062,11 +1158,11 @@ | ||
| 1062 | 1158 | |
| 1063 | 1159 | if ( ! $attachment_id ) { |
| 1064 | 1160 | wp_send_json_error( |
| 1065 | 1161 | __( 'Missing attachment ID.', 'jetpack' ), |
| 1066 | - 403 | |
| 1162 | + 403, | |
| 1163 | + JSON_UNESCAPED_SLASHES | |
| 1067 | 1164 | ); |
| 1068 | - return; | |
| 1069 | 1165 | } |
| 1070 | 1166 | |
| 1071 | 1167 | $attachment_post = get_post( $attachment_id ); |
| 1072 | 1168 | // If we have no info about that attachment, bail. |
| @@ -1072,11 +1168,11 @@ | ||
| 1072 | 1168 | // If we have no info about that attachment, bail. |
| 1073 | 1169 | if ( ! ( $attachment_post instanceof WP_Post ) ) { |
| 1074 | 1170 | wp_send_json_error( |
| 1075 | 1171 | __( 'Missing attachment info.', 'jetpack' ), |
| 1076 | - 403 | |
| 1172 | + 403, | |
| 1173 | + JSON_UNESCAPED_SLASHES | |
| 1077 | 1174 | ); |
| 1078 | - return; | |
| 1079 | 1175 | } |
| 1080 | 1176 | |
| 1081 | 1177 | // This AJAX call should only be used to fetch comments of attachments. |
| 1082 | 1178 | if ( 'attachment' !== $attachment_post->post_type ) { |
| @@ -1081,11 +1177,11 @@ | ||
| 1081 | 1177 | // This AJAX call should only be used to fetch comments of attachments. |
| 1082 | 1178 | if ( 'attachment' !== $attachment_post->post_type ) { |
| 1083 | 1179 | wp_send_json_error( |
| 1084 | 1180 | __( 'You aren’t authorized to do that.', 'jetpack' ), |
| 1085 | - 403 | |
| 1181 | + 403, | |
| 1182 | + JSON_UNESCAPED_SLASHES | |
| 1086 | 1183 | ); |
| 1087 | - return; | |
| 1088 | 1184 | } |
| 1089 | 1185 | |
| 1090 | 1186 | $parent_post = get_post_parent( $attachment_id ); |
| 1091 | 1187 | |
| @@ -1103,11 +1199,11 @@ | ||
| 1103 | 1199 | $current_user = wp_get_current_user(); |
| 1104 | 1200 | if ( ! ( $current_user instanceof WP_User ) ) { |
| 1105 | 1201 | wp_send_json_error( |
| 1106 | 1202 | __( 'Missing user info.', 'jetpack' ), |
| 1107 | - 403 | |
| 1203 | + 403, | |
| 1204 | + JSON_UNESCAPED_SLASHES | |
| 1108 | 1205 | ); |
| 1109 | - return; | |
| 1110 | 1206 | } |
| 1111 | 1207 | |
| 1112 | 1208 | /* |
| 1113 | 1209 | * If a post is private / draft |
| @@ -1119,11 +1215,11 @@ | ||
| 1119 | 1215 | && ! current_user_can( 'read_post', $parent_post->ID ) |
| 1120 | 1216 | ) { |
| 1121 | 1217 | wp_send_json_error( |
| 1122 | 1218 | __( 'You aren’t authorized to do that.', 'jetpack' ), |
| 1123 | - 403 | |
| 1219 | + 403, | |
| 1220 | + JSON_UNESCAPED_SLASHES | |
| 1124 | 1221 | ); |
| 1125 | - return; | |
| 1126 | 1222 | } |
| 1127 | 1223 | } |
| 1128 | 1224 | |
| 1129 | 1225 | if ( $offset < 1 ) { |
| @@ -1157,13 +1253,15 @@ | ||
| 1157 | 1253 | 'content' => wpautop( $comment->comment_content ), |
| 1158 | 1254 | ); |
| 1159 | 1255 | } |
| 1160 | 1256 | |
| 1161 | - die( wp_json_encode( $out ) ); | |
| 1257 | + wp_send_json( $out, null, JSON_UNESCAPED_SLASHES ); | |
| 1162 | 1258 | } |
| 1163 | 1259 | |
| 1164 | 1260 | /** |
| 1165 | 1261 | * Adds a new comment to the database |
| 1262 | + * | |
| 1263 | + * @return never | |
| 1166 | 1264 | */ |
| 1167 | 1265 | public function post_attachment_comment() { |
| 1168 | 1266 | if ( ! headers_sent() ) { |
| 1169 | 1267 | header( 'Content-type: text/javascript' ); |
| @@ -1169,9 +1267,9 @@ | ||
| 1169 | 1267 | header( 'Content-type: text/javascript' ); |
| 1170 | 1268 | } |
| 1171 | 1269 | |
| 1172 | 1270 | if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'carousel_nonce' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WP Core doesn't unslash or sanitize nonces either |
| 1173 | - die( wp_json_encode( array( 'error' => __( 'Nonce verification failed.', 'jetpack' ) ) ) ); | |
| 1271 | + die( wp_json_encode( array( 'error' => __( 'Nonce verification failed.', 'jetpack' ) ), JSON_UNESCAPED_SLASHES ) ); | |
| 1174 | 1272 | } |
| 1175 | 1273 | |
| 1176 | 1274 | $_blog_id = isset( $_POST['blog_id'] ) ? (int) $_POST['blog_id'] : 0; |
| 1177 | 1275 | $_post_id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0; |
| @@ -1177,17 +1275,17 @@ | ||
| 1177 | 1275 | $_post_id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0; |
| 1178 | 1276 | $comment = isset( $_POST['comment'] ) ? filter_var( wp_unslash( $_POST['comment'] ) ) : null; |
| 1179 | 1277 | |
| 1180 | 1278 | if ( empty( $_blog_id ) ) { |
| 1181 | - die( wp_json_encode( array( 'error' => __( 'Missing target blog ID.', 'jetpack' ) ) ) ); | |
| 1279 | + die( wp_json_encode( array( 'error' => __( 'Missing target blog ID.', 'jetpack' ) ), JSON_UNESCAPED_SLASHES ) ); | |
| 1182 | 1280 | } |
| 1183 | 1281 | |
| 1184 | 1282 | if ( empty( $_post_id ) ) { |
| 1185 | - die( wp_json_encode( array( 'error' => __( 'Missing target post ID.', 'jetpack' ) ) ) ); | |
| 1283 | + die( wp_json_encode( array( 'error' => __( 'Missing target post ID.', 'jetpack' ) ), JSON_UNESCAPED_SLASHES ) ); | |
| 1186 | 1284 | } |
| 1187 | 1285 | |
| 1188 | 1286 | if ( empty( $comment ) ) { |
| 1189 | - die( wp_json_encode( array( 'error' => __( 'No comment text was submitted.', 'jetpack' ) ) ) ); | |
| 1287 | + die( wp_json_encode( array( 'error' => __( 'No comment text was submitted.', 'jetpack' ) ), JSON_UNESCAPED_SLASHES ) ); | |
| 1190 | 1288 | } |
| 1191 | 1289 | |
| 1192 | 1290 | // Used in context like NewDash. |
| 1193 | 1291 | $switched = false; |
| @@ -1202,9 +1300,9 @@ | ||
| 1202 | 1300 | if ( ! comments_open( $_post_id ) ) { |
| 1203 | 1301 | if ( $switched ) { |
| 1204 | 1302 | restore_current_blog(); |
| 1205 | 1303 | } |
| 1206 | - die( wp_json_encode( array( 'error' => __( 'Comments on this post are closed.', 'jetpack' ) ) ) ); | |
| 1304 | + die( wp_json_encode( array( 'error' => __( 'Comments on this post are closed.', 'jetpack' ) ), JSON_UNESCAPED_SLASHES ) ); | |
| 1207 | 1305 | } |
| 1208 | 1306 | |
| 1209 | 1307 | if ( is_user_logged_in() ) { |
| 1210 | 1308 | $user = wp_get_current_user(); |
| @@ -1216,15 +1314,18 @@ | ||
| 1216 | 1314 | if ( empty( $user_id ) ) { |
| 1217 | 1315 | if ( $switched ) { |
| 1218 | 1316 | restore_current_blog(); |
| 1219 | 1317 | } |
| 1220 | - die( wp_json_encode( array( 'error' => __( 'Sorry, but we could not authenticate your request.', 'jetpack' ) ) ) ); | |
| 1318 | + die( wp_json_encode( array( 'error' => __( 'Sorry, but we could not authenticate your request.', 'jetpack' ) ), JSON_UNESCAPED_SLASHES ) ); | |
| 1221 | 1319 | } |
| 1222 | 1320 | } else { |
| 1223 | 1321 | $user_id = 0; |
| 1224 | 1322 | $display_name = isset( $_POST['author'] ) ? sanitize_text_field( wp_unslash( $_POST['author'] ) ) : null; |
| 1225 | - $email = isset( $_POST['email'] ) ? wp_unslash( $_POST['email'] ) : null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Checked or sanitized below. | |
| 1226 | - $url = isset( $_POST['url'] ) ? esc_url_raw( wp_unslash( $_POST['url'] ) ) : null; | |
| 1323 | + $email = null; | |
| 1324 | + if ( isset( $_POST['email'] ) && is_string( $_POST['email'] ) ) { | |
| 1325 | + $email = wp_unslash( $_POST['email'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Checked or sanitized below. | |
| 1326 | + } | |
| 1327 | + $url = isset( $_POST['url'] ) && is_string( $_POST['url'] ) ? esc_url_raw( wp_unslash( $_POST['url'] ) ) : null; | |
| 1227 | 1328 | |
| 1228 | 1329 | if ( get_option( 'require_name_email' ) ) { |
| 1229 | 1330 | if ( empty( $display_name ) ) { |
| 1230 | 1331 | if ( $switched ) { |
| @@ -1229,9 +1330,9 @@ | ||
| 1229 | 1330 | if ( empty( $display_name ) ) { |
| 1230 | 1331 | if ( $switched ) { |
| 1231 | 1332 | restore_current_blog(); |
| 1232 | 1333 | } |
| 1233 | - die( wp_json_encode( array( 'error' => __( 'Please provide your name.', 'jetpack' ) ) ) ); | |
| 1334 | + die( wp_json_encode( array( 'error' => __( 'Please provide your name.', 'jetpack' ) ), JSON_UNESCAPED_SLASHES ) ); | |
| 1234 | 1335 | } |
| 1235 | 1336 | |
| 1236 | 1337 | if ( empty( $email ) ) { |
| 1237 | 1338 | if ( $switched ) { |
| @@ -1236,9 +1337,9 @@ | ||
| 1236 | 1337 | if ( empty( $email ) ) { |
| 1237 | 1338 | if ( $switched ) { |
| 1238 | 1339 | restore_current_blog(); |
| 1239 | 1340 | } |
| 1240 | - die( wp_json_encode( array( 'error' => __( 'Please provide an email address.', 'jetpack' ) ) ) ); | |
| 1341 | + die( wp_json_encode( array( 'error' => __( 'Please provide an email address.', 'jetpack' ) ), JSON_UNESCAPED_SLASHES ) ); | |
| 1241 | 1342 | } |
| 1242 | 1343 | |
| 1243 | 1344 | if ( ! is_email( $email ) ) { |
| 1244 | 1345 | if ( $switched ) { |
| @@ -1243,9 +1344,9 @@ | ||
| 1243 | 1344 | if ( ! is_email( $email ) ) { |
| 1244 | 1345 | if ( $switched ) { |
| 1245 | 1346 | restore_current_blog(); |
| 1246 | 1347 | } |
| 1247 | - die( wp_json_encode( array( 'error' => __( 'Please provide a valid email address.', 'jetpack' ) ) ) ); | |
| 1348 | + die( wp_json_encode( array( 'error' => __( 'Please provide a valid email address.', 'jetpack' ) ), JSON_UNESCAPED_SLASHES ) ); | |
| 1248 | 1349 | } |
| 1249 | 1350 | } else { |
| 1250 | 1351 | $email = $email !== null ? sanitize_email( $email ) : null; |
| 1251 | 1352 | } |
| @@ -1286,9 +1387,10 @@ | ||
| 1286 | 1387 | wp_json_encode( |
| 1287 | 1388 | array( |
| 1288 | 1389 | 'comment_id' => $comment_id, |
| 1289 | 1390 | 'comment_status' => $comment_status, |
| 1290 | - ) | |
| 1391 | + ), | |
| 1392 | + JSON_UNESCAPED_SLASHES | |
| 1291 | 1393 | ) |
| 1292 | 1394 | ); |
| 1293 | 1395 | } |
| 1294 | 1396 | |
| @@ -1416,9 +1518,9 @@ | ||
| 1416 | 1518 | * Sanitize input for the `carousel_display_exif` setting. |
| 1417 | 1519 | * |
| 1418 | 1520 | * @param mixed $value User input setting value. |
| 1419 | 1521 | * |
| 1420 | - * @return number Sanitized value, only 1 or 0. | |
| 1522 | + * @return int Sanitized value, only 1 or 0. | |
| 1421 | 1523 | */ |
| 1422 | 1524 | public function carousel_display_exif_sanitize( $value ) { |
| 1423 | 1525 | return $this->sanitize_1or0_option( $value ); |
| 1424 | 1526 | } |
| @@ -1425,11 +1527,11 @@ | ||
| 1425 | 1527 | |
| 1426 | 1528 | /** |
| 1427 | 1529 | * Return sanitized option for value that controls whether comments will be hidden or not. |
| 1428 | 1530 | * |
| 1429 | - * @param number $value Value to sanitize. | |
| 1531 | + * @param mixed $value Value to sanitize. | |
| 1430 | 1532 | * |
| 1431 | - * @return number Sanitized value, only 1 or 0. | |
| 1533 | + * @return int Sanitized value, only 1 or 0. | |
| 1432 | 1534 | */ |
| 1433 | 1535 | public function carousel_display_comments_sanitize( $value ) { |
| 1434 | 1536 | return $this->sanitize_1or0_option( $value ); |
| 1435 | 1537 | } |
| @@ -1469,9 +1571,9 @@ | ||
| 1469 | 1571 | * Sanitize input for the `carousel_enable_it` setting. |
| 1470 | 1572 | * |
| 1471 | 1573 | * @param mixed $value User input. |
| 1472 | 1574 | * |
| 1473 | - * @return number Sanitized value, only 1 or 0. | |
| 1575 | + * @return int Sanitized value, only 1 or 0. | |
| 1474 | 1576 | */ |
| 1475 | 1577 | public function carousel_enable_it_sanitize( $value ) { |
| 1476 | 1578 | return $this->sanitize_1or0_option( $value ); |
| 1477 | 1579 | } |