'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[^>]*>.*?\1>#is', '', $html );
$html = preg_replace( '#
|(p|li|h[1-6]|blockquote|cite|summary|div|pre)>#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' ) ) );
}
}