PluginProbe
BeyondWords – AI audio for publishers / 7.1.0
BeyondWords – AI audio for publishers v7.1.0
7.2.0 7.1.0 trunk 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.1.0 4.1.1 4.1.2 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 4.3.0 4.4.0 4.5.0 4.5.1 4.6.0 4.6.1 4.6.2 All 44 releases
speechkit / src / editor / components / block-attributes / class-block-attributes.php

class-block-attributes.php in BeyondWords – AI audio for publishers 7.1.0, at src/editor/components/block-attributes/class-block-attributes.php

221 lines 5.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare( strict_types = 1 );
4
5 /**
6 * BeyondWords support for Gutenberg blocks.
7 *
8 * @package BeyondWords\Editor\Components
9 * @author Stuart McAlpine <[email protected]>
10 * @since 3.7.0
11 * @since 4.0.0 Renamed from BlockAudioAttribute.php to BlockAttributes.php to support multiple attributes
12 * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods.
13 */
14
15 namespace BeyondWords\Editor\Components;
16
17 /**
18 * BlockAttributes
19 *
20 * @since 3.7.0
21 * @since 4.0.0 Renamed from BlockAudioAttribute to BlockAttributes to support multiple attributes.
22 * @since 6.0.0 Stop adding beyondwordsMarker attribute to blocks.
23 * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods.
24 */
25 defined( 'ABSPATH' ) || exit;
26
27 class BlockAttributes {
28
29 /**
30 * Block attribute holding the per-block language code (e.g. `en_GB`).
31 *
32 * @since 7.1.0
33 */
34 public const LANGUAGE_ATTRIBUTE = 'beyondwordsLanguageCode';
35
36 /**
37 * Block attribute holding the per-block voice id, which also carries the model.
38 *
39 * @since 7.1.0
40 */
41 public const VOICE_ATTRIBUTE = 'beyondwordsVoiceId';
42
43 /**
44 * Init.
45 *
46 * @since 4.0.0
47 * @since 6.0.0 Make static and remove renderBlock registration.
48 * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods.
49 */
50 public static function init() {
51 add_filter( 'register_block_type_args', [ self::class, 'register_audio_attribute'] );
52 add_filter( 'register_block_type_args', [ self::class, 'register_marker_attribute'] );
53 add_filter( 'register_block_type_args', [ self::class, 'register_language_attribute' ] );
54 add_filter( 'register_block_type_args', [ self::class, 'register_voice_attribute' ] );
55 }
56
57 /**
58 * Register "Audio" attribute for Gutenberg blocks.
59 *
60 * @since 6.0.0 Make static.
61 * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods.
62 */
63 public static function register_audio_attribute( $args ) {
64 return self::register_attribute(
65 $args,
66 'beyondwordsAudio',
67 [
68 'type' => 'boolean',
69 'default' => true,
70 ]
71 );
72 }
73
74 /**
75 * Register "Segment marker" attribute for Gutenberg blocks.
76 *
77 * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods.
78 *
79 * @deprecated This attribute is no longer used as of 6.0.0, but kept for backward compatibility.
80 *
81 * @since 6.0.0 Make static.
82 */
83 public static function register_marker_attribute( $args ) {
84 return self::register_attribute(
85 $args,
86 'beyondwordsMarker',
87 [
88 'type' => 'string',
89 'default' => '',
90 ]
91 );
92 }
93
94 /**
95 * Register the per-block "Language" attribute for Gutenberg blocks.
96 *
97 * @since 7.1.0
98 */
99 public static function register_language_attribute( $args ) {
100 return self::register_attribute(
101 $args,
102 self::LANGUAGE_ATTRIBUTE,
103 [
104 'type' => 'string',
105 'default' => '',
106 ]
107 );
108 }
109
110 /**
111 * Register the per-block "Voice" attribute for Gutenberg blocks.
112 *
113 * @since 7.1.0
114 */
115 public static function register_voice_attribute( $args ) {
116 return self::register_attribute(
117 $args,
118 self::VOICE_ATTRIBUTE,
119 [
120 'type' => 'string',
121 'default' => '',
122 ]
123 );
124 }
125
126 /**
127 * Add a block attribute, leaving an existing definition of the same name alone.
128 *
129 * @since 7.1.0
130 *
131 * @param mixed $args The `register_block_type_args` args, which may be null.
132 */
133 private static function register_attribute( $args, string $name, array $schema ): array {
134 if ( ! isset( $args['attributes'] ) ) {
135 $args['attributes'] = [];
136 }
137
138 if ( ! array_key_exists( $name, $args['attributes'] ) ) {
139 $args['attributes'][ $name ] = $schema;
140 }
141
142 return $args;
143 }
144
145 /**
146 * Add the segment-scoped voice data attributes to a rendered block.
147 *
148 * Not registered in init(): it is added around the API body build only, so
149 * front-end output is untouched.
150 *
151 * @since 7.1.0
152 *
153 * @param string $block_content The rendered block HTML.
154 * @param array $block The parsed block.
155 */
156 public static function add_segment_attributes( $block_content, $block ): string {
157 $block_content = (string) $block_content;
158
159 $attrs = ( is_array( $block ) && is_array( $block['attrs'] ?? null ) ) ? $block['attrs'] : [];
160
161 $language = self::language_code( self::attribute_value( $attrs, self::LANGUAGE_ATTRIBUTE ) );
162 $voice_id = self::voice_id( self::attribute_value( $attrs, self::VOICE_ATTRIBUTE ) );
163
164 if ( '' === $language && '' === $voice_id ) {
165 return $block_content;
166 }
167
168 $processor = new \WP_HTML_Tag_Processor( $block_content );
169
170 // Tagless output (an empty block, or a shortcode block) has nothing to carry them.
171 if ( ! $processor->next_tag() ) {
172 return $block_content;
173 }
174
175 if ( '' !== $language ) {
176 $processor->set_attribute( 'data-beyondwords-language', $language );
177 }
178
179 if ( '' !== $voice_id ) {
180 $processor->set_attribute( 'data-beyondwords-voice-id', $voice_id );
181 }
182
183 return $processor->get_updated_html();
184 }
185
186 /**
187 * A block attribute as a trimmed string.
188 *
189 * @since 7.1.0
190 *
191 * @return string The value, or '' when unset or non-scalar.
192 */
193 private static function attribute_value( array $attrs, string $name ): string {
194 $value = $attrs[ $name ] ?? '';
195
196 return is_scalar( $value ) ? trim( (string) $value ) : '';
197 }
198
199 /**
200 * A language code in the shape the API issues, or '' — the comment
201 * delimiter is editor-writable, so it is not trusted to carry one.
202 *
203 * Fits every code the API lists, including `fil_PH`, `sr_Latn_RS` and
204 * `zh_CN_henan`; nothing with whitespace, quotes or markup does.
205 *
206 * @since 7.1.0
207 */
208 private static function language_code( string $value ): string {
209 return preg_match( '/^[a-z]{2,3}(_[A-Za-z]{2,8}){1,2}$/', $value ) ? $value : '';
210 }
211
212 /**
213 * A voice id as the API issues them — a positive integer — or ''.
214 *
215 * @since 7.1.0
216 */
217 private static function voice_id( string $value ): string {
218 return ctype_digit( $value ) && (int) $value > 0 ? (string) (int) $value : '';
219 }
220 }
221