PluginProbe
BeyondWords – AI audio for publishers / 4.4.0
BeyondWords – AI audio for publishers v4.4.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 / Post / PostMetaUtils.php

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

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