Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
69.37% covered (warning)
69.37%
77 / 111
50.00% covered (danger)
50.00%
4 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
BulkEdit
69.37% covered (warning)
69.37%
77 / 111
50.00% covered (danger)
50.00%
4 / 8
73.25
0.00% covered (danger)
0.00%
0 / 1
 init
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 bulkEditCustomBox
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
3
 saveBulkEdit
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
9.03
 generateAudioForPosts
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 deleteAudioForPosts
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
30
 bulkActionsEdit
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 handleBulkGenerateAction
96.67% covered (success)
96.67%
29 / 30
0.00% covered (danger)
0.00%
0 / 1
8
 handleBulkDeleteAction
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3declare(strict_types=1);
4
5/**
6 * BeyondWords Bulk Edit.
7 *
8 * Text Domain: beyondwords
9 *
10 * @package Beyondwords\Wordpress
11 * @author  Stuart McAlpine <stu@beyondwords.io>
12 * @since   3.0.0
13 */
14
15namespace Beyondwords\Wordpress\Component\Posts\BulkEdit;
16
17use Beyondwords\Wordpress\Core\Core;
18use Beyondwords\Wordpress\Core\CoreUtils;
19use Beyondwords\Wordpress\Component\Settings\SettingsUtils;
20use Beyondwords\Wordpress\Plugin;
21
22/**
23 * BulkEdit
24 *
25 * @since 3.0.0
26 */
27class BulkEdit
28{
29    /**
30     * Init.
31     *
32     * @since 4.0.0
33     */
34    public function init()
35    {
36        add_action('bulk_edit_custom_box', array($this, 'bulkEditCustomBox'), 10, 2);
37        add_action('wp_ajax_save_bulk_edit_beyondwords', array($this, 'saveBulkEdit'));
38
39        add_action('wp_loaded', function () {
40            $postTypes = SettingsUtils::getCompatiblePostTypes();
41
42            if (is_array($postTypes)) {
43                foreach ($postTypes as $postType) {
44                    add_filter("bulk_actions-edit-{$postType}", array($this, 'bulkActionsEdit'), 10, 1);
45                    add_filter("handle_bulk_actions-edit-{$postType}", array($this, 'handleBulkDeleteAction'), 10, 3);
46                    add_filter("handle_bulk_actions-edit-{$postType}", array($this, 'handleBulkGenerateAction'), 10, 3);
47                }
48            }
49        });
50    }
51
52    /**
53     * Adds the meta box container.
54     */
55    public function bulkEditCustomBox($columnName, $postType)
56    {
57        if ($columnName !== 'beyondwords') {
58            return;
59        }
60
61        $postTypes = SettingsUtils::getCompatiblePostTypes();
62
63        if (! in_array($postType, $postTypes)) {
64            return;
65        }
66
67        wp_nonce_field('beyondwords_bulk_edit_nonce', 'beyondwords_bulk_edit');
68
69        ?>
70        <fieldset class="inline-edit-col-right">
71            <div class="inline-edit-col">
72                <div class="inline-edit-group wp-clearfix">
73                    <label class="alignleft">
74                        <span class="title"><?php esc_html_e('BeyondWords', 'speechkit'); ?></span>
75                        <select name="beyondwords_generate_audio">
76                            <option value="-1"><?php esc_html_e('— No change —', 'speechkit'); ?></option>
77                            <option value="generate"><?php esc_html_e('Generate audio', 'speechkit'); ?></option>
78                            <option value="delete"><?php esc_html_e('Delete audio', 'speechkit'); ?></option>
79                        </select>
80                    </label>
81                </div>
82            </div>
83        </fieldset>
84        <?php
85    }
86
87    /**
88     * Save Bulk Edit updates.
89     *
90     * @link https://rudrastyh.com/wordpress/bulk-edit.html
91     */
92    public function saveBulkEdit()
93    {
94        /*
95         * We need to verify this came from the our screen and with proper authorization,
96         * because save_post can be triggered at other times.
97         */
98        if (
99            ! isset($_POST['beyondwords_bulk_edit_nonce']) ||
100            ! wp_verify_nonce(sanitize_key($_POST['beyondwords_bulk_edit_nonce']), 'beyondwords_bulk_edit')
101        ) {
102            wp_nonce_ays('');
103        }
104
105        if (! isset($_POST['beyondwords_bulk_edit']) || ! isset($_POST['post_ids'])) {
106            return [];
107        }
108
109        if (is_array($_POST['post_ids']) && count($_POST['post_ids'])) {
110            $postIds = array_map('intval', $_POST['post_ids']);
111            $postIds = array_filter($postIds);
112
113            switch ($_POST['beyondwords_bulk_edit']) {
114                case 'generate':
115                    return $this->generateAudioForPosts($postIds);
116                    break;
117                case 'delete':
118                    return $this->deleteAudioForPosts($postIds);
119                    break;
120            }
121        }
122
123        return [];
124    }
125
126    public function generateAudioForPosts($postIds)
127    {
128        if (! is_array($postIds)) {
129            return false;
130        }
131
132        $updatedPostIds = [];
133
134        foreach ($postIds as $postId) {
135            if (! get_post_meta($postId, 'beyondwords_content_id', true)) {
136                update_post_meta($postId, 'beyondwords_generate_audio', '1');
137            }
138            $updatedPostIds[] = $postId;
139        }
140
141        return $updatedPostIds;
142    }
143
144    /**
145     * @SuppressWarnings(PHPMD.LongVariable)
146     */
147    public function deleteAudioForPosts($postIds)
148    {
149        global $beyondwords_wordpress_plugin;
150
151        if (! is_array($postIds)) {
152            return false;
153        }
154
155        $updatedPostIds = [];
156
157        $response = $beyondwords_wordpress_plugin->core->batchDeleteAudioForPosts($postIds);
158
159        if (! $response) {
160            throw new \Exception(esc_html__('Error while bulk deleting audio. Please contact support with reference BULK-NO-RESPONSE.', 'speechkit')); // phpcs:ignore Generic.Files.LineLength.TooLong
161        }
162
163        // Now process all posts
164        $keys = CoreUtils::getPostMetaKeys('all');
165
166        foreach ($response as $postId) {
167            foreach ($keys as $key) {
168                delete_post_meta($postId, $key);
169            }
170            $updatedPostIds[] = $postId;
171        }
172
173        return $updatedPostIds;
174    }
175
176    /**
177     *
178     */
179    public function bulkActionsEdit($bulk_array)
180    {
181        $bulk_array['beyondwords_generate_audio'] = __('Generate audio', 'speechkit');
182        $bulk_array['beyondwords_delete_audio']   = __('Delete audio', 'speechkit');
183
184        return $bulk_array;
185    }
186
187    /**
188     * @SuppressWarnings(PHPMD.LongVariable)
189     */
190    public function handleBulkGenerateAction($redirect, $doaction, $objectIds)
191    {
192        if ($doaction !== 'beyondwords_generate_audio') {
193            return $redirect;
194        }
195
196        global $beyondwords_wordpress_plugin;
197
198        // Remove query args
199        $args = [
200            'beyondwords_bulk_generated',
201            'beyondwords_bulk_deleted',
202            'beyondwords_bulk_failed',
203            'beyondwords_bulk_error',
204        ];
205
206        $redirect = remove_query_arg($args, $redirect);
207
208        // Order batch by Post ID asc
209        sort($objectIds);
210
211        $generated = 0;
212        $failed    = 0;
213
214        try {
215            // Update all custom fields before attempting any processing
216            foreach ($objectIds as $postId) {
217                update_post_meta($postId, 'beyondwords_generate_audio', '1');
218            }
219
220            // Now process all posts
221            foreach ($objectIds as $postId) {
222                if (
223                    $beyondwords_wordpress_plugin instanceof Plugin
224                    && $beyondwords_wordpress_plugin->core instanceof Core
225                ) {
226                    $response = $beyondwords_wordpress_plugin->core->generateAudioForPost($postId);
227
228                    if ($response) {
229                        $generated++;
230                    } else {
231                        $failed++;
232                    }
233                } else {
234                    throw new \Exception(esc_html__('Error while bulk generating audio. Please contact support with reference BULK-NO-PLUGIN.', 'speechkit')); // phpcs:ignore Generic.Files.LineLength.TooLong
235                }
236            }
237        } catch (\Exception $e) {
238            $redirect = add_query_arg('beyondwords_bulk_error', $e->getMessage(), $redirect);
239        }
240
241        // Add $generated & $failed query args into redirect
242        $redirect = add_query_arg('beyondwords_bulk_generated', $generated, $redirect);
243        $redirect = add_query_arg('beyondwords_bulk_failed', $failed, $redirect);
244
245        // Add nonce to redirect url
246        $nonce = wp_create_nonce('beyondwords_bulk_edit_result');
247        $redirect = add_query_arg('beyondwords_bulk_edit_result_nonce', $nonce, $redirect);
248
249        return $redirect;
250    }
251
252    /**
253     *
254     */
255    public function handleBulkDeleteAction($redirect, $doaction, $objectIds)
256    {
257        if ($doaction !== 'beyondwords_delete_audio') {
258            return $redirect;
259        }
260
261        // Remove query args
262        $args = [
263            'beyondwords_bulk_generated',
264            'beyondwords_bulk_deleted',
265            'beyondwords_bulk_failed',
266            'beyondwords_bulk_error',
267        ];
268
269        $redirect = remove_query_arg($args, $redirect);
270
271        // Order batch by Post ID asc
272        sort($objectIds);
273
274        $deleted = 0;
275
276        // Handle "Delete audio" bulk action
277        try {
278            $result = $this->deleteAudioForPosts($objectIds);
279
280            $deleted = count($result);
281
282            // Add $deleted query arg into redirect
283            $redirect = add_query_arg('beyondwords_bulk_deleted', $deleted, $redirect);
284        } catch (\Exception $e) {
285            $redirect = add_query_arg('beyondwords_bulk_error', $e->getMessage(), $redirect);
286        }
287
288        // Add $nonce query arg into redirect
289        $nonce = wp_create_nonce('beyondwords_bulk_edit_result');
290        $redirect = add_query_arg('beyondwords_bulk_edit_result_nonce', $nonce, $redirect);
291
292        return $redirect;
293    }
294}