<?php
/**
 * Informer block theme functions.
 *
 * @package Informer
 * @since 1.0.0
 */

// Prevent direct access.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

if ( ! defined( 'INFORMER_VERSION' ) ) {
	define( 'INFORMER_VERSION', '1.0.0' );
}

/**
 * Theme setup.
 */
function informer_theme_setup() {
	load_theme_textdomain( 'informer', get_template_directory() . '/languages' );

	add_theme_support( 'automatic-feed-links' );
	add_theme_support( 'title-tag' );
	add_theme_support( 'post-thumbnails' );
	add_theme_support( 'html5', array( 'search-form', 'comment-form', 'comment-list', 'gallery', 'caption', 'style', 'script' ) );
	add_theme_support( 'customize-selective-refresh-widgets' );
	add_theme_support( 'responsive-embeds' );
	add_theme_support( 'wp-block-styles' );
	add_theme_support( 'align-wide' );
	add_theme_support( 'editor-styles' );
	add_theme_support(
		'custom-logo',
		array(
			'height'      => 100,
			'width'       => 250,
			'flex-height' => true,
			'flex-width'  => true,
		)
	);

	add_editor_style( 'style.css' );

	// The classic Informer Magazine image sizes, registered under their
	// original names and computed with the original formulas so the crops
	// already present in the cloned uploads resolve exactly.
	$ratio = (int) get_option( 'informer_magazine_site_width', '1260' );

	$width25thumb = round( ( ( ( $ratio - 20 ) / 4 ) - 20 ) / 4 );
	$width25      = round( ( ( $ratio - 20 ) * 0.25 ) - 20 );
	$width50      = round( ( ( $ratio - 20 ) * 0.5 ) - 20 );
	$width75      = round( ( ( $ratio - 20 ) * 0.75 ) - 20 );
	$width100     = round( $ratio - 20 );

	$block  = round( ( ( $width25 / 0.877 ) / 4 ) - 20 + ( 20 / 4 ) );
	$block2 = $block * 2 + 20;
	$block4 = $block * 4 + 20 * 3;
	$block6 = $block * 6 + 20 * 5;

	add_image_size( 'informermagazinefullwidth', 1920, 540, true );
	add_image_size( 'informermagazinesuperslider', 635, 480, true );
	add_image_size( 'informermagazinesmallthumb', $width25thumb, $block, true );
	add_image_size( 'informermagazinehugeimagefeatured', $width50, $block4, true );
	add_image_size( 'informermagazinemediumimagefeatured', $width25, $block4, true );
	add_image_size( 'informermagazinesmallimagefeatured', $width25, $block2, true );
	add_image_size( 'informermagazineslider-three', $width75, $block6, true );
	add_image_size( 'informermagazineslider-four', $width100, $block6, true );
}
add_action( 'after_setup_theme', 'informer_theme_setup' );

/**
 * The Google Fonts URL the classic theme built in informer_magazine_fonts_url().
 *
 * @return string
 */
function informer_fonts_url() {
	$fonts = array( 'Montserrat:300,400,600,700,800', 'Asap:300,400,600,700,800', 'Open Sans' );

	return add_query_arg(
		array(
			'family' => rawurlencode( implode( '|', $fonts ) ),
			'subset' => '',
		),
		'https://fonts.googleapis.com/css'
	);
}

/**
 * Front end assets - the classic Informer Magazine stylesheet chain, ported
 * into this theme (css/*.css are byte-identical copies apart from url()
 * rewrites; live-style.css is the classic customizer output baked from this
 * site's options), followed by the theme's own bridge stylesheet.
 */
function informer_scripts() {
	$dir = get_template_directory();

	wp_enqueue_style( 'informer-fonts', informer_fonts_url(), array(), null );
	wp_enqueue_style( 'informer-style', get_stylesheet_uri(), array(), filemtime( $dir . '/style.css' ) );

	if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
		wp_enqueue_script( 'comment-reply' );
	}
}
add_action( 'wp_enqueue_scripts', 'informer_scripts' );

/**
 * Load the theme stylesheet inside the block editor too.
 */
function informer_editor_assets() {
	wp_enqueue_style( 'informer-editor-style', get_template_directory_uri() . '/style.css', array(), INFORMER_VERSION );
}
add_action( 'enqueue_block_editor_assets', 'informer_editor_assets' );

/**
 * Widen the mobile / tablet previews in the site editor, matching the other
 * StepFox block themes.
 */
function informer_editor_preview_sizes() {
	wp_register_style( 'informer-style-preview', false, array(), INFORMER_VERSION );
	wp_enqueue_style( 'informer-style-preview' );
	wp_add_inline_style(
		'informer-style-preview',
		'.is-mobile-preview, iframe.edit-site-visual-editor__editor-canvas[style*="360px"]{width:480px !important;}
		 .is-tablet-preview, iframe.edit-site-visual-editor__editor-canvas[style*="780px"]{width:860px !important;}'
	);
}
add_action( 'admin_enqueue_scripts', 'informer_editor_preview_sizes' );

/* -------------------------------------------------------------------------
 * Classic-markup parity.
 *
 * The templates contain no shortcodes: every position below is a real, editable
 * core block carrying a marker className. Where a core block cannot express the
 * classic Informer Magazine markup - first-category-only links, wp_trim_words()
 * titles, the informer_magazine_excerpt() "..." trim, the prev/next navigation
 * with the neighbour's author and date, the flat next_posts_link() pagination,
 * the empty-anchor social rows, the classic search form - informer_block_parity()
 * substitutes the classic markup on the front end. The Site Editor keeps showing
 * an ordinary block with a live preview.
 * ------------------------------------------------------------------------- */

/**
 * "Monday - August 3, 2026" - the date line in the classic theme's top bar.
 *
 * @return string
 */
