PluginProbe
FluentSnippets – High-Performance Code Snippets, Header & Footer Code, Custom CSS & PHP Code Manager / trunk
FluentSnippets – High-Performance Code Snippets, Header & Footer Code, Custom CSS & PHP Code Manager vtrunk
10.56 1.2.1 10 10.1 10.2 10.3 10.31 10.32 10.33 10.34 10.50 10.51 10.52 10.53 10.55 9.0 9.0.1 9.4 trunk 1.0.0 1.1 1.2
easy-code-manager / app / Http / Controllers / SettingsController.php

SettingsController.php in FluentSnippets – High-Performance Code Snippets, Header & Footer Code, Custom CSS & PHP Code Manager trunk, at app/Http/Controllers/SettingsController.php

384 lines 12.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentSnippets\App\Http\Controllers;
4
5
6 use FluentSnippets\App\Helpers\Arr;
7 use FluentSnippets\App\Helpers\Helper;
8 use FluentSnippets\App\Model\Snippet;
9
10 class SettingsController
11 {
12 public static function getSettings(\WP_REST_Request $request)
13 {
14 if ($restricted = self::denyUnlessCanManageSettings()) {
15 return $restricted;
16 }
17
18 $config = Helper::getIndexedConfig();
19
20 // enable_line_wrap defaulted to 'yes' here while saveSettings() and the editor
21 // bootstrap in AdminMenuHandler::render() both defaulted to 'no'. The editor is
22 // what actually decides the behaviour, so the settings screen was showing the
23 // toggle on while wrapping was off. Aligned to 'no'; the editor is unchanged.
24 $defaults = [
25 'auto_disable' => 'yes',
26 'auto_publish' => 'no',
27 'remove_on_uninstall' => 'no',
28 'enable_line_wrap' => 'no'
29 ];
30
31 if (!$config || !is_array($config) || empty($config['meta'])) {
32 $config = $defaults;
33 } else {
34 $config = \FluentSnippets\App\Helpers\Arr::only($config['meta'], array_keys($defaults));
35 $config = array_filter($config);
36 }
37
38 $settings = wp_parse_args($config, $defaults);
39
40 return [
41 'settings' => $settings,
42 'is_standalone' => defined('FLUENT_SNIPPETS_RUNNING_MU'),
43 'secret_url' => Helper::getSafeModeUrl()
44 ];
45 }
46
47 /**
48 * Issue a new Safe Mode URL and invalidate the one before it.
49 *
50 * Behind denyUnlessCanChangeSettings() like every other write here: the key lives in
51 * the generated index.php, so replacing it is a file modification.
52 */
53 public static function regenerateSecretUrl(\WP_REST_Request $request)
54 {
55 if ($restricted = self::denyUnlessCanChangeSettings()) {
56 return $restricted;
57 }
58
59 $url = Helper::regenerateSecretKey();
60
61 if (is_wp_error($url)) {
62 return $url;
63 }
64
65 return [
66 'message' => __('A new Safe Mode URL has been generated. The previous one no longer works.', 'easy-code-manager'),
67 'secret_url' => $url
68 ];
69 }
70
71 public static function saveSettings(\WP_REST_Request $request)
72 {
73 if ($restricted = self::denyUnlessCanChangeSettings()) {
74 return $restricted;
75 }
76
77 $settings = $request->get_param('settings');
78
79 if (!is_array($settings)) {
80 return new \WP_Error('invalid_settings', 'Invalid settings');
81 }
82
83 $defaults = [
84 'auto_disable' => 'yes',
85 'auto_publish' => 'no',
86 'remove_on_uninstall' => 'no',
87 'enable_line_wrap' => 'no'
88
89 ];
90
91 $settings = Arr::only($settings, array_keys($defaults));
92 $settings = array_filter($settings);
93
94 $config = Helper::getIndexedConfig();
95
96 if (!$config) {
97 Helper::cacheSnippetIndex();
98 }
99
100 $config = Helper::getIndexedConfig();
101
102 if (!$config) {
103 return new \WP_Error('invalid_config', 'Config file could not be generated');
104 }
105
106 $config['meta']['auto_disable'] = sanitize_text_field($settings['auto_disable']);
107 $config['meta']['auto_publish'] = sanitize_text_field($settings['auto_publish']);
108 $config['meta']['remove_on_uninstall'] = sanitize_text_field($settings['remove_on_uninstall']);
109 $config['meta']['enable_line_wrap'] = sanitize_text_field($settings['enable_line_wrap']);
110
111 $config = Helper::saveIndexedConfig($config);
112
113 if (is_wp_error($config)) {
114 return $config;
115 }
116
117 return [
118 'message' => __('Settings has been successfully updated', 'easy-code-manager'),
119 'settings' => $settings
120 ];
121 }
122
123 public static function disableSafeMode(\WP_REST_Request $request)
124 {
125 if ($restricted = self::denyUnlessCanChangeSettings()) {
126 return $restricted;
127 }
128
129 $config = Helper::getIndexedConfig();
130
131 if (!$config) {
132 return new \WP_Error('invalid_config', 'Config file could not be generated');
133 }
134
135 $config['meta']['force_disabled'] = 'no';
136
137 $config = Helper::saveIndexedConfig($config);
138
139 return [
140 'message' => __('Safe mode has been disabled', 'easy-code-manager')
141 ];
142 }
143
144 public static function configStandAloneSystem(\WP_REST_Request $request)
145 {
146 if ($restricted = self::denyUnlessCanChangeSettings()) {
147 return $restricted;
148 }
149
150 $isEnable = $request->get_param('enable') == 'yes';
151
152 // $isEnable is already a bool; the old `$isEnable == 'yes'` compared a bool to a
153 // string and happened to work under loose comparison (L8).
154 if ($isEnable) {
155 $result = Helper::enableStandAlone();
156 $message = __('Standalone mode has been activated', 'easy-code-manager');
157 } else {
158 $message = __('Standalone mode has been deactivated', 'easy-code-manager');
159 $result = Helper::disableStandAlone();
160 }
161
162 if (is_wp_error($result)) {
163 return $result;
164 }
165
166 return [
167 'message' => $message,
168 'is_standalone' => defined('FLUENT_SNIPPETS_RUNNING_MU'),
169 ];
170 }
171
172 /**
173 * Guard for reading the settings surface: plugin-wide behaviour, safe mode,
174 * standalone mode, and the kill-switch URL. Stricter than
175 * SnippetsController::denyUnlessCanAuthorSnippets() on purpose — it also wants
176 * manage_options.
177 *
178 * Reading is all this covers. Everything that saves goes through
179 * denyUnlessCanChangeSettings() below, so the settings screen still opens on a site
180 * where file modifications are off — the toggles are just not yours to move.
181 */
182 private static function denyUnlessCanManageSettings()
183 {
184 if (current_user_can('unfiltered_html') && current_user_can('manage_options')) {
185 return false;
186 }
187
188 return new \WP_Error('invalid_request', 'You do not have permission to perform this action. Required Permission: unfiltered_html & manage_options');
189 }
190
191 /**
192 * Guard for changing any of it.
193 *
194 * Every setting here ends up written to a file in wp-content — the index config, the
195 * must-use plugin that standalone mode installs — so this is a write in the sense
196 * DISALLOW_FILE_MODS means, and install_plugins is the capability that says so.
197 */
198 private static function denyUnlessCanChangeSettings()
199 {
200 if ($restricted = self::denyUnlessCanManageSettings()) {
201 return $restricted;
202 }
203
204 if (current_user_can('install_plugins')) {
205 return false;
206 }
207
208 return new \WP_Error('invalid_request', 'You do not have permission to perform this action. Required Permission: install_plugins');
209 }
210
211 public static function getRestOptions(\WP_REST_Request $request)
212 {
213 /*
214 * This was the one method here without the guard. It returns titles of draft and
215 * private posts across every public post type, plus every taxonomy term — content
216 * an install_plugins user can already reach, so nothing was exposed that should
217 * not have been. It only serves the condition builder on the snippet edit screen,
218 * which is unusable without the capabilities below anyway.
219 */
220 if ($restricted = self::denyUnlessCanManageSettings()) {
221 return $restricted;
222 }
223
224 $optionKey = $request->get_param('rest_key');
225 $options = [];
226
227 if ($optionKey == 'tax_term_groups') {
228 // Get public taxonomies
229 $taxonomies = get_taxonomies([
230 'public' => true
231 ]);
232
233 $taxonomies = array_keys($taxonomies);
234 $terms = get_terms([
235 'taxonomy' => $taxonomies,
236 'suppress_filter' => true,
237 'hide_empty' => false,
238 'number' => 9000
239 ]);
240
241 foreach ($terms as $term) {
242 if (!isset($formattedTaxGroups[$term->taxonomy])) {
243 $options[$term->taxonomy] = [
244 'label' => $term->taxonomy,
245 'options' => [],
246 ];
247 }
248
249 $options[$term->taxonomy]['options'][] = [
250 'id' => (string)$term->term_id,
251 'title' => $term->name,
252 ];
253 }
254
255 return [
256 'options' => $options,
257 'is_cachable' => true,
258 ];
259 }
260
261 if ($optionKey == 'post_cpt_groups') {
262
263 $publicPostTypes = get_post_types([
264 'public' => true
265 ]);
266
267 $posts = get_posts([
268 'post_type' => array_keys($publicPostTypes),
269 'numberposts' => 200,
270 'post_status' => ['publish', 'draft', 'private'],
271 's' => sanitize_text_field($request->get_param('search')),
272 'search_columns' => ['post_title']
273 ]);
274
275 $requestValues = $request->get_param('values');
276
277 if (empty($requestValues) || !is_array($requestValues)) {
278 $requestValues = [];
279 }
280
281 $includedIds = [];
282
283 foreach ($posts as $post) {
284 if (!isset($options[$post->post_type])) {
285 $options[$post->post_type] = [
286 'label' => ucfirst($post->post_type),
287 'options' => [],
288 ];
289 }
290
291 $includedIds[] = $post->ID;
292
293 $options[$post->post_type]['options'][] = [
294 'id' => (string)$post->ID,
295 'title' => $post->post_title,
296 ];
297 }
298
299 $restIds = array_diff($requestValues, $includedIds);
300 $restIds = array_filter($restIds, 'is_int');
301
302 if ($restIds) {
303 $restPosts = get_posts([
304 'post_type' => 'any',
305 'numberposts' => 200,
306 'post_status' => ['publish', 'draft', 'private'],
307 'post__in' => $restIds
308 ]);
309
310 foreach ($restPosts as $post) {
311 if (!isset($options[$post->post_type])) {
312 $options[$post->post_type] = [
313 'label' => ucfirst($post->post_type),
314 'options' => [],
315 ];
316 }
317
318 $options[$post->post_type]['options'][] = [
319 'id' => (string)$post->ID,
320 'title' => $post->post_title,
321 ];
322 }
323 }
324
325
326 return [
327 'options' => $options,
328 'is_cachable' => false,
329 ];
330 }
331
332 if ($optionKey == 'fluentcrm_tags') {
333 if (!defined('FLUENTCRM')) {
334 return [
335 'options' => [],
336 'is_cachable' => true
337 ];
338 }
339
340 $tags = \FluentCrm\App\Models\Tag::orderBy('title', 'ASC')->get();
341 foreach ($tags as $tag) {
342 $options[] = [
343 'id' => (string)$tag->id,
344 'title' => $tag->title,
345 ];
346 }
347
348 return [
349 'options' => $options,
350 'is_cachable' => true
351 ];
352
353 }
354
355 if ($optionKey == 'fluentcrm_lists') {
356 if (!defined('FLUENTCRM')) {
357 return [
358 'options' => [],
359 'is_cachable' => true
360 ];
361 }
362
363 $tags = \FluentCrm\App\Models\Lists::orderBy('title', 'ASC')->get();
364 foreach ($tags as $tag) {
365 $options[] = [
366 'id' => (string)$tag->id,
367 'title' => $tag->title,
368 ];
369 }
370
371 return [
372 'options' => $options,
373 'is_cachable' => true
374 ];
375
376 }
377
378 return [
379 'options' => $options
380 ];
381
382 }
383 }
384