[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/blocks/ -> cover.php (source)

   1  <?php
   2  /**
   3   * Server-side rendering of the `core/cover` block.
   4   *
   5   * @package WordPress
   6   */
   7  
   8  /**
   9   * Renders the `core/cover` block on server.
  10   *
  11   * @since 6.0.0
  12   *
  13   * @param array  $attributes The block attributes.
  14   * @param string $content    The block rendered content.
  15   *
  16   * @return string Returns the cover block markup, if useFeaturedImage is true.
  17   */
  18  function render_block_core_cover( $attributes, $content ) {
  19      // The view module decides whether a background video may play, so it is
  20      // only needed for the two video background types.
  21      if (
  22          isset( $attributes['backgroundType'] ) &&
  23          in_array( $attributes['backgroundType'], array( 'video', 'embed-video' ), true )
  24      ) {
  25          wp_enqueue_script_module( '@wordpress/block-library/cover/view' );
  26      }
  27  
  28      // Handle embed video background.
  29      if (
  30          isset( $attributes['backgroundType'] ) &&
  31          'embed-video' === $attributes['backgroundType'] &&
  32          isset( $attributes['url'] ) &&
  33          ! empty( $attributes['url'] ) &&
  34          is_string( $attributes['url'] )
  35      ) {
  36          $url = $attributes['url'];
  37  
  38          // Use WordPress's native oEmbed processing (includes caching).
  39          $oembed_html = wp_oembed_get( $url );
  40  
  41          if ( $oembed_html ) {
  42              // Read the iframe src with the HTML API, which returns the attribute
  43              // value decoded, so `&amp;` in the query string becomes `&`.
  44              $oembed_processor = new WP_HTML_Tag_Processor( $oembed_html );
  45              $iframe_src       = $oembed_processor->next_tag( 'IFRAME' )
  46                  ? $oembed_processor->get_attribute( 'src' )
  47                  : null;
  48              if ( is_string( $iframe_src ) && '' !== $iframe_src ) {
  49                  // Detect provider from iframe src URL.
  50                  $lower_src = strtolower( $iframe_src );
  51                  $provider  = null;
  52  
  53                  if ( str_contains( $lower_src, 'youtube.com' ) || str_contains( $lower_src, 'youtu.be' ) ) {
  54                      $provider = 'youtube';
  55                  } elseif ( str_contains( $lower_src, 'vimeo.com' ) ) {
  56                      $provider = 'vimeo';
  57                  } elseif ( str_contains( $lower_src, 'videopress.com' ) ) {
  58                      $provider = 'videopress';
  59                  } elseif ( str_contains( $lower_src, 'wordpress.tv' ) ) {
  60                      $provider = 'wordpress-tv';
  61                  }
  62  
  63                  // Modify iframe src to add background video parameters based on provider.
  64                  $parsed_url         = wp_parse_url( $iframe_src );
  65                  $reduced_motion_src = null;
  66                  if ( $parsed_url && isset( $parsed_url['host'] ) ) {
  67                      // Parse existing query parameters.
  68                      $query_params = array();
  69                      if ( isset( $parsed_url['query'] ) ) {
  70                          parse_str( $parsed_url['query'], $query_params );
  71                      }
  72  
  73                      // Add background video parameters based on provider. The
  74                      // autoplay parameters are kept separate so a source without
  75                      // them can also be built, which the view script swaps in for
  76                      // a visitor who has asked for reduced motion.
  77                      $autoplay_params = array();
  78                      if ( 'youtube' === $provider ) {
  79                          $query_params['mute']           = '1';
  80                          $query_params['loop']           = '1';
  81                          $query_params['controls']       = '0';
  82                          $query_params['modestbranding'] = '1';
  83                          $query_params['playsinline']    = '1';
  84  
  85                          // For loop to work, we need the playlist parameter.
  86                          $path          = $parsed_url['path'] ?? '';
  87                          $path_segments = explode( '/', $path );
  88                          $video_id      = end( $path_segments );
  89                          if ( $video_id ) {
  90                                  $query_params['playlist'] = $video_id;
  91                          }
  92  
  93                          $autoplay_params['autoplay'] = '1';
  94                      } elseif ( 'vimeo' === $provider ) {
  95                          $query_params['muted']       = '1';
  96                          $query_params['loop']        = '1';
  97                          $query_params['controls']    = '0';
  98                          $query_params['transparent'] = '0';
  99  
 100                          // Vimeo's background mode always autoplays, so it can only
 101                          // be used when autoplay is allowed.
 102                          $autoplay_params['autoplay']   = '1';
 103                          $autoplay_params['background'] = '1';
 104                      } elseif ( 'videopress' === $provider || 'wordpress-tv' === $provider ) {
 105                          $query_params['loop']  = '1';
 106                          $query_params['muted'] = '1';
 107  
 108                          $autoplay_params['autoplay'] = '1';
 109                      }
 110  
 111                      // Rebuild the URL with new parameters.
 112                      $iframe_base = $parsed_url['scheme'] . '://' . $parsed_url['host'];
 113                      if ( isset( $parsed_url['path'] ) ) {
 114                          $iframe_base .= $parsed_url['path'];
 115                      }
 116  
 117                      $reduced_motion_src = empty( $query_params )
 118                          ? $iframe_base
 119                          : $iframe_base . '?' . http_build_query( $query_params );
 120  
 121                      $iframe_src = empty( $autoplay_params )
 122                          ? $reduced_motion_src
 123                          : $iframe_base . '?' . http_build_query( array_merge( $query_params, $autoplay_params ) );
 124                  }
 125  
 126                  // Build the iframe HTML that will replace the figure. The server
 127                  // renders the autoplaying source, and the view script swaps in the
 128                  // one without autoplay for a visitor who prefers reduced motion.
 129                  $iframe_html = sprintf(
 130                      '<div class="wp-block-cover__video-background wp-block-cover__embed-background" data-wp-interactive="core/cover" data-wp-context=\'%s\'><iframe src="%s" data-wp-bind--src="state.videoSrc" title="Background video" frameborder="0" allow="autoplay; fullscreen"></iframe></div>',
 131                      esc_attr(
 132                          wp_json_encode(
 133                              array(
 134                                  'src'              => $iframe_src,
 135                                  'reducedMotionSrc' => $reduced_motion_src,
 136                              )
 137                          )
 138                      ),
 139                      esc_url( $iframe_src )
 140                  );
 141  
 142                  // Use the HTML API to find and replace the figure.wp-block-embed element.
 143                  $processor = new WP_HTML_Tag_Processor( $content );
 144  
 145                  if ( $processor->next_tag(
 146                      array(
 147                          'tag_name'   => 'FIGURE',
 148                          'class_name' => 'wp-block-embed',
 149                      )
 150                  ) ) {
 151                      // Use regex with PREG_OFFSET_CAPTURE to find the position of the figure element.
 152                      // This follows the same pattern used for featured image insertion below.
 153                      $figure_pattern = '/<figure\s+[^>]*\bwp-block-embed\b[^>]*>.*?<\/figure>/is';
 154                      if ( 1 === preg_match( $figure_pattern, $content, $matches, PREG_OFFSET_CAPTURE ) ) {
 155                          $figure_start  = $matches[0][1];
 156                          $figure_length = strlen( $matches[0][0] );
 157                          $figure_end    = $figure_start + $figure_length;
 158  
 159                          // Replace the figure element with the iframe HTML.
 160                          $content = substr( $content, 0, $figure_start ) . $iframe_html . substr( $content, $figure_end );
 161                      }
 162                  }
 163              }
 164          }
 165  
 166          return $content;
 167      }
 168  
 169      // Handle uploaded video background. The video is saved with `autoplay`, so
 170      // the view module stops it for a visitor who prefers reduced motion. The
 171      // directives are added here rather than in the saved markup so existing
 172      // content does not need a deprecation.
 173      if (
 174          isset( $attributes['backgroundType'] ) &&
 175          'video' === $attributes['backgroundType']
 176      ) {
 177          $processor = new WP_HTML_Tag_Processor( $content );
 178  
 179          if ( $processor->next_tag(
 180              array(
 181                  'tag_name'   => 'VIDEO',
 182                  'class_name' => 'wp-block-cover__video-background',
 183              )
 184          ) ) {
 185              $processor->set_attribute( 'data-wp-interactive', 'core/cover' );
 186              $processor->set_attribute( 'data-wp-init', 'callbacks.stopBackgroundVideo' );
 187  
 188              $content = $processor->get_updated_html();
 189          }
 190  
 191          return $content;
 192      }
 193  
 194      if ( 'image' !== $attributes['backgroundType'] || false === $attributes['useFeaturedImage'] ) {
 195          return $content;
 196      }
 197  
 198      $cover_focal_point = $attributes['focalPoint'] ?? null;
 199      $focal_point_x     = null;
 200      $focal_point_y     = null;
 201      if ( is_array( $cover_focal_point ) ) {
 202          $focal_point_x = isset( $cover_focal_point['x'] ) && is_numeric( $cover_focal_point['x'] ) ? $cover_focal_point['x'] : null;
 203          $focal_point_y = isset( $cover_focal_point['y'] ) && is_numeric( $cover_focal_point['y'] ) ? $cover_focal_point['y'] : null;
 204      }
 205      $object_position = null !== $focal_point_x && null !== $focal_point_y
 206          ? round( $focal_point_x * 100 ) . '% ' . round( $focal_point_y * 100 ) . '%'
 207          : null;
 208  
 209      if ( ! ( $attributes['hasParallax'] || $attributes['isRepeated'] ) ) {
 210          $attr = array(
 211              'class'           => 'wp-block-cover__image-background',
 212              'data-object-fit' => 'cover',
 213          );
 214  
 215          if ( $object_position ) {
 216              $attr['data-object-position'] = $object_position;
 217              $attr['style']                = 'object-position:' . $object_position . ';';
 218          }
 219  
 220          $image = get_the_post_thumbnail( null, $attributes['sizeSlug'] ?? 'post-thumbnail', $attr );
 221      } else {
 222          if ( in_the_loop() ) {
 223              update_post_thumbnail_cache();
 224          }
 225          $current_featured_image = get_the_post_thumbnail_url( null, $attributes['sizeSlug'] ?? 'full' );
 226          if ( ! $current_featured_image ) {
 227              return $content;
 228          }
 229  
 230          $current_thumbnail_id = get_post_thumbnail_id();
 231  
 232          $processor = new WP_HTML_Tag_Processor( '<div></div>' );
 233          $processor->next_tag();
 234  
 235          $current_alt = trim( strip_tags( get_post_meta( $current_thumbnail_id, '_wp_attachment_image_alt', true ) ) );
 236          if ( $current_alt ) {
 237              $processor->set_attribute( 'role', 'img' );
 238              $processor->set_attribute( 'aria-label', $current_alt );
 239          }
 240  
 241          $processor->add_class( 'wp-block-cover__image-background' );
 242          $processor->add_class( 'wp-image-' . $current_thumbnail_id );
 243          if ( $attributes['hasParallax'] ) {
 244              $processor->add_class( 'has-parallax' );
 245          }
 246          if ( $attributes['isRepeated'] ) {
 247              $processor->add_class( 'is-repeated' );
 248          }
 249  
 250          $styles  = 'background-position:' . ( $object_position ?? '50% 50%' ) . ';';
 251          $styles .= 'background-image:url(' . esc_url( $current_featured_image ) . ');';
 252          $processor->set_attribute( 'style', $styles );
 253  
 254          $image = $processor->get_updated_html();
 255      }
 256  
 257      /*
 258       * Inserts the featured image between the (1st) cover 'background' `span` and 'inner_container' `div`,
 259       * and removes eventual whitespace characters between the two (typically introduced at template level)
 260       */
 261      $inner_container_start = '/<div\b[^>]+wp-block-cover__inner-container[\s|"][^>]*>/U';
 262      if ( 1 === preg_match( $inner_container_start, $content, $matches, PREG_OFFSET_CAPTURE ) ) {
 263          $offset  = $matches[0][1];
 264          $content = substr( $content, 0, $offset ) . $image . substr( $content, $offset );
 265      }
 266  
 267      return $content;
 268  }
 269  
 270  /**
 271   * Registers the `core/cover` block renderer on server.
 272   *
 273   * @since 6.0.0
 274   */
 275  function register_block_core_cover() {
 276      register_block_type_from_metadata(
 277          __DIR__ . '/cover',
 278          array(
 279              'render_callback' => 'render_block_core_cover',
 280          )
 281      );
 282  }
 283  add_action( 'init', 'register_block_core_cover' );


Generated : Tue Oct 27 08:20:35 2026 Cross-referenced by PHPXref