'; /** wp_enqueue_script() handle for the player loader. */ private const HANDLE = 'readerr-player'; /** §8.1, verbatim — the same sentence the ID3 COMM frame carries. */ public const LABELS = array( 'pl' => 'Audio generowane przez AI (Readerr) - może zawierać błędy', 'de' => 'KI-generiertes Audio (Readerr) - kann Fehler enthalten', 'fr' => 'Audio généré par IA (Readerr) - peut contenir des erreurs', 'en' => 'AI-generated audio (Readerr) - may contain errors', ); /** * Spec §14: the stage and the label hold the card's exact height before the * module runs, inline so they win over theme classes and need no stylesheet. */ private const STAGE_STYLE = 'box-sizing:border-box;height:128px;display:flex;align-items:center;padding:0 12px;border-radius:16px;background:rgba(128,128,128,.08)'; private const LABEL_STYLE = 'margin:8px 0 0;font-size:13px;line-height:20px'; /** Spec §12 and §16: the reader-facing "coming soon" text, in the article language. */ public const PENDING = array( 'pl' => 'Wersja audio w przygotowaniu', 'de' => 'Audioversion in Vorbereitung', 'fr' => 'Version audio en préparation', 'en' => 'Audio version coming soon', ); /** Spec §12: an older placeholder is a promise nobody is keeping. */ public const PENDING_MAX_AGE_S = 3600; private const HEADPHONES = ''; public static function register(): void { // After wpautop (10) and shortcodes (11), so neither rewraps our markup — // and so the shortcode's marker is already in the content when we look. add_filter( 'the_content', array( self::class, 'place_player' ), 20 ); add_shortcode( 'readerr_player', array( self::class, 'shortcode' ) ); add_action( 'wp_enqueue_scripts', array( self::class, 'enqueue_player' ) ); add_action( 'init', array( self::class, 'register_block' ) ); add_action( 'enqueue_block_editor_assets', array( self::class, 'enqueue_block_editor_script' ) ); add_filter( 'wp_script_attributes', array( self::class, 'harden_script_tag' ) ); } /** * The Gutenberg block: dynamic, so its saved content is always empty * (block-player.js's save() returns null) and shortcode() — the same * method the [readerr_player] shortcode uses — decides at render time * what the block leaves behind. do_blocks() runs at the_content priority * 9, before place_player() at 20, so the block's marker is in the content * exactly like the shortcode's by the time place_player() looks for it. */ public static function register_block(): void { self::register_block_script(); register_block_type( __DIR__ . '/../blocks/player', array( 'render_callback' => array( self::class, 'shortcode' ) ) ); } /** * Split out from register_block() so it can be re-run on its own — * wp_register_script() is safe to call repeatedly, but register_block_type() * is not (a second call for the same name is a doing_it_wrong()). */ public static function register_block_script(): void { wp_register_script( 'readerr-block-player', plugins_url( 'assets/block-player.js', READERR_FILE ), array( 'wp-blocks', 'wp-block-editor', 'wp-element', 'wp-i18n', 'wp-data' ), READERR_VERSION, true ); wp_set_script_translations( 'readerr-block-player', 'readerr' ); } /** * The block renders only on a single `post` (renders_here()), so it has * no reason to appear in the inserter anywhere else — but gating that * with allowed_block_types_all (tried first, docs/zalozenia.md #50) went * too far: on any non-post context (a page, a CPT, the site editor, the * widgets editor, where $context->post is null) it replaces WordPress's * own `true` with an explicit list of every server-registered block, * which also hides every JS-only third-party block that never registered * server-side — and a `false` input from another plugin becomes `[false]`. * Gating the editor script's enqueue instead (the same gate * Readerr_Editor::enqueue() uses) hides the block from the inserter on a * page without touching what any other editor is allowed to show — the * server-side registration below still makes existing blocks in old * content render correctly everywhere. */ public static function enqueue_block_editor_script(): void { if ( 'post' !== get_post_type() ) { return; } wp_enqueue_script( 'readerr-block-player' ); } /** §10: one deferred script, only where a player will actually render. */ public static function enqueue_player(): void { if ( ! is_singular( 'post' ) ) { return; } $post_id = get_queried_object_id(); $path = Readerr_Meta::sanitize_path( get_post_meta( $post_id, Readerr_Meta::PATH, true ) ); if ( '' === $path || self::hidden( $post_id ) || self::password_protected( $post_id ) || 'lapsed' === Readerr_Access::state() ) { return; } // Under 'manual' only the shortcode or the block renders a player // (place_player()). if ( 'manual' === Readerr_Settings::get( 'position' ) && ! has_shortcode( (string) get_post_field( 'post_content', $post_id ), 'readerr_player' ) && ! has_block( 'readerr/player', $post_id ) ) { return; } self::enqueue_player_script(); } /** The player loader under our handle, so harden_script_tag() reaches it (the settings preview too). */ public static function enqueue_player_script(): void { wp_enqueue_script( self::HANDLE, (string) Readerr_Settings::get( 'player_url' ), array(), READERR_VERSION, array( 'strategy' => 'defer', 'in_footer' => false, ) ); } /** * docs/todo.md, "Wygląd playera — nowy projekt", „Przy okazji z ich * wtyczki…": Cloudflare Rocket Loader and JS-optimizing plugins rewrite *