Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
48.15% covered (danger)
48.15%
39 / 81
50.00% covered (danger)
50.00%
4 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
SettingsUtils
48.15% covered (danger)
48.15%
39 / 81
50.00% covered (danger)
50.00%
4 / 8
46.37
0.00% covered (danger)
0.00%
0 / 1
 getConsideredPostTypes
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
1
 getCompatiblePostTypes
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getIncompatiblePostTypes
85.71% covered (success)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
2.01
 hasApiCreds
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 hasValidApiConnection
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 validateApiConnection
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
20
 colorInput
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 addSettingsErrorMessage
85.71% covered (success)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
1<?php
2// phpcs:disable Generic.Files.LineLength.TooLong
3
4declare(strict_types=1);
5
6namespace Beyondwords\Wordpress\Component\Settings;
7
8use Beyondwords\Wordpress\Core\ApiClient;
9use Beyondwords\Wordpress\Core\Environment;
10use Beyondwords\Wordpress\Core\Request;
11
12/**
13 * BeyondWords Settings Utilities.
14 *
15 * @package    Beyondwords
16 * @subpackage Beyondwords/includes
17 * @author     Stuart McAlpine <stu@beyondwords.io>
18 * @since      3.5.0
19 */
20class SettingsUtils
21{
22    /**
23     * Get the post types BeyondWords will consider for compatibility.
24     *
25     * We don't consider many of the core built-in post types for compatibity
26     * because they don't support the features we need such as titles, body,
27     * custom fields, etc.
28     *
29     * @since 3.7.0
30     * @since 4.5.0 Renamed from getAllowedPostTypes to getConsideredPostTypes.
31     * @since 4.6.2 Exclude wp_font_face and wp_font_family from considered post types.
32     *
33     * @static
34     *
35     * @return string[] Array of post type names.
36     **/
37    public static function getConsideredPostTypes()
38    {
39        $postTypes = get_post_types();
40
41        $skip = [
42            'attachment',
43            'custom_css',
44            'customize_changeset',
45            'nav_menu_item',
46            'oembed_cache',
47            'revision',
48            'user_request',
49            'wp_block',
50            'wp_font_face',
51            'wp_font_family',
52            'wp_template',
53            'wp_template_part',
54            'wp_global_styles',
55            'wp_navigation',
56        ];
57
58        // Remove the skipped post types
59        $postTypes = array_diff($postTypes, $skip);
60
61        return array_values($postTypes);
62    }
63
64    /**
65     * Get the post types that are compatible with BeyondWords.
66     *
67     * - Start with the considered post types
68     * - Allow publishers to filter the list
69     * - Filter again, removing any that are incompatible
70     *
71     * @since 3.0.0
72     * @since 3.2.0 Removed $output parameter to always output names, never objects.
73     * @since 3.2.0 Added `beyondwords_post_types` filter.
74     * @since 3.5.0 Moved from Core\Utils to Component\Settings\SettingsUtils.
75     * @since 3.7.0 Refactored forbidden/allowed/supported post type methods to improve site health debugging info.
76     * @since 4.5.0 Renamed from getSupportedPostTypes to getCompatiblePostTypes.
77     * @since 5.0.0 Remove beyondwords_post_types filter.
78     *
79     * @static
80     *
81     * @return string[] Array of post type names.
82     **/
83    public static function getCompatiblePostTypes()
84    {
85        $postTypes = SettingsUtils::getConsideredPostTypes();
86
87        /**
88         * Filters the post types supported by BeyondWords.
89         *
90         * This defaults to all registered post types with 'custom-fields' in their 'supports' array.
91         *
92         * If any of the supplied post types do not support custom fields then they will be removed
93         * from the array.
94         *
95         * @since 3.3.3 Introduced as beyondwords_post_types
96         * @since 4.3.0 Renamed from beyondwords_post_types to beyondwords_settings_post_types.
97         *
98         * @param string[] The post types supported by BeyondWords.
99         */
100        $postTypes = apply_filters('beyondwords_settings_post_types', $postTypes);
101
102        // Remove incompatible post types
103        $postTypes = array_diff($postTypes, SettingsUtils::getIncompatiblePostTypes());
104
105        return array_values($postTypes);
106    }
107
108    /**
109     * Get the post types that are incompatible with BeyondWords.
110     *
111     * The requirements are:
112     * - Must support Custom Fields.
113     *
114     * @since 4.5.0
115     *
116     * @static
117     *
118     * @return string[] Array of post type names.
119     **/
120    public static function getIncompatiblePostTypes()
121    {
122        $postTypes = SettingsUtils::getConsideredPostTypes();
123
124        // Filter the array, removing unsupported post types
125        $postTypes = array_filter($postTypes, function ($postType) {
126            if (post_type_supports($postType, 'custom-fields')) {
127                return false;
128            }
129
130            return true;
131        });
132
133        return array_values($postTypes);
134    }
135
136    /**
137     * Do we have both an API Key and Project ID?
138     *
139     * @since  5.2.0
140     * @static
141     *
142     * @return boolean
143     */
144    public static function hasApiCreds()
145    {
146        $projectId = trim(strval(get_option('beyondwords_project_id')));
147        $apiKey    = trim(strval(get_option('beyondwords_api_key')));
148
149        return strlen($projectId) && strlen($apiKey);
150    }
151
152    /**
153     * Do we have a valid REST API connection for the BeyondWords REST API?
154     *
155     * Note that this only whether a valid REST API connection was made when
156     * the API Key was supplied. The API connection may be invalidated at a later
157     * date e.g. if the API Key is revoked.
158     *
159     * @since  5.2.0
160     * @static
161     *
162     * @return boolean
163     */
164    public static function hasValidApiConnection()
165    {
166        return boolval(get_option('beyondwords_valid_api_connection'));
167    }
168
169    /**
170     * Validate the BeyondWords REST API connection.
171     *
172     * @since 5.0.0
173     * @since 5.2.0 Moved from Sync class into SettingsUtils class.
174     * @static
175     *
176     * @return boolean
177     **/
178    public static function validateApiConnection()
179    {
180        // This may have been left over from previous versions
181        delete_transient('beyondwords_validate_api_connection');
182
183        // Assume invalid connection
184        delete_option('beyondwords_valid_api_connection');
185
186        $projectId = get_option('beyondwords_project_id');
187        $apiKey    = get_option('beyondwords_api_key');
188
189        if (! $projectId || ! $apiKey) {
190            return false;
191        }
192
193        // Use ApiClient::callApi directly to access HTTP response code
194        $response = ApiClient::callApi(
195            new Request('GET', sprintf('%s/projects/%d', Environment::getApiUrl(), $projectId))
196        );
197
198        $statusCode = wp_remote_retrieve_response_code($response);
199
200        if ($statusCode === 200) {
201            update_option('beyondwords_valid_api_connection', gmdate(\DateTime::ATOM), false);
202            wp_cache_set('beyondwords_sync_to_wordpress', ['all'], 'beyondwords', 60);
203            return true;
204        }
205
206        // Cancel any syncs
207        wp_cache_delete('beyondwords_sync_to_wordpress', 'beyondwords');
208
209        $debug = sprintf(
210            '<code>%s</code>: <code>%s</code>',
211            wp_remote_retrieve_response_code($response),
212            wp_remote_retrieve_body($response)
213        );
214
215        self::addSettingsErrorMessage(
216            sprintf(
217                /* translators: %s is replaced with the BeyondWords REST API response debug data */
218                __(
219                    'We were unable to validate your BeyondWords REST API connection.<br />Please check your project ID and API key, save changes, and contact us for support if this message remains.<br /><br />BeyondWords REST API Response:<br />%s', // phpcs:ignore Generic.Files.LineLength.TooLong
220                    'speechkit'
221                ),
222                $debug,
223            ),
224            'Settings/ValidApiConnection'
225        );
226
227        return false;
228    }
229
230    /**
231     * A color input.
232     *
233     * @since  5.0.0
234     * @static
235     *
236     * @param string $label Content for the `<label>`
237     * @param string $name  `name` attribute for the `<input />`
238     * @param string $value `value` attribute for the `<input />`
239     *
240     * @return string
241     */
242    public static function colorInput($label, $name, $value)
243    {
244        ?>
245        <div class="color-input">
246            <label>
247                    <?php echo esc_attr($label); ?>
248            </label>
249            <output
250                for="<?php echo esc_attr($name); ?>"
251                style="background: <?php echo esc_attr($value); ?>; margin-right: 0.25rem;"
252            ></output>
253            <input
254                name="<?php echo esc_attr($name); ?>"
255                type="text"
256                value="<?php echo esc_attr($value); ?>"
257                class="small-text"
258                oninput="this.previousElementSibling.style.background = 'transparent'; this.previousElementSibling.style.background = `${this.value}`"
259            />
260        </div>
261        <?php
262    }
263
264    /**
265     * Add settings error message.
266     *
267     * @since 5.2.0
268     * @static
269     *
270     * @param string $message The error message.
271     * @param string $errorId The error ID.
272     *
273     * @return void
274     **/
275    public static function addSettingsErrorMessage($message, $errorId = '')
276    {
277        $errors = wp_cache_get('beyondwords_settings_errors', 'beyondwords');
278
279        if (empty($errors)) {
280            $errors = [];
281        }
282
283        if (empty($errorId)) {
284            $errorId = bin2hex(random_bytes(8));
285        }
286
287        $errors[$errorId] = $message;
288
289        wp_cache_set('beyondwords_settings_errors', $errors, 'beyondwords');
290    }
291}