PluginProbe
BeyondWords – AI audio for publishers / 4.3.0
BeyondWords – AI audio for publishers v4.3.0
7.2.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 All 44 releases
speechkit / src / Component / Post / PostMetaUtils.php

PostMetaUtils.php in BeyondWords – AI audio for publishers 4.3.0, at src/Component/Post/PostMetaUtils.php

481 lines 14.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 namespace Beyondwords\Wordpress\Component\Post;
6
7 use Beyondwords\Wordpress\Component\Settings\PlayerStyle\PlayerStyle;
8 use Beyondwords\Wordpress\Core\CoreUtils;
9
10 /**
11 * BeyondWords Post Meta (Custom Field) Utilities.
12 *
13 * @package Beyondwords
14 * @subpackage Beyondwords/includes
15 * @author Stuart McAlpine <[email protected]>
16 * @since 3.5.0
17 */
18 class PostMetaUtils
19 {
20 public const WP_ERROR_FORMAT = 'WP_Error [%s] %s';
21
22 /**
23 * Get "renamed" Post Meta.
24 *
25 * We previously saved custom fields with a prefix of `speechkit_*` and we now
26 * save them with a prefix of `beyondwords_*`.
27 *
28 * This method checks both prefixes, copying `speechkit_*` data to `beyondwords_*`.
29 *
30 * @since 3.7.0
31 *
32 * @param int $postId Post ID.
33 * @param string $name Custom field name, without the prefix.
34 *
35 * @return string
36 */
37 public static function getRenamedPostMeta($postId, $name)
38 {
39 if (metadata_exists('post', $postId, 'beyondwords_' . $name)) {
40 return get_post_meta($postId, 'beyondwords_' . $name, true);
41 }
42
43 if (metadata_exists('post', $postId, 'speechkit_' . $name)) {
44 $value = get_post_meta($postId, 'speechkit_' . $name, true);
45
46 // Migrate over to newer `beyondwords_*` format
47 update_post_meta($postId, 'beyondwords_' . $name, $value);
48
49 return $value;
50 }
51
52 return '';
53 }
54
55 /**
56 * Get the BeyondWords metadata for a Post.
57 *
58 * @since 4.1.0 Append 'beyondwords_version' and 'wordpress_version'.
59 */
60 public static function getAllBeyondwordsMetadata($postId)
61 {
62 global $wp_version;
63
64 $keysToCheck = CoreUtils::getPostMetaKeys('all');
65
66 $metadata = has_meta($postId);
67
68 $metadata = array_filter($metadata, function ($item) use ($keysToCheck) {
69 return in_array($item['meta_key'], $keysToCheck);
70 });
71
72 // Prepend the WordPress Post ID to the meta data
73 array_push(
74 $metadata,
75 [
76 'meta_id' => null,
77 'meta_key' => 'beyondwords_version',
78 'meta_value' => BEYONDWORDS__PLUGIN_VERSION, // phpcs:ignore
79 ],
80 [
81 'meta_id' => null,
82 'meta_key' => 'wordpress_version',
83 'meta_value' => $wp_version, // phpcs:ignore
84 ],
85 [
86 'meta_id' => null,
87 'meta_key' => 'wordpress_post_id',
88 'meta_value' => $postId, // phpcs:ignore
89 ],
90 );
91
92 return $metadata;
93 }
94
95 /**
96 * Remove the BeyondWords metadata for a Post.
97 */
98 public static function removeAllBeyondwordsMetadata($postId)
99 {
100 $keysToCheck = [
101 'beyondwords_generate_audio',
102 'beyondwords_project_id',
103 'beyondwords_content_id',
104 'beyondwords_podcast_id',
105 'beyondwords_player_style',
106 'beyondwords_language_id',
107 'beyondwords_body_voice_id',
108 'beyondwords_title_voice_id',
109 'beyondwords_summary_voice_id',
110 'beyondwords_error_message',
111 'beyondwords_disabled',
112 'publish_post_to_speechkit',
113 'speechkit_generate_audio',
114 'speechkit_project_id',
115 'speechkit_podcast_id',
116 'speechkit_error_message',
117 'speechkit_disabled',
118 'speechkit_access_key',
119 'speechkit_error',
120 'speechkit_info',
121 'speechkit_response',
122 'speechkit_retries',
123 'speechkit_status',
124 '_speechkit_link',
125 '_speechkit_text',
126 ];
127
128 foreach ($keysToCheck as $key) {
129 // todo do we need to check this boolean?
130 delete_post_meta($postId, $key, null);
131 }
132
133 return true;
134 }
135
136 /**
137 * Get the Content ID for a WordPress Post.
138 *
139 * Over time there have been various approaches to storing the Content ID.
140 * This function tries each approach in reverse-date order.
141 *
142 * @since 3.0.0
143 * @since 3.5.0 Moved from Core\Utils to Component\Post\PostUtils
144 * @since 4.0.0 Renamed to getContentId() & prioritise beyondwords_content_id
145 *
146 * @param int $postId Post ID.
147 *
148 * @return int|false Content ID, or false
149 */
150 public static function getContentId($postId)
151 {
152 // Check for "Content ID" custom field (string, uuid)
153 $contentId = get_post_meta($postId, 'beyondwords_content_id', true);
154
155 if (! $contentId) {
156 // Also try "Podcast ID" custom field (number, or string for > 4.x)
157 $contentId = PostMetaUtils::getPodcastId($postId);
158 }
159
160 /**
161 * Filters the BeyondWords Content ID.
162 *
163 * Scheduled for removal in plugin version 5.0.0.
164 *
165 * @since 4.0.0
166 *
167 * @deprecated 4.3.0 No replacement is expected. Email [email protected] if you are using this filter.
168 *
169 * @param string $contentId The BeyondWords Content ID.
170 * @param int $postId The post ID.
171 */
172 $contentId = apply_filters('beyondwords_content_id', $contentId, $postId);
173
174 return $contentId;
175 }
176
177 /**
178 * Get the (legacy) Podcast ID for a WordPress Post.
179 *
180 * Over time there have been various approaches to storing the Podcast ID.
181 * This function tries each approach in reverse-date order.
182 *
183 * @since 3.0.0
184 * @since 3.5.0 Moved from Core\Utils to Component\Post\PostUtils
185 * @since 4.0.0 Allow string values for UUIDs stored >= v4.x
186 *
187 * @param int $postId Post ID.
188 *
189 * @return int|false Podcast ID, or false
190 */
191 public static function getPodcastId($postId)
192 {
193 // Check for "Podcast ID" custom field (number, or string for > 4.x)
194 $podcastId = PostMetaUtils::getRenamedPostMeta($postId, 'podcast_id');
195
196 if ($podcastId) {
197 return $podcastId;
198 }
199
200 // It may also be found by parsing post_meta._speechkit_link
201 $speechkit_link = get_post_meta($postId, '_speechkit_link', true);
202 // Player URL can be either /a/[ID] or /e/[ID] or /m/[ID]
203 preg_match('/\/[aem]\/(\d+)/', (string)$speechkit_link, $matches);
204 if ($matches) {
205 return intval($matches[1]);
206 }
207
208 // It may also be found by parsing post_meta.speechkit_response
209 $speechkit_response = static::getHttpResponseBodyFromPostMeta($postId, 'speechkit_response');
210 preg_match('/"podcast_id":(")?(\d+)(?(1)\1|)/', (string)$speechkit_response, $matches);
211 // $matches[2] is the Podcast ID (response.podcast_id)
212 if ($matches && $matches[2]) {
213 return intval($matches[2]);
214 }
215
216 /**
217 * It may also be found by parsing post_meta.speechkit_info
218 *
219 * NOTE: This has been copied verbatim from the existing iframe player check
220 * at Speechkit_Public::iframe_player_embed_html(), in case it is
221 * needed for posts which were created a very long time ago.
222 * I cannot write unit tests for this to pass, they always fail for me,
223 * so there are currently no tests for it.
224 **/
225 $article = get_post_meta($postId, 'speechkit_info', true);
226 if (empty($article) || ! isset($article['share_url'])) {
227 // This is exactly the same if/else statement that we have at
228 // Speechkit_Public::iframe_player_embed_html(), but there is
229 // nothing for us to to do here.
230 } else {
231 // This is the part that we need...
232 $url = $article['share_url'];
233
234 // Player URL can be either /a/[ID] or /e/[ID] or /m/[ID]
235 preg_match('/\/[aem]\/(\d+)/', (string)$url, $matches);
236 if ($matches) {
237 return intval($matches[1]);
238 }
239 }
240
241 // todo throw ContentIdNotFoundException???
242 return false;
243 }
244
245 /**
246 * Get the 'Generate Audio' value for a Post.
247 *
248 * @since 3.0.0
249 * @since 3.5.0 Moved from Core\Utils to Component\Post\PostUtils
250 *
251 * @param int $postId Post ID.
252 *
253 * @return bool
254 */
255 public static function hasGenerateAudio($postId)
256 {
257 if (PostMetaUtils::getRenamedPostMeta($postId, 'generate_audio') === '1') {
258 return true;
259 }
260
261 if (get_post_meta($postId, 'publish_post_to_speechkit', true) === '1') {
262 return true;
263 }
264
265 $projectId = PostMetaUtils::getProjectId($postId);
266 $contentId = PostMetaUtils::getContentId($postId);
267
268 if ($projectId && $contentId) {
269 return true;
270 }
271
272 return false;
273 }
274
275 /**
276 * Get the Project ID for a WordPress Post.
277 *
278 * It is possible to change the BeyondWords project ID in the plugin settings,
279 * so the current Project ID setting will not necessarily be correct for all
280 * historic Posts. Because of this, we attempt to retrive the correct Project ID
281 * from various custom fields, then fall-back to the plugin setting.
282 *
283 * @since 3.0.0
284 * @since 3.5.0 Moved from Core\Utils to Component\Post\PostUtils
285 * @since 4.0.0 Apply beyondwords_project_id filter
286 *
287 * @param int $postId Post ID.
288 *
289 * @return int|false Project ID, or false
290 */
291 public static function getProjectId($postId)
292 {
293 // Check custom fields
294 $projectId = intval(PostMetaUtils::getRenamedPostMeta($postId, 'project_id'));
295
296 if (! $projectId) {
297 // It may also be found by parsing post_meta.speechkit_response
298 $speechkit_response = static::getHttpResponseBodyFromPostMeta($postId, 'speechkit_response');
299 preg_match('/"project_id":(")?(\d+)(?(1)\1|)/', (string)$speechkit_response, $matches);
300 // $matches[2] is the Project ID (response.project_id)
301 if ($matches && $matches[2]) {
302 $projectId = intval($matches[2]);
303 }
304 }
305
306 // If we still don't have an ID then use the default from the plugin settings
307 if (! $projectId) {
308 $projectId = intval(get_option('beyondwords_project_id'));
309 }
310
311 if (! $projectId) {
312 // Return boolean false instead of numeric 0
313 $projectId = false;
314 }
315
316 // todo throw ProjectIdNotFoundException?
317
318 /**
319 * Filters the BeyondWords Project ID.
320 *
321 * Scheduled for removal in plugin version 5.0.0.
322 *
323 * @since 4.0.0
324 *
325 * @deprecated 4.3.0 No replacement is expected. Email [email protected] if you are using this filter.
326 *
327 * @param string $contentId The BeyondWords Project ID.
328 * @param int $postId The post ID.
329 */
330 $projectId = apply_filters('beyondwords_project_id', $projectId, $postId);
331
332 return $projectId;
333 }
334
335 /**
336 * Get the Body Voice ID for a WordPress Post.
337 *
338 * We do not filter this, because the Block Editor directly accesses this
339 * custom field, bypassing any filters we add here.
340 *
341 * @since 4.0.0
342 *
343 * @param int $postId Post ID.
344 *
345 * @return int|false Body Voice ID, or false
346 */
347 public static function getBodyVoiceId($postId)
348 {
349 $voiceId = get_post_meta($postId, 'beyondwords_body_voice_id', true);
350
351 return $voiceId;
352 }
353
354 /**
355 * Get the Title Voice ID for a WordPress Post.
356 *
357 * We do not filter this, because the Block Editor directly accesses this
358 * custom field, bypassing any filters we add here.
359 *
360 * @since 4.0.0
361 *
362 * @param int $postId Post ID.
363 *
364 * @return int|false Title Voice ID, or false
365 */
366 public static function getTitleVoiceId($postId)
367 {
368 $voiceId = get_post_meta($postId, 'beyondwords_title_voice_id', true);
369
370 return $voiceId;
371 }
372
373 /**
374 * Get the Summary Voice ID for a WordPress Post.
375 *
376 * We do not filter this, because the Block Editor directly accesses this
377 * custom field, bypassing any filters we add here.
378 *
379 * @since 4.0.0
380 *
381 * @param int $postId Post ID.
382 *
383 * @return int|false Summary Voice ID, or false
384 */
385 public static function getSummaryVoiceId($postId)
386 {
387 $voiceId = get_post_meta($postId, 'beyondwords_summary_voice_id', true);
388
389 return $voiceId;
390 }
391
392 /**
393 * Get the player style for a post.
394 *
395 * Defaults to the plugin setting if the custom field doesn't exist.
396 *
397 * @since 4.1.0
398 *
399 * @param int $postId Post ID.
400 *
401 * @return string Player style.
402 */
403 public static function getPlayerStyle($postId)
404 {
405 $playerStyle = get_post_meta($postId, 'beyondwords_player_style', true);
406
407 // Prefer custom field
408 if ($playerStyle) {
409 return $playerStyle;
410 }
411
412 // Fall back to plugin setting
413 return get_option('beyondwords_player_style', PlayerStyle::STANDARD);
414 }
415
416 /**
417 * Get the "Error Message" value for a WordPress Post.
418 *
419 * Supports data saved with the `beyondwords_*` prefix and the legacy `speechkit_*` prefix.
420 *
421 * @since 3.7.0
422 *
423 * @param int $postId Post ID.
424 *
425 * @return string
426 */
427 public static function getErrorMessage($postId)
428 {
429 return PostMetaUtils::getRenamedPostMeta($postId, 'error_message');
430 }
431
432 /**
433 * Get the "Disabled" value for a WordPress Post.
434 *
435 * Supports data saved with the `beyondwords_*` prefix and the legacy `speechkit_*` prefix.
436 *
437 * @since 3.7.0
438 *
439 * @param int $postId Post ID.
440 *
441 * @return string
442 */
443 public static function getDisabled($postId)
444 {
445 return PostMetaUtils::getRenamedPostMeta($postId, 'disabled');
446 }
447
448 /**
449 * Get HTTP response body from post meta.
450 *
451 * The data may have been saved as a WordPress HTTP response array. If it was,
452 * then return the 'body' key of the HTTP response instead of the raw post meta.
453 *
454 * The data may also have been saved as a WordPress WP_Error instance. If it was,
455 * then return a string containing the WP_Error code and message.
456 *
457 * @since 3.0.3
458 * @since 3.5.0 Moved from Core\Utils to Component\Post\PostUtils
459 * @since 3.6.1 Handle responses saved as object of class WP_Error
460 *
461 * @param int $postId Post ID.
462 * @param string $metaName Post Meta name.
463 *
464 * @return string
465 */
466 public static function getHttpResponseBodyFromPostMeta($postId, $metaName)
467 {
468 $postMeta = get_post_meta($postId, $metaName, true);
469
470 if (is_array($postMeta)) {
471 return (string)wp_remote_retrieve_body($postMeta);
472 }
473
474 if (is_wp_error($postMeta)) {
475 return sprintf(PostMetaUtils::WP_ERROR_FORMAT, $postMeta->get_error_code(), $postMeta->get_error_message());
476 }
477
478 return (string)$postMeta;
479 }
480 }
481