PluginProbe ʕ •ᴥ•ʔ
Really Simple Security – Simple and Performant Security (formerly Really Simple SSL) / 9.4.1
Really Simple Security – Simple and Performant Security (formerly Really Simple SSL) v9.4.1
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 / onboarding / class-onboarding.php
really-simple-ssl / onboarding Last commit date
class-onboarding.php 11 months ago index.php 11 months ago
class-onboarding.php
681 lines
1 <?php
2 defined('ABSPATH') or die();
3 require_once(rsssl_path . 'class-installer.php');
4
5 require_once rsssl_path . 'lib/admin/class-encryption.php';
6 use RSSSL\lib\admin\Encryption;
7
8 class rsssl_onboarding {
9
10 use Encryption;
11
12 private static $_this;
13 function __construct() {
14 if ( isset( self::$_this ) ) {
15 wp_die( sprintf( __( '%s is a singleton class and you cannot create a second instance.', 'really-simple-ssl' ), get_class( $this ) ) );
16 }
17
18 self::$_this = $this;
19 add_action( 'admin_init', array( $this, 'maybe_redirect_to_settings_page'), 40);
20 add_filter("rsssl_run_test", array($this, 'handle_onboarding_request'), 10, 3);
21 add_filter("rsssl_do_action", array($this, 'handle_onboarding_action'), 10, 3);
22
23 }
24
25 static function this() {
26 return self::$_this;
27 }
28
29 public function handle_onboarding_request($response, $test, $data){
30 if ( ! rsssl_user_can_manage() ) {
31 return [];
32 }
33 switch( $test ){
34 case 'activate_ssl':
35 $data['is_rest_request'] = true;
36 $response = RSSSL()->admin->activate_ssl($data);
37 break;
38 case 'activate_ssl_networkwide':
39 $response = RSSSL()->multisite->process_ssl_activation_step();
40 break;
41 default:
42 return $response;
43 }
44
45 return $response;
46 }
47
48 /**
49 * @param $response
50 * @param $action
51 * @param $data
52 *
53 * @return array|bool[]|false|mixed
54 */
55 public function handle_onboarding_action($response, $action, $data){
56 if ( ! rsssl_user_can_manage() ) {
57 return false;
58 }
59 $error = false;
60 $next_action = 'none';
61 switch( $action ){
62 case 'onboarding_data':
63 $response = $this->onboarding_data($data);
64 break;
65 case 'get_modal_status':
66 $response = ["dismissed" => !$this->show_onboarding_modal()];
67 break;
68 case 'dismiss_modal':
69 $this->dismiss_modal($data);
70 break;
71 case 'override_ssl_detection':
72 $response = $this->override_ssl_detection($data);
73 break;
74 case 'install_plugin':
75 require_once(rsssl_path . 'class-installer.php');
76 $plugin = new rsssl_installer(sanitize_title($data['id']));
77 $success = $plugin->download_plugin();
78 $response = [
79 'next_action' => 'activate',
80 'success' => $success
81 ];
82 break;
83 case 'activate':
84 require_once(rsssl_path . 'class-installer.php');
85 $plugin = new rsssl_installer(sanitize_title($data['id']));
86 $success = $plugin->activate_plugin();
87 $response = [
88 'next_action' => 'completed',
89 'success' => $success
90 ];
91 break;
92 case 'update_email':
93 $email = sanitize_email($data['email']);
94 if (is_email($email )) {
95 rsssl_update_option('notifications_email_address', $email );
96 rsssl_update_option('send_notifications_email', 1 );
97 if ( $data['includeTips'] ) {
98 $this->signup_for_mailinglist( $email );
99 }
100 $mailer = new rsssl_mailer();
101 $mailer->send_verification_mail( $email );
102 }
103
104 $response = [
105 'success' => true,
106 ];
107 break;
108 case 'activate_setting':
109 $id = isset($data['id']) ? sanitize_title($data['id']) : false;
110 if ($id==='hardening') {
111 $recommended_ids = $this->get_hardening_fields();
112 foreach ($recommended_ids as $h ){
113 rsssl_update_option($h, 1);
114 }
115 }
116 if ($id === 'vulnerability_detection') {
117 rsssl_update_option('enable_vulnerability_scanner', 1);
118
119 }
120 if ($id === 'two_fa_enabled_roles_totp') {
121 rsssl_update_option('two_fa_enabled_roles_totp', ['administrator']);
122 }
123 $response = [
124 'next_action' => 'completed',
125 'success' => true,
126 ];
127 break;
128
129 }
130 $response['request_success'] = true;
131 return $response;
132 }
133
134 /**
135 * Signup for Tips & Tricks from Really Simple Security
136 *
137 * @param string $email
138 *
139 * @return void
140 */
141 public function signup_for_mailinglist( string $email): void {
142 $license_key = '';
143 if ( defined('rsssl_pro') ) {
144 $license_key = RSSSL()->licensing->license_key();
145 $license_key = $this->decrypt_if_prefixed( $license_key , 'really_simple_ssl_');
146 }
147
148 $api_params = array(
149 'has_premium' => defined('rsssl_pro'),
150 'license' => $license_key,
151 'email' => sanitize_email($email),
152 'domain' => esc_url_raw( site_url() ),
153 );
154 wp_remote_post( 'https://mailinglist.really-simple-ssl.com', array( 'timeout' => 15, 'sslverify' => true, 'body' => $api_params ) );
155 }
156
157 /**
158 * Two possibilities:
159 * - a new install: show activation notice, and process onboarding
160 * - an upgrade to 6. Only show new features.
161 *
162 * @param WP_REST_Request $request
163 *
164 * @return array
165 */
166
167 public function onboarding_data( $data ): array {
168 // "warning", // yellow dot
169 // "error", // red dot
170 // "active" // green dot
171 $refresh = isset( $data['forceRefresh'] ) && $data['forceRefresh'] === true;
172 $nonce = $data['nonce'] ?? false;
173 if ( ! wp_verify_nonce( $nonce, 'rsssl_nonce' ) ) {
174 return [];
175 }
176
177 $steps = [
178 [
179 "id" => 'activate_ssl',
180 "title" => __( "Welcome to Really Simple Security", 'really-simple-ssl' ),
181 "subtitle" => __( "The onboarding wizard will help to configure essential security features in 1 minute! Select your hosting provider to start.", "really-simple-ssl" ),
182 "items" => $this->activate_ssl(),
183 ],
184 [
185 "id" => 'email',
186 "title" => __( "Verify your email", 'really-simple-ssl' ),
187 "subtitle" => __( "Really Simple Security will send email notifications and security warnings from your server. We will send a test email to confirm that email is correctly configured on your site. Look for the confirmation button in the email.", "really-simple-ssl" ),
188 "button" => __( "Save and continue", "really-simple-ssl" ),
189 ],
190 [
191 "id" => 'features',
192 "title" => __( "Essential security", 'really-simple-ssl' ),
193 "subtitle" => $this->features_subtitle(),
194 "items" => $this->recommended_features(),
195 "button" => __( "Enable", "really-simple-ssl" ),
196 ],
197 [
198 "id" => 'activate_license',
199 "title" => __( "Activate your license key", 'really-simple-ssl' ),
200 "subtitle" => '',
201 "items" => [
202 'type' => 'license',
203 ],
204 "button" => __( "Activate", "really-simple-ssl" ),
205 "value" => '',
206 ],
207 [
208 "id" => 'plugins',
209 "title" => __( "We think you will like this", "really-simple-ssl" ),
210 "subtitle" => __( "Really Simple Plugins is also the author of the below privacy-focused plugins including consent management and legal documents!", "really-simple-ssl" ),
211 "items" => $this->plugins(),
212 "button" => __( "Install", "really-simple-ssl" ),
213 ],
214 [
215 "id" => 'pro',
216 "title" => "Really Simple Security Pro",
217 "subtitle" => __( "Heavyweight security features, in a lightweight performant plugin from Really Simple Plugins. Get started with below features and get the latest and greatest updates for peace of mind!", "really-simple-ssl" ),
218 "items" => $this->pro_features(),
219 "button" => __( "Install", "really-simple-ssl" ),
220 ],
221 ];
222
223 // Only add activate_license field when rsssl_pro is defined
224 if ( ! defined( 'rsssl_pro' ) ) {
225 $steps = array_filter( $steps, static function ( $step ) {
226 return ! in_array( $step['id'], [ 'activate_license' ] );
227 } );
228 } else if ( get_option( "rsssl_upgraded_from_free" ) ) {
229 $steps = array_filter( $steps, static function ( $step ) {
230 return ! in_array( $step['id'], [ 'activate_ssl', 'features', 'email', 'plugins' ] );
231 } );
232
233 }
234
235 // Re-order keys to prevent issues after array_filter
236 $steps = array_values( $steps );
237
238 //if the user called with a refresh action, clear the cache
239 if ( $refresh ) {
240 delete_transient( 'rsssl_certinfo' );
241 }
242
243 $data_to_return = [
244 "request_success" => true,
245 "steps" => $steps,
246 "ssl_enabled" => rsssl_get_option( "ssl_enabled" ),
247 "ssl_detection_overridden" => get_option( 'rsssl_ssl_detection_overridden' ),
248 'certificate_valid' => RSSSL()->certificate->is_valid(),
249 "networkwide" => is_multisite() && rsssl_is_networkwide_active(),
250 "network_activation_status" => get_site_option( 'rsssl_network_activation_status' ),
251 'rsssl_upgraded_from_free' => get_option( "rsssl_upgraded_from_free" ),
252 ];
253
254
255 if ( get_option('rsssl_upgraded_from_free' ) ) {
256 delete_option('rsssl_upgraded_from_free' );
257 }
258
259 return $data_to_return;
260
261 }
262
263 /**
264 * Return onboarding items for fresh installs
265 * @return array[]
266 */
267 function activate_ssl (): array
268 {
269 $items = [];
270
271 //if the site url is not yet https, the user may need to login again
272 if ( strpos( site_url(), 'https://') === false ) {
273 $items[] = [
274 "title" => __("You may need to login in again, have your credentials prepared.", "really-simple-ssl"),
275 "status" => "inactive",
276 "id" => "login",
277 ];
278 }
279
280 if ( RSSSL()->certificate->is_valid() ) {
281 $items[] = [
282 "title" => __("An SSL certificate has been detected", "really-simple-ssl"),
283 "status" => "success",
284 "id" => "certificate",
285 ];
286 } else if ( RSSSL()->certificate->detection_failed() ) {
287 $items[] = [
288 "title" => __("Could not test certificate", "really-simple-ssl") . " " . __("Automatic certificate detection is not possible on your server.", "really-simple-ssl"),
289 "status" => "error",
290 "id" => "certificate",
291 ];
292 } else {
293 $items[] = [
294 "title" => __("No SSL certificate has been detected.", "really-simple-ssl") . " " . __("Please refresh the SSL status if a certificate has been installed recently.", "really-simple-ssl"),
295 "status" => "error",
296 "id" => "certificate",
297 ];
298 }
299
300 return $items;
301 }
302
303 /**
304 * Get the list of recommended plugins for the onboarding process.
305 *
306 * This function prepares plugin data for display in the onboarding wizard.
307 * It handles plugin status, actions, and checkbox initialization based on configuration.
308 *
309 * Each plugin can be configured with a 'pre_checked' parameter:
310 * - pre_checked = true: Plugin checkbox starts checked, will be installed/activated by default
311 * - pre_checked = false: Plugin checkbox starts unchecked, requires user selection for installation
312 *
313 * Special action handling:
314 * - Plugins with pre_checked = false are assigned an action of "none" and a default_action of "install_plugin"
315 * - When the user checks these plugins, the frontend component will use the default_action value instead of action
316 *
317 * @access public
318 *
319 * @return array List of plugin items with their status, actions and UI properties
320 */
321 public function plugins(): array {
322 $items = [];
323 $plugins_to_install = [
324 [
325 "slug" => "complianz-gdpr",
326 'constant_premium' => 'cmplz_premium',
327 "title" => "Complianz",
328 "description" => __( "Consent Management as it should be.", "really-simple-ssl" ),
329 "pre_checked" => true,
330 ],
331 [
332 "slug" => "complianz-terms-conditions",
333 'constant_premium' => false,
334 "title" => "Complianz Terms & Conditions",
335 "description" => __( "Terms & Conditions", "really-simple-ssl" ),
336 "pre_checked" => true,
337 ],
338 [
339 "slug" => "simplybook",
340 'constant_premium' => false,
341 "title" => "SimplyBook.me",
342 "description" => __( "Online Booking System", "really-simple-ssl" ),
343 "pre_checked" => false,
344 ]
345 ];
346
347 foreach ( $plugins_to_install as $plugin_info ) {
348 require_once( rsssl_path . 'class-installer.php' );
349 $plugin = new rsssl_installer( sanitize_key( $plugin_info["slug"] ) );
350 $premium_active = $plugin_info['constant_premium'] && defined( $plugin_info['constant_premium'] );
351 $free_active = $plugin->plugin_is_downloaded() && $plugin->plugin_is_activated();
352
353 // Determine whether plugin should be checked or not
354 $is_pre_checked = isset( $plugin_info['pre_checked'] ) ? $plugin_info['pre_checked'] : true;
355
356 if (!$is_pre_checked && !$premium_active && !$free_active) {
357 $action = "none";
358 $default_action = "install_plugin";
359 } else if ($premium_active || $free_active) {
360 $action = "none";
361 } else if (!$plugin->plugin_is_downloaded()) {
362 $action = "install_plugin";
363 } else if ( $plugin->plugin_is_downloaded() && ! $plugin->plugin_is_activated() ) {
364 $action = "activate";
365 } else {
366 $action = "none";
367 }
368
369 $activated = $is_pre_checked;
370
371 $items[] = [
372 "id" => $plugin_info['slug'],
373 "title" => $plugin_info["title"],
374 "description" => $plugin_info["description"],
375 "action" => $action,
376 "activated" => $activated,
377 "current_action" => "none",
378 "default_action" => ($default_action ?? null)
379 ];
380 }
381
382 return $items;
383 }
384
385 /**
386 * Returns onboarding items if user upgraded plugin to 6.0 or SSL is detected
387 * @return array
388 */
389 public function recommended_features(): array
390 {
391 $features = [
392 [
393 "title" => __( "Vulnerability scan", "really-simple-ssl" ),
394 "id" => "vulnerability_detection",
395 "options" => [ "enable_vulnerability_scanner" ],
396 "activated" => true,
397 ],
398 [
399 "title" => __( "Essential WordPress hardening", "really-simple-ssl" ),
400 "id" => "hardening",
401 "options" => $this->get_hardening_fields(),
402 "activated" => true,
403 ],
404 [
405 "title" => __( "E-mail login", "really-simple-ssl" ),
406 "id" => "two_fa",
407 "options" => [ "login_protection_enabled" ],
408 "activated" => true,
409 ],
410 [
411 "title" => __( "Mixed Content Fixer", "really-simple-ssl" ),
412 "id" => "mixed_content_fixer",
413 "options" => [ "mixed_content_fixer" ],
414 "activated" => true,
415 ],
416 ];
417
418 if ( ! defined( 'rsssl_pro' ) ) {
419 $features += [
420 [
421 "title" => __( "Firewall", "really-simple-ssl" ),
422 "id" => "firewall",
423 "premium" => true,
424 "options" => [ "enable_firewall" ],
425 "activated" => true,
426 ],
427 [
428 "title" => __( "Two-Factor Authentication", "really-simple-ssl" ),
429 "id" => "two_fa",
430 "premium" => true,
431 "options" => [ 'login_protection_enabled'],
432 "activated" => true,
433 ],
434 [
435 "title" => __( "Limit Login Attempts", "really-simple-ssl" ),
436 "id" => "limit_login_attempts",
437 "premium" => true,
438 "options" => [ 'enable_limited_login_attempts' ],
439 "activated" => true,
440 ],
441 [
442 "title" => __( "Security Headers", "really-simple-ssl" ),
443 "id" => "advanced_headers",
444 "premium" => true,
445 "options" => [],
446 "activated" => true,
447 ],
448 ];
449 }
450
451 return $features;
452 }
453
454 /**
455 * Returns onboarding items if user upgraded plugin to 6.0 or SSL is detected
456 * @return array
457 */
458 public function pro_features (): array
459 {
460 return [
461 [
462 "title" => __("Firewall", "really-simple-ssl"),
463 "id" => "firewall",
464 "premium" => true,
465 "options" => ['enable_firewall'],
466 "activated" => true,
467 ],
468 [
469 "title" => __("Two-Factor Authentication", "really-simple-ssl"),
470 "id" => "two_fa",
471 "premium" => true,
472 "options" => ['two_fa_enabled_roles_totp'],
473 "value" => ['administrator'],
474 "activated" => true,
475 ],
476 [
477 "title" => __("Limit Login Attempts", "really-simple-ssl"),
478 "id" => "limit_login_attempts",
479 "premium" => true,
480 "options" => ['enable_limited_login_attempts'],
481 "activated" => true,
482 ],
483 [
484 "title" => __("Security Headers", "really-simple-ssl"),
485 "id" => "advanced_headers",
486 "premium" => true,
487 "options" => [ 'upgrade_insecure_requests',
488 'x_content_type_options',
489 'hsts',
490 ['x_xss_protection' => 'zero'],
491 'x_content_type_options',
492 ['x_frame_options' => 'SAMEORIGIN'],
493 ['referrer_policy' => 'strict-origin-when-cross-origin'],
494 ['csp_frame_ancestors' => 'self'],
495 ],
496 "activated" => true,
497 ],
498 [
499 "title" => __("Vulnerability Measures", "really-simple-ssl"),
500 "id" => "vulnerability_measures",
501 "options" => ["enable_vulnerability_scanner", "measures_enabled"],
502 "activated" => true,
503 ],
504 [
505 "title" => __("Advanced WordPress Hardening", "really-simple-ssl"),
506 "id" => "advanced_hardening",
507 "premium" => true,
508 "options" => [ 'change_debug_log_location', 'disable_http_methods' ],
509 "activated" => true,
510 ],
511 [
512 "title" => __("Strong Password policy", "really-simple-ssl"),
513 "id" => "password_security",
514 "options" => ['enforce_password_security_enabled', 'enable_hibp_check'],
515 "activated" => true,
516 ],
517 ];
518 }
519
520 /**
521 * Toggle modal status
522 *
523 * @param array $data
524 *
525 * @return void
526 */
527 public function dismiss_modal($data): void
528 {
529 if (!rsssl_user_can_manage()) return;
530 $dismiss = $data['dismiss'] ?? false;
531 update_option("rsssl_onboarding_dismissed", (bool) $dismiss, false);
532 }
533
534 public function maybe_redirect_to_settings_page(): void
535 {
536 if ( get_transient('rsssl_redirect_to_settings_page' ) ) {
537 delete_transient('rsssl_redirect_to_settings_page' );
538 if ( !RSSSL()->admin->is_settings_page() ) {
539 wp_redirect( add_query_arg(array('page' => 'really-simple-security'), rsssl_admin_url() ) );
540 exit;
541 }
542 }
543 }
544
545 /**
546 * Check if any of the recommended features has been disabled
547 * @return bool
548 */
549 public function all_recommended_hardening_features_enabled(){
550 $recommended_ids = $this->get_hardening_fields();
551 foreach ($recommended_ids as $h ){
552 if ( rsssl_get_option($h)!=1 ) {
553 return false;
554 }
555 }
556 return true;
557 }
558
559 public function get_hardening_fields(): array {
560 $fields = rsssl_fields(false);
561 //get all fields that are recommended
562 $recommended = array_filter($fields, function($field){
563 return isset($field['recommended']) && $field['recommended'];
564 });
565 //get all id's from this array
566 return array_map( static function($field){
567 return $field['id'];
568 }, $recommended);
569 }
570
571 public function onboarding_rest_route() {
572 register_rest_route( 'reallysimplessl/v1', 'onboarding', array(
573 'methods' => 'GET',
574 'callback' => array($this, 'onboarding_data'),
575 'permission_callback' => function () {
576 return rsssl_user_can_manage();
577 }
578 ) );
579 }
580
581
582 /**
583 * Update SSL detection overridden option
584 */
585
586 public function override_ssl_detection($data) {
587 if ( ! rsssl_user_can_manage() ) {
588 return false;
589 }
590 $override_ssl = isset($data['overrideSSL']) ? $data['overrideSSL']===true : false;
591 if ($override_ssl) {
592 update_option('rsssl_ssl_detection_overridden', true, false );
593 } else {
594 delete_option('rsssl_ssl_detection_overridden' );
595 }
596 return ['success'=>true];
597 }
598
599 /**
600 * Logic if the activation notice should be shown
601 */
602
603 public function show_onboarding_modal(): bool
604 {
605 if ( get_option("rsssl_onboarding_dismissed") ) {
606 return false;
607 }
608
609 //ensure the checks have been run
610 if ( !RSSSL()->admin->configuration_loaded ) {
611 RSSSL()->admin->detect_configuration();
612 }
613
614 if ( RSSSL()->admin->do_wpconfig_loadbalancer_fix() && !RSSSL()->admin->wpconfig_has_fixes() ) {
615 return false;
616 }
617
618 //for multisite environments, we check if the activation process was started but not completed.
619 if ( is_multisite() && RSSSL()->multisite->ssl_activation_started_but_not_completed() ){
620 return true;
621 }
622
623 $is_upgrade = get_option('rsssl_show_onboarding');
624 if ( rsssl_get_option('ssl_enabled') && !$is_upgrade ) {
625 return false;
626 }
627
628 if ( defined( "RSSSL_DISMISS_ACTIVATE_SSL_NOTICE" ) && RSSSL_DISMISS_ACTIVATE_SSL_NOTICE ) {
629 return false;
630 }
631
632 //don't show in our Let's Encrypt wizard
633 if ( isset( $_GET['letsencrypt'] ) ) {
634 return false;
635 }
636
637 if ( ! RSSSL()->admin->wpconfig_ok() ) {
638 return false;
639 }
640
641 if ( ! rsssl_user_can_manage() ) {
642 return false;
643 }
644
645 return true;
646 }
647
648 /**
649 * @return void
650 *
651 * Maybe reset onboarding modal
652 */
653 public function reset_onboarding(): void
654 {
655 //ensure onboarding triggers again so user gets to enter the license on reload.
656 update_option( "rsssl_show_onboarding", true, false );
657 update_option( "rsssl_onboarding_dismissed", false, false );
658 update_option( "rsssl_upgraded_from_free", true, false );
659 }
660
661 /**
662 * @return string|null
663 *
664 * Generate notice based on Pro being installed or not
665 */
666 public function features_subtitle(): ?string
667 {
668 $notice = __( "Instantly configure these essential features.", "really-simple-ssl" );
669
670 if ( ! defined('rsssl_pro') ) {
671 $notice .= ' ' . sprintf(
672 __( "Please %sconsider upgrading to Pro%s to enjoy all simple and performant security features.", "really-simple-ssl" ),
673 '<a href="https://really-simple-ssl.com/pro?mtm_campaign=security&mtm_source=free&mtm_content=upgrade" target="_blank">',
674 '</a>'
675 );
676 }
677
678 return $notice;
679 }
680
681 }