function informer_today_markup() {
	// &#045; keeps wptexturize() from turning " - " into an en dash.
	return '<div id="top-date">' . esc_html( date( 'l' ) ) . ' &#045; ' . esc_html( date( get_option( 'date_format' ) ) ) . '</div>';
}

/**
 * Post title trimmed to N words, linked. The mega menu and the thumbnails
 * widget print wp_trim_words( get_the_title(), N ); core/post-title cannot trim.
 *
 * @param int $words Word limit.
 * @return string
 */
function informer_trim_title_markup( $words = 10 ) {
	return '<a href="' . esc_url( get_permalink() ) . '">' . esc_html( wp_trim_words( get_the_title(), (int) $words ) ) . '</a>';
}

/**
 * First category only, linked. The classic widgets print get_the_category()[0];
 * core/post-terms always lists every term.
 *
 * @return string
 */
function informer_first_category_markup() {
	$categories = get_the_category();

	if ( empty( $categories ) ) {
		return '';
	}

	$category = $categories[0];

	return '<a href="' . esc_url( get_category_link( $category->term_id ) ) . '" title="' . esc_attr( $category->name ) . '">' . esc_html( $category->name ) . '</a>';
}

/**
 * The hero slider caption: the classic super slider prints the post's subtitle
 * meta if it has one, otherwise the raw excerpt field trimmed to N words,
 * otherwise the content trimmed to N words.
 *
 * @param int $words Word limit.
 * @return string
 */
function informer_slider_text_markup( $words = 30 ) {
	$id       = get_the_ID();
	$subtitle = get_post_meta( $id, 'informer_magazine_sub_title', true );

	if ( ! empty( $subtitle ) ) {
		return esc_html( $subtitle );
	}

	$excerpt = wp_trim_words( get_post_field( 'post_excerpt', $id ), (int) $words );

	if ( ! empty( $excerpt ) ) {
		return esc_html( $excerpt );
	}

	return esc_html( wp_trim_words( get_post_field( 'post_content', $id ), (int) $words ) );
}

/**
 * The classic theme's excerpt_length filter value.
 *
 * @return int
 */
function informer_excerpt_length_120() {
	return 120;
}

/**
 * The classic archive / widget excerpt. excerpt_length is filtered to 120 words,
 * then informer_magazine_excerpt() re-splits it to $limit and appends "..." with
 * no separating space. core/post-excerpt gives 55 words and " [...]".
 *
 * @param int $words Word limit.
 * @return string
 */
function informer_excerpt_markup( $words = 50 ) {
	$limit = (int) $words;

	add_filter( 'excerpt_length', 'informer_excerpt_length_120', 999 );
	$source = get_the_excerpt();
	remove_filter( 'excerpt_length', 'informer_excerpt_length_120', 999 );

	$excerpt = explode( ' ', $source, $limit );

	if ( count( $excerpt ) >= $limit ) {
		array_pop( $excerpt );
		$excerpt = empty( $excerpt ) ? '' : implode( ' ', $excerpt ) . '...';
	} elseif ( false === strpos( $source, 'more-link' ) ) {
		$excerpt = implode( ' ', $excerpt ) . '...';
	} else {
		$excerpt = implode( ' ', $excerpt );
	}

	$excerpt = preg_replace( '`\[[^\]]*\]`', '', $excerpt );

	// Numeric entities keep wptexturize() from turning "..." into a real ellipsis.
	return str_replace( '...', '&#046;&#046;&#046;', $excerpt );
}

/**
 * Search results heading: the classic prints the "Search" label option followed
 * by the query in straight double quotes.
 *
 * @return string
 */
function informer_search_title_markup() {
	$label = get_option( 'informer_magazine_search_translate', 'Search' );

	// &#034; so wptexturize() does not curl the quotes. The classic search.php
	// prints a bare <h1> inside the .widget-title wrapper the template carries.
	return '<h1>' . esc_html( $label ) . ' &#034;' . esc_html( get_search_query() ) . '&#034;</h1>';
}

/**
 * Date archive heading. archive.php prints single_month_title( '' ), whose
 * empty prefix concatenates month and year with no separator ("August2016")
 * and which prints nothing at all on a year-only archive. get_the_archive_title()
 * gives "August 2016" and "2016". Tag and author archives already match, so only
 * the date case is rewritten - the block's own wrapper and classes are kept.
 *
 * @param string $content Rendered core/query-title HTML.
 * @return string
 */
function informer_archive_title_markup( $content ) {
	if ( is_date() ) {
		ob_start();
		single_month_title( '' );
		$title = ob_get_clean();
	} else {
		$title = get_the_archive_title();
	}

	// The classic archive.php prints a bare <h1> inside .widget-title.
	return '<h1>' . $title . '</h1>';
}

/**
 * The classic "Load More" link. next_posts_link() prints nothing at all when
 * there is no next page; core/query-pagination always renders its container and
 * says "Next Page".
 *
 * @return string
 */
function informer_load_more_markup() {
	$label = get_option( 'informer_magazine_word_load_more', 'Load More' );

	ob_start();
	next_posts_link( esc_html( $label ), '' );

	// The classic archive.php always prints the wrapper, even with no link.
	return '<div class="pagination pagination-load-more">' . ob_get_clean() . '</div>';
}

/**
 * The comment count printed in the classic single post meta line.
 *
 * @return string
 */
function informer_comment_count_markup() {
	return '<span class="post-page-comments">' . (int) get_comments_number( get_the_ID() ) . '</span>';
}

/**
 * Previous / next post navigation.
 *
 * core/post-navigation-link renders a fixed "Previous/Next article" label and
 * always renders both sides. The classic markup omits a side entirely when
 * there is no neighbour and prints that neighbour's author and date too.
 *
 * @return string
 */
