WooCommerce Code Reference

class-video.php

Source code

<?php
/**
 * This file is part of the WooCommerce Email Editor package
 *
 * @package Automattic\WooCommerce\EmailEditor
 */

declare( strict_types = 1 );
namespace Automattic\WooCommerce\EmailEditor\Integrations\Core\Renderer\Blocks;

use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Rendering_Context;
use Automattic\WooCommerce\EmailEditor\Integrations\Utils\Dom_Document_Helper;

/**
 * Video block renderer.
 * This renderer handles core/video blocks by reusing the cover block renderer
 * to show a thumbnail with a play button overlay.
 */
class Video extends Cover {
	/**
	 * Renders the video block content by transforming it into a cover block structure.
	 * Shows the video poster/thumbnail with a play button overlay using the parent cover renderer.
	 *
	 * @param string            $block_content Block content.
	 * @param array             $parsed_block Parsed block.
	 * @param Rendering_Context $rendering_context Rendering context.
	 * @return string
	 */
	protected function render_content( string $block_content, array $parsed_block, Rendering_Context $rendering_context ): string {
		$block_attrs = $parsed_block['attrs'] ?? array();

		// Extract poster URL from video attributes.
		$poster_url = $this->extract_poster_url( $block_attrs, $block_content );

		// If no poster image, return empty content.
		if ( empty( $poster_url ) ) {
			return '';
		}

		// Transform video block into cover block structure and delegate to parent.
		$cover_block = $this->transform_to_cover_block( $parsed_block, $poster_url );
		return parent::render_content( $block_content, $cover_block, $rendering_context );
	}

	/**
	 * Extract poster URL from block attributes.
	 *
	 * @param array  $block_attrs Block attributes.
	 * @param string $block_content Original block content (unused, kept for consistency).
	 * @return string Poster URL or empty string.
	 */
	private function extract_poster_url( array $block_attrs, string $block_content ): string {
		// Check for poster attribute.
		if ( ! empty( $block_attrs['poster'] ) ) {
			return esc_url( $block_attrs['poster'] );
		}

		return '';
	}

	/**
	 * Extract video URL from block content.
	 *
	 * @param string $block_content Block content HTML.
	 * @return string Video URL or empty string.
	 */
	private function extract_video_url( string $block_content ): string {
		// Use Dom_Document_Helper for robust HTML parsing.
		$dom_helper = new Dom_Document_Helper( $block_content );

		// Find the wp-block-embed__wrapper div.
		$wrapper_element = $dom_helper->find_element( 'div' );
		if ( ! $wrapper_element ) {
			return '';
		}

		// Check if this div has the correct class.
		$class_attr = $dom_helper->get_attribute_value( $wrapper_element, 'class' );
		if ( strpos( $class_attr, 'wp-block-embed__wrapper' ) === false ) {
			return '';
		}

		// Get the inner HTML content from the wrapper div.
		$inner_html = $dom_helper->get_element_inner_html( $wrapper_element );

		// Look for HTTP/HTTPS URLs in the inner HTML content.
		if ( preg_match( '/(?<![a-zA-Z0-9.-])https?:\/\/[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}[a-zA-Z0-9\/?=&%-]*(?![a-zA-Z0-9.-])/', $inner_html, $matches ) ) {
			$url = $matches[0];

			// Decode HTML entities and validate URL.
			$url = html_entity_decode( $url, ENT_QUOTES | ENT_HTML5, 'UTF-8' );

			// Validate the URL.
			if ( filter_var( $url, FILTER_VALIDATE_URL ) && wp_http_validate_url( $url ) ) {
				return $url;
			}
		}

		return '';
	}

	/**
	 * Transform a video block into a cover block structure.
	 *
	 * @param array  $video_block Original video block.
	 * @param string $poster_url Poster URL to use as background.
	 * @return array Cover block structure.
	 */
	private function transform_to_cover_block( array $video_block, string $poster_url ): array {
		$block_attrs   = $video_block['attrs'] ?? array();
		$block_content = $video_block['innerHTML'] ?? '';

		// Extract video URL from block content, fall back to post URL.
		// Priority: 1) Video URL (if found), 2) Post permalink (fallback).
		$video_url = $this->extract_video_url( $block_content );
		$link_url  = ! empty( $video_url ) ? $video_url : $this->get_current_post_url();

		return array(
			'blockName'   => 'core/cover',
			'attrs'       => array(
				'url'       => $poster_url,
				'minHeight' => '390px', // Custom attribute for video blocks.
			),
			'innerBlocks' => array(
				array(
					'blockName'    => 'core/html',
					'attrs'        => array(),
					'innerBlocks'  => array(),
					'innerHTML'    => $this->create_play_button_html( $link_url ),
					'innerContent' => array( $this->create_play_button_html( $link_url ) ),
				),
			),
			'innerHTML'   => $block_content,
		);
	}

	/**
	 * Create the play button HTML with optional link.
	 *
	 * @param string $link_url Optional URL to link to.
	 * @return string Play button HTML.
	 */
	private function create_play_button_html( string $link_url = '' ): string {
		$play_icon_url = $this->get_play_icon_url();

		$play_button = sprintf(
			'<img src="%s" alt="%s" style="width: 48px; height: 48px; display: inline-block;" />',
			esc_url( $play_icon_url ),
			// translators: Alt text for video play button icon.
			esc_attr( __( 'Play', 'woocommerce' ) )
		);

		// Wrap the play button in a link if URL is provided.
		if ( ! empty( $link_url ) ) {
			$play_button = sprintf(
				'<a href="%s" target="_blank" rel="noopener noreferrer nofollow" style="display: inline-block; text-decoration: none;">%s</a>',
				esc_url( $link_url ),
				$play_button
			);
		}

		return sprintf(
			'<p style="text-align: center;">%s</p>',
			$play_button
		);
	}

	/**
	 * Get the URL for the play button icon.
	 *
	 * @return string Play button icon URL.
	 */
	private function get_play_icon_url(): string {
		$file_name = '/icons/video/play2x.png';
		return plugins_url( $file_name, __FILE__ );
	}

	/**
	 * Get the current post permalink with security validation.
	 *
	 * @return string Post permalink or empty string if invalid.
	 */
	private function get_current_post_url(): string {
		global $post;

		if ( ! $post instanceof \WP_Post ) {
			return '';
		}

		$permalink = get_permalink( $post->ID );
		if ( empty( $permalink ) ) {
			return '';
		}

		// Validate URL type and format (following audio block pattern).
		if ( strpos( $permalink, 'https://' ) !== 0 && strpos( $permalink, 'http://' ) !== 0 ) {
			// Reject non-HTTP protocols for security.
			return '';
		}

		// For all HTTP(S) URLs, validate with wp_http_validate_url.
		if ( ! wp_http_validate_url( $permalink ) ) {
			return '';
		}

		return $permalink;
	}
}