PluginProbe
BeyondWords – AI audio for publishers / 7.0.0
BeyondWords – AI audio for publishers v7.0.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 / editor / components / inspect-panel / class-inspect-panel.php

class-inspect-panel.php in BeyondWords – AI audio for publishers 7.0.0, at src/editor/components/inspect-panel/class-inspect-panel.php

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