function informer_post_nav_markup() {
	$previous = get_previous_post();
	$next     = get_next_post();
	$by       = get_option( 'informer_magazine_word_before_author', 'by' );

	$side = static function ( $post_object, $label, $class ) use ( $by ) {
		$author_id  = $post_object->post_author;
		$author_url = get_author_posts_url( $author_id );

		$out  = '<div class="nav-' . esc_attr( $class ) . '">';
		$out .= '<div class="' . esc_attr( $class ) . '-article"><a href="' . esc_url( get_permalink( $post_object ) ) . '">' . esc_html( $label ) . '</a></div>';
		$out .= '<div class="' . esc_attr( $class ) . '-title"><h2><a href="' . esc_url( get_permalink( $post_object ) ) . '">' . esc_html( get_the_title( $post_object ) ) . '</a></h2>';
		$out .= '<div class="post-date"><span class="bypostauthor">' . esc_html( $by ) . ' <a href="' . esc_url( $author_url ) . '">' . esc_html( get_the_author_meta( 'display_name', $author_id ) ) . '</a> &#045;</span> ';
		$out .= '<span class="date-' . esc_attr( 'previous' === $class ? 'prev' : 'next' ) . '">' . esc_html( mysql2date( 'M j, Y', $post_object->post_date ) ) . '</span>';
		$out .= '</div></div></div>';

		return $out;
	};

	$html = '<div class="nav-next-prev">';

	if ( ! empty( $previous ) ) {
		$html .= $side( $previous, get_option( 'informer_magazine_older_article', 'Older Article' ), 'previous' );
	}

	$html .= '<div class="splitter"></div>';

	if ( ! empty( $next ) ) {
		$html .= $side( $next, get_option( 'informer_magazine_next_article', 'Next Article' ), 'next' );
	}

	return $html . '</div>';
}

/**
 * Read an integer suffix off a marker class, e.g. "informer-excerpt-14" -> 14.
 *
 * @param string $class   Full className attribute.
 * @param string $marker  Marker prefix.
 * @param int    $default Fallback.
 * @return int
 */
function informer_marker_number( $class, $marker, $default ) {
	if ( preg_match( '/\b' . preg_quote( $marker, '/' ) . '-(\d+)\b/', $class, $m ) ) {
		return (int) $m[1];
	}

	return $default;
}

/**
 * Swap selected core blocks' output for the exact classic Informer markup.
 *
 * Each substitution is keyed on an explicit className set in the template, so
 * ordinary uses of these blocks anywhere else are untouched.
 *
 * @param string $content Rendered block HTML.
 * @param array  $block   Parsed block.
 * @return string
 */
function informer_block_parity( $content, $block ) {
	$name = isset( $block['blockName'] ) ? $block['blockName'] : '';

	if ( '' === $name || 0 !== strpos( $name, 'core/' ) ) {
		return $content;
	}

	$class = isset( $block['attrs']['className'] ) ? $block['attrs']['className'] : '';

	if ( '' === $class ) {
		return $content;
	}

	if ( 'core/post-terms' === $name && false !== strpos( $class, 'informer-first-category' ) ) {
		return informer_first_category_markup();
	}

	if ( 'core/post-title' === $name && false !== strpos( $class, 'informer-trim-title' ) ) {
		return informer_trim_title_markup( informer_marker_number( $class, 'informer-trim-title', 10 ) );
	}

	if ( 'core/post-excerpt' === $name ) {
		if ( false !== strpos( $class, 'informer-slider-text' ) ) {
			return informer_slider_text_markup( informer_marker_number( $class, 'informer-slider-text', 30 ) );
		}
		if ( false !== strpos( $class, 'informer-excerpt' ) ) {
			return informer_excerpt_markup( informer_marker_number( $class, 'informer-excerpt', 50 ) );
		}
		return $content;
	}

	if ( 'core/query-title' === $name ) {
		if ( false !== strpos( $class, 'informer-search-title' ) ) {
			return informer_search_title_markup();
		}
		if ( false !== strpos( $class, 'informer-archive-title' ) ) {
			return informer_archive_title_markup( $content );
		}
		return $content;
	}

	if ( 'core/query-pagination' === $name && false !== strpos( $class, 'informer-load-more' ) ) {
		return informer_load_more_markup();
	}

	if ( 'core/post-comments-count' === $name && false !== strpos( $class, 'informer-comment-count' ) ) {
		return informer_comment_count_markup();
	}

	if ( 'core/post-navigation-link' === $name && false !== strpos( $class, 'informer-post-nav' ) ) {
		return informer_post_nav_markup();
	}

	if ( 'core/paragraph' === $name ) {
		if ( false !== strpos( $class, 'informer-today' ) ) {
			return informer_today_markup();
		}
		if ( false !== strpos( $class, 'informer-copyright' ) ) {
			return '<div class="copyright-text">' . esc_html( get_option( 'informer_magazine_copyright' ) ) . '</div>';
		}
		if ( false !== strpos( $class, 'informer-no-match' ) ) {
			return '<div class="widget-title">' . esc_html( get_option( 'informer_magazine_no_match', 'No posts matched your criteria.' ) ) . '</div>';
		}
		if ( false !== strpos( $class, 'informer-chrome' ) ) {
			return informer_chrome_markup( $class );
		}
		return $content;
	}

	if ( 'core/post-title' === $name && false !== strpos( $class, 'informer-ticker-item' ) ) {
		return '<a href="' . esc_url( get_permalink() ) . '"><span class="ticker-sign" title="' . esc_attr( get_the_title() ) . '">+</span>' . get_the_title() . '</a>';
	}

	if ( 'core/post-title' === $name && false !== strpos( $class, 'informer-bare-title' ) ) {
		return '<a href="' . esc_url( get_permalink() ) . '" title="' . esc_attr( get_the_title() ) . '">' . get_the_title() . '</a>';
	}

	if ( 'core/post-date' === $name ) {
		if ( false !== strpos( $class, 'informer-date-span-full' ) ) {
			return '<span class="post-page-date">' . esc_html( get_the_date() ) . ' ' . esc_html( get_the_time() ) . '</span>';
		}
		if ( false !== strpos( $class, 'informer-date-span' ) ) {
			return '<span class="post-date">' . esc_html( get_the_date() ) . '</span>';
		}
		if ( false !== strpos( $class, 'informer-date-archive' ) ) {
			return '<div class="post-page-date">' . esc_html( get_the_date() ) . ' ' . esc_html( get_the_time() ) . '</div>';
		}
		if ( false !== strpos( $class, 'informer-date-div' ) ) {
			return '<div class="date">' . esc_html( get_the_date() ) . '</div>';
		}
		if ( false !== strpos( $class, 'informer-blog-date' ) ) {
			return '<div class="blog-post-date">' . esc_html( get_the_date() ) . '</div>';
		}
		return $content;
	}

	if ( 'core/post-author-name' === $name ) {
		$author_link = '<a href="' . esc_url( get_author_posts_url( get_the_author_meta( 'ID' ) ) ) . '" title="' . esc_attr( get_the_author() ) . '" rel="author">' . esc_html( get_the_author() ) . '</a>';

		if ( false !== strpos( $class, 'informer-author-by' ) ) {
			$by = get_option( 'informer_magazine_word_before_author', 'by' );
			return esc_html( $by ) . ' ' . $author_link;
		}
		if ( false !== strpos( $class, 'informer-author-span' ) ) {
			$by = get_option( 'informer_magazine_word_before_author', 'by' );
			return '<span class="post-author">' . esc_html( $by ) . ' ' . $author_link . '</span>';
		}
		if ( false !== strpos( $class, 'informer-author-div' ) ) {
			return '<div class="author">' . $author_link . '</div>';
		}
		if ( false !== strpos( $class, 'informer-blog-author' ) ) {
			return '<div class="blog-post-author">' . $author_link . '</div>';
		}
		return $content;
	}

	if ( 'core/read-more' === $name && false !== strpos( $class, 'informer-read-more-span' ) ) {
		return '<span class="read-more"><a href="' . esc_url( get_permalink() ) . '">' . esc_html( get_option( 'informer_magazine_read_more_translate', 'Read More' ) ) . '</a></span>';
	}

	return $content;
}
/*
 * Priority 5, not 10: stepfox-looks stamps id="block_<customId>" from its own
 * render_block filter at priority 10, and being a plugin it is registered
 * first. Any block whose markup this filter REPLACES (post-terms, the bare and
 * trimmed titles, the "by author" line) would lose that id and render with no
 * responsiveStyles at all - which is every card on the front page.
 */
