'text', 'content' ) and array( 'type' => 'image', 'url', 'alt'? ); the featured image first. * @type string $source_url The post's address once it is published; null before. * @type int $words The text's words. * @type array $left_out Pictures not sent, as array( 'url', 'why' ): "private" (Ojelio cannot fetch it) or "limit". * } */ public static function from_post( WP_Post $post ) { $items = array(); foreach ( parse_blocks( $post->post_content ) as $block ) { self::read_block( $block, $items ); } // The featured image opens the video, unless the content already shows it. $featured = get_post_thumbnail_id( $post ); $featured = $featured ? self::image_of( '', (int) $featured ) : null; if ( $featured && ! in_array( $featured['url'], wp_list_pluck( wp_list_filter( $items, array( 'type' => 'image' ) ), 'url' ), true ) ) { array_unshift( $items, $featured ); } $blocks = array(); $left_out = array(); $images = 0; foreach ( $items as $item ) { if ( 'text' === $item['type'] ) { // Text that follows text joins it: the script reads on, and the list stays short. $last = count( $blocks ) - 1; if ( $last >= 0 && 'text' === $blocks[ $last ]['type'] ) { $blocks[ $last ]['content'] .= "\n\n" . $item['content']; } else { $blocks[] = $item; } continue; } if ( ! self::is_public_url( $item['url'] ) ) { $left_out[] = array( 'url' => $item['url'], 'why' => 'private' ); } elseif ( $images >= self::MAX_IMAGES ) { $left_out[] = array( 'url' => $item['url'], 'why' => 'limit' ); } else { ++$images; $blocks[] = $item; } } $words = 0; foreach ( $blocks as $block ) { if ( 'text' === $block['type'] ) { $words += count( preg_split( '/\s+/u', $block['content'], -1, PREG_SPLIT_NO_EMPTY ) ); } } return array( 'title' => self::title( $post ), 'blocks' => $blocks, 'source_url' => 'publish' === $post->post_status ? get_permalink( $post ) : null, 'words' => $words, 'left_out' => $left_out, ); } public static function title( WP_Post $post ) { $title = self::clean( get_the_title( $post ) ); if ( '' === $title ) { $title = __( 'Untitled post', 'ojelio' ); } return function_exists( 'mb_substr' ) ? mb_substr( $title, 0, self::TITLE_MAX ) : substr( $title, 0, self::TITLE_MAX ); } /** * Ojelio fetches the pictures itself, so an address on this computer or a * private network would fail the whole video: such pictures are left out. */ public static function is_public_url( $url ) { $parts = wp_parse_url( $url ); if ( ! $parts || empty( $parts['host'] ) || empty( $parts['scheme'] ) || ! in_array( strtolower( $parts['scheme'] ), array( 'http', 'https' ), true ) ) { return false; } $host = strtolower( trim( $parts['host'], '[]' ) ); if ( filter_var( $host, FILTER_VALIDATE_IP ) ) { return (bool) filter_var( $host, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ); } if ( false === strpos( $host, '.' ) ) { return false; } return ! preg_match( '/\.(localhost|local|test|internal|lan|home|example|invalid)$/', $host ); } // ---- Reading blocks ---------------------------------------------------------------- private static function read_block( array $block, array &$items ) { $name = $block['blockName']; if ( null === $name ) { // Content from before the block editor (or between blocks): HTML as it is. self::read_html( $block['innerHTML'], $items ); return; } if ( in_array( $name, self::TEXT_BLOCKS, true ) ) { self::add_text( self::text_of( $block ), $items ); return; } if ( 'core/image' === $name ) { $image = self::image_of( $block['innerHTML'], isset( $block['attrs']['id'] ) ? (int) $block['attrs']['id'] : 0 ); if ( $image ) { $items[] = $image; } return; } if ( 'core/media-text' === $name || 'core/cover' === $name ) { // The picture first, then the text over or beside it. $id = isset( $block['attrs']['mediaId'] ) ? (int) $block['attrs']['mediaId'] : ( isset( $block['attrs']['id'] ) ? (int) $block['attrs']['id'] : 0 ); $image = self::image_of( $block['innerHTML'], $id, isset( $block['attrs']['url'] ) ? $block['attrs']['url'] : null ); if ( $image && ( 'core/media-text' !== $name || ! isset( $block['attrs']['mediaType'] ) || 'image' === $block['attrs']['mediaType'] ) ) { $items[] = $image; } } elseif ( ! in_array( $name, self::CONTAINERS, true ) && 'core/block' !== $name ) { // Embeds, code, tables, buttons, shortcodes, other plugins' blocks: left out. return; } if ( 'core/block' === $name && ! empty( $block['attrs']['ref'] ) ) { // A synced pattern: its own post's blocks. $pattern = get_post( (int) $block['attrs']['ref'] ); if ( $pattern && 'wp_block' === $pattern->post_type && 'publish' === $pattern->post_status ) { foreach ( parse_blocks( $pattern->post_content ) as $inner ) { self::read_block( $inner, $items ); } } return; } foreach ( $block['innerBlocks'] as $inner ) { self::read_block( $inner, $items ); } } /** A text block's words: its own HTML and its inner blocks', in order. */ private static function text_of( array $block ) { $html = ''; $inner = 0; foreach ( $block['innerContent'] as $piece ) { if ( null === $piece ) { $html .= "\n" . self::text_of( $block['innerBlocks'][ $inner ] ) . "\n"; ++$inner; } else { $html .= $piece; } } return self::html_to_text( $html ); } /** Classic content: paragraphs and pictures in the order they come. */ private static function read_html( $html, array &$items ) { $html = wpautop( $html ); $parts = preg_split( '/(]*>)/i', $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); foreach ( $parts as $part ) { if ( preg_match( '/^ 'text', 'content' => $text ); } } /** * A picture: the full-size file of the attachment when there is one (the block's * HTML usually shows a smaller size), else the image's own address. */ private static function image_of( $html, $attachment_id, $fallback_url = null ) { $url = $attachment_id ? wp_get_attachment_url( $attachment_id ) : null; if ( ! $url && preg_match( '/]*\bsrc=(["\'])(.*?)\1/i', $html, $match ) ) { $url = html_entity_decode( $match[2], ENT_QUOTES, 'UTF-8' ); } if ( ! $url ) { $url = $fallback_url; } if ( ! $url ) { return null; } $alt = ''; if ( preg_match( '/]*\balt=(["\'])(.*?)\1/i', $html, $match ) ) { $alt = self::clean( $match[2] ); } if ( '' === $alt && $attachment_id ) { $alt = self::clean( (string) get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ); } $image = array( 'type' => 'image', 'url' => esc_url_raw( $url ) ); if ( '' !== $alt ) { $image['alt'] = function_exists( 'mb_substr' ) ? mb_substr( $alt, 0, self::ALT_MAX ) : substr( $alt, 0, self::ALT_MAX ); } return $image; } private static function attachment_id_of( $img ) { return preg_match( '/\bwp-image-(\d+)\b/', $img, $match ) ? (int) $match[1] : 0; } /** Visible text: line breaks kept between blocks of HTML, tags, shortcodes and entities gone. */ private static function html_to_text( $html ) { $html = strip_shortcodes( $html ); $html = preg_replace( '#<(script|style)\b[^>]*>.*?#is', '', $html ); $html = preg_replace( '#|#i', "\n", $html ); $text = html_entity_decode( wp_strip_all_tags( $html, false ), ENT_QUOTES, 'UTF-8' ); $text = preg_replace( "/[ \t\x{00A0}]+/u", ' ', $text ); $text = preg_replace( "/ *\n */", "\n", $text ); $text = preg_replace( "/\n{2,}/", "\n", $text ); return trim( $text ); } private static function clean( $text ) { return trim( preg_replace( '/\s+/u', ' ', html_entity_decode( wp_strip_all_tags( $text ), ENT_QUOTES, 'UTF-8' ) ) ); } }