iThemes Security (formerly Better WP Security) - Version 6.7.0

Version Description

  • New Feature: Introduces the Notification Center, a centralized place to manage and customize email notifications sent by iThemes Security.
  • Enhancement: Updated queries and prepare statements to account for changes to the esc_sql() function in WordPress 4.8.3.
  • Bug Fix: Corrected some Javascript and CSS links not generating correctly on Windows servers.
Download this release

Release Info

Developer chrisjean
Plugin Icon 128x128 iThemes Security (formerly Better WP Security)
Version 6.7.0
Comparing to
See all releases

Code changes from version 6.6.1 to 6.7.0

Files changed (56) hide show
  1. better-wp-security.php +1 -1
  2. core/admin-pages/css/style.css +23 -0
  3. core/admin-pages/js/script.js +84 -11
  4. core/admin-pages/module-settings.php +8 -1
  5. core/admin-pages/page-settings.php +21 -3
  6. core/core.php +21 -1
  7. core/history.txt +19 -0
  8. core/lib.php +49 -236
  9. core/lib/class-itsec-mail.php +255 -34
  10. core/lib/mail-templates/close.html +8 -0
  11. core/lib/mail-templates/file-change-summary.html +40 -0
  12. core/lib/mail-templates/footer-user.html +27 -0
  13. core/lib/mail-templates/footer.html +156 -162
  14. core/lib/mail-templates/header.html +7 -6
  15. core/lib/mail-templates/large-code.html +23 -0
  16. core/lib/mail-templates/list.html +27 -0
  17. core/lib/mail-templates/lockouts-table.html +1 -1
  18. core/lib/mail-templates/table.html +13 -0
  19. core/lockout.php +115 -68
  20. core/logger.php +1 -3
  21. core/modules/404-detection/settings-page.php +4 -2
  22. core/modules/admin-user/validator.php +13 -11
  23. core/modules/backup/class-itsec-backup.php +49 -10
  24. core/modules/brute-force/settings-page.php +4 -1
  25. core/modules/core/setup.php +8 -0
  26. core/modules/database-prefix/utility.php +4 -6
  27. core/modules/file-change/class-itsec-file-change.php +34 -0
  28. core/modules/file-change/scanner.php +66 -97
  29. core/modules/file-change/settings-page.php +0 -8
  30. core/modules/file-change/settings.php +0 -1
  31. core/modules/file-change/validator.php +2 -2
  32. core/modules/global/settings-page.php +0 -31
  33. core/modules/global/settings.php +0 -8
  34. core/modules/global/validator.php +2 -10
  35. core/modules/hide-backend/class-itsec-hide-backend.php +45 -0
  36. core/modules/hide-backend/validator.php +20 -39
  37. core/modules/notification-center/active.php +7 -0
  38. core/modules/notification-center/class-notification-center.php +1079 -0
  39. core/modules/notification-center/css/index.php +1 -0
  40. core/modules/notification-center/css/settings-page.css +42 -0
  41. core/modules/notification-center/index.php +1 -0
  42. core/modules/notification-center/js/index.php +1 -0
  43. core/modules/notification-center/js/settings-page.js +44 -0
  44. core/modules/notification-center/settings-page.php +320 -0
  45. core/modules/notification-center/settings.php +148 -0
  46. core/modules/notification-center/setup.php +123 -0
  47. core/modules/notification-center/validator.php +313 -0
  48. core/modules/strong-passwords/class-itsec-strong-passwords.php +1 -3
  49. core/notify.php +217 -158
  50. core/response.php +51 -9
  51. core/setup.php +4 -4
  52. core/sidebar-widget-active-lockouts.php +1 -1
  53. core/sidebar-widget-temp-whitelist.php +3 -1
  54. core/sync-verbs/itsec-get-lockouts.php +2 -1
  55. history.txt +4 -0
  56. readme.txt +9 -4
better-wp-security.php CHANGED
@@ -6,7 +6,7 @@
6
  * Description: Take the guesswork out of WordPress security. iThemes Security offers 30+ ways to lock down WordPress in an easy-to-use WordPress security plugin.
7
  * Author: iThemes
8
  * Author URI: https://ithemes.com
9
- * Version: 6.6.1
10
  * Text Domain: better-wp-security
11
  * Network: True
12
  * License: GPLv2
6
  * Description: Take the guesswork out of WordPress security. iThemes Security offers 30+ ways to lock down WordPress in an easy-to-use WordPress security plugin.
7
  * Author: iThemes
8
  * Author URI: https://ithemes.com
9
+ * Version: 6.7.0
10
  * Text Domain: better-wp-security
11
  * Network: True
12
  * License: GPLv2
core/admin-pages/css/style.css CHANGED
@@ -384,6 +384,10 @@ body.itsec-modal-open {
384
  border-left-color: #00a0d2;
385
  box-shadow: 1px 1px 0px rgba(0,0,0,.1);
386
  }
 
 
 
 
387
  .itsec-module-card-content .module-actions {
388
  float: none;
389
  margin: 1em 0 0 0;
@@ -597,6 +601,25 @@ body.itsec-modal-open {
597
  background: #fff9ec;
598
  }
599
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
600
  /*
601
  * Security Check
602
  */
384
  border-left-color: #00a0d2;
385
  box-shadow: 1px 1px 0px rgba(0,0,0,.1);
386
  }
387
+ .itsec-module-type-enabled.itsec-module-status--warning .itsec-module-card-content {
388
+ background: #fff8e5;
389
+ border-left-color: #ffb900;
390
+ }
391
  .itsec-module-card-content .module-actions {
392
  float: none;
393
  margin: 1em 0 0 0;
601
  background: #fff9ec;
602
  }
603
 
604
+ .notice.itsec-is-dismissible {
605
+ padding-right: 38px;
606
+ position: relative;
607
+ }
608
+
609
+ .itsec-notification-center-link {
610
+ float: right;
611
+ text-decoration: none;
612
+ font-style: italic;
613
+ font-weight: normal;
614
+ font-size: .8rem;
615
+ }
616
+
617
+ .itsec-notification-center-link::after {
618
+ content: "\f345";
619
+ font-family: Dashicons;
620
+ font-size: .8rem;
621
+ vertical-align: sub;
622
+ }
623
  /*
624
  * Security Check
625
  */
core/admin-pages/js/script.js CHANGED
@@ -14,6 +14,7 @@ var itsecSettingsPage = {
14
 
15
  this.initFilters();
16
  this.initCurrentModule();
 
17
  },
18
 