add_filter( 'render_block', 'informer_block_parity', 5, 2 );

/**
 * core/read-more appends a visually hidden ": <post title>" span that repeats
 * the heading immediately above it; the classic <span class="read-more"> link
 * has no such duplicate. Move the text onto aria-label so the link keeps its
 * accessible name without the repeated markup.
 *
 * @param string $content Rendered block content.
 * @return string
 */
function informer_read_more_block( $content ) {
	$content = preg_replace(
		'#(<a\b[^>]*?)(>)(.*?)<span class="screen-reader-text">(.*?)</span>#s',
		'$1 aria-label="$3$4"$2$3',
		$content
	);

	return preg_replace( '#<span class="screen-reader-text">.*?</span>#s', '', $content );
}
add_filter( 'render_block_core/read-more', 'informer_read_more_block' );

/**
 * The classic comment form has no privacy note, no "required fields" note and
 * a bare comment textarea; core adds all three.
 *
 * @param array $defaults Comment form arguments.
 * @return array
 */
function informer_comment_form_defaults( $defaults ) {
	$defaults['comment_notes_before'] = '';
	$defaults['comment_notes_after']  = '';
	$defaults['comment_field']        = '<p class="comment-form-comment"><textarea id="comment" name="comment" cols="45" rows="8" aria-required="true"></textarea></p>';
	$defaults['title_reply']          = get_option( 'informer_magazine_comments_post_reply', 'Leave a Reply' );
	$defaults['title_reply_to']       = get_option( 'informer_magazine_comments_post_reply_to', 'Leave a Reply to' ) . ' %s';
	$defaults['cancel_reply_link']    = get_option( 'informer_magazine_comments_cancel_reply', 'Cancel reply' );

	return $defaults;
}
add_filter( 'comment_form_defaults', 'informer_comment_form_defaults' );

/**
 * Archive titles: the classic prints single_cat_title() with no "Category:"
 * prefix, so drop the prefix core/query-title adds.
 *
 * @param string $prefix Archive title prefix.
 * @return string
 */
function informer_archive_title_prefix( $prefix ) {
	return '';
}
add_filter( 'get_the_archive_title_prefix', 'informer_archive_title_prefix' );

/**
 * Archive page size.
 *
 * informer_magazine_archive_page_queries() overrides posts_per_page with the
 * informer_magazine_category_number option (9) while the site's posts_per_page
 * option is 10 - but its condition is `is_archive() && ! is_tax()`, and
 * is_archive() is FALSE on a search results page. Search therefore keeps the
 * site's own posts_per_page of 10. Including is_search() here silently dropped
 * the tenth result from every search that matched more than nine posts.
 *
 * @param WP_Query $query Query being prepared.
 */
function informer_archive_page_size( $query ) {
	if ( is_admin() || ! $query->is_main_query() ) {
		return;
	}

	$number = (int) get_option( 'informer_magazine_category_number' );

	if ( $number && $query->is_archive() && ! $query->is_tax() ) {
		$query->set( 'posts_per_page', $number );
	}
}
add_action( 'pre_get_posts', 'informer_archive_page_size' );

