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

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

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