Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
48.15% |
39 / 81 |
|
50.00% |
4 / 8 |
CRAP | |
0.00% |
0 / 1 |
SettingsUtils | |
48.15% |
39 / 81 |
|
50.00% |
4 / 8 |
46.37 | |
0.00% |
0 / 1 |
getConsideredPostTypes | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
1 | |||
getCompatiblePostTypes | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getIncompatiblePostTypes | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
2.01 | |||
hasApiCreds | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
hasValidApiConnection | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
validateApiConnection | |
0.00% |
0 / 31 |
|
0.00% |
0 / 1 |
20 | |||
colorInput | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
addSettingsErrorMessage | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
3.03 |
1 | <?php |
2 | // phpcs:disable Generic.Files.LineLength.TooLong |
3 | |
4 | declare(strict_types=1); |
5 | |
6 | namespace Beyondwords\Wordpress\Component\Settings; |
7 | |
8 | use Beyondwords\Wordpress\Core\ApiClient; |
9 | use Beyondwords\Wordpress\Core\Environment; |
10 | use 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 | */ |
20 | class 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 | } |