/**
 * Query loop variants core cannot express through block attributes.
 *
 * - informer/trending : the "Popular posts" widget orders every post by the
 *                       informer_magazine_post_views_count meta value.
 * - informer/related  : the single post's related block reuses the post's own
 *                       categories and excludes the post itself.
 * - informer/popular  : the archive's "Popular Posts This Week" widget orders
 *                       the queried category by the same view-count meta.
 *
 * @param array    $query Query vars.
 * @param WP_Block $block Block instance.
 * @return array
 */
function informer_query_loop_vars( $query, $block ) {
	$namespace = isset( $block->context['query']['namespace'] ) ? $block->context['query']['namespace'] : '';

	if ( 'informer/trending' === $namespace ) {
		$query['meta_key'] = 'informer_magazine_post_views_count';
		$query['orderby']  = 'meta_value_num';
		$query['order']    = 'DESC';

		return $query;
	}

	if ( 'informer/related' === $namespace ) {
		$categories = get_the_category( get_the_ID() );

		if ( ! empty( $categories ) ) {
			$query['category__in'] = wp_list_pluck( $categories, 'term_id' );
		}

		$query['post__not_in']        = array( get_the_ID() );
		$query['ignore_sticky_posts'] = 1;

		return $query;
	}

	if ( 'informer/popular' === $namespace ) {
		$query['meta_key'] = 'informer_magazine_post_views_count';
		$query['orderby']  = 'meta_value_num';
		$query['order']    = 'DESC';

		$category = get_queried_object();

		if ( $category instanceof WP_Term ) {
			$query['cat'] = $category->term_id;
		}
	}

	return $query;
}
add_filter( 'query_loop_block_query_vars', 'informer_query_loop_vars', 10, 2 );

/**
 * Register the pattern category used by the bundled patterns in /patterns.
 * The patterns themselves are picked up automatically by WordPress from their
 * file headers.
 */
function informer_register_pattern_category() {
	register_block_pattern_category(
		'informer',
		array(
			'label'       => __( 'Informer', 'informer' ),
			'description' => __( 'Sections from the Informer news magazine layout.', 'informer' ),
		)
	);
}
add_action( 'init', 'informer_register_pattern_category' );

/**
 * The classic Informer search form renders no visible or assistive-only text:
 * the field carries its own placeholder and the submit button is a CSS sprite.
 * Core's Search block always emits a `screen-reader-text` <label>, which shows
 * up as stray "Search" copy in text extractions of the page. Keep the real
 * core/search block (the theme styles .wp-block-search__input/__button) and
 * fold the label into an aria-label on the input so nothing is lost for AT.
 */
function informer_search_block_label( $content ) {
	if ( false === strpos( $content, 'wp-block-search__label' ) ) {
		return $content;
	}

	$label = '';
	$content = preg_replace_callback(
		'#<label\b[^>]*class="[^"]*wp-block-search__label[^"]*"[^>]*>(.*?)</label>#s',
		static function ( $matches ) use ( &$label ) {
			$label = trim( wp_strip_all_tags( $matches[1] ) );
			return '';
		},
		$content
	);

	if ( '' !== $label && false === strpos( $content, 'aria-label' ) ) {
		$content = preg_replace(
			'#(<input\b[^>]*class="[^"]*wp-block-search__input)#',
			'$1',
			$content,
			1
		);
		$content = preg_replace(
			'#(<input\b(?=[^>]*wp-block-search__input))#',
			'$1 aria-label="' . esc_attr( $label ) . '"',
			$content,
			1
		);
	}

	return $content;
}
add_filter( 'render_block_core/search', 'informer_search_block_label' );

/**
 * Same story for the "SHARE THIS ARTICLE" row on single posts: the classic
 * theme's share links are empty anchors styled from a sprite, with the service
 * name carried in the title attribute only. Core's Social Link block appends a
 * `screen-reader-text` <span> with the service name. Keep the real
 * core/social-link block and move that name onto aria-label/title instead.
 */
function informer_social_link_block_label( $content ) {
	if ( false === strpos( $content, 'wp-block-social-link-label' ) ) {
		return $content;
	}

	return preg_replace_callback(
		'#<a\b([^>]*)>(.*?)<span class="wp-block-social-link-label screen-reader-text">(.*?)</span>(.*?)</a>#s',
		static function ( $matches ) {
			$attrs = $matches[1];
			$name  = trim( wp_strip_all_tags( $matches[3] ) );

			if ( '' !== $name && false === strpos( $attrs, 'aria-label' ) ) {
				$attrs .= ' aria-label="' . esc_attr( $name ) . '"';
			}
			if ( '' !== $name && false === strpos( $attrs, 'title=' ) ) {
				$attrs .= ' title="' . esc_attr( $name ) . '"';
			}

			return '<a' . $attrs . '>' . $matches[2] . $matches[4] . '</a>';
		},
		$content
	);
}
add_filter( 'render_block_core/social-link', 'informer_social_link_block_label' );

/* -------------------------------------------------------------------------
 * Classic DOM reproduction.
 *
 * The templates carry the classic Informer Magazine ids (via block anchors)
 * and classes (via className). Two small pieces of machinery close the gaps
 * core blocks cannot express:
 *
 * 1. informer_tag_swap() - a group/paragraph marked `informer-as-<tag>`
 *    renders as that tag (<ul>/<li>/<div>/<span>/<nav>), so the classic
 *    ul/li menu structures exist as ordinary editable blocks.
 * 2. informer_chrome_markup() - header/footer chrome the classic theme
 *    printed from options (logos, share icon rows, the search form). Each
 *    lives behind a paragraph with a `informer-chrome-*` marker class.
 * ------------------------------------------------------------------------- */

