Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.74% covered (success)
83.74%
103 / 123
22.22% covered (danger)
22.22%
2 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Inspect
83.74% covered (success)
83.74%
103 / 123
22.22% covered (danger)
22.22%
2 / 9
33.87
0.00% covered (danger)
0.00%
0 / 1
 init
55.56% covered (danger)
55.56%
5 / 9
0.00% covered (danger)
0.00%
0 / 1
3.79
 adminEnqueueScripts
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 hideMetaBox
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 addMetaBox
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
3.00
 renderMetaBoxContent
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
1
 postMetaTable
95.24% covered (success)
95.24%
40 / 42
0.00% covered (danger)
0.00%
0 / 1
8
 formatPostMetaValue
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 getClipboardText
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 save
81.82% covered (success)
81.82%
9 / 11
0.00% covered (danger)
0.00%
0 / 1
6.22
1<?php
2
3declare(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
13namespace Beyondwords\Wordpress\Component\Post\Panel\Inspect;
14
15use Beyondwords\Wordpress\Component\Post\PostMetaUtils;
16use Beyondwords\Wordpress\Component\Settings\SettingsUtils;
17
18/**
19 * Inspect
20 *
21 * @since 3.0.0
22 */
23class Inspect
24{
25    /**
26     * Constructor
27     */
28    public function init()
29    {
30        add_action('admin_enqueue_scripts', array($this, 'adminEnqueueScripts'));
31        add_action("add_meta_boxes", array($this, 'addMetaBox'));
32
33        add_filter('default_hidden_meta_boxes', array($this, 'hideMetaBox'));
34
35        add_action('wp_loaded', function () {
36            $postTypes = SettingsUtils::getCompatiblePostTypes();
37
38            if (is_array($postTypes)) {
39                foreach ($postTypes as $postType) {
40                    add_action("save_post_{$postType}", array($this, 'save'), 5);
41                }
42            }
43        });
44    }
45
46    /**
47     * Enqueue JS for Inspect feature.
48     */
49    public function adminEnqueueScripts($hook)
50    {
51        // Only enqueue for Post screens
52        if ($hook === 'post.php' || $hook === 'post-new.php') {
53            wp_enqueue_script(
54                'beyondwords-inspect',
55                BEYONDWORDS__PLUGIN_URI . 'src/Component/Post/Panel/Inspect/js/inspect.js',
56                array('jquery'),
57                BEYONDWORDS__PLUGIN_VERSION,
58                true
59            );
60        }
61    }
62
63    /**
64     * Hides the metabox by default.
65     *
66     * @param string[] $hidden An array of IDs of meta boxes hidden by default.
67     */
68    public function hideMetaBox($hidden)
69    {
70        $hidden[] = 'beyondwords__inspect';
71        return $hidden;
72    }
73
74    /**
75     * Adds the meta box container for the Classic Editor.
76     *
77     * The Block Editor UI is handled using JavaScript.
78     *
79     * @param string $postType
80     */
81    public function addMetaBox($postType)
82    {
83        $postTypes = SettingsUtils::getCompatiblePostTypes();
84
85        if (is_array($postTypes) && ! in_array($postType, $postTypes)) {
86            return;
87        }
88
89        add_meta_box(
90            'beyondwords__inspect',
91            __('BeyondWords', 'speechkit') . ': ' . __('Inspect', 'speechkit'),
92            array($this, 'renderMetaBoxContent'),
93            $postType,
94            'advanced',
95            'low',
96            [
97                '__back_compat_meta_box' => true,
98            ]
99        );
100    }
101
102    /**
103     * Render Meta Box content.
104     *
105     * @param WP_Post $post The post object.
106     */
107    public function renderMetaBoxContent($post)
108    {
109        $metadata = PostMetaUtils::getAllBeyondwordsMetadata($post->ID);
110
111        $this->postMetaTable($metadata);
112        ?>
113        <button
114            type="button"
115            id="beyondwords__inspect--copy"
116            class="button button-large"
117            style="margin: 10px 0 0;"
118            data-clipboard-text="<?php echo esc_attr($this->getClipboardText($metadata)); ?>"
119        >
120            <?php esc_html_e('Copy', 'speechkit'); ?>
121            <span
122                id="beyondwords__inspect--copy-confirm"
123                style="display: none; margin: 5px 0 0;"
124                class="dashicons dashicons-yes"
125            ></span>
126        </button>
127
128        <button
129            type="button"
130            id="beyondwords__inspect--remove"
131            class="button button-large button-link-delete"
132            style="margin: 10px 0 0; float: right;"
133        >
134            <?php esc_html_e('Remove', 'speechkit'); ?>
135            <span
136                id="beyondwords__inspect--remove"
137                style="display: none; margin: 5px 0 0;"
138                class="dashicons dashicons-yes"
139            ></span>
140        </button>
141
142        <?php
143    }
144
145    /**
146     * Render Meta Box table.
147     *
148     * @param array   $metadata The metadata returned by has_meta.
149     *
150     * @since v3.0.0
151     * @since v3.9.0 Change $postMetaKeys param to $metadata, to support meta_ids.
152     */
153    public function postMetaTable($metadata)
154    {
155        if (! is_array($metadata)) {
156            return;
157        }
158        ?>
159        <div id="postcustomstuff">
160            <table id="inspect-table">
161                <thead>
162                    <tr>
163                        <th class="left"><?php esc_html_e('Name', 'speechkit'); ?></th>
164                        <th><?php esc_html_e('Value', 'speechkit'); ?></th>
165                    </tr>
166                </thead>
167                <tbody id="inspect-table-list">
168                    <?php
169                    foreach ($metadata as $item) :
170                        if (
171                            ! is_array($item) ||
172                            ! array_key_exists('meta_id', $item) ||
173                            ! array_key_exists('meta_key', $item) ||
174                            ! array_key_exists('meta_value', $item)
175                        ) {
176                            continue;
177                        }
178
179                        $metaId    = $item['meta_id'] ? $item['meta_id'] : $item['meta_key'];
180                        $metaKey   = $item['meta_key'];
181                        $metaValue = $this->formatPostMetaValue($item['meta_value']);
182                        ?>
183                        <tr id="beyondwords-inspect-<?php echo esc_attr($metaId); ?>" class="alternate">
184                            <td class="left">
185                                <label
186                                    class="screen-reader-text"
187                                    for="beyondwords-inspect-<?php echo esc_attr($metaId); ?>-key"
188                                >
189                                    <?php esc_html_e('Key', 'speechkit'); ?>
190                                </label>
191                                <input
192                                    id="beyondwords-inspect-<?php echo esc_attr($metaId); ?>-key"
193                                    type="text"
194                                    size="20"
195                                    value="<?php echo esc_attr($metaKey); ?>"
196                                    readonly
197                                />
198                            </td>
199                            <td>
200                                <label
201                                    class="screen-reader-text"
202                                    for="beyondwords-inspect-<?php echo esc_attr($metaId); ?>-value"
203                                >
204                                    <?php esc_html_e('Value', 'speechkit'); ?>
205                                </label>
206                                <textarea
207                                    id="beyondwords-inspect-<?php echo esc_attr($metaId); ?>-value"
208                                    rows="2"
209                                    cols="30"
210                                    data-beyondwords-metavalue="true"
211                                    readonly
212                                ><?php echo esc_html($metaValue); ?></textarea>
213                            </td>
214                        </tr>
215                        <?php
216                    endforeach;
217
218                    wp_nonce_field('beyondwords_delete_content', 'beyondwords_delete_content_nonce');
219                    ?>
220                    <input
221                        type="hidden"
222                        id="beyondwords_delete_content"
223                        name="beyondwords_delete_content"
224                        value="1"
225                        disabled
226                    />
227                </tbody>
228            </table>
229        </div>
230        <?php
231    }
232
233    /**
234     * Format post meta value.
235     *
236     * @param mixed $value The metadata value.
237     *
238     * @since v3.9.0
239     */
240    public function formatPostMetaValue($value)
241    {
242        if (is_numeric($value) || is_string($value)) {
243            return $value;
244        }
245
246        return wp_json_encode($value);
247    }
248
249    /**
250     * Get Clipboard Text.
251     *
252     * @param array $metadata Post metadata.
253     *
254     * @since 3.0.0
255     * @since 3.9.0 Output all post meta data from the earlier has_meta() call instead of
256     *              the previous multiple get_post_meta() calls.
257     *
258     * @return string
259     */
260    public function getClipboardText($metadata)
261    {
262        $lines = [];
263
264        foreach ($metadata as $m) {
265            $lines[] = $m['meta_key'] . "\r\n" . $this->formatPostMetaValue($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     * If "Remove" has been pressed in the Classic Editor we set the `beyondwords_delete_content`
277     * custom field. At a later priority we check for this custom field and if it's set
278     * we make a DELETE request to the BeyondWords REST API, keeping WordPress and the
279     * REST API in sync.
280     *
281     * If we don't perform a DELETE REST API request to keep them in sync then the
282     * API will respond with a "source_id is already in use" error message whenver we
283     * attempt to regenerate audio for a post that has audio content "Removed" in
284     * WordPress but still exists in the REST API.
285     *
286     * @since 4.0.7
287     *
288     * @param int $postId The ID of the post being saved.
289     */
290    public function save($postId)
291    {
292        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
293            return $postId;
294        }
295
296        // "save_post" can be triggered at other times, so verify this request came from the our component
297        if (
298            ! isset($_POST['beyondwords_delete_content_nonce']) ||
299            ! wp_verify_nonce(
300                sanitize_key($_POST['beyondwords_delete_content_nonce']),
301                'beyondwords_delete_content'
302            )
303        ) {
304            return $postId;
305        }
306
307        if (isset($_POST['beyondwords_delete_content'])) {
308            // Set the flag - the DELETE request is performed at a later priority
309            update_post_meta($postId, 'beyondwords_delete_content', '1');
310        }
311
312        return $postId;
313    }
314}