PluginProbe ʕ •ᴥ•ʔ
Really Simple Security – Simple and Performant Security (formerly Really Simple SSL) / 9.5.11
Really Simple Security – Simple and Performant Security (formerly Really Simple SSL) v9.5.11
9.5.11 9.5.10.1 9.5.10 trunk 9.4.0 9.4.1 9.4.2 9.4.3 9.5.0 9.5.0.1 9.5.0.2 9.5.1 9.5.2 9.5.2.2 9.5.2.3 9.5.3 9.5.3.1 9.5.3.2 9.5.4 9.5.5 9.5.6 9.5.7 9.5.8 9.5.9
really-simple-ssl / settings / settings.php
really-simple-ssl / settings Last commit date
build 4 weeks ago config 4 weeks ago src 4 weeks ago index.php 4 weeks ago settings.php 4 weeks ago webpack.config.js 4 weeks ago webpack.feature.config.js 4 weeks ago
settings.php
1183 lines
1 <?php
2 defined('ABSPATH') or die();
3 /**
4 * Enqueue Gutenberg block assets for backend editor.
5 *
6 * @since 1.0.0
7 */
8
9 require_once(rsssl_path.'settings/config/config.php');
10 require_once(rsssl_path.'settings/config/menu.php');
11 require_once(rsssl_path.'settings/config/disable-fields-filter.php');
12
13 /**
14 * Fix for WPML issue where WPML breaks the rest api by adding a language locale in the url
15 *
16 * @param $url
17 * @param $path
18 * @param $blog_id
19 * @param $scheme
20 *
21 * @return string
22 */
23 function rsssl_fix_rest_url_for_wpml($url, $path, $blog_id, $scheme)
24 {
25 if (strpos($url, 'really-simple-security/v') === false) {
26 return $url;
27 }
28
29 $current_language = false;
30 if (function_exists('icl_register_string')) {
31 $current_language = apply_filters('wpml_current_language', null);
32 }
33
34 if (function_exists('qtranxf_getLanguage')) {
35 $current_language = qtranxf_getLanguage();
36 }
37
38 if ($current_language) {
39 if (strpos($url, '/'.$current_language.'/wp-json/')) {
40 $url = str_replace('/'.$current_language.'/wp-json/', '/wp-json/', $url);
41 }
42
43 // Handle plain permalinks
44 if (strpos($url, '/'.$current_language.'/?rest_route=')) {
45 $url = str_replace('/'.$current_language.'/?rest_route=', '/?rest_route=', $url);
46 }
47 }
48
49 return $url;
50 }
51
52 add_filter('rest_url', 'rsssl_fix_rest_url_for_wpml', 10, 4);
53
54 /**
55 * WordPress doesn't allow for translation of chunks resulting of code splitting.
56 * Several workarounds have popped up in JetPack and Woocommerce: https://developer.wordpress.com/2022/01/06/wordpress-plugin-i18n-webpack-and-composer/
57 * Below is mainly based on the Woocommerce solution, which seems to be the most simple approach. Simplicity is king here.
58 *
59 * @return array
60 */
61 function rsssl_get_chunk_translations($path = 'settings/build' ) {
62 //get all files from the settings/build folder
63 $base_path = rsssl_path . $path;
64 $files = scandir($base_path );
65 $json_translations = [];
66
67 // filter the filenames to get the JavaScript and asset filenames
68 $jsFilename = '';
69 $assetFilename = '';
70 $candidates = [];
71
72 foreach ($files as $file) {
73 // Collect build candidates and pick the newest matching index.<version>.js + asset.php pair.
74 if (strpos($file, 'index.') === 0 && substr($file, -10) === '.asset.php') {
75 $asset_file_path = trailingslashit($base_path) . $file;
76 $asset_data = require $asset_file_path;
77 if (!is_array($asset_data) || empty($asset_data['version']) || !is_string($asset_data['version'])) {
78 continue;
79 }
80
81 $version = $asset_data['version'];
82 $js_file = 'index.' . $version . '.js';
83 $js_file_path = trailingslashit($base_path) . $js_file;
84
85 if (!file_exists($js_file_path)) {
86 continue;
87 }
88
89 $mtime = max((int) @filemtime($asset_file_path), (int) @filemtime($js_file_path));
90 $candidates[] = [
91 'version' => $version,
92 'js' => $js_file,
93 'asset' => $file,
94 'mtime' => $mtime,
95 ];
96 }
97
98 if (strpos($file, '.js') === false) {
99 continue;
100 }
101 $chunk_handle = str_replace('.js', '', $file );
102 //temporarily register the script, so we can get a translations object.
103 wp_register_script( $chunk_handle, plugins_url('build/'.$file, __FILE__), [], true );
104 $language_path = defined('rsssl_pro') ? rsssl_path . 'languages' : false;
105 $localeData = load_script_textdomain( $chunk_handle, 'really-simple-ssl', $language_path );
106
107 if (!empty($localeData)){
108 $json_translations[] = $localeData;
109 }
110 wp_deregister_script( $chunk_handle );
111 }
112
113 // Prefer the newest valid build output.
114 if (!empty($candidates)) {
115 usort($candidates, static function($a, $b) {
116 return ($b['mtime'] ?? 0) <=> ($a['mtime'] ?? 0);
117 });
118 $jsFilename = $candidates[0]['js'];
119 $assetFilename = $candidates[0]['asset'];
120 }
121
122 if (empty($jsFilename) || empty($assetFilename) ) {
123 return [];
124 }
125 $assetFile = require( rsssl_path . trailingslashit( $path ) . $assetFilename );
126 return [
127 'json_translations' => $json_translations,
128 'dependencies' => $assetFile['dependencies'],
129 'version' => $assetFile['version'],
130 'js_file' => $jsFilename,
131 ];
132 }
133
134 function rsssl_plugin_admin_scripts()
135 {
136
137 $js_data = rsssl_get_chunk_translations();
138 // check if the necessary files are found
139 if ( !empty($js_data) ) {
140 $handle = 'rsssl-settings';
141 wp_enqueue_script( $handle);
142 wp_enqueue_script(
143 'rsssl-settings',
144 plugins_url( 'build/' . $js_data['js_file'], __FILE__ ),
145 $js_data['dependencies'],
146 $js_data['version'],
147 true
148 );
149 wp_set_script_translations($handle, 'really-simple-ssl');
150
151 wp_localize_script(
152 'rsssl-settings',
153 'rsssl_settings',
154 apply_filters('rsssl_localize_script', [
155 'json_translations' => $js_data['json_translations'],
156 'menu' => rsssl_menu(),
157 'is_bf' => RSSSL()->admin->is_bf(),
158 'site_url' => get_rest_url(),
159 'plugins_url' => admin_url('update-core.php'),
160 'admin_ajax_url' => add_query_arg(
161 array(
162 'type' => 'errors',
163 'action' => 'rsssl_rest_api_fallback'
164 ),
165 admin_url('admin-ajax.php') ),
166 'dashboard_url' => rsssl_admin_url(),
167 'letsencrypt_url' => rsssl_letsencrypt_wizard_url(),
168 'le_generated_by_rsssl' => rsssl_generated_by_rsssl(),
169 'upgrade_link' => rsssl_link('pro', 'upgrade' ),
170 'ref' => rsssl_get_url_ref(),
171 'plugin_url' => rsssl_url,
172 'network_link' => network_site_url('plugins.php'),
173 'pro_plugin_active' => defined('rsssl_pro'),
174 'networkwide_active' => !is_multisite() || rsssl_is_networkwide_active(),//true for single sites and network wide activated
175 'nonce' => wp_create_nonce('wp_rest'),//to authenticate the logged-in user
176 'rsssl_nonce' => wp_create_nonce('rsssl_nonce'),
177 'wpconfig_fix_required' => RSSSL()->admin->do_wpconfig_loadbalancer_fix() && ! RSSSL()->admin->wpconfig_has_fixes() && ! RSSSL()->admin->uses_bitnami(),
178 'cloudflare' => rsssl_uses_cloudflare(),
179 'email_verified' => rsssl_is_email_verified(),
180 ])
181 );
182 }
183 }
184
185 /**
186 * Check if this server is behind CloudFlare
187 *
188 * @return bool
189 */
190 function rsssl_uses_cloudflare(): bool {
191 return isset( $_SERVER['HTTP_CF_CONNECTING_IP'] );
192 }
193
194 /**
195 * Add SSL menu
196 *
197 * @return void
198 */
199 function rsssl_add_top_level_menu() {
200 if ( ! rsssl_user_can_manage() ) {
201 return;
202 }
203
204 if ( is_multisite() && rsssl_is_networkwide_active() ) {
205 return;
206 }
207
208 $count = RSSSL()->admin->count_plusones();
209 $update_count = $count > 0 ? "<span class='update-plugins rsssl-update-count'><span class='update-count'>$count</span></span>" : "";
210
211 $icon_svg = '<?xml version="1.0" encoding="UTF-8"?>
212 <svg id="rss-menu-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 -15 90 130" width="34" height="34">
213 <defs>
214 <style>.cls-1{fill:#fff;stroke-width:0px;}</style>
215 </defs>
216 <g fill="none" stroke-width="2">
217 <path class="cls-1" d="M72.92,26.6h-13v-9.4c0-7.6-6.1-13.7-13.7-13.7s-13.8,6.1-13.8,13.7v9.4h-13.1v-9.4C19.32,2.4,31.32,-9.6,46.12,-9.6s26.8,12,26.8,26.8v9.4h0Z"/>
218 <rect class="cls-1" x="10.02" y="84.6" width="72.3" height="5.6"/>
219 <path class="cls-1" d="M82.32,82H10.02V31.8c0-2.9,2.3-5.2,5.2-5.2h61.9c2.9,0,5.2,2.3,5.2,5.2V82h0ZM64.62,37.8c-2.2-2.2-5.9-2.2-8.2,0l-15.7,15.3l-4.9-4.9c-2.2-2.2-5.9-2.2-8.2,0l-1.9,1.9c-2.2,2.2-2.2,5.9,0,8.2l8.5,8.5c0.1,0.2,0.3,0.4,0.5,0.6l1.9,1.9l4.2,4l3.5-3.5c0.2-0.1,0.4-0.3,0.6-0.5l1.9-1.9c0.2-0.2,0.4-0.4,0.5-0.6l19.1-18.9c2.2-2.2,2.2-5.9,0-8.2l-1.8-1.9Z"/>
220 </g>
221 </svg>';
222
223 $icon_base64 = 'data:image/svg+xml;base64,' . base64_encode($icon_svg);
224
225 $page_hook_suffix = add_menu_page(
226 __( "Security", "really-simple-ssl" ),
227 __( "Security", "really-simple-ssl" ) . $update_count,
228 'manage_security',
229 'really-simple-security',
230 'rsssl_settings_page',
231 $icon_base64,
232 100 // This will place it near the bottom of the menu
233 );
234
235 add_action( "admin_print_scripts-{$page_hook_suffix}", 'rsssl_plugin_admin_scripts' );
236 // Update the page title to prevent issues with an empty title causing strip_tags deprecation warnings
237 add_action("load-{$page_hook_suffix}", 'rsssl_set_admin_page_title');
238 add_action('admin_head', 'rsssl_override_wordpress_svg_size');
239
240 }
241
242 add_action( 'admin_menu', 'rsssl_add_top_level_menu' );
243
244 function rsssl_override_wordpress_svg_size() {
245 echo '<style>
246 #adminmenu .toplevel_page_really-simple-security div.wp-menu-image.svg {
247 background-size: 23px auto !important;
248 }
249 </style>';
250 }
251
252 /**
253 * @return void
254 *
255 * Set title of RSSSL admin page
256 */
257 function rsssl_set_admin_page_title() {
258 global $title;
259 $title = __( "Security", "really-simple-ssl" );
260 }
261
262 /**
263 * Render the settings page
264 */
265
266 function rsssl_settings_page()
267 {
268 if ( ! rsssl_user_can_manage()) {
269 return;
270 }
271
272 ?>
273 <div id="really-simple-ssl" class="rsssl"></div>
274 <div id="really-simple-ssl-modal"></div>
275 <?php
276 }
277
278 /**
279 * If the rest api is blocked, the code will try an admin ajax call as fall back.
280 *
281 * @return void
282 */
283 add_action('rest_api_init', 'rsssl_settings_rest_route', 10);
284 function rsssl_settings_rest_route()
285 {
286 if (!rsssl_user_can_manage()) {
287 return;
288 }
289
290 register_rest_route('really-simple-security/v1', 'fields/get', array(
291 'methods' => 'GET',
292 'callback' => 'rsssl_rest_api_fields_get',
293 'permission_callback' => function () {
294 return rsssl_user_can_manage();
295 }
296 ));
297
298 register_rest_route('really-simple-security/v1', 'fields/set', array(
299 'methods' => 'POST',
300 'callback' => 'rsssl_rest_api_fields_set',
301 'permission_callback' => function () {
302 return rsssl_user_can_manage();
303 }
304 ));
305
306 register_rest_route('really-simple-security/v1', 'tests/(?P<test>[a-z\_\-]+)', array(
307 'methods' => 'GET',
308 'callback' => 'rsssl_run_test',
309 'permission_callback' => function () {
310 return rsssl_user_can_manage();
311 }
312 ));
313
314 register_rest_route('really-simple-security/v1', 'do_action/(?P<action>[a-z\_\-]+)', array(
315 'methods' => 'POST',
316 'callback' => 'rsssl_do_action',
317 'permission_callback' => function () {
318 return rsssl_user_can_manage();
319 }
320 ));
321
322 }
323
324 /**
325 * Store SSL Labs result
326 * @param array $data
327 *
328 * @return array
329 */
330 function rsssl_store_ssl_labs($data)
331 {
332 if (!rsssl_user_can_manage()) {
333 return [];
334 }
335 update_option('rsssl_ssl_labs_data', $data, false);
336 return [];
337 }
338
339 /**
340 * @param WP_REST_Request $request
341 * @param array|bool $ajax_data
342 *
343 * @return void
344 */
345 function rsssl_do_action($request, $ajax_data = false)
346 {
347 if (!rsssl_user_can_manage()) {
348 return;
349 }
350
351 $action = sanitize_title($request->get_param('action'));
352 $data = $ajax_data !== false ? $ajax_data : $request->get_params();
353
354 $nonce = $data['nonce'];
355 if (!wp_verify_nonce($nonce, 'rsssl_nonce')) {
356 return;
357 }
358 switch ($action) {
359 case 'ssltest_get':
360 $response = ['data' => get_option('rsssl_ssl_labs_data')];
361 break;
362 case 'ssltest_run':
363 $response = rsssl_ssltest_run($data);
364 break;
365 case 'store_ssl_labs':
366 $response = rsssl_store_ssl_labs($data);
367 break;
368 case 'send_test_mail':
369 $mailer = new rsssl_mailer();
370 $response = $mailer->send_test_mail();
371 break;
372 case 'send_verification_mail':
373 $mailer = new rsssl_mailer();
374 $response = $mailer->send_verification_mail( rsssl_get_option('notifications_email_address') );
375 break;
376 case 'plugin_actions':
377 $response = rsssl_plugin_actions($data);
378 break;
379 case 'clear_cache':
380 $response = rsssl_clear_test_caches($data);
381 break;
382 case 'fix':
383 $response = rsssl_fix($data);
384 break;
385 case 'otherpluginsdata':
386 $response = rsssl_other_plugins_data();
387 break;
388 case 'get_roles':
389 $roles = rsssl_get_roles();
390 $response = [];
391 $response['roles'] = $roles;
392 break;
393 case 'get_hosts':
394 $response = [
395 'hosts' => [], // fallback response
396 ];
397 if ( function_exists( 'RSSSL_LE' ) ) {
398 $response['hosts'] = RSSSL_LE()->hosts->getKnownHosts();
399 }
400 break;
401 default:
402 $response = apply_filters("rsssl_do_action", [], $action, $data);
403 }
404
405 // Backward compatibility: some legacy actions returned the payload at the top-level,
406 // while older Settings code expects it under `data`.
407 if (in_array($action, ['hardening_data'], true) && is_array($response) && !isset($response['data'])) {
408 $response = array_merge(['data' => $response], $response);
409 }
410
411 if (is_array($response)) {
412 $response['request_success'] = true;
413 }
414
415 return $response;
416 }
417
418 /**
419 * @param array $data
420 *
421 * @return array
422 */
423 function rsssl_clear_test_caches($data)
424 {
425 if (!rsssl_user_can_manage()) {
426 return [];
427 }
428
429 $cache_id = sanitize_title($data['cache_id']);
430
431 do_action('rsssl_clear_test_caches', $data);
432 return [];
433 }
434
435 /**
436 * @param array $data
437 *
438 * @return array
439 */
440 function rsssl_fix($data)
441 {
442 if (!rsssl_user_can_manage()) {
443 return [];
444 }
445
446 $fix_id = sanitize_title($data['fix_id']);
447 $output = [];
448 $output = apply_filters('rsssl_run_fix', $output, $fix_id);
449 return $output;
450 }
451
452 /**
453 * Process plugin installation or activation actions
454 *
455 * @param array $data
456 *
457 * @return array
458 */
459
460 function rsssl_plugin_actions($data)
461 {
462 if (!rsssl_user_can_manage()) {
463 return [];
464 }
465 $slug = sanitize_title($data['slug']);
466 $action = sanitize_title($data['pluginAction']);
467
468 if (class_exists('rsssl_installer') === false) {
469 require_once( rsssl_path . 'class-installer.php');
470 }
471
472 $installer = new rsssl_installer($slug);
473 if ($action === 'download') {
474 $installer->download_plugin();
475 } elseif ($action === 'activate') {
476 $installer->activate_plugin();
477 }
478
479 return rsssl_other_plugins_data($slug);
480 }
481
482 /**
483 * Run a request to SSL Labs
484 *
485 * @param $data
486 *
487 * @return string
488 */
489 function rsssl_ssltest_run( $data ) {
490 if ( ! rsssl_user_can_manage() ) {
491 return '';
492 }
493 $url = $data['url'];
494 $response = wp_safe_remote_get( $url );
495 $data = wp_remote_retrieve_body( $response );
496 if ( empty( $data ) ) {
497 $data = [ 'errors' => 'Request failed, please try again.' ];
498 }
499
500 return $data;
501 }
502
503 /**
504 * @param WP_REST_Request $request
505 *
506 * @return array
507 */
508 function rsssl_run_test($request, $ajax_data = false)
509 {
510 if (!rsssl_user_can_manage()) {
511 return [];
512 }
513 $nonce = $request->get_param('nonce');
514 if (!wp_verify_nonce($nonce, 'rsssl_nonce')) {
515 return [];
516 }
517 $data = $ajax_data !== false ? $ajax_data : $request->get_params();
518 $test = sanitize_title($request->get_param('test'));
519 $state = $request->get_param('state');
520 $state = $state !== 'undefined' && $state !== 'false' ? $state : false;
521 switch ($test) {
522 case 'progressdata':
523 $response = RSSSL()->progress->get();
524 break;
525 case 'dismiss_task':
526 $response = RSSSL()->progress->dismiss_task($state);
527 break;
528 default:
529 $response = apply_filters("rsssl_run_test", [], $test, $data);
530 }
531 if (is_array($response)) {
532 $response['request_success'] = true;
533 }
534 return $response;
535 }
536
537 /**
538 * Get plugin data for other plugin section
539 * @param string $slug
540 * @return array
541 */
542 function rsssl_other_plugins_data($slug = false)
543 {
544 if (!rsssl_user_can_manage()) {
545 return [];
546 }
547 $plugins = array(
548 [
549 'slug' => 'complianz-gdpr',
550 'constant_premium' => 'cmplz_premium',
551 'wordpress_url' => 'https://wordpress.org/plugins/complianz-gdpr/',
552 'upgrade_url' => 'https://complianz.io/pricing?src=rsssl-plugin',
553 'title' => __("Complianz - Consent Management as it should be", "really-simple-ssl"),
554 ],
555 [
556 'slug' => 'complianz-terms-conditions',
557 'wordpress_url' => 'https://wordpress.org/plugins/complianz-terms-conditions/',
558 'upgrade_url' => 'https://complianz.io?src=rsssl-plugin',
559 'title' => 'Complianz - ' . __("Terms and Conditions", "really-simple-ssl"),
560 ],
561 [
562 'slug' => 'simplybook',
563 'wordpress_url' => 'https://wordpress.org/plugins/simplybook/',
564 'upgrade_url' => 'https://simplybook.me/en/pricing',
565 'title' => 'SimplyBook.me - ' . __("Online Booking System", "really-simple-ssl"),
566 ],
567 );
568
569 if (class_exists('rsssl_installer') === false) {
570 require_once( rsssl_path . 'class-installer.php');
571 }
572
573 foreach ($plugins as $index => $plugin) {
574 $installer = new rsssl_installer($plugin['slug']);
575 if (isset($plugin['constant_premium']) && defined($plugin['constant_premium'])) {
576 $plugins[$index]['pluginAction'] = 'installed';
577 } else if (!$installer->plugin_is_downloaded() && !$installer->plugin_is_activated()) {
578 $plugins[$index]['pluginAction'] = 'download';
579 } else if ($installer->plugin_is_downloaded() && !$installer->plugin_is_activated()) {
580 $plugins[$index]['pluginAction'] = 'activate';
581 } else {
582 if (isset($plugin['constant_premium'])) {
583 $plugins[$index]['pluginAction'] = 'upgrade-to-premium';
584 } else {
585 $plugins[$index]['pluginAction'] = 'installed';
586 }
587 }
588 }
589
590 if ($slug) {
591 foreach ($plugins as $key => $plugin) {
592 if ($plugin['slug'] === $slug) {
593 return $plugin;
594 }
595 }
596 }
597 return ['plugins' => $plugins];
598
599 }
600
601 /**
602 * List of allowed field types
603 * @param $type
604 *
605 * @return string|bool
606 */
607 function rsssl_sanitize_field_type($type) {
608 $types = [
609 'hidden',
610 'license',
611 'database',
612 'checkbox',
613 'password',
614 'radio',
615 'text',
616 'textarea',
617 'number',
618 'email',
619 'select',
620 'host',
621 'permissionspolicy',
622 'learningmode',
623 'mixedcontentscan',
624 'vulnerablemeasures',
625 'LetsEncrypt',
626 'postdropdown',
627 'two_fa_roles',
628 'roles_enabled_dropdown',
629 'roles_dropdown',
630 'captcha',
631 'captcha_key',
632 ];
633 if (in_array($type, $types, true)) {
634 return $type;
635 }
636 // re-moving checkbox as a return type to the end of the function
637 return false;
638 }
639
640 /**
641 * @param WP_REST_Request $request
642 * @param array $ajax_data
643 *
644 * @return array
645 */
646 function rsssl_rest_api_fields_set(WP_REST_Request $request, $ajax_data = false): array
647 {
648 if (!rsssl_user_can_manage()) {
649 return [];
650 }
651
652 $fields = $ajax_data ?: $request->get_json_params();
653 //get the nonce
654 $nonce = false;
655 foreach ($fields as $index => $field) {
656 if (isset($field['nonce'])) {
657 $nonce = $field['nonce'];
658 unset($fields[$index]);
659 }
660 }
661
662 if (!wp_verify_nonce($nonce, 'rsssl_nonce')) {
663 return [];
664 }
665
666 $config_fields = rsssl_fields(false);
667 $config_ids = array_column($config_fields, 'id');
668 foreach ($fields as $index => $field) {
669 if (!isset($field['id'])) {
670 unset($fields[$index]);
671 continue;
672 }
673 $field_id = sanitize_text_field($field['id']);
674 $config_field_index = array_search($field_id, $config_ids);
675 if ($config_field_index === false) {
676 unset($fields[$index]);
677 continue;
678 }
679 $config_field = $config_fields[$config_field_index];
680 $type = isset($config_field['type']) ? rsssl_sanitize_field_type($config_field['type']) : false;
681 if ($type === false) {
682 return [
683 'success' => false,
684 'error' => 'Invalid field type configured for field ' . $field_id,
685 ];
686 }
687 $value = rsssl_sanitize_field($field['value'] ?? false, $type, $field_id);
688 //if an endpoint is defined, we use that endpoint instead
689 if (isset($config_field['data_endpoint'])) {
690 //the updateItemId allows us to update one specific item in a field set.
691 $update_item_id = isset($field['updateItemId']) ? $field['updateItemId'] : false;
692 $action = isset($field['action']) && $field['action'] === 'delete' ? 'delete' : 'update';
693 $endpoint = $config_field['data_endpoint'];
694 if (is_array($endpoint)) {
695 $main = $endpoint[0];
696 $class = $endpoint[1];
697 $function = $endpoint[2];
698 if (function_exists($main)) {
699 $main()->$class->$function($value, $update_item_id, $action);
700 }
701 } else if (function_exists($endpoint)) {
702 $endpoint($value, $update_item_id, $action);
703 }
704
705 unset($fields[$index]);
706 continue;
707 }
708
709 $field['value'] = $value;
710 $field['id'] = $field_id;
711 $field['type'] = $type;
712 $fields[$index] = $field;
713 }
714
715 if (is_multisite() && rsssl_is_networkwide_active()) {
716 $options = get_site_option('rsssl_options', []);
717 } else {
718 $options = get_option('rsssl_options', []);
719 }
720
721 //build a new options array
722 foreach ($fields as $field) {
723 $prev_value = isset($options[$field['id']]) ? $options[$field['id']] : false;
724 do_action("rsssl_before_save_option", $field['id'], $field['value'], $prev_value, $field['type']);
725 $options[$field['id']] = apply_filters("rsssl_fieldvalue", $field['value'], $field['id'], $field['type']);
726 }
727 if (!empty($options)) {
728 if (is_multisite() && rsssl_is_networkwide_active()) {
729 update_site_option('rsssl_options', $options);
730 } else {
731 update_option('rsssl_options', $options);
732 }
733 }
734 RSSSL()->admin->clear_admin_notices_cache();
735 do_action('rsssl_after_saved_fields', $fields );
736 foreach ( $fields as $field ) {
737 do_action( "rsssl_after_save_field", $field['id'], $field['value'], $prev_value, $field['type'] );
738 }
739 return [
740 'success' => true,
741 'progress' => RSSSL()->progress->get(),
742 'fields' => rsssl_fields(true),
743 ];
744 }
745
746 /**
747 * Update a rsssl option
748 * @param string $name
749 * @param mixed $value
750 *
751 * @return void
752 */
753
754 function rsssl_update_option($name, $value)
755 {
756 if (!rsssl_user_can_manage()) {
757 return;
758 }
759 $config_fields = rsssl_fields(false);
760 $config_ids = array_column($config_fields, 'id');
761 $config_field_index = array_search($name, $config_ids);
762 if ($config_field_index === false) {
763 return;
764 }
765
766 $config_field = $config_fields[$config_field_index];
767 $type = $config_field['type'] ?? false;
768 if (!$type) {
769 return;
770 }
771 if (is_multisite() && rsssl_is_networkwide_active()) {
772 $options = get_site_option('rsssl_options', []);
773 } else {
774 $options = get_option('rsssl_options', []);
775 }
776 if (!is_array($options)) $options = [];
777 $prev_value = $options[$name] ?? false;
778 $name = sanitize_text_field($name);
779 $type = rsssl_sanitize_field_type($config_field['type']);
780 $value = rsssl_sanitize_field($value, $type, $name);
781 $value = apply_filters("rsssl_fieldvalue", $value, sanitize_text_field($name), $type);
782 #skip if value wasn't changed
783 if (isset($options[$name]) && $options[$name] === $value) {
784 return;
785 }
786
787 $options[$name] = $value;
788 if ( is_multisite() && rsssl_is_networkwide_active() ) {
789 update_site_option( 'rsssl_options', $options );
790 } else {
791 update_option( 'rsssl_options', $options );
792 }
793 $config_field['value'] = $value;
794 RSSSL()->admin->clear_admin_notices_cache();
795 do_action('rsssl_after_saved_fields',[$config_field] );
796 do_action( "rsssl_after_save_field", $name, $value, $prev_value, $type );
797 }
798
799 /**
800 * Get the rest api fields
801 * @return array
802 */
803 function rsssl_rest_api_fields_get()
804 {
805 if (!rsssl_user_can_manage()) {
806 return [];
807 }
808
809 $output = array();
810 $fields = rsssl_fields();
811 foreach ($fields as $index => $field) {
812 /**
813 * Load data from source
814 */
815 if (isset($field['data_source'])) {
816 $data_source = $field['data_source'];
817 if (is_array($data_source)) {
818 $main = $data_source[0];
819 $class = $data_source[1];
820 $function = $data_source[2];
821 $field['value'] = [];
822 if (function_exists($main)) {
823 $field['value'] = $main()->$class->$function();
824 }
825 } else if (function_exists($field['data_source'])) {
826 $func = $field['data_source'];
827 $field['value'] = $func();
828 }
829 }
830
831 $fields[$index] = $field;
832 }
833
834 $output['fields'] = $fields;
835 $output['request_success'] = true;
836 $output['progress'] = RSSSL()->progress->get();
837 return apply_filters('rsssl_rest_api_fields_get', $output);
838 }
839
840 /**
841 * Sanitize a field
842 *
843 * @param mixed $value
844 * @param string $type
845 * @oaram string $id
846 *
847 * @return array|bool|int|string|void
848 */
849 function rsssl_sanitize_field($value, string $type, string $id)
850 {
851 switch ($type) {
852 case 'checkbox':
853 case 'number':
854 return (int)$value;
855 case 'hidden':
856 return sanitize_title($value);
857 case 'select':
858 case 'host':
859 case 'text':
860 case 'license':
861 case 'password':
862 case 'captcha_key':
863 case 'postdropdown':
864 return sanitize_text_field($value);
865 case 'textarea':
866 return wp_kses($value, array());
867 case 'multicheckbox':
868 if (!is_array($value)) {
869 $value = array($value);
870 }
871 return array_map('sanitize_text_field', $value);
872 case 'email':
873 return sanitize_email($value);
874 case 'url':
875 return esc_url_raw($value);
876 case 'permissionspolicy':
877 return rsssl_sanitize_permissions_policy($value, $type, $id);
878 case 'learningmode':
879 return rsssl_sanitize_datatable($value, $type, $id);
880 case 'mixedcontentscan':
881 return $value;
882 case 'roles_dropdown':
883 case 'roles_enabled_dropdown':
884 case 'two_fa_roles':
885 $value = !is_array($value) ? [] : $value;
886 $roles = rsssl_get_roles();
887 foreach ($value as $index => $role) {
888 if (! in_array( $role, $roles, true ) ) {
889 unset($value[$index]);
890 }
891 }
892 return $value;
893 default:
894 return sanitize_text_field($value);
895 }
896 }
897
898 /**
899 * Dedicated permission policy sanitization
900 *
901 * @param $value
902 * @param $type
903 * @param $field_name
904 *
905 * @return array|false
906 */
907 function rsssl_sanitize_permissions_policy($value, $type, $field_name)
908 {
909 $possible_keys = apply_filters("rsssl_datatable_datatypes_$type", [
910 'id' => 'string',
911 'title' => 'string',
912 'status' => 'boolean',
913 ]);
914 // Datatable array will look something like this, whith 0 the row index, and id, title the col indexes.
915 // [0] => Array
916 // (
917 // [id] => camera
918 // [title] => Camera
919 // [value] => ()
920 // [status] => 1/0
921 // )
922 //)
923 $config_fields = rsssl_fields(false);
924 //check if there is a default available
925 $default = false;
926 foreach ($config_fields as $config_field) {
927 if ($config_field['id'] === $field_name) {
928 $default = isset($config_field['default']) ? $config_field['default'] : false;
929 }
930 }
931
932 $stored_ids = [];
933 if (!is_array($value)) {
934 return $default;
935 } else {
936 foreach ($value as $row_index => $row) {
937 //check if we have invalid values
938 if (is_array($row)) {
939 foreach ($row as $column_index => $row_value) {
940 if ($column_index === 'id' && $row_value === false) {
941 unset($value[$column_index]);
942 }
943 }
944 }
945
946 //has to be an array.
947 if (!is_array($row)) {
948 if (isset($default[$row_index])) {
949 $value[$row_index] = $default[$row_index];
950 } else {
951 unset($value[$row_index]);
952 }
953 }
954
955 foreach ($row as $col_index => $col_value) {
956 if (!isset($possible_keys[$col_index])) {
957 unset($value[$row_index][$col_index]);
958 } else {
959 $datatype = $possible_keys[$col_index];
960 switch ($datatype) {
961 case 'string':
962 $value[$row_index][$col_index] = sanitize_text_field($col_value);
963 break;
964 case 'int':
965 case 'boolean':
966 default:
967 $value[$row_index][$col_index] = intval($col_value);
968 break;
969 }
970 }
971 }
972
973 //Ensure that all required keys are set with at least an empty value
974 foreach ($possible_keys as $key => $data_type) {
975 if (!isset($value[$row_index][$key])) {
976 $value[$row_index][$key] = false;
977 }
978 }
979 }
980 }
981
982 //ensure that there are no duplicate ids
983 foreach ($value as $index => $item) {
984 if (in_array($item['id'], $stored_ids)) {
985 unset($value[$index]);
986 continue;
987 }
988 $stored_ids[] = $item['id'];
989 }
990
991 //if the default contains items not in the setting (newly added), add them.
992 if (count($value) < count($default)) {
993 foreach ($default as $def_row_index => $def_row) {
994 //check if it is available in the array. If not, add
995 if (!in_array($def_row['id'], $stored_ids)) {
996 $value[] = $def_row;
997 }
998 }
999 }
1000 return $value;
1001 }
1002
1003 function rsssl_sanitize_datatable($value, $type, $field_name)
1004 {
1005 $possible_keys = apply_filters("rsssl_datatable_datatypes_$type", [
1006 'id' => 'string',
1007 'title' => 'string',
1008 'status' => 'boolean',
1009 ]);
1010
1011 if (!is_array($value)) {
1012 return false;
1013 } else {
1014 foreach ($value as $row_index => $row) {
1015 //check if we have invalid values
1016 if (is_array($row)) {
1017 foreach ($row as $column_index => $row_value) {
1018 if ($column_index === 'id' && $row_value === false) {
1019 unset($value[$column_index]);
1020 }
1021 }
1022 }
1023
1024 //has to be an array.
1025 if (!is_array($row)) {
1026 unset($value[$row_index]);
1027 }
1028
1029 foreach ($row as $col_index => $col_value) {
1030 if (!isset($possible_keys[$col_index])) {
1031 unset($value[$row_index][$col_index]);
1032 } else {
1033 $datatype = $possible_keys[$col_index];
1034 switch ($datatype) {
1035 case 'string':
1036 $value[$row_index][$col_index] = sanitize_text_field($col_value);
1037 break;
1038 case 'int':
1039 case 'boolean':
1040 default:
1041 $value[$row_index][$col_index] = intval($col_value);
1042 break;
1043 }
1044 }
1045 }
1046
1047 //Ensure that all required keys are set with at least an empty value
1048 foreach ($possible_keys as $key => $data_type) {
1049 if (!isset($value[$row_index][$key])) {
1050 $value[$row_index][$key] = false;
1051 }
1052 }
1053 }
1054 }
1055 return $value;
1056 }
1057
1058
1059 /**
1060 * Check if the server side conditions apply
1061 *
1062 * @param array $conditions
1063 *
1064 * @return bool
1065 */
1066
1067 function rsssl_conditions_apply(array $conditions)
1068 {
1069
1070 $defaults = ['relation' => 'AND'];
1071 $conditions = wp_parse_args($conditions, $defaults);
1072 $relation = $conditions['relation'] === 'AND' ? 'AND' : 'OR';
1073 unset($conditions['relation']);
1074 $condition_applies = true;
1075 foreach ($conditions as $condition => $condition_value) {
1076 $invert = substr($condition, 1) === '!';
1077 $condition = ltrim($condition, '!');
1078
1079 if (is_array($condition_value)) {
1080 $this_condition_applies = rsssl_conditions_apply($condition_value);
1081 } else {
1082 //check if it's a function
1083 if (substr($condition, -2) === '()') {
1084 $func = $condition;
1085 if (preg_match('/(.*)\(\)\-\>(.*)->(.*)/i', $func, $matches)) {
1086 $base = $matches[1];
1087 $class = $matches[2];
1088 $func = $matches[3];
1089 $func = str_replace('()', '', $func);
1090 $this_condition_applies = call_user_func(array($base()->{$class}, $func)) === $condition_value;
1091 } else {
1092 $func = str_replace('()', '', $func);
1093 $this_condition_applies = $func() === $condition_value;
1094 }
1095 } else {
1096 $var = $condition;
1097 if (preg_match('/(.*)\(\)\-\>(.*)->(.*)/i', $var, $matches)) {
1098 $base = $matches[1];
1099 $class = $matches[2];
1100 $var = $matches[3];
1101 $this_condition_applies = $base()->{$class}->_get($var) === $condition_value;
1102 } else {
1103 $this_condition_applies = rsssl_get_option($var) === $condition_value;
1104 }
1105 }
1106
1107 if ($invert) {
1108 $this_condition_applies = !$this_condition_applies;
1109 }
1110
1111 }
1112
1113 if ($relation === 'AND') {
1114 $condition_applies = $condition_applies && $this_condition_applies;
1115 } else {
1116 $condition_applies = $condition_applies || $this_condition_applies;
1117 }
1118 }
1119
1120 return $condition_applies;
1121 }
1122
1123 /**
1124 * Fetch all user roles.
1125 *
1126 * Tries to get roles from cache first. If roles are not in cache, it fetches them and stores them in cache.
1127 *
1128 * @return array An array of roles, each role being an associative array with 'label' and 'value' keys.
1129 */
1130 function rsssl_get_roles( ): array {
1131 if ( ! rsssl_admin_logged_in() ) {
1132 return [];
1133 }
1134
1135 global $wp_roles;
1136
1137 // Try to get roles from cache
1138 $roles = wp_cache_get( 'rsssl_roles' );
1139
1140 // If roles are not in cache, fetch and set cache
1141 if ( ! $roles ) {
1142 // Just return the names, not the capabilities
1143 $roles_names = array_keys( $wp_roles->roles );
1144
1145 // Extract unique role values from the role names
1146 $roles = array_values( array_unique( $roles_names ));
1147 // Set the roles in cache for future use
1148 wp_cache_set( 'rsssl_roles', $roles );
1149 }
1150
1151 return $roles;
1152 }
1153
1154 /**
1155 * @param $response
1156 * @param $user
1157 * @param $request
1158 *
1159 * @return mixed
1160 *
1161 * Add user roles to /users endpoint
1162 */
1163 function rsssl_add_user_role_to_api_response( $response, $user, $request ) {
1164 if ( rsssl_is_logged_in_rest() ) {
1165 $data = $response->get_data();
1166 $data['roles'] = $user->roles;
1167 $response->set_data( $data );
1168 }
1169
1170 return $response;
1171 }
1172 add_filter( 'rest_prepare_user', 'rsssl_add_user_role_to_api_response', 10, 3 );
1173
1174 if ( ! function_exists('rsssl_change_email_status_on_email_change' ) ) {
1175 function rsssl_change_email_status_on_email_change(string $field_id, $new_value, $previous_value) {
1176 if ( $field_id === 'notifications_email_address' && $new_value !== $previous_value ) {
1177 update_option( 'rsssl_email_verification_status', 'email_changed' );
1178 }
1179 }
1180 }
1181
1182 add_filter('rsssl_after_save_field', 'rsssl_change_email_status_on_email_change', 10, 3);
1183