/**
 * Render a block wrapper as a different HTML tag.
 *
 * @param string $content Rendered block HTML.
 * @param array  $block   Parsed block.
 * @return string
 */
function informer_tag_swap( $content, $block ) {
	$name = isset( $block['blockName'] ) ? $block['blockName'] : '';

	if ( 'core/group' !== $name && 'core/paragraph' !== $name ) {
		return $content;
	}

	$class = isset( $block['attrs']['className'] ) ? $block['attrs']['className'] : '';

	if ( '' === $class || false === strpos( $class, 'informer-as-' ) || ! preg_match( '/\binformer-as-(ul|li|div|span|nav)\b/', $class, $m ) ) {
		return $content;
	}

	$tag = $m[1];

	$content = preg_replace( '#<(div|p)\b#', '<' . $tag, $content, 1 );

	$pos = strrpos( $content, '</' );
	if ( false !== $pos ) {
		$content = substr( $content, 0, $pos ) . '</' . $tag . '>';
	}

	// Drop the marker class from the front end output.
	return str_replace( array( ' informer-as-' . $tag, 'informer-as-' . $tag . ' ', 'informer-as-' . $tag ), '', $content );
}
add_filter( 'render_block', 'informer_tag_swap', 20, 2 );

/**
 * Render a core/post-template as the classic <div><div> stack instead of
 * <ul><li> (the trending widget's featured post). Marker: informer-divlist.
 *
 * @param string $content Rendered block HTML.
 * @param array  $block   Parsed block.
 * @return string
 */
function informer_post_template_divlist( $content, $block ) {
	if ( ! isset( $block['blockName'] ) || 'core/post-template' !== $block['blockName'] ) {
		return $content;
	}

	$class = isset( $block['attrs']['className'] ) ? $block['attrs']['className'] : '';

	if ( false === strpos( $class, 'informer-divlist' ) ) {
		return $content;
	}

	// Drop the list container entirely - the classic trending widget prints
	// its featured post directly inside .trending-posts.
	$content = preg_replace( '#<ul\b[^>]*>#', '', $content, 1 );
	$pos     = strrpos( $content, '</ul>' );
	if ( false !== $pos ) {
		$content = substr_replace( $content, '', $pos, 5 );
	}
	$content = preg_replace( '#<li\b#', '<div', $content );
	$content = str_replace( '</li>', '</div>', $content );
	$content = str_replace( 'wp-block-post ', '', $content );
	$content = preg_replace( '#informer-divlist ?#', '', $content, 1 );

	return $content;
}
add_filter( 'render_block', 'informer_post_template_divlist', 20, 2 );

/**
 * The classic theme's post_class filter REPLACES the class list: every post
 * li/div on the demo carries exactly one class, category-color-<term id>.
 *
 * @return array
 */
function informer_post_classes() {
	$categories = get_the_category();

	if ( empty( $categories ) ) {
		return array();
	}

	return array( esc_attr( 'category-color-' . strtolower( $categories[0]->term_id ) ) );
}
if ( ! is_admin() ) {
	add_filter( 'post_class', 'informer_post_classes', 10, 3 );
}

/**
 * Header / footer chrome printed from theme options, exactly as the classic
 * header.php / footer.php / share-functions.php do.
 *
 * @param string $class The full className attribute carrying the marker.
 * @return string
 */
function informer_chrome_markup( $class ) {
	$menu_logo = get_option( 'informer_magazine_menu_logo' );
	$home      = esc_url( home_url( '/' ) );
	$name      = esc_attr( get_bloginfo( 'name' ) );

	if ( false !== strpos( $class, 'informer-chrome-nav-logo' ) ) {
		if ( empty( $menu_logo ) ) {
			return '';
		}
		return '<div class="nav-logo"><a href="' . $home . '"><img src="' . esc_attr( $menu_logo ) . '" alt="' . $name . '"/></a></div>';
	}

	if ( false !== strpos( $class, 'informer-chrome-mob-menu' ) ) {
		return '<div id="mob-menu"><div class="mob-menu-button"></div><a href="' . $home . '"><img src="' . esc_attr( $menu_logo ) . '" alt="' . $name . '"/></a><div class="search-box"></div></div>';
	}

	if ( false !== strpos( $class, 'informer-chrome-menu-social' ) ) {
		$out = '<span class="close-search-box"></span><a href="#" class="search-menu-icon"></a>';
		return $out . informer_social_list_markup( false );
	}

	if ( false !== strpos( $class, 'informer-chrome-search-form' ) ) {
		$label = get_option( 'informer_magazine_search_translate' );
		$out   = '<form method="get" id="searchform" action="' . $home . '">';
		$out  .= '<input type="text" name="s" id="s" value="' . esc_attr( $label ) . '" onfocus="if(this.value !== \'\') {this.value=\'\'}" onblur="if(this.value == \'\'){this.value=\'\'}" autocomplete="off" placeholder="' . esc_attr( $label ) . '" />';
		$out  .= '<button type="submit" class="submit-button"><span class="search-menu-icon"></span></button>';
		$out  .= '<ul class="featured-thumbnails"></ul></form>';
		return $out;
	}

	if ( false !== strpos( $class, 'informer-chrome-header-ad' ) ) {
		$advert = get_option( 'informer_magazine_header_advert' );
		if ( empty( $advert ) ) {
			return '';
		}
		return '<div class="header-ad-space">' . $advert . '</div>';
	}

	if ( false !== strpos( $class, 'informer-chrome-post-ad' ) ) {
		$advert = get_option( 'informer_magazine_postend_advert' );
		if ( empty( $advert ) ) {
			return '';
		}
		return '<div class="post-ad-space">' . $advert . '</div>';
	}

	if ( false !== strpos( $class, 'informer-chrome-footer-logo' ) ) {
		return '<div class="footer-logo"><a href="' . $home . '"><img src="' . esc_attr( $menu_logo ) . '" alt="' . $name . '"/></a></div>';
	}

	if ( false !== strpos( $class, 'informer-chrome-footer-social' ) ) {
		return informer_social_list_markup( true );
	}

	if ( false !== strpos( $class, 'informer-chrome-copyright-image' ) ) {
		$image = get_option( 'informer_magazine_footer_default' );
		if ( empty( $image ) ) {
			return '';
		}
		$link = get_option( 'informer_magazine_footer_link' );
		$out  = '<div class="copyright-image">';
		if ( $link ) {
			$out .= '<a href="' . esc_url( $link ) . '" target="_blank">';
		}
		$out .= '<img src="' . esc_url( $image ) . '" alt="' . $name . '"/>';
		if ( $link ) {
			$out .= '</a>';
		}
		return $out . '</div>';
	}

	if ( false !== strpos( $class, 'informer-chrome-floating-share' ) ) {
		return informer_floating_share_markup();
	}

	if ( false !== strpos( $class, 'informer-chrome-share-post' ) ) {
		return informer_share_post_markup();
	}

	if ( false !== strpos( $class, 'informer-chrome-author-box' ) ) {
		return informer_author_box_markup();
	}

	return '';
}

