` krijgen zonder iets te slopen. * * Drie routes leiden naar dezelfde afbeelding — het attributenfilter, de * contentfilter en `render_block` — en ze kunnen elkaar overlappen. Alles wat * met bestaande markup te maken heeft staat daarom hier, op één plek, zodat de * drie filters alleen nog hoeven te beslissen wélke afbeelding aan de beurt is. * * ── Twee regels die deze klasse afdwingt ──────────────────────────────────── * * 1. Nooit twee keer. `render_block` draait binnen `the_content` (prioriteit 9) * en `wp_filter_content_tags` daarna (prioriteit 12), dus dezelfde `` * passeert ons vaak twee keer. De tweede keer is een no-op. * * 2. Een handmatige `object-position` wint altijd. WordPress schrijft er zelf * één in bij `core/cover`, `core/media-text` en sinds 7.0 ook `core/image` * met een focalPoint. Dat is een expliciete keuze van de redacteur op dat * ene blok; die overschrijven we niet met een instelling van de afbeelding. * * @package Yinga\Focus */ declare( strict_types = 1 ); namespace Yinga\Focus\Frontend; use Yinga\Focus\Support\Naming; use WP_HTML_Tag_Processor; defined( 'ABSPATH' ) || exit; final class Markup { /** * Niet instantieerbaar. */ private function __construct() {} /** * Voegt class en style toe aan een attributen-array. * * `class` en `style` worden samengevoegd met wat er al stond; al het andere * wordt gezet. Bestaande waarden overschrijven we nooit stilzwijgend. * * @param array $attr Bestaande attributen. * @param array $add Wat erbij moet. * @return array */ public static function merge_attributes( array $attr, array $add ): array { if ( array() === $add ) { return $attr; } $class = isset( $attr['class'] ) ? trim( (string) $attr['class'] ) : ''; $style = isset( $attr['style'] ) ? trim( (string) $attr['style'] ) : ''; $attr['class'] = '' === $class ? $add['class'] : $class . ' ' . $add['class']; $attr['style'] = self::merge_style( $style, $add['style'] ); foreach ( $add as $key => $value ) { if ( 'class' !== $key && 'style' !== $key ) { $attr[ $key ] = $value; } } return $attr; } /** * Plakt twee style-declaraties aan elkaar. * * @param string $existing Bestaande style, mag leeg zijn. * @param string $add Wat erbij moet, eindigt al op een puntkomma. * @return string */ public static function merge_style( string $existing, string $add ): string { $existing = trim( $existing ); if ( '' === $existing ) { return $add; } return rtrim( $existing, "; \t\n\r" ) . ';' . $add; } /** * Heeft deze `` al een positie — van ons of van iemand anders? * * @param string $class Het class-attribuut. * @param string $style Het style-attribuut. * @return array{done: bool, positioned: bool} `done` = wij waren hier al, * `positioned` = er staat al een * object-position. */ public static function inspect( string $class, string $style ): array { $classes = preg_split( '/\s+/', trim( $class ) ) ?: array(); return array( 'done' => in_array( Naming::css_class( 'image' ), $classes, true ), 'positioned' => false !== stripos( $style, 'object-position' ), ); } /** * Leest het beeldformaat uit de classes van een content-afbeelding. * * WordPress zet er `size-large`, `size-thumbnail` en dergelijke op. Dat is * de enige manier om bij een `` uit de post-content te weten welk * formaat er geserveerd wordt — en dus of het hard gecropt is. * * @param string $class Het class-attribuut. * @return string|null Het formaat, of null als het er niet in staat. */ public static function size_from_class( string $class ): ?string { if ( ! preg_match( '/(?:^|\s)size-([A-Za-z0-9_-]+)/', $class, $match ) ) { return null; } return $match[1]; } /** * Past de focus toe op de eerste `` in een stuk HTML. * * Gebruikt WP_HTML_Tag_Processor en geen reguliere expressie: die parser * kent de echte HTML-regels, laat de rest van de markup met rust en kan * niet in een attribuutwaarde struikelen. * * @param string $html De markup. * @param int $id Attachment-ID. * @param ApplyPolicy $policy De policy. * @param string|null $size Het beeldformaat, of null om het uit de classes te lezen. * @return string De markup, ongewijzigd als er niets toe te passen viel. */ public static function apply( string $html, int $id, ApplyPolicy $policy, ?string $size = null ): string { if ( '' === $html || $id <= 0 || ! class_exists( WP_HTML_Tag_Processor::class ) ) { return $html; } // Goedkope voorcontrole vóór het parsen: staat er überhaupt een img in? if ( false === stripos( $html, 'next_tag( array( 'tag_name' => 'IMG' ) ) ) { return $html; } $class = (string) ( $tags->get_attribute( 'class' ) ?? '' ); $style = (string) ( $tags->get_attribute( 'style' ) ?? '' ); $state = self::inspect( $class, $style ); if ( $state['done'] ) { return $html; } if ( null === $size ) { $size = self::size_from_class( $class ); } if ( ! $policy->should_apply( $id, $size ) ) { return $html; } $add = $policy->attributes( $id, ! $state['positioned'] ); if ( array() === $add ) { return $html; } $tags->add_class( $add['class'] ); $tags->set_attribute( 'style', self::merge_style( $style, $add['style'] ) ); foreach ( $add as $key => $value ) { if ( 'class' !== $key && 'style' !== $key ) { $tags->set_attribute( $key, $value ); } } return $tags->get_updated_html(); } }