19
  initFilters: function() {
@@ -236,6 +237,7 @@ var itsecSettingsPage = {
236
  itsecSettingsPage.showErrors( results.errors, results.module, 'open' );
237
  itsecSettingsPage.showErrors( results.warnings, results.module, 'open', 'warning' );
238
  itsecSettingsPage.showMessages( results.messages, results.module, 'open' );
 
239
 
240
  if ( 'grid' === view ) {
241
  $container.find( '.itsec-module-settings-content-container:visible' ).animate( {'scrollTop': 0}, 'fast' );
@@ -246,6 +248,7 @@ var itsecSettingsPage = {
246
  }
247
  } else {
248
  itsecSettingsPage.showMessages( results.messages, results.module, 'closed' );
 
249
 
250
  if ( 'grid' === view ) {
251
  $container.find( '.itsec-module-settings-content-container:visible' ).scrollTop( 0 );
@@ -303,13 +306,16 @@ var itsecSettingsPage = {
303
  container.append( $notice ).addClass( 'visible' );
304
  },
305
 
306
- showMessages: function( messages, module, containerStatus ) {
307
  jQuery.each( messages, function( index, message ) {
308
- itsecSettingsPage.showMessage( message, module, containerStatus );
309
  } );
310
  },
311
 
312
- showMessage: function( message, module, containerStatus ) {
 
 
 
313
  if ( jQuery( '.itsec-module-cards-container' ).hasClass( 'grid' ) ) {
314
  var view = 'grid';
315
  } else {
@@ -328,17 +334,25 @@ var itsecSettingsPage = {
328
  if ( 'closed' === containerStatus || '' === module ) {
329
  var container = jQuery( '#itsec-settings-messages-container' );
330
 
331
- setTimeout( function() {
 
 
 
 
 
332
  container.removeClass( 'visible' );
333
- setTimeout( function() {
334
  container.find( 'div' ).remove();
335
  }, 500 );
336
- }, 4000 );
 
 
337
  } else {
338
  var container = jQuery( '#itsec-module-card-' + module + ' .itsec-module-messages-container' );
339
  }
340
 
341
- var $notice = jQuery( '<div class="notice notice-success fade"><p><strong>' + message + '</strong></p></div>' );
 
342
 
343
  if ( containerStatus === 'open' || module.length ) {
344
  $notice.addClass( 'notice-alt' );
@@ -434,7 +448,7 @@ var itsecSettingsPage = {
434
  var $listClassElement = $module.parents( '.itsec-module-cards-container' ),
435
  $toggleButton = $module.find( '.itsec-toggle-settings' );
436
 
437
- if ( highlight.length ) {
438
  jQuery( 'label[for="' + highlight + '"]', $module ).parents( 'tr' ).addClass( 'itsec-highlighted-setting' );
439
  }
440
 
@@ -447,6 +461,14 @@ var itsecSettingsPage = {
447
  var type = $module.hasClass( 'itsec-module-type-advanced' ) ? 'advanced' : 'recommended';
448
 
449
  window.history.pushState( {module: module}, module, '?page=itsec&module=' + module + '&module_type=' + type );
 
 
 
 
 
 
 
 
450
  },
451
 
452
  toggleSettings: function( e ) {
@@ -698,6 +720,11 @@ var itsecSettingsPage = {
698
 
699
  jQuery( '#itsec-module-filter-enabled .count' ).html( '(' + enabledCount + ')' );
700
  jQuery( '#itsec-module-filter-disabled .count' ).html( '(' + disabledCount + ')' );
 
 
 
 
 
701
  },
702
 
703
  isModuleActive: function( module ) {
@@ -733,13 +760,25 @@ var itsecSettingsPage = {
733
 
734
  itsecSettingsPage.sendAJAXRequest( module, method, data, function( results ) {
735
  if ( results.success && results.response ) {
736
- jQuery( '#itsec-module-card-' + module + ' .itsec-module-settings-content-main' ).html( results.response );
737
- jQuery( '.itsec-settings-toggle' ).trigger( 'change' );
 
 
 
 
 
 
 
 
738
  } else if ( results.errors && results.errors.length > 0 ) {
739
  itsecSettingsPage.showErrors( results.errors, results.module, 'open' );
740
  } else if ( results.warnings && results.warnings.length > 0 ) {
741
  itsecSettingsPage.showErrors( results.warnings, results.module, 'open', 'warning' );
742
  }
 
 
 
 
743
  } );
744
  },
745
 
@@ -779,10 +818,12 @@ var itsecSettingsPage = {
779
 
780
  if ( initialResponse ) {
781
  itsecSettingsPage.showMessages( initialResponse.messages, initialResponse.module, $open ? 'open' : 'closed' );
 
782
  itsecSettingsPage.showErrors( initialResponse.errors, initialResponse.module, $open ? 'open' : 'closed' );
783
  itsecSettingsPage.showErrors( initialResponse.warnings, initialResponse.module, $open ? 'open' : 'closed', 'warning' );
784
  }
785
 
 
786
  itsecSettingsPage.events.trigger( 'modulesReloaded', initialResponse );
787
  } );
788
  },
@@ -828,6 +869,7 @@ var itsecSettingsPage = {
828
  'errors': [],
829
  'warnings': [],
830
  'messages': [],
 
831
  'functionCalls': [],
832
  'redirect': false,
833
  'closeModal': true
@@ -842,6 +884,7 @@ var itsecSettingsPage = {
842
  results.errors = a.errors;
843
  results.warnings = a.warnings;
844
  results.messages = a.messages;
 
845
  results.functionCalls = a.functionCalls;
846
  results.redirect = a.redirect;
847
  results.closeModal = a.closeModal;
@@ -934,7 +977,37 @@ var itsecSettingsPage = {
934
  }
935
  // If the requested parameter doesn't exist, return false
936
  return false;
937
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
938
  };
939
 
940
  jQuery(document).ready(function( $ ) {
14
 
15
  this.initFilters();
16
  this.initCurrentModule();
17
+ this.makeNoticesDismissible();
18
  },
19
 
20
  initFilters: function() {
237
  itsecSettingsPage.showErrors( results.errors, results.module, 'open' );
238
  itsecSettingsPage.showErrors( results.warnings, results.module, 'open', 'warning' );
239
  itsecSettingsPage.showMessages( results.messages, results.module, 'open' );
240
+ itsecSettingsPage.showMessages( results.infos, results.module, 'open', 'info' );
241
 
242
  if ( 'grid' === view ) {
243
  $container.find( '.itsec-module-settings-content-container:visible' ).animate( {'scrollTop': 0}, 'fast' );
248
  }
249
  } else {
250
  itsecSettingsPage.showMessages( results.messages, results.module, 'closed' );
251
+ itsecSettingsPage.showMessages( results.infos, results.module, 'closed', 'info' );
252
 
253
  if ( 'grid' === view ) {
254
  $container.find( '.itsec-module-settings-content-container:visible' ).scrollTop( 0 );
306
  container.append( $notice ).addClass( 'visible' );
307
  },
308
 
309
+ showMessages: function( messages, module, containerStatus, type ) {
310
  jQuery.each( messages, function( index, message ) {
311
+ itsecSettingsPage.showMessage( message, module, containerStatus, type );
312
  } );
313
  },
314
 
315
+ showMessage: function( message, module, containerStatus, type ) {
316
+
317
+ type = type || 'success';
318
+
319
  if ( jQuery( '.itsec-module-cards-container' ).hasClass( 'grid' ) ) {
320
  var view = 'grid';
321
  } else {
334
  if ( 'closed' === containerStatus || '' === module ) {
335
  var container = jQuery( '#itsec-settings-messages-container' );
336
 
337
+ var dismiss = function () {
338
+
339
+ if ( container.is( ':hover' ) ) {
340
+ return setTimeout( dismiss, 2000 );
341
+ }
342
+
343
  container.removeClass( 'visible' );
344
+ setTimeout( function () {
345
  container.find( 'div' ).remove();
346
  }, 500 );
347
+ };
348
+
349
+ setTimeout( dismiss, 4000 );
350
  } else {
351
  var container = jQuery( '#itsec-module-card-' + module + ' .itsec-module-messages-container' );
352
  }
353
 
354
+ var $notice = jQuery( '<div class="notice fade"><p><strong>' + message + '</strong></p></div>' );
355
+ $notice.addClass( 'notice-' + type );
356
 
357
  if ( containerStatus === 'open' || module.length ) {
358
  $notice.addClass( 'notice-alt' );
448
  var $listClassElement = $module.parents( '.itsec-module-cards-container' ),
449
  $toggleButton = $module.find( '.itsec-toggle-settings' );
450
 
451
+ if ( highlight && highlight.length ) {
452
  jQuery( 'label[for="' + highlight + '"]', $module ).parents( 'tr' ).addClass( 'itsec-highlighted-setting' );
453
  }
454
 
461
  var type = $module.hasClass( 'itsec-module-type-advanced' ) ? 'advanced' : 'recommended';
462
 
463
  window.history.pushState( {module: module}, module, '?page=itsec&module=' + module + '&module_type=' + type );
464
+
465
+ var href = $link.attr( 'href' );
466
+
467
+ if ( href && href.length > 1 && href.charAt( 0 ) === '#' ) {
468
+ setTimeout( function () {
469
+ jQuery( '.itsec-module-settings-content-container', '#itsec-module-card-notification-center' ).scrollTo( jQuery( href ), 'swing', { offset: -30 } );
470
+ }, 350 );
471
+ }
472
  },
473
 
474
  toggleSettings: function( e ) {
720
 
721
  jQuery( '#itsec-module-filter-enabled .count' ).html( '(' + enabledCount + ')' );
722
  jQuery( '#itsec-module-filter-disabled .count' ).html( '(' + disabledCount + ')' );
723
+
724
+
725
+ itsecSettingsPage.showErrors( results.warnings, results.module, 'closed', 'warning' );
726
+ itsecSettingsPage.showMessages( results.messages, results.module, 'closed' );
727
+ itsecSettingsPage.showMessages( results.infos, results.module, 'closed', 'info' );
728
  },
729
 
730
  isModuleActive: function( module ) {
760
 
761
  itsecSettingsPage.sendAJAXRequest( module, method, data, function( results ) {
762
  if ( results.success && results.response ) {
763
+ var $card = jQuery( '#itsec-module-card-' + module );
764
+ var isHidden = $card.is( ':hidden' );
765
+
766
+ jQuery( '.itsec-module-settings-content-main', $card ).html( results.response );
767
+
768
+ if ( isHidden ) {
769
+ $card.hide();
770
+ } else {
771
+ jQuery( '.itsec-settings-toggle' ).trigger( 'change' );
772
+ }
773
  } else if ( results.errors && results.errors.length > 0 ) {
774
  itsecSettingsPage.showErrors( results.errors, results.module, 'open' );
775
  } else if ( results.warnings && results.warnings.length > 0 ) {
776
  itsecSettingsPage.showErrors( results.warnings, results.module, 'open', 'warning' );
777
  }
778
+
779
+ itsecSettingsPage.events.trigger( 'moduleReloaded', module );
780
+
781
+ itsecSettingsPage.makeNoticesDismissible();
782
  } );
783
  },
784
 
818
 
819
  if ( initialResponse ) {
820
  itsecSettingsPage.showMessages( initialResponse.messages, initialResponse.module, $open ? 'open' : 'closed' );
821
+ itsecSettingsPage.showMessages( initialResponse.infos, initialResponse.module, $open ? 'open' : 'closed', 'info' );
822
  itsecSettingsPage.showErrors( initialResponse.errors, initialResponse.module, $open ? 'open' : 'closed' );
823
  itsecSettingsPage.showErrors( initialResponse.warnings, initialResponse.module, $open ? 'open' : 'closed', 'warning' );
824
  }
825
 
826
+ itsecSettingsPage.makeNoticesDismissible();
827
  itsecSettingsPage.events.trigger( 'modulesReloaded', initialResponse );
828
  } );
829
  },
869
  'errors': [],
870
  'warnings': [],
871
  'messages': [],
872
+ 'infos': [],
873
  'functionCalls': [],
874
  'redirect': false,
875
  'closeModal': true
884
  results.errors = a.errors;
885
  results.warnings = a.warnings;
886
  results.messages = a.messages;
887
+ results.infos = a.infos;
888
  results.functionCalls = a.functionCalls;
889
  results.redirect = a.redirect;
890
  results.closeModal = a.closeModal;
977
  }
978
  // If the requested parameter doesn't exist, return false
979
  return false;
980
+ },
981
+
982
+ // Make notices dismissible
983
+ makeNoticesDismissible: function(){
984
+ jQuery( '.notice.itsec-is-dismissible' ).each( function() {
985
+ var $el = jQuery( this ),
986
+ $button = jQuery( '<button type="button" class="notice-dismiss"><span class="screen-reader-text"></span></button>' ),
987
+ btnText = itsec_page.translations.dismiss || '';
988
+
989
+ // Don't rebind twice
990
+ if ( jQuery( '.notice-dismiss', $el ).length ) {
991
+ return;
992
+ }
993
+
994
+ // Ensure plain text
995
+ $button.find( '.screen-reader-text' ).text( btnText );
996
+ $button.on( 'click.wp-dismiss-notice', function( event ) {
997
+ event.preventDefault();
998
+
999
+ $el.trigger( 'itsec-dismiss-notice' );
1000
+
1001
+ $el.fadeTo( 100, 0, function() {
1002
+ $el.slideUp( 100, function() {
1003
+ $el.remove();
1004
+ });
1005
+ });
1006
+ });
1007
+
1008
+ $el.append( $button );
1009
+ });
1010
+ }
1011
  };
1012
 
1013
  jQuery(document).ready(function( $ ) {
core/admin-pages/module-settings.php CHANGED
@@ -94,6 +94,13 @@ class ITSEC_Module_Settings_Page {
94
  */
95
  protected $information_only = false;
96
 
 
 
 
 
 
 
 
97
 
98
  /**
99
  * Constructor.
@@ -120,7 +127,7 @@ class ITSEC_Module_Settings_Page {
120
  * @return mixed Property.
121
  */
122
  public function __get( $name ) {
123
- if ( in_array( $name, array( 'id', 'title', 'description', 'type', 'pro', 'can_save', 'redraw_on_save', 'upsell', 'upsell_url', 'information_only' ) ) ) {
124
  return $this->$name;
125
  }
126
 
94
  */
95
  protected $information_only = false;
96
 
97
+ /**
98
+ * Set the module status to 'warning' to signal to the user it needs attention.
99
+ *
100
+ * @var string
101
+ */
102
+ protected $status = '';
103
+
104
 
105
  /**
106
  * Constructor.
127
  * @return mixed Property.
128
  */
129
  public function __get( $name ) {
130
+ if ( in_array( $name, array( 'id', 'title', 'description', 'type', 'pro', 'can_save', 'redraw_on_save', 'upsell', 'upsell_url', 'information_only', 'status' ) ) ) {
131
  return $this->$name;
132
  }
133
 
core/admin-pages/page-settings.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
 
4
  final class ITSEC_Settings_Page {
5
- private $version = 1.8;
6
 
7
  private static $instance;
8
 
@@ -114,6 +114,7 @@ final class ITSEC_Settings_Page {
114
  'activate' => __( 'Enable', 'better-wp-security' ),
115
  'deactivate' => __( 'Disable', 'better-wp-security' ),
116
  'error' => __( 'Error', 'better-wp-security' ),
 
117
  'copied' => __( 'Copied!', 'better-wp-security' ),
118
  'copy_instruction' => __( 'Please press Ctrl/Cmd+C to copy.', 'better-wp-security' ),
119
 
@@ -167,12 +168,21 @@ final class ITSEC_Settings_Page {
167
  ITSEC_Response::add_error( new WP_Error( 'itsec-settings-page-missing-method', __( 'The server did not receive a valid request. The required "method" argument is missing. Please try again.', 'better-wp-security' ) ) );
168
  } else if ( 'save' === $method ) {
169
  $this->handle_post();
 
170
  } else if ( empty( $module ) ) {
171
  ITSEC_Response::add_error( new WP_Error( 'itsec-settings-page-missing-module', __( 'The server did not receive a valid request. The required "module" argument is missing. Please try again.', 'better-wp-security' ) ) );
172
  } else if ( 'activate' === $method ) {
173
- ITSEC_Response::set_response( ITSEC_Modules::activate( $module ) );
 
 
 
 
 
 
 
174
  } else if ( 'deactivate' === $method ) {
175
  ITSEC_Response::set_response( ITSEC_Modules::deactivate( $module ) );
 
176
  } else if ( 'is_active' === $method ) {
177
  ITSEC_Response::set_response( ITSEC_Modules::is_active( $module ) );
178
  } else if ( 'get_refreshed_module_settings' === $method ) {
@@ -310,6 +320,7 @@ final class ITSEC_Settings_Page {
310
  return;
311
  }
312
 
 
313
  ITSEC_Response::maybe_regenerate_wp_config();
314
  ITSEC_Response::maybe_regenerate_server_config();
315
  ITSEC_Response::maybe_do_force_logout();
@@ -543,6 +554,10 @@ final class ITSEC_Settings_Page {
543
  if ( $module->pro ) {
544
  $classes[] = 'itsec-module-type-pro';
545
  }
 
 
 
 
546
  ?>
547
  <li id="itsec-module-card-<?php echo $id; ?>" class="itsec-module-card <?php echo implode( ' ', $classes ); ?>" data-module-id="<?php echo $id; ?>">
548
  <div class="itsec-module-card-content">
@@ -579,7 +594,10 @@ final class ITSEC_Settings_Page {
579
  </div>
580
  <div class="itsec-module-settings-content-container">
581
  <div class="itsec-module-settings-content">
582
- <h3 class="itsec-modal-header"><?php echo esc_html( $module->title ); ?></h3>
 
 
 
583
  <div class="itsec-module-messages-container"></div>
584
  <div class="itsec-module-settings-content-main">
585
  <?php $this->get_module_settings( $id, $form, true ); ?>
2
 
3
 
4
  final class ITSEC_Settings_Page {
5
+ private $version = 1.9;
6
 
7
  private static $instance;
8
 
114
  'activate' => __( 'Enable', 'better-wp-security' ),
115
  'deactivate' => __( 'Disable', 'better-wp-security' ),
116
  'error' => __( 'Error', 'better-wp-security' ),
117
+ 'dismiss' => __( 'Dismiss Notice', 'better-wp-security' ), // Screen reader text for dismissible notices
118
  'copied' => __( 'Copied!', 'better-wp-security' ),
119
  'copy_instruction' => __( 'Please press Ctrl/Cmd+C to copy.', 'better-wp-security' ),
120
 
168
  ITSEC_Response::add_error( new WP_Error( 'itsec-settings-page-missing-method', __( 'The server did not receive a valid request. The required "method" argument is missing. Please try again.', 'better-wp-security' ) ) );
169
  } else if ( 'save' === $method ) {
170
  $this->handle_post();
171
+ ITSEC_Response::maybe_flag_new_notifications_available();
172
  } else if ( empty( $module ) ) {
173
  ITSEC_Response::add_error( new WP_Error( 'itsec-settings-page-missing-module', __( 'The server did not receive a valid request. The required "module" argument is missing. Please try again.', 'better-wp-security' ) ) );
174
  } else if ( 'activate' === $method ) {
175
+ $was_active = ITSEC_Modules::activate( $module );
176
+ ITSEC_Response::set_response( $was_active );
177
+
178
+ if ( ! $was_active ) {
179
+ ITSEC_Modules::load_module_file( 'active.php', $module );
180
+ }
181
+
182
+ ITSEC_Response::maybe_flag_new_notifications_available();
183
  } else if ( 'deactivate' === $method ) {
184
  ITSEC_Response::set_response( ITSEC_Modules::deactivate( $module ) );
185
+ ITSEC_Response::maybe_flag_new_notifications_available();
186
  } else if ( 'is_active' === $method ) {
187
  ITSEC_Response::set_response( ITSEC_Modules::is_active( $module ) );
188
  } else if ( 'get_refreshed_module_settings' === $method ) {
320
  return;
321
  }
322
 
323
+ ITSEC_Response::maybe_flag_new_notifications_available();
324
  ITSEC_Response::maybe_regenerate_wp_config();
325
  ITSEC_Response::maybe_regenerate_server_config();
326
  ITSEC_Response::maybe_do_force_logout();
554
  if ( $module->pro ) {
555
  $classes[] = 'itsec-module-type-pro';
556
  }
557
+
558
+ if ( 'warning' === $module->status ) {
559
+ $classes[] = 'itsec-module-status--warning';
560
+ }
561
  ?>
562
  <li id="itsec-module-card-<?php echo $id; ?>" class="itsec-module-card <?php echo implode( ' ', $classes ); ?>" data-module-id="<?php echo $id; ?>">
563
  <div class="itsec-module-card-content">
594
  </div>
595
  <div class="itsec-module-settings-content-container">
596
  <div class="itsec-module-settings-content">
597
+ <h3 class="itsec-modal-header">
598
+ <?php echo esc_html( $module->title ); ?>
599
+ <?php do_action( 'itsec_module_settings_after_title', $id ); ?>
600
+ </h3>
601
  <div class="itsec-module-messages-container"></div>
602
  <div class="itsec-module-settings-content-main">
603
  <?php $this->get_module_settings( $id, $form, true ); ?>
core/core.php CHANGED
@@ -25,7 +25,7 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
25
  *
26
  * @access private
27
  */
28
- private $plugin_build = 4075;
29
 
30
  /**
31
  * Used to distinguish between a user modifying settings and the API modifying settings (such as from Sync
@@ -41,6 +41,7 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
41
  private
42
  $itsec_files,
43
  $itsec_notify,
 
44
  $sync_api,
45
  $plugin_file,
46
  $plugin_dir,
@@ -190,6 +191,24 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
190
  return $self->itsec_notify;
191
  }
192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  /**
194
  * Retrieve the global instance of the Sync API.
195
  *
@@ -222,6 +241,7 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
222
 
223
  ITSEC_Modules::register_module( 'security-check', "$path/modules/security-check", 'always-active' );
224
  ITSEC_Modules::register_module( 'global', "$path/modules/global", 'always-active' );
 
225
  ITSEC_Modules::register_module( '404-detection', "$path/modules/404-detection" );
226
  ITSEC_Modules::register_module( 'admin-user', "$path/modules/admin-user", 'always-active' );
227
  ITSEC_Modules::register_module( 'away-mode', "$path/modules/away-mode" );
25
  *
26
  * @access private
27
  */
28
+ private $plugin_build = 4078;
29
 
30
  /**
31
  * Used to distinguish between a user modifying settings and the API modifying settings (such as from Sync
41
  private
42
  $itsec_files,
43
  $itsec_notify,
44
+ $notifications,
45
  $sync_api,
46
  $plugin_file,
47
  $plugin_dir,
191
  return $self->itsec_notify;
192
  }
193
 
194
+ /**
195
+ * Set the notification center instance.
196
+ *
197
+ * @param ITSEC_Notification_Center $center
198
+ */
199
+ public static function set_notification_center( ITSEC_Notification_Center $center ) {
200
+ self::get_instance()->notifications = $center;
201
+ }
202
+
203
+ /**
204
+ * Get the notification center instance.
205
+ *
206
+ * @return ITSEC_Notification_Center
207
+ */
208
+ public static function get_notification_center() {
209
+ return self::get_instance()->notifications;
210
+ }
211
+
212
  /**
213
  * Retrieve the global instance of the Sync API.
214
  *
241
 
242
  ITSEC_Modules::register_module( 'security-check', "$path/modules/security-check", 'always-active' );
243
  ITSEC_Modules::register_module( 'global', "$path/modules/global", 'always-active' );
244
+ ITSEC_Modules::register_module( 'notification-center', "$path/modules/notification-center", 'always-active' );
245
  ITSEC_Modules::register_module( '404-detection', "$path/modules/404-detection" );
246
  ITSEC_Modules::register_module( 'admin-user', "$path/modules/admin-user", 'always-active' );
247
  ITSEC_Modules::register_module( 'away-mode', "$path/modules/away-mode" );
core/history.txt CHANGED
@@ -579,3 +579,22 @@
579
  Bug Fix: Fixed SQL query bug that resulted in the "Minutes to Remember Bad Login (check period)" setting being ignored.
580
  Bug Fix: Fixed bug that prevents wp-admin/install.php blocking from working properly on nginx servers.
581
  Bug Fix: Don't attempt to do an SSL redirect when WP CLI is running.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
579
  Bug Fix: Fixed SQL query bug that resulted in the "Minutes to Remember Bad Login (check period)" setting being ignored.
580
  Bug Fix: Fixed bug that prevents wp-admin/install.php blocking from working properly on nginx servers.
581
  Bug Fix: Don't attempt to do an SSL redirect when WP CLI is running.
582
+ 3.9.0 - 2017-10-25 - Chris Jean, Timothy Jacobs and Saylor Bullington
583
+ New Feature: Introduces the Notification Center, a centralized place to manage and customize email notifications sent by iThemes Security.
584
+ Bug Fix: Corrected some Javascript and CSS links not generating correctly on Windows servers.
585
+ 3.9.1 - 2017-10-26 - Chris Jean & Timothy Jacobs
586
+ Bug Fix: Only enable the Lockout email notification is the Daily Digest was previously disabled.
587
+ Bug Fix: Fix JavaScript error when loading the Notification Center on some systems.
588
+ Bug Fix: Don't store WP Error objects for mail errors preventing a fatal error for rare PHPMailer errors.
589
+ Bug Fix: Prevent error on upgrade warning the subject line was empty.
590
+ Bug Fix: Ensure file change notification is properly enabled/disabled on upgrade.
591
+ Bug Fix: Fallback to correct default subject lines.
592
+ Bug Fix: Don't enable all administrators as the recipients for emails where all custom email addresses did not have corresponding users.
593
+ Upgrade Routine: Properly enable lockout and file change notifications, uncheck all administrators as recipients when necessary.
594
+ 3.9.2 - 2017-11-01 - Chris Jean & Timothy Jacobs
595
+ Enhancement: Updated queries and prepare statements to account for changes to the esc_sql() function in WordPress 4.8.3.
596
+ Bug Fix: Fixed the File Change module being incorrectly enabled when upgrading.
597
+ 3.9.3 - 2017-11-02 - Chris Jean & Timothy Jacobs
598
+ Bug Fix: Fixed source of the following warning: "mysql_real_escape_string() expects parameter 1 to be string, object given".
599
+ 3.9.4 - 2017-11-06 - Chris Jean & Timothy Jacobs
600
+ Bug Fix: Don't display file change admin notifications if the Notify Admin setting is not enabled.
core/lib.php CHANGED
@@ -147,42 +147,6 @@ final class ITSEC_Lib {
147
  return ITSEC_Lib_Config_File::get_wp_config_file_path();
148
  }
149
 
150
- /**
151
- * Gets current url
152
- *
153
- * Finds and returns current url.
154
- *
155
- * @since 4.3.0
156
- *
157
- * @return string current url
158
- * */
159
- public static function get_current_url() {
160
-
161
- $page_url = 'http';
162
-
163
- if ( isset( $_SERVER["HTTPS"] ) ) {
164
-
165
- if ( 'on' == $_SERVER["HTTPS"] ) {
166
- $page_url .= "s";
167
- }
168
-
169
- }
170
-
171
- $page_url .= "://";
172
-
173
- if ( '80' != $_SERVER["SERVER_PORT"] ) {
174
-
175
- $page_url .= $_SERVER["SERVER_NAME"] . ":" . $_SERVER["SERVER_PORT"] . $_SERVER["REQUEST_URI"];
176
-
177
- } else {
178
-
179
- $page_url .= $_SERVER["SERVER_NAME"] . $_SERVER["REQUEST_URI"];
180
-
181
- }
182
-
183
- return esc_url( $page_url );
184
- }
185
-
186
  /**
187
  * Return primary domain from given url.
188
  *
@@ -392,43 +356,6 @@ final class ITSEC_Lib {
392
  return $GLOBALS['__itsec_remote_ip'];
393
  }
394
 
395
- /**
396
- * Gets PHP Memory Limit.
397
- *
398
- * Attempts to get the maximum amount of memory allowed for the application by the server.
399
- *
400
- * @since 4.0.0
401
- *
402
- * @return int php memory limit in megabytes
403
- */
404
- public static function get_memory_limit() {
405
-
406
- return (int) ini_get( 'memory_limit' );
407
-
408
- }
409
-
410
- /**
411
- * Returns the URL of the current module.
412
- *
413
- * Get's the full URL of the current module.
414
- *
415
- * @since 4.0.0
416
- *
417
- * @param string $file the module file from which to derive the path
418
- *
419
- * @return string the path of the current module
420
- */
421
- public static function get_module_path( $file ) {
422
-
423
- $path = str_replace( ITSEC_Core::get_plugin_dir(), '', dirname( $file ) );
424
- $path = ltrim( str_replace( '\\', '/', $path ), '/' );
425
-
426
- $url_base = trailingslashit( plugin_dir_url( ITSEC_Core::get_plugin_file() ) );
427
-
428
- return trailingslashit( $url_base . $path );
429
-
430
- }
431
-
432
  /**
433
  * Returns the server type of the plugin user.
434
  *
@@ -444,63 +371,6 @@ final class ITSEC_Lib {
444
  return ITSEC_Lib_Utility::get_web_server();
445
  }
446
 
447
- /**
448
- * Determine whether the server supports SSL (shared cert not supported.
449
- *
450
- * Attempts to retrieve an HTML version of the homepage in an effort to determine if SSL is available.
451
- *
452
- * @since 4.0.0
453
- *
454
- * @return bool true if ssl is supported or false
455
- */
456
- public static function get_ssl() {
457
-
458
- $url = str_ireplace( 'http://', 'https://', get_bloginfo( 'url' ) );
459
-
460
- if ( function_exists( 'wp_http_supports' ) && wp_http_supports( array( 'ssl' ), $url ) ) {
461
-
462
- return true;
463
-
464
- } elseif ( function_exists( 'curl_init' ) ) {
465
-
466
- //use a manual CURL request to better account for self-signed certificates
467
- $timeout = 5; //timeout for the request
468
- $site_title = trim( get_bloginfo() );
469
-
470
- $request = curl_init();
471
-
472
- curl_setopt( $request, CURLOPT_RETURNTRANSFER, true );
473
- curl_setopt( $request, CURLOPT_VERBOSE, false );
474
- curl_setopt( $request, CURLOPT_SSL_VERIFYPEER, false );
475
- curl_setopt( $request, CURLOPT_HEADER, true );
476
- curl_setopt( $request, CURLOPT_URL, $url );
477
- curl_setopt( $request, CURLOPT_RETURNTRANSFER, true );
478
- curl_setopt( $request, CURLOPT_CONNECTTIMEOUT, $timeout );
479
-
480
- $data = curl_exec( $request );
481
-
482
- $header_size = curl_getinfo( $request, CURLINFO_HEADER_SIZE );
483
- $http_code = intval( curl_getinfo( $request, CURLINFO_HTTP_CODE ) );
484
- $body = substr( $data, $header_size );
485
-
486
- preg_match( '/<title>(.+)<\/title>/', $body, $matches );
487
-
488
- if ( 200 == $http_code && isset( $matches[1] ) && false !== strpos( $matches[1], $site_title ) ) {
489
-
490
- return true;
491
-
492
- } else {
493
-
494
- return false;
495
-
496
- }
497
-
498
- }
499
-
500
- return false;
501
-
502
- }
503
-
504
  public static function get_whitelisted_ips() {
505
  return apply_filters( 'itsec_white_ips', array() );
506
  }
@@ -591,21 +461,6 @@ final class ITSEC_Lib {
591
  return false;
592
  }
593
 
594
- /**
595
- * Determine whether we're on the login page or not.
596
- *
597
- * Attempts to determine whether or not the user is on the WordPress dashboard login page.
598
- *
599
- * @since 4.0.0
600
- *
601
- * @return bool true if is login page else false
602
- */
603
- public static function is_login_page() {
604
-
605
- return in_array( $GLOBALS['pagenow'], array( 'wp-login.php', 'wp-register.php' ) );
606
-
607
- }
608
-
609
  /**
610
  * Set a 404 error.
611
  *
@@ -724,7 +579,7 @@ final class ITSEC_Lib {
724
  }
725
 
726
  //queary the user table to see if the user is there
727
- $saved_id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM `" . $wpdb->users . "` WHERE ID='%s';", sanitize_text_field( $user_id ) ) );
728
 
729
  if ( $saved_id == $user_id ) {
730
 
@@ -738,96 +593,6 @@ final class ITSEC_Lib {
738
 
739
  }
740
 
741
- /**
742
- * Validates a file path
743
- *
744
- * Adapted from http://stackoverflow.com/questions/4049856/replace-phps-realpath/4050444#4050444 as a replacement for PHP's realpath
745
- *
746
- * @since 4.0.0
747
- *
748
- * @param string $path The original path, can be relative etc.
749
- *
750
- * @return bool true if the path is valid and writeable else false
751
- */
752
- public static function validate_path( $path ) {
753
-
754
- // whether $path is unix or not
755
- $unipath = strlen( $path ) == 0 || $path{0} != '/';
756
-
757
- // attempts to detect if path is relative in which case, add cwd
758
- if ( false === strpos( $path, ':' ) && $unipath ) {
759
- $path = getcwd() . DIRECTORY_SEPARATOR . $path;
760
- }
761
-
762
- // resolve path parts (single dot, double dot and double delimiters)
763
- $path = str_replace( array( '/', '\\' ), DIRECTORY_SEPARATOR, $path );
764
- $parts = array_filter( explode( DIRECTORY_SEPARATOR, $path ), 'strlen' );
765
- $absolutes = array();
766
-
767
- foreach ( $parts as $part ) {
768
-
769
- if ( '.' == $part ) {
770
- continue;
771
- }
772
-
773
- if ( '..' == $part ) {
774
-
775
- array_pop( $absolutes );
776
-
777
- } else {
778
-
779
- $absolutes[] = $part;
780
-
781
- }
782
-
783
- }
784
-
785
- $path = implode( DIRECTORY_SEPARATOR, $absolutes );
786
-
787
- // resolve any symlinks
788
- if ( function_exists( 'linkinfo' ) ) { //linkinfo not available on Windows with PHP < 5.3.0
789
-
790
- if ( file_exists( $path ) && 0 < linkinfo( $path ) ) {
791
- $path = @readlink( $path );
792
- }
793
-
794
- } else {
795
-
796
- if ( file_exists( $path ) && 0 < linkinfo( $path ) ) {
797
- $path = @readlink( $path );
798
- }
799
-
800
- }
801
-
802
- // put initial separator that could have been lost
803
- $path = ! $unipath ? '/' . $path : $path;
804
-
805
- $test = @touch( $path . '/test.txt' );
806
- @unlink( $path . '/test.txt' );
807
-
808
- return $test;
809
-
810
- }
811
-
812
- /**
813
- * Validates a URL
814
- *
815
- * Ensures the provided URL is a valid URL.
816
- *
817
- * @since 4.3.0
818
- *
819
- * @param string $url the url to validate
820
- *
821
- * @return bool true if valid url else false
822
- */
823
- public static function validate_url( $url ) {
824
-
825
- $pattern = "/^(http|https|ftp):\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?/i";
826
-
827
- return (bool) preg_match( $pattern, $url );
828
-
829
- }
830
-
831
  public static function show_status_message( $message ) {
832
  echo "<div class=\"updated fade\"><p><strong>$message</strong></p></div>\n";
833
  }
@@ -1239,4 +1004,52 @@ final class ITSEC_Lib {
1239
 
1240
  return apply_filters( 'itsec-ssl-support-probability', $probability );
1241
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1242
  }
147
  return ITSEC_Lib_Config_File::get_wp_config_file_path();
148
  }
149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  /**
151
  * Return primary domain from given url.
152
  *
356
  return $GLOBALS['__itsec_remote_ip'];
357
  }
358
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
  /**
360
  * Returns the server type of the plugin user.
361
  *
371
  return ITSEC_Lib_Utility::get_web_server();
372
  }
373
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
  public static function get_whitelisted_ips() {
375
  return apply_filters( 'itsec_white_ips', array() );
376
  }
461
  return false;
462
  }
463
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
  /**
465
  * Set a 404 error.
466
  *
579
  }
580
 
581
  //queary the user table to see if the user is there
582
+ $saved_id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM `" . $wpdb->users . "` WHERE ID= %d;", $user_id ) );
583
 
584
  if ( $saved_id == $user_id ) {
585
 
593
 
594
  }
595
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
596
  public static function show_status_message( $message ) {
597
  echo "<div class=\"updated fade\"><p><strong>$message</strong></p></div>\n";
598
  }
1004
 
1005
  return apply_filters( 'itsec-ssl-support-probability', $probability );
1006
  }
1007
+
1008
+ /**
1009
+ * Format a date using date_i18n and convert the time from GMT to local.
1010
+ *
1011
+ * @author Modified from ticket #25331
1012
+ *
1013
+ * @param int $timestamp
1014
+ * @param string $format Specify the format. If blank, will default to the date and time format settings.
1015
+ *
1016
+ * @return string
1017
+ */
1018
+ public static function date_format_i18n_and_local_timezone( $timestamp, $format = '' ) {
1019
+
1020
+ if ( ! $format ) {
1021
+ $format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' );
1022
+ }
1023
+
1024
+ return date_i18n( $format, strtotime( get_date_from_gmt( date( 'Y-m-d H:i:s', $timestamp ) ) ) );
1025
+ }
1026
+
1027
+ public static function array_get( $array, $key, $default = null ) {
1028
+
1029
+ if ( ! is_array( $array ) ) {
1030
+ return $default;
1031
+ }
1032
+
1033
+ if ( null === $key ) {
1034
+ return $array;
1035
+ }
1036
+
1037
+ if ( isset( $array[ $key ] ) ) {
1038
+ return $array[ $key ];
1039
+ }
1040
+
1041
+ if ( strpos( $key, '.' ) === false ) {
1042
+ return isset( $array[ $key ] ) ? $array[ $key ] : $default;
1043
+ }
1044
+
1045
+ foreach ( explode( '.', $key ) as $segment ) {
1046
+ if ( is_array( $array ) && isset( $array[ $segment ] ) ) {
1047
+ $array = $array[ $segment ];
1048
+ } else {
1049
+ return $default;
1050
+ }
1051
+ }
1052
+
1053
+ return $array;
1054
+ }
1055
  }
core/lib/class-itsec-mail.php CHANGED
@@ -11,45 +11,29 @@ final class ITSEC_Mail {
11
  $this->template_path = dirname( __FILE__ ) . '/mail-templates/';
12
  }
13
 
14
- public function add_header( $title, $banner_title ) {
15
  $header = $this->get_template( 'header.html' );
16
 
 
 
 
 
 
 
 
 
17
  $replacements = array(
18
  'lang' => esc_attr( get_bloginfo( 'language' ) ),
19
  'charset' => esc_attr( get_bloginfo( 'charset' ) ),
20
  'title_tag' => $title,
21
  'banner_title' => $banner_title,
22
- 'logo' => ITSEC_Core::is_pro() ? $this->get_image_url( 'pro_logo' ) : $this->get_image_url( 'logo' ),
23
  'title' => $title,
24
  );
25
 
26
  $this->content .= $this->replace_all( $header, $replacements );
27
  }
28
 
29
- private function replace( $content, $variable, $value ) {
30
- return preg_replace( '/{{ \$' . preg_quote( $variable, '/' ) . ' }}/', $value, $content );
31
- }
32
-
33
- private function replace_all( $content, $replacements ) {
34
- foreach ( $replacements as $variable => $value ) {
35
- $content = preg_replace( '/{{ \$' . preg_quote( $variable, '/' ) . ' }}/', $value, $content );
36
- }
37
-
38
- return $content;
39
- }
40
-
41
- private function replace_images( $content ) {
42
- return preg_replace_callback( '/{! \$([a-zA-Z_][\w]*) }}/', array( $this, 'replace_image_callback' ), $content );
43
- }
44
-
45
- private function replace_image_callback( $matches ) {
46
- if ( empty( $matches ) || empty( $matches[1] ) ) {
47
- return '';
48
- }
49
-
50
- return esc_url( $this->get_image_url( $matches[1] ) );
51
- }
52
-
53
  public function add_footer() {
54
  $footer = '';
55
 
@@ -69,6 +53,8 @@ final class ITSEC_Mail {
69
 
70
  $footer .= $this->get_template( 'footer.html' );
71
 
 
 
72
  $replacements = array(
73
  'security_resources' => esc_html__( 'Security Resources', 'better-wp-security' ),
74
  'articles' => esc_html__( 'Articles', 'better-wp-security' ),
@@ -81,15 +67,35 @@ final class ITSEC_Mail {
81
  'support' => esc_html__( 'Support', 'better-wp-security' ),
82
  'pro' => esc_html__( 'Pro', 'better-wp-security' ),
83
  'support_content' => sprintf( wp_kses( __( 'Pro customers can contact <a href="%s">iThemes Helpdesk</a> for help. Our support team answers questions Monday – Friday, 8am – 5pm (CST).', 'better-wp-security' ), array( 'a' => array( 'href' => array() ) ) ), esc_url( 'https://members.ithemes.com/panel/helpdesk.php' ) ),
84
- 'security_settings_link' => esc_url( self::filter_admin_page_url( ITSEC_Core::get_settings_page_url() ) ),
85
- 'unsubscribe_link_text' => esc_html__( 'This email was generated by the iThemes Security plugin.', 'better-wp-security' ) . '<br>' . esc_html__( 'To unsubscribe from these updates, visit the Settings page in the iThemes Security plugin menu.', 'better-wp-security' ),
86
  'security_guide' => esc_html__( 'Free WordPress Security Guide', 'better-wp-security' ),
87
  'security_guide_content' => sprintf( wp_kses( __( 'Learn simple WordPress security tips — including 3 kinds of security your site needs and 4 best security practices for keeping your WordPress site safe with our <a href="%s">free guide</a>.', 'better-wp-security' ), array( 'a' => array( 'href' => array() ) ) ), esc_url( 'https://ithemes.com/publishing/wordpress-security/' ) ),
88
 
89
  );
90
 
91
- $footer = $this->replace_all( $footer, $replacements );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
 
93
  $this->content .= $footer;
94
  }
95
 
@@ -112,7 +118,7 @@ final class ITSEC_Mail {
112
  }
113
 
114
  public function add_info_box( $content, $icon_type = 'info' ) {
115
- $icon_url = $this->get_image_url( "{$icon_type}_icon" );
116
 
117
  $module = $this->get_template( 'info-box.html' );
118
  $module = $this->replace_all( $module, compact( 'content', 'icon_url' ) );
@@ -127,6 +133,13 @@ final class ITSEC_Mail {
127
  $this->content .= $module;
128
  }
129
 
 
 
 
 
 
 
 
130
  public function add_section_heading( $content, $icon_type = false ) {
131
  if ( empty( $icon_type ) ) {
132
  $heading = $this->get_template( 'section-heading.html' );
@@ -156,10 +169,27 @@ final class ITSEC_Mail {
156
  $this->content .= $lockouts;
157
  }
158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  public function add_button( $link_text, $href ) {
160
  $module = $this->get_template( 'module-button.html' );
161
- $module = preg_replace( '/{{ \$href }}/', $href, $module );
162
- $module = preg_replace( '/{{ \$link_text }}/', $link_text, $module );
163
 
164
  $this->content .= $module;
165
  }
@@ -194,19 +224,165 @@ final class ITSEC_Mail {
194
  $this->content .= $table;
195
  }
196
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  public function get_content() {
198
  return $this->content;
199
  }
200
 
201
  public function set_subject( $subject, $add_site_url = true ) {
202
  if ( $add_site_url ) {
203
- /* translators: 1: site URL, 2: email subject */
204
- $subject = sprintf( __( '[%1$s] %2$s', 'better-wp-security' ), get_option( 'siteurl' ), $subject );
205
  }
206
 
207
  $this->subject = esc_html( $subject );
208
  }
209
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  public function set_recipients( $recipients ) {
211
  $this->recipients = array();
212
 
@@ -224,6 +400,10 @@ final class ITSEC_Mail {
224
  $this->set_recipients( $recipients );
225
  }
226
 
 
 
 
 
227
  public function set_attachments( $attachments ) {
228
  $this->attachments = $attachments;
229
  }
@@ -244,10 +424,51 @@ final class ITSEC_Mail {
244
  return wp_mail( $this->recipients, $this->subject, $this->content, array( 'Content-Type: text/html; charset=UTF-8' ), $this->attachments );
245
  }
246
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  private function get_template( $template ) {
248
  return $this->replace_images( file_get_contents( $this->template_path . $template ) );
249
  }
250
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  private function get_image_url( $name ) {
252
  return plugin_dir_url( ITSEC_Core::get_core_dir() . 'img/mail/index.php' ) . "{$name}.png";
253
  }
11
  $this->template_path = dirname( __FILE__ ) . '/mail-templates/';
12
  }
13
 
14
+ public function add_header( $title, $banner_title, $use_site_logo = false ) {
15
  $header = $this->get_template( 'header.html' );
16
 
17
+ if ( $use_site_logo ) {
18
+ $logo = $this->get_site_logo_url();
19
+ } elseif ( ITSEC_Core::is_pro() ) {
20
+ $logo = $this->get_image_url( 'pro_logo' );
21
+ } else {
22
+ $logo = $this->get_image_url( 'logo' );
23
+ }
24
+
25
  $replacements = array(
26
  'lang' => esc_attr( get_bloginfo( 'language' ) ),
27
  'charset' => esc_attr( get_bloginfo( 'charset' ) ),
28
  'title_tag' => $title,
29
  'banner_title' => $banner_title,
30
+ 'logo' => $logo,
31
  'title' => $title,
32
  );
33
 
34
  $this->content .= $this->replace_all( $header, $replacements );
35
  }
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  public function add_footer() {
38
  $footer = '';
39
 
53
 
54
  $footer .= $this->get_template( 'footer.html' );
55
 
56
+ $settings = esc_url( self::filter_admin_page_url( ITSEC_Core::get_settings_page_url() ) );
57
+
58
  $replacements = array(
59
  'security_resources' => esc_html__( 'Security Resources', 'better-wp-security' ),
60
  'articles' => esc_html__( 'Articles', 'better-wp-security' ),
67
  'support' => esc_html__( 'Support', 'better-wp-security' ),
68
  'pro' => esc_html__( 'Pro', 'better-wp-security' ),
69
  'support_content' => sprintf( wp_kses( __( 'Pro customers can contact <a href="%s">iThemes Helpdesk</a> for help. Our support team answers questions Monday – Friday, 8am – 5pm (CST).', 'better-wp-security' ), array( 'a' => array( 'href' => array() ) ) ), esc_url( 'https://members.ithemes.com/panel/helpdesk.php' ) ),
70
+ 'security_settings_link' => $settings,
71
+ 'unsubscribe_link_text' => esc_html__( 'This email was generated by the iThemes Security plugin.', 'better-wp-security' ) . '<br>' . sprintf( esc_html__( 'To unsubscribe from these updates, visit the %1$sSettings page%2$s in the iThemes Security plugin menu.', 'better-wp-security' ), "<a href=\"{$settings}\" style=\"color: #0084CB\">", '</a>' ),
72
  'security_guide' => esc_html__( 'Free WordPress Security Guide', 'better-wp-security' ),
73
  'security_guide_content' => sprintf( wp_kses( __( 'Learn simple WordPress security tips — including 3 kinds of security your site needs and 4 best security practices for keeping your WordPress site safe with our <a href="%s">free guide</a>.', 'better-wp-security' ), array( 'a' => array( 'href' => array() ) ) ), esc_url( 'https://ithemes.com/publishing/wordpress-security/' ) ),
74
 
75
  );
76
 
77
+ $this->content .= $this->replace_all( $footer, $replacements );
78
+
79
+ if ( defined( 'ITSEC_DEBUG' ) && ITSEC_DEBUG ) {
80
+ $this->include_debug_info();
81
+ }
82
+
83
+ $this->content .= $this->get_template( 'close.html' );
84
+ }
85
+
86
+ public function add_user_footer() {
87
+
88
+ $link_text = sprintf( esc_html__( 'This email was generated by the iThemes Security plugin on behalf of %s.', 'better-wp-security' ), get_bloginfo( 'name', 'display' ) ) . '<br>';
89
+ $link_text .= sprintf(
90
+ esc_html__( 'To unsubscribe from these notifications, please %1$scontact the site administrator%2$s.', 'better-wp-security' ),
91
+ '<a href="' . esc_url( site_url() ) . '" style="color: #0084CB">', '</a>'
92
+ );
93
+
94
+ $footer = $this->replace_all( $this->replace_images( $this->get_template( 'footer-user.html' ) ), array(
95
+ 'unsubscribe_link_text' => $link_text,
96
+ ) );
97
 
98
+ $footer .= $this->get_template( 'close.html' );
99
  $this->content .= $footer;
100
  }
101
 
118
  }
119
 
120
  public function add_info_box( $content, $icon_type = 'info' ) {
121
+ $icon_url = $this->get_image_url( $icon_type === 'warning' ? 'warning_icon_yellow' : "{$icon_type}_icon" );
122
 
123
  $module = $this->get_template( 'info-box.html' );
124
  $module = $this->replace_all( $module, compact( 'content', 'icon_url' ) );
133
  $this->content .= $module;
134
  }
135
 
136
+ public function add_large_code( $content ) {
137
+ $module = $this->get_template( 'large-code.html' );
138
+ $module = $this->replace( $module, 'content', $content );
139
+
140
+ $this->content .= $module;
141
+ }
142
+
143
  public function add_section_heading( $content, $icon_type = false ) {
144
  if ( empty( $icon_type ) ) {
145
  $heading = $this->get_template( 'section-heading.html' );
169
  $this->content .= $lockouts;
170
  }
171
 
172
+ public function add_file_change_summary( $added, $removed, $modified ) {
173
+ $lockouts = $this->get_template( 'file-change-summary.html' );
174
+
175
+ $replacements = array(
176
+ 'added_text' => esc_html_x( 'Added', 'Files added', 'better-wp-security' ),
177
+ 'removed_text' => esc_html_x( 'Removed', 'Files removed', 'better-wp-security' ),
178
+ 'modified_text' => esc_html_x( 'Modified', 'Files modified', 'better-wp-security' ),
179
+ 'added_count' => $added,
180
+ 'removed_count' => $removed,
181
+ 'modified_count' => $modified,
182
+ );
183
+
184
+ $lockouts = $this->replace_all( $lockouts, $replacements );
185
+
186
+ $this->content .= $lockouts;
187
+ }
188
+
189
  public function add_button( $link_text, $href ) {
190
  $module = $this->get_template( 'module-button.html' );
191
+ $module = $this->replace( $module, 'href', $href );
192
+ $module = $this->replace( $module, 'link_text', $link_text );
193
 
194
  $this->content .= $module;
195
  }
224
  $this->content .= $table;
225
  }
226
 
227
+ /**
228
+ * Add a generic table.
229
+ *
230
+ * @param string[] $headers
231
+ * @param array[] $entries
232
+ */
233
+ public function add_table( $headers, $entries ) {
234
+
235
+ $template = $this->get_template( 'table.html' );
236
+ $html = $this->build_table_header( $headers );
237
+
238
+ foreach ( $entries as $entry ) {
239
+ $html .= $this->build_table_row( $entry, count( $headers ) );
240
+ }
241
+
242
+ $this->content .= $this->replace( $template, 'html', $html );
243
+ }
244
+
245
+ /**
246
+ * Build the table header.
247
+ *
248
+ * @param array $headers
249
+ *
250
+ * @return string
251
+ */
252
+ private function build_table_header( $headers ) {
253
+
254
+ $html = '<tr>';
255
+
256
+ foreach ( $headers as $header ) {
257
+ $html .= '<th style="text-align: left;font-weight: bold;padding:5px 10px;border:1px solid #cdcece;color: #666f72;">';
258
+ $html .= $header;
259
+ $html .= '</th>';
260
+ }
261
+
262
+ $html .= '</tr>';
263
+
264
+ return $html;
265
+ }
266
+
267
+ /**
268
+ * Build a table row.
269
+ *
270
+ * @param array|string $columns
271
+ * @param int $count
272
+ *
273
+ * @return string
274
+ */
275
+ private function build_table_row( $columns, $count ) {
276
+ $html = '<tr>';
277
+
278
+ if ( is_array( $columns ) ) {
279
+ foreach ( $columns as $i => $column ) {
280
+ $style = 'border:1px solid #cdcece;padding:10px;';
281
+
282
+ if ( 0 === $i ) {
283
+ $style .= 'font-style:italic;';
284
+ $el = 'th';
285
+ } else {
286
+ $el = 'td';
287
+ }
288
+
289
+ $html .= "<{$el} style=\"{$style}\">";
290
+ $html .= $column;
291
+ $html .= "</{$el}>";
292
+ }
293
+ } else {
294
+ $html .= "<td style=\"border:1px solid #cdcece;padding:10px;\" colspan=\"{$count}\">{$columns}</td>";
295
+ }
296
+
297
+ $html .= '</tr>';
298
+
299
+ return $html;
300
+ }
301
+
302
+ /**
303
+ * Add an HTML list to an email.
304
+ *
305
+ * @param string[] $items
306
+ * @param bool $bold_first Whether to emphasize the first item of the list.
307
+ */
308
+ public function add_list( $items, $bold_first = false ) {
309
+
310
+ $template = $this->get_template( 'list.html' );
311
+ $html = '';
312
+
313
+ foreach ( $items as $i => $item ) {
314
+ $html .= $this->build_list_item( $item, $bold_first && 0 === $i );
315
+ }
316
+
317
+ $this->content .= $this->replace( $template, 'html', $html );
318
+ }
319
+
320
+ private function build_list_item( $item, $bold = false ) {
321
+ $bold_tag = $bold ? 'font-weight: bold;' : '';
322
+
323
+ return "<li style=\"margin: 0; padding: 5px 10px;{$bold_tag}\">{$item}</li>";
324
+ }
325
+
326
+ /**
327
+ * Include debug info in the email.
328
+ *
329
+ * This is automatically included in non-user emails if ITSEC_DEBUG is turned on.
330
+ */
331
+ public function include_debug_info() {
332
+ $this->add_text( sprintf( esc_html__( 'Debug info (source page): %s', 'better-wp-security' ), esc_url( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ) ) );
333
+ }
334
+
335
+ /**
336
+ * Get the site URL formatted for display in emails.
337
+ *
338
+ * This strips out the URL scheme, but keeps the path in case of multisite.
339
+ *
340
+ * @return string
341
+ */
342
+ public function get_display_url() {
343
+
344
+ $url = network_site_url();
345
+ $parsed = parse_url( $url );
346
+
347
+ $display = $parsed['host'];
348
+
349
+ if ( ! empty( $parsed['path'] ) ) {
350
+ $display .= $parsed['path'];
351
+ }
352
+
353
+ // Escape URL will force a scheme.
354
+ return esc_html( $display );
355
+ }
356
+
357
+ public function set_content( $content ) {
358
+ $this->content = $content;
359
+ }
360
+
361
  public function get_content() {
362
  return $this->content;
363
  }
364
 
365
  public function set_subject( $subject, $add_site_url = true ) {
366
  if ( $add_site_url ) {
367
+ $subject = $this->prepend_site_url_to_subject( $subject );
 
368
  }
369
 
370
  $this->subject = esc_html( $subject );
371
  }
372
 
373
+ public function prepend_site_url_to_subject( $subject ) {
374
+ /* translators: 1: site URL, 2: email subject */
375
+ return sprintf( __( '[%1$s] %2$s', 'better-wp-security' ), $this->get_display_url(), $subject );
376
+ }
377
+
378
+ public function set_default_subject() {
379
+ return __( 'New Notification from iThemes Security', 'better-wp-security' );
380
+ }
381
+
382
+ public function get_subject() {
383
+ return $this->subject;
384
+ }
385
+
386
  public function set_recipients( $recipients ) {
387
  $this->recipients = array();
388
 
400
  $this->set_recipients( $recipients );
401
  }
402
 
403
+ public function get_recipients() {
404
+ return $this->recipients;
405
+ }
406
+
407
  public function set_attachments( $attachments ) {
408
  $this->attachments = $attachments;
409
  }
424
  return wp_mail( $this->recipients, $this->subject, $this->content, array( 'Content-Type: text/html; charset=UTF-8' ), $this->attachments );
425
  }
426
 
427
+ /**
428
+ * Get the URL to the site logo.
429
+ *
430
+ * @return string
431
+ */
432
+ private function get_site_logo_url() {
433
+ $custom_logo_id = get_theme_mod( 'custom_logo' );
434
+
435
+ if ( ! $custom_logo_id ) {
436
+ return '';
437
+ }
438
+
439
+ $image = wp_get_attachment_image_src( $custom_logo_id, array( 300, 127 ) );
440
+
441
+ if ( ! $image || empty( $image[0] ) ) {
442
+ return '';
443
+ }
444
+
445
+ return $image[0];
446
+ }
447
+
448
  private function get_template( $template ) {
449
  return $this->replace_images( file_get_contents( $this->template_path . $template ) );
450
  }
451
 
452
+ private function replace( $content, $variable, $value ) {
453
+ return ITSEC_Lib::replace_tag( $content, $variable, $value );
454
+ }
455
+
456
+ private function replace_all( $content, $replacements ) {
457
+ return ITSEC_Lib::replace_tags( $content, $replacements );
458
+ }
459
+
460
+ private function replace_images( $content ) {
461
+ return preg_replace_callback( '/{! \$([a-zA-Z_][\w]*) }}/', array( $this, 'replace_image_callback' ), $content );
462
+ }
463
+
464
+ private function replace_image_callback( $matches ) {
465
+ if ( empty( $matches ) || empty( $matches[1] ) ) {
466
+ return '';
467
+ }
468
+
469
+ return esc_url( $this->get_image_url( $matches[1] ) );
470
+ }
471
+
472
  private function get_image_url( $name ) {
473
  return plugin_dir_url( ITSEC_Core::get_core_dir() . 'img/mail/index.php' ) . "{$name}.png";
474
  }
core/lib/mail-templates/close.html ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ </table>
2
+ </td>
3
+ </tr>
4
+ </table>
5
+ </center>
6
+ </body>
7
+
8
+ </html>
core/lib/mail-templates/file-change-summary.html ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <tr>
2
+ <td class="file-change-summary" align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
3
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
4
+ <tr>
5
+ <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
6
+ <table class="container" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
7
+ <tr>
8
+ <td class="section-padding" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-right: 20px;padding-left: 20px;">
9
+ <table class="container left-column" align="left" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;margin-right: 60px;">
10
+ <tr>
11
+ <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;padding-bottom: 20px;">
12
+ <h4 style="color: #ACAAAA;font-family: Helvetica;font-size: 16px;font-weight: normal;line-height: 150%;margin: 0;padding: 0;text-align: center;">{{ $added_text }}</h4>
13
+ <p style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;font-family: Helvetica;font-size: 30px;line-height: 150%;margin-top: 10px;margin-right: 0;margin-bottom: 10px;margin-left: 0;padding: 0;text-align: center;color: #505050;font-weight: bold;">{{ $added_count }}</p>
14
+ </td>
15
+ </tr>
16
+ </table>
17
+ <table class="container center-column" align="left" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;margin-right: 60px;">
18
+ <tr>
19
+ <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;padding-bottom: 20px;">
20
+ <h4 style="color: #ACAAAA;font-family: Helvetica;font-size: 16px;font-weight: normal;line-height: 150%;margin: 0;padding: 0;text-align: center;">{{ $removed_text }}</h4>
21
+ <p style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;font-family: Helvetica;font-size: 30px;line-height: 150%;margin-top: 10px;margin-right: 0;margin-bottom: 10px;margin-left: 0;padding: 0;text-align: center;color: #505050;font-weight: bold;">{{ $removed_count }}</p>
22
+ </td>
23
+ </tr>
24
+ </table>
25
+ <table class="container right-column" align="right" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
26
+ <tr>
27
+ <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;padding-bottom: 20px;">
28
+ <h4 style="color: #ACAAAA;font-family: Helvetica;font-size: 16px;font-weight: normal;line-height: 150%;margin: 0;padding: 0;text-align: center;">{{ $modified_text }}</h4>
29
+ <p style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;font-family: Helvetica;font-size: 30px;line-height: 150%;margin-top: 10px;margin-right: 0;margin-bottom: 10px;margin-left: 0;padding: 0;text-align: center;color: #505050;font-weight: bold;">{{ $modified_count }}</p>
30
+ </td>
31
+ </tr>
32
+ </table>
33
+ </td>
34
+ </tr>
35
+ </table>
36
+ </td>
37
+ </tr>
38
+ </table>
39
+ </td>
40
+ </tr>
core/lib/mail-templates/footer-user.html ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <tr>
2
+ <td id="footer-source-details" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
3
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
4
+ <tr>
5
+ <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
6
+ <table class="container" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
7
+ <tr>
8
+ <td class="section-padding" align="center" valign="top" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-right: 20px;padding-left: 20px;">
9
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
10
+ <tr>
11
+ <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 200%;text-align: center;padding-bottom: 0;padding-top: 60px;">
12
+ <img class="preserve-ratio" src="{! $footer_logo }}" style="max-width: 50px;border: 0;outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;height: auto;" width="50" alt="" align="center"><br>
13
+ <br>
14
+ <span style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #666f72;font-family: Helvetica;font-size: 11px;line-height: 200%;text-align: center;text-decoration: none;font-weight: bold;">
15
+ {{ $unsubscribe_link_text }}
16
+ </span>
17
+ </td>
18
+ </tr>
19
+ </table>
20
+ </td>
21
+ </tr>
22
+ </table>
23
+ </td>
24
+ </tr>
25
+ </table>
26
+ </td>
27
+ </tr>
core/lib/mail-templates/footer.html CHANGED
@@ -1,208 +1,204 @@
 
 
 
 
 
 
1
  <tr>
2
- <td class="footer-heading" align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
3
  <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
4
  <tr>
5
- <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
6
- <table class="container" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
7
- <tr>
8
- <td class="section-padding" align="center" valign="top" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-right: 20px;padding-left: 20px;">
9
- <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
10
- <tr>
11
- <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;padding-bottom: 20px;">
12
- <h2 style="color: #002030;font-family: Helvetica;font-size: 26px;font-weight: bold;line-height: 150%;margin: 0;padding: 0;text-align: center;">{{ $security_resources }}</h2>
13
- </td>
14
- </tr>
15
- </table>
16
- </td>
17
- </tr>
18
- </table>
19
  </td>
20
  </tr>
21
  </table>
22
  </td>
23
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
24
  <tr>
25
- <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
26
- <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
27
  <tr>
28
- <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
29
- <table class="container" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
30
- <tr>
31
- <td class="section-padding" valign="top" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-right: 20px;padding-left: 20px;">
32
- <table class="container" align="left" border="0" cellpadding="0" cellspacing="0" width="260" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
33
- <tr>
34
- <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;padding-bottom: 20px;">
35
- <img class="preserve-ratio" src="{! $article_icon }}" style="max-width: 61px;border: 0;outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;height: auto;" width="61" alt="" align="center">
36
- <br>
37
- <h4 style="color: #202020;font-family: Helvetica;font-size: 20px;font-weight: bold;line-height: 150%;margin: 0;padding: 0;text-align: center;">
38
- <a href="https://ithemes.com/category/wordpress-security/" target="_blank" style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #0084CB;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;text-decoration: none;">{{ $articles }}</a>
39
- </h4>
40
- <br>
41
- <p style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;font-family: Helvetica;font-size: 16px;line-height: 150%;margin-top: 10px;margin-right: 0;margin-bottom: 10px;margin-left: 0;padding: 0;text-align: center;">{{ $articles_content }}</p>
42
- </td>
43
- </tr>
44
- </table>
45
- <table class="container" align="right" border="0" cellpadding="0" cellspacing="0" width="260" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
46
- <tr>
47
- <td class="container-cell container-cell-bottom" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;padding-bottom: 20px;">
48
- <img class="preserve-ratio" src="{! $video_icon }}" style="max-width: 61px;border: 0;outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;height: auto;" width="61" alt="" align="center">
49
- <br>
50
- <h4 style="color: #202020;font-family: Helvetica;font-size: 20px;font-weight: bold;line-height: 150%;margin: 0;padding: 0;text-align: center;">
51
- <a href="https://ithemes.com/tutorial/category/ithemes-security/" target="_blank" style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #0084CB;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;text-decoration: none;">{{ $tutorials }}</a>
52
- </h4>
53
- <br>
54
- <p style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;font-family: Helvetica;font-size: 16px;line-height: 150%;margin-top: 10px;margin-right: 0;margin-bottom: 10px;margin-left: 0;padding: 0;text-align: center;">{{ $tutorials_content }}</p>
55
- </td>
56
- </tr>
57
- </table>
58
- </td>
59
- </tr>
60
- </table>
61
  </td>
62
  </tr>
63
  </table>
64
  </td>
65
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
66
  <tr>
67
- <td class="divider" align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
68
- <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
69
  <tr>
70
- <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
71
- <table class="container" border="0" cellpadding="0" cellspacing="0" width="450" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
72
- <tr>
73
- <td class="section-padding" align="center" valign="top" width="450" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-right: 20px;padding-left: 20px;">
74
- <table class="divider-border" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;border-top-width: 1px;border-top-style: solid;border-top-color: #E8E8E8;">
75
- <tr>
76
- <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 1px;text-align: center;padding-bottom: 20px;width: 450px;">
77
- &nbsp;
78
- </td>
79
- </tr>
80
- </table>
81
- </td>
82
- </tr>
83
- </table>
84
  </td>
85
  </tr>
86
  </table>
87
  </td>
88
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
89
  <tr>
90
- <td class="footer-heading" align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
91
  <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
92
  <tr>
93
- <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
94
- <table class="container" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
95
- <tr>
96
- <td class="section-padding" align="center" valign="top" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-right: 20px;padding-left: 20px;">
97
- <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
98
- <tr>
99
- <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;padding-bottom: 20px;">
100
- <h2 style="color: #002030;font-family: Helvetica;font-size: 26px;font-weight: bold;line-height: 150%;margin: 0;padding: 0;text-align: center;">{{ $help_and_support }}</h2>
101
- </td>
102
- </tr>
103
- </table>
104
- </td>
105
- </tr>
106
- </table>
107
  </td>
108
  </tr>
109
  </table>
110
  </td>
111
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
112
  <tr>
113
- <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
114
- <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  <tr>
116
- <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
117
- <table class="container" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
118
- <tr>
119
- <td class="section-padding" valign="top" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-right: 20px;padding-left: 20px;">
120
- <table class="container" align="left" border="0" cellpadding="0" cellspacing="0" width="260" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
121
- <tr>
122
- <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;padding-bottom: 20px;">
123
- <img class="preserve-ratio" src="{! $documentation_icon }}" style="max-width: 62px;border: 0;outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;height: auto;" width="62" alt="" align="center">
124
- <br>
125
- <h4 style="color: #202020;font-family: Helvetica;font-size: 20px;font-weight: bold;line-height: 150%;margin: 0;padding: 0;text-align: center;">
126
- <a href="http://ithemes.com/codex/page/IThemes_Security" target="_blank" style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #0084CB;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;text-decoration: none;">{{ $documentation }}</a>
127
- </h4>
128
- <br>
129
- <p style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;font-family: Helvetica;font-size: 16px;line-height: 150%;margin-top: 10px;margin-right: 0;margin-bottom: 10px;margin-left: 0;padding: 0;text-align: center;">{{ $documentation_content }}</p>
130
- </td>
131
- </tr>
132
- </table>
133
- <table class="container" align="right" border="0" cellpadding="0" cellspacing="0" width="260" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
134
- <tr>
135
- <td class="container-cell container-cell-bottom" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;padding-bottom: 20px;">
136
- <img class="preserve-ratio" src="{! $support_icon }}" style="max-width: 62px;border: 0;outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;height: auto;" width="62" alt="" align="center">
137
- <br>
138
- <h4 style="color: #202020;font-family: Helvetica;font-size: 20px;font-weight: bold;line-height: 150%;margin: 0;padding: 0;text-align: center;">
139
- <a href="https://members.ithemes.com/panel/helpdesk.php" target="_blank" style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #0084CB;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;text-decoration: none;">{{ $support }}</a>
140
- <span class="pro-flag" style="background-color: #FFCC00;color: #000000;font-size: 10px;display: inline-block;padding: 3px;line-height: 1;position: relative;bottom: 10px;text-transform: uppercase;">{{ $pro }}</span>
141
- </h4>
142
- <br>
143
- <p style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;font-family: Helvetica;font-size: 16px;line-height: 150%;margin-top: 10px;margin-right: 0;margin-bottom: 10px;margin-left: 0;padding: 0;text-align: center;">{{ $support_content }}</p>
144
- </td>
145
- </tr>
146
- </table>
147
- </td>
148
- </tr>
149
- </table>
150
  </td>
151
  </tr>
152
  </table>
153
  </td>
154
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
155
  <tr>
156
- <td id="security-guide" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
157
- <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
 
 
 
 
 
 
 
158
  <tr>
159
- <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
160
- <table id="security-guide-container" class="container" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;border: 1px solid #CDCECE;background-color: #D3E8E9;">
161
- <tr>
162
- <td class="section-padding" valign="top" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-right: 20px;padding-left: 20px;">
163
- <table class="container" align="left" border="0" cellpadding="0" cellspacing="0" width="104" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
164
- <tr>
165
- <td class="section-padding-bottom" align="left" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-bottom: 20px;">
166
- <img class="preserve-ratio" src="{! $wp_security_book }}" style="max-width: 84px;border: 0;outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;height: auto;" width="84" alt="" align="center">
167
- </td>
168
- </tr>
169
- </table>
170
- <table class="container" align="right" border="0" cellpadding="0" cellspacing="0" width="454" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
171
- <tr>
172
- <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #6C6C6C;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: left;padding-bottom: 20px;">
173
- <h4 style="color: #6C6C6C;font-family: Helvetica;font-size: 18px;font-weight: bold;line-height: 150%;margin: 0;padding: 0;text-align: left;padding-bottom: 10px;">{{ $security_guide }}</h4>
174
- {{ $security_guide_content }}
175
- </td>
176
- </tr>
177
- </table>
178
- </td>
179
- </tr>
180
- </table>
181
  </td>
182
  </tr>
183
  </table>
184
  </td>
185
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
186
  <tr>
187
- <td id="footer-source-details" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
188
  <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
189
  <tr>
190
- <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
191
- <table class="container" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
192
- <tr>
193
- <td class="section-padding" align="center" valign="top" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-right: 20px;padding-left: 20px;">
194
- <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
195
- <tr>
196
- <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 200%;text-align: center;padding-bottom: 0;padding-top: 60px;">
197
- <img class="preserve-ratio" src="{! $footer_logo }}" style="max-width: 50px;border: 0;outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;height: auto;" width="50" alt="" align="center"><br>
198
- <br>
199
- <a href="{{ $security_settings_link }}" style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #0084CB;font-family: Helvetica;font-size: 11px;line-height: 200%;text-align: center;text-decoration: none;font-weight: bold;">{{ $unsubscribe_link_text }}</a>
200
- </td>
201
- </tr>
202
- </table>
203
- </td>
204
- </tr>
205
- </table>
206
  </td>
207
  </tr>
208
  </table>
@@ -212,7 +208,5 @@
212
  </td>
213
  </tr>
214
  </table>
215
- </center>
216
- </body>
217
-
218
- </html>
1
+ <tr>
2
+ <td class="footer-heading" align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
3
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
4
+ <tr>
5
+ <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
6
+ <table class="container" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
7
  <tr>
8
+ <td class="section-padding" align="center" valign="top" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-right: 20px;padding-left: 20px;">
9
  <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
10
  <tr>
11
+ <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;padding-bottom: 20px;">
12
+ <h2 style="color: #002030;font-family: Helvetica;font-size: 26px;font-weight: bold;line-height: 150%;margin: 0;padding: 0;text-align: center;">{{ $security_resources }}</h2>
 
 
 
 
 
 
 
 
 
 
 
 
13
  </td>
14
  </tr>
15
  </table>
16
  </td>
17
  </tr>
18
+ </table>
19
+ </td>
20
+ </tr>
21
+ </table>
22
+ </td>
23
+ </tr>
24
+ <tr>
25
+ <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
26
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
27
+ <tr>
28
+ <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
29
+ <table class="container" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
30
  <tr>
31
+ <td class="section-padding" valign="top" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-right: 20px;padding-left: 20px;">
32
+ <table class="container" align="left" border="0" cellpadding="0" cellspacing="0" width="260" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
33
  <tr>
34
+ <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;padding-bottom: 20px;">
35
+ <img class="preserve-ratio" src="{! $article_icon }}" style="max-width: 61px;border: 0;outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;height: auto;" width="61" alt="" align="center">
36
+ <br>
37
+ <h4 style="color: #202020;font-family: Helvetica;font-size: 20px;font-weight: bold;line-height: 150%;margin: 0;padding: 0;text-align: center;">
38
+ <a href="https://ithemes.com/category/wordpress-security/" target="_blank" style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #0084CB;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;text-decoration: none;">{{ $articles }}</a>
39
+ </h4>
40
+ <br>
41
+ <p style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;font-family: Helvetica;font-size: 16px;line-height: 150%;margin-top: 10px;margin-right: 0;margin-bottom: 10px;margin-left: 0;padding: 0;text-align: center;">{{ $articles_content }}</p>
42
+ </td>
43
+ </tr>
44
+ </table>
45
+ <table class="container" align="right" border="0" cellpadding="0" cellspacing="0" width="260" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
46
+ <tr>
47
+ <td class="container-cell container-cell-bottom" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;padding-bottom: 20px;">
48
+ <img class="preserve-ratio" src="{! $video_icon }}" style="max-width: 61px;border: 0;outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;height: auto;" width="61" alt="" align="center">
49
+ <br>
50
+ <h4 style="color: #202020;font-family: Helvetica;font-size: 20px;font-weight: bold;line-height: 150%;margin: 0;padding: 0;text-align: center;">
51
+ <a href="https://ithemes.com/tutorial/category/ithemes-security/" target="_blank" style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #0084CB;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;text-decoration: none;">{{ $tutorials }}</a>
52
+ </h4>
53
+ <br>
54
+ <p style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;font-family: Helvetica;font-size: 16px;line-height: 150%;margin-top: 10px;margin-right: 0;margin-bottom: 10px;margin-left: 0;padding: 0;text-align: center;">{{ $tutorials_content }}</p>
 
 
 
 
 
 
 
 
 
 
 
 
55
  </td>
56
  </tr>
57
  </table>
58
  </td>
59
  </tr>
60
+ </table>
61
+ </td>
62
+ </tr>
63
+ </table>
64
+ </td>
65
+ </tr>
66
+ <tr>
67
+ <td class="divider" align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
68
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
69
+ <tr>
70
+ <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
71
+ <table class="container" border="0" cellpadding="0" cellspacing="0" width="450" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
72
  <tr>
73
+ <td class="section-padding" align="center" valign="top" width="450" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-right: 20px;padding-left: 20px;">
74
+ <table class="divider-border" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;border-top-width: 1px;border-top-style: solid;border-top-color: #E8E8E8;">
75
  <tr>
76
+ <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 1px;text-align: center;padding-bottom: 20px;width: 450px;">
77
+ &nbsp;
 
 
 
 
 
 
 
 
 
 
 
 
78
  </td>
79
  </tr>
80
  </table>
81
  </td>
82
  </tr>
83
+ </table>
84
+ </td>
85
+ </tr>
86
+ </table>
87
+ </td>
88
+ </tr>
89
+ <tr>
90
+ <td class="footer-heading" align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
91
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
92
+ <tr>
93
+ <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
94
+ <table class="container" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
95
  <tr>
96
+ <td class="section-padding" align="center" valign="top" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-right: 20px;padding-left: 20px;">
97
  <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
98
  <tr>
99
+ <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;padding-bottom: 20px;">
100
+ <h2 style="color: #002030;font-family: Helvetica;font-size: 26px;font-weight: bold;line-height: 150%;margin: 0;padding: 0;text-align: center;">{{ $help_and_support }}</h2>
 
 
 
 
 
 
 
 
 
 
 
 
101
  </td>
102
  </tr>
103
  </table>
104
  </td>
105
  </tr>
106
+ </table>
107
+ </td>
108
+ </tr>
109
+ </table>
110
+ </td>
111
+ </tr>
112
+ <tr>
113
+ <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
114
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
115
+ <tr>
116
+ <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
117
+ <table class="container" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
118
  <tr>
119
+ <td class="section-padding" valign="top" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-right: 20px;padding-left: 20px;">
120
+ <table class="container" align="left" border="0" cellpadding="0" cellspacing="0" width="260" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
121
+ <tr>
122
+ <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;padding-bottom: 20px;">
123
+ <img class="preserve-ratio" src="{! $documentation_icon }}" style="max-width: 62px;border: 0;outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;height: auto;" width="62" alt="" align="center">
124
+ <br>
125
+ <h4 style="color: #202020;font-family: Helvetica;font-size: 20px;font-weight: bold;line-height: 150%;margin: 0;padding: 0;text-align: center;">
126
+ <a href="http://ithemes.com/codex/page/IThemes_Security" target="_blank" style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #0084CB;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;text-decoration: none;">{{ $documentation }}</a>
127
+ </h4>
128
+ <br>
129
+ <p style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;font-family: Helvetica;font-size: 16px;line-height: 150%;margin-top: 10px;margin-right: 0;margin-bottom: 10px;margin-left: 0;padding: 0;text-align: center;">{{ $documentation_content }}</p>
130
+ </td>
131
+ </tr>
132
+ </table>
133
+ <table class="container" align="right" border="0" cellpadding="0" cellspacing="0" width="260" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
134
  <tr>
135
+ <td class="container-cell container-cell-bottom" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;padding-bottom: 20px;">
136
+ <img class="preserve-ratio" src="{! $support_icon }}" style="max-width: 62px;border: 0;outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;height: auto;" width="62" alt="" align="center">
137
+ <br>
138
+ <h4 style="color: #202020;font-family: Helvetica;font-size: 20px;font-weight: bold;line-height: 150%;margin: 0;padding: 0;text-align: center;">
139
+ <a href="https://members.ithemes.com/panel/helpdesk.php" target="_blank" style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #0084CB;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;text-decoration: none;">{{ $support }}</a>
140
+ <span class="pro-flag" style="background-color: #FFCC00;color: #000000;font-size: 10px;display: inline-block;padding: 3px;line-height: 1;position: relative;bottom: 10px;text-transform: uppercase;">{{ $pro }}</span>
141
+ </h4>
142
+ <br>
143
+ <p style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;font-family: Helvetica;font-size: 16px;line-height: 150%;margin-top: 10px;margin-right: 0;margin-bottom: 10px;margin-left: 0;padding: 0;text-align: center;">{{ $support_content }}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  </td>
145
  </tr>
146
  </table>
147
  </td>
148
  </tr>
149
+ </table>
150
+ </td>
151
+ </tr>
152
+ </table>
153
+ </td>
154
+ </tr>
155
+ <tr>
156
+ <td id="security-guide" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
157
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
158
+ <tr>
159
+ <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
160
+ <table id="security-guide-container" class="container" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;border: 1px solid #CDCECE;background-color: #D3E8E9;">
161
  <tr>
162
+ <td class="section-padding" valign="top" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-right: 20px;padding-left: 20px;">
163
+ <table class="container" align="left" border="0" cellpadding="0" cellspacing="0" width="104" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
164
+ <tr>
165
+ <td class="section-padding-bottom" align="left" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-bottom: 20px;">
166
+ <img class="preserve-ratio" src="{! $wp_security_book }}" style="max-width: 84px;border: 0;outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;height: auto;" width="84" alt="" align="center">
167
+ </td>
168
+ </tr>
169
+ </table>
170
+ <table class="container" align="right" border="0" cellpadding="0" cellspacing="0" width="454" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
171
  <tr>
172
+ <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #6C6C6C;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: left;padding-bottom: 20px;">
173
+ <h4 style="color: #6C6C6C;font-family: Helvetica;font-size: 18px;font-weight: bold;line-height: 150%;margin: 0;padding: 0;text-align: left;padding-bottom: 10px;">{{ $security_guide }}</h4>
174
+ {{ $security_guide_content }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  </td>
176
  </tr>
177
  </table>
178
  </td>
179
  </tr>
180
+ </table>
181
+ </td>
182
+ </tr>
183
+ </table>
184
+ </td>
185
+ </tr>
186
+ <tr>
187
+ <td id="footer-source-details" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
188
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
189
+ <tr>
190
+ <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
191
+ <table class="container" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
192
  <tr>
193
+ <td class="section-padding" align="center" valign="top" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-right: 20px;padding-left: 20px;">
194
  <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
195
  <tr>
196
+ <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 200%;text-align: center;padding-bottom: 0;padding-top: 60px;">
197
+ <img class="preserve-ratio" src="{! $footer_logo }}" style="max-width: 50px;border: 0;outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;height: auto;" width="50" alt="" align="center"><br>
198
+ <br>
199
+ <span style="-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #666f72;font-family: Helvetica;font-size: 11px;line-height: 200%;text-align: center;text-decoration: none;font-weight: bold;">
200
+ {{ $unsubscribe_link_text }}
201
+ </span>
 
 
 
 
 
 
 
 
 
 
202
  </td>
203
  </tr>
204
  </table>
208
  </td>
209
  </tr>
210
  </table>
211
+ </td>
212
+ </tr>
 
 
core/lib/mail-templates/header.html CHANGED
@@ -1,3 +1,4 @@
 
1
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
  <html xmlns="http://www.w3.org/1999/xhtml" lang="{{ $lang }}">
3
 
@@ -49,11 +50,11 @@
49
  .lockouts-summary .container.left-column{margin-right:60px;}
50
  .lockouts-summary h4{color:#ACAAAA;font-size:16px;font-weight:normal;}
51
  .lockouts-summary p{color:#505050;font-size:30px;font-weight:bold;}
52
- .lockouts-table{border:1px solid #cdcece;font-family:Helvetica;font-size:14px;}
53
- .lockouts-table th,.lockouts-table td{border:1px solid #cdcece;padding:10px;}
54
- .lockouts-table th{text-align:left;font-weight:bold;padding:5px 10px;}
55
- .lockouts-table .row-label{font-style:italic;}
56
- .lockouts-table a,.lockouts-table b{font-size:14px;}
57
  .large-text h4{color:#505050;margin-bottom:10px;}
58
  .details-box-container{padding-top:20px;padding-bottom:20px;}
59
  .details-box{background-color:#E4EEF7;border:1px solid #CDCECE;}
@@ -132,7 +133,7 @@
132
  <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
133
  <tr>
134
  <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;padding-bottom: 20px;padding-top: 20px;">
135
- <img class="preserve-ratio" src="{{ $logo }}" style="max-width: 300px;border: 0;outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;height: auto;" width="300" height="127" alt="" align="center">
136
  </td>
137
  </tr>
138
  </table>
1
+
2
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
  <html xmlns="http://www.w3.org/1999/xhtml" lang="{{ $lang }}">
4
 
50
  .lockouts-summary .container.left-column{margin-right:60px;}
51
  .lockouts-summary h4{color:#ACAAAA;font-size:16px;font-weight:normal;}
52
  .lockouts-summary p{color:#505050;font-size:30px;font-weight:bold;}
53
+ .table{border:1px solid #cdcece;color: #404040;font-family:Helvetica;font-size:14px;}
54
+ .table th,.table td{border:1px solid #cdcece;padding:10px;}
55
+ .table th{text-align:left;font-weight:bold;padding:5px 10px;}
56
+ .table .row-label{font-style:italic;}
57
+ .table a,.table b{font-size:14px;}
58
  .large-text h4{color:#505050;margin-bottom:10px;}
59
  .details-box-container{padding-top:20px;padding-bottom:20px;}
60
  .details-box{background-color:#E4EEF7;border:1px solid #CDCECE;}
133
  <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
134
  <tr>
135
  <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;padding-bottom: 20px;padding-top: 20px;">
136
+ <img class="preserve-ratio" src="{{ $logo }}" style="max-width: 300px;border: 0;outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;height: auto;" alt="" align="center">
137
  </td>
138
  </tr>
139
  </table>
core/lib/mail-templates/large-code.html ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <tr>
2
+ <td class="details-box-container" align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-bottom: 20px;">
3
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
4
+ <tr>
5
+ <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
6
+ <table class="details-box container" border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;background-color: #CEEAF5;border: 1px solid #BFE7FF;">
7
+ <tr>
8
+ <td class="section-padding" align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-right: 40px;padding-left: 40px;">
9
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
10
+ <tr>
11
+ <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #0b70b9;font-family: monospace;font-size: 32px;font-weight:bold;line-height: 150%;text-align: center;padding-bottom: 20px;">
12
+ {{ $content }}
13
+ </td>
14
+ </tr>
15
+ </table>
16
+ </td>
17
+ </tr>
18
+ </table>
19
+ </td>
20
+ </tr>
21
+ </table>
22
+ </td>
23
+ </tr>
core/lib/mail-templates/list.html ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <tr>
2
+ <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
3
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
4
+ <tr>
5
+ <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
6
+ <table class="container" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
7
+ <tr>
8
+ <td class="section-padding" align="center" valign="top" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;padding-top: 20px;padding-right: 20px;padding-left: 20px;">
9
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
10
+ <tr>
11
+ <td class="container-cell" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;color: #404040;font-family: Helvetica;font-size: 16px;line-height: 150%;text-align: center;padding-bottom: 20px;">
12
+ <div style="text-align: left;">
13
+ <ul style="font-size: 15px; line-height: 1.2; text-align: left; margin: 20px 0 20px 20px; padding: 0;">
14
+ {{ $html }}
15
+ </ul>
16
+ </div>
17
+ </td>
18
+ </tr>
19
+ </table>
20
+ </td>
21
+ </tr>
22
+ </table>
23
+ </td>
24
+ </tr>
25
+ </table>
26
+ </td>
27
+ </tr>
core/lib/mail-templates/lockouts-table.html CHANGED
@@ -3,7 +3,7 @@
3
  <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
4
  <tr>
5
  <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
6
- <table class="lockouts-table" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;border:1px solid #CDCECE; font-family: Helvetica;">
7
  <tr>
8
  <th style="text-align: left;font-weight: bold;padding:5px 10px;border:1px solid #cdcece;">{{ $heading_types }}</th>
9
  <th style="text-align: left;font-weight: bold;padding:5px 10px;border:1px solid #cdcece;">{{ $heading_until }}</th>
3
  <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
4
  <tr>
5
  <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
6
+ <table class="table" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;border:1px solid #CDCECE; font-family: Helvetica;">
7
  <tr>
8
  <th style="text-align: left;font-weight: bold;padding:5px 10px;border:1px solid #cdcece;">{{ $heading_types }}</th>
9
  <th style="text-align: left;font-weight: bold;padding:5px 10px;border:1px solid #cdcece;">{{ $heading_until }}</th>
core/lib/mail-templates/table.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <tr>
2
+ <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
3
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
4
+ <tr>
5
+ <td align="center" valign="top" style="border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
6
+ <table class="table" border="0" cellpadding="0" cellspacing="0" style="width: auto;max-width: 850px;border-collapse: collapse;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;border:1px solid #CDCECE; color: #404040;font-family: Helvetica;">
7
+ {{ $html }}
8
+ </table>
9
+ </td>
10
+ </tr>
11
+ </table>
12
+ </td>
13
+ </tr>
core/lockout.php CHANGED
@@ -104,6 +104,9 @@ final class ITSEC_Lockout {
104
 
105
  add_action( 'itsec-settings-page-init', array( $this, 'init_settings_page' ) );
106
  add_action( 'itsec-logs-page-init', array( $this, 'init_settings_page' ) );
 
 
 
107
  }
108
 
109
  public function init_settings_page() {
@@ -183,17 +186,25 @@ final class ITSEC_Lockout {
183
  $user_id = $user->ID;
184
 
185
  if ( $username !== false && $username != '' ) {
186
- $username_check = $wpdb->get_results( "SELECT `lockout_username`, `lockout_type` FROM `" . $wpdb->base_prefix . "itsec_lockouts` WHERE `lockout_active`=1 AND `lockout_expire_gmt` > '" . date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] ) . "' AND `lockout_username`='" . $username . "';" );
 
 
 
187
  }
188
 
189
- $host_check = $wpdb->get_results( "SELECT `lockout_host`, `lockout_type` FROM `" . $wpdb->base_prefix . "itsec_lockouts` WHERE `lockout_active`=1 AND `lockout_expire_gmt` > '" . date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] ) . "' AND `lockout_host`='" . $host . "';" );
 
 
 
190
 
191
  }
192
 
193
  if ( $user_id !== 0 && $user_id !== null ) {
194
 
195
- $user_check = $wpdb->get_results( "SELECT `lockout_user`, `lockout_type` FROM `" . $wpdb->base_prefix . "itsec_lockouts` WHERE `lockout_active`=1 AND `lockout_expire_gmt` > '" . date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] ) . "' AND `lockout_user`=" . intval( $user_id ) . ";" );
196
-
 
 
197
  }
198
 
199
  $error = $wpdb->last_error;
@@ -306,8 +317,8 @@ final class ITSEC_Lockout {
306
 
307
  $host_count = $wpdb->get_var(
308
  $wpdb->prepare(
309
- "SELECT COUNT(*) FROM `" . $wpdb->base_prefix . "itsec_temp` WHERE `temp_date_gmt` > '%s' AND `temp_host`='%s';",
310
- date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] - ( $options['period'] * 60 ) ),
311
  $host
312
  )
313
  );
@@ -339,8 +350,8 @@ final class ITSEC_Lockout {
339
 
340
  $user_count = $wpdb->get_var(
341
  $wpdb->prepare(
342
- "SELECT COUNT(*) FROM `" . $wpdb->base_prefix . "itsec_temp` WHERE `temp_date_gmt` > '%s' AND (`temp_username`='%s' OR `temp_user`=%s);",
343
- date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] - ( $options['period'] * 60 ) ),
344
  sanitize_text_field( $username ),
345
  intval( $user_id )
346
  )
@@ -368,8 +379,8 @@ final class ITSEC_Lockout {
368
 
369
  $user_count = $wpdb->get_var(
370
  $wpdb->prepare(
371
- "SELECT COUNT(*) FROM `" . $wpdb->base_prefix . "itsec_temp` WHERE `temp_date_gmt` > '%s' AND `temp_username`='%s';",
372
- date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] - ( $options['period'] * 60 ) ),
373
  $username
374
  )
375
  );
@@ -539,66 +550,80 @@ final class ITSEC_Lockout {
539
  * @since 4.0
540
  *
541
  * @param string $type 'all', 'host', or 'user'
542
- * @param bool $current false for all lockouts, true for current lockouts
543
- * @param int $limit the maximum number of locks to return
544
  *
545
  * @return array all lockouts in the system
546
  */
547
- public function get_lockouts( $type = 'all', $current = false, $limit = 0 ) {
548
 
549
- global $wpdb, $itsec_globals;
550
 
551
- if ( $type !== 'all' || $current === true ) {
552
- $where = " WHERE ";
553
- } else {
554
- $where = '';
555
  }
556
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
557
  switch ( $type ) {
558
 
559
  case 'host':
560
- $type_statement = "`lockout_host` IS NOT NULL && `lockout_host` != ''";
561
  break;
562
  case 'user':
563
- $type_statement = "`lockout_user` != 0";
564
  break;
565
  case 'username':
566
- $type_statement = "`lockout_username` IS NOT NULL && `lockout_username` != ''";
567
- break;
568
- default:
569
- $type_statement = '';
570
  break;
571
-
572
  }
573
 
574
- if ( $current === true ) {
575
-
576
- if ( $type_statement !== '' ) {
577
- $and = ' AND ';
578
- } else {
579
- $and = '';
580
- }
581
-
582
- $active = $and . " `lockout_active`=1 AND `lockout_expire_gmt` > '" . date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] ) . "'";
583
-
584
- } else {
585
 
586
- $active = '';
 
 
587
 
 
588
  }
589
 
590
- if ( absint( $limit ) > 0 ) {
 
 
591
 
592
- $limit = " LIMIT " . absint( $limit );
 
 
593
 
 
 
 
594
  } else {
 
 
 
595
 
596
- $limit = '';
597
 
 
 
598
  }
599
 
600
- return $wpdb->get_results( "SELECT * FROM `" . $wpdb->base_prefix . "itsec_lockouts`" . $where . $type_statement . $active . $limit . ";", ARRAY_A );
601
-
602
  }
603
 
604
  /**
@@ -774,8 +799,8 @@ final class ITSEC_Lockout {
774
 
775
  $host_count = 1 + $wpdb->get_var(
776
  $wpdb->prepare(
777
- "SELECT COUNT(*) FROM `" . $wpdb->base_prefix . "itsec_lockouts` WHERE `lockout_expire_gmt` > '%s' AND `lockout_host`='%s';",
778
- date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] - $blacklist_seconds ),
779
  $host
780
  )
781
  );
@@ -892,9 +917,7 @@ final class ITSEC_Lockout {
892
 
893
  if ( $whitelisted === false ) {
894
 
895
- if ( ITSEC_Modules::get_setting( 'global', 'email_notifications' ) ) { //send email notifications
896
- $this->send_lockout_email( $good_host, $good_user, $good_username, $host_expiration, $user_expiration, $reason );
897
- }
898
 
899
  $lock_context = array(
900
  'type' => $type,
@@ -966,10 +989,10 @@ final class ITSEC_Lockout {
966
  */
967
  public function purge_lockouts() {
968
 
969
- global $wpdb, $itsec_globals;
970
 
971
- $wpdb->query( "DELETE FROM `" . $wpdb->base_prefix . "itsec_lockouts` WHERE `lockout_expire_gmt` < '" . date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] - ( ( ITSEC_Modules::get_setting( 'global', 'blacklist_period' ) + 1 ) * DAY_IN_SECONDS ) ) . "';" );
972
- $wpdb->query( "DELETE FROM `" . $wpdb->base_prefix . "itsec_temp` WHERE `temp_date_gmt` < '" . date( 'Y-m-d H:i:s', $itsec_globals['current_time_gmt'] - DAY_IN_SECONDS ) . "';" );
973
 
974
  }
975
 
@@ -1057,9 +1080,7 @@ final class ITSEC_Lockout {
1057
 
1058
  if ( $id !== null && trim( $id ) !== '' ) {
1059
 
1060
- $sanitized_id = intval( $id );
1061
-
1062
- $lockout = $wpdb->get_results( "SELECT * FROM `" . $wpdb->base_prefix . "itsec_lockouts` WHERE lockout_id = " . $sanitized_id . ";", ARRAY_A );
1063
 
1064
  if ( is_array( $lockout ) && sizeof( $lockout ) >= 1 ) {
1065
 
@@ -1069,7 +1090,7 @@ final class ITSEC_Lockout {
1069
  'lockout_active' => 0,
1070
  ),
1071
  array(
1072
- 'lockout_id' => $sanitized_id,
1073
  )
1074
  );
1075
 
@@ -1100,7 +1121,7 @@ final class ITSEC_Lockout {
1100
  'lockout_active' => 0,
1101
  ),
1102
  array(
1103
- 'lockout_id' => intval( $value ),
1104
  )
1105
  );
1106
 
@@ -1128,6 +1149,37 @@ final class ITSEC_Lockout {
1128
 
1129
  }
1130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1131
  /**
1132
  * Sends an email to notify site admins of lockouts
1133
  *
@@ -1143,17 +1195,13 @@ final class ITSEC_Lockout {
1143
  * @return void
1144
  */
1145
  private function send_lockout_email( $host, $user_id, $username, $host_expiration, $user_expiration, $reason ) {
1146
- if ( ITSEC_Modules::get_setting( 'global', 'digest_email' ) ) {
1147
- // The daily digest will show the relevant lockout details.
1148
- return;
1149
- }
1150
 
1151
- if ( ! ITSEC_Modules::get_setting( 'global', 'email_notifications', true ) ) {
1152
- // Email notifications are disabled.
 
1153
  return;
1154
  }
1155
 
1156
-
1157
  $lockouts = array();
1158
  $show_remove_ip_ban_message = false;
1159
  $show_remove_lockout_message = false;
@@ -1191,30 +1239,29 @@ final class ITSEC_Lockout {
1191
  }
1192
 
1193
 
1194
- require_once( ITSEC_Core::get_core_dir() . 'lib/class-itsec-mail.php' );
1195
- $mail = new ITSEC_Mail();
1196
 
1197
  $mail->add_header( esc_html__( 'Site Lockout Notification', 'better-wp-security' ), esc_html__( 'Site Lockout Notification', 'better-wp-security' ) );
1198
  $mail->add_lockouts_table( $lockouts );
1199
 
1200
  if ( $show_remove_lockout_message ) {
1201
  $mail->add_text( __( 'Release lockouts from the Active Lockouts section of the settings page.', 'better-wp-security' ) );
1202
- $mail->add_button( __( 'Visit Settings Page', 'better-wp-security' ), wp_login_url( ITSEC_Core::get_settings_page_url() ) );
1203
  }
1204
 
1205
  if ( $show_remove_ip_ban_message ) {
1206
  $mail->add_text( __( 'Release the permanent host ban from Ban Hosts list in the Banned Users section of the settings page.', 'better-wp-security' ) );
1207
- $mail->add_button( __( 'Visit Banned Users Settings', 'better-wp-security' ), wp_login_url( ITSEC_Core::get_settings_module_url( 'ban-users' ) ) );
1208
  }
1209
 
1210
  $mail->add_footer();
1211
 
1212
 
1213
- $subject = sprintf( esc_html__( '[%s] Site Lockout Notification', 'better-wp-security' ), esc_url( get_option( 'siteurl' ) ) );
1214
  $subject = apply_filters( 'itsec_lockout_email_subject', $subject );
1215
  $mail->set_subject( $subject, false );
1216
 
1217
- $mail->send();
1218
  }
1219
 
1220
  /**
104
 
105
  add_action( 'itsec-settings-page-init', array( $this, 'init_settings_page' ) );
106
  add_action( 'itsec-logs-page-init', array( $this, 'init_settings_page' ) );
107
+
108
+ add_filter( 'itsec_notifications', array( $this, 'register_notification' ) );
109
+ add_filter( 'itsec_lockout_notification_strings', array( $this, 'notification_strings' ) );
110
  }
111
 
112
  public function init_settings_page() {
186
  $user_id = $user->ID;
187
 
188
  if ( $username !== false && $username != '' ) {
189
+ $username_check = $wpdb->get_results( $wpdb->prepare(
190
+ "SELECT `lockout_username`, `lockout_type` FROM `{$wpdb->base_prefix}itsec_lockouts` WHERE `lockout_active`=1 AND `lockout_expire_gmt` > %s AND `lockout_username`= %s;",
191
+ date( 'Y-m-d H:i:s', ITSEC_Core::get_current_time_gmt() ), $username
192
+ ) );
193
  }
194
 
195
+ $host_check = $wpdb->get_results( $wpdb->prepare(
196
+ "SELECT `lockout_host`, `lockout_type` FROM `{$wpdb->base_prefix}itsec_lockouts` WHERE `lockout_active`=1 AND `lockout_expire_gmt` > %s AND `lockout_host`= %s;",
197
+ date( 'Y-m-d H:i:s', ITSEC_Core::get_current_time_gmt() ), $host
198
+ ) );
199
 
200
  }
201
 
202
  if ( $user_id !== 0 && $user_id !== null ) {
203
 
204
+ $user_check = $wpdb->get_results( $wpdb->prepare(
205
+ "SELECT `lockout_user`, `lockout_type` FROM `{$wpdb->base_prefix}itsec_lockouts` WHERE `lockout_active`=1 AND `lockout_expire_gmt` > %s AND `lockout_user`= %d;",
206
+ date( 'Y-m-d H:i:s', ITSEC_Core::get_current_time_gmt() ), $user_id
207
+ ) );
208
  }
209
 
210
  $error = $wpdb->last_error;
317
 
318
  $host_count = $wpdb->get_var(
319
  $wpdb->prepare(
320
+ "SELECT COUNT(*) FROM `" . $wpdb->base_prefix . "itsec_temp` WHERE `temp_date_gmt` > %s AND `temp_host`= %s;",
321
+ date( 'Y-m-d H:i:s', ITSEC_Core::get_current_time_gmt() - ( $options['period'] * 60 ) ),
322
  $host
323
  )
324
  );
350
 
351
  $user_count = $wpdb->get_var(
352
  $wpdb->prepare(
353
+ "SELECT COUNT(*) FROM `" . $wpdb->base_prefix . "itsec_temp` WHERE `temp_date_gmt` > '%s' AND (`temp_username`= %s OR `temp_user`= %d);",
354
+ date( 'Y-m-d H:i:s', ITSEC_Core::get_current_time_gmt() - ( $options['period'] * 60 ) ),
355
  sanitize_text_field( $username ),
356
  intval( $user_id )
357
  )
379
 
380
  $user_count = $wpdb->get_var(
381
  $wpdb->prepare(
382
+ "SELECT COUNT(*) FROM `" . $wpdb->base_prefix . "itsec_temp` WHERE `temp_date_gmt` > %s AND `temp_username`= %s;",
383
+ date( 'Y-m-d H:i:s', ITSEC_Core::get_current_time_gmt() - ( $options['period'] * 60 ) ),
384
  $username
385
  )
386
  );
550
  * @since 4.0
551
  *
552
  * @param string $type 'all', 'host', or 'user'
553
+ * @param array $args Additional arguments.
 
554
  *
555
  * @return array all lockouts in the system
556
  */
557
+ public function get_lockouts( $type = 'all', $args = array() ) {
558
 
559
+ global $wpdb;
560
 
561
+ if ( is_bool( $args ) ) {
562
+ $args = array( 'current' => $args );
 
 
563
  }
564
 
565
+ if ( func_num_args() === 3 ) {
566
+ $third = func_get_arg( 2 );
567
+
568
+ if ( $third && is_numeric( $third ) ) {
569
+ $args['limit'] = $third;
570
+ }
571
+ }
572
+
573
+ $args = wp_parse_args( $args, array(
574
+ 'current' => true,
575
+ ) );
576
+
577
+ $where = $limit = '';
578
+ $wheres = array();
579
+
580
  switch ( $type ) {
581
 
582
  case 'host':
583
+ $wheres[] = "`lockout_host` IS NOT NULL AND `lockout_host` != ''";
584
  break;
585
  case 'user':
586
+ $wheres[] = '`lockout_user` != 0';
587
  break;
588
  case 'username':
589
+ $wheres[] = "`lockout_username` IS NOT NULL AND `lockout_username` != ''";
 
 
 
590
  break;
 
591
  }
592
 
593
+ if ( $args['current'] ) {
594
+ $wheres[] = "`lockout_active` = 1 AND `lockout_expire_gmt` > '" . date( 'Y-m-d H:i:s', ITSEC_Core::get_current_time_gmt() ) . "'";
595
+ }
 
 
 
 
 
 
 
 
596
 
597
+ if ( isset( $args['after'] ) ) {
598
+ $after = is_int( $args['after'] ) ? $args['after'] : strtotime( $args['after'] );
599
+ $after = date( 'Y-m-d H:i:s', $after );
600
 
601
+ $wheres[] = "`lockout_start_gmt` > '{$after}'";
602
  }
603
 
604
+ if ( $wheres ) {
605
+ $where = ' WHERE ' . implode( ' AND ', $wheres );
606
+ }
607
 
608
+ if ( ! empty( $args['limit'] ) ) {
609
+ $limit = ' LIMIT ' . absint( $args['limit'] );
610
+ }
611
 
612
+ if ( isset( $args['return'] ) && 'count' === $args['return'] ) {
613
+ $select = 'SELECT COUNT(1) as COUNT';
614
+ $is_count = true;
615
  } else {
616
+ $select = 'SELECT *';
617
+ $is_count = false;
618
+ }
619
 
620
+ $results = $wpdb->get_results( "{$select} FROM `" . $wpdb->base_prefix . "itsec_lockouts`" . $where . $limit . ';', ARRAY_A );
621
 
622
+ if ( $is_count && $results ) {
623
+ return $results[0]['COUNT'];
624
  }
625
 
626
+ return $results;
 
627
  }
628
 
629
  /**
799
 
800
  $host_count = 1 + $wpdb->get_var(
801
  $wpdb->prepare(
802
+ "SELECT COUNT(*) FROM `" . $wpdb->base_prefix . "itsec_lockouts` WHERE `lockout_expire_gmt` > %s AND `lockout_host`= %s;",
803
+ date( 'Y-m-d H:i:s', ITSEC_Core::get_current_time_gmt() - $blacklist_seconds ),
804
  $host
805
  )
806
  );
917
 
918
  if ( $whitelisted === false ) {
919
 
920
+ $this->send_lockout_email( $good_host, $good_user, $good_username, $host_expiration, $user_expiration, $reason );
 
 
921
 
922
  $lock_context = array(
923
  'type' => $type,
989
  */
990
  public function purge_lockouts() {
991
 
992
+ global $wpdb;
993
 
994
+ $wpdb->query( "DELETE FROM `" . $wpdb->base_prefix . "itsec_lockouts` WHERE `lockout_expire_gmt` < '" . date( 'Y-m-d H:i:s', ITSEC_Core::get_current_time_gmt() - ( ( ITSEC_Modules::get_setting( 'global', 'blacklist_period' ) + 1 ) * DAY_IN_SECONDS ) ) . "';" );
995
+ $wpdb->query( "DELETE FROM `" . $wpdb->base_prefix . "itsec_temp` WHERE `temp_date_gmt` < '" . date( 'Y-m-d H:i:s', ITSEC_Core::get_current_time_gmt() - DAY_IN_SECONDS ) . "';" );
996
 
997
  }
998
 
1080
 
1081
  if ( $id !== null && trim( $id ) !== '' ) {
1082
 
1083
+ $lockout = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM `{$wpdb->base_prefix}itsec_lockouts` WHERE lockout_id = %d;", $id ), ARRAY_A );
 
 
1084
 
1085
  if ( is_array( $lockout ) && sizeof( $lockout ) >= 1 ) {
1086
 
1090
  'lockout_active' => 0,
1091
  ),
1092
  array(
1093
+ 'lockout_id' => (int) $id,
1094
  )
1095
  );
1096
 
1121
  'lockout_active' => 0,
1122
  ),
1123
  array(
1124
+ 'lockout_id' => (int) $value,
1125
  )
1126
  );
1127
 
1149
 
1150
  }
1151
 
1152
+ /**
1153
+ * Register the lockout notification.
1154
+ *
1155
+ * @param array $notifications
1156
+ *
1157
+ * @return array
1158
+ */
1159
+ public function register_notification( $notifications ) {
1160
+ $notifications['lockout'] = array(
1161
+ 'subject_editable' => true,
1162
+ 'recipient' => ITSEC_Notification_Center::R_USER_LIST_ADMIN_UPGRADE,
1163
+ 'schedule' => ITSEC_Notification_Center::S_NONE,
1164
+ 'optional' => true,
1165
+ );
1166
+
1167
+ return $notifications;
1168
+ }
1169
+
1170
+ /**
1171
+ * Get the strings for the lockout notification.
1172
+ *
1173
+ * @return array
1174
+ */
1175
+ public function notification_strings() {
1176
+ return array(
1177
+ 'label' => esc_html__( 'Site Lockouts', 'better-wp-security' ),
1178
+ 'description' => esc_html__( 'Various modules send emails to notify you when a user or host is locked out of your website.', 'better-wp-security' ),
1179
+ 'subject' => esc_html__( 'Site Lockout Notification', 'better-wp-security' ),
1180
+ );
1181
+ }
1182
+
1183
  /**
1184
  * Sends an email to notify site admins of lockouts
1185
  *
1195
  * @return void
1196
  */
1197
  private function send_lockout_email( $host, $user_id, $username, $host_expiration, $user_expiration, $reason ) {
 
 
 
 
1198
 
1199
+ $nc = ITSEC_Core::get_notification_center();
1200
+
1201
+ if ( ! $nc->is_notification_enabled( 'lockout' ) ) {
1202
  return;
1203
  }
1204
 
 
1205
  $lockouts = array();
1206
  $show_remove_ip_ban_message = false;
1207
  $show_remove_lockout_message = false;
1239
  }
1240
 
1241
 
1242
+ $mail = $nc->mail();
 
1243
 
1244
  $mail->add_header( esc_html__( 'Site Lockout Notification', 'better-wp-security' ), esc_html__( 'Site Lockout Notification', 'better-wp-security' ) );
1245
  $mail->add_lockouts_table( $lockouts );
1246
 
1247
  if ( $show_remove_lockout_message ) {
1248
  $mail->add_text( __( 'Release lockouts from the Active Lockouts section of the settings page.', 'better-wp-security' ) );
1249
+ $mail->add_button( __( 'Visit Settings Page', 'better-wp-security' ), ITSEC_Mail::filter_admin_page_url( ITSEC_Core::get_settings_page_url() ) );
1250
  }
1251
 
1252
  if ( $show_remove_ip_ban_message ) {
1253
  $mail->add_text( __( 'Release the permanent host ban from Ban Hosts list in the Banned Users section of the settings page.', 'better-wp-security' ) );
1254
+ $mail->add_button( __( 'Visit Banned Users Settings', 'better-wp-security' ), ITSEC_Mail::filter_admin_page_url( ITSEC_Core::get_settings_module_url( 'ban-users' ) ) );
1255
  }
1256
 
1257
  $mail->add_footer();
1258
 
1259
 
1260
+ $subject = $mail->prepend_site_url_to_subject( $nc->get_subject( 'lockout' ) );
1261
  $subject = apply_filters( 'itsec_lockout_email_subject', $subject );
1262
  $mail->set_subject( $subject, false );
1263
 
1264
+ $nc->send( 'lockout', $mail );
1265
  }
1266
 
1267
  /**
core/logger.php CHANGED
@@ -11,8 +11,7 @@ final class ITSEC_Logger {
11
  private
12
  $log_file,
13
  $logger_displays,
14
- $logger_modules,
15
- $module_path;
16
 
17
  /**
18
  * @access private
@@ -25,7 +24,6 @@ final class ITSEC_Logger {
25
 
26
  $this->logger_modules = array(); //array to hold information on modules using this feature
27
  $this->logger_displays = array(); //array to hold metabox information
28
- $this->module_path = ITSEC_Lib::get_module_path( __FILE__ );
29
 
30
  add_action( 'plugins_loaded', array( $this, 'register_modules' ) );
31
  add_action( 'plugins_loaded', array( $this, 'write_pending_events_to_file' ) );
11
  private
12
  $log_file,
13
  $logger_displays,
14
+ $logger_modules;
 
15
 
16
  /**
17
  * @access private
24
 
25
  $this->logger_modules = array(); //array to hold information on modules using this feature
26
  $this->logger_displays = array(); //array to hold metabox information
 
27
 
28
  add_action( 'plugins_loaded', array( $this, 'register_modules' ) );
29
  add_action( 'plugins_loaded', array( $this, 'write_pending_events_to_file' ) );
core/modules/404-detection/settings-page.php CHANGED
@@ -19,9 +19,11 @@ final class ITSEC_404_Detection_Settings_Page extends ITSEC_Module_Settings_Page
19
  }
20
 
21
  protected function render_settings( $form ) {
22
-
 
 
23
  ?>
24
- <?php echo $GLOBALS['itsec_lockout']->get_lockout_description(); ?>
25
  <table class="form-table">
26
  <tr>
27
  <th scope="row"><label for="itsec-404-detection-check_period"><?php _e( 'Minutes to Remember 404 Error (Check Period)', 'better-wp-security' ); ?></label></th>
19
  }
20
 
21
  protected function render_settings( $form ) {
22
+
23
+ /** @var ITSEC_Lockout $itsec_lockout */
24
+ global $itsec_lockout;
25
  ?>
26
+ <?php echo $itsec_lockout->get_lockout_description(); ?>
27
  <table class="form-table">
28
  <tr>
29
  <th scope="row"><label for="itsec-404-detection-check_period"><?php _e( 'Minutes to Remember 404 Error (Check Period)', 'better-wp-security' ); ?></label></th>
core/modules/admin-user/validator.php CHANGED
@@ -83,13 +83,14 @@ final class ITSEC_Admin_User_Validator extends ITSEC_Validator {
83
  } else { // we're only changing the username
84
 
85
  //query main user table
86
- $wpdb->query( "UPDATE `" . $wpdb->users . "` SET user_login = '" . esc_sql( $new_user ) . "' WHERE user_login='admin';" );
87
 
88
  if ( is_multisite() ) { //process sitemeta if we're in a multi-site situation
89
 
90
- $oldAdmins = $wpdb->get_var( "SELECT meta_value FROM `" . $wpdb->sitemeta . "` WHERE meta_key = 'site_admins'" );
91
- $newAdmins = str_replace( '5:"admin"', strlen( $new_user ) . ':"' . esc_sql( $new_user ) . '"', $oldAdmins );
92
- $wpdb->query( "UPDATE `" . $wpdb->sitemeta . "` SET meta_value = '" . esc_sql( $newAdmins ) . "' WHERE meta_key = 'site_admins'" );
 
93
 
94
  }
95
 
@@ -124,18 +125,19 @@ final class ITSEC_Admin_User_Validator extends ITSEC_Validator {
124
 
125
  if ( is_multisite() && $username !== null && validate_username( $new_user ) ) { //process sitemeta if we're in a multi-site situation
126
 
127
- $oldAdmins = $wpdb->get_var( "SELECT meta_value FROM `" . $wpdb->sitemeta . "` WHERE meta_key = 'site_admins'" );
128
- $newAdmins = str_replace( '5:"admin"', strlen( $new_user ) . ':"' . esc_sql( $new_user ) . '"', $oldAdmins );
129
- $wpdb->query( "UPDATE `" . $wpdb->sitemeta . "` SET meta_value = '" . esc_sql( $newAdmins ) . "' WHERE meta_key = 'site_admins'" );
 
130
 
131
  }
132
 
133
  $new_user = $wpdb->insert_id;
134
 
135
- $wpdb->query( "UPDATE `" . $wpdb->posts . "` SET post_author = '" . $new_user . "' WHERE post_author = 1;" );
136
- $wpdb->query( "UPDATE `" . $wpdb->usermeta . "` SET user_id = '" . $new_user . "' WHERE user_id = 1;" );
137
- $wpdb->query( "UPDATE `" . $wpdb->comments . "` SET user_id = '" . $new_user . "' WHERE user_id = 1;" );
138
- $wpdb->query( "UPDATE `" . $wpdb->links . "` SET link_owner = '" . $new_user . "' WHERE link_owner = 1;" );
139
 
140
  /**
141
  * Fires when the admin user with id of #1 has been changed.
83
  } else { // we're only changing the username
84
 
85
  //query main user table
86
+ $wpdb->query( $wpdb->prepare( "UPDATE `{$wpdb->users}` SET user_login = %s WHERE user_login = %s", $new_user, 'admin' ) );
87
 
88
  if ( is_multisite() ) { //process sitemeta if we're in a multi-site situation
89
 
90
+ $old_admins = $wpdb->get_var( "SELECT meta_value FROM `" . $wpdb->sitemeta . "` WHERE meta_key = 'site_admins'" );
91
+ // No need to escape the new username. It is already safe via validate_userame() which will check for quotes
92
+ $new_admins = str_replace( '5:"admin"', strlen( $new_user ) . ':"' . $new_user . '"', $old_admins );
93
+ $wpdb->query( $wpdb->prepare( "UPDATE `{$wpdb->sitemeta}` SET meta_value = %s WHERE meta_key = 'site_admins'", $new_admins ) );
94
 
95
  }
96
 
125
 
126
  if ( is_multisite() && $username !== null && validate_username( $new_user ) ) { //process sitemeta if we're in a multi-site situation
127
 
128
+ $old_admins = $wpdb->get_var( "SELECT meta_value FROM `{$wpdb->sitemeta}` WHERE meta_key = 'site_admins'" );
129
+ // No need to escape the new username. It is already safe via validate_userame() which will check for quotes
130
+ $new_admins = str_replace( '5:"admin"', strlen( $new_user ) . ':"' . $new_user . '"', $old_admins );
131
+ $wpdb->query( $wpdb->prepare( "UPDATE `{$wpdb->sitemeta}` SET meta_value = %s WHERE meta_key = 'site_admins'", $new_admins ) );
132
 
133
  }
134
 
135
  $new_user = $wpdb->insert_id;
136
 
137
+ $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->posts} SET post_author = %d WHERE post_author = 1", $new_user ) );
138
+ $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->usermeta} SET user_id = %d WHERE user_id = 1", $new_user ) );
139
+ $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->comments} SET user_id = %d WHERE user_id = 1", $new_user ) );
140
+ $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->links} SET link_owner = %d WHERE link_owner = 1", $new_user ) );
141
 
142
  /**
143
  * Fires when the admin user with id of #1 has been changed.
core/modules/backup/class-itsec-backup.php CHANGED
@@ -37,6 +37,9 @@ class ITSEC_Backup {
37
  add_action( 'itsec_execute_backup_cron', array( $this, 'do_backup' ) );
38
  add_filter( 'itsec_logger_modules', array( $this, 'register_logger' ) );
39
 
 
 
 
40
  if ( defined( 'ITSEC_BACKUP_CRON' ) && true === ITSEC_BACKUP_CRON ) {
41
  if ( ! wp_next_scheduled( 'itsec_execute_backup_cron' ) ) {
42
  wp_schedule_event( time(), 'daily', 'itsec_execute_backup_cron' );
@@ -145,7 +148,7 @@ class ITSEC_Backup {
145
  if ( $this->settings['all_sites'] ) {
146
  $tables = $wpdb->get_col( 'SHOW TABLES' );
147
  } else {
148
- $tables = $wpdb->get_col( 'SHOW TABLES LIKE "' . $wpdb->base_prefix . '%"' );
149
  }
150
 
151
  $max_rows_per_query = 1000;
@@ -168,7 +171,7 @@ class ITSEC_Backup {
168
  $has_more_rows = true;
169
 
170
  while ( $has_more_rows ) {
171
- $rows = $wpdb->get_results( "SELECT * FROM `$table` LIMIT $offset, $max_rows_per_query;", ARRAY_N );
172
 
173
  foreach ( $rows as $row ) {
174
  $sql = "INSERT INTO `$table` VALUES (";
@@ -276,15 +279,16 @@ class ITSEC_Backup {
276
  }
277
 
278
  private function send_mail( $file ) {
279
- require_once( ITSEC_Core::get_core_dir() . 'lib/class-itsec-mail.php' );
280
 
281
- $mail = new ITSEC_Mail();
282
- $mail->add_header( esc_html__( 'Database Backup', 'better-wp-security' ), sprintf( wp_kses( __( 'Site Database Backup for <b>%s</b>', 'better-wp-security' ), array( 'b' => array() ) ), date_i18n( get_option( 'date_format' ) ) ) );
 
 
283
  $mail->add_info_box( esc_html__( 'Attached is the database backup file for your site.', 'better-wp-security' ), 'attachment' );
284
 
285
 
286
  $mail->add_section_heading( esc_html__( 'Website', 'better-wp-security' ) );
287
- $mail->add_text( esc_html( network_home_url() ) );
288
 
289
  $mail->add_section_heading( esc_html__( 'Date', 'better-wp-security' ) );
290
  $mail->add_text( esc_html( date_i18n( get_option( 'date_format' ) ) ) );
@@ -292,16 +296,15 @@ class ITSEC_Backup {
292
  $mail->add_footer();
293
 
294
 
295
- $recipients = ITSEC_Modules::get_setting( 'global', 'backup_email' );
296
- $mail->set_recipients( $recipients );
297
 
298
- $subject = sprintf( esc_html__( '[%s] Database Backup', 'better-wp-security' ), esc_url( network_home_url() ) );
299
  $subject = apply_filters( 'itsec_backup_email_subject', $subject );
300
  $mail->set_subject( $subject, false );
301
 
302
  $mail->add_attachment( $file );
303
 
304
- return $mail->send();
305
  }
306
 
307
  /**
@@ -326,4 +329,40 @@ class ITSEC_Backup {
326
 
327
  }
328
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  }
37
  add_action( 'itsec_execute_backup_cron', array( $this, 'do_backup' ) );
38
  add_filter( 'itsec_logger_modules', array( $this, 'register_logger' ) );
39
 
40
+ add_filter( 'itsec_notifications', array( $this, 'register_notification' ) );
41
+ add_filter( 'itsec_backup_notification_strings', array( $this, 'notification_strings' ) );
42
+
43
  if ( defined( 'ITSEC_BACKUP_CRON' ) && true === ITSEC_BACKUP_CRON ) {
44
  if ( ! wp_next_scheduled( 'itsec_execute_backup_cron' ) ) {
45
  wp_schedule_event( time(), 'daily', 'itsec_execute_backup_cron' );
148
  if ( $this->settings['all_sites'] ) {
149
  $tables = $wpdb->get_col( 'SHOW TABLES' );
150
  } else {
151
+ $tables = $wpdb->get_col( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->base_prefix . '%' ) );
152
  }
153
 
154
  $max_rows_per_query = 1000;
171
  $has_more_rows = true;
172
 
173
  while ( $has_more_rows ) {
174
+ $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM `$table` LIMIT %d, %d", $offset, $max_rows_per_query ), ARRAY_N );
175
 
176
  foreach ( $rows as $row ) {
177
  $sql = "INSERT INTO `$table` VALUES (";
279
  }
280
 
281
  private function send_mail( $file ) {
 
282
 
283
+ $nc = ITSEC_Core::get_notification_center();
284
+ $mail = $nc->mail();
285
+
286
+ $mail->add_header( esc_html__( 'Database Backup', 'better-wp-security' ), sprintf( esc_html__( 'Site Database Backup for %s', 'better-wp-security' ), '<b>' . date_i18n( get_option( 'date_format' ) ) . '</b>' ) );
287
  $mail->add_info_box( esc_html__( 'Attached is the database backup file for your site.', 'better-wp-security' ), 'attachment' );
288
 
289
 
290
  $mail->add_section_heading( esc_html__( 'Website', 'better-wp-security' ) );
291
+ $mail->add_text( $mail->get_display_url() );
292
 
293
  $mail->add_section_heading( esc_html__( 'Date', 'better-wp-security' ) );
294
  $mail->add_text( esc_html( date_i18n( get_option( 'date_format' ) ) ) );
296
  $mail->add_footer();
297
 
298
 
299
+ $mail->set_recipients( $nc->get_recipients( 'backup' ) );
 
300
 
301
+ $subject = $mail->prepend_site_url_to_subject( $nc->get_subject( 'backup' ) );
302
  $subject = apply_filters( 'itsec_backup_email_subject', $subject );
303
  $mail->set_subject( $subject, false );
304
 
305
  $mail->add_attachment( $file );
306
 
307
+ return $nc->send( 'backup', $mail );
308
  }
309
 
310
  /**
329
 
330
  }
331
 
332
+ /**
333
+ * Register the Backup notification email.
334
+ *
335
+ * @param array $notifications
336
+ *
337
+ * @return array
338
+ */
339
+ public function register_notification( $notifications ) {
340
+
341
+ $method = ITSEC_Modules::get_setting( 'backup', 'method' );
342
+
343
+ if ( 0 === $method || 1 === $method ) {
344
+ $notifications['backup'] = array(
345
+ 'subject_editable' => true,
346
+ 'recipient' => ITSEC_Notification_Center::R_EMAIL_LIST,
347
+ 'schedule' => ITSEC_Notification_Center::S_NONE,
348
+ 'module' => 'backup',
349
+ );
350
+ }
351
+
352
+ return $notifications;
353
+ }
354
+
355
+ /**
356
+ * Register the strings for the Backup email.
357
+ *
358
+ * @return array
359
+ */
360
+ public function notification_strings() {
361
+ return array(
362
+ 'label' => esc_html__( 'Database Backup', 'better-wp-security' ),
363
+ 'description' => sprintf( esc_html__( 'The %1$sDatabase Backup%2$s module will send a copy of any backups to the email addresses listed below.', 'better-wp-security' ), '<a href="#" data-module-link="backup">', '</a>' ),
364
+ 'subject' => esc_html__( 'Database Backup', 'better-wp-security' ),
365
+ );
366
+ }
367
+
368
  }
core/modules/brute-force/settings-page.php CHANGED
@@ -19,9 +19,12 @@ final class ITSEC_Brute_Force_Settings_Page extends ITSEC_Module_Settings_Page {
19
  }
20
 
21
  protected function render_settings( $form ) {
 
 
 
22
 
23
  ?>
24
- <?php echo $GLOBALS['itsec_lockout']->get_lockout_description(); ?>
25
  <table class="form-table" id="brute_force-settings">
26
  <tr>
27
  <th scope="row"><label for="itsec-brute-force-max_attempts_host"><?php _e( 'Max Login Attempts Per Host', 'better-wp-security' ); ?></label></th>
19
  }
20
 
21
  protected function render_settings( $form ) {
22
+
23
+ /** @var ITSEC_Lockout $itsec_lockout */
24
+ global $itsec_lockout;
25
 
26
  ?>
27
+ <?php echo $itsec_lockout->get_lockout_description(); ?>
28
  <table class="form-table" id="brute_force-settings">
29
  <tr>
30
  <th scope="row"><label for="itsec-brute-force-max_attempts_host"><?php _e( 'Max Login Attempts Per Host', 'better-wp-security' ); ?></label></th>
core/modules/core/setup.php CHANGED
@@ -53,6 +53,14 @@ if ( ! class_exists( 'ITSEC_Core_Setup' ) ) {
53
  if ( $build < 4069 ) {
54
  delete_site_option( 'itsec_free_just_activated' );
55
  }
 
 
 
 
 
 
 
 
56
  }
57
 
58
  }
53
  if ( $build < 4069 ) {
54
  delete_site_option( 'itsec_free_just_activated' );
55
  }
56
+
57
+ if ( $build < 4076 ) {
58
+ $digest = wp_next_scheduled( 'itsec_digest_email' );
59
+
60
+ if ( $digest ) {
61
+ wp_unschedule_event( $digest, 'itsec_digest_email' );
62
+ }
63
+ }
64
  }
65
 
66
  }
core/modules/database-prefix/utility.php CHANGED
@@ -39,9 +39,7 @@ final class ITSEC_Database_Prefix_Utility {
39
  //complete with underscore
40
  $new_prefix .= '_';
41
 
42
- $new_prefix = esc_sql( $new_prefix ); //just be safe
43
-
44
- $check_prefix = $wpdb->get_results( 'SHOW TABLES LIKE "' . $new_prefix . '%";', ARRAY_N ); //if there are no tables with that prefix in the database set checkPrefix to false
45
 
46
  }
47
 
@@ -72,7 +70,7 @@ final class ITSEC_Database_Prefix_Utility {
72
 
73
 
74
 
75
- $tables = $wpdb->get_results( 'SHOW TABLES LIKE "' . $wpdb->base_prefix . '%"', ARRAY_N ); //retrieve a list of all tables in the DB
76
 
77
  //Rename each table
78
  foreach ( $tables as $table ) {
@@ -113,7 +111,7 @@ final class ITSEC_Database_Prefix_Utility {
113
 
114
  }
115
 
116
- $rows = $wpdb->get_results( 'SELECT * FROM `' . $new_prefix . 'usermeta`' ); //get all rows in usermeta
117
 
118
  //update all prefixes in usermeta
119
  foreach ( $rows as $row ) {
@@ -122,7 +120,7 @@ final class ITSEC_Database_Prefix_Utility {
122
 
123
  $pos = $new_prefix . substr( $row->meta_key, strlen( $wpdb->base_prefix ), strlen( $row->meta_key ) );
124
 
125
- $result = $wpdb->query( 'UPDATE `' . $new_prefix . 'usermeta` SET meta_key="' . $pos . '" WHERE meta_key= "' . $row->meta_key . '" LIMIT 1;' );
126
 
127
  if ( $result == false ) {
128
 
39
  //complete with underscore
40
  $new_prefix .= '_';
41
 
42
+ $check_prefix = $wpdb->get_results( $wpdb->prepare( 'SHOW TABLES LIKE %s;', $new_prefix . '%' ), ARRAY_N ); //if there are no tables with that prefix in the database set checkPrefix to false
 
 
43
 
44
  }
45
 
70
 
71
 
72
 
73
+ $tables = $wpdb->get_results( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->base_prefix . '%' ), ARRAY_N ); //retrieve a list of all tables in the DB
74
 
75
  //Rename each table
76
  foreach ( $tables as $table ) {
111
 
112
  }
113
 
114
+ $rows = $wpdb->get_results( "SELECT * FROM `{$new_prefix}usermeta`" ); //get all rows in usermeta
115
 
116
  //update all prefixes in usermeta
117
  foreach ( $rows as $row ) {
120
 
121
  $pos = $new_prefix . substr( $row->meta_key, strlen( $wpdb->base_prefix ), strlen( $row->meta_key ) );
122
 
123
+ $result = $wpdb->query( $wpdb->prepare( "UPDATE `{$new_prefix}usermeta` SET meta_key = %s WHERE meta_key = %s LIMIT 1", $pos, $row->meta_key ) );
124
 
125
  if ( $result == false ) {
126
 
core/modules/file-change/class-itsec-file-change.php CHANGED
@@ -37,6 +37,8 @@ class ITSEC_File_Change {
37
  add_filter( 'itsec_logger_displays', array( $this, 'itsec_logger_displays' ) ); //adds logs metaboxes
38
  add_filter( 'itsec_logger_modules', array( $this, 'itsec_logger_modules' ) );
39
  add_action( 'ithemes_sync_register_verbs', array( $this, 'register_sync_verbs' ) );
 
 
40
 
41
 
42
  if (
@@ -169,4 +171,36 @@ class ITSEC_File_Change {
169
  public function register_sync_verbs( $api ) {
170
  $api->register( 'itsec-perform-file-scan', 'Ithemes_Sync_Verb_ITSEC_Perform_File_Scan', dirname( __FILE__ ) . '/sync-verbs/itsec-perform-file-scan.php' );
171
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  }
37
  add_filter( 'itsec_logger_displays', array( $this, 'itsec_logger_displays' ) ); //adds logs metaboxes
38
  add_filter( 'itsec_logger_modules', array( $this, 'itsec_logger_modules' ) );
39
  add_action( 'ithemes_sync_register_verbs', array( $this, 'register_sync_verbs' ) );
40
+ add_filter( 'itsec_notifications', array( $this, 'register_notification' ) );
41
+ add_filter( 'itsec_file-change_notification_strings', array( $this, 'register_notification_strings' ) );
42
 
43
 
44
  if (
171
  public function register_sync_verbs( $api ) {
172
  $api->register( 'itsec-perform-file-scan', 'Ithemes_Sync_Verb_ITSEC_Perform_File_Scan', dirname( __FILE__ ) . '/sync-verbs/itsec-perform-file-scan.php' );
173
  }
174
+
175
+ /**
176
+ * Register the file change notification.
177
+ *
178
+ * @param array $notifications
179
+ *
180
+ * @return array
181
+ */
182
+ public function register_notification( $notifications ) {
183
+ $notifications['file-change'] = array(
184
+ 'recipient' => ITSEC_Notification_Center::R_USER_LIST_ADMIN_UPGRADE,
185
+ 'schedule' => ITSEC_Notification_Center::S_NONE,
186
+ 'subject_editable' => true,
187
+ 'optional' => true,
188
+ 'module' => 'file-change',
189
+ );
190
+
191
+ return $notifications;
192
+ }
193
+
194
+ /**
195
+ * Register the file change notification strings.
196
+ *
197
+ * @return array
198
+ */
199
+ public function register_notification_strings() {
200
+ return array(
201
+ 'label' => esc_html__( 'File Change', 'better-wp-security' ),
202
+ 'description' => sprintf( esc_html__( 'The %1$sFile Change Detection%2$s module will email a file scan report after changes have been detected.', 'better-wp-security' ), '<a href="#" data-module-link="file-change">', '</a>' ),
203
+ 'subject' => esc_html__( 'File Change Warning', 'better-wp-security' ),
204
+ );
205
+ }
206
  }
core/modules/file-change/scanner.php CHANGED
@@ -245,8 +245,6 @@ final class ITSEC_File_Change_Scanner {
245
  if (
246
  true === $send_email &&
247
  false !== $scheduled_call &&
248
- isset( $this->settings['email'] ) &&
249
- true === $this->settings['email'] &&
250
  (
251
  0 < $files_added_count ||
252
  0 < $files_changed_count ||
@@ -270,8 +268,7 @@ final class ITSEC_File_Change_Scanner {
270
  ! isset( get_current_screen()->id ) ||
271
  false === strpos( get_current_screen()->id, 'security_page_toplevel_page_itsec_logs' )
272
  ) &&
273
- isset( $this->settings['notify_admin'] ) &&
274
- true === $this->settings['notify_admin']
275
  ) {
276
  ITSEC_Modules::set_setting( 'file-change', 'show_warning', true );
277
  }
@@ -325,74 +322,9 @@ final class ITSEC_File_Change_Scanner {
325
  * @return string report details
326
  */
327
  public function get_email_report( $email_details ) {
 
328
 
329
- //seperate array by category
330
- $added = $email_details[3]['added'];
331
- $removed = $email_details[3]['removed'];
332
- $changed = $email_details[3]['changed'];
333
- $report = '<strong>' . __( 'Scan Time:', 'better-wp-security' ) . '</strong> ' . date( 'l, F jS g:i a e', ITSEC_Core::get_current_time() ) . "<br />" . PHP_EOL;
334
- $report .= '<strong>' . __( 'Files Added:', 'better-wp-security' ) . '</strong> ' . $email_details[0] . "<br />" . PHP_EOL;
335
- $report .= '<strong>' . __( 'Files Deleted:', 'better-wp-security' ) . '</strong> ' . $email_details[1] . "<br />" . PHP_EOL;
336
- $report .= '<strong>' . __( 'Files Modified:', 'better-wp-security' ) . '</strong> ' . $email_details[2] . "<br />" . PHP_EOL;
337
- $report .= '<strong>' . __( 'Memory Used:', 'better-wp-security' ) . '</strong> ' . $email_details[3]['memory'] . " MB<br />" . PHP_EOL;
338
-
339
- $report .= $this->build_table_section( __( 'Added', 'better-wp-security' ), $added );
340
- $report .= $this->build_table_section( __( 'Deleted', 'better-wp-security' ), $removed );
341
- $report .= $this->build_table_section( __( 'Modified', 'better-wp-security' ), $changed );
342
-
343
- return $report;
344
-
345
- }
346
-
347
- /**
348
- * Builds table section for file report
349
- *
350
- * Builds the individual table areas for files added, changed and deleted that goes in the file
351
- * change notification emails.
352
- *
353
- * @since 4.6.0
354
- *
355
- * @access private
356
- *
357
- * @param string $title User readable title to display
358
- * @param array $files array of files to build the report on
359
- *
360
- * @return string the markup with the given files to be added to the report
361
- */
362
- private function build_table_section( $title, $files ) {
363
-
364
- $section = '<h4>' . __( 'Files', 'better-wp-security' ) . ' ' . $title . '</h4>';
365
- $section .= '<table border="1" style="width: 100%; text-align: center;">' . PHP_EOL;
366
- $section .= '<tr>' . PHP_EOL;
367
- $section .= '<th>' . __( 'File', 'better-wp-security' ) . '</th>' . PHP_EOL;
368
- $section .= '<th>' . __( 'Modified', 'better-wp-security' ) . '</th>' . PHP_EOL;
369
- $section .= '<th>' . __( 'File Hash', 'better-wp-security' ) . '</th>' . PHP_EOL;
370
- $section .= '</tr>' . PHP_EOL;
371
-
372
- if ( isset( $files ) && is_array( $files ) && 0 < sizeof( $files ) ) {
373
-
374
- foreach ( $files as $item => $attr ) {
375
-
376
- $section .= '<tr>' . PHP_EOL;
377
- $section .= '<td>' . $item . '</td>' . PHP_EOL;
378
- $section .= '<td>' . date( 'l F jS, Y \a\t g:i a e', ( isset( $attr['mod_date'] ) ? $attr['mod_date'] : $attr['d'] ) ) . '</td>' . PHP_EOL;
379
- $section .= '<td>' . ( isset( $attr['hash'] ) ? $attr['hash'] : $attr['h'] ) . '</td>' . PHP_EOL;
380
- $section .= '</tr>' . PHP_EOL;
381
-
382
- }
383
-
384
- } else {
385
-
386
- $section .= '<tr>' . PHP_EOL;
387
- $section .= '<td colspan="3">' . __( 'No files were changed.', 'better-wp-security' ) . '</td>' . PHP_EOL;
388
- $section .= '</tr>' . PHP_EOL;
389
-
390
- }
391
-
392
- $section .= '</table>' . PHP_EOL;
393
-
394
- return $section;
395
-
396
  }
397
 
398
  /**
@@ -577,50 +509,87 @@ final class ITSEC_File_Change_Scanner {
577
  */
578
  private function send_notification_email( $email_details ) {
579
 
580
- $itsec_notify = ITSEC_Core::get_itsec_notify();
581
 
582
- if ( ! ITSEC_Modules::get_setting( 'global', 'digest_email' ) ) {
 
 
583
 
584
- $headers = 'From: ' . get_bloginfo( 'name' ) . ' <' . get_option( 'admin_email' ) . '>' . "\r\n";
585
- $subject = '[' . get_option( 'siteurl' ) . '] ' . __( 'WordPress File Change Warning', 'better-wp-security' ) . ' ' . date( 'l, F jS, Y \a\\t g:i a e', ITSEC_Core::get_current_time() );
586
 
587
- $body = '<p>' . __( 'A file (or files) on your site at ', 'better-wp-security' ) . ' ' . get_option( 'siteurl' ) . __( ' have been changed. Please review the report below to verify changes are not the result of a compromise.', 'better-wp-security' ) . '</p>';
588
- $body .= $this->get_email_report( $email_details ); //get report
 
589
 
590
- $args = array(
591
- 'headers' => $headers,
592
- 'message' => $body,
593
- 'subject' => $subject,
594
- );
595
 
596
- $itsec_notify->notify( $args );
 
 
 
 
 
 
 
 
597
 
598
- } else {
 
 
 
 
599
 
600
- $changed = $email_details[0] + $email_details[1] + $email_details[2];
 
601
 
602
- if ( $changed > 0 ) {
603
- $itsec_notify->register_file_change();
604
- }
605
 
 
 
 
 
 
 
 
 
 
 
606
  }
607
 
 
 
 
 
 
 
 
 
608
  }
609
 
610
  /**
611
- * Set HTML content type for email
612
  *
613
- * This filter allows for the content type of the file change notification emails to be set to
614
- * HTML in order to send the tables and related data included in file change reporting.
615
  *
616
- * @since 4.0.0
617
- *
618
- * @return string html content type
619
  */
620
- public function set_html_content_type() {
 
621
 
622
- return 'text/html';
 
623
 
624
- }
 
 
 
 
 
625
 
 
 
626
  }
245
  if (
246
  true === $send_email &&
247
  false !== $scheduled_call &&
 
 
248
  (
249
  0 < $files_added_count ||
250
  0 < $files_changed_count ||
268
  ! isset( get_current_screen()->id ) ||
269
  false === strpos( get_current_screen()->id, 'security_page_toplevel_page_itsec_logs' )
270
  ) &&
271
+ ! empty( $this->settings['notify_admin'] )
 
272
  ) {
273
  ITSEC_Modules::set_setting( 'file-change', 'show_warning', true );
274
  }
322
  * @return string report details
323
  */
324
  public function get_email_report( $email_details ) {
325
+ _deprecated_function( __METHOD__, '3.9.0' );
326
 
327
+ return $this->generate_notification_email( $email_details )->get_content();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  }
329
 
330
  /**
509
  */
510
  private function send_notification_email( $email_details ) {
511
 
512
+ $changed = $email_details[0] + $email_details[1] + $email_details[2];
513
 
514
+ if ( $changed <= 0 ) {
515
+ return;
516
+ }
517
 
518
+ $nc = ITSEC_Core::get_notification_center();
 
519
 
520
+ if ( $nc->is_notification_enabled( 'digest' ) ) {
521
+ $nc->enqueue_data( 'digest', array( 'type' => 'file-change' ) );
522
+ }
523
 
524
+ if ( $nc->is_notification_enabled( 'file-change' ) ) {
525
+ $mail = $this->generate_notification_email( $email_details );
526
+ $nc->send( 'file-change', $mail );
527
+ }
528
+ }
529
 
530
+ /**
531
+ * Generate the notification email.
532
+ *
533
+ * @param array $email_details
534
+ *
535
+ * @return ITSEC_Mail
536
+ */
537
+ private function generate_notification_email( $email_details ) {
538
+ $mail = ITSEC_Core::get_notification_center()->mail();
539
 
540
+ $mail->add_header(
541
+ esc_html__( 'File Change Warning', 'better-wp-security' ),
542
+ sprintf( esc_html__( 'File Scan Report for %s', 'better-wp-security' ), '<b>' . date_i18n( get_option( 'date_format' ) ) . '</b>' )
543
+ );
544
+ $mail->add_text( esc_html__( 'A file (or files) on your site have been changed. Please review the report below to verify changes are not the result of a compromise.', 'better-wp-security' ) );
545
 
546
+ $mail->add_section_heading( esc_html__( 'Scan Summary', 'better-wp-security' ) );
547
+ $mail->add_file_change_summary( $email_details[0], $email_details[1], $email_details[2] );
548
 
549
+ $mail->add_section_heading( esc_html__( 'Scan Details', 'better-wp-security' ) );
 
 
550
 
551
+ $headers = array( esc_html__( 'File', 'better-wp-security' ), esc_html__( 'Modified', 'better-wp-security' ), esc_html__( 'File Hash', 'better-wp-security' ) );
552
+
553
+ if ( $email_details[0] ) {
554
+ $mail->add_large_text( esc_html__( 'Added Files', 'better-wp-security' ) );
555
+ $mail->add_table( $headers, $this->generate_email_rows( $email_details[3]['added'] ) );
556
+ }
557
+
558
+ if ( $email_details[1] ) {
559
+ $mail->add_large_text( esc_html__( 'Removed Files', 'better-wp-security' ) );
560
+ $mail->add_table( $headers, $this->generate_email_rows( $email_details[3]['removed'] ) );
561
  }
562
 
563
+ if ( $email_details[2] ) {
564
+ $mail->add_large_text( esc_html__( 'Changed Files', 'better-wp-security' ) );
565
+ $mail->add_table( $headers, $this->generate_email_rows( $email_details[3]['changed'] ) );
566
+ }
567
+
568
+ $mail->add_footer();
569
+
570
+ return $mail;
571
  }
572
 
573
  /**
574
+ * Generate email report rows for a series of files.
575
  *
576
+ * @param array $files
 
577
  *
578
+ * @return array
 
 
579
  */
580
+ private function generate_email_rows( $files ) {
581
+ $rows = array();
582
 
583
+ foreach ( $files as $item => $attr ) {
584
+ $time = isset( $attr['mod_date'] ) ? $attr['mod_date'] : $attr['d'];
585
 
586
+ $rows[] = array(
587
+ $item,
588
+ ITSEC_Lib::date_format_i18n_and_local_timezone( $time ),
589
+ isset( $attr['hash'] ) ? $attr['hash'] : $attr['h']
590
+ );
591
+ }
592
 
593
+ return $rows;
594
+ }
595
  }
core/modules/file-change/settings-page.php CHANGED
@@ -125,14 +125,6 @@ final class ITSEC_File_Change_Settings_Page extends ITSEC_Module_Settings_Page {
125
  <label for="itsec-file-change-types"><?php _e( 'File types listed here will not be checked for changes. While it is possible to change files such as images it is quite rare and nearly all known WordPress attacks exploit php, js and other text files.', 'better-wp-security' ); ?></label>
126
  </td>
127
  </tr>
128
- <tr>
129
- <th scope="row"><label for="itsec-file-change-email"><?php _e( 'Email File Change Notifications', 'better-wp-security' ); ?></label></th>
130
- <td>
131
- <?php $form->add_checkbox( 'email' ); ?>
132
- <label for="itsec-file-change-email"><?php _e( 'Email file change notifications', 'better-wp-security' ); ?></label>
133
- <p class="description"><?php _e( 'Notifications will be sent to all emails set to receive notifications on the global settings page.', 'better-wp-security' ); ?></p>
134
- </td>
135
- </tr>
136
  <tr>
137
  <th scope="row"><label for="itsec-file-change-notify_admin"><?php _e( 'Display File Change Admin Warning', 'better-wp-security' ); ?></label></th>
138
  <td>
125
  <label for="itsec-file-change-types"><?php _e( 'File types listed here will not be checked for changes. While it is possible to change files such as images it is quite rare and nearly all known WordPress attacks exploit php, js and other text files.', 'better-wp-security' ); ?></label>
126
  </td>
127
  </tr>
 
 
 
 
 
 
 
 
128
  <tr>
129
  <th scope="row"><label for="itsec-file-change-notify_admin"><?php _e( 'Display File Change Admin Warning', 'better-wp-security' ); ?></label></th>
130
  <td>
core/modules/file-change/settings.php CHANGED
@@ -18,7 +18,6 @@ final class ITSEC_File_Change_Settings extends ITSEC_Settings {
18
  '.mo',
19
  '.po'
20
  ),
21
- 'email' => true,
22
  'notify_admin' => true,
23
  'last_run' => 0,
24
  'last_chunk' => false,
18
  '.mo',
19
  '.po'
20
  ),
 
21
  'notify_admin' => true,
22
  'last_run' => 0,
23
  'last_chunk' => false,
core/modules/file-change/validator.php CHANGED
@@ -18,14 +18,14 @@ class ITSEC_File_Change_Validator extends ITSEC_Validator {
18
  $this->settings['show_warning'] = $previous_settings['show_warning'];
19
  }
20
 
21
- $this->set_previous_if_empty( array( 'latest_changes' ) );
22
  $this->vars_to_skip_validate_matching_types[] = 'last_chunk';
 
23
 
24
  $this->sanitize_setting( 'bool', 'split', __( 'Split File Scanning', 'better-wp-security' ) );
25
  $this->sanitize_setting( array( 'exclude', 'include' ), 'method', __( 'Include/Exclude Files and Folders', 'better-wp-security' ) );
26
  $this->sanitize_setting( 'newline-separated-array', 'file_list', __( 'Files and Folders List', 'better-wp-security' ) );
27
  $this->sanitize_setting( 'newline-separated-extensions', 'types', __( 'Ignore File Types', 'better-wp-security' ) );
28
- $this->sanitize_setting( 'bool', 'email', __( 'Email File Change Notifications', 'better-wp-security' ) );
29
  $this->sanitize_setting( 'bool', 'notify_admin', __( 'Display File Change Admin Warning', 'better-wp-security' ) );
30
  $this->sanitize_setting( 'positive-int', 'last_run', __( 'Last Run', 'better-wp-security' ), false );
31
 
18
  $this->settings['show_warning'] = $previous_settings['show_warning'];
19
  }
20
 
21
+ $this->set_previous_if_empty( array( 'latest_changes', 'email' ) );
22
  $this->vars_to_skip_validate_matching_types[] = 'last_chunk';
23
+ $this->vars_to_skip_validate_matching_fields[] = 'email';
24
 
25
  $this->sanitize_setting( 'bool', 'split', __( 'Split File Scanning', 'better-wp-security' ) );
26
  $this->sanitize_setting( array( 'exclude', 'include' ), 'method', __( 'Include/Exclude Files and Folders', 'better-wp-security' ) );
27
  $this->sanitize_setting( 'newline-separated-array', 'file_list', __( 'Files and Folders List', 'better-wp-security' ) );
28
  $this->sanitize_setting( 'newline-separated-extensions', 'types', __( 'Ignore File Types', 'better-wp-security' ) );
 
29
  $this->sanitize_setting( 'bool', 'notify_admin', __( 'Display File Change Admin Warning', 'better-wp-security' ) );
30
  $this->sanitize_setting( 'positive-int', 'last_run', __( 'Last Run', 'better-wp-security' ), false );
31
 
core/modules/global/settings-page.php CHANGED
@@ -83,29 +83,6 @@ final class ITSEC_Global_Settings_Page extends ITSEC_Module_Settings_Page {
83
  <p class="description"><?php _e( 'Whether or not iThemes Security should be allowed to write to wp-config.php and .htaccess automatically. If disabled you will need to manually place configuration options in those files.', 'better-wp-security' ); ?></p>
84
  </td>
85
  </tr>
86
- <tr>
87
- <th scope="row"><label for="itsec-global-notification_email"><?php _e( 'Notification Email', 'better-wp-security' ); ?></label></th>
88
- <td>
89
- <?php $form->add_textarea( 'notification_email', array( 'class' => 'textarea-small' ) ); ?>
90
- <p class="description"><?php _e( 'The email address(es) all security notifications will be sent to. One address per line.', 'better-wp-security' ); ?></p>
91
- </td>
92
- </tr>
93
- <tr>
94
- <th scope="row"><label for="itsec-global-digest_email"><?php _e( 'Send Digest Email', 'better-wp-security' ); ?></label></th>
95
- <td>
96
- <?php $form->add_checkbox( 'digest_email' ); ?>
97
- <label for="itsec-global-digest_email"><?php _e( 'Send digest email', 'better-wp-security' ); ?></label>
98
- <p class="description"><?php _e( 'During periods of heavy attack or other times a security plugin can generate a LOT of email just telling you that it is doing its job. Turning this on will reduce the emails from this plugin to no more than one per day for any notification.', 'better-wp-security' ); ?></p>
99
- </td>
100
- </tr>
101
- <tr>
102
- <th scope="row"><label for="itsec-global-backup_email"><?php _e( 'Backup Delivery Email', 'better-wp-security' ); ?></label></th>
103
- <td>
104
- <?php $form->add_textarea( 'backup_email', array( 'class' => 'textarea-small' ) ); ?>
105
- <br />
106
- <p class="description"><?php _e( 'The email address(es) all database backups will be sent to. One address per line.', 'better-wp-security' ); ?></p>
107
- </td>
108
- </tr>
109
  <tr>
110
  <th scope="row"><label for="itsec-global-lockout_message"><?php _e( 'Host Lockout Message', 'better-wp-security' ); ?></label></th>
111
  <td>
@@ -183,14 +160,6 @@ final class ITSEC_Global_Settings_Page extends ITSEC_Module_Settings_Page {
183
  <p class="description"><strong><?php _e( 'This white list will prevent any IP listed from triggering an automatic lockout. You can still block the IP address manually in the banned users settings.', 'better-wp-security' ); ?></strong></p>
184
  </td>
185
  </tr>
186
- <tr>
187
- <th scope="row"><label for="itsec-global-email_notifications"><?php _e( 'Email Lockout Notifications', 'better-wp-security' ); ?></label></th>
188
- <td>
189
- <?php $form->add_checkbox( 'email_notifications' ); ?>
190
- <label for="itsec-global-email_notifications"><?php _e( 'Enable Email Lockout Notifications', 'better-wp-security' ); ?></label>
191
- <p class="description"><?php _e( 'This feature will trigger an email to be sent to the email addresses listed in the Notification Email setting whenever a host or user is locked out of the system.', 'better-wp-security' ); ?></p>
192
- </td>
193
- </tr>
194
  <tr>
195
  <th scope="row"><label for="itsec-global-log_type"><?php _e( 'Log Type', 'better-wp-security' ); ?></label></th>
196
  <td>
83
  <p class="description"><?php _e( 'Whether or not iThemes Security should be allowed to write to wp-config.php and .htaccess automatically. If disabled you will need to manually place configuration options in those files.', 'better-wp-security' ); ?></p>
84
  </td>
85
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  <tr>
87
  <th scope="row"><label for="itsec-global-lockout_message"><?php _e( 'Host Lockout Message', 'better-wp-security' ); ?></label></th>
88
  <td>
160
  <p class="description"><strong><?php _e( 'This white list will prevent any IP listed from triggering an automatic lockout. You can still block the IP address manually in the banned users settings.', 'better-wp-security' ); ?></strong></p>
161
  </td>
162
  </tr>
 
 
 
 
 
 
 
 
163
  <tr>
164
  <th scope="row"><label for="itsec-global-log_type"><?php _e( 'Log Type', 'better-wp-security' ); ?></label></th>
165
  <td>
core/modules/global/settings.php CHANGED
@@ -6,18 +6,13 @@ final class ITSEC_Global_Settings_New extends ITSEC_Settings {
6
  }
7
 
8
  public function get_defaults() {
9
- $email = get_option( 'admin_email' );
10
-
11
  return array(
12
- 'notification_email' => array( $email ),
13
- 'backup_email' => array( $email ),
14
  'lockout_message' => __( 'error', 'better-wp-security' ),
15
  'user_lockout_message' => __( 'You have been locked out due to too many invalid login attempts.', 'better-wp-security' ),
16
  'community_lockout_message' => __( 'Your IP address has been flagged as a threat by the iThemes Security network.', 'better-wp-security' ),
17
  'blacklist' => true,
18
  'blacklist_count' => 3,
19
  'blacklist_period' => 7,
20
- 'email_notifications' => true,
21
  'lockout_period' => 15,
22
  'lockout_white_list' => array(),
23
  'log_rotation' => 14,
@@ -30,14 +25,11 @@ final class ITSEC_Global_Settings_New extends ITSEC_Settings {
30
  'infinitewp_compatibility' => false,
31
  'did_upgrade' => false,
32
  'lock_file' => false,
33
- 'digest_email' => false,
34
  'proxy_override' => false,
35
  'hide_admin_bar' => false,
36
  'show_error_codes' => false,
37
  'show_new_dashboard_notice' => true,
38
  'show_security_check' => true,
39
- 'digest_last_sent' => 0,
40
- 'digest_messages' => array(),
41
  'build' => 0,
42
  'activation_timestamp' => 0,
43
  );
6
  }
7
 
8
  public function get_defaults() {
 
 
9
  return array(
 
 
10
  'lockout_message' => __( 'error', 'better-wp-security' ),
11
  'user_lockout_message' => __( 'You have been locked out due to too many invalid login attempts.', 'better-wp-security' ),
12
  'community_lockout_message' => __( 'Your IP address has been flagged as a threat by the iThemes Security network.', 'better-wp-security' ),
13
  'blacklist' => true,
14
  'blacklist_count' => 3,
15
  'blacklist_period' => 7,
 
16
  'lockout_period' => 15,
17
  'lockout_white_list' => array(),
18
  'log_rotation' => 14,
25
  'infinitewp_compatibility' => false,
26
  'did_upgrade' => false,
27
  'lock_file' => false,
 
28
  'proxy_override' => false,
29
  'hide_admin_bar' => false,
30
  'show_error_codes' => false,
31
  'show_new_dashboard_notice' => true,
32
  'show_security_check' => true,
 
 
33
  'build' => 0,
34
  'activation_timestamp' => 0,
35
  );
core/modules/global/validator.php CHANGED
@@ -19,14 +19,13 @@ class ITSEC_Global_Validator extends ITSEC_Validator {
19
  }
20
 
21
 
22
- $this->set_previous_if_empty( array( 'did_upgrade', 'log_info', 'show_new_dashboard_notice', 'show_security_check', 'digest_last_sent', 'digest_messages', 'build', 'activation_timestamp', 'lock_file' ) );
 
23
  $this->set_default_if_empty( array( 'log_location', 'nginx_file' ) );
24
 
25
 
26
  $this->sanitize_setting( 'bool', 'write_files', __( 'Write to Files', 'better-wp-security' ) );
27
- $this->sanitize_setting( 'bool', 'digest_email', __( 'Send Digest Email', 'better-wp-security' ) );
28
  $this->sanitize_setting( 'bool', 'blacklist', __( 'Blacklist Repeat Offender', 'better-wp-security' ) );
29
- $this->sanitize_setting( 'bool', 'email_notifications', __( 'Email Lockout Notifications', 'better-wp-security' ) );
30
  $this->sanitize_setting( 'bool', 'allow_tracking', __( 'Allow Data Tracking', 'better-wp-security' ) );
31
  $this->sanitize_setting( 'bool', 'proxy_override', __( 'Override Proxy Detection', 'better-wp-security' ) );
32
  $this->sanitize_setting( 'bool', 'hide_admin_bar', __( 'Hide Security Menu in Admin Bar', 'better-wp-security' ) );
@@ -48,19 +47,12 @@ class ITSEC_Global_Validator extends ITSEC_Validator {
48
 
49
  $this->sanitize_setting( 'newline-separated-ips', 'lockout_white_list', __( 'Lockout White List', 'better-wp-security' ) );
50
 
51
- $this->sanitize_setting( 'newline-separated-emails', 'notification_email', __( 'Notification Email', 'better-wp-security' ) );
52
- $this->sanitize_setting( 'newline-separated-emails', 'backup_email', __( 'Backup Delivery Email', 'better-wp-security' ) );
53
-
54
 
55
  $allowed_tags = $this->get_allowed_tags();
56
 
57
  $this->settings['lockout_message'] = trim( wp_kses( $this->settings['lockout_message'], $allowed_tags ) );
58
  $this->settings['user_lockout_message'] = trim( wp_kses( $this->settings['user_lockout_message'], $allowed_tags ) );
59
  $this->settings['community_lockout_message'] = trim( wp_kses( $this->settings['community_lockout_message'], $allowed_tags ) );
60
-
61
- if ( $this->settings['digest_last_sent'] <= 0 ) {
62
- $this->settings['digest_last_sent'] = ITSEC_Core::get_current_time_gmt();
63
- }
64
  }
65
 
66
  public function get_valid_log_types() {
19
  }
20
 
21
 
22
+ $this->vars_to_skip_validate_matching_fields = array( 'digest_last_sent', 'digest_messages', 'digest_email', 'email_notifications', 'notification_email', 'backup_email' );
23
+ $this->set_previous_if_empty( array( 'did_upgrade', 'log_info', 'show_new_dashboard_notice', 'show_security_check', 'digest_last_sent', 'digest_messages', 'build', 'activation_timestamp', 'lock_file', 'digest_email', 'email_notifications', 'notification_email', 'backup_email' ) );
24
  $this->set_default_if_empty( array( 'log_location', 'nginx_file' ) );
25
 
26
 
27
  $this->sanitize_setting( 'bool', 'write_files', __( 'Write to Files', 'better-wp-security' ) );
 
28
  $this->sanitize_setting( 'bool', 'blacklist', __( 'Blacklist Repeat Offender', 'better-wp-security' ) );
 
29
  $this->sanitize_setting( 'bool', 'allow_tracking', __( 'Allow Data Tracking', 'better-wp-security' ) );
30
  $this->sanitize_setting( 'bool', 'proxy_override', __( 'Override Proxy Detection', 'better-wp-security' ) );
31
  $this->sanitize_setting( 'bool', 'hide_admin_bar', __( 'Hide Security Menu in Admin Bar', 'better-wp-security' ) );
47
 
48
  $this->sanitize_setting( 'newline-separated-ips', 'lockout_white_list', __( 'Lockout White List', 'better-wp-security' ) );
49
 
 
 
 
50
 
51
  $allowed_tags = $this->get_allowed_tags();
52
 
53
  $this->settings['lockout_message'] = trim( wp_kses( $this->settings['lockout_message'], $allowed_tags ) );
54
  $this->settings['user_lockout_message'] = trim( wp_kses( $this->settings['user_lockout_message'], $allowed_tags ) );
55
  $this->settings['community_lockout_message'] = trim( wp_kses( $this->settings['community_lockout_message'], $allowed_tags ) );
 
 
 
 
56
  }
57
 
58
  public function get_valid_log_types() {
core/modules/hide-backend/class-itsec-hide-backend.php CHANGED
@@ -14,6 +14,9 @@ class ITSEC_Hide_Backend {
14
  public function run() {
15
  $this->settings = ITSEC_Modules::get_settings( 'hide-backend' );
16
 
 
 
 
17
  if ( ! $this->settings['enabled'] ) {
18
  return;
19
  }
@@ -317,6 +320,48 @@ class ITSEC_Hide_Backend {
317
  }
318
  }
319
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  /**
321
  * Creates a cookie to validate future requests.
322
  *
14
  public function run() {
15
  $this->settings = ITSEC_Modules::get_settings( 'hide-backend' );
16
 
17
+ add_filter( 'itsec_notifications', array( $this, 'register_notification' ) );
18
+ add_filter( 'itsec_hide-backend_notification_strings', array( $this, 'notification_strings' ) );
19
+
20
  if ( ! $this->settings['enabled'] ) {
21
  return;
22
  }
320
  }
321
  }
322
 
323
+ /**
324
+ * Register the New Login URL notification.
325
+ *
326
+ * @param array $notifications
327
+ *
328
+ * @return array
329
+ */
330
+ public function register_notification( $notifications ) {
331
+
332
+ if ( ITSEC_Modules::get_setting( 'hide-backend', 'enabled' ) ) {
333
+ $notifications['hide-backend'] = array(
334
+ 'subject_editable' => true,
335
+ 'message_editable' => true,
336
+ 'schedule' => ITSEC_Notification_Center::S_NONE,
337
+ 'recipient' => ITSEC_Notification_Center::R_USER_LIST,
338
+ 'tags' => array( 'login_url', 'site_title', 'site_url' ),
339
+ 'module' => 'hide-backend',
340
+ );
341
+ }
342
+
343
+ return $notifications;
344
+ }
345
+
346
+ /**
347
+ * Register the strings for the Hide Backend change notification.
348
+ *
349
+ * @return array
350
+ */
351
+ public function notification_strings() {
352
+ return array(
353
+ 'label' => esc_html__( 'Hide Backend – New Login URL', 'better-wp-security' ),
354
+ 'description' => sprintf( esc_html__( '%1$sHide Backend%2$s will notify the chosen recipients whenever the login URL is changed.', 'better-wp-security' ), '<a href="#" data-module-link="hide-backend">', '</a>' ),
355
+ 'subject' => esc_html__( 'WordPress Login Address Changed', 'better-wp-security' ),
356
+ 'message' => esc_html__( 'The login address for {{ $site_title }} has changed. The new login address is {{ $login_url }}. You will be unable to use the old login address.', 'better-wp-security' ),
357
+ 'tags' => array(
358
+ 'login_url' => esc_html__( 'The new login link.', 'better-wp-security' ),
359
+ 'site_title' => esc_html__( 'The WordPress Site Title. Can be changed under Settings -> General -> Site Title', 'better-wp-security' ),
360
+ 'site_url' => esc_html__( 'The URL to your website.', 'better-wp-security' ),
361
+ ),
362
+ );
363
+ }
364
+
365
  /**
366
  * Creates a cookie to validate future requests.
367
  *
core/modules/hide-backend/validator.php CHANGED
@@ -48,13 +48,14 @@ final class ITSEC_Hide_Backend_Validator extends ITSEC_Validator {
48
 
49
  if ( $this->settings['enabled'] && $this->settings['slug'] !== $this->previous_settings['slug'] ) {
50
  $url = get_site_url() . '/' . $this->settings['slug'];
51
- ITSEC_Response::add_message( sprintf( __( 'The Hide Backend feature is now active. Your new login URL is <strong><code>%1$s</code></strong>. Please note this may be different than what you sent as the URL was sanitized to meet various requirements. A reminder has also been sent to the notification email addresses set in iThemes Security\'s Global settings.', 'better-wp-security' ), esc_url( $url ) ) );
52
  } else if ( $this->settings['enabled'] && ! $this->previous_settings['enabled'] ) {
 
53
  $url = get_site_url() . '/' . $this->settings['slug'];
54
- ITSEC_Response::add_message( sprintf( __( 'The Hide Backend feature is now active. Your new login URL is <strong><code>%1$s</code></strong>. A reminder has also been sent to the notification email addresses set in iThemes Security\'s Global settings.', 'better-wp-security' ), esc_url( $url ) ) );
55
  } else if ( ! $this->settings['enabled'] && $this->previous_settings['enabled'] ) {
56
  $url = get_site_url() . '/wp-login.php';
57
- ITSEC_Response::add_message( sprintf( __( 'The Hide Backend feature is now disabled. Your new login URL is <strong><code>%1$s</code></strong>. A reminder has also been sent to the notification email addresses set in iThemes Security\'s Global settings.', 'better-wp-security' ), esc_url( $url ) ) );
58
  }
59
 
60
  if ( isset( $url ) ) {
@@ -73,42 +74,22 @@ final class ITSEC_Hide_Backend_Validator extends ITSEC_Validator {
73
  return;
74
  }
75
 
76
- $message = '<p>' . __( 'Dear Site Admin,', 'better-wp-security' ) . "</p>\n";
77
-
78
- /* translators: 1: Site name, 2: Site address, 3: New login address */
79
- $message .= '<p>' . sprintf( __( 'The login address for %1$s (<code>%2$s</code>) has changed. The new login address is <code>%3$s</code>. You will be unable to use the old login address.', 'better-wp-security' ), get_bloginfo( 'name' ), esc_url( get_site_url() ), esc_url( $url ) ) . "</p>\n";
80
-
81
- if ( defined( 'ITSEC_DEBUG' ) && ITSEC_DEBUG === true ) {
82
- $message.= '<p>Debug info (source page): ' . esc_url( $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"] ) . "</p>\n";
83
- }
84
-
85
- $message = "<html>\n$message</html>\n";
86
-
87
-
88
- //Setup the remainder of the email
89
- $recipients = ITSEC_Modules::get_setting( 'global', 'notification_email' );
90
- $subject = sprintf( __( '[%1$s] WordPress Login Address Changed', 'better-wp-security' ), get_site_url() );
91
- $subject = apply_filters( 'itsec_lockout_email_subject', $subject );
92
- $headers = 'From: ' . get_bloginfo( 'name' ) . ' <' . get_option( 'admin_email' ) . '>' . "\r\n";
93
-
94
- //Use HTML Content type
95
- add_filter( 'wp_mail_content_type', array( $this, 'get_html_content_type' ) );
96
-
97
- //Send emails to all recipients
98
- foreach ( $recipients as $recipient ) {
99
- $recipient = trim( $recipient );
100
-
101
- if ( is_email( $recipient ) ) {
102
-
103
- wp_mail( $recipient, $subject, $message, $headers );
104
-
105
- }
106
-
107
- }
108
-
109
- //Remove HTML Content type
110
- remove_filter( 'wp_mail_content_type', array( $this, 'get_html_content_type' ) );
111
-
112
  }
113
 
114
  /**
48
 
49
  if ( $this->settings['enabled'] && $this->settings['slug'] !== $this->previous_settings['slug'] ) {
50
  $url = get_site_url() . '/' . $this->settings['slug'];
51
+ ITSEC_Response::add_message( sprintf( __( 'The Hide Backend feature is now active. Your new login URL is <strong><code>%1$s</code></strong>. Please note this may be different than what you sent as the URL was sanitized to meet various requirements. A reminder has also been sent to the notification email addresses set in iThemes Security\'s Notification Center.', 'better-wp-security' ), esc_url( $url ) ) );
52
  } else if ( $this->settings['enabled'] && ! $this->previous_settings['enabled'] ) {
53
+ ITSEC_Core::get_notification_center()->clear_notifications_cache();
54
  $url = get_site_url() . '/' . $this->settings['slug'];
55
+ ITSEC_Response::add_message( sprintf( __( 'The Hide Backend feature is now active. Your new login URL is <strong><code>%1$s</code></strong>. A reminder has also been sent to the notification email addresses set in iThemes Security\'s Notification Center.', 'better-wp-security' ), esc_url( $url ) ) );
56
  } else if ( ! $this->settings['enabled'] && $this->previous_settings['enabled'] ) {
57
  $url = get_site_url() . '/wp-login.php';
58
+ ITSEC_Response::add_message( sprintf( __( 'The Hide Backend feature is now disabled. Your new login URL is <strong><code>%1$s</code></strong>. A reminder has also been sent to the notification email addresses set in iThemes Security\'s Notification Center.', 'better-wp-security' ), esc_url( $url ) ) );
59
  }
60
 
61
  if ( isset( $url ) ) {
74
  return;
75
  }
76
 
77
+ $nc = ITSEC_Core::get_notification_center();
78
+ $mail = $nc->mail();
79
+
80
+ $mail->add_header( esc_html__( 'New Login URL', 'better-wp-security' ), esc_html__( 'New Login URL', 'better-wp-security' ) );
81
+ $mail->add_text( ITSEC_Lib::replace_tags( $nc->get_message( 'hide-backend' ), array(
82
+ 'login_url' => '<code>' . esc_url( $url ) . '</code>',
83
+ 'site_title' => get_bloginfo( 'name', 'display' ),
84
+ 'site_url' => $mail->get_display_url(),
85
+ ) ) );
86
+ $mail->add_button( esc_html__( 'Login Now', 'better-wp-security' ), $url );
87
+ $mail->add_footer();
88
+
89
+ $subject = $mail->prepend_site_url_to_subject( $nc->get_subject( 'hide-backend' ) );
90
+ $subject = apply_filters( 'itsec_hide_backend_email_subject', $subject );
91
+ $mail->set_subject( $subject, false );
92
+ $nc->send( 'hide-backend', $mail );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  }
94
 
95
  /**
core/modules/notification-center/active.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once( dirname( __FILE__ ) . '/class-notification-center.php' );
4
+
5
+ $center = new ITSEC_Notification_Center();
6
+ $center->run();
7
+ ITSEC_Core::set_notification_center( $center );
core/modules/notification-center/class-notification-center.php ADDED
@@ -0,0 +1,1079 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Class ITSEC_Notification_Center
5
+ */
6
+ final class ITSEC_Notification_Center {
7
+
8
+ const R_USER = 'user'; // Goes to an end user. Two Factor or Magic Links.
9
+ const R_ADMIN = 'admin'; // Emails currently listed in Global Settings -> Notification Email
10
+ const R_USER_LIST = 'user-list'; // Can select users who should receive the email. For example Malware Scheduling.
11
+ const R_EMAIL_LIST = 'email-list'; // List of email addresses.
12
+ const R_PER_USE = 'per-use'; // Email address is selected before performing the action. For example Import/Export.
13
+ const R_USER_LIST_ADMIN_UPGRADE = 'user-list-admin-upgrade'; // Can select users/roles, but was previously the admin email list. Contains upgrade functionality
14
+
15
+ const S_NONE = 'none';
16
+ const S_DAILY = 'daily';
17
+ const S_WEEKLY = 'weekly';
18
+ const S_MONTHLY = 'monthly';
19
+ const S_CONFIGURABLE = 'configurable';
20
+
21
+ // If this is updated, make sure to update setup.php as well
22
+ const CRON_ACTION = 'itsec-send-scheduled-notifications';
23
+
24
+ /** @var bool */
25
+ private $use_cron;
26
+
27
+ /**
28
+ * Array of notification configs, keyed by notification slug.
29
+ *
30
+ * Lazily computed, see ::get_notifications().
31
+ *
32
+ * @var array
33
+ */
34
+ private $notifications;
35
+
36
+ /**
37
+ * Array of notification strings, keyed by notification slug.
38
+ * Separated from regular configuration due to gettext perforamnce.
39
+ *
40
+ * Lazily computed, see ::get_notification_strings().
41
+ *
42
+ * @var array
43
+ */
44
+ private $strings = array();
45
+
46
+ /**
47
+ * The current notification being sent by ::send().
48
+ *
49
+ * Used for providing additional information when capturing mail errors.
50
+ *
51
+ * This could be replaced with closure scope if migrated to PHP 5.3.
52
+ *
53
+ * @var string
54
+ */
55
+ private $_sending_notification = '';
56
+
57
+ /**
58
+ * ITSEC_Notification_Center constructor.
59
+ */
60
+ public function __construct() {
61
+ $this->use_cron = defined( 'ITSEC_NOTIFY_USE_CRON' ) && ITSEC_NOTIFY_USE_CRON;
62
+ }
63
+
64
+ /**
65
+ * Get registered notifications.
66
+ *
67
+ * This value is cached.
68
+ *
69
+ * @return array
70
+ */
71
+ public function get_notifications() {
72
+
73
+ if ( null === $this->notifications ) {
74
+ /**
75
+ * Filter the registered notifications.
76
+ *
77
+ * Do not conditionally register the filter, instead perform any conditional registration in the callback,
78
+ * so the cache can be properly cleared on settings changes.
79
+ *
80
+ * @param array $notifications
81
+ * @param ITSEC_Notification_Center $this
82
+ */
83
+ $notifications = apply_filters( 'itsec_notifications', array(), $this );
84
+
85
+ foreach ( $notifications as $slug => $notification ) {
86
+ $notification = $this->notification_defaults( $notification );
87
+ $notification['slug'] = $slug;
88
+ $this->notifications[ $slug ] = $notification;
89
+ }
90
+ }
91
+
92
+ return $this->notifications;
93
+ }
94
+
95
+ /**
96
+ * Clear the notifications cache.
97
+ *
98
+ * This shouldn't be necessary in the vast majority of cases.
99
+ */
100
+ public function clear_notifications_cache() {
101
+ $this->notifications = null;
102
+ }
103
+
104
+ /**
105
+ * Get enabled notifications.
106
+ *
107
+ * @return array
108
+ */
109
+ public function get_enabled_notifications() {
110
+ $notifications = $this->get_notifications();
111
+ $enabled = array();
112
+
113
+ foreach ( $notifications as $slug => $notification ) {
114
+ if ( $this->is_notification_enabled( $slug ) ) {
115
+ $enabled[ $slug ] = $notification;
116
+ }
117
+ }
118
+
119
+ return $enabled;
120
+ }
121
+
122
+ /**
123
+ * Check if a notification is enabled.
124
+ *
125
+ * @param string $notification
126
+ *
127
+ * @return bool
128
+ */
129
+ public function is_notification_enabled( $notification ) {
130
+
131
+ $config = $this->get_notification( $notification );
132
+
133
+ if ( ! $config ) {
134
+ return false;
135
+ }
136
+
137
+ if ( empty( $config['optional'] ) ) {
138
+ return true;
139
+ }
140
+
141
+ $settings = $this->get_notification_settings( $notification );
142
+
143
+ return ! empty( $settings['enabled'] );
144
+ }
145
+
146
+ /**
147
+ * Parse notification defaults.
148
+ *
149
+ * @param array $args
150
+ *
151
+ * @return array
152
+ */
153
+ private function notification_defaults( $args ) {
154
+ $args = wp_parse_args( $args, array(
155
+ 'recipient' => self::R_ADMIN,
156
+ 'schedule' => self::S_NONE,
157
+ 'subject_editable' => false,
158
+ 'message_editable' => false,
159
+ 'optional' => false,
160
+ 'tags' => array(),
161
+ 'module' => '',
162
+ ) );
163
+
164
+ $schedules = self::get_schedule_order();
165
+ $schedule = array(
166
+ 'min' => $schedules[0],
167
+ 'max' => $schedules[ count( $schedules ) - 1 ],
168
+ 'default' => self::S_DAILY,
169
+ );
170
+
171
+ if ( $args['schedule'] === self::S_CONFIGURABLE ) {
172
+ $args['schedule'] = $schedule;
173
+ } elseif ( is_array( $args['schedule'] ) ) {
174
+ $args['schedule'] = wp_parse_args( $args['schedule'], $schedule );
175
+ }
176
+
177
+ return $args;
178
+ }
179
+
180
+ /**
181
+ * Get the notification config.
182
+ *
183
+ * @param string $slug
184
+ *
185
+ * @return array|null
186
+ */
187
+ public function get_notification( $slug ) {
188
+ $notifications = $this->get_notifications();
189
+
190
+ return isset( $notifications[ $slug ] ) ? $notifications[ $slug ] : null;
191
+ }
192
+
193
+ /**
194
+ * Get strings for a notification.
195
+ *
196
+ * @param string $slug
197
+ *
198
+ * @return array
199
+ */
200
+ public function get_notification_strings( $slug ) {
201
+
202
+ if ( ! isset( $this->strings[ $slug ] ) ) {
203
+ $this->strings[ $slug ] = apply_filters( "itsec_{$slug}_notification_strings", array() );
204
+ }
205
+
206
+ return $this->strings[ $slug ];
207
+ }
208
+
209
+ /**
210
+ * Get the configured subject for a notification.
211
+ *
212
+ * @param string $notification
213
+ *
214
+ * @return string
215
+ */
216
+ public function get_subject( $notification ) {
217
+
218
+ $config = $this->get_notification( $notification );
219
+
220
+ if ( ! $config ) {
221
+ return '';
222
+ }
223
+
224
+ $settings = $this->get_notification_settings( $notification );
225
+
226
+ if ( ! empty( $config['subject_editable'] ) && ! empty( $settings['subject'] ) ) {
227
+ return $settings['subject'];
228
+ }
229
+
230
+ $strings = $this->get_notification_strings( $notification );
231
+
232
+ return isset( $strings['subject'] ) ? $strings['subject'] : '';
233
+ }
234
+
235
+ /**
236
+ * Get the configured main message for a notification.
237
+ *
238
+ * @param string $notification
239
+ * @param string $format Either 'raw' or 'display'. If 'display', the message will have wpautop. Defaults to 'display'.
240
+ *
241
+ * @return string
242
+ */
243
+ public function get_message( $notification, $format = 'display' ) {
244
+
245
+ $config = $this->get_notification( $notification );
246
+
247
+ if ( ! $config ) {
248
+ return '';
249
+ }
250
+
251
+ $settings = $this->get_notification_settings( $notification );
252
+
253
+ if ( ! empty( $config['message_editable'] ) && ! empty( $settings['message'] ) ) {
254
+ return 'display' === $format ? wpautop( $settings['message'] ) : $settings['message'];
255
+ }
256
+
257
+ $strings = $this->get_notification_strings( $notification );
258
+
259
+ if ( isset( $strings['message'] ) ) {
260
+ return 'display' === $format ? wpautop( $strings['message'] ) : $strings['message'];
261
+ }
262
+
263
+ return '';
264
+ }
265
+
266
+ /**
267
+ * Get the selected schedule for a notification.
268
+ *
269
+ * @param string $notification
270
+ *
271
+ * @return string
272
+ */
273
+ public function get_schedule( $notification ) {
274
+
275
+ $config = $this->get_notification( $notification );
276
+
277
+ if ( ! $config ) {
278
+ return self::S_NONE;
279
+ }
280
+
281
+ if ( self::S_CONFIGURABLE !== $config['schedule'] && ! is_array( $config['schedule'] ) ) {
282
+ return $config['schedule'];
283
+ }
284
+
285
+ $settings = $this->get_notification_settings( $notification );
286
+
287
+ if ( ! empty( $settings['schedule'] ) ) {
288
+ return $settings['schedule'];
289
+ }
290
+
291
+ return $config['schedule']['min'];
292
+ }
293
+
294
+ /**
295
+ * Get the email addresses a notification should be sent to.
296
+ *
297
+ * @param string $notification
298
+ *
299
+ * @return string[]
300
+ */
301
+ public function get_recipients( $notification ) {
302
+
303
+ $config = $this->get_notification( $notification );
304
+
305
+ if ( self::R_ADMIN === $config['recipient'] ) {
306
+ return array( get_option( 'admin_email' ) );
307
+ }
308
+
309
+ if ( self::R_EMAIL_LIST === $config['recipient'] ) {
310
+ $settings = $this->get_notification_settings( $notification );
311
+
312
+ return ! empty( $settings['email_list'] ) ? $settings['email_list'] : array();
313
+ }
314
+
315
+ if ( self::R_USER_LIST !== $config['recipient'] && self::R_USER_LIST_ADMIN_UPGRADE !== $config['recipient'] ) {
316
+ return array();
317
+ }
318
+
319
+ $settings = $this->get_notification_settings( $notification );
320
+ $contacts = $settings['user_list'];
321
+
322
+ $addresses = array();
323
+
324
+ foreach ( $contacts as $contact ) {
325
+ if ( (string) $contact === (string) intval( $contact ) ) {
326
+ $users = array( get_userdata( $contact ) );
327
+ } else {
328
+ list( $prefix, $role ) = explode( ':', $contact, 2 );
329
+
330
+ if ( empty( $role ) ) {
331
+ continue;
332
+ }
333
+
334
+ $users = get_users( array( 'role' => $role ) );
335
+ }
336
+
337
+ foreach ( $users as $user ) {
338
+ if ( is_object( $user ) && ! empty( $user->user_email ) ) {
339
+ $addresses[] = $user->user_email;
340
+ }
341
+ }
342
+ }
343
+
344
+ if ( self::R_USER_LIST_ADMIN_UPGRADE === $config['recipient'] && ! empty( $settings['previous_emails'] ) ) {
345
+ $addresses = array_merge( $addresses, $settings['previous_emails'] );
346
+ }
347
+
348
+ return array_unique( $addresses );
349
+ }
350
+
351
+ /**
352
+ * Get the time the notification was last sent.
353
+ *
354
+ * @param string $notification
355
+ *
356
+ * @return int
357
+ */
358
+ public function get_last_sent( $notification ) {
359
+ $last_sent = $this->get_all_last_sent();
360
+
361
+ return isset( $last_sent[ $notification ] ) ? $last_sent[ $notification ] : 0;
362
+ }
363
+
364
+ /**
365
+ * Get the time that the notification should next be sent.
366
+ *
367
+ * @param string $notification The notification slug.
368
+ *
369
+ * @return int|false False if invalid notification or invalid notification schedule. Unix time otherwise.
370
+ */
371
+ public function get_next_send_time( $notification ) {
372
+ return $this->calculate_next_send_time( $notification, $this->get_last_sent( $notification ) );
373
+ }
374
+
375
+ /**
376
+ * Enqueue some data a scheduled notification should have access to when sending.
377
+ *
378
+ * @param string $notification
379
+ * @param mixed $data
380
+ * @param bool $enforce_unique Whether to enforce all the data for that notification is unique. Only set to false if you are sure data is already unique.
381
+ */
382
+ public function enqueue_data( $notification, $data, $enforce_unique = true ) {
383
+ $all_data = ITSEC_Modules::get_setting( 'notification-center', 'data' );
384
+
385
+ $notification_data = isset( $all_data[ $notification ] ) ? $all_data[ $notification ] : array();
386
+ $notification_data[] = $data;
387
+
388
+ if ( $enforce_unique ) {
389
+ $notification_data = array_unique( $notification_data );
390
+ }
391
+
392
+ $all_data[ $notification ] = $notification_data;
393
+
394
+ ITSEC_Modules::set_setting( 'notification-center', 'data', $all_data );
395
+ }
396
+
397
+ /**
398
+ * Get the data for a notification.
399
+ *
400
+ * @param string $notification
401
+ *
402
+ * @return array
403
+ */
404
+ public function get_data( $notification ) {
405
+
406
+ $all_data = ITSEC_Modules::get_setting( 'notification-center', 'data' );
407
+
408
+ return isset( $all_data[ $notification ] ) ? $all_data[ $notification ] : array();
409
+ }
410
+
411
+ /**
412
+ * Initialize a Mail instance.
413
+ *
414
+ * @return ITSEC_Mail
415
+ */
416
+ public function mail() {
417
+ require_once( ITSEC_Core::get_core_dir() . 'lib/class-itsec-mail.php' );
418
+
419
+ return new ITSEC_Mail();
420
+ }
421
+
422
+ /**
423
+ * Send an email.
424
+ *
425
+ * This will set the subject and recipients configured for the notification if they have not been set.
426
+ *
427
+ * Additionally, will log any errors encountered while sending.
428
+ *
429
+ * @param string $notification
430
+ * @param ITSEC_Mail $mail
431
+ *
432
+ * @return bool
433
+ */
434
+ public function send( $notification, $mail ) {
435
+
436
+ if ( ! $mail->get_subject() ) {
437
+ $mail->set_subject( $this->get_subject( $notification ) );
438
+ }
439
+
440
+ if ( ! $mail->get_recipients() ) {
441
+ $mail->set_recipients( $this->get_recipients( $notification ) );
442
+ }
443
+
444
+ add_action( 'wp_mail_failed', array( $this, 'capture_mail_fail' ) );
445
+
446
+ $this->_sending_notification = $notification;
447
+ $result = $mail->send();
448
+ $this->_sending_notification = '';
449
+
450
+ remove_action( 'wp_mail_failed', array( $this, 'capture_mail_fail' ) );
451
+
452
+ return $result;
453
+ }
454
+
455
+ /**
456
+ * Dismiss an error encountered while sending a notification with wp_mail().
457
+ *
458
+ * @param string $error_id
459
+ */
460
+ public function dismiss_mail_error( $error_id ) {
461
+ $errors = ITSEC_Modules::get_setting( 'notification-center', 'mail_errors', array() );
462
+ unset( $errors[ $error_id ] );
463
+ ITSEC_Modules::set_setting( 'notification-center', 'mail_errors', $errors );
464
+ }
465
+
466
+ /**
467
+ * Get the loggged mail errors keyed by id.
468
+ *
469
+ * @return array
470
+ */
471
+ public function get_mail_errors() {
472
+ return ITSEC_Modules::get_setting( 'notification-center', 'mail_errors', array() );
473
+ }
474
+
475
+ /**
476
+ * Initialize the module.
477
+ */
478
+ public function run() {
479
+ add_action( 'itsec_change_admin_user_id', array( $this, 'update_notification_user_id_on_admin_change' ) );
480
+ add_action( 'itsec_module_settings_after_title', array( $this, 'display_notification_center_link_for_module' ) );
481
+ $this->setup_scheduling();
482
+ }
483
+
484
+ /**
485
+ * Capture whenever an error occurs in wp_mail() while sending a notification so it can be displayed later in the Notification Center.
486
+ *
487
+ * @param WP_Error $error
488
+ */
489
+ public function capture_mail_fail( $error ) {
490
+
491
+ $errors = ITSEC_Modules::get_setting( 'notification-center', 'mail_errors', array() );
492
+
493
+ $errors[ uniqid() ] = array(
494
+ 'error' => array( 'message' => $error->get_error_message(), 'code' => $error->get_error_code() ),
495
+ 'time' => ITSEC_Core::get_current_time_gmt(),
496
+ 'notification' => $this->_sending_notification,
497
+ );
498
+
499
+ ITSEC_Modules::set_setting( 'notification-center', 'mail_errors', $errors );
500
+
501
+ if ( ITSEC_Core::is_interactive() ) {
502
+ ITSEC_Response::reload_module( 'notification-center' );
503
+ }
504
+ }
505
+
506
+ /**
507
+ * Update the notification settings when the admin user id changes.
508
+ *
509
+ * @since 4.1.0
510
+ *
511
+ * @param int $new_user_id
512
+ */
513
+ public function update_notification_user_id_on_admin_change( $new_user_id ) {
514
+
515
+ $settings = ITSEC_Modules::get_settings_obj( 'notification-center' );
516
+ $notifications = $settings->get( 'notifications' );
517
+
518
+ if ( empty( $notifications ) ) {
519
+ return;
520
+ }
521
+
522
+ $changed = false;
523
+
524
+ foreach ( $notifications as $slug => $notification ) {
525
+
526
+ if ( empty( $notification['user_list'] ) ) {
527
+ continue;
528
+ }
529
+
530
+ $user_list = $notification['user_list'];
531
+
532
+ foreach ( $user_list as $i => $contact ) {
533
+ if ( is_numeric( $contact ) && 1 === (int) $contact ) {
534
+ $notifications[ $slug ]['user_list'][ $i ] = $new_user_id;
535
+
536
+ $changed = true;
537
+ break;
538
+ }
539
+ }
540
+ }
541
+
542
+ if ( $changed ) {
543
+ $settings->set( 'notifications', $notifications );
544
+ }
545
+ }
546
+
547
+ /**
548
+ * Display a link to the notification center for any modules that have an associated notification.
549
+ *
550
+ * @param string $module_slug
551
+ */
552
+ public function display_notification_center_link_for_module( $module_slug ) {
553
+
554
+ $display = false;
555
+
556
+ foreach ( $this->get_notifications() as $slug => $notification ) {
557
+ if ( $module_slug === $notification['module'] ) {
558
+ $display = $slug;
559
+ break;
560
+ }
561
+ }
562
+
563
+ if ( $display ) {
564
+ $href = esc_attr( "#itsec-notification-center-notification-settings--{$display}" );
565
+ echo '<a href="' . $href .'" class="itsec-notification-center-link" data-module-link="notification-center">' . esc_html__( 'Notification Center', 'better-wp-security' ) . '</a>';
566
+ }
567
+ }
568
+
569
+ /**
570
+ * Setup scheduling actions.
571
+ */
572
+ private function setup_scheduling() {
573
+ if ( $this->use_cron ) {
574
+ if ( ! wp_next_scheduled( self::CRON_ACTION ) ) {
575
+ wp_schedule_event( time(), 'daily', self::CRON_ACTION );
576
+ }
577
+
578
+ // We can afford the more expensive check when running cron.
579
+ add_action( self::CRON_ACTION, array( $this, 'check_notification_schedule_accurate' ) );
580
+ } else {
581
+ add_action( 'init', array( $this, 'check_notification_schedule_fast' ), 20 );
582
+ }
583
+ }
584
+
585
+ /**
586
+ * This runs on every page load, so we only use the cached last sent options and don't get a lock unless we think some notifications
587
+ * need to be run.
588
+ *
589
+ * @return array|WP_Error
590
+ */
591
+ public function check_notification_schedule_fast() {
592
+
593
+ $last_sent = $this->get_all_last_sent();
594
+ $resend_at = $this->get_all_resend_at();
595
+ $to_send = array();
596
+
597
+ foreach ( $this->get_enabled_notifications() as $slug => $notification ) {
598
+
599
+ $time = $resend_at[ $slug ] > $last_sent[ $slug ] ? $resend_at[ $slug ] : $last_sent[ $slug ];
600
+
601
+ if ( $this->is_time_to_send_notification( $slug, $time ) ) {
602
+ $to_send[ $slug ] = $notification;
603
+ }
604
+ }
605
+
606
+ if ( $to_send ) {
607
+ return $this->check_notification_schedule_accurate( $to_send );
608
+ }
609
+
610
+ return array();
611
+ }
612
+
613
+ /**
614
+ * This checks against the uncached last sent times.
615
+ *
616
+ * @param array[] $notifications Notifications to check. Leave empty to check against all.
617
+ *
618
+ * @return array|WP_Error
619
+ */
620
+ public function check_notification_schedule_accurate( $notifications = array() ) {
621
+
622
+ $notifications = $notifications && is_array( $notifications ) ? $notifications : $this->get_enabled_notifications();
623
+
624
+ if ( ! ITSEC_Lib::get_lock( 'notification-center', 120 ) ) {
625
+ return new WP_Error( 'itsec-notification-center-cannot-get-lock', esc_html__( 'Cannot get lock.', 'better-wp-security' ) );
626
+ }
627
+
628
+ $last_sent = $this->get_all_last_sent_uncached();
629
+ $resend_at = $this->get_all_resend_at_uncached();
630
+ $to_send = array();
631
+
632
+ foreach ( $notifications as $slug => $notification ) {
633
+ $time = $resend_at[ $slug ] > $last_sent[ $slug ] ? $resend_at[ $slug ] : $last_sent[ $slug ];
634
+
635
+ if ( $this->is_time_to_send_notification( $slug, $time ) ) {
636
+ $to_send[] = $slug;
637
+ }
638
+ }
639
+
640
+ $ret = array();
641
+
642
+ if ( $to_send ) {
643
+ $ret = $this->send_scheduled_notifications( $to_send );
644
+ }
645
+
646
+ ITSEC_Lib::release_lock( 'notification-center' );
647
+
648
+ return $ret;
649
+ }
650
+
651
+ /**
652
+ * Send scheduled notifications.
653
+ *
654
+ * @param string[] $notification_slugs The notification slugs to send.
655
+ * @param bool $silent If true, will not update last sent times or destroy data. Defaults to false.
656
+ *
657
+ * @return array Notification slugs keyed to send success.
658
+ */
659
+ public function send_scheduled_notifications( $notification_slugs, $silent = false ) {
660
+
661
+ @set_time_limit( 120 );
662
+ $sent = array();
663
+
664
+ foreach ( $notification_slugs as $notification_slug ) {
665
+ $sent[ $notification_slug ] = $this->send_scheduled_notification( $notification_slug );
666
+ }
667
+
668
+ if ( $silent ) {
669
+ return $sent;
670
+ }
671
+
672
+ $settings = ITSEC_Modules::get_settings( 'notification-center' );
673
+
674
+ foreach ( $notification_slugs as $slug ) {
675
+
676
+ // Only clear queued data if the notification was actually able to be sent.
677
+ if ( ! empty( $sent[ $slug ] ) ) {
678
+ $settings['data'][ $slug ] = array();
679
+ $settings['last_sent'][ $slug ] = ITSEC_Core::get_current_time_gmt();
680
+ } else {
681
+ // Retry sending the notification in 6 hours.
682
+ $settings['resend_at'][ $slug ] = ITSEC_Core::get_current_time_gmt() + 6 * HOUR_IN_SECONDS;
683
+ }
684
+ }
685
+
686
+ ITSEC_Modules::set_settings( 'notification-center', $settings );
687
+ ITSEC_Storage::save();
688
+
689
+ return $sent;
690
+ }
691
+
692
+ /**
693
+ * Send a scheduled notification.
694
+ *
695
+ * @param string $slug
696
+ *
697
+ * @return bool
698
+ */
699
+ private function send_scheduled_notification( $slug ) {
700
+
701
+ $last_sent = $this->get_last_sent( $slug );
702
+ $data = $this->get_data( $slug );
703
+
704
+ if ( ! has_filter( "itsec_send_notification_{$slug}" ) ) {
705
+ return $this->default_send( $slug, $last_sent, $data );
706
+ }
707
+
708
+ /**
709
+ * Fire an action to send the requested notification.
710
+ *
711
+ * @param bool $sent Whether the notification has been successfully sent.
712
+ * @param int $last_sent The time this notification was last sent to the user.
713
+ * @param array $data Queued data.
714
+ */
715
+ return apply_filters( "itsec_send_notification_{$slug}", false, $last_sent, $data );
716
+ }
717
+
718
+ /**
719
+ * Default schedule notification handler.
720
+ *
721
+ * @param string $notification
722
+ * @param int $last_sent
723
+ * @param array $data
724
+ *
725
+ * @return bool
726
+ */
727
+ private function default_send( $notification, $last_sent, $data ) {
728
+
729
+ $config = $this->get_notification( $notification );
730
+
731
+ $mail = $this->mail();
732
+
733
+ if ( ! isset( $config['template'] ) ) {
734
+ $mail->add_header( $this->get_subject( $notification ), '' );
735
+ $mail->add_text( $this->get_message( $notification ) );
736
+ $mail->add_footer();
737
+ } elseif ( $data ) {
738
+ foreach ( $config['template'] as $part ) {
739
+ $this->render_template_part( $mail, $data, $part );
740
+ }
741
+ } else {
742
+ return true;
743
+ }
744
+
745
+ $this->replace_computed_tags( $mail, $last_sent, $config );
746
+
747
+ return $this->send( $notification, $mail );
748
+ }
749
+
750
+ /**
751
+ * Render a template part.
752
+ *
753
+ * @param ITSEC_Mail $mail
754
+ * @param array[] $data
755
+ * @param array $part
756
+ */
757
+ private function render_template_part( $mail, $data, $part ) {
758
+
759
+ if ( empty( $part ) || ! is_array( $part ) || empty( $part[0] ) ) {
760
+ return;
761
+ }
762
+
763
+ switch ( $part[0] ) {
764
+ case 'header':
765
+ $func = 'add_header';
766
+ break;
767
+ case 'footer':
768
+ $func = 'add_footer';
769
+ break;
770
+ case 'table':
771
+
772
+ if ( ! isset( $part[1], $part[2] ) ) {
773
+ return;
774
+ }
775
+
776
+ $paths = $part[2];
777
+ $columns = array();
778
+
779
+ foreach ( $paths as $path ) {
780
+ $resolved = $this->resolve_data_path( $data, $path );
781
+
782
+ if ( $resolved === false ) {
783
+ $columns[] = array_fill( 0, count( $data ), $path );
784
+ } else {
785
+ $columns[] = $resolved;
786
+ }
787
+ }
788
+
789
+ $rows = self::flip_2d_array( $columns );
790
+ $mail->add_table( $part[1], $rows );
791
+
792
+ return;
793
+ default:
794
+ return;
795
+
796
+ }
797
+
798
+ $args = array_slice( $part, 1 );
799
+ call_user_func_array( array( $mail, $func ), $args );
800
+ }
801
+
802
+ /**
803
+ * Replace the computed tags.
804
+ *
805
+ * @param ITSEC_Mail $mail
806
+ * @param int $last_sent
807
+ * @param array $config
808
+ */
809
+ private function replace_computed_tags( $mail, $last_sent, $config ) {
810
+
811
+ $df = get_option( 'date_format' );
812
+
813
+ if ( self::S_DAILY === $config['schedule'] ) {
814
+ $_period = ITSEC_Lib::date_format_i18n_and_local_timezone( ITSEC_Core::get_current_time_gmt(), $df );
815
+ } else {
816
+ $_period = sprintf(
817
+ '%s - %s',
818
+ ITSEC_Lib::date_format_i18n_and_local_timezone( $last_sent, $df ),
819
+ ITSEC_Lib::date_format_i18n_and_local_timezone( ITSEC_Core::get_current_time_gmt(), $df )
820
+ );
821
+ }
822
+
823
+ $tags = compact( '_period' );
824
+
825
+ $mail->set_content( ITSEC_Lib::replace_tags( $mail->get_content(), $tags ) );
826
+ }
827
+
828
+ /**
829
+ * Resolve a data path from stored data.
830
+ *
831
+ * @param array $data
832
+ * @param string $path
833
+ *
834
+ * @return array|false
835
+ */
836
+ private function resolve_data_path( $data, $path ) {
837
+
838
+ if ( strpos( $path, ':data' ) !== 0 ) {
839
+ return false;
840
+ }
841
+
842
+ $path = substr( $path, 6 );
843
+ $values = array();
844
+
845
+ foreach ( $data as $entry ) {
846
+ $values[] = ITSEC_Lib::array_get( $entry, $path );
847
+ }
848
+
849
+ return $values;
850
+ }
851
+
852
+ /**
853
+ * Check if enough time has elapsed for a scheduled notification to be sent.
854
+ *
855
+ * @param string $notification Notification slug.
856
+ * @param int $last_sent
857
+ *
858
+ * @return bool False if not time, the notification isn't scheduled, or it has an unknown period.
859
+ */
860
+ private function is_time_to_send_notification( $notification, $last_sent ) {
861
+
862
+ $next = $this->calculate_next_send_time( $notification, $last_sent );
863
+
864
+ return $next && $next < ITSEC_Core::get_current_time_gmt();
865
+ }
866
+
867
+ /**
868
+ * Calculate the next time a notification should be sent.
869
+ *
870
+ * @param string $notification The notification slug.
871
+ * @param int $last_sent Time to calculate from.
872
+ *
873
+ * @return int|false
874
+ */
875
+ private function calculate_next_send_time( $notification, $last_sent ) {
876
+ $schedule = $this->get_schedule( $notification );
877
+
878
+ if ( self::S_NONE === $schedule ) {
879
+ return false; // This is an on-demand
880
+ }
881
+
882
+ switch ( $schedule ) {
883
+ case self::S_DAILY:
884
+ $period = DAY_IN_SECONDS;
885
+ break;
886
+ case self::S_WEEKLY:
887
+ $period = WEEK_IN_SECONDS;
888
+ break;
889
+ case self::S_MONTHLY:
890
+ $period = MONTH_IN_SECONDS;
891
+ break;
892
+ default:
893
+ return false;
894
+ }
895
+
896
+ return $last_sent + $period;
897
+ }
898
+
899
+ /**
900
+ * Get the settings for a notification.
901
+ *
902
+ * @param string $notification
903
+ *
904
+ * @return array|null
905
+ */
906
+ private function get_notification_settings( $notification ) {
907
+ $settings = ITSEC_Modules::get_setting( 'notification-center', 'notifications' );
908
+
909
+ return isset( $settings[ $notification ] ) ? $settings[ $notification ] : null;
910
+ }
911
+
912
+ /**
913
+ * Get the cached value that all notifications have last been sent.
914
+ *
915
+ * @return int[]
916
+ */
917
+ private function get_all_last_sent() {
918
+
919
+ $last_sent = ITSEC_Modules::get_setting( 'notification-center', 'last_sent' );
920
+
921
+ if ( ! is_array( $last_sent ) || empty( $last_sent ) ) {
922
+ return $this->fill_last_sent();
923
+ }
924
+
925
+ return $this->fill_last_sent( $last_sent );
926
+ }
927
+
928
+ /**
929
+ * Get the time scheduled notifications were last sent directly from the database.
930
+ *
931
+ * @return int[]
932
+ */
933
+ private function get_all_last_sent_uncached() {
934
+
935
+ $storage = $this->get_uncached_options();
936
+
937
+ if ( isset( $storage['last_sent'] ) ) {
938
+ $last_sent = $storage['last_sent'];
939
+
940
+ if ( count( $last_sent ) === count( $this->get_notifications() ) ) {
941
+ return $last_sent;
942
+ }
943
+
944
+ return $this->fill_last_sent( $last_sent );
945
+ }
946
+
947
+ return $this->fill_last_sent();
948
+ }
949
+
950
+ /**
951
+ * Fill the last sent array with the time the plugin was activated for notifications that haven't been sent yet.
952
+ *
953
+ * @param array $last_sent
954
+ *
955
+ * @return array
956
+ */
957
+ private function fill_last_sent( $last_sent = array() ) {
958
+ $activated = ITSEC_Modules::get_setting( 'global', 'activation_timestamp' );
959
+
960
+ if ( $last_sent ) {
961
+ return array_merge( array_fill_keys( array_keys( $this->get_notifications() ), $activated ), $last_sent );
962
+ }
963
+
964
+ return array_fill_keys( array_keys( $this->get_notifications() ), $activated );
965
+ }
966
+
967
+ /**
968
+ * Get the cached value that all notification should be resent at.
969
+ *
970
+ * @return int[]
971
+ */
972
+ private function get_all_resend_at() {
973
+
974
+ $resend_at = ITSEC_Modules::get_setting( 'notification-center', 'resend_at' );
975
+
976
+ if ( ! is_array( $resend_at ) || empty( $resend_at ) ) {
977
+ $resend_at = array();
978
+ }
979
+
980
+ return array_merge( array_fill_keys( array_keys( $this->get_notifications() ), 0 ), $resend_at );
981
+ }
982
+
983
+ /**
984
+ * Get the time scheduled notifications are scheduled to be resent at.
985
+ *
986
+ * @return int[]
987
+ */
988
+ private function get_all_resend_at_uncached() {
989
+
990
+ $storage = $this->get_uncached_options();
991
+
992
+ if ( isset( $storage['resend_at'] ) ) {
993
+ $resend_at = $storage['resend_at'];
994
+
995
+ if ( count( $resend_at ) === count( $this->get_notifications() ) ) {
996
+ return $resend_at;
997
+ }
998
+
999
+ return array_merge( array_fill_keys( array_keys( $this->get_notifications() ), 0 ), $resend_at );
1000
+ }
1001
+
1002
+ return array_fill_keys( array_keys( $this->get_notifications() ), 0 );
1003
+ }
1004
+
1005
+ /**
1006
+ * Get the uncached options storage.
1007
+ *
1008
+ * @return array
1009
+ */
1010
+ private function get_uncached_options() {
1011
+ /** @var $wpdb \wpdb */
1012
+ global $wpdb;
1013
+
1014
+ $option = 'itsec-storage';
1015
+ $storage = array();
1016
+
1017
+ if ( is_multisite() ) {
1018
+ $network_id = get_current_site()->id;
1019
+ $row = $wpdb->get_row( $wpdb->prepare( "SELECT meta_value FROM $wpdb->sitemeta WHERE meta_key = %s AND site_id = %d", $option, $network_id ) );
1020
+
1021
+ if ( is_object( $row ) ) {
1022
+ $storage = maybe_unserialize( $row->meta_value );
1023
+ }
1024
+ } else {
1025
+ $row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", $option ) );
1026
+
1027
+ if ( is_object( $row ) ) {
1028
+ $storage = maybe_unserialize( $row->option_value );
1029
+ }
1030
+ }
1031
+
1032
+ if ( ! isset( $storage['notification-center'] ) ) {
1033
+ return array();
1034
+ }
1035
+
1036
+ return $storage['notification-center'];
1037
+ }
1038
+
1039
+ /**
1040
+ * Get labels for the different schedule options.
1041
+ *
1042
+ * @return array
1043
+ */
1044
+ public static function get_schedule_labels() {
1045
+ return array(
1046
+ self::S_DAILY => esc_html__( 'Daily', 'better-wp-security' ),
1047
+ self::S_WEEKLY => esc_html__( 'Weekly', 'better-wp-security' ),
1048
+ self::S_MONTHLY => esc_html__( 'Monthly', 'better-wp-security' ),
1049
+ );
1050
+ }
1051
+
1052
+ /**
1053
+ * Get the order of schedules from smallest to largest.
1054
+ *
1055
+ * @return array
1056
+ */
1057
+ public static function get_schedule_order() {
1058
+ return array( self::S_DAILY, self::S_WEEKLY, self::S_MONTHLY );
1059
+ }
1060
+
1061
+ /**
1062
+ * Flip a 2-dimensional array.
1063
+ *
1064
+ * @param array $array
1065
+ *
1066
+ * @return array
1067
+ */
1068
+ private static function flip_2d_array( $array ) {
1069
+ $out = array();
1070
+
1071
+ foreach ( $array as $row => $columns ) {
1072
+ foreach ( $columns as $new_row => $new_column ) {
1073
+ $out[ $new_row ][ $row ] = $new_column;
1074
+ }
1075
+ }
1076
+
1077
+ return $out;
1078
+ }
1079
+ }
core/modules/notification-center/css/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/modules/notification-center/css/settings-page.css ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .itsec-email-contacts-setting ul {
2
+ margin: 1em 0;
3
+ padding-left: 0;
4
+ }
5
+
6
+ .itsec-email-contacts-setting ul + ul,
7
+ .itsec-notification-center--deprecated-recipients {
8
+ padding-top: 1em;
9
+ border-top: 1px solid #ddd;
10
+ }
11
+
12
+ .itsec-email-contacts-setting ul li {
13
+ list-style: none;
14
+ }
15
+
16
+ .itsec-email-contacts-setting ul li input[type="checkbox"] {
17
+ margin-top: 0;
18
+ }
19
+
20
+ #itsec-module-card-notification-center h4 {
21
+ margin: .25em 0;
22
+ }
23
+
24
+ .itsec-notification-center-notification-settings .itsec-settings-section {
25
+ border: none;
26
+ padding: 0;
27
+ margin: 0;
28
+ }
29
+
30
+ .itsec-notification-center-notification-settings {
31
+ border-top: 1px solid #ddd;
32
+ margin-top: 20px;
33
+ padding-top: 20px;
34
+ }
35
+
36
+ .itsec-notification-center-notification-settings input[type="text"] {
37
+ width: 25em;
38
+ }
39
+
40
+ .itsec-notification-center-tags dt {
41
+ font-family: monospace;
42
+ }
core/modules/notification-center/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/modules/notification-center/js/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/modules/notification-center/js/settings-page.js ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery( function ( $ ) {
2
+
3
+ $( document ).on( 'click', '.itsec-notification-center-enable-notification input[type="checkbox"]', function () {
4
+ toggleSettings( $( this ) );
5
+ } );
6
+
7
+ $( document ).on( 'itsec-dismiss-notice', '.itsec-notification-center-mail-errors-container .notice.itsec-is-dismissible', function () {
8
+ var errorId = $( this ).data( 'id' );
9
+
10
+ itsecSettingsPage.sendModuleAJAXRequest( 'notification-center', { method: 'dismiss-mail-error', mail_error: errorId }, function ( r ) {
11
+ if ( r.response && r.response.status === 'all-cleared' ) {
12
+ jQuery( '#itsec-module-card-notification-center' ).removeClass( 'itsec-module-status--warning' );
13
+ }
14
+ } )
15
+ } );
16
+
17
+ function initializeHiding() {
18
+
19
+ $( '.itsec-notification-center-enable-notification input[type="checkbox"]' ).each( function () {
20
+ toggleSettings( $( this ) );
21
+ } );
22
+ }
23
+
24
+ initializeHiding();
25
+
26
+ function toggleSettings( $input ) {
27
+ var isEnabled = $input.is( ':checked' ), slug = $input.data( 'slug' );
28
+
29
+ var $other = $( 'tr:not(.itsec-notification-center-enable-notification)', '#itsec-notification-center-notification-' + slug );
30
+
31
+ if ( isEnabled ) {
32
+ $other.show();
33
+ } else {
34
+ $other.hide();
35
+ }
36
+ }
37
+
38
+ itsecSettingsPage.events.on( 'modulesReloaded', initializeHiding );
39
+ itsecSettingsPage.events.on( 'moduleReloaded', function ( _, module ) {
40
+ if ( 'notification-center' === module ) {
41
+ initializeHiding();
42
+ }
43
+ } );
44
+ } );
core/modules/notification-center/settings-page.php ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Notification_Center_Settings_Page extends ITSEC_Module_Settings_Page {
4
+
5
+ private $version = 1;
6
+
7
+ /** @var ITSEC_Notification_Center_Validator */
8
+ private $validator;
9
+
10
+ /** @var array */
11
+ private $last_sent = array();
12
+
13
+ public function __construct() {
14
+ $this->id = 'notification-center';
15
+ $this->title = __( 'Notification Center', 'better-wp-security' );
16
+ $this->description = __( 'Manage and configure email notifications sent by iThemes Security related to various settings modules.', 'better-wp-security' );
17
+ $this->type = 'recommended';
18
+ $this->can_save = true;
19
+
20
+ $this->validator = ITSEC_Modules::get_validator( 'notification-center' );
21
+
22
+ if ( ITSEC_Core::get_notification_center()->get_mail_errors() ) {
23
+ $this->status = 'warning';
24
+ }
25
+
26
+ parent::__construct();
27
+ }
28
+
29
+ public function enqueue_scripts_and_styles() {
30
+ wp_enqueue_style( 'itsec-notification-center-admin', plugins_url( 'css/settings-page.css', __FILE__ ), array(), $this->version );
31
+ wp_enqueue_script( 'itsec-notification-center-admin', plugins_url( 'js/settings-page.js', __FILE__ ), array( 'jquery', 'itsec-settings-page-script' ), $this->version );
32
+ }
33
+
34
+ public function handle_ajax_request( $data ) {
35
+
36
+ if ( empty( $data['method'] ) ) {
37
+ return;
38
+ }
39
+
40
+ switch ( $data['method'] ) {
41
+ case 'dismiss-mail-error':
42
+
43
+ if ( ! empty( $data['mail_error'] ) ) {
44
+ ITSEC_Core::get_notification_center()->dismiss_mail_error( $data['mail_error'] );
45
+
46
+ if ( ! ITSEC_Core::get_notification_center()->get_mail_errors() ) {
47
+ ITSEC_Response::set_response( array( 'status' => 'all-cleared' ) );
48
+ }
49
+ }
50
+
51
+ break;
52
+ }
53
+ }
54
+
55
+ protected function render_description( $form ) {
56
+
57
+ ?>
58
+ <p><?php esc_html_e( 'Manage and configure email notifications sent by iThemes Security related to various settings modules. If errors are encountered while sending notification emails, they will be reported here..', 'better-wp-security' ); ?></p>
59
+ <?php
60
+
61
+ }
62
+
63
+ /**
64
+ * @param ITSEC_Form $form
65
+ */
66
+ protected function render_settings( $form ) {
67
+
68
+ $this->last_sent = ITSEC_Modules::get_setting( 'notification-center', 'last_sent' );
69
+
70
+ $this->render_mail_errors();
71
+ ?>
72
+
73
+ <table class="form-table itsec-settings-section">
74
+ <tbody>
75
+ </tbody>
76
+ </table>
77
+
78
+
79
+ <?php
80
+
81
+ $notifications = ITSEC_Core::get_notification_center()->get_notifications();
82
+ usort( $notifications, array( $this, 'sort_notifications' ) );
83
+
84
+ $form->add_input_group( 'notifications' );
85
+ foreach ( $notifications as $notification ) {
86
+ $this->render_notification_setting( $form, $notification['slug'], $notification );
87
+ }
88
+ $form->remove_input_group();
89
+ }
90
+
91
+ protected function render_mail_errors() {
92
+ $errors = ITSEC_Core::get_notification_center()->get_mail_errors();
93
+
94
+ if ( ! $errors ) {
95
+ return;
96
+ }
97
+
98
+ ?>
99
+ <div class="itsec-notification-center-mail-errors-container">
100
+ <?php foreach ( $errors as $id => $error ):
101
+ $strings = ITSEC_Core::get_notification_center()->get_notification_strings( $error['notification'] );
102
+ $error = $error['error'];
103
+
104
+ if ( is_wp_error( $error ) ) {
105
+ $message = $error->get_error_message();
106
+ } elseif ( is_array( $error ) && isset( $error['message'] ) && is_string( $error['message'] ) ) {
107
+ $message = $error['message'];
108
+ } else {
109
+ $message = __( 'Unknown error encountered while sending.', 'better-wp-security' );
110
+ }
111
+ ?>
112
+ <div class="notice notice-alt notice-error below-h2 itsec-is-dismissible itsec-notification-center-mail-error" data-id="<?php echo esc_attr( $id ); ?>">
113
+ <p><?php printf( esc_html__( 'Error while sending %1$s notification at %2$s: %3$s', 'better-wp-security' ), '<b>' . $strings['label'] . '</b>', '<b>' . ITSEC_Lib::date_format_i18n_and_local_timezone( $error['time'] ) . '</b>', $message ); ?></p>
114
+ </div>
115
+ <?php endforeach; ?>
116
+ </div>
117
+ <?php
118
+ }
119
+
120
+ /**
121
+ * @param ITSEC_Form $form
122
+ * @param string $slug
123
+ * @param array $config
124
+ */
125
+ protected function render_notification_setting( $form, $slug, $config ) {
126
+ $strings = ITSEC_Core::get_notification_center()->get_notification_strings( $slug );
127
+
128
+ $form->add_input_group( $slug );
129
+ ?>
130
+
131
+ <div class="itsec-notification-center-notification-settings" id="itsec-notification-center-notification-settings--<?php echo esc_attr( $slug ); ?>">
132
+ <h4><?php echo $strings['label']; ?></h4>
133
+ <?php if ( ! empty( $strings['description'] ) ): ?>
134
+ <p class="description"><?php echo $strings['description']; ?></p>
135
+ <?php endif; ?>
136
+
137
+ <table class="form-table itsec-settings-section" id="itsec-notification-center-notification-<?php echo esc_attr( $slug ); ?>">
138
+
139
+ <?php if ( ! empty( $config['optional'] ) ): ?>
140
+ <tr class="itsec-notification-center-enable-notification">
141
+ <th><label for="itsec-notification-center-notifications-<?php echo esc_attr( $slug ); ?>-enabled"><?php esc_html_e( 'Enabled', 'better-wp-security' ); ?></label></th>
142
+ <td><?php $form->add_checkbox( 'enabled', array( 'data-slug' => $slug ) ); ?></td>
143
+ </tr>
144
+ <?php endif; ?>
145
+
146
+ <?php if ( ! empty( $config['subject_editable'] ) ) :
147
+ $form->get_option( 'subject' ) ? '' : $form->set_option( 'subject', $strings['subject'] ); ?>
148
+ <tr>
149
+ <th><label for="itsec-notification-center-notifications-<?php echo esc_attr( $slug ); ?>-subject"><?php esc_html_e( 'Subject', 'better-wp-security' ); ?></label></th>
150
+ <td><?php $form->add_text( 'subject' ); ?></td>
151
+ </tr>
152
+ <?php endif; ?>
153
+
154
+ <?php if ( ! empty( $config['message_editable'] ) ) :
155
+ $form->get_option( 'message' ) ? '' : $form->set_option( 'message', $strings['message'] ); ?>
156
+ <tr>
157
+ <th><label for="itsec-notification-center-notifications-<?php echo esc_attr( $slug ); ?>-message"><?php esc_html_e( 'Message', 'better-wp-security' ); ?></label></th>
158
+ <td>
159
+ <?php $form->add_textarea( 'message' ); ?>
160
+
161
+ <p class="description">
162
+ <?php echo wp_sprintf( esc_html__( 'You can use HTML in your message. Allowed HTML includes: %l.', 'better-wp-security' ), array_keys( $this->validator->get_allowed_html() ) ); ?>
163
+
164
+ <?php if ( ! empty( $config['tags'] ) ) : ?>
165
+ <?php printf( esc_html__( 'This notification supports email tags. Tags are formatted as follows %s.', 'better-wp-security' ), '<code>{{ $tag_name }}</code>' ); ?>
166
+ <?php endif; ?>
167
+ </p>
168
+
169
+ <?php if ( ! empty( $config['tags'] ) ) : ?>
170
+ <dl class="itsec-notification-center-tags">
171
+ <?php foreach( $strings['tags'] as $tag => $description ): ?>
172
+ <dt><?php echo esc_html( $tag ); ?></dt>
173
+ <dd><?php echo $description; // Already escaped. ?></dd>
174
+ <?php endforeach; ?>
175
+ </dl>
176
+ <?php endif; ?>
177
+ </td>
178
+ </tr>
179
+ <?php endif; ?>
180
+
181
+ <?php if ( is_array( $config['schedule'] ) ): ?>
182
+ <tr>
183
+ <th><label for="itsec-notification-center-notifications-<?php echo esc_attr( $slug ); ?>-schedule"><?php esc_html_e( 'Schedule', 'better-wp-security' ); ?></label></th>
184
+ <td>
185
+ <?php $form->add_select( 'schedule', $this->validator->get_schedule_options( $config['schedule'] ) ); ?>
186
+ <p class="description">
187
+ <?php if ( empty( $this->last_sent[ $slug ] ) ): ?>
188
+ <?php esc_html_e( 'Not yet sent.', 'better-wp-security' ); ?>
189
+ <?php else: ?>
190
+ <?php printf( esc_html__( 'Last sent on %s', 'better-wp-security' ), ITSEC_Lib::date_format_i18n_and_local_timezone( $this->last_sent[ $slug ] ) ); ?>
191
+ <?php endif; ?>
192
+ </p>
193
+ </td>
194
+ </tr>
195
+ <?php endif; ?>
196
+
197
+ <?php switch( $config['recipient'] ) :
198
+ case ITSEC_Notification_Center::R_USER: ?>
199
+ <tr>
200
+ <th><?php esc_html_e( 'Recipient', 'better-wp-security' ); ?></th>
201
+ <td><em><?php esc_html_e( 'Site Users', 'better-wp-security' ); ?></em></td>
202
+ </tr>
203
+ <?php break; ?>
204
+
205
+ <?php case ITSEC_Notification_Center::R_ADMIN: ?>
206
+ <tr>
207
+ <th><?php esc_html_e( 'Recipient', 'better-wp-security' ); ?></th>
208
+ <td><em><?php esc_html_e( 'Admin Emails', 'better-wp-security' ); ?></em></td>
209
+ </tr>
210
+ <?php break; ?>
211
+
212
+ <?php case ITSEC_Notification_Center::R_PER_USE: ?>
213
+ <tr>
214
+ <th><?php esc_html_e( 'Recipient', 'better-wp-security' ); ?></th>
215
+ <td><em><?php esc_html_e( 'Specified when sending', 'better-wp-security' ); ?></em></td>
216
+ </tr>
217
+ <?php break; ?>
218
+
219
+ <?php case ITSEC_Notification_Center::R_EMAIL_LIST: ?>
220
+ <tr>
221
+ <th><label for="itsec-notification-center-notifications-<?php echo esc_attr( $slug ); ?>-email_list"><?php esc_html_e( 'Recipient', 'better-wp-security' ); ?></label></th>
222
+ <td>
223
+ <?php $form->add_textarea( 'email_list', array( 'class' => 'textarea-small' ) ); ?>
224
+ <p class="description"><?php _e( 'The email address(es) this notification will be sent to. One address per line.', 'better-wp-security' ); ?></p>
225
+ </td>
226
+ </tr>
227
+ <?php break; ?>
228
+
229
+ <?php case ITSEC_Notification_Center::R_USER_LIST: case ITSEC_Notification_Center::R_USER_LIST_ADMIN_UPGRADE: ?>
230
+ <?php $this->render_user_list( $slug, $form, $config['recipient'] ); ?>
231
+ <?php break; ?>
232
+
233
+ <?php endswitch; ?>
234
+ </table>
235
+ </div>
236
+ <?php
237
+ $form->remove_input_group();
238
+ }
239
+
240
+ /**
241
+ * Render the User List form.
242
+ *
243
+ * @param string $slug Notification slug.
244
+ * @param ITSEC_Form $form
245
+ * @param string $type
246
+ */
247
+ protected function render_user_list( $slug, $form, $type ) {
248
+
249
+ $users_and_roles = $this->validator->get_available_admin_users_and_roles();
250
+
251
+ $users = $users_and_roles['users'];
252
+ $roles = $users_and_roles['roles'];
253
+
254
+ natcasesort( $users );
255
+ ?>
256
+
257
+ <tr class="itsec-email-contacts-setting">
258
+ <th><?php esc_html_e( 'Recipient', 'better-wp-security' ); ?></th>
259
+ <td>
260
+ <fieldset>
261
+ <legend class="screen-reader-text"><?php esc_html_e( 'Recipients for this email.', 'better-wp-security' ); ?></legend>
262
+ <p><?php esc_html_e( 'Select which users should be emailed.', 'better-wp-security' ); ?></p>
263
+
264
+ <ul>
265
+ <?php foreach ( $roles as $role => $name ) : ?>
266
+ <li>
267
+ <label>
268
+ <?php $form->add_multi_checkbox( 'user_list', $role ); ?>
269
+ <?php echo esc_html( sprintf( _x( 'All %s users', 'role', 'better-wp-security' ), $name ) ); ?>
270
+ </label>
271
+ </li>
272
+ <?php endforeach; ?>
273
+ </ul>
274
+
275
+ <ul>
276
+ <?php foreach ( $users as $id => $name ) : ?>
277
+ <li>
278
+ <label>
279
+ <?php $form->add_multi_checkbox( 'user_list', $id ); ?>
280
+ <?php echo esc_html( $name ); ?>
281
+ </label>
282
+ </li>
283
+ <?php endforeach; ?>
284
+ </ul>
285
+
286
+ <?php if ( ITSEC_Notification_Center::R_USER_LIST_ADMIN_UPGRADE === $type && $form->get_option( 'previous_emails' ) ): ?>
287
+
288
+ <div class="itsec-notification-center--deprecated-recipients">
289
+ <span><?php esc_html_e( 'Deprecated Recipients', 'better-wp-security' ); ?></span>
290
+ <p class="description">
291
+ <?php esc_html_e( 'The following email recipients are deprecated. Please create new users for these email addresses or remove them.', 'better-wp-security' ); ?>
292
+ </p>
293
+ <ul>
294
+ <?php foreach ( $form->get_option( 'previous_emails' ) as $email ): ?>
295
+ <li>
296
+ <label>
297
+ <?php $form->add_multi_checkbox( 'previous_emails', $email ); ?>
298
+ <?php echo esc_html( $email ); ?>
299
+ </label>
300
+ </li>
301
+ <?php endforeach; ?>
302
+ </ul>
303
+ </div>
304
+ <?php endif; ?>
305
+ </fieldset>
306
+ </td>
307
+ </tr>
308
+ <?php
309
+ }
310
+
311
+ private function sort_notifications( $a, $b ) {
312
+
313
+ $a_s = ITSEC_Core::get_notification_center()->get_notification_strings( $a['slug'] );
314
+ $b_s = ITSEC_Core::get_notification_center()->get_notification_strings( $b['slug'] );
315
+
316
+ return strcmp( $a_s['label'], $b_s['label'] );
317
+ }
318
+ }
319
+
320
+ new ITSEC_Notification_Center_Settings_Page();
core/modules/notification-center/settings.php ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Notification_Center_Settings extends ITSEC_Settings {
4
+
5
+ /**
6
+ * ITSEC_Notification_Center_Settings constructor.
7
+ */
8
+ public function __construct() {
9
+ parent::__construct();
10
+
11
+ add_action( 'itsec_notification_center_continue_upgrade', array( $this, 'continue_upgrade' ) );
12
+ }
13
+
14
+
15
+ public function get_id() {
16
+ return 'notification-center';
17
+ }
18
+
19
+ public function get_defaults() {
20
+ return array(
21
+ 'last_sent' => array(),
22
+ 'resend_at' => array(),
23
+ 'data' => array(),
24
+ 'mail_errors' => array(),
25
+ 'notifications' => array(),
26
+ 'admin_emails' => array(),
27
+ );
28
+ }
29
+
30
+ public function load() {
31
+ $this->settings = ITSEC_Storage::get( $this->get_id() );
32
+ $defaults = $this->get_defaults();
33
+
34
+ if ( ! is_array( $this->settings ) ) {
35
+ $this->settings = array();
36
+ }
37
+
38
+ $this->settings = array_merge( $defaults, $this->settings );
39
+
40
+ $notifications = ITSEC_Core::get_notification_center()->get_notifications();
41
+
42
+ foreach ( $notifications as $slug => $notification ) {
43
+ if ( ! isset( $this->settings['notifications'][ $slug ] ) ) {
44
+ $value = $this->get_notification_defaults( $notification, true );
45
+ } else {
46
+ $value = wp_parse_args( $this->settings['notifications'][ $slug ], $this->get_notification_defaults( $notification ) );
47
+ }
48
+
49
+ $this->settings['notifications'][ $slug ] = $value;
50
+ }
51
+ }
52
+
53
+ public function refresh_notification_settings( $save = true ) {
54
+
55
+ $nc = ITSEC_Core::get_notification_center();
56
+
57
+ foreach ( $this->settings['notifications'] as $slug => $notification ) {
58
+ $this->settings['notifications'][ $slug ] = array_merge( $this->get_notification_defaults( $nc->get_notification( $slug ), true ), $notification );
59
+ }
60
+
61
+ if ( $save ) {
62
+ $this->set_all( $this->settings );
63
+ }
64
+ }
65
+
66
+ public function continue_upgrade() {
67
+ $nc = ITSEC_Core::get_notification_center();
68
+
69
+ $nc->clear_notifications_cache();
70
+ $this->refresh_notification_settings( false );
71
+
72
+ $admin_users = array();
73
+ $admin_emails = array();
74
+
75
+ foreach ( $this->settings['admin_emails'] as $admin_email ) {
76
+ $user = get_user_by( 'email', $admin_email );
77
+
78
+ if ( $user && $user->has_cap( 'manage_options' ) ) {
79
+ $admin_users[] = $user->ID;
80
+ } else {
81
+ $admin_emails[] = $admin_email;
82
+ }
83
+ }
84
+
85
+ foreach ( $nc->get_notifications() as $slug => $notification ) {
86
+
87
+ if ( ITSEC_Notification_Center::R_USER_LIST_ADMIN_UPGRADE === $notification['recipient'] ) {
88
+ if ( $admin_users ) {
89
+ $this->settings['notifications'][ $slug ]['user_list'] = $admin_users;
90
+ } elseif ( $admin_emails ) {
91
+ $this->settings['notifications'][ $slug ]['user_list'] = array();
92
+ }
93
+
94
+ $this->settings['notifications'][ $slug ]['previous_emails'] = $admin_emails;
95
+ }
96
+ }
97
+
98
+ $this->set_all( $this->settings );
99
+ }
100
+
101
+ /**
102
+ * Get the defaults for a notification.
103
+ *
104
+ * @param array $notification
105
+ * @param bool $include_strings Whether to include translated strings. Used for default subject and message.
106
+ * Defaults to false for performance reasons.
107
+ *
108
+ * @return array
109
+ */
110
+ private function get_notification_defaults( $notification, $include_strings = false ) {
111
+
112
+ $strings = $include_strings ? ITSEC_Core::get_notification_center()->get_notification_strings( $notification['slug'] ) : array();
113
+ $defaults = array();
114
+
115
+ if ( is_array( $notification['schedule'] ) ) {
116
+ $defaults['schedule'] = $notification['schedule']['default'];
117
+ }
118
+
119
+ if ( ! empty( $strings['subject'] ) ) {
120
+ $defaults['subject'] = $strings['subject'];
121
+ }
122
+
123
+ if ( ! empty( $strings['message'] ) ) {
124
+ $defaults['message'] = $strings['message'];
125
+ }
126
+
127
+ if ( ! empty( $notification['optional'] ) ) {
128
+ $defaults['enabled'] = true;
129
+ }
130
+
131
+ if ( ITSEC_Notification_Center::R_USER_LIST === $notification['recipient'] ) {
132
+ $defaults['user_list'] = array( 'role:administrator' );
133
+ }
134
+
135
+ if ( ITSEC_Notification_Center::R_USER_LIST_ADMIN_UPGRADE === $notification['recipient'] ) {
136
+ $defaults['user_list'] = array( 'role:administrator' );
137
+ $defaults['previous_emails'] = array();
138
+ }
139
+
140
+ if ( ITSEC_Notification_Center::R_EMAIL_LIST === $notification['recipient'] ) {
141
+ $defaults['email_list'] = array( get_option( 'admin_email' ) );
142
+ }
143
+
144
+ return $defaults;
145
+ }
146
+ }
147
+
148
+ ITSEC_Modules::register_settings( new ITSEC_Notification_Center_Settings() );
core/modules/notification-center/setup.php ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Notification_Center_Setup {
4
+
5
+ public function __construct() {
6
+ add_action( 'itsec_modules_do_plugin_uninstall', array( $this, 'execute_uninstall' ) );
7
+ add_action( 'itsec_modules_do_plugin_upgrade', array( $this, 'execute_upgrade' ) );
8
+ }
9
+
10
+ /**
11
+ * Execute module uninstall
12
+ *
13
+ * @return void
14
+ */
15
+ public function execute_uninstall() {
16
+ $scheduled = wp_next_scheduled( 'itsec-send-scheduled-notifications' );
17
+
18
+ if ( $scheduled ) {
19
+ wp_unschedule_event( $scheduled, 'itsec-send-scheduled-notifications' );
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Execute module upgrade
25
+ *
26
+ * @param int $itsec_old_version
27
+ */
28
+ public function execute_upgrade( $itsec_old_version ) {
29
+ if ( $itsec_old_version < 4076 ) {
30
+
31
+ ITSEC_Modules::load_module_file( 'active.php', 'notification-center' );
32
+
33
+ $settings = ITSEC_Modules::get_settings( 'notification-center' );
34
+
35
+ $global = ITSEC_Modules::get_settings( 'global' );
36
+
37
+ $settings['admin_emails'] = $global['notification_email'];
38
+
39
+ $settings['notifications']['digest']['enabled'] = $global['digest_email'];
40
+ $settings['notifications']['backup']['email_list'] = $global['backup_email'];
41
+ $settings['notifications']['lockout']['enabled'] = $global['email_notifications'] && ! $global['digest_email'];
42
+
43
+ $settings['last_sent']['digest'] = $global['digest_last_sent'];
44
+
45
+ if ( $global['digest_messages'] ) {
46
+ $settings['data']['digest'] = array();
47
+
48
+ foreach ( $global['digest_messages'] as $message ) {
49
+ if ( 'file-change' === $message ) {
50
+ $settings['data']['digest'][] = array( 'type' => 'file-change' );
51
+ } else {
52
+ $settings['data']['digest'][] = array( 'type' => 'general', 'message' => $message );
53
+ }
54
+ }
55
+ }
56
+
57
+ if ( $malware = ITSEC_Modules::get_settings( 'malware-scheduling' ) ) {
58
+ $settings['notifications']['malware-scheduling']['enabled'] = ! empty( $malware['email_notifications'] );
59
+ $settings['notifications']['malware-scheduling']['user_list'] = ! empty( $malware['email_contacts'] ) ? $malware['email_contacts'] : array( 'role:administrator' );
60
+ }
61
+
62
+ if ( $vm = ITSEC_Modules::get_settings( 'version-management' ) ) {
63
+ $settings['notifications']['automatic-updates-debug']['enabled'] = ! empty( $vm['automatic_update_emails'] );
64
+ $settings['notifications']['automatic-updates-debug']['user_list'] = ! empty( $vm['email_contacts'] ) ? $vm['email_contacts'] : array( 'role:administrator' );
65
+ $settings['notifications']['old-site-scan']['user_list'] = ! empty( $vm['email_contacts'] ) ? $vm['email_contacts'] : array( 'role:administrator' );
66
+ }
67
+
68
+ if ( $file_change = ITSEC_Modules::get_settings( 'file-change' ) ) {
69
+ $settings['notifications']['file-change']['enabled'] = ! empty( $file_change['email'] ) && ! $global['digest_email'];
70
+ }
71
+
72
+ ITSEC_Modules::set_settings( 'notification-center', $settings );
73
+
74
+ add_action( 'itsec_initialized', array( $this, 'fire_continue_upgrade' ) );
75
+ } elseif ( $itsec_old_version < 4077 ) { // Only run this if user is updating from 4076 -> 4077
76
+ ITSEC_Modules::load_module_file( 'active.php', 'notification-center' );
77
+
78
+ $settings = ITSEC_Modules::get_settings( 'notification-center' );
79
+ $global = ITSEC_Modules::get_settings( 'global' );
80
+
81
+ $settings['notifications']['lockout']['enabled'] = $global['email_notifications'] && ! $global['digest_email'];
82
+
83
+ if ( $file_change = ITSEC_Modules::get_settings( 'file-change' ) ) {
84
+ $settings['notifications']['file-change']['enabled'] = ! empty( $file_change['email'] ) && ! $global['digest_email'];
85
+ }
86
+
87
+ foreach ( $settings['notifications'] as $slug => $notification ) {
88
+ if ( empty( $notification['previous_emails'] ) ) {
89
+ continue;
90
+ }
91
+
92
+ if ( ! isset( $notification['user_list'] ) || $notification['user_list'] === array( 'role:administrator' ) ) {
93
+ $notification['user_list'] = array();
94
+
95
+ $settings['notifications'][ $slug ] = $notification;
96
+ }
97
+ }
98
+
99
+ ITSEC_Modules::set_settings( 'notification-center', $settings );
100
+ } elseif ( $itsec_old_version < 4078 ) { // Only run if user updating from 4077 -> 4078
101
+ ITSEC_Modules::load_module_file( 'active.php', 'notification-center' );
102
+
103
+ $settings = ITSEC_Modules::get_settings( 'notification-center' );
104
+
105
+ if ( ! isset( $settings['notifications']['file-change']['enabled'] ) || $settings['notifications']['file-change']['enabled'] ) {
106
+
107
+ $global = ITSEC_Modules::get_settings( 'global' );
108
+
109
+ if ( $file_change = ITSEC_Modules::get_settings( 'file-change' ) ) {
110
+ $settings['notifications']['file-change']['enabled'] = ! empty( $file_change['email'] ) && ! $global['digest_email'];
111
+ }
112
+
113
+ ITSEC_Modules::set_settings( 'notification-center', $settings );
114
+ }
115
+ }
116
+ }
117
+
118
+ public function fire_continue_upgrade() {
119
+ do_action( 'itsec_notification_center_continue_upgrade' );
120
+ }
121
+ }
122
+
123
+ new ITSEC_Notification_Center_Setup();
core/modules/notification-center/validator.php ADDED
@@ -0,0 +1,313 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Notification Center Validator.
4
+ */
5
+
6
+ class ITSEC_Notification_Center_Validator extends ITSEC_Validator {
7
+
8
+ private $current_tags = array();
9
+ private $tag_errors = array();
10
+
11
+ public function get_id() {
12
+ return 'notification-center';
13
+ }
14
+
15
+ protected function sanitize_settings() {
16
+ $this->vars_to_skip_validate_matching_fields = array( 'last_sent', 'data', 'resend_at', 'mail_errors', 'admin_emails' );
17
+ $this->set_previous_if_empty( array( 'last_sent', 'data', 'resend_at', 'admin_emails' ) );
18
+
19
+ if ( ! isset( $this->settings['mail_errors'] ) ) {
20
+ $this->settings['mail_errors'] = $this->previous_settings['mail_errors'];
21
+ }
22
+
23
+ if ( ! $this->sanitize_setting( 'array', 'notifications', esc_html__( 'Notifications', 'better-wp-security' ) ) ) {
24
+ return;
25
+ }
26
+
27
+ $notifications = $this->settings['notifications'];
28
+
29
+ foreach ( $notifications as $notification => $settings ) {
30
+ $config = ITSEC_Core::get_notification_center()->get_notification( $notification );
31
+
32
+ if ( ! $config ) {
33
+ $notifications[ $notification ] = array();
34
+ break;
35
+ }
36
+
37
+ $strings = ITSEC_Core::get_notification_center()->get_notification_strings( $notification );
38
+
39
+ if ( ITSEC_Notification_Center::R_USER_LIST !== $config['recipient'] && ITSEC_Notification_Center::R_USER_LIST_ADMIN_UPGRADE !== $config['recipient'] ) {
40
+ unset( $settings['user_list'] );
41
+ } else {
42
+ if ( ! is_array( $settings['user_list'] ) ) {
43
+ $settings['user_list'] = array();
44
+ }
45
+
46
+ $users_and_roles = $this->get_available_admin_users_and_roles();
47
+ $valid_contacts = $users_and_roles['users'] + $users_and_roles['roles'];
48
+
49
+ $contact_errors = array();
50
+
51
+ foreach ( $settings['user_list'] as $contact ) {
52
+ if ( ! isset( $valid_contacts[ $contact ] ) ) {
53
+ $contact_errors[] = $contact;
54
+ }
55
+ }
56
+
57
+ if ( ITSEC_Notification_Center::R_USER_LIST_ADMIN_UPGRADE === $config['recipient'] && isset( $settings['previous_emails'] ) ) {
58
+ foreach ( $settings['previous_emails'] as $previous_email ) {
59
+ if ( ! in_array( $previous_email, $this->settings['admin_emails'], true ) ) {
60
+ $contact_errors[] = $previous_email;
61
+ }
62
+ }
63
+ }
64
+
65
+ if ( $contact_errors ) {
66
+ $this->add_error( new WP_Error(
67
+ 'itsec-validator-notification-center-invalid-type-notifications[user_list]-invalid-contacts',
68
+ wp_sprintf( esc_html__( 'Unknown contacts for %1$s, %2$l.', 'better-wp-security' ), $strings['label'], $contact_errors )
69
+ ) );
70
+
71
+ if ( ITSEC_Core::is_interactive() ) {
72
+ $this->set_can_save( false );
73
+ }
74
+ }
75
+ }
76
+
77
+ if ( ITSEC_Notification_Center::R_EMAIL_LIST !== $config['recipient'] ) {
78
+ unset( $settings['email_list'] );
79
+ } else {
80
+
81
+ if ( ! isset( $settings['email_list'] ) ) {
82
+ $settings['email_list'] = '';
83
+ }
84
+
85
+ $settings['email_list'] = $this->convert_string_to_array( $settings['email_list'] );
86
+
87
+ $email_list_error = null; // Safety
88
+
89
+ if ( ! is_array( $settings['email_list'] ) ) {
90
+ $email_list_error = sprintf( __( 'The %1$s email list must be a string with each entry separated by a new line.', 'better-wp-security' ), $strings['label'] );
91
+ } else {
92
+ $invalid_emails = array();
93
+
94
+ foreach ( $settings['email_list'] as $index => $email ) {
95
+ $email = sanitize_text_field( trim( $email ) );
96
+ $settings['email_list'][ $index ] = $email;
97
+
98
+ if ( empty( $email ) ) {
99
+ unset( $settings['email_list'][ $index ] );
100
+ } elseif ( ! is_email( $email ) ) {
101
+ $invalid_emails[] = $email;
102
+ }
103
+ }
104
+
105
+ $settings['email_list'] = array_unique( $settings['email_list'] );
106
+
107
+ if ( ! empty( $invalid_emails ) ) {
108
+ $email_list_error = wp_sprintf( _n( 'The following email in %1$s is invalid: %2$l', 'The following emails in %1$s are invalid: %2$l', count( $invalid_emails ), 'better-wp-security' ), $strings['label'], $invalid_emails );
109
+ }
110
+ }
111
+
112
+ if ( $email_list_error ) {
113
+ $this->add_error( new WP_Error(
114
+ 'itsec-validator-notification-center-invalid-type-notifications[email_list]-invalid-emails',
115
+ $email_list_error
116
+ ) );
117
+
118
+ if ( ITSEC_Core::is_interactive() ) {
119
+ $this->set_can_save( false );
120
+ }
121
+ }
122
+ }
123
+
124
+ if ( empty( $config['optional'] ) ) {
125
+ unset( $settings['enabled'] );
126
+ } else {
127
+ if ( 'false' === $settings['enabled'] ) {
128
+ $settings['enabled'] = false;
129
+ } elseif ( 'true' === $settings['enabled'] ) {
130
+ $settings['enabled'] = true;
131
+ } else {
132
+ $settings['enabled'] = (bool) $settings['enabled'];
133
+ }
134
+ }
135
+
136
+ if ( ! is_array( $config['schedule'] ) ) {
137
+ unset( $settings['schedule'] );
138
+ } else {
139
+ $options = $this->get_schedule_options( $config['schedule'] );
140
+
141
+ if ( ! isset( $options[ $settings['schedule'] ] ) ) {
142
+ $this->add_error( new WP_Error(
143
+ 'itsec-validator-notification-center-invalid-type-notifications[schedule]-unknown-schedule',
144
+ sprintf( esc_html__( 'Unknown schedule for %1$s, %2$s.', 'better-wp-security' ), $strings['label'], $settings['schedule'] )
145
+ ) );
146
+
147
+ if ( ITSEC_Core::is_interactive() ) {
148
+ $this->set_can_save( false );
149
+ }
150
+ }
151
+ }
152
+
153
+ if ( empty( $config['subject_editable'] ) ) {
154
+ unset( $settings['subject'] );
155
+ } elseif ( ! empty( $settings['subject'] ) ) {
156
+ $subject = trim( wp_strip_all_tags( $settings['subject'], true ) );
157
+
158
+ if ( $subject === $strings['subject'] ) {
159
+ $subject = null;
160
+ }
161
+
162
+ $settings['subject'] = $subject;
163
+ }
164
+
165
+ if ( empty( $config['message_editable'] ) ) {
166
+ unset( $settings['message'] );
167
+ } else {
168
+ $message = isset( $settings['message'] ) ? trim( wp_kses( $settings['message'], $this->get_allowed_html() ) ) : '';
169
+
170
+ if ( ! empty( $message ) ) {
171
+ $this->check_unknown_tags( $message, $config['tags'], $strings['label'] );
172
+ }
173
+
174
+ if ( $message === $strings['message'] ) {
175
+ $message = null;
176
+ }
177
+
178
+ $settings['message'] = $message;
179
+ }
180
+
181
+ $notifications[ $notification ] = $settings;
182
+ }
183
+
184
+ $this->settings['notifications'] = $notifications;
185
+ }
186
+
187
+ /**
188
+ * Check whether any unknown tags are being used in the content.
189
+ *
190
+ * @param string $content
191
+ * @param string[] $tags
192
+ * @param string $label
193
+ */
194
+ private function check_unknown_tags( $content, $tags, $label ) {
195
+ $this->current_tags = $tags;
196
+
197
+ preg_replace_callback( '/{{ \$(\w+) }}/', array( $this, 'is_known_tag' ), $content );
198
+
199
+ if ( $this->tag_errors ) {
200
+ $this->add_error( new WP_Error(
201
+ 'itsec-validator-notification-center-invalid-type-notifications[message]-unknown-tags',
202
+ /* translators: %1$s notification label, %2$l list of unknown tags. */
203
+ wp_sprintf( esc_html__( 'Unknown tags for %1$s, %2$l.', 'better-wp-security' ), $label, $this->tag_errors )
204
+ ) );
205
+
206
+ if ( ITSEC_Core::is_interactive() ) {
207
+ $this->set_can_save( false );
208
+ }
209
+ }
210
+
211
+ $this->tag_errors = array();
212
+ $this->current_tags = array();
213
+ }
214
+
215
+ /**
216
+ * Check if a tag is known.
217
+ *
218
+ * @param array $matches
219
+ */
220
+ public function is_known_tag( $matches ) {
221
+ if ( empty( $matches[1] ) ) {
222
+ return; // Sanity check
223
+ }
224
+
225
+ if ( ! in_array( $matches[1], $this->current_tags, true ) ) {
226
+ $this->tag_errors[] = $matches[1];
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Get the list of available users and roles that can be selected as a notification email contact.
232
+ *
233
+ * @return array
234
+ */
235
+ public function get_available_admin_users_and_roles() {
236
+ if ( is_callable( 'wp_roles' ) ) {
237
+ $roles = wp_roles();
238
+ } else {
239
+ $roles = new WP_Roles();
240
+ }
241
+
242
+ $available_roles = array();
243
+ $available_users = array();
244
+
245
+ foreach ( $roles->roles as $role => $details ) {
246
+ if ( isset( $details['capabilities']['manage_options'] ) && ( true === $details['capabilities']['manage_options'] ) ) {
247
+ $available_roles["role:$role"] = translate_user_role( $details['name'] );
248
+
249
+ $users = get_users( array( 'role' => $role ) );
250
+
251
+ foreach ( $users as $user ) {
252
+ /* translators: 1: user display name, 2: user login */
253
+ $available_users[ $user->ID ] = sprintf( __( '%1$s (%2$s)', 'better-wp-security' ), $user->display_name, $user->user_login );
254
+ }
255
+ }
256
+ }
257
+
258
+ natcasesort( $available_users );
259
+
260
+ return array(
261
+ 'users' => $available_users,
262
+ 'roles' => $available_roles,
263
+ );
264
+ }
265
+
266
+ /**
267
+ * Get the available schedule options.
268
+ *
269
+ * @param array $schedule_config
270
+ *
271
+ * @return array
272
+ */
273
+ public function get_schedule_options( $schedule_config ) {
274
+ $labels = ITSEC_Notification_Center::get_schedule_labels();
275
+ $ordered = ITSEC_Notification_Center::get_schedule_order();
276
+ $min = array_search( $schedule_config['min'], $ordered, true );
277
+ $max = array_search( $schedule_config['max'], $ordered, true );
278
+
279
+ $options = array();
280
+
281
+ foreach ( $ordered as $i => $schedule ) {
282
+ if ( $min <= $i && $max >= $i ) {
283
+ $options[ $schedule ] = $labels[ $schedule ];
284
+ }
285
+ }
286
+
287
+ return $options;
288
+ }
289
+
290
+ /**
291
+ * Get the allowed HTML tags for messages.
292
+ *
293
+ * @return array
294
+ */
295
+ public function get_allowed_html() {
296
+ return array(
297
+ 'a' => array(
298
+ 'href' => array(),
299
+ 'title' => array(),
300
+ ),
301
+ 'i' => array(),
302
+ 'b' => array(),
303
+ 'h2' => array(),
304
+ 'h3' => array(),
305
+ 'h4' => array(),
306
+ 'h5' => array(),
307
+ 'h6' => array(),
308
+ 'p' => array(),
309
+ );
310
+ }
311
+ }
312
+
313
+ ITSEC_Modules::register_validator( new ITSEC_Notification_Center_Validator() );
core/modules/strong-passwords/class-itsec-strong-passwords.php CHANGED
@@ -17,9 +17,7 @@ final class ITSEC_Strong_Passwords {
17
  * @return void
18
  */
19
  public function add_scripts() {
20
- $module_path = ITSEC_Lib::get_module_path( __FILE__ );
21
-
22
- wp_enqueue_script( 'itsec_strong_passwords', $module_path . 'js/script.js', array( 'jquery' ), ITSEC_Core::get_plugin_build() );
23
  }
24
 
25
  /**
17
  * @return void
18
  */
19
  public function add_scripts() {
20
+ wp_enqueue_script( 'itsec_strong_passwords', plugins_url( 'js/script.js', __FILE__ ), array( 'jquery' ), ITSEC_Core::get_plugin_build() );
 
 
21
  }
22
 
23
  /**
core/notify.php CHANGED
@@ -9,108 +9,52 @@
9
  class ITSEC_Notify {
10
 
11
  public function __construct() {
12
-
13
- if ( ! ITSEC_Modules::get_setting( 'global', 'digest_email' ) ) {
14
- return;
15
- }
16
-
17
- if ( defined( 'ITSEC_NOTIFY_USE_CRON' ) && true === ITSEC_NOTIFY_USE_CRON ) {
18
-
19
- add_action( 'itsec_digest_email', array( $this, 'init' ) ); //Action to execute during a cron run.
20
-
21
- //schedule digest email
22
- if ( false === wp_next_scheduled( 'itsec_digest_email' ) ) {
23
- wp_schedule_event( time(), 'daily', 'itsec_digest_email' );
24
- }
25
-
26
- } else {
27
- add_action( 'init', array( $this, 'init' ) );
28
- }
29
-
30
  }
31
 
32
  /**
33
- * Processes and sends daily digest message
34
  *
35
- * @since 4.5
36
  *
37
- * @return bool
38
  */
39
- public function init() {
40
-
41
- if ( is_404() ) {
42
- return false;
43
- }
44
-
45
- $use_cron = defined( 'ITSEC_NOTIFY_USE_CRON' ) && ITSEC_NOTIFY_USE_CRON;
46
- $doing_cron = defined( 'DOING_CRON' ) && DOING_CRON;
47
-
48
- if ( $doing_cron && ! $use_cron ) {
49
- return false;
50
- }
51
-
52
- // Check the cached digest_last_sent value. This will be fast but may be inaccurate.
53
- if ( ! $use_cron ) {
54
- $last_sent = ITSEC_Modules::get_setting( 'global', 'digest_last_sent' );
55
- $yesterday = ITSEC_Core::get_current_time_gmt() - DAY_IN_SECONDS;
56
-
57
- if ( $last_sent > $yesterday ) {
58
- return false;
59
- }
60
- }
61
-
62
- // Attempt to acquire a lock so only one process can send the daily digest at a time.
63
- if ( ! ITSEC_Lib::get_lock( 'daily-digest' ) ) {
64
- return false;
65
- }
66
-
67
- if ( ! $use_cron ) {
68
- // This prevents errors where the last sent value is loaded in memory early in the request, before another process has finished sending the value.
69
- $last_sent = $this->get_last_sent_uncached();
70
-
71
- // Send digest if it has been 24 hours
72
- if ( $last_sent > $yesterday ) {
73
-
74
- return false;
75
- }
76
- }
77
-
78
- $result = $this->send_daily_digest();
79
-
80
- ITSEC_Lib::release_lock( 'daily-digest' );
81
 
82
- return $result;
83
  }
84
 
85
  /**
86
- * Get the time the daily digest was last sent directly from the database.
87
  *
88
- * @return int
89
  */
90
- private function get_last_sent_uncached() {
 
91
 
92
- /** @var $wpdb \wpdb */
93
- global $wpdb;
94
-
95
- $option = 'itsec-storage';
96
- $storage = array();
97
-
98
- if ( is_multisite() ) {
99
- $network_id = get_current_site()->id;
100
- $row = $wpdb->get_row( $wpdb->prepare( "SELECT meta_value FROM $wpdb->sitemeta WHERE meta_key = %s AND site_id = %d", $option, $network_id ) );
101
-
102
- if ( is_object( $row ) ) {
103
- $storage = maybe_unserialize( $row->meta_value );
104
- }
105
  } else {
106
- $row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", $option ) );
107
-
108
- if ( is_object( $row ) ) {
109
- $storage = maybe_unserialize( $row->option_value );
110
- }
111
  }
112
 
113
- return isset( $storage['global'], $storage['global']['digest_last_sent'] ) ? $storage['global']['digest_last_sent'] : 0;
 
 
 
 
114
  }
115
 
116
  /**
@@ -118,27 +62,75 @@ class ITSEC_Notify {
118
  *
119
  * @since 2.6.0
120
  *
 
 
 
 
121
  * @return bool
122
  */
123
- public function send_daily_digest() {
124
 
125
  /** @var ITSEC_Lockout $itsec_lockout */
126
  global $itsec_lockout;
127
 
128
-
129
  $send_email = false;
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
- require_once( ITSEC_Core::get_core_dir() . 'lib/class-itsec-mail.php' );
133
- $mail = new ITSEC_Mail();
134
- $mail->add_header( esc_html__( 'Daily Security Digest', 'better-wp-security' ), sprintf( wp_kses( __( 'Your Daily Security Digest for <b>%s</b>', 'better-wp-security' ), array( 'b' => array() ) ), date_i18n( get_option( 'date_format' ) ) ) );
135
- $mail->add_info_box( sprintf( wp_kses( __( 'The following is a summary of security related activity on your site: <b>%s</b>', 'better-wp-security' ), array( 'b' => array() ) ), get_option( 'siteurl' ) ) );
136
 
 
 
137
 
138
  $mail->add_section_heading( esc_html__( 'Lockouts', 'better-wp-security' ), 'lock' );
139
 
140
- $user_count = sizeof( $itsec_lockout->get_lockouts( 'user', true ) );
141
- $host_count = sizeof( $itsec_lockout->get_lockouts( 'host', true ) );
142
 
143
  if ( $host_count > 0 || $user_count > 0 ) {
144
  $mail->add_lockouts_summary( $user_count, $host_count );
@@ -147,22 +139,36 @@ class ITSEC_Notify {
147
  $mail->add_text( esc_html__( 'No lockouts since the last email check.', 'better-wp-security' ) );
148
  }
149
 
 
150
 
151
- $messages = ITSEC_Modules::get_setting( 'global', 'digest_messages' );
152
-
153
- if ( in_array( 'file-change', $messages ) ) {
154
  $mail->add_section_heading( esc_html__( 'File Changes', 'better-wp-security' ), 'folder' );
155
  $mail->add_text( esc_html__( 'File changes detected on the site.', 'better-wp-security' ) );
156
  $send_email = true;
 
157
 
158
- foreach ( $messages as $index => $message ) {
159
- if ( 'file-change' === $message ) {
160
- unset( $messages[$index] );
161
- }
162
- }
 
 
 
 
 
 
 
 
 
 
 
 
163
  }
164
 
165
- if ( ! empty( $messages ) ) {
 
 
166
  $mail->add_section_heading( esc_html__( 'Messages', 'better-wp-security' ), 'message' );
167
 
168
  foreach ( $messages as $message ) {
@@ -174,31 +180,50 @@ class ITSEC_Notify {
174
 
175
 
176
  if ( ! $send_email ) {
177
- return;
178
  }
179
 
180
 
181
- $mail->add_details_box( sprintf( wp_kses( __( 'For more details, <a href="%s"><b>visit your security logs</b></a>', 'better-wp-security' ), array( 'a' => array( 'href' => array() ), 'b' => array() ) ), ITSEC_Mail::filter_admin_page_url( ITSEC_Core::get_logs_page_url() ) ) );
 
 
 
 
182
  $mail->add_divider();
183
  $mail->add_large_text( esc_html__( 'Is your site as secure as it could be?', 'better-wp-security' ) );
184
  $mail->add_text( esc_html__( 'Ensure your site is using recommended settings and features with a security check.', 'better-wp-security' ) );
185
  $mail->add_button( esc_html__( 'Run a Security Check ✓', 'better-wp-security' ), ITSEC_Mail::filter_admin_page_url( ITSEC_Core::get_security_check_page_url() ) );
186
 
187
- if ( defined( 'ITSEC_DEBUG' ) && true === ITSEC_DEBUG ) {
188
- $mail->add_text( sprintf( esc_html__( 'Debug info (source page): %s', 'better-wp-security' ), esc_url( $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"] ) ) );
189
- }
190
-
191
  $mail->add_footer();
192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
 
194
- ITSEC_Modules::set_setting( 'global', 'digest_last_sent', ITSEC_Core::get_current_time_gmt() );
195
- ITSEC_Modules::set_setting( 'global', 'digest_messages', array() );
196
- ITSEC_Storage::save();
197
 
198
- $subject = esc_html__( 'Daily Security Digest', 'better-wp-security' );
199
- $mail->set_subject( $subject );
 
 
 
 
 
 
 
 
200
 
201
- return $mail->send();
202
  }
203
 
204
  /**
@@ -209,17 +234,8 @@ class ITSEC_Notify {
209
  * @return null
210
  */
211
  public function register_file_change() {
212
- // Until a better system can be devised, use the message queue to store this flag.
213
-
214
- $messages = ITSEC_Modules::get_setting( 'global', 'digest_messages' );
215
-
216
- if ( in_array( 'file-change', $messages ) ) {
217
- return;
218
- }
219
-
220
- $messages[] = 'file-change';
221
-
222
- ITSEC_Modules::set_setting( 'global', 'digest_messages', $messages );
223
  }
224
 
225
  /**
@@ -233,6 +249,12 @@ class ITSEC_Notify {
233
  */
234
  public function notify( $body = null ) {
235
 
 
 
 
 
 
 
236
  $allowed_tags = array(
237
  'a' => array(
238
  'href' => array(),
@@ -253,43 +275,20 @@ class ITSEC_Notify {
253
  'h4' => array(),
254
  );
255
 
256
- if ( ITSEC_Modules::get_setting( 'global', 'digest_email' ) ) {
257
-
258
- $messages = ITSEC_Modules::get_setting( 'global', 'digest_messages' );
259
-
260
- if ( ! in_array( wp_kses( $body, $allowed_tags ), $messages ) ) {
261
 
262
- $messages[] = wp_kses( $body, $allowed_tags );
263
 
264
- ITSEC_Modules::set_setting( 'global', 'digest_messages', $messages );
265
 
266
- }
267
-
268
- return true;
269
-
270
- } else if ( ITSEC_Modules::get_setting( 'global', 'email_notifications', true ) ) {
271
-
272
- $subject = trim( sanitize_text_field( $body['subject'] ) );
273
- $message = wp_kses( $body['message'], $allowed_tags );
274
-
275
- if ( isset( $body['headers'] ) ) {
276
-
277
- $headers = $body['headers'];
278
-
279
- } else {
280
-
281
- $headers = '';
282
-
283
- }
284
-
285
- $attachments = isset( $body['attachments'] ) && is_array( $body['attachments'] ) ? $body['attachments'] : array();
286
 
287
- return $this->send_mail( $subject, $message, $headers, $attachments );
288
 
289
  }
290
 
291
- return true;
292
-
293
  }
294
 
295
  /**
@@ -300,11 +299,10 @@ class ITSEC_Notify {
300
  * @param string $subject Email subject
301
  * @param string $message Message contents
302
  * @param string|array $headers Optional. Additional headers.
303
- * @param string|array $attachments Optional. Files to attach.
304
  *
305
  * @return bool Whether the email contents were sent successfully.
306
  */
307
- private function send_mail( $subject, $message, $headers = '', $attachments = array() ) {
308
 
309
  $recipients = ITSEC_Modules::get_setting( 'global', 'notification_email' );
310
  $all_success = true;
@@ -348,3 +346,64 @@ class ITSEC_Notify {
348
 
349
  }
350
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  class ITSEC_Notify {
10
 
11
  public function __construct() {
12
+ add_filter( 'itsec_notifications', array( $this, 'register_notification' ) );
13
+ add_filter( 'itsec_digest_notification_strings', array( $this, 'notification_strings' ) );
14
+ add_filter( 'itsec_send_notification_digest', array( $this, 'send_daily_digest' ), 10, 3 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  }
16
 
17
  /**
18
+ * Register the digest notification.
19
  *
20
+ * @param array $notifications
21
  *
22
+ * @return array
23
  */
24
+ public function register_notification( $notifications ) {
25
+ $notifications['digest'] = array(
26
+ 'slug' => 'digest',
27
+ 'recipient' => ITSEC_Notification_Center::R_USER_LIST_ADMIN_UPGRADE,
28
+ 'schedule' => array(
29
+ 'min' => ITSEC_Notification_Center::S_DAILY,
30
+ 'max' => ITSEC_Notification_Center::S_WEEKLY,
31
+ ),
32
+ 'subject_editable' => true,
33
+ 'optional' => true,
34
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
+ return $notifications;
37
  }
38
 
39
  /**
40
+ * Get the digest notification strings.
41
  *
42
+ * @return array
43
  */
44
+ public function notification_strings() {
45
+ $description = esc_html__( 'During periods of heavy attack, iThemes Security can generate a LOT of email.', 'better-wp-security' );
46
 
47
+ if ( ITSEC_Core::is_pro() ) {
48
+ $features = esc_html__( 'The Security Digest reduces the number of emails sent so you can receive a summary of lockouts, file change detection scans, and privilege escalations.' );
 
 
 
 
 
 
 
 
 
 
 
49
  } else {
50
+ $features = esc_html__( 'The Security Digest reduces the number of emails sent so you can receive a summary of lockouts and file change detection scans.' );
 
 
 
 
51
  }
52
 
53
+ return array(
54
+ 'label' => esc_html__( 'Security Digest', 'better-wp-security' ),
55
+ 'description' => $description . ' ' . $features,
56
+ 'subject' => esc_html__( 'Daily Security Digest', 'better-wp-security' ), // Default schedule is Daily
57
+ );
58
  }
59
 
60
  /**
62
  *
63
  * @since 2.6.0
64
  *
65
+ * @param bool $sent
66
+ * @param int $last_sent
67
+ * @param array $data
68
+ *
69
  * @return bool
70
  */
71
+ public function send_daily_digest( $sent, $last_sent, $data ) {
72
 
73
  /** @var ITSEC_Lockout $itsec_lockout */
74
  global $itsec_lockout;
75
 
 
76
  $send_email = false;
77
 
78
+ $df = get_option( 'date_format' );
79
+ $nc = ITSEC_Core::get_notification_center();
80
+
81
+ switch ( $nc->get_schedule( 'digest' ) ) {
82
+ case ITSEC_Notification_Center::S_DAILY:
83
+ $title = esc_html__( 'Daily Security Digest', 'better-wp-security' );
84
+ $banner_title = sprintf( esc_html__( 'Your Daily Security Digest for %s', 'better-wp-security' ), '<b>' . date_i18n( $df ) . '</b>' );
85
+ break;
86
+ case ITSEC_Notification_Center::S_WEEKLY:
87
+ $period = sprintf(
88
+ '%s - %s',
89
+ ITSEC_Lib::date_format_i18n_and_local_timezone( $last_sent, $df ),
90
+ ITSEC_Lib::date_format_i18n_and_local_timezone( ITSEC_Core::get_current_time_gmt(), $df )
91
+ );
92
+
93
+ $title = esc_html__( 'Weekly Security Digest', 'better-wp-security' );
94
+ $banner_title = sprintf( esc_html__( 'Your Weekly Security Digest for %s', 'better-wp-security' ), '<b>' . $period . '</b>' );
95
+ break;
96
+ case ITSEC_Notification_Center::S_MONTHLY:
97
+
98
+ $this_day = (int) date( 'j', ITSEC_Core::get_current_time_gmt() );
99
+
100
+ if ( $this_day <= 3 ) {
101
+ $period = date_i18n('F Y', $last_sent );
102
+ } else {
103
+ $period = sprintf(
104
+ '%s - %s',
105
+ ITSEC_Lib::date_format_i18n_and_local_timezone( $last_sent, $df ),
106
+ ITSEC_Lib::date_format_i18n_and_local_timezone( ITSEC_Core::get_current_time_gmt(), $df )
107
+ );
108
+ }
109
+
110
+ $title = esc_html__( 'Monthly Security Digest', 'better-wp-security' );
111
+ $banner_title = sprintf( esc_html__( 'Your Monthly Security Digest for %s', 'better-wp-security' ), '<b>' . $period . '</b>' );
112
+ break;
113
+ default:
114
+ $period = sprintf(
115
+ '%s - %s',
116
+ ITSEC_Lib::date_format_i18n_and_local_timezone( $last_sent, $df ),
117
+ ITSEC_Lib::date_format_i18n_and_local_timezone( ITSEC_Core::get_current_time_gmt(), $df )
118
+ );
119
+
120
+ $title = esc_html__( 'Security Digest', 'better-wp-security' );
121
+ $banner_title = sprintf( esc_html__( 'Your Security Digest for %s', 'better-wp-security' ), '<b>' . $period . '</b>' );
122
+ break;
123
+ }
124
 
125
+ $mail = $nc->mail();
 
 
 
126
 
127
+ $mail->add_header( $title, $banner_title );
128
+ $mail->add_info_box( sprintf( esc_html__( 'The following is a summary of security related activity on your site: %s', 'better-wp-security' ), '<b>' . $mail->get_display_url() . '</b>' ) );
129
 
130
  $mail->add_section_heading( esc_html__( 'Lockouts', 'better-wp-security' ), 'lock' );
131
 
132
+ $user_count = $itsec_lockout->get_lockouts( 'user', array( 'after' => $last_sent, 'return' => 'count' ) );
133
+ $host_count = $itsec_lockout->get_lockouts( 'host', array( 'after' => $last_sent, 'return' => 'count' ) );
134
 
135
  if ( $host_count > 0 || $user_count > 0 ) {
136
  $mail->add_lockouts_summary( $user_count, $host_count );
139
  $mail->add_text( esc_html__( 'No lockouts since the last email check.', 'better-wp-security' ) );
140
  }
141
 
142
+ $data_proxy = new ITSEC_Notify_Data_Proxy( $data );
143
 
144
+ if ( $data_proxy->has_message( 'file-change' ) ) {
 
 
145
  $mail->add_section_heading( esc_html__( 'File Changes', 'better-wp-security' ), 'folder' );
146
  $mail->add_text( esc_html__( 'File changes detected on the site.', 'better-wp-security' ) );
147
  $send_email = true;
148
+ }
149
 
150
+ if ( ! $send_email ) {
151
+ $content = $mail->get_content();
152
+ }
153
+
154
+ /**
155
+ * Fires when additional info should be attached to the Security Digest.
156
+ *
157
+ * @since 3.9.0
158
+ *
159
+ * @param ITSEC_Mail $mail
160
+ * @param ITSEC_Notify_Data_Proxy $data_proxy
161
+ * @param int $last_sent
162
+ */
163
+ do_action( 'itsec_security_digest_attach_additional_info', $mail, $data_proxy, $last_sent );
164
+
165
+ if ( ! $send_email && $content !== $mail->get_content() ) {
166
+ $send_email = true;
167
  }
168
 
169
+ $messages = $this->get_general_messages( $data );
170
+
171
+ if ( $messages ) {
172
  $mail->add_section_heading( esc_html__( 'Messages', 'better-wp-security' ), 'message' );
173
 
174
  foreach ( $messages as $message ) {
180
 
181
 
182
  if ( ! $send_email ) {
183
+ return true;
184
  }
185
 
186
 
187
+ $mail->add_details_box( sprintf(
188
+ esc_html__( 'For more details, %1$svisit your security logs%2$s', 'better-wp-security' ),
189
+ '<a href="' . ITSEC_Mail::filter_admin_page_url( ITSEC_Core::get_logs_page_url() ) . '"><b>',
190
+ '</b></a>'
191
+ ) );
192
  $mail->add_divider();
193
  $mail->add_large_text( esc_html__( 'Is your site as secure as it could be?', 'better-wp-security' ) );
194
  $mail->add_text( esc_html__( 'Ensure your site is using recommended settings and features with a security check.', 'better-wp-security' ) );
195
  $mail->add_button( esc_html__( 'Run a Security Check ✓', 'better-wp-security' ), ITSEC_Mail::filter_admin_page_url( ITSEC_Core::get_security_check_page_url() ) );
196
 
 
 
 
 
197
  $mail->add_footer();
198
 
199
+ return $nc->send( 'digest', $mail );
200
+ }
201
+
202
+ /**
203
+ * Get general digest messages.
204
+ *
205
+ * @param array $data
206
+ *
207
+ * @return string[]
208
+ */
209
+ private function get_general_messages( $data ) {
210
+
211
+ $messages = array();
212
 
213
+ foreach ( $data as $datum ) {
 
 
214
 
215
+ if ( ! is_array( $datum ) || ! isset( $datum['message'] ) ) {
216
+ continue;
217
+ }
218
+
219
+ if ( isset( $datum['type'] ) && 'general' !== $datum['type'] ) {
220
+ continue;
221
+ }
222
+
223
+ $messages[] = $datum['message'];
224
+ }
225
 
226
+ return $messages;
227
  }
228
 
229
  /**
234
  * @return null
235
  */
236
  public function register_file_change() {
237
+ _deprecated_function( __METHOD__, '3.9.0', 'ITSEC_Notification_Center::enqueue_data' );
238
+ ITSEC_Core::get_notification_center()->enqueue_data( 'digest', array( 'type' => 'file-change' ) );
 
 
 
 
 
 
 
 
 
239
  }
240
 
241
  /**
249
  */
250
  public function notify( $body = null ) {
251
 
252
+ _deprecated_function( __METHOD__, '3.9.0', 'ITSEC_Notification_Center' );
253
+
254
+ if ( empty( $body ) || ! is_array( $body ) ) {
255
+ return true;
256
+ }
257
+
258
  $allowed_tags = array(
259
  'a' => array(
260
  'href' => array(),
275
  'h4' => array(),
276
  );
277
 
278
+ $subject = trim( sanitize_text_field( $body['subject'] ) );
279
+ $message = wp_kses( $body['message'], $allowed_tags );
 
 
 
280
 
281
+ if ( isset( $body['headers'] ) ) {
282
 
283
+ $headers = $body['headers'];
284
 
285
+ } else {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
 
287
+ $headers = '';
288
 
289
  }
290
 
291
+ return $this->send_mail( $subject, $message, $headers );
 
292
  }
293
 
294
  /**
299
  * @param string $subject Email subject
300
  * @param string $message Message contents
301
  * @param string|array $headers Optional. Additional headers.
 
302
  *
303
  * @return bool Whether the email contents were sent successfully.
304
  */
305
+ private function send_mail( $subject, $message, $headers = '' ) {
306
 
307
  $recipients = ITSEC_Modules::get_setting( 'global', 'notification_email' );
308
  $all_success = true;
346
 
347
  }
348
  }
349
+
350
+ class ITSEC_Notify_Data_Proxy {
351
+
352
+ /** @var array */
353
+ private $data;
354
+
355
+ /**
356
+ * ITSEC_Notify_Data_Proxy constructor.
357
+ *
358
+ * @param array $data
359
+ */
360
+ public function __construct( $data ) { $this->data = $data; }
361
+
362
+ /**
363
+ * Check for a queued message.
364
+ *
365
+ * @param string $type
366
+ *
367
+ * @return array|null
368
+ */
369
+ public function has_message( $type ) {
370
+
371
+ foreach ( $this->data as $datum ) {
372
+
373
+ if ( ! is_array( $datum ) ) {
374
+ continue;
375
+ }
376
+
377
+ if ( isset( $datum['type'] ) && $type === $datum['type'] ) {
378
+ return $datum;
379
+ }
380
+ }
381
+
382
+ return null;
383
+ }
384
+
385
+ /**
386
+ * Get all messages of a given type.
387
+ *
388
+ * @param string $type
389
+ *
390
+ * @return array
391
+ */
392
+ public function get_messages_of_type( $type ) {
393
+
394
+ $of_type = array();
395
+
396
+ foreach ( $this->data as $datum ) {
397
+ if ( ! is_array( $datum ) ) {
398
+ continue;
399
+ }
400
+
401
+ if ( isset( $datum['type'] ) && $type === $datum['type'] ) {
402
+ $of_type[] = $datum;
403
+ }
404
+ }
405
+
406
+ return $of_type;
407
+ }
408
+
409
+ }
core/response.php CHANGED
@@ -7,6 +7,7 @@ final class ITSEC_Response {
7
  private $errors;
8
  private $warnings;
9
  private $messages;
 
10
  private $success;
11
  private $js_function_calls;
12
  private $show_default_success_message;
@@ -16,6 +17,7 @@ final class ITSEC_Response {
16
  private $close_modal;
17
  private $regenerate_wp_config;
18
  private $regenerate_server_config;
 
19
 
20
  private function __construct() {
21
  $this->reset_to_defaults();
@@ -113,6 +115,24 @@ final class ITSEC_Response {
113
  return $self->messages;
114
  }
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  public static function add_js_function_call( $js_function, $args = null ) {
117
  $self = self::get_instance();
118
 
@@ -249,6 +269,26 @@ final class ITSEC_Response {
249
  }
250
  }
251
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  public static function get_raw_data() {
253
  $self = self::get_instance();
254
 
@@ -263,15 +303,17 @@ final class ITSEC_Response {
263
 
264
 
265
  $data = array(
266
- 'source' => 'ITSEC_Response',
267
- 'success' => $self->success,
268
- 'response' => $self->response,
269
- 'errors' => self::get_error_strings( $self->errors ),
270
- 'warnings' => self::get_error_strings( $self->warnings ),
271
- 'messages' => $self->messages,
272
- 'functionCalls' => self::parse_js_function_calls_for_module_reloads(),
273
- 'redirect' => $self->redirect,
274
- 'closeModal' => $self->close_modal,
 
 
275
  );
276
 
277
  return $data;
7
  private $errors;
8
  private $warnings;
9
  private $messages;
10
+ private $infos;
11
  private $success;
12
  private $js_function_calls;
13
  private $show_default_success_message;
17
  private $close_modal;
18
  private $regenerate_wp_config;
19
  private $regenerate_server_config;
20
+ private $has_new_notifications = false;
21
 
22
  private function __construct() {
23
  $this->reset_to_defaults();
115
  return $self->messages;
116
  }
117
 
118
+ public static function add_infos( $messages ) {
119
+ foreach ( $messages as $message ) {
120
+ self::add_info( $message );
121
+ }
122
+ }
123
+
124
+ public static function add_info( $message ) {
125
+ $self = self::get_instance();
126
+
127
+ $self->infos[] = $message;
128
+ }
129
+
130
+ public static function get_infos() {
131
+ $self = self::get_instance();
132
+
133
+ return $self->infos;
134
+ }
135
+
136
  public static function add_js_function_call( $js_function, $args = null ) {
137
  $self = self::get_instance();
138
 
269
  }
270
  }
271
 
272
+ public static function maybe_flag_new_notifications_available() {
273
+ $nc = ITSEC_Core::get_notification_center();
274
+
275
+ $current = array_keys( $nc->get_notifications() );
276
+ $nc->clear_notifications_cache();
277
+ $new = array_keys( $nc->get_notifications() );
278
+
279
+ $added = array_diff( $new, $current );
280
+
281
+ if ( $added ) {
282
+ self::reload_module( 'notification-center' );
283
+ self::get_instance()->has_new_notifications = true;
284
+ self::get_instance()->add_info( sprintf(
285
+ esc_html__( 'New notifications available in the %1$sNotification Center%2$s.', 'better-wp-security' ),
286
+ '<a href="#" data-module-link="notification-center">',
287
+ '</a>'
288
+ ) );
289
+ }
290
+ }
291
+
292
  public static function get_raw_data() {
293
  $self = self::get_instance();
294
 
303
 
304
 
305
  $data = array(
306
+ 'source' => 'ITSEC_Response',
307
+ 'success' => $self->success,
308
+ 'response' => $self->response,
309
+ 'errors' => self::get_error_strings( $self->errors ),
310
+ 'warnings' => self::get_error_strings( $self->warnings ),
311
+ 'messages' => $self->messages,
312
+ 'infos' => $self->infos,
313
+ 'functionCalls' => self::parse_js_function_calls_for_module_reloads(),
314
+ 'redirect' => $self->redirect,
315
+ 'closeModal' => $self->close_modal,
316
+ 'newNotifications' => $self->has_new_notifications,
317
  );
318
 
319
  return $data;
core/setup.php CHANGED
@@ -57,10 +57,7 @@ final class ITSEC_Setup {
57
 
58
  if ( is_array( $plugin_data ) && ! empty( $plugin_data['build'] ) ) {
59
  $build = $plugin_data['build'];
60
-
61
- if ( ! empty( $plugin_data['activation_timestamp'] ) ) {
62
- ITSEC_Modules::set_setting( 'global', 'activation_timestamp', $plugin_data['activation_timestamp'] );
63
- }
64
  }
65
 
66
  delete_site_option( 'itsec_data' );
@@ -82,6 +79,9 @@ final class ITSEC_Setup {
82
  }
83
  }
84
 
 
 
 
85
 
86
  // Ensure that the database tables are present and updated to the current schema.
87
  ITSEC_Lib::create_database_tables();
57
 
58
  if ( is_array( $plugin_data ) && ! empty( $plugin_data['build'] ) ) {
59
  $build = $plugin_data['build'];
60
+ ITSEC_Modules::set_setting( 'global', 'activation_timestamp', $plugin_data['activation_timestamp'] );
 
 
 
61
  }
62
 
63
  delete_site_option( 'itsec_data' );
79
  }
80
  }
81
 
82
+ if ( ! ITSEC_Modules::get_setting( 'global', 'activation_timestamp' ) ) {
83
+ ITSEC_Modules::set_setting( 'global', 'activation_timestamp', ITSEC_Core::get_current_time_gmt() );
84
+ }
85
 
86
  // Ensure that the database tables are present and updated to the current schema.
87
  ITSEC_Lib::create_database_tables();
core/sidebar-widget-active-lockouts.php CHANGED
@@ -14,7 +14,7 @@ class ITSEC_Settings_Page_Sidebar_Widget_Active_Lockouts extends ITSEC_Settings_
14
  /** @var ITSEC_Lockout $itsec_lockout */
15
  global $itsec_lockout;
16
 
17
- $lockouts = $itsec_lockout->get_lockouts( 'all', true );
18
  $users = array();
19
  $hosts = array();
20
 
14
  /** @var ITSEC_Lockout $itsec_lockout */
15
  global $itsec_lockout;
16
 
17
+ $lockouts = $itsec_lockout->get_lockouts();
18
  $users = array();
19
  $hosts = array();
20
 
core/sidebar-widget-temp-whitelist.php CHANGED
@@ -13,7 +13,7 @@ class ITSEC_Settings_Page_Sidebar_Widget_Temp_Whitelist extends ITSEC_Settings_P
13
  /** @var ITSEC_Lockout $itsec_lockout */
14
  global $itsec_lockout;
15
 
16
- $lockouts = $itsec_lockout->get_lockouts( 'all', true );
17
  $users = array();
18
  $hosts = array();
19
 
@@ -79,6 +79,8 @@ class ITSEC_Settings_Page_Sidebar_Widget_Temp_Whitelist extends ITSEC_Settings_P
79
  }
80
 
81
  protected function save( $data ) {
 
 
82
  global $itsec_lockout;
83
 
84
  $count = 0;
13
  /** @var ITSEC_Lockout $itsec_lockout */
14
  global $itsec_lockout;
15
 
16
+ $lockouts = $itsec_lockout->get_lockouts();
17
  $users = array();
18
  $hosts = array();
19
 
79
  }
80
 
81
  protected function save( $data ) {
82
+
83
+ /** @var ITSEC_Lockout $itsec_lockout */
84
  global $itsec_lockout;
85
 
86
  $count = 0;
core/sync-verbs/itsec-get-lockouts.php CHANGED
@@ -9,9 +9,10 @@ class Ithemes_Sync_Verb_ITSEC_Get_Lockouts extends Ithemes_Sync_Verb {
9
 
10
  public function run( $arguments ) {
11
 
 
12
  global $itsec_lockout;
13
 
14
- $lockouts = $itsec_lockout->get_lockouts( 'all', true ); //Gets all lockouts, host and user
15
 
16
  //Send the user name or false
17
  foreach ( $lockouts as $key => $lockout ) {
9
 
10
  public function run( $arguments ) {
11
 
12
+ /** @var ITSEC_Lockout $itsec_lockout */
13
  global $itsec_lockout;
14
 
15
+ $lockouts = $itsec_lockout->get_lockouts(); //Gets all lockouts, host and user
16
 
17
  //Send the user name or false
18
  foreach ( $lockouts as $key => $lockout ) {
history.txt CHANGED
@@ -692,3 +692,7 @@
692
  Bug Fix: Fixed SQL query bug that resulted in the "Minutes to Remember Bad Login (check period)" setting being ignored.
693
  Bug Fix: Fixed bug that prevents wp-admin/install.php blocking from working properly on nginx servers.
694
  Bug Fix: Don't attempt to do an SSL redirect when WP CLI is running.
 
 
 
 
692
  Bug Fix: Fixed SQL query bug that resulted in the "Minutes to Remember Bad Login (check period)" setting being ignored.
693
  Bug Fix: Fixed bug that prevents wp-admin/install.php blocking from working properly on nginx servers.
694
  Bug Fix: Don't attempt to do an SSL redirect when WP CLI is running.
695
+ 6.7.0 - 2017-11-07 - Chris Jean & Timothy Jacobs
696
+ New Feature: Introduces the Notification Center, a centralized place to manage and customize email notifications sent by iThemes Security.
697
+ Enhancement: Updated queries and prepare statements to account for changes to the esc_sql() function in WordPress 4.8.3.
698
+ Bug Fix: Corrected some Javascript and CSS links not generating correctly on Windows servers.
readme.txt CHANGED
@@ -2,8 +2,8 @@
2
  Contributors: ithemes, chrisjean, gerroald, mattdanner, timothyblynjacobs
3
  Tags: security, security plugin, malware, hack, secure, block, SSL, admin, htaccess, lockdown, login, protect, protection, anti virus, attack, injection, login security, maintenance, permissions, prevention, authentication, administration, password, brute force, ban, permissions, bots, user agents, xml rpc, security log
4
  Requires at least: 4.6
5
- Tested up to: 4.8.2
6
- Stable tag: 6.6.1
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -188,6 +188,11 @@ Free support may be available with the help of the community in the <a href="htt
188
 
189
  == Changelog ==
190
 
 
 
 
 
 
191
  = 6.6.1 =
192
  * Bug Fix: Fixed SQL query bug that resulted in the "Minutes to Remember Bad Login (check period)" setting being ignored.
193
  * Bug Fix: Fixed bug that prevents wp-admin/install.php blocking from working properly on nginx servers.
@@ -372,5 +377,5 @@ Free support may be available with the help of the community in the <a href="htt
372
 
373
  == Upgrade Notice ==
374
 
375
- = 6.6.1 =
376
- Version 6.6.1 contains important bug fixes. It is recommended for all users.
2
  Contributors: ithemes, chrisjean, gerroald, mattdanner, timothyblynjacobs
3
  Tags: security, security plugin, malware, hack, secure, block, SSL, admin, htaccess, lockdown, login, protect, protection, anti virus, attack, injection, login security, maintenance, permissions, prevention, authentication, administration, password, brute force, ban, permissions, bots, user agents, xml rpc, security log
4
  Requires at least: 4.6
5
+ Tested up to: 4.9
6
+ Stable tag: 6.7.0
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
188
 
189
  == Changelog ==
190
 
191
+ = 6.7.0 =
192
+ * New Feature: Introduces the Notification Center, a centralized place to manage and customize email notifications sent by iThemes Security.
193
+ * Enhancement: Updated queries and prepare statements to account for changes to the esc_sql() function in WordPress 4.8.3.
194
+ * Bug Fix: Corrected some Javascript and CSS links not generating correctly on Windows servers.
195
+
196
  = 6.6.1 =
197
  * Bug Fix: Fixed SQL query bug that resulted in the "Minutes to Remember Bad Login (check period)" setting being ignored.
198
  * Bug Fix: Fixed bug that prevents wp-admin/install.php blocking from working properly on nginx servers.
377
 
378
  == Upgrade Notice ==
379
 
380
+ = 6.7.0 =
381
+ Version 6.7.0 contains important bug fixes. It is recommended for all users.