/**
 * The social icon <ul> used by the main nav and the footer, mirroring
 * informer_magazine_menu_share_icons() / the footer.php block.
 *
 * @param bool $footer Include the footer's title line.
 * @return string
 */
function informer_social_list_markup( $footer ) {
	$services = array(
		'facebook'  => 'fb-social-icon',
		'twitter'   => 'twitter-social-icon',
		'pinterest' => 'pinterest-social-icon',
		'google'    => 'google-social-icon',
		'youtube'   => 'youtube-social-icon',
		'instagram' => 'instagram-social-icon',
	);

	$any = false;
	foreach ( array_keys( $services ) as $service ) {
		if ( get_option( 'informer_magazine_' . $service ) ) {
			$any = true;
			break;
		}
	}

	if ( ! $any ) {
		return '';
	}

	$out = '<div class="content-social">';

	if ( $footer ) {
		$out .= '<div class="social-title-footer">' . esc_html( get_option( 'informer_magazine_social_footer_translate' ) ) . '</div>';
	}

	$out .= '<ul>';

	foreach ( $services as $service => $icon_class ) {
		$url = get_option( 'informer_magazine_' . $service );
		if ( ! $url ) {
			continue;
		}
		if ( 'google' === $service ) {
			$url .= '/posts';
		}
		$out .= '<li><a href="' . esc_url( $url ) . '" class="' . $icon_class . '" target="_blank"></a></li>';
	}

	$out .= '<li><a href="' . esc_url( get_bloginfo( 'rss_url' ) ) . '" class="rss-social-icon"></a></li>';

	return $out . '</ul></div>';
}

/**
 * Share URL set used by both share rows, mirroring share-functions.php.
 *
 * @return array
 */
function informer_share_targets() {
	$permalink = get_permalink();
	$title     = get_the_title();
	$thumb     = wp_get_attachment_url( get_post_thumbnail_id( get_the_ID() ) );

	return array(
		'fb'        => 'http://www.facebook.com/sharer.php?u=' . $permalink,
		'twitter'   => 'https://twitter.com/intent/tweet?original_referer=' . $permalink . '&text=' . $title . '&tw_p=tweetbutton&url=' . $permalink,
		'google'    => 'https://plusone.google.com/_/+1/confirm?hl=en-US&url=' . $permalink,
		'pinterest' => 'http://pinterest.com/pin/create/button/?media=' . $thumb . '&url=' . $permalink . '&description=' . $title,
		'reddit'    => 'http://www.reddit.com/submit?url=' . $permalink . '&title=' . $title,
	);
}

/**
 * The single post's floating share column.
 *
 * @return string
 */
function informer_floating_share_markup() {
	$targets = informer_share_targets();

	$out = '<div class="floating-share-icons"><ul>';
	foreach ( $targets as $key => $url ) {
		$out .= '<li><a href="' . esc_url( $url ) . '" class="' . esc_attr( $key ) . '-social-float-icon" target="_blank"></a></li>';
	}

	return $out . '</ul></div>';
}

/**
 * The single post's "SHARE THIS ARTICLE" row.
 *
 * @return string
 */
function informer_share_post_markup() {
	$targets = informer_share_targets();

	$out  = '<div class="share-post">';
	$out .= '<div class="share-title">' . esc_html( get_option( 'informer_magazine_share_translate', 'SHARE THIS ARTICLE' ) ) . '</div><ul>';
	foreach ( $targets as $key => $url ) {
		$out .= '<li><a href="' . esc_url( $url ) . '" class="' . esc_attr( $key ) . '-share-icon" target="_blank"></a></li>';
	}

	return $out . '</ul></div>';
}

/**
 * The single post's author box (#author-info).
 *
 * @return string
 */
function informer_author_box_markup() {
	$author_id = get_the_author_meta( 'ID' );

	$out  = '<div id="author-info">';
	$out .= '<div id="author-image">' . get_avatar( $author_id, 96 ) . '</div>';
	$out .= '<div id="author-desc"><h2><a href="' . esc_url( get_author_posts_url( $author_id ) ) . '">' . esc_html( get_the_author() ) . '</a></h2>';
	$out .= '<div class="description-author">' . esc_html( get_the_author_meta( 'description' ) ) . '</div></div>';

	return $out . '</div>';
}

/**
 * The classic single.php wraps everything in <div id="post-<ID>">; block
 * anchors are static, so a group marked `informer-post-anchor` gets the
 * dynamic id injected here.
 *
 * @param string $content Rendered block HTML.
 * @param array  $block   Parsed block.
 * @return string
 */
