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

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

344 lines 11.3 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 * Setting: Sync
7 *
8 * @package Beyondwords\Wordpress
9 * @author Stuart McAlpine <stu@beyondwords.io>
10 * @since 5.0.0
11 */
12
13 namespace Beyondwords\Wordpress\Component\Settings;
14
15 use Beyondwords\Wordpress\Core\ApiClient;
16 use Beyondwords\Wordpress\Core\Environment;
17 use Symfony\Component\PropertyAccess\PropertyAccess;
18
19 /**
20 * Sync
21 *
22 * @since 5.0.0
23 *
24 * @SuppressWarnings(PHPMD.CyclomaticComplexity)
25 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
26 * @SuppressWarnings(PHPMD.NPathComplexity)
27 */
28 defined('ABSPATH') || exit;
29
30 class Sync
31 {
32 /**
33 * Map settings.
34 *
35 * @since 5.0.0
36 */
37 public const MAP_SETTINGS = [
38 // Player
39 'beyondwords_player_style' => '[player_settings][player_style]',
40 'beyondwords_player_theme' => '[player_settings][theme]',
41 'beyondwords_player_theme_dark' => '[player_settings][dark_theme]',
42 'beyondwords_player_theme_light' => '[player_settings][light_theme]',
43 'beyondwords_player_theme_video' => '[player_settings][video_theme]',
44 'beyondwords_player_call_to_action' => '[player_settings][call_to_action]',
45 'beyondwords_player_widget_style' => '[player_settings][widget_style]',
46 'beyondwords_player_widget_position' => '[player_settings][widget_position]',
47 'beyondwords_player_skip_button_style' => '[player_settings][skip_button_style]',
48 'beyondwords_player_clickable_sections' => '[player_settings][segment_playback_enabled]',
49 // Project
50 'beyondwords_project_auto_publish_enabled' => '[project][auto_publish_enabled]',
51 'beyondwords_project_language_code' => '[project][language]',
52 'beyondwords_project_body_voice_id' => '[project][body][voice][id]',
53 'beyondwords_project_body_voice_speaking_rate' => '[project][body][voice][speaking_rate]',
54 'beyondwords_project_title_enabled' => '[project][title][enabled]',
55 'beyondwords_project_title_voice_id' => '[project][title][voice][id]',
56 'beyondwords_project_title_voice_speaking_rate' => '[project][title][voice][speaking_rate]',
57 // Video
58 'beyondwords_video_enabled' => '[video_settings][enabled]',
59 ];
60
61 /**
62 * Init.
63 *
64 * @since 5.0.0
65 * @since 6.0.0 Make static.
66 */
67 public static function init()
68 {
69 add_action('load-settings_page_beyondwords', [self::class, 'syncToWordPress'], 30);
70
71 if (Environment::hasAutoSyncSettings()) {
72 add_action('load-settings_page_beyondwords', [self::class, 'scheduleSyncs'], 20);
73 add_action('shutdown', [self::class, 'syncToDashboard']);
74 }
75 }
76
77 /**
78 * Should we schedule a sync on the current settings tab?
79 *
80 * @since 5.0.0
81 * @since 5.2.0 Remove API creds validation.
82 * @since 6.0.0 Make static.
83 *
84 * @return void
85 */
86 public static function scheduleSyncs()
87 {
88 $tab = Settings::getActiveTab();
89 $endpoints = [];
90
91 switch ($tab) {
92 case 'content':
93 case 'voices':
94 $endpoints = ['project'];
95 break;
96 case 'player':
97 $endpoints = ['player_settings', 'video_settings'];
98 break;
99 }
100
101 if (count($endpoints)) {
102 wp_cache_set('beyondwords_sync_to_wordpress', $endpoints, 'beyondwords', 60);
103 }
104 }
105
106 /**
107 * Sync from the dashboard/BeyondWords REST API to WordPress.
108 *
109 * @since 5.0.0 Introduced.
110 * @since 5.4.0 Stop saving language ID – we only need the ISO code now.
111 * @since 6.0.0 Make static.
112 *
113 * @return void
114 **/
115 public static function syncToWordPress()
116 {
117 $sync_to_wordpress = wp_cache_get('beyondwords_sync_to_wordpress', 'beyondwords');
118 wp_cache_delete('beyondwords_sync_to_wordpress', 'beyondwords');
119
120 if (empty($sync_to_wordpress) || ! is_array($sync_to_wordpress)) {
121 return;
122 }
123
124 $responses = [];
125
126 if (! empty(array_intersect($sync_to_wordpress, ['all', 'project']))) {
127 $project = ApiClient::getProject();
128 if (! empty($project)) {
129 $responses['project'] = $project;
130 }
131 }
132
133 if (! empty(array_intersect($sync_to_wordpress, ['all', 'player_settings']))) {
134 $player_settings = ApiClient::getPlayerSettings();
135 if (! empty($player_settings)) {
136 $responses['player_settings'] = $player_settings;
137 }
138 }
139
140 if (! empty(array_intersect($sync_to_wordpress, ['all', 'video_settings']))) {
141 $video_settings = ApiClient::getVideoSettings();
142 if (! empty($video_settings)) {
143 $responses['video_settings'] = $video_settings;
144 }
145 }
146
147 // Update WordPress options using the REST API response data.
148 self::updateOptionsFromResponses($responses);
149 }
150
151 /**
152 * Update WordPress options from REST API responses.
153 *
154 * @since 5.0.0
155 * @since 6.0.0 Make static.
156 * @since 6.0.3 Check path is readable before getting value.
157 *
158 * @return boolean
159 **/
160 public static function updateOptionsFromResponses($responses)
161 {
162 if (empty($responses)) {
163 add_settings_error(
164 'beyondwords_settings',
165 'beyondwords_settings',
166 '<span class="dashicons dashicons-controls-volumeon"></span> Unexpected BeyondWords REST API response.',
167 'error'
168 );
169 return false;
170 }
171
172 $propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
173 ->disableExceptionOnInvalidPropertyPath()
174 ->getPropertyAccessor();
175
176 $updated = false;
177
178 foreach (self::MAP_SETTINGS as $optionName => $path) {
179 if ($propertyAccessor->isReadable($responses, $path)) {
180 $value = $propertyAccessor->getValue($responses, $path);
181 if ($value !== null) {
182 update_option($optionName, $value, false);
183 $updated = true;
184 }
185 }
186 }
187
188 return $updated;
189 }
190
191 /**
192 * Sync from WordPress to the dashboard/BeyondWords REST API.
193 *
194 * @since 5.0.0
195 * @since 6.0.0 Make static.
196 *
197 * @return void
198 **/
199 public static function syncToDashboard()
200 {
201 $options = wp_cache_get('beyondwords_sync_to_dashboard', 'beyondwords');
202 wp_cache_delete('beyondwords_sync_to_dashboard', 'beyondwords');
203
204 if (empty($options) || ! is_array($options)) {
205 return;
206 }
207
208 $propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
209 ->disableExceptionOnInvalidPropertyPath()
210 ->getPropertyAccessor();
211
212 $settings = [];
213
214 foreach ($options as $option) {
215 if (self::shouldSyncOptionToDashboard($option)) {
216 $propertyAccessor->setValue(
217 $settings,
218 self::MAP_SETTINGS[$option],
219 get_option($option)
220 );
221 }
222 }
223
224 // Sync player settings back to API
225 if (isset($settings['player_settings'])) {
226 ApiClient::updatePlayerSettings($settings['player_settings']);
227
228 add_settings_error(
229 'beyondwords_settings',
230 'beyondwords_settings',
231 '<span class="dashicons dashicons-rest-api"></span> Player settings synced from WordPress to the BeyondWords dashboard.', // phpcs:ignore Generic.Files.LineLength.TooLong
232 'success'
233 );
234 }
235
236 // Sync title voice back to API
237 if (in_array('beyondwords_project_title_voice_speaking_rate', $options)) {
238 $value = $propertyAccessor->getValue(
239 $settings,
240 self::MAP_SETTINGS['beyondwords_project_title_voice_speaking_rate']
241 );
242
243 if ($value !== null) {
244 $titleVoiceId = get_option('beyondwords_project_title_voice_id');
245 ApiClient::updateVoice($titleVoiceId, [
246 'speaking_rate' => (int)$value,
247 ]);
248 }
249 }
250
251 // Sync body voice back to API
252 if (in_array('beyondwords_project_body_voice_speaking_rate', $options)) {
253 $value = $propertyAccessor->getValue(
254 $settings,
255 self::MAP_SETTINGS['beyondwords_project_body_voice_speaking_rate']
256 );
257
258 if ($value !== null) {
259 $bodyVoiceId = get_option('beyondwords_project_body_voice_id');
260 ApiClient::updateVoice($bodyVoiceId, [
261 'speaking_rate' => (int)$value,
262 ]);
263 }
264 }
265
266 // Sync project settings back to API
267 if (isset($settings['project'])) {
268 // Don't send speaking rates back to /project endpoint
269 $titleSpeakingRate = $propertyAccessor->getValue(
270 $settings,
271 self::MAP_SETTINGS['beyondwords_project_title_voice_speaking_rate']
272 );
273 if ($titleSpeakingRate) {
274 unset($settings['project']['title']['voice']['speaking_rate']);
275 }
276
277 // Don't send speaking rates back to /project endpoint
278 $bodySpeakingRate = $propertyAccessor->getValue(
279 $settings,
280 self::MAP_SETTINGS['beyondwords_project_body_voice_speaking_rate']
281 );
282 if ($bodySpeakingRate) {
283 unset($settings['project']['body']['voice']['speaking_rate']);
284 }
285
286 ApiClient::updateProject($settings['project']);
287
288 add_settings_error(
289 'beyondwords_settings',
290 'beyondwords_settings',
291 '<span class="dashicons dashicons-rest-api"></span> Project settings synced from WordPress to the BeyondWords dashboard.', // phpcs:ignore Generic.Files.LineLength.TooLong
292 'success'
293 );
294 }
295 }
296
297 /**
298 * Should we sync this option to the dashboard?
299 *
300 * @since 5.0.0
301 * @since 6.0.0 Make static.
302 *
303 * @param string $option_name Option name.
304 *
305 * @return void
306 **/
307 public static function shouldSyncOptionToDashboard($option_name)
308 {
309 if (! array_key_exists($option_name, self::MAP_SETTINGS)) {
310 return false;
311 }
312
313 // Check the option was updated without error
314 $hasErrors = get_settings_errors($option_name);
315
316 return is_array($hasErrors) && count($hasErrors) === 0;
317 }
318
319 /**
320 * Sync an option to the WordPress dashboard.
321 *
322 * Note that this DOES NOT make the API call, it instead flags the field
323 * as one to sync so that we can group fields and send them in a single
324 * request to the BeyondWords REST API.
325 *
326 * @since 5.0.0
327 *
328 * @return void
329 **/
330 public static function syncOptionToDashboard($optionName)
331 {
332 $options = wp_cache_get('beyondwords_sync_to_dashboard', 'beyondwords');
333
334 if (! is_array($options)) {
335 $options = [];
336 }
337
338 $options[] = $optionName;
339 $options = array_unique($options);
340
341 wp_cache_set('beyondwords_sync_to_dashboard', $options, 'beyondwords', 60);
342 }
343 }
344