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

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

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