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

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

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