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 / Settings / Settings.php

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

525 lines 16.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 settings.
7 *
8 * @package Beyondwords\Wordpress
9 * @author Stuart McAlpine <stu@beyondwords.io>
10 * @since 3.0.0
11 */
12
13 namespace Beyondwords\Wordpress\Component\Settings;
14
15 use Beyondwords\Wordpress\Component\Settings\Fields\IntegrationMethod\IntegrationMethod;
16 use Beyondwords\Wordpress\Component\Settings\Fields\Languages\Languages;
17 use Beyondwords\Wordpress\Component\Settings\Fields\PreselectGenerateAudio\PreselectGenerateAudio;
18 use Beyondwords\Wordpress\Component\Settings\Tabs\Content\Content;
19 use Beyondwords\Wordpress\Component\Settings\Tabs\Credentials\Credentials;
20 use Beyondwords\Wordpress\Component\Settings\Tabs\Player\Player;
21 use Beyondwords\Wordpress\Component\Settings\Tabs\Pronunciations\Pronunciations;
22 use Beyondwords\Wordpress\Component\Settings\Tabs\Summarization\Summarization;
23 use Beyondwords\Wordpress\Component\Settings\Tabs\Voices\Voices;
24 use Beyondwords\Wordpress\Component\Settings\SettingsUtils;
25 use Beyondwords\Wordpress\Component\Settings\Sync;
26 use Beyondwords\Wordpress\Core\Environment;
27
28 /**
29 * Settings
30 *
31 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
32 *
33 * @since 3.0.0
34 */
35 class Settings
36 {
37 public const REVIEW_NOTICE_TIME_FORMAT = '-14 days';
38
39 /**
40 * Init
41 *
42 * @since 3.0.0 Introduced.
43 * @since 5.4.0 Add plugin review notice.
44 * @since 6.0.0 Make static.
45 */
46 public static function init()
47 {
48 (new Credentials())::init();
49 (new Sync())::init();
50
51 if (SettingsUtils::hasValidApiConnection()) {
52 (new Voices())::init();
53 (new Content())::init();
54 (new Player())::init();
55 (new Summarization())::init();
56 (new Pronunciations())::init();
57 }
58
59 add_action('admin_menu', [self::class, 'addOptionsPage'], 1);
60 add_action('admin_notices', [self::class, 'printMissingApiCredsWarning'], 100);
61 add_action('admin_notices', [self::class, 'printSettingsErrors'], 200);
62 add_action('admin_notices', [self::class, 'maybePrintPluginReviewNotice']);
63 add_action('admin_enqueue_scripts', [self::class, 'enqueueScripts']);
64 add_action('load-settings_page_beyondwords', [self::class, 'maybeValidateApiCreds']);
65
66 add_action('rest_api_init', [self::class, 'restApiInit']);
67
68 add_filter('plugin_action_links_speechkit/speechkit.php', [self::class, 'addSettingsLinkToPluginPage']);
69 }
70
71 /**
72 * Add items to the WordPress admin menu.
73 *
74 * @since 3.0.0
75 * @since 6.0.0 Make static.
76 */
77 public static function addOptionsPage(): void
78 {
79 // Settings > BeyondWords
80 add_options_page(
81 __('BeyondWords Settings', 'speechkit'),
82 __('BeyondWords', 'speechkit'),
83 'manage_options',
84 'beyondwords',
85 [self::class, 'createAdminInterface']
86 );
87 }
88
89 /**
90 * Validate API creds if we are on the credentials tab.
91 *
92 * @since 5.4.0
93 * @since 6.0.0 Make static.
94 */
95 public static function maybeValidateApiCreds(): void
96 {
97 $activeTab = self::getActiveTab();
98
99 if ($activeTab === 'credentials') {
100 SettingsUtils::validateApiConnection();
101 }
102 }
103
104 /**
105 * Prints the admin interface for plugin settings.
106 *
107 * @since 3.0.0
108 * @since 4.7.0 Added tabs.
109 * @since 6.0.0 Make static.
110 */
111 public static function createAdminInterface(): void
112 {
113 $tabs = self::getTabs();
114 $activeTab = self::getActiveTab();
115 ?>
116 <div class="wrap">
117 <h1>
118 <?php esc_attr_e('BeyondWords Settings', 'speechkit'); ?>
119 </h1>
120
121 <form
122 id="beyondwords-plugin-settings"
123 action="<?php echo esc_url(admin_url('options.php')); ?>"
124 method="post"
125 >
126 <nav class="nav-tab-wrapper">
127 <ul>
128 <?php
129 foreach ($tabs as $id => $title) {
130 $activeClass = $id === $activeTab ? 'nav-tab-active' : '';
131
132 $url = add_query_arg([
133 'page' => 'beyondwords',
134 'tab' => urlencode($id),
135 ]);
136 ?>
137 <li>
138 <a
139 class="nav-tab <?php echo esc_attr($activeClass); ?>"
140 href="<?php echo esc_url($url); ?>"
141 >
142 <?php echo wp_kses_post($title); ?>
143 </a>
144 </li>
145 <?php
146 }
147 ?>
148 </ul>
149 </nav>
150
151 <hr class="wp-header-end">
152
153 <?php
154 settings_fields("beyondwords_{$activeTab}_settings");
155 do_settings_sections("beyondwords_{$activeTab}");
156
157 // Some tabs have no fields to submit
158 if (! in_array($activeTab, ['summarization', 'pronunciations'])) {
159 submit_button('Save changes');
160 }
161 ?>
162 </form>
163 </div>
164 <?php
165 }
166
167 /**
168 * Add "Settings" link to plugin page.
169 *
170 * @since 3.0.0
171 * @since 4.7.0 Prepend custom links instead of appending them.
172 * @since 6.0.0 Make static.
173 */
174 public static function addSettingsLinkToPluginPage($links)
175 {
176 $settingsLink = '<a href="' .
177 esc_url(admin_url('options-general.php?page=beyondwords')) .
178 '">' . __('Settings', 'speechkit') . '</a>';
179
180 array_unshift($links, $settingsLink);
181
182 return $links;
183 }
184
185 /**
186 * Get tabs.
187 *
188 * @since 4.7.0
189 * @since 5.2.0 Make static.
190 *
191 * @return array Tabs
192 */
193 public static function getTabs(): array
194 {
195 $tabs = [
196 'credentials' => __('Credentials', 'speechkit'),
197 'content' => __('Content', 'speechkit'),
198 'voices' => __('Voices', 'speechkit'),
199 'player' => __('Player', 'speechkit'),
200 'summarization' => __('Summarization', 'speechkit'),
201 'pronunciations' => __('Pronunciations', 'speechkit'),
202 ];
203
204 if (! SettingsUtils::hasValidApiConnection()) {
205 $tabs = array_splice($tabs, 0, 1);
206 }
207
208 return $tabs;
209 }
210
211 /**
212 * Get active tab.
213 *
214 * @since 4.7.0
215 * @since 5.2.0 Make static.
216 *
217 * @return string Active tab
218 */
219 public static function getActiveTab(): string
220 {
221 $tabs = self::getTabs();
222
223 if (! count($tabs)) {
224 return '';
225 }
226
227 $defaultTab = array_key_first($tabs);
228
229 // phpcs:disable WordPress.Security.NonceVerification.Recommended
230 if (isset($_GET['tab'])) {
231 $tab = sanitize_text_field(wp_unslash($_GET['tab']));
232 } else {
233 $tab = $defaultTab;
234 }
235 // phpcs:enable WordPress.Security.NonceVerification.Recommended
236
237 if (!empty($tab) && array_key_exists($tab, $tabs)) {
238 $activeTab = $tab;
239 } else {
240 $activeTab = $defaultTab;
241 }
242
243 return $activeTab;
244 }
245
246 /**
247 * Print missing API creds warning.
248 *
249 * @since 5.2.0
250 * @since 6.0.0 Make static.
251 */
252 public static function printMissingApiCredsWarning(): void
253 {
254 if (! SettingsUtils::hasApiCreds()) :
255 ?>
256 <div class="notice notice-info">
257 <p>
258 <strong>
259 <?php
260 printf(
261 /* translators: %s is replaced with a "plugin settings" link */
262 esc_html__('To use BeyondWords, please update the %s.', 'speechkit'),
263 sprintf(
264 '<a href="%s">%s</a>',
265 esc_url(admin_url('options-general.php?page=beyondwords')),
266 esc_html__('plugin settings', 'speechkit')
267 )
268 );
269 ?>
270 </strong>
271 </p>
272 <p>
273 <?php esc_html_e('Don’t have a BeyondWords account yet?', 'speechkit'); ?>
274 </p>
275 <p>
276 <a
277 class="button button-secondary"
278 href="<?php echo esc_url(sprintf('%s/auth/signup', Environment::getDashboardUrl())); ?>"
279 target="_blank"
280 >
281 <?php esc_html_e('Sign up free', 'speechkit'); ?>
282 </a>
283 </p>
284 </div>
285 <?php
286 endif;
287 }
288
289 /**
290 * Maybe print plugin review notice.
291 *
292 * @since 5.4.0
293 * @since 6.0.0 Make static.
294 */
295 public static function maybePrintPluginReviewNotice(): void
296 {
297 $screen = get_current_screen();
298 if ($screen && 'settings_page_beyondwords' !== $screen->id) {
299 return;
300 }
301
302 $dateActivated = get_option('beyondwords_date_activated', '2025-03-01');
303 $dateNoticeDismissed = get_option('beyondwords_notice_review_dismissed', '');
304
305 $showNotice = false;
306
307 if (empty($dateNoticeDismissed)) {
308 $dateActivated = strtotime($dateActivated);
309
310 if ($dateActivated < strtotime(self::REVIEW_NOTICE_TIME_FORMAT)) {
311 $showNotice = true;
312 }
313 }
314
315 if ($showNotice) :
316 ?>
317 <div id="beyondwords_notice_review" class="notice notice-info is-dismissible">
318 <p>
319 <strong>
320 <?php
321 printf(
322 /* translators: %s is replaced with a "WordPress Plugin Repo" link */
323 esc_html__('Happy with our work? Help us spread the word with a rating on the %s.', 'speechkit'), // phpcs:ignore Generic.Files.LineLength.TooLong
324 sprintf(
325 '<a href="%s">%s</a>',
326 'https://wordpress.org/support/plugin/speechkit/reviews/',
327 esc_html__('WordPress Plugin Repo', 'speechkit')
328 )
329 );
330 ?>
331 </strong>
332 </p>
333 </div>
334 <?php
335 endif;
336 }
337
338 /**
339 * Print settings errors.
340 *
341 * @since 3.0.0
342 * @since 6.0.0 Make static.
343 */
344 public static function printSettingsErrors(): void
345 {
346 $settingsErrors = wp_cache_get('beyondwords_settings_errors', 'beyondwords');
347 wp_cache_delete('beyondwords_settings_errors', 'beyondwords');
348
349 if (is_array($settingsErrors) && count($settingsErrors)) :
350 ?>
351 <div class="notice notice-error">
352 <ul class="ul-disc">
353 <?php
354 foreach ($settingsErrors as $error) {
355 printf(
356 '<li>%s</li>',
357 // Only allow links with href and target attributes
358 wp_kses(
359 $error,
360 [
361 'a' => [
362 'href' => [],
363 'target' => [],
364 ],
365 'b' => [],
366 'strong' => [],
367 'i' => [],
368 'em' => [],
369 'br' => [],
370 'code' => [],
371 ]
372 )
373 );
374 }
375 ?>
376 </ul>
377 </div>
378 <?php
379 endif;
380 }
381
382 /**
383 * Register WP REST API routes
384 *
385 * @since 5.4.0 Add REST API route to dismiss review notice.
386 * @since 6.0.0 Make static.
387 */
388 public static function restApiInit(): void
389 {
390 // settings endpoint
391 register_rest_route('beyondwords/v1', '/settings', [
392 'methods' => \WP_REST_Server::READABLE,
393 'callback' => [self::class, 'restApiResponse'],
394 'permission_callback' => fn() => current_user_can('edit_posts'),
395 ]);
396
397 // settings endpoint
398 register_rest_route('beyondwords/v1', '/settings', [
399 'methods' => \WP_REST_Server::READABLE,
400 'callback' => [self::class, 'restApiResponse'],
401 'permission_callback' => fn() => current_user_can('edit_posts'),
402 ]);
403
404 // dismiss review notice endpoint
405 register_rest_route('beyondwords/v1', '/settings/notices/review/dismiss', [
406 'methods' => \WP_REST_Server::CREATABLE,
407 'callback' => [self::class, 'dismissReviewNotice'],
408 'permission_callback' => fn() => current_user_can('manage_options'),
409 ]);
410 }
411
412 /**
413 * WP REST API response (required for the Gutenberg editor).
414 *
415 * DO NOT expose ALL settings e.g. be sure to never expose the API key.
416 *
417 * @since 3.0.0
418 * @since 3.4.0 Add pluginVersion and wpVersion.
419 * @since 6.0.0 Make static and add integrationMethod.
420 *
421 * @return \WP_REST_Response
422 */
423 public static function restApiResponse()
424 {
425 global $wp_version;
426
427 return new \WP_REST_Response([
428 'apiKey' => get_option('beyondwords_api_key', ''),
429 'integrationMethod' => IntegrationMethod::getIntegrationMethod(),
430 'pluginVersion' => BEYONDWORDS__PLUGIN_VERSION,
431 'projectId' => get_option('beyondwords_project_id', ''),
432 'preselect' => get_option(PreselectGenerateAudio::OPTION_NAME, PreselectGenerateAudio::DEFAULT_PRESELECT), // phpcs:ignore Generic.Files.LineLength.TooLong
433 'projectLanguageCode' => get_option('beyondwords_project_language_code', ''),
434 'projectBodyVoiceId' => get_option('beyondwords_project_body_voice_id', ''),
435 'restUrl' => get_rest_url(),
436 'wpVersion' => $wp_version,
437 ]);
438 }
439
440 /**
441 * Dismiss review notice.
442 *
443 * @since 5.4.0
444 * @since 6.0.0 Make static.
445 *
446 * @return \WP_REST_Response
447 */
448 public static function dismissReviewNotice()
449 {
450 $success = update_option('beyondwords_notice_review_dismissed', gmdate(\DateTime::ATOM));
451
452 return new \WP_REST_Response(
453 [
454 'success' => $success
455 ],
456 $success ? 200 : 500
457 );
458 }
459
460 /**
461 * Register the settings script.
462 *
463 * @since 5.0.0
464 * @since 6.0.0 Make static.
465 *
466 * @param string $hook Page hook
467 */
468 public static function enqueueScripts($hook)
469 {
470 if ($hook === 'settings_page_beyondwords') {
471 // jQuery UI JS
472 wp_enqueue_script('jquery-ui-core');// enqueue jQuery UI Core
473 wp_enqueue_script('jquery-ui-tabs');// enqueue jQuery UI Tabs
474
475 // Plugin settings JS
476 wp_register_script(
477 'beyondwords-settings',
478 BEYONDWORDS__PLUGIN_URI . 'build/settings.js',
479 ['jquery', 'jquery-ui-core', 'jquery-ui-tabs', 'underscore', 'tom-select'],
480 BEYONDWORDS__PLUGIN_VERSION,
481 true
482 );
483
484 // Tom Select JS
485 wp_enqueue_script(
486 'tom-select',
487 'https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/js/tom-select.complete.min.js', // phpcs:ignore
488 [],
489 '2.2.2',
490 true
491 );
492
493 // Plugin settings CSS
494 wp_enqueue_style(
495 'beyondwords-settings',
496 BEYONDWORDS__PLUGIN_URI . 'src/Component/Settings/settings.css',
497 'forms',
498 BEYONDWORDS__PLUGIN_VERSION
499 );
500
501 // Tom Select CSS
502 wp_enqueue_style(
503 'tom-select',
504 'https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/css/tom-select.css', // phpcs:ignore
505 false,
506 BEYONDWORDS__PLUGIN_VERSION
507 );
508
509 /**
510 * Localize the script to handle ajax requests
511 */
512 wp_add_inline_script(
513 'beyondwords-settings',
514 '
515 var beyondwordsData = beyondwordsData || {};
516 beyondwordsData.nonce = "' . wp_create_nonce('wp_rest') . '";
517 beyondwordsData.root = "' . esc_url_raw(rest_url()) . '";
518 ',
519 'before',
520 );
521
522 wp_enqueue_script('beyondwords-settings');
523 }
524 }
525 }