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 / Post / Panel / Inspect / Inspect.php

Inspect.php in BeyondWords – AI audio for publishers 6.3.0, at src/Component/Post/Panel/Inspect/Inspect.php

433 lines 13.5 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 Post Inspect Panel.
7 *
8 * @package Beyondwords\Wordpress
9 * @author Stuart McAlpine <stu@beyondwords.io>
10 * @since 3.0.0
11 */
12
13 namespace Beyondwords\Wordpress\Component\Post\Panel\Inspect;
14
15 use Beyondwords\Wordpress\Component\Post\PostMetaUtils;
16 use Beyondwords\Wordpress\Component\Settings\SettingsUtils;
17 use Beyondwords\Wordpress\Core\ApiClient;
18
19 /**
20 * Inspect
21 *
22 * @since 3.0.0
23 */
24 defined('ABSPATH') || exit;
25
26 class Inspect
27 {
28 /**
29 * Constructor
30 *
31 * @since 6.0.0 Make static.
32 */
33 public static function init()
34 {
35 add_action('admin_enqueue_scripts', [self::class, 'adminEnqueueScripts']);
36 add_action('add_meta_boxes', [self::class, 'addMetaBox']);
37 add_action('rest_api_init', [self::class, 'restApiInit']);
38
39 add_filter('default_hidden_meta_boxes', [self::class, 'hideMetaBox']);
40
41 add_action('wp_loaded', function (): void {
42 $postTypes = SettingsUtils::getCompatiblePostTypes();
43
44 if (is_array($postTypes)) {
45 foreach ($postTypes as $postType) {
46 add_action("save_post_{$postType}", [self::class, 'save'], 5);
47 }
48 }
49 });
50 }
51
52 /**
53 * Enqueue JS for Inspect feature.
54 *
55 * @since 6.0.0 Make static.
56 */
57 public static function adminEnqueueScripts($hook)
58 {
59 // Only enqueue for Post screens
60 if ($hook === 'post.php' || $hook === 'post-new.php') {
61 wp_enqueue_script(
62 'beyondwords-inspect',
63 BEYONDWORDS__PLUGIN_URI . 'src/Component/Post/Panel/Inspect/js/inspect.js',
64 ['jquery'],
65 BEYONDWORDS__PLUGIN_VERSION,
66 true
67 );
68 }
69 }
70
71 /**
72 * Hides the metabox by default.
73 *
74 * @since 6.0.0 Make static.
75 *
76 * @param string[] $hidden An array of IDs of meta boxes hidden by default.
77 */
78 public static function hideMetaBox($hidden)
79 {
80 $hidden[] = 'beyondwords__inspect';
81 return $hidden;
82 }
83
84 /**
85 * Adds the meta box container for the Classic Editor.
86 *
87 * The Block Editor UI is handled using JavaScript.
88 *
89 * @since 6.0.0 Make static.
90 *
91 * @param string $postType
92 */
93 public static function addMetaBox($postType)
94 {
95 $postTypes = SettingsUtils::getCompatiblePostTypes();
96
97 if (! in_array($postType, $postTypes)) {
98 return;
99 }
100
101 add_meta_box(
102 'beyondwords__inspect',
103 __('BeyondWords', 'speechkit') . ': ' . __('Inspect', 'speechkit'),
104 [self::class, 'renderMetaBoxContent'],
105 $postType,
106 'advanced',
107 'low',
108 [
109 '__back_compat_meta_box' => true,
110 ]
111 );
112 }
113
114 /**
115 * Render Meta Box content.
116 *
117 * @since 6.0.0 Make static.
118 *
119 * @param \WP_Post $post The post object.
120 */
121 public static function renderMetaBoxContent($post)
122 {
123 $metadata = PostMetaUtils::getAllBeyondwordsMetadata($post->ID);
124
125 self::postMetaTable($metadata);
126 ?>
127 <button
128 type="button"
129 id="beyondwords__inspect--copy"
130 class="button button-large"
131 style="margin: 10px 0 0;"
132 data-clipboard-text="<?php echo esc_attr(self::getClipboardText($metadata)); ?>"
133 >
134 <?php esc_html_e('Copy', 'speechkit'); ?>
135 <span
136 id="beyondwords__inspect--copy-confirm"
137 style="display: none; margin: 5px 0 0;"
138 class="dashicons dashicons-yes"
139 ></span>
140 </button>
141
142 <button
143 type="button"
144 id="beyondwords__inspect--remove"
145 class="button button-large button-link-delete"
146 style="margin: 10px 0 0; float: right;"
147 >
148 <?php esc_html_e('Remove', 'speechkit'); ?>
149 <span
150 id="beyondwords__inspect--remove"
151 style="display: none; margin: 5px 0 0;"
152 class="dashicons dashicons-yes"
153 ></span>
154 </button>
155
156 <?php
157 }
158
159 /**
160 * Render Meta Box table.
161 *
162 * @param array $metadata The metadata returned by has_meta.
163 *
164 * @since v3.0.0
165 * @since v3.9.0 Change $postMetaKeys param to $metadata, to support meta_ids.
166 * @since 6.0.0 Make static.
167 */
168 public static function postMetaTable($metadata)
169 {
170 if (! is_array($metadata)) {
171 return;
172 }
173 ?>
174 <div id="postcustomstuff">
175 <table id="inspect-table">
176 <thead>
177 <tr>
178 <th class="left"><?php esc_html_e('Name', 'speechkit'); ?></th>
179 <th><?php esc_html_e('Value', 'speechkit'); ?></th>
180 </tr>
181 </thead>
182 <tbody id="inspect-table-list">
183 <?php
184 foreach ($metadata as $item) :
185 if (
186 ! is_array($item) ||
187 ! array_key_exists('meta_id', $item) ||
188 ! array_key_exists('meta_key', $item) ||
189 ! array_key_exists('meta_value', $item)
190 ) {
191 continue;
192 }
193
194 $metaId = $item['meta_id'] ?: $item['meta_key'];
195 $metaKey = $item['meta_key'];
196 $metaValue = self::formatPostMetaValue($item['meta_value']);
197 ?>
198 <tr id="beyondwords-inspect-<?php echo esc_attr($metaId); ?>" class="alternate">
199 <td class="left">
200 <label
201 class="screen-reader-text"
202 for="beyondwords-inspect-<?php echo esc_attr($metaId); ?>-key"
203 >
204 <?php esc_html_e('Key', 'speechkit'); ?>
205 </label>
206 <input
207 id="beyondwords-inspect-<?php echo esc_attr($metaId); ?>-key"
208 type="text"
209 size="20"
210 value="<?php echo esc_attr($metaKey); ?>"
211 readonly
212 />
213 </td>
214 <td>
215 <label
216 class="screen-reader-text"
217 for="beyondwords-inspect-<?php echo esc_attr($metaId); ?>-value"
218 >
219 <?php esc_html_e('Value', 'speechkit'); ?>
220 </label>
221 <textarea
222 id="beyondwords-inspect-<?php echo esc_attr($metaId); ?>-value"
223 rows="2"
224 cols="30"
225 data-beyondwords-metavalue="true"
226 readonly
227 ><?php echo esc_html($metaValue); ?></textarea>
228 </td>
229 </tr>
230 <?php
231 endforeach;
232
233 wp_nonce_field('beyondwords_delete_content', 'beyondwords_delete_content_nonce');
234 ?>
235 <input
236 type="hidden"
237 id="beyondwords_delete_content"
238 name="beyondwords_delete_content"
239 value="1"
240 disabled
241 />
242 </tbody>
243 </table>
244 </div>
245 <?php
246 }
247
248 /**
249 * Format post meta value.
250 *
251 * @param mixed $value The metadata value.
252 *
253 * @since 3.9.0
254 * @since 6.0.0 Make static.
255 */
256 public static function formatPostMetaValue($value)
257 {
258 if (is_numeric($value) || is_string($value)) {
259 return $value;
260 }
261
262 return wp_json_encode($value);
263 }
264
265 /**
266 * Get Clipboard Text.
267 *
268 * @param array $metadata Post metadata.
269 *
270 * @since 3.0.0
271 * @since 3.9.0 Output all post meta data from the earlier has_meta() call instead of
272 * the previous multiple get_post_meta() calls.
273 * @since 6.0.0 Make static.
274 *
275 * @return string
276 */
277 public static function getClipboardText($metadata)
278 {
279 $lines = [];
280
281 foreach ($metadata as $m) {
282 $lines[] = $m['meta_key'] . "\r\n" . self::formatPostMetaValue($m['meta_value']);
283 }
284
285 $lines[] = "=== " . __('Copied using the Classic Editor', 'speechkit') . " ===\r\n\r\n";
286
287 return implode("\r\n\r\n", $lines);
288 }
289
290 /**
291 * Runs when a post is saved.
292 *
293 * If "Remove" has been pressed in the Classic Editor we set the `beyondwords_delete_content`
294 * custom field. At a later priority we check for this custom field and if it's set
295 * we make a DELETE request to the BeyondWords REST API, keeping WordPress and the
296 * REST API in sync.
297 *
298 * If we don't perform a DELETE REST API request to keep them in sync then the
299 * API will respond with a "source_id is already in use" error message whenver we
300 * attempt to regenerate audio for a post that has audio content "Removed" in
301 * WordPress but still exists in the REST API.
302 *
303 * @since 4.0.7
304 * @since 6.0.0 Make static.
305 *
306 * @param int $postId The ID of the post being saved.
307 */
308 public static function save($postId)
309 {
310 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
311 return $postId;
312 }
313
314 // "save_post" can be triggered at other times, so verify this request came from the our component
315 if (
316 ! isset($_POST['beyondwords_delete_content_nonce']) ||
317 ! wp_verify_nonce(
318 sanitize_key($_POST['beyondwords_delete_content_nonce']),
319 'beyondwords_delete_content'
320 )
321 ) {
322 return $postId;
323 }
324
325 if (isset($_POST['beyondwords_delete_content'])) {
326 // Set the flag - the DELETE request is performed at a later priority
327 update_post_meta($postId, 'beyondwords_delete_content', '1');
328 }
329
330 return $postId;
331 }
332
333 /**
334 * REST API init.
335 *
336 * Register REST API routes.
337 *
338 * @since 6.0.0 Make static.
339 **/
340 public static function restApiInit()
341 {
342 register_rest_route('beyondwords/v1', '/projects/(?P<projectId>[0-9]+)/content/(?P<beyondwordsId>[a-zA-Z0-9\-]+)', [ // phpcs:ignore Generic.Files.LineLength.TooLong
343 'methods' => \WP_REST_Server::READABLE,
344 'callback' => [self::class, 'restApiResponse'],
345 'permission_callback' => fn() => current_user_can('edit_posts'),
346 ]);
347 }
348
349 /**
350 * REST API response.
351 *
352 * Fetches a content object from the BeyondWords REST API.
353 *
354 * @since 6.0.0 Make static.
355 *
356 * @param \WP_REST_Request $request The REST request object.
357 *
358 * @return \WP_REST_Response
359 **/
360 public static function restApiResponse(\WP_REST_Request $request)
361 {
362 $projectId = $request['projectId'] ?? '';
363 $beyondwordsId = $request['beyondwordsId'] ?? ''; // Can be either contentId or sourceId
364
365 if (! is_numeric($projectId)) {
366 return rest_ensure_response(
367 new \WP_Error(
368 400,
369 __('Invalid Project ID', 'speechkit'),
370 ['projectId' => $projectId]
371 )
372 );
373 }
374
375 if (empty($beyondwordsId)) {
376 return rest_ensure_response(
377 new \WP_Error(
378 400,
379 __('Invalid Content ID', 'speechkit'),
380 ['beyondwordsId' => $beyondwordsId]
381 )
382 );
383 }
384
385 $response = ApiClient::getContent($beyondwordsId, $projectId);
386
387 // Check for REST API connection errors.
388 if (is_wp_error($response)) {
389 return rest_ensure_response(
390 new \WP_Error(
391 500,
392 __('Could not connect to BeyondWords API', 'speechkit'),
393 $response->get_error_data()
394 )
395 );
396 }
397
398 $code = wp_remote_retrieve_response_code($response);
399 $body = wp_remote_retrieve_body($response);
400
401 // Check for REST API response errors.
402 if ($code < 200 || $code >= 300) {
403 return rest_ensure_response(
404 new \WP_Error(
405 $code,
406 /* translators: %d is replaced with the error code. */
407 sprintf(__('BeyondWords REST API returned error code %d', 'speechkit'), $code),
408 [
409 'body' => $body,
410 ]
411 )
412 );
413 }
414
415 $data = json_decode($body, true);
416
417 // Check for REST API JSON response.
418 if (! is_array($data)) {
419 return rest_ensure_response(
420 new \WP_Error(
421 500,
422 __('Invalid response from BeyondWords API', 'speechkit')
423 )
424 );
425 }
426
427 // Return the project ID in the response.
428 $data['project_id'] = $projectId;
429
430 return rest_ensure_response($data);
431 }
432 }
433