PluginProbe
BeyondWords – AI audio for publishers / 6.0.3
BeyondWords – AI audio for publishers v6.0.3
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 4.7.0 All 43 releases
speechkit / src / Component / Post / SelectVoice / SelectVoice.php

SelectVoice.php in BeyondWords – AI audio for publishers 6.0.3, at src/Component/Post/SelectVoice/SelectVoice.php

367 lines 10.9 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 Component: Select Voice
7 *
8 * @package Beyondwords\Wordpress
9 * @author Stuart McAlpine <stu@beyondwords.io>
10 * @since 4.0.0
11 */
12
13 namespace Beyondwords\Wordpress\Component\Post\SelectVoice;
14
15 use Beyondwords\Wordpress\Core\CoreUtils;
16 use Beyondwords\Wordpress\Component\Settings\SettingsUtils;
17 use Beyondwords\Wordpress\Core\ApiClient;
18
19 /**
20 * SelectVoice
21 *
22 * @since 4.0.0
23 */
24 class SelectVoice
25 {
26 /**
27 * Init.
28 *
29 * @since 4.0.0
30 * @since 6.0.0 Make static.
31 */
32 public static function init()
33 {
34 add_action('rest_api_init', [self::class, 'restApiInit']);
35 add_action('admin_enqueue_scripts', [self::class, 'adminEnqueueScripts']);
36
37 add_action('wp_loaded', function (): void {
38 $postTypes = SettingsUtils::getCompatiblePostTypes();
39
40 if (is_array($postTypes)) {
41 foreach ($postTypes as $postType) {
42 add_action("save_post_{$postType}", [self::class, 'save'], 10);
43 }
44 }
45 });
46 }
47
48 /**
49 * HTML output for this component.
50 *
51 * @since 4.0.0
52 * @since 4.5.1 Hide element if no language data exists.
53 * @since 5.4.0 Always display all languages and associated voices.
54 * @since 6.0.0 Make static.
55 *
56 * @param \WP_Post $post The post object.
57 *
58 * @return string|null
59 */
60 public static function element($post)
61 {
62 $languageCode = self::getLanguageCode($post->ID);
63 $voiceId = self::getVoiceId($post->ID);
64 $languages = ApiClient::getLanguages();
65 $voices = self::getVoicesForLanguage($languageCode);
66
67 wp_nonce_field('beyondwords_select_voice', 'beyondwords_select_voice_nonce');
68
69 self::renderLanguageSelect($languages, $languageCode);
70 self::renderVoiceSelect($voices, $voiceId, $languageCode);
71 self::renderLoadingSpinner();
72 }
73
74 /**
75 * Get the language code for a post.
76 *
77 * @since 6.0.0
78 *
79 * @param int $postId The post ID.
80 * @return string|false The language code or false if not set.
81 */
82 private static function getLanguageCode(int $postId)
83 {
84 $postLanguageCode = get_post_meta($postId, 'beyondwords_language_code', true);
85 return $postLanguageCode ?: get_option('beyondwords_project_language_code');
86 }
87
88 /**
89 * Get the voice ID for a post.
90 *
91 * @since 6.0.0
92 *
93 * @param int $postId The post ID.
94 * @return string|false The voice ID or false if not set.
95 */
96 private static function getVoiceId(int $postId)
97 {
98 $postVoiceId = get_post_meta($postId, 'beyondwords_body_voice_id', true);
99 return $postVoiceId ?: get_option('beyondwords_project_body_voice_id');
100 }
101
102 /**
103 * Get voices for a language code.
104 *
105 * @since 6.0.0
106 *
107 * @param string|false $languageCode The language code.
108 * @return array The voices array.
109 */
110 private static function getVoicesForLanguage($languageCode): array
111 {
112 if ($languageCode === false || $languageCode === '') {
113 return [];
114 }
115
116 $voices = ApiClient::getVoices($languageCode);
117 return is_array($voices) ? $voices : [];
118 }
119
120 /**
121 * Render the language select dropdown.
122 *
123 * @since 6.0.0
124 *
125 * @param array $languages The languages array.
126 * @param string|false $selectedLanguageCode The selected language code.
127 */
128 private static function renderLanguageSelect(array $languages, $selectedLanguageCode): void
129 {
130 ?>
131 <p
132 id="beyondwords-metabox-select-voice--language-code"
133 class="post-attributes-label-wrapper page-template-label-wrapper"
134 >
135 <label class="post-attributes-label" for="beyondwords_language_code">
136 Language
137 </label>
138 </p>
139 <select id="beyondwords_language_code" name="beyondwords_language_code" style="width: 100%;">
140 <?php
141 foreach ($languages as $language) {
142 if (empty($language['code']) || empty($language['name']) || empty($language['accent'])) {
143 continue;
144 }
145 printf(
146 '<option value="%s" data-default-voice-id="%s" %s>%s (%s)</option>',
147 esc_attr($language['code']),
148 esc_attr($language['default_voices']['body']['id'] ?? ''),
149 selected(strval($language['code']), strval($selectedLanguageCode)),
150 esc_html($language['name']),
151 esc_html($language['accent'])
152 );
153 }
154 ?>
155 </select>
156 <?php
157 }
158
159 /**
160 * Render the voice select dropdown.
161 *
162 * @since 6.0.0
163 *
164 * @param array $voices The voices array.
165 * @param string|false $selectedVoiceId The selected voice ID.
166 * @param string|false $languageCode The language code.
167 */
168 private static function renderVoiceSelect(array $voices, $selectedVoiceId, $languageCode): void
169 {
170 ?>
171 <p
172 id="beyondwords-metabox-select-voice--voice-id"
173 class="post-attributes-label-wrapper page-template-label-wrapper"
174 >
175 <label class="post-attributes-label" for="beyondwords_voice_id">
176 Voice
177 </label>
178 </p>
179 <select
180 id="beyondwords_voice_id"
181 name="beyondwords_voice_id"
182 style="width: 100%;"
183 <?php echo disabled(!strval($languageCode)) ?>
184 >
185 <?php
186 foreach ($voices as $voice) {
187 printf(
188 '<option value="%s" %s>%s</option>',
189 esc_attr($voice['id']),
190 selected(strval($voice['id']), strval($selectedVoiceId)),
191 esc_html($voice['name'])
192 );
193 }
194 ?>
195 </select>
196 <?php
197 }
198
199 /**
200 * Render the loading spinner.
201 *
202 * @since 6.0.0
203 */
204 private static function renderLoadingSpinner(): void
205 {
206 ?>
207 <img
208 src="/wp-admin/images/spinner.gif"
209 class="beyondwords-settings__loader"
210 style="display:none; padding: 3px 0;"
211 />
212 <?php
213 }
214
215 /**
216 * Save the meta when the post is saved.
217 *
218 * @since 4.0.0
219 * @since 6.0.0 Make static.
220 *
221 * @param int $postId The ID of the post being saved.
222 */
223 public static function save($postId)
224 {
225 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
226 return $postId;
227 }
228
229 // "save_post" can be triggered at other times, so verify this request came from the our component
230 if (
231 ! isset($_POST['beyondwords_language_code']) ||
232 ! isset($_POST['beyondwords_voice_id']) ||
233 ! isset($_POST['beyondwords_select_voice_nonce'])
234 ) {
235 return $postId;
236 }
237
238 // "save_post" can be triggered at other times, so verify this request came from the our component
239 if (
240 ! wp_verify_nonce(
241 sanitize_key($_POST['beyondwords_select_voice_nonce']),
242 'beyondwords_select_voice'
243 )
244 ) {
245 return $postId;
246 }
247
248 $languageCode = sanitize_text_field(wp_unslash($_POST['beyondwords_language_code']));
249
250 if (! empty($languageCode)) {
251 update_post_meta($postId, 'beyondwords_language_code', $languageCode);
252 } else {
253 delete_post_meta($postId, 'beyondwords_language_code');
254 }
255
256 $voiceId = sanitize_text_field(wp_unslash($_POST['beyondwords_voice_id']));
257
258 if (! empty($voiceId)) {
259 update_post_meta($postId, 'beyondwords_body_voice_id', $voiceId);
260 update_post_meta($postId, 'beyondwords_title_voice_id', $voiceId);
261 update_post_meta($postId, 'beyondwords_summary_voice_id', $voiceId);
262 } else {
263 delete_post_meta($postId, 'beyondwords_body_voice_id');
264 delete_post_meta($postId, 'beyondwords_title_voice_id');
265 delete_post_meta($postId, 'beyondwords_summary_voice_id');
266 }
267
268 return $postId;
269 }
270
271 /**
272 * Register WP REST API route
273 *
274 * @since 4.0.0
275 * @since 6.0.0 Make static.
276 *
277 * @return void
278 */
279 public static function restApiInit()
280 {
281 // Languages endpoint
282 register_rest_route('beyondwords/v1', '/languages', [
283 'methods' => \WP_REST_Server::READABLE,
284 'callback' => [self::class, 'languagesRestApiResponse'],
285 'permission_callback' => fn() => current_user_can('edit_posts'),
286 ]);
287
288 // Voices endpoint
289 register_rest_route('beyondwords/v1', '/languages/(?P<languageCode>[a-zA-Z0-9-_]+)/voices', [
290 'methods' => \WP_REST_Server::READABLE,
291 'callback' => [self::class, 'voicesRestApiResponse'],
292 'permission_callback' => fn() => current_user_can('edit_posts'),
293 ]);
294 }
295
296 /**
297 * "Languages" WP REST API response (required for the Gutenberg editor).
298 *
299 * @since 4.0.0
300 * @since 5.4.0 No longer filter by "Languages" plugin setting.
301 * @since 6.0.0 Make static.
302 *
303 * @return \WP_REST_Response
304 */
305 public static function languagesRestApiResponse()
306 {
307 $languages = ApiClient::getLanguages();
308
309 return new \WP_REST_Response($languages);
310 }
311
312 /**
313 * "Voices" WP REST API response (required for the Gutenberg editor
314 * and Block Editor).
315 *
316 * @since 4.0.0
317 * @since 6.0.0 Make static.
318 *
319 * @return \WP_REST_Response
320 */
321 public static function voicesRestApiResponse(\WP_REST_Request $data)
322 {
323 $params = $data->get_url_params();
324
325 $voices = ApiClient::getVoices($params['languageCode']);
326
327 return new \WP_REST_Response($voices);
328 }
329
330 /**
331 * Register the component scripts.
332 *
333 * @since 4.0.0
334 * @since 6.0.0 Make static.
335 *
336 * @param string $hook Page hook
337 *
338 * @return void
339 */
340 public static function adminEnqueueScripts($hook)
341 {
342 if (! CoreUtils::isGutenbergPage() && ( $hook === 'post.php' || $hook === 'post-new.php')) {
343 wp_register_script(
344 'beyondwords-metabox--select-voice',
345 BEYONDWORDS__PLUGIN_URI . 'src/Component/Post/SelectVoice/classic-metabox.js',
346 ['jquery', 'underscore'],
347 BEYONDWORDS__PLUGIN_VERSION,
348 true
349 );
350
351 /**
352 * Localize the script to handle ajax requests
353 */
354 wp_localize_script(
355 'beyondwords-metabox--select-voice',
356 'beyondwordsData',
357 [
358 'nonce' => wp_create_nonce('wp_rest'),
359 'root' => esc_url_raw(rest_url()),
360 ]
361 );
362
363 wp_enqueue_script('beyondwords-metabox--select-voice');
364 }
365 }
366 }
367