function informer_post_anchor( $content, $block ) {
	if ( ! isset( $block['blockName'] ) || 'core/group' !== $block['blockName'] ) {
		return $content;
	}

	$class = isset( $block['attrs']['className'] ) ? $block['attrs']['className'] : '';

	if ( false === strpos( $class, 'informer-post-anchor' ) ) {
		return $content;
	}

	$content = preg_replace( '#<div\b#', '<div id="post-' . (int) get_the_ID() . '"', $content, 1 );

	return str_replace( ' informer-post-anchor', '', $content );
}
add_filter( 'render_block', 'informer_post_anchor', 20, 2 );

/**
 * The classic markup has no equivalents of the core/query and
 * core/template-part wrapper <div>s - strip them so the classic CSS sees the
 * exact DOM it was written for (ul.small-category directly inside
 * .sub-menu, card markup directly inside <li>, <header id="header"> at the
 * top level, ...).
 *
 * @param string $content Rendered block HTML.
 * @param array  $block   Parsed block.
 * @return string
 */
function informer_unwrap_wrappers( $content, $block ) {
	$name = isset( $block['blockName'] ) ? $block['blockName'] : '';

	// core/query only. Template parts are no longer unwrapped: every part in
	// this theme is now zero-CSS, and its root <header> / <footer> / <div>
	// carries the stepfox id that paints it - stripping that root would throw
	// the styling away.
	if ( 'core/query' !== $name ) {
		return $content;
	}

	$content = preg_replace( '#^\s*<(div|header|footer|section|aside|main)\b[^>]*>#', '', $content, 1, $count );

	if ( ! $count ) {
		return $content;
	}

	$pos = strrpos( $content, '</' );
	if ( false !== $pos ) {
		$content = substr( $content, 0, $pos );
	}

	return $content;
}
add_filter( 'render_block', 'informer_unwrap_wrappers', 30, 2 );

/**
 * More classic-DOM alignment:
 * - core/post-content wraps the content in div.entry-content; the classic
 *   #post-content .content holds the paragraphs directly.
 * - core/comments wraps everything in div.wp-block-comments; the classic
 *   comments.php has no such wrapper inside .comments.
 * - core/query-no-results renders div.wp-block-query-no-results; the classic
 *   archive.php prints an (empty) <ul> that carries the no-match note.
 *
 * @param string $content Rendered block HTML.
 * @param array  $block   Parsed block.
 * @return string
 */
function informer_unwrap_content_blocks( $content, $block ) {
	$name = isset( $block['blockName'] ) ? $block['blockName'] : '';

	if ( 'core/post-content' === $name || 'core/comments' === $name ) {
		$class_re = ( 'core/comments' === $name ) ? 'wp-block-comments' : 'wp-block-post-content';
		$content  = preg_replace( '#<div\b[^>]*class="[^"]*' . $class_re . '[^"]*"[^>]*>#', '', $content, 1, $count );
		if ( $count ) {
			$pos = strrpos( $content, '</div>' );
			if ( false !== $pos ) {
				$content = substr( $content, 0, $pos ) . substr( $content, $pos + 6 );
			}
		}
		return $content;
	}

	if ( 'core/query-no-results' === $name ) {
		$content = preg_replace( '#^\s*<div\b[^>]*>#', '<ul>', $content, 1, $count );
		if ( $count ) {
			$pos = strrpos( $content, '</div>' );
			if ( false !== $pos ) {
				$content = substr_replace( $content, '</ul>', $pos, 6 );
			}
		}
		return $content;
	}

	return $content;
}
add_filter( 'render_block', 'informer_unwrap_content_blocks', 30, 2 );

/**
 * Bare term links, exactly like the classic the_tags( '', '', '' ) and the
 * per-category span loop on single.php. Marker: informer-bare-terms on a
 * core/post-terms block.
 *
 * @param string $content Rendered block HTML.
 * @param array  $block   Parsed block.
 * @return string
 */
function informer_bare_terms( $content, $block ) {
	if ( ! isset( $block['blockName'] ) || 'core/post-terms' !== $block['blockName'] ) {
		return $content;
	}

	$class = isset( $block['attrs']['className'] ) ? $block['attrs']['className'] : '';

	if ( false === strpos( $class, 'informer-bare-terms' ) ) {
		return $content;
	}

	$taxonomy = isset( $block['attrs']['term'] ) ? $block['attrs']['term'] : 'category';
	$terms    = get_the_terms( get_the_ID(), $taxonomy );

	if ( empty( $terms ) || is_wp_error( $terms ) ) {
		return '';
	}

	$out = '';
	foreach ( $terms as $term ) {
		$out .= '<a href="' . esc_url( get_term_link( $term ) ) . '" rel="tag">' . esc_html( $term->name ) . '</a>';
	}

	return $out;
}
add_filter( 'render_block', 'informer_bare_terms', 15, 2 );

/**
 * Core's flow-layout support stamps `is-layout-flow` classes on groups and
 * post templates and emits `:where(.is-layout-flow) > *` rules that zero the
 * child margins the classic stylesheets rely on (.ad-widget-box margin-top,
 * .combinator-part margins, ...). The classic CSS owns ALL spacing here, so
 * drop those hook classes from the front end output.
 *
 * @param string $content Rendered block HTML.
 * @param array  $block   Parsed block.
 * @return string
 */
function informer_strip_flow_layout_classes( $content, $block ) {
	$name = isset( $block['blockName'] ) ? $block['blockName'] : '';

	if ( 'core/group' !== $name && 'core/post-template' !== $name ) {
		return $content;
	}

	return str_replace(
		array( ' is-layout-flow', ' wp-block-group-is-layout-flow', ' wp-block-post-template-is-layout-flow' ),
		'',
		$content
	);
}
add_filter( 'render_block', 'informer_strip_flow_layout_classes', 40, 2 );
