iThemes Security (formerly Better WP Security) - Version 7.1.0

Version Description

  • New Feature: Allow for globally setting recipients for admin-targeted notifications. All new notifications will default to the recipients in this list. Notifications can be set to use the default list or switch to a custom list.
  • Enhancement: Added a setting to enable/disable the Grade Report feature of Pro.
  • Tweak: Check if an IP is blacklisted on page load for compatibility with servers that cannot process server configuration level bans immediately.
  • Tweak: Display a time diff until the next event on the Debug page.
  • Tweak: Use Logging API for tracking Notification Center errors.
  • Tweak: Register Scheduler Events whenever the plugin build changes.
  • Tweak: Allow for filtering logs by any module recorded.
  • Tweak: Account for 3rd-party Backup Plugin in Security Check.
  • Bug Fix: 404 detection for plugins that mark is_404 later in the hook sequence.
  • Bug Fix: REST API Protection blocked the Taxonomies route for all users.
  • Bug Fix: Account for any CLI PHP SAPI instead of just WP-CLI in the SSL Module.
  • Bug Fix: Fixed how the Grade Report enable/disable status is stored to fix admin page loading issues on some sites.
  • Bug Fix: Fix serialization of closure error when a plugin registering a hook with a closure is in the boot-up stack and the notification center is triggered too early in the cycle.
Download this release

Release Info

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

Code changes from version 7.0.4 to 7.1.0

Files changed (41) hide show
  1. better-wp-security.php +1 -1
  2. core/admin-pages/css/style.css +3 -0
  3. core/admin-pages/js/debug.js +4 -0
  4. core/admin-pages/js/script.js +25 -21
  5. core/admin-pages/js/settings.js +25 -21
  6. core/admin-pages/logs-list-table.php +76 -20
  7. core/admin-pages/page-debug.php +8 -8
  8. core/admin-pages/page-logs.php +20 -0
  9. core/core.php +14 -4
  10. core/history.txt +22 -1
  11. core/lib.php +110 -29
  12. core/lib/class-itsec-job.php +4 -3
  13. core/lib/class-itsec-lib-canonical-roles.php +27 -0
  14. core/lib/class-itsec-mail.php +41 -5
  15. core/lib/class-itsec-scheduler-cron.php +15 -12
  16. core/lib/class-itsec-scheduler-page-load.php +17 -2
  17. core/lib/class-itsec-scheduler.php +14 -0
  18. core/lib/class-itsec-wp-list-table.php +9 -11
  19. core/lib/form.php +21 -0
  20. core/lib/validator.php +44 -0
  21. core/lockout.php +1 -1
  22. core/modules/404-detection/class-itsec-four-oh-four.php +4 -8
  23. core/modules/404-detection/logs.php +5 -0
  24. core/modules/global/settings-page.php +14 -0
  25. core/modules/global/settings.php +12 -0
  26. core/modules/global/validator.php +2 -1
  27. core/modules/notification-center/class-notification-center.php +68 -24
  28. core/modules/notification-center/debug.php +2 -2
  29. core/modules/notification-center/js/settings-page.js +6 -4
  30. core/modules/notification-center/logs.php +94 -0
  31. core/modules/notification-center/settings-page.php +103 -75
  32. core/modules/notification-center/settings.php +79 -29
  33. core/modules/notification-center/setup.php +19 -2
  34. core/modules/notification-center/validator.php +60 -5
  35. core/modules/security-check/scanner.php +22 -1
  36. core/modules/ssl/class-itsec-ssl.php +2 -2
  37. core/modules/wordpress-tweaks/class-itsec-wordpress-tweaks.php +155 -5
  38. core/response.php +22 -8
  39. core/setup.php +4 -6
  40. history.txt +15 -1
  41. readme.txt +19 -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: 7.0.4
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: 7.1.0
10
  * Text Domain: better-wp-security
11
  * Network: True
12
  * License: GPLv2
core/admin-pages/css/style.css CHANGED
@@ -866,4 +866,7 @@ body.security_page_itsec-logs #old-logs-migration-status p {
866
 
867
  #itsec-settings-editor:empty {
868
  display: none;
 
 
 
869
  }
866
 
867
  #itsec-settings-editor:empty {
868
  display: none;
869
+ }
870
+ .itsec-module-cards-container .bulkactions:empty {
871
+ display: none;
872
  }
core/admin-pages/js/debug.js CHANGED
@@ -20,6 +20,10 @@
20
  } );
21
  } );
22
 
 
 
 
 
23
  $( '#itsec-scheduler-reset' ).on( 'click', function () {
24
 
25
  var $btn = $( this );
20
  } );
21
  } );
22
 
23
+ $( document ).on( 'click', '#itsec-events-data-toggle', function () {
24
+ $( '.itsec-events-data' ).toggleClass( 'hidden' );
25
+ } );
26
+
27
  $( '#itsec-scheduler-reset' ).on( 'click', function () {
28
 
29
  var $btn = $( this );
core/admin-pages/js/script.js CHANGED
@@ -980,34 +980,38 @@ var itsecSettingsPage = {
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( $ ) {
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
+ refreshPage: function() {
1013
+ location.reload( true );
1014
+ }
1015
  };
1016
 
1017
  jQuery(document).ready(function( $ ) {
core/admin-pages/js/settings.js CHANGED
@@ -824,34 +824,38 @@ var itsecSettingsPage = {
824
  },
825
 
826
  // Make notices dismissible
827
- makeNoticesDismissible: function(){
828
- jQuery( '.notice.itsec-is-dismissible' ).each( function() {
829
- var $el = jQuery( this ),
830
- $button = jQuery( '<button type="button" class="notice-dismiss"><span class="screen-reader-text"></span></button>' ),
831
- btnText = itsec_page.translations.dismiss || '';
832
-
833
- // Don't rebind twice
834
- if ( jQuery( '.notice-dismiss', $el ).length ) {
835
- return;
836
- }
837
 
838
- // Ensure plain text
839
- $button.find( '.screen-reader-text' ).text( btnText );
840
- $button.on( 'click.wp-dismiss-notice', function( event ) {
841
- event.preventDefault();
842
 
843
- $el.trigger( 'itsec-dismiss-notice' );
844
 
845
- $el.fadeTo( 100, 0, function() {
846
- $el.slideUp( 100, function() {
847
- $el.remove();
 
848
  });
849
  });
 
 
850
  });
 
851
 
852
- $el.append( $button );
853
- });
854
- }
855
  };
856
 
857
  jQuery(document).ready(function( $ ) {
824
  },
825
 
826
  // Make notices dismissible
827
+ makeNoticesDismissible: function() {
828
+ jQuery( '.notice.itsec-is-dismissible' ).each( function() {
829
+ var $el = jQuery( this ),
830
+ $button = jQuery( '<button type="button" class="notice-dismiss"><span class="screen-reader-text"></span></button>' ),
831
+ btnText = itsec_page.translations.dismiss || '';
832
+
833
+ // Don't rebind twice
834
+ if ( jQuery( '.notice-dismiss', $el ).length ) {
835
+ return;
836
+ }
837
 
838
+ // Ensure plain text
839
+ $button.find( '.screen-reader-text' ).text( btnText );
840
+ $button.on( 'click.wp-dismiss-notice', function( event ) {
841
+ event.preventDefault();
842
 
843
+ $el.trigger( 'itsec-dismiss-notice' );
844
 
845
+ $el.fadeTo( 100, 0, function() {
846
+ $el.slideUp( 100, function() {
847
+ $el.remove();
848
+ });
849
  });
850
  });
851
+
852
+ $el.append( $button );
853
  });
854
+ },
855
 
856
+ refreshPage: function() {
857
+ location.reload( true );
858
+ }
859
  };
860
 
861
  jQuery(document).ready(function( $ ) {
core/admin-pages/logs-list-table.php CHANGED
@@ -110,12 +110,17 @@ final class ITSEC_Logs_List_Table extends ITSEC_WP_List_Table {
110
  return;
111
  }
112
 
113
- if ( 'four_oh_four' === $item['module'] && 'code' === $column_name ) {
114
- $url = self::get_self_link( array( 'filters[10]' => "url|{$item['url']}", 'filters[11]' => 'module|four_oh_four' ) );
 
115
  } else {
116
- $url = self::get_self_link( array( 'filters' => "$column_name|{$item[$column_name]}" ) );
 
117
  }
118
 
 
 
 
119
  $out = '&nbsp;<a class="dashicons dashicons-filter" href="' . esc_url( $url ) . '" title="' . sprintf( esc_attr__( 'Show only entries for this %s', 'better-wp-security' ), strtolower( $column_header ) ) . '">&nbsp;</a>';
120
 
121
  if ( 'module' === $column_name ) {
@@ -372,7 +377,7 @@ final class ITSEC_Logs_List_Table extends ITSEC_WP_List_Table {
372
  $current = $this->get_current_view();
373
 
374
  foreach ( $views as $type => $description ) {
375
- $url = self::get_self_link( array( 'filters' => "type|$type" ), array() );
376
 
377
  if ( $current === $type ) {
378
  $description = '<a href="' . esc_url( $url ) . '" class="current" aria-current="page">' . $description . '</a>';
@@ -409,28 +414,79 @@ final class ITSEC_Logs_List_Table extends ITSEC_WP_List_Table {
409
  }
410
 
411
  protected function extra_tablenav( $which ) {
412
- echo '<div class="alignleft actions">';
413
 
414
- if ( 'top' === $which ) {
415
- /*
416
- ob_start();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
 
418
- $output = ob_get_clean();
419
 
420
- if ( ! empty( $output ) ) {
421
- echo $output;
422
- submit_button( __( 'Filter' ), '', 'filter_action', false, array( 'id' => 'post-query-submit' ) );
423
- }*/
424
  }
425
 
426
- /* if ( $this->is_trash && current_user_can( get_post_type_object( $this->screen->post_type )->cap->edit_others_posts ) && $this->has_items() ) {
427
- submit_button( __( 'Empty Trash' ), 'apply', 'delete_all', false );
428
- }*/
429
 
430
- echo '</div>';
431
- }
 
 
 
 
 
 
432
 
433
- public function no_items() {
434
- esc_html_e( 'No events.', 'better-wp-security' );
 
 
 
 
 
 
435
  }
436
  }
110
  return;
111
  }
112
 
113
+ if ( false === strpos( $item['code'], '::' ) ) {
114
+ $code = $item['code'];
115
+ $data = array();
116
  } else {
117
+ list( $code, $data ) = explode( '::', $item['code'], 2 );
118
+ $data = explode( ',', $data );
119
  }
120
 
121
+ $vars = apply_filters( "itsec_logs_prepare_{$item['module']}_filter_row_action_for_{$column_name}", array( 'filters' => "{$column_name}|{$item[ $column_name ]}"), $item, $code, $data );
122
+ $url = $this->get_self_link( $vars );
123
+
124
  $out = '&nbsp;<a class="dashicons dashicons-filter" href="' . esc_url( $url ) . '" title="' . sprintf( esc_attr__( 'Show only entries for this %s', 'better-wp-security' ), strtolower( $column_header ) ) . '">&nbsp;</a>';
125
 
126
  if ( 'module' === $column_name ) {
377
  $current = $this->get_current_view();
378
 
379
  foreach ( $views as $type => $description ) {
380
+ $url = $this->get_self_link( array( 'filters' => "type|$type" ), array() );
381
 
382
  if ( $current === $type ) {
383
  $description = '<a href="' . esc_url( $url ) . '" class="current" aria-current="page">' . $description . '</a>';
414
  }
415
 
416
  protected function extra_tablenav( $which ) {
 
417
 
418
+ $filters = $this->get_raw_filters();
419
+ $current = isset( $filters['module'] ) ? $filters['module'] : '';
420
+
421
+ ?>
422
+ <div class="alignleft actions">
423
+ <?php if ( 'top' === $which ): ?>
424
+ <label for="itsec-module-filter" class="screen-reader-text"><?php esc_html_e( 'Filter by Module', 'better-wp-security' ) ?></label>
425
+ <select name="filters[]" id="itsec-module-filter">
426
+ <option value=""><?php esc_html_e( 'All Modules', 'better-wp-security' ); ?></option>
427
+ <?php foreach ( $this->get_modules() as $module => $label ): ?>
428
+ <option value="module|<?php echo esc_attr( $module ) ?>" <?php selected( $module, $current ); ?>>
429
+ <?php echo $label; // Expected to be escaped by modules. ?>
430
+ </option>
431
+ <?php endforeach; ?>
432
+ </select>
433
+
434
+ <?php submit_button( __( 'Filter' ), '', 'filter_action', false, array( 'id' => 'itsec-logs-query-submit' ) ); ?>
435
+
436
+ <?php if ( isset( $filters['type'] ) ): ?>
437
+ <input type="hidden" name="filters[]" value="type|<?php echo esc_attr( $filters['type'] ); ?>">
438
+ <?php endif; ?>
439
+ <?php endif; ?>
440
+ </div>
441
+ <?php
442
+ }
443
+
444
+ public function no_items() {
445
+ esc_html_e( 'No events.', 'better-wp-security' );
446
+ }
447
+
448
+ private function get_modules() {
449
+ $columns = implode(', ', array(
450
+ 'id',
451
+ 'parent_id',
452
+ 'module',
453
+ 'type',
454
+ 'code',
455
+ 'timestamp',
456
+ 'init_timestamp',
457
+ 'remote_ip',
458
+ 'user_id',
459
+ 'url',
460
+ 'memory_current',
461
+ 'memory_peak',
462
+ ) );
463
+
464
+ global $wpdb;
465
 
466
+ $items = $wpdb->get_results( "SELECT {$columns} FROM {$wpdb->prefix}itsec_logs GROUP BY `module`", ARRAY_A );
467
 
468
+ if ( ! is_array( $items ) ) {
469
+ return array();
 
 
470
  }
471
 
472
+ $modules = array();
 
 
473
 
474
+ foreach ( $items as $item ) {
475
+ if ( false === strpos( $item['code'], '::' ) ) {
476
+ $code = $item['code'];
477
+ $data = array();
478
+ } else {
479
+ list( $code, $data ) = explode( '::', $item['code'], 2 );
480
+ $data = explode( ',', $data );
481
+ }
482
 
483
+ $item['description'] = $item['code'];
484
+ $item['module_display'] = $item['module'];
485
+ $item = apply_filters( "itsec_logs_prepare_{$item['module']}_entry_for_list_display", $item, $code, $data );
486
+
487
+ $modules[ $item['module'] ] = $item['module_display'];
488
+ }
489
+
490
+ return $modules;
491
  }
492
  }
core/admin-pages/page-debug.php CHANGED
@@ -89,12 +89,12 @@ final class ITSEC_Debug_Page {
89
  if ( empty( $_POST['data']['id'] ) ) {
90
  ITSEC_Response::add_error( new WP_Error( 'itsec-debug-page-run-event-missing-id', __( 'The server did not receive a valid request. The required "data.id" argument for the "run_event" method is missing.', 'better-wp-security' ) ) );
91
  } elseif ( ! empty( $_POST['data']['data'] ) ) {
92
- $data = json_decode( wp_unslash( $_POST['data']['data'] ), true );
93
 
94
- if ( ! is_array( $data ) ) {
95
- ITSEC_Response::add_error( new WP_Error( 'itsec-debug-page-run-event-invalid-data', __( 'The server did not receive a valid request. The "data.data" argument for the "run_event" method is invalid JSON.', 'better-wp-security' ) ) );
96
  } else {
97
- ITSEC_Core::get_scheduler()->run_single_event( $_POST['data']['id'], $data );
98
  ITSEC_Response::set_response( $this->get_events_table() );
99
  ITSEC_Response::set_success( true );
100
  ITSEC_Response::add_message( __( 'Event successfully run.', 'better-wp-security' ) );
@@ -225,7 +225,7 @@ final class ITSEC_Debug_Page {
225
  <th><?php esc_html_e( 'ID', 'better-wp-security' ) ?></th>
226
  <th><?php esc_html_e( 'Fire At', 'better-wp-security' ) ?></th>
227
  <th><?php esc_html_e( 'Schedule', 'better-wp-security' ) ?></th>
228
- <th><?php esc_html_e( 'Data', 'better-wp-security' ) ?></th>
229
  <th></th>
230
  </tr>
231
  </thead>
@@ -233,12 +233,12 @@ final class ITSEC_Debug_Page {
233
  <?php foreach ( array_merge( $scheduler->get_recurring_events(), $scheduler->get_single_events() ) as $event ) : ?>
234
  <tr>
235
  <td><?php echo esc_html( $event['id'] ); ?></td>
236
- <td><?php echo date( 'Y-m-d H:i:s', $event['fire_at'] ); ?></td>
237
  <td><?php echo isset( $event['schedule'] ) ? $event['schedule'] : '–'; ?></td>
238
- <td><?php $event['data'] ? ITSEC_Lib::print_r( $event['data'] ) : print( '–' ); ?></td>
239
  <td>
240
  <button class="button" data-id="<?php echo esc_attr( $event['id'] ); ?>"
241
- data-data="<?php echo isset( $event['schedule'] ) ? '' : esc_attr( wp_json_encode( $event['data'] ) ); ?>">
242
  <?php esc_html_e( 'Run', 'better-wp-security' ) ?>
243
  </button>
244
  </td>
89
  if ( empty( $_POST['data']['id'] ) ) {
90
  ITSEC_Response::add_error( new WP_Error( 'itsec-debug-page-run-event-missing-id', __( 'The server did not receive a valid request. The required "data.id" argument for the "run_event" method is missing.', 'better-wp-security' ) ) );
91
  } elseif ( ! empty( $_POST['data']['data'] ) ) {
92
+ $hash = $_POST['data']['data'];
93
 
94
+ if ( ! is_string( $hash ) ) {
95
+ ITSEC_Response::add_error( new WP_Error( 'itsec-debug-page-run-event-invalid-data', __( 'The server did not receive a valid request. The "data.data" argument for the "run_event" method is an invalid string.', 'better-wp-security' ) ) );
96
  } else {
97
+ ITSEC_Core::get_scheduler()->run_single_event_by_hash( $_POST['data']['id'], $hash );
98
  ITSEC_Response::set_response( $this->get_events_table() );
99
  ITSEC_Response::set_success( true );
100
  ITSEC_Response::add_message( __( 'Event successfully run.', 'better-wp-security' ) );
225
  <th><?php esc_html_e( 'ID', 'better-wp-security' ) ?></th>
226
  <th><?php esc_html_e( 'Fire At', 'better-wp-security' ) ?></th>
227
  <th><?php esc_html_e( 'Schedule', 'better-wp-security' ) ?></th>
228
+ <th><button class="button-link" id="itsec-events-data-toggle"><?php esc_html_e( 'Data', 'better-wp-security' ) ?></button></th>
229
  <th></th>
230
  </tr>
231
  </thead>
233
  <?php foreach ( array_merge( $scheduler->get_recurring_events(), $scheduler->get_single_events() ) as $event ) : ?>
234
  <tr>
235
  <td><?php echo esc_html( $event['id'] ); ?></td>
236
+ <td><?php echo date( 'Y-m-d H:i:s', $event['fire_at'] ); ?> (<?php echo esc_html( human_time_diff( $event['fire_at'] ) ) ?>)</td>
237
  <td><?php echo isset( $event['schedule'] ) ? $event['schedule'] : '–'; ?></td>
238
+ <td><div class="hidden itsec-events-data"><?php $event['data'] ? ITSEC_Lib::print_r( $event['data'] ) : print( '–' ); ?></div></td>
239
  <td>
240
  <button class="button" data-id="<?php echo esc_attr( $event['id'] ); ?>"
241
+ data-data="<?php echo isset( $event['schedule'] ) ? '' : esc_attr( $event['hash'] ); ?>">
242
  <?php esc_html_e( 'Run', 'better-wp-security' ) ?>
243
  </button>
244
  </td>
core/admin-pages/page-logs.php CHANGED
@@ -274,34 +274,42 @@ final class ITSEC_Logs_Page {
274
  'module' => array(
275
  'header' => esc_html__( 'Module', 'better-wp-security' ),
276
  'content' => esc_html( $entry['module'] ),
 
277
  ),
278
  'type' => array(
279
  'header' => esc_html__( 'Type', 'better-wp-security' ),
280
  'content' => $type,
 
281
  ),
282
  'description' => array(
283
  'header' => esc_html__( 'Description', 'better-wp-security' ),
284
  'content' => esc_html( $code ),
 
285
  ),
286
  'timestamp' => array(
287
  'header' => esc_html__( 'Timestamp', 'better-wp-security' ),
288
  'content' => esc_html( $datetime ),
 
289
  ),
290
  'host' => array(
291
  'header' => esc_html__( 'Host', 'better-wp-security' ),
292
  'content' => '<code>' . esc_html( $entry['remote_ip'] ) . '</code>',
 
293
  ),
294
  'user' => array(
295
  'header' => esc_html__( 'User', 'better-wp-security' ),
296
  'content' => esc_html( $username ),
 
297
  ),
298
  'url' => array(
299
  'header' => esc_html__( 'URL', 'better-wp-security' ),
300
  'content' => '<code>' . $url . '</code>',
 
301
  ),
302
  'raw-details' => array(
303
  'header' => esc_html__( 'Raw Details', 'better-wp-security' ),
304
  'content' => true,
 
305
  ),
306
  );
307
 
@@ -329,6 +337,17 @@ final class ITSEC_Logs_Page {
329
 
330
  $details['raw-details']['content'] = '<p><a class="itsec-log-raw-details-toggle" href="#">' . $this->translations['show_raw_details'] . '</a></p><div class="itsec-log-raw-details">' . $details['raw-details']['content'] . '</div>';
331
  }
 
 
 
 
 
 
 
 
 
 
 
332
  }
333
 
334
  ob_start();
@@ -468,6 +487,7 @@ final class ITSEC_Logs_Page {
468
  $list->prepare_items();
469
  $list->views();
470
  $form->start_form( array( 'method' => 'GET' ) );
 
471
  $list->display();
472
  $form->end_form();
473
  ?>
274
  'module' => array(
275
  'header' => esc_html__( 'Module', 'better-wp-security' ),
276
  'content' => esc_html( $entry['module'] ),
277
+ 'order' => 0,
278
  ),
279
  'type' => array(
280
  'header' => esc_html__( 'Type', 'better-wp-security' ),
281
  'content' => $type,
282
+ 'order' => 10,
283
  ),
284
  'description' => array(
285
  'header' => esc_html__( 'Description', 'better-wp-security' ),
286
  'content' => esc_html( $code ),
287
+ 'order' => 20,
288
  ),
289
  'timestamp' => array(
290
  'header' => esc_html__( 'Timestamp', 'better-wp-security' ),
291
  'content' => esc_html( $datetime ),
292
+ 'order' => 30,
293
  ),
294
  'host' => array(
295
  'header' => esc_html__( 'Host', 'better-wp-security' ),
296
  'content' => '<code>' . esc_html( $entry['remote_ip'] ) . '</code>',
297
+ 'order' => 40,
298
  ),
299
  'user' => array(
300
  'header' => esc_html__( 'User', 'better-wp-security' ),
301
  'content' => esc_html( $username ),
302
+ 'order' => 50,
303
  ),
304
  'url' => array(
305
  'header' => esc_html__( 'URL', 'better-wp-security' ),
306
  'content' => '<code>' . $url . '</code>',
307
+ 'order' => 60,
308
  ),
309
  'raw-details' => array(
310
  'header' => esc_html__( 'Raw Details', 'better-wp-security' ),
311
  'content' => true,
312
+ 'order' => PHP_INT_MAX,
313
  ),
314
  );
315
 
337
 
338
  $details['raw-details']['content'] = '<p><a class="itsec-log-raw-details-toggle" href="#">' . $this->translations['show_raw_details'] . '</a></p><div class="itsec-log-raw-details">' . $details['raw-details']['content'] . '</div>';
339
  }
340
+
341
+ $i = 1;
342
+
343
+ foreach ( $details as $column => $detail ) {
344
+ if ( ! isset( $detail['order'] ) ) {
345
+ $details[ $column ]['order'] = PHP_INT_MAX - 10 * $i;
346
+ $i ++;
347
+ }
348
+ }
349
+
350
+ $details = wp_list_sort( $details, 'order', 'ASC', true );
351
  }
352
 
353
  ob_start();
487
  $list->prepare_items();
488
  $list->views();
489
  $form->start_form( array( 'method' => 'GET' ) );
490
+ $form->add_hidden( 'page', 'itsec-logs' );
491
  $list->display();
492
  $form->end_form();
493
  ?>
core/core.php CHANGED
@@ -24,7 +24,7 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
24
  *
25
  * @access private
26
  */
27
- private $plugin_build = 4097;
28
 
29
  /**
30
  * Used to distinguish between a user modifying settings and the API modifying settings (such as from Sync
@@ -604,11 +604,21 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
604
  return $url;
605
  }
606
 
607
- public static function get_logs_page_url( $filter = false ) {
608
  $url = network_admin_url( 'admin.php?page=itsec-logs' );
609
 
610
- if ( ! empty( $filter ) ) {
611
- $url = add_query_arg( array( 'filters' => rawurlencode( "module|{$filter}" ) ), $url );
 
 
 
 
 
 
 
 
 
 
612
  }
613
 
614
  return $url;
24
  *
25
  * @access private
26
  */
27
+ private $plugin_build = 4106;
28
 
29
  /**
30
  * Used to distinguish between a user modifying settings and the API modifying settings (such as from Sync
604
  return $url;
605
  }
606
 
607
+ public static function get_logs_page_url( $module = false, $type = false ) {
608
  $url = network_admin_url( 'admin.php?page=itsec-logs' );
609
 
610
+ $filters = array();
611
+
612
+ if ( $module ) {
613
+ $filters[] = rawurlencode("module|{$module}");
614
+ }
615
+
616
+ if ( $type ) {
617
+ $filters[] = rawurlencode( "type|{$type}" );
618
+ }
619
+
620
+ if ( $filters ) {
621
+ $url = add_query_arg( array( 'filters' => $filters ), $url );
622
  }
623
 
624
  return $url;
core/history.txt CHANGED
@@ -725,4 +725,25 @@
725
  4.6.5 - 2018-06-27 - Chris Jean & Timothy Jacobs
726
  Enhancement: Add mitigation for the WordPress Attachment File Traversal and Deletion vulnerability.
727
  Tweak: Fire a WordPress action whenever settings are updated.
728
- Bug Fix: Improved input sanitization on the logs page to prevent triggering warnings.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
725
  4.6.5 - 2018-06-27 - Chris Jean & Timothy Jacobs
726
  Enhancement: Add mitigation for the WordPress Attachment File Traversal and Deletion vulnerability.
727
  Tweak: Fire a WordPress action whenever settings are updated.
728
+ Bug Fix: Improved input sanitization on the logs page to prevent triggering warnings.
729
+ 4.6.6 - 2018-07-09 - Chris Jean & Timothy Jacobs
730
+ Tweak: Check if an IP is blacklisted on page load for compatibility with servers that cannot process server configuration level bans immediately.
731
+ 4.7.0 - 2018-07-17 - Chris Jean & Timothy Jacobs
732
+ Tweak: Display a time diff until the next event on the Debug page.
733
+ Compatibility Fix: 404 detection for plugins that mark is_404 later in the hook sequence.
734
+ 4.7.1 - 2018-07-24 - Chris Jean & Timothy Jacobs
735
+ Tweak: Use Logging API for tracking Notification Center errors.
736
+ Tweak: Register Scheduler Events whenever the plugin build changes.
737
+ Tweak: Allow for filtering logs by any module recorded.
738
+ Bug Fix: Account for any CLI PHP SAPI instead of just WP-CLI in the SSL Module.
739
+ 4.7.2 - 2018-07-31 - Chris Jean & Timothy Jacobs
740
+ New Feature: Allow for globally setting recipients for admin-targeted notifications. All new notifications will default to the recipients in this list. Notifications can be set to use the default list or switch to a custom list.
741
+ Tweak: Account for 3rd-party Backup Plugin in Security Check.
742
+ 4.7.3 - 2018-08-01 - Chris Jean & Timothy Jacobs
743
+ Bug Fix: Fix serialization of closure error when a plugin registering a hook with a closure is in the boot-up stack and the notification center is triggered too early in the cycle.
744
+ 4.7.4 - 2018-08-06 - Chris Jean & Timothy Jacobs
745
+ Enhancement: Added a setting to enable/disable the Grade Report feature of Pro.
746
+ 4.7.5 - 2018-08-07 - Chris Jean & Timothy Jacobs
747
+ Bug Fix: Fixed how the Grade Report enable/disable status is stored to fix admin page loading issues on some sites.
748
+ 4.7.6 - 2018-08-14 - Chris Jean & Timothy Jacobs
749
+ Bug Fix: REST API Protection blocked the Taxonomies route for all users.
core/lib.php CHANGED
@@ -39,7 +39,7 @@ final class ITSEC_Lib {
39
  w3tc_dbcache_flush();
40
  w3tc_objectcache_flush();
41
 
42
- } else if ( function_exists( 'wp_cache_clear_cache' ) && true == $page ) {
43
 
44
  wp_cache_clear_cache();
45
 
@@ -84,7 +84,7 @@ final class ITSEC_Lib {
84
  *
85
  * @since 4.0.0
86
  *
87
- * @param string $url URL to filter
88
  *
89
  * @return string domain name or '*' on error or domain mapped multisite
90
  * */
@@ -106,7 +106,7 @@ final class ITSEC_Lib {
106
  $host_parts = explode( '.', $host );
107
 
108
  if ( count( $host_parts ) > 2 ) {
109
- $host_parts = array_slice( $host_parts, -2, 2 );
110
  }
111
 
112
  return implode( '.', $host_parts );
@@ -175,6 +175,7 @@ final class ITSEC_Lib {
175
 
176
  if ( ! empty( $ip ) ) {
177
  $GLOBALS['__itsec_remote_ip'] = $ip;
 
178
  return $ip;
179
  }
180
  }
@@ -184,6 +185,7 @@ final class ITSEC_Lib {
184
 
185
  if ( ITSEC_Modules::get_setting( 'global', 'proxy_override' ) ) {
186
  $GLOBALS['__itsec_remote_ip'] = $_SERVER['REMOTE_ADDR'];
 
187
  return $GLOBALS['__itsec_remote_ip'];
188
  }
189
 
@@ -203,19 +205,19 @@ final class ITSEC_Lib {
203
 
204
  // Loop through twice. The first run won't accept a reserved or private range IP. If an acceptable IP is not
205
  // found, try again while accepting reserved or private range IPs.
206
- for ( $x = 0; $x < 2; $x++ ) {
207
  foreach ( $headers as $header ) {
208
- if ( ! isset( $_SERVER[$header] ) ) {
209
  continue;
210
  }
211
 
212
- $ip = trim( $_SERVER[$header] );
213
 
214
  if ( empty( $ip ) ) {
215
  continue;
216
  }
217
 
218
- if ( false !== ( $comma_index = strpos( $_SERVER[$header], ',' ) ) ) {
219
  $ip = substr( $ip, 0, $comma_index );
220
  }
221
 
@@ -406,21 +408,21 @@ final class ITSEC_Lib {
406
 
407
  if ( - 1 < $memory_limit ) {
408
 
409
- $unit = strtolower( substr( $memory_limit, - 1 ) );
410
  $memory_limit = (int) $memory_limit;
411
 
412
- $new_unit = strtolower( substr( $new_memory_limit, - 1 ) );
413
  $new_memory_limit = (int) $new_memory_limit;
414
 
415
  if ( 'm' == $unit ) {
416
 
417
  $memory_limit *= 1048576;
418
 
419
- } else if ( 'g' == $unit ) {
420
 
421
  $memory_limit *= 1073741824;
422
 
423
- } else if ( 'k' == $unit ) {
424
 
425
  $memory_limit *= 1024;
426
 
@@ -430,11 +432,11 @@ final class ITSEC_Lib {
430
 
431
  $new_memory_limit *= 1048576;
432
 
433
- } else if ( 'g' == $new_unit ) {
434
 
435
  $new_memory_limit *= 1073741824;
436
 
437
- } else if ( 'k' == $new_unit ) {
438
 
439
  $new_memory_limit *= 1024;
440
 
@@ -531,11 +533,11 @@ final class ITSEC_Lib {
531
 
532
  if ( false === $user ) {
533
  $user = wp_get_current_user();
534
- } else if ( is_int( $user ) ) {
535
  $user = get_user_by( 'id', $user );
536
- } else if ( is_string( $user ) ) {
537
  $user = get_user_by( 'login', $user );
538
- } else if ( is_object( $user ) && isset( $user->ID ) ) {
539
  $user = get_user_by( 'id', $user->ID );
540
  } else {
541
  if ( is_object( $user ) ) {
@@ -607,7 +609,7 @@ final class ITSEC_Lib {
607
 
608
  if ( 'Basic ' === $http_auth_type ) {
609
  $authentication_types[] = 'header_http_basic_auth';
610
- } else if ( 'OAuth ' === $http_auth_type ) {
611
  $authentication_types[] = 'header_http_oauth';
612
  }
613
  }
@@ -624,14 +626,14 @@ final class ITSEC_Lib {
624
  $authentication_types[] = 'post_oauth';
625
  }
626
 
627
- if ( defined('XMLRPC_REQUEST') && XMLRPC_REQUEST ) {
628
- $source = 'xmlrpc';
629
  $authentication_types = array( 'username_and_password' );
630
- } else if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
631
- $source = 'rest_api';
632
  $authentication_types[] = 'cookie';
633
  } else {
634
- $source = 'wp-login.php';
635
  $authentication_types = array( 'username_and_password' );
636
  }
637
 
@@ -673,7 +675,7 @@ final class ITSEC_Lib {
673
  */
674
  public static function get_request_path() {
675
  if ( ! isset( $GLOBALS['__itsec_lib_get_request_path'] ) ) {
676
- $request_uri = preg_replace( '|//+|', '/', $_SERVER['REQUEST_URI'] );
677
  $GLOBALS['__itsec_lib_get_request_path'] = self::get_url_path( $request_uri, self::get_home_root() );
678
  }
679
 
@@ -696,8 +698,8 @@ final class ITSEC_Lib {
696
  global $wpdb;
697
  $main_options = $wpdb->base_prefix . 'options';
698
 
699
- $lock = "itsec-lock-{$name}";
700
- $now = ITSEC_Core::get_current_time_gmt();
701
  $release_at = $now + $expires_in;
702
 
703
  if ( is_multisite() ) {
@@ -780,7 +782,7 @@ final class ITSEC_Lib {
780
  $alloptions = wp_cache_get( 'alloptions' );
781
 
782
  if ( is_array( $alloptions ) && isset( $alloptions[ $lock ] ) ) {
783
- unset( $alloptions[$lock] );
784
  wp_cache_set( 'alloptions', $alloptions, 'options' );
785
  } else {
786
  wp_cache_delete( $lock, 'options' );
@@ -845,13 +847,13 @@ final class ITSEC_Lib {
845
  wp_cache_switch_to_blog( 1 );
846
 
847
  $alloptions = wp_cache_get( 'alloptions' );
848
- $set_all = false;
849
 
850
  foreach ( $rows as $row ) {
851
  $lock = $row->option_name;
852
 
853
  if ( is_array( $alloptions ) && isset( $alloptions[ $lock ] ) ) {
854
- unset( $alloptions[$lock] );
855
  $set_all = true;
856
  } else {
857
  wp_cache_delete( $lock, 'options' );
@@ -918,7 +920,7 @@ final class ITSEC_Lib {
918
  public static function get_ssl_support_probability() {
919
  if ( is_ssl() ) {
920
  $probability = 50; // The site appears to be on an SSL connection but it could be self-signed or otherwise
921
- // not valid to a visitor.
922
  } else {
923
  $probability = 0;
924
  }
@@ -1183,4 +1185,83 @@ final class ITSEC_Lib {
1183
 
1184
  return $array;
1185
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1186
  }
39
  w3tc_dbcache_flush();
40
  w3tc_objectcache_flush();
41
 
42
+ } elseif ( function_exists( 'wp_cache_clear_cache' ) && true == $page ) {
43
 
44
  wp_cache_clear_cache();
45
 
84
  *
85
  * @since 4.0.0
86
  *
87
+ * @param string $url URL to filter
88
  *
89
  * @return string domain name or '*' on error or domain mapped multisite
90
  * */
106
  $host_parts = explode( '.', $host );
107
 
108
  if ( count( $host_parts ) > 2 ) {
109
+ $host_parts = array_slice( $host_parts, - 2, 2 );
110
  }
111
 
112
  return implode( '.', $host_parts );
175
 
176
  if ( ! empty( $ip ) ) {
177
  $GLOBALS['__itsec_remote_ip'] = $ip;
178
+
179
  return $ip;
180
  }
181
  }
185
 
186
  if ( ITSEC_Modules::get_setting( 'global', 'proxy_override' ) ) {
187
  $GLOBALS['__itsec_remote_ip'] = $_SERVER['REMOTE_ADDR'];
188
+
189
  return $GLOBALS['__itsec_remote_ip'];
190
  }
191
 
205
 
206
  // Loop through twice. The first run won't accept a reserved or private range IP. If an acceptable IP is not
207
  // found, try again while accepting reserved or private range IPs.
208
+ for ( $x = 0; $x < 2; $x ++ ) {
209
  foreach ( $headers as $header ) {
210
+ if ( ! isset( $_SERVER[ $header ] ) ) {
211
  continue;
212
  }
213
 
214
+ $ip = trim( $_SERVER[ $header ] );
215
 
216
  if ( empty( $ip ) ) {
217
  continue;
218
  }
219
 
220
+ if ( false !== ( $comma_index = strpos( $_SERVER[ $header ], ',' ) ) ) {
221
  $ip = substr( $ip, 0, $comma_index );
222
  }
223
 
408
 
409
  if ( - 1 < $memory_limit ) {
410
 
411
+ $unit = strtolower( substr( $memory_limit, - 1 ) );
412
  $memory_limit = (int) $memory_limit;
413
 
414
+ $new_unit = strtolower( substr( $new_memory_limit, - 1 ) );
415
  $new_memory_limit = (int) $new_memory_limit;
416
 
417
  if ( 'm' == $unit ) {
418
 
419
  $memory_limit *= 1048576;
420
 
421
+ } elseif ( 'g' == $unit ) {
422
 
423
  $memory_limit *= 1073741824;
424
 
425
+ } elseif ( 'k' == $unit ) {
426
 
427
  $memory_limit *= 1024;
428
 
432
 
433
  $new_memory_limit *= 1048576;
434
 
435
+ } elseif ( 'g' == $new_unit ) {
436
 
437
  $new_memory_limit *= 1073741824;
438
 
439
+ } elseif ( 'k' == $new_unit ) {
440
 
441
  $new_memory_limit *= 1024;
442
 
533
 
534
  if ( false === $user ) {
535
  $user = wp_get_current_user();
536
+ } elseif ( is_int( $user ) ) {
537
  $user = get_user_by( 'id', $user );
538
+ } elseif ( is_string( $user ) ) {
539
  $user = get_user_by( 'login', $user );
540
+ } elseif ( is_object( $user ) && isset( $user->ID ) ) {
541
  $user = get_user_by( 'id', $user->ID );
542
  } else {
543
  if ( is_object( $user ) ) {
609
 
610
  if ( 'Basic ' === $http_auth_type ) {
611
  $authentication_types[] = 'header_http_basic_auth';
612
+ } elseif ( 'OAuth ' === $http_auth_type ) {
613
  $authentication_types[] = 'header_http_oauth';
614
  }
615
  }
626
  $authentication_types[] = 'post_oauth';
627
  }
628
 
629
+ if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) {
630
+ $source = 'xmlrpc';
631
  $authentication_types = array( 'username_and_password' );
632
+ } elseif ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
633
+ $source = 'rest_api';
634
  $authentication_types[] = 'cookie';
635
  } else {
636
+ $source = 'wp-login.php';
637
  $authentication_types = array( 'username_and_password' );
638
  }
639
 
675
  */
676
  public static function get_request_path() {
677
  if ( ! isset( $GLOBALS['__itsec_lib_get_request_path'] ) ) {
678
+ $request_uri = preg_replace( '|//+|', '/', $_SERVER['REQUEST_URI'] );
679
  $GLOBALS['__itsec_lib_get_request_path'] = self::get_url_path( $request_uri, self::get_home_root() );
680
  }
681
 
698
  global $wpdb;
699
  $main_options = $wpdb->base_prefix . 'options';
700
 
701
+ $lock = "itsec-lock-{$name}";
702
+ $now = ITSEC_Core::get_current_time_gmt();
703
  $release_at = $now + $expires_in;
704
 
705
  if ( is_multisite() ) {
782
  $alloptions = wp_cache_get( 'alloptions' );
783
 
784
  if ( is_array( $alloptions ) && isset( $alloptions[ $lock ] ) ) {
785
+ unset( $alloptions[ $lock ] );
786
  wp_cache_set( 'alloptions', $alloptions, 'options' );
787
  } else {
788
  wp_cache_delete( $lock, 'options' );
847
  wp_cache_switch_to_blog( 1 );
848
 
849
  $alloptions = wp_cache_get( 'alloptions' );
850
+ $set_all = false;
851
 
852
  foreach ( $rows as $row ) {
853
  $lock = $row->option_name;
854
 
855
  if ( is_array( $alloptions ) && isset( $alloptions[ $lock ] ) ) {
856
+ unset( $alloptions[ $lock ] );
857
  $set_all = true;
858
  } else {
859
  wp_cache_delete( $lock, 'options' );
920
  public static function get_ssl_support_probability() {
921
  if ( is_ssl() ) {
922
  $probability = 50; // The site appears to be on an SSL connection but it could be self-signed or otherwise
923
+ // not valid to a visitor.
924
  } else {
925
  $probability = 0;
926
  }
1185
 
1186
  return $array;
1187
  }
1188
+
1189
+ /**
1190
+ * Array unique implementation that allows for non-scalar values.
1191
+ *
1192
+ * Will compare elements using `serialize()`.
1193
+ *
1194
+ * Keys are preserved. If a numeric array is given, the array will be re-indexed.
1195
+ *
1196
+ * @param array $array
1197
+ *
1198
+ * @return array
1199
+ */
1200
+ public static function non_scalar_array_unique( $array ) {
1201
+
1202
+ $is_numeric = wp_is_numeric_array( $array );
1203
+
1204
+ $hashes = array();
1205
+
1206
+ foreach ( $array as $key => $value ) {
1207
+ $hash = serialize( $value );
1208
+
1209
+ if ( isset( $hashes[ $hash ] ) ) {
1210
+ unset( $array[ $key ] );
1211
+ } else {
1212
+ $hashes[ $hash ] = 1;
1213
+ }
1214
+ }
1215
+
1216
+ if ( $is_numeric ) {
1217
+ return array_values( $array );
1218
+ }
1219
+
1220
+ return $array;
1221
+ }
1222
+
1223
+ /**
1224
+ * Get whatever backup plugin is being used on this site.
1225
+ *
1226
+ * @return string
1227
+ */
1228
+ public static function get_backup_plugin() {
1229
+
1230
+ $possible = array(
1231
+ 'backupbuddy/backupbuddy.php',
1232
+ 'updraftplus/updraftplus.php',
1233
+ 'backwpup/backwpup.php',
1234
+ 'xcloner-backup-and-restore/xcloner.php',
1235
+ 'duplicator/duplicator.php',
1236
+ 'backup/backup.php',
1237
+ 'wp-db-backup/wp-db-backup.php',
1238
+ 'backupwordpress/backupwordpress.php',
1239
+ 'blogvault-real-time-backup/blogvault.php',
1240
+ 'wp-all-backup/wp-all-backup.php',
1241
+ 'vaultpress/vaultpress.php',
1242
+ );
1243
+
1244
+ /**
1245
+ * Filter the list of possible backup plugins.
1246
+ *
1247
+ * @param string[] List of Backup Plugin __FILE__.
1248
+ */
1249
+ $possible = apply_filters( 'itsec_possible_backup_plugins', $possible );
1250
+
1251
+ if ( ! function_exists( 'is_plugin_active' ) ) {
1252
+ require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
1253
+ }
1254
+
1255
+ if ( ! function_exists( 'is_plugin_active' ) ) {
1256
+ return '';
1257
+ }
1258
+
1259
+ foreach ( $possible as $file ) {
1260
+ if ( is_plugin_active( $file ) ) {
1261
+ return $file;
1262
+ }
1263
+ }
1264
+
1265
+ return '';
1266
+ }
1267
  }
core/lib/class-itsec-job.php CHANGED
@@ -34,10 +34,11 @@ class ITSEC_Job {
34
  *
35
  * The original event will not fire while a reschedule is pending.
36
  *
37
- * @param int $seconds
 
38
  */
39
- public function reschedule_in( $seconds ) {
40
- $data = $this->get_data();
41
 
42
  if ( isset( $data['retry_count'] ) ) {
43
  $data['retry_count'] ++;
34
  *
35
  * The original event will not fire while a reschedule is pending.
36
  *
37
+ * @param int $seconds
38
+ * @param array $data Additional data to attach to the rescheduled event.
39
  */
40
+ public function reschedule_in( $seconds, $data = array() ) {
41
+ $data = array_merge( $this->data, $data );
42
 
43
  if ( isset( $data['retry_count'] ) ) {
44
  $data['retry_count'] ++;
core/lib/class-itsec-lib-canonical-roles.php CHANGED
@@ -165,6 +165,33 @@ final class ITSEC_Lib_Canonical_Roles {
165
  return self::get_role_from_caps( array_merge( $role_caps, $user_caps ) );
166
  }
167
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  /**
169
  * Get a list of all of the capabilities that are unique to each role.
170
  *
165
  return self::get_role_from_caps( array_merge( $role_caps, $user_caps ) );
166
  }
167
 
168
+ /**
169
+ * Get all users that have the given canonical role.
170
+ *
171
+ * @param string|string[] $canonical
172
+ * @param array $additional_args
173
+ *
174
+ * @return WP_User[]
175
+ */
176
+ public static function get_users_with_canonical_role( $canonical, $additional_args = array() ) {
177
+
178
+ $canonical = (array) $canonical;
179
+
180
+ $roles = array();
181
+
182
+ foreach ( wp_roles()->roles as $role => $_ ) {
183
+ if ( in_array( self::get_canonical_role_from_role( $role ), $canonical, true ) ) {
184
+ $roles[] = $role;
185
+ }
186
+ }
187
+
188
+ if ( empty( $roles ) ) {
189
+ return array();
190
+ }
191
+
192
+ return get_users( array_merge( $additional_args, array( 'role__in' => $roles ) ) );
193
+ }
194
+
195
  /**
196
  * Get a list of all of the capabilities that are unique to each role.
197
  *
core/lib/class-itsec-mail.php CHANGED
@@ -369,7 +369,13 @@ final class ITSEC_Mail {
369
  return "<li style=\"margin: 0; padding: 5px 10px;{$bold_tag}\">{$item}</li>";
370
  }
371
 
372
- private function add_html( $html, $identifier = null ) {
 
 
 
 
 
 
373
 
374
  if ( null !== $this->current_group ) {
375
  $this->deferred .= $html;
@@ -400,7 +406,18 @@ final class ITSEC_Mail {
400
  * This is automatically included in non-user emails if ITSEC_DEBUG is turned on.
401
  */
402
  public function include_debug_info() {
403
- $this->add_text( sprintf( esc_html__( 'Debug info (source page): %s', 'better-wp-security' ), esc_url( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ) ) );
 
 
 
 
 
 
 
 
 
 
 
404
  }
405
 
406
  /**
@@ -429,7 +446,7 @@ final class ITSEC_Mail {
429
  $this->content = $content;
430
  }
431
 
432
- public function get_content() {
433
 
434
  $groups = $this->groups;
435
 
@@ -439,8 +456,9 @@ final class ITSEC_Mail {
439
  *
440
  * @param array $groups
441
  * @param ITSEC_Mail $this
 
442
  */
443
- $groups = apply_filters( "itsec_mail_{$this->name}", $groups, $this );
444
  }
445
 
446
  return implode( '', $groups );
@@ -505,7 +523,25 @@ final class ITSEC_Mail {
505
  $this->set_default_subject();
506
  }
507
 
508
- return wp_mail( $this->recipients, $this->get_subject(), $this->content ? $this->content : $this->get_content(), array( 'Content-Type: text/html; charset=UTF-8' ), $this->attachments );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
509
  }
510
 
511
  /**
369
  return "<li style=\"margin: 0; padding: 5px 10px;{$bold_tag}\">{$item}</li>";
370
  }
371
 
372
+ /**
373
+ * Add a section of HTML to the email.
374
+ *
375
+ * @param string $html
376
+ * @param string|null $identifier
377
+ */
378
+ public function add_html( $html, $identifier = null ) {
379
 
380
  if ( null !== $this->current_group ) {
381
  $this->deferred .= $html;
406
  * This is automatically included in non-user emails if ITSEC_DEBUG is turned on.
407
  */
408
  public function include_debug_info() {
409
+
410
+ if ( ( defined( 'DOING_CRON' ) && DOING_CRON ) || ( function_exists( 'wp_doing_cron' ) && wp_doing_cron() ) ) {
411
+ $page = 'WP-Cron';
412
+ } elseif ( defined( 'WP_CLI' ) && WP_CLI ) {
413
+ $page = 'WP-CLI';
414
+ } elseif ( isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) {
415
+ $page = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
416
+ } else {
417
+ $page = 'unknown';
418
+ }
419
+
420
+ $this->add_text( sprintf( esc_html__( 'Debug info (source page): %s', 'better-wp-security' ), esc_html( $page ) ) );
421
  }
422
 
423
  /**
446
  $this->content = $content;
447
  }
448
 
449
+ public function get_content( $recipient = '' ) {
450
 
451
  $groups = $this->groups;
452
 
456
  *
457
  * @param array $groups
458
  * @param ITSEC_Mail $this
459
+ * @param string $recipient
460
  */
461
+ $groups = apply_filters( "itsec_mail_{$this->name}", $groups, $this, $recipient );
462
  }
463
 
464
  return implode( '', $groups );
523
  $this->set_default_subject();
524
  }
525
 
526
+ $headers = array(
527
+ 'Content-Type: text/html; charset=UTF-8',
528
+ );
529
+
530
+ if ( $from = ITSEC_Modules::get_setting( 'notification-center', 'from_email' ) ) {
531
+ $headers[] = "From: <{$from}>";
532
+ }
533
+
534
+ if ( $this->name ) {
535
+ $result = true;
536
+
537
+ foreach ( $this->recipients as $recipient ) {
538
+ $result = wp_mail( $recipient, $this->get_subject(), $this->content ? $this->content : $this->get_content( $recipient ), $headers, $this->attachments ) && $result;
539
+ }
540
+
541
+ return $result;
542
+ }
543
+
544
+ return wp_mail( $this->recipients, $this->get_subject(), $this->content ? $this->content : $this->get_content(), $headers, $this->attachments );
545
  }
546
 
547
  /**
core/lib/class-itsec-scheduler-cron.php CHANGED
@@ -12,15 +12,19 @@ class ITSEC_Scheduler_Cron extends ITSEC_Scheduler {
12
 
13
  public function register_cron_schedules( $schedules ) {
14
 
15
- $schedules[ 'itsec-' . self::S_FOUR_DAILY ] = array(
 
 
 
 
16
  'display' => esc_html__( 'Four Times per Day', 'better-wp-security' ),
17
  'interval' => DAY_IN_SECONDS / 4,
18
  );
19
- $schedules[ 'itsec-' . self::S_WEEKLY ] = array(
20
  'display' => esc_html__( 'Weekly', 'better-wp-security' ),
21
  'interval' => WEEK_IN_SECONDS,
22
  );
23
- $schedules[ 'itsec-' . self::S_MONTHLY ] = array(
24
  'display' => esc_html__( 'Monthly', 'better-wp-security' ),
25
  'interval' => MONTH_IN_SECONDS,
26
  );
@@ -62,20 +66,18 @@ class ITSEC_Scheduler_Cron extends ITSEC_Scheduler {
62
  $this->run_single_event_by_hash( $id, $this->hash_data( $data ) );
63
  }
64
 
65
- /**
66
- * Run a single event.
67
- *
68
- * @param string $id
69
- * @param string $hash
70
- */
71
- private function run_single_event_by_hash( $id, $hash ) {
72
 
73
  $opts = array( 'single' => true );
74
 
75
  $storage = $this->get_options();
76
- $data = $storage['single'][ $id ][ $hash ]['data'];
77
 
78
- $job = $this->make_job( $id, $data, $opts );
 
 
 
 
 
79
 
80
  $this->unschedule_single( $id, $data );
81
  $this->call_action( $job );
@@ -351,6 +353,7 @@ class ITSEC_Scheduler_Cron extends ITSEC_Scheduler {
351
  'id' => $id,
352
  'data' => $options['single'][ $id ][ $hash ]['data'],
353
  'fire_at' => $timestamp,
 
354
  );
355
  }
356
  }
12
 
13
  public function register_cron_schedules( $schedules ) {
14
 
15
+ $schedules[ 'itsec-' . self::S_TWICE_HOURLY ] = array(
16
+ 'display' => esc_html__( 'Twice Hourly', 'better-wp-security' ),
17
+ 'interval' => HOUR_IN_SECONDS / 2,
18
+ );
19
+ $schedules[ 'itsec-' . self::S_FOUR_DAILY ] = array(
20
  'display' => esc_html__( 'Four Times per Day', 'better-wp-security' ),
21
  'interval' => DAY_IN_SECONDS / 4,
22
  );
23
+ $schedules[ 'itsec-' . self::S_WEEKLY ] = array(
24
  'display' => esc_html__( 'Weekly', 'better-wp-security' ),
25
  'interval' => WEEK_IN_SECONDS,
26
  );
27
+ $schedules[ 'itsec-' . self::S_MONTHLY ] = array(
28
  'display' => esc_html__( 'Monthly', 'better-wp-security' ),
29
  'interval' => MONTH_IN_SECONDS,
30
  );
66
  $this->run_single_event_by_hash( $id, $this->hash_data( $data ) );
67
  }
68
 
69
+ public function run_single_event_by_hash( $id, $hash ) {
 
 
 
 
 
 
70
 
71
  $opts = array( 'single' => true );
72
 
73
  $storage = $this->get_options();
 
74
 
75
+ if ( ! isset( $storage['single'][ $id ][ $hash ] ) ) {
76
+ return;
77
+ }
78
+
79
+ $data = $storage['single'][ $id ][ $hash ]['data'];
80
+ $job = $this->make_job( $id, $data, $opts );
81
 
82
  $this->unschedule_single( $id, $data );
83
  $this->call_action( $job );
353
  'id' => $id,
354
  'data' => $options['single'][ $id ][ $hash ]['data'],
355
  'fire_at' => $timestamp,
356
+ 'hash' => $hash,
357
  );
358
  }
359
  }
core/lib/class-itsec-scheduler-page-load.php CHANGED
@@ -159,6 +159,7 @@ class ITSEC_Scheduler_Page_Load extends ITSEC_Scheduler {
159
  'id' => $id,
160
  'data' => $event['data'],
161
  'fire_at' => $event['fire_at'],
 
162
  );
163
  }
164
  }
@@ -265,6 +266,13 @@ class ITSEC_Scheduler_Page_Load extends ITSEC_Scheduler {
265
  }
266
 
267
  public function run_single_event( $id, $data = array() ) {
 
 
 
 
 
 
 
268
 
269
  if ( $this->operating_data ) {
270
  $clear_operating_data = false;
@@ -274,12 +282,19 @@ class ITSEC_Scheduler_Page_Load extends ITSEC_Scheduler {
274
  $storage = $this->operating_data = $this->get_options();
275
  }
276
 
277
- $hash = $this->hash_data( $data );
 
 
 
 
 
 
 
278
  $event = $storage['single'][ $id ][ $hash ];
279
 
280
  $job = $this->make_job( $id, $event['data'], array( 'single' => true ) );
281
 
282
- $this->unschedule_single( $id, $data );
283
  $this->call_action( $job );
284
 
285
  if ( $clear_operating_data ) {
159
  'id' => $id,
160
  'data' => $event['data'],
161
  'fire_at' => $event['fire_at'],
162
+ 'hash' => $hash,
163
  );
164
  }
165
  }
266
  }
267
 
268
  public function run_single_event( $id, $data = array() ) {
269
+ $this->run_single_event_by_hash( $id, $this->hash_data( $data ) );
270
+ }
271
+
272
+ /**
273
+ * @inheritDoc
274
+ */
275
+ public function run_single_event_by_hash( $id, $hash ) {
276
 
277
  if ( $this->operating_data ) {
278
  $clear_operating_data = false;
282
  $storage = $this->operating_data = $this->get_options();
283
  }
284
 
285
+ if ( ! isset( $storage['single'][ $id ][ $hash ] ) ) {
286
+ if ( $clear_operating_data ) {
287
+ $this->operating_data = null;
288
+ }
289
+
290
+ return;
291
+ }
292
+
293
  $event = $storage['single'][ $id ][ $hash ];
294
 
295
  $job = $this->make_job( $id, $event['data'], array( 'single' => true ) );
296
 
297
+ $this->unschedule_single( $id, $event['data'] );
298
  $this->call_action( $job );
299
 
300
  if ( $clear_operating_data ) {
core/lib/class-itsec-scheduler.php CHANGED
@@ -2,6 +2,7 @@
2
 
3
  abstract class ITSEC_Scheduler {
4
 
 
5
  const S_HOURLY = 'hourly';
6
  const S_FOUR_DAILY = 'four-daily';
7
  const S_TWICE_DAILY = 'twice-daily';
@@ -140,6 +141,7 @@ abstract class ITSEC_Scheduler {
140
  * - id: The ID the event was scheduled with.
141
  * - data: The data the event was scheduled with.
142
  * - fire_at: The time the event should be fired.
 
143
  *
144
  * @return array
145
  */
@@ -168,6 +170,16 @@ abstract class ITSEC_Scheduler {
168
  */
169
  abstract public function run_single_event( $id, $data = array() );
170
 
 
 
 
 
 
 
 
 
 
 
171
  /**
172
  * Run any events that are due now.
173
  *
@@ -326,6 +338,8 @@ abstract class ITSEC_Scheduler {
326
  */
327
  final public function get_schedule_interval( $schedule ) {
328
  switch ( $schedule ) {
 
 
329
  case self::S_HOURLY:
330
  return HOUR_IN_SECONDS;
331
  case self::S_FOUR_DAILY:
2
 
3
  abstract class ITSEC_Scheduler {
4
 
5
+ const S_TWICE_HOURLY = 'twice-hourly';
6
  const S_HOURLY = 'hourly';
7
  const S_FOUR_DAILY = 'four-daily';
8
  const S_TWICE_DAILY = 'twice-daily';
141
  * - id: The ID the event was scheduled with.
142
  * - data: The data the event was scheduled with.
143
  * - fire_at: The time the event should be fired.
144
+ * - hash: The event's data hash.
145
  *
146
  * @return array
147
  */
170
  */
171
  abstract public function run_single_event( $id, $data = array() );
172
 
173
+ /**
174
+ * Run a single event by it's hash.
175
+ *
176
+ * @param string $id
177
+ * @param string $hash
178
+ *
179
+ * @return void
180
+ */
181
+ abstract public function run_single_event_by_hash( $id, $hash );
182
+
183
  /**
184
  * Run any events that are due now.
185
  *
338
  */
339
  final public function get_schedule_interval( $schedule ) {
340
  switch ( $schedule ) {
341
+ case self::S_TWICE_HOURLY:
342
+ return HOUR_IN_SECONDS / 2;
343
  case self::S_HOURLY:
344
  return HOUR_IN_SECONDS;
345
  case self::S_FOUR_DAILY:
core/lib/class-itsec-wp-list-table.php CHANGED
@@ -757,9 +757,9 @@ class ITSEC_WP_List_Table {
757
  }
758
 
759
  if ( $disable_first ) {
760
- $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&laquo;</span>';
761
  } else {
762
- $page_links[] = sprintf( "<a class='first-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
763
  esc_url( remove_query_arg( 'paged', $current_url ) ),
764
  __( 'First page' ),
765
  '&laquo;'
@@ -767,9 +767,9 @@ class ITSEC_WP_List_Table {
767
  }
768
 
769
  if ( $disable_prev ) {
770
- $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&lsaquo;</span>';
771
  } else {
772
- $page_links[] = sprintf( "<a class='prev-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
773
  esc_url( add_query_arg( 'paged', max( 1, $current-1 ), $current_url ) ),
774
  __( 'Previous page' ),
775
  '&lsaquo;'
@@ -790,9 +790,9 @@ class ITSEC_WP_List_Table {
790
  $page_links[] = $total_pages_before . sprintf( _x( '%1$s of %2$s', 'paging' ), $html_current_page, $html_total_pages ) . $total_pages_after;
791
 
792
  if ( $disable_next ) {
793
- $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&rsaquo;</span>';
794
  } else {
795
- $page_links[] = sprintf( "<a class='next-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
796
  esc_url( add_query_arg( 'paged', min( $total_pages, $current+1 ), $current_url ) ),
797
  __( 'Next page' ),
798
  '&rsaquo;'
@@ -800,9 +800,9 @@ class ITSEC_WP_List_Table {
800
  }
801
 
802
  if ( $disable_last ) {
803
- $page_links[] = '<span class="tablenav-pages-navspan" aria-hidden="true">&raquo;</span>';
804
  } else {
805
- $page_links[] = sprintf( "<a class='last-page' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
806
  esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ),
807
  __( 'Last page' ),
808
  '&raquo;'
@@ -1134,9 +1134,7 @@ class ITSEC_WP_List_Table {
1134
  <div class="tablenav <?php echo esc_attr( $which ); ?>">
1135
 
1136
  <?php if ( $this->has_items() ): ?>
1137
- <div class="alignleft actions bulkactions">
1138
- <?php $this->bulk_actions( $which ); ?>
1139
- </div>
1140
  <?php endif;
1141
  $this->extra_tablenav( $which );
1142
  $this->pagination( $which );
757
  }
758
 
759
  if ( $disable_first ) {
760
+ $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&laquo;</span>';
761
  } else {
762
+ $page_links[] = sprintf( "<a class='first-page button' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
763
  esc_url( remove_query_arg( 'paged', $current_url ) ),
764
  __( 'First page' ),
765
  '&laquo;'
767
  }
768
 
769
  if ( $disable_prev ) {
770
+ $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&lsaquo;</span>';
771
  } else {
772
+ $page_links[] = sprintf( "<a class='prev-page button' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
773
  esc_url( add_query_arg( 'paged', max( 1, $current-1 ), $current_url ) ),
774
  __( 'Previous page' ),
775
  '&lsaquo;'
790
  $page_links[] = $total_pages_before . sprintf( _x( '%1$s of %2$s', 'paging' ), $html_current_page, $html_total_pages ) . $total_pages_after;
791
 
792
  if ( $disable_next ) {
793
+ $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&rsaquo;</span>';
794
  } else {
795
+ $page_links[] = sprintf( "<a class='next-page button' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
796
  esc_url( add_query_arg( 'paged', min( $total_pages, $current+1 ), $current_url ) ),
797
  __( 'Next page' ),
798
  '&rsaquo;'
800
  }
801
 
802
  if ( $disable_last ) {
803
+ $page_links[] = '<span class="tablenav-pages-navspan button disabled" aria-hidden="true">&raquo;</span>';
804
  } else {
805
+ $page_links[] = sprintf( "<a class='last-page button' href='%s'><span class='screen-reader-text'>%s</span><span aria-hidden='true'>%s</span></a>",
806
  esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ),
807
  __( 'Last page' ),
808
  '&raquo;'
1134
  <div class="tablenav <?php echo esc_attr( $which ); ?>">
1135
 
1136
  <?php if ( $this->has_items() ): ?>
1137
+ <div class="alignleft actions bulkactions"><?php $this->bulk_actions( $which ); ?></div>
 
 
1138
  <?php endif;
1139
  $this->extra_tablenav( $which );
1140
  $this->pagination( $which );
core/lib/form.php CHANGED
@@ -27,6 +27,9 @@ final class ITSEC_Form {
27
  parse_str( $data['data']['--itsec-form-serialized-data'], $data );
28
  }
29
 
 
 
 
30
 
31
  $defaults = array(
32
  'booleans' => false,
@@ -480,6 +483,24 @@ final class ITSEC_Form {
480
  $this->add_select( $var, $options );
481
  }
482
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
483
  private function add_custom_input( $var, $options ) {
484
  if ( empty( $options['type'] ) ) {
485
  trigger_error( 'add_custom_input called without a type option set' );
27
  parse_str( $data['data']['--itsec-form-serialized-data'], $data );
28
  }
29
 
30
+ if ( get_magic_quotes_gpc() ) {
31
+ $data = stripslashes_deep( $data );
32
+ }
33
 
34
  $defaults = array(
35
  'booleans' => false,
483
  $this->add_select( $var, $options );
484
  }
485
 
486
+ public function get_clean_var( $var ) {
487
+ $clean_var = trim( preg_replace( '/[^a-z0-9_]+/i', '-', $var ), '-' );
488
+
489
+ if ( ! empty( $this->input_group ) ) {
490
+ if ( false === strpos( $var, '[' ) ) {
491
+ $var = "[{$var}]";
492
+ } else {
493
+ $var = preg_replace( '/^([^\[]+)\[/', '[$1][', $var );
494
+ }
495
+
496
+ $var = "{$this->input_group}{$var}";
497
+
498
+ $clean_var = trim( preg_replace( '/[^a-z0-9_]+/i', '-', $var ), '-' );
499
+ }
500
+
501
+ return "itsec-$clean_var";
502
+ }
503
+
504
  private function add_custom_input( $var, $options ) {
505
  if ( empty( $options['type'] ) ) {
506
  trigger_error( 'add_custom_input called without a type option set' );
core/lib/validator.php CHANGED
@@ -142,6 +142,19 @@ abstract class ITSEC_Validator {
142
  if ( empty( $this->settings[$var] ) ) {
143
  $error = sprintf( __( 'The %1$s value cannot be empty.', 'better-wp-security' ), $name );
144
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  } else if ( 'array' === $type ) {
146
  if ( ! is_array( $this->settings[$var] ) ) {
147
  if ( empty( $this->settings[$var] ) ) {
@@ -414,6 +427,37 @@ abstract class ITSEC_Validator {
414
  $error = wp_sprintf( _n( 'The following extension in %1$s is invalid: %2$l', 'The following extensions in %1$s are invalid: %2$l', count( $invalid_extensions ), 'better-wp-security' ), $name, $invalid_extensions );
415
  }
416
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  } else {
418
  /* translators: 1: sanitize type, 2: input name */
419
  $error = sprintf( __( 'An invalid sanitize type of "%1$s" was received for the %2$s input.', 'better-wp-security' ), $type, $name );
142
  if ( empty( $this->settings[$var] ) ) {
143
  $error = sprintf( __( 'The %1$s value cannot be empty.', 'better-wp-security' ), $name );
144
  }
145
+ } elseif ( 'text' === $type || 'non-empty-text' === $type ) {
146
+ $string = (string) $this->settings[ $var ];
147
+ $string = wp_strip_all_tags( $string );
148
+
149
+ if ( $trim_value ) {
150
+ $string = trim( $string );
151
+ }
152
+
153
+ $this->settings[ $var ] = $string;
154
+
155
+ if ( 'non-empty-text' === $type && empty( $this->settings[ $var ] ) ) {
156
+ $error = sprintf( __( 'The %1$s value cannot be empty.', 'better-wp-security' ), $name );
157
+ }
158
  } else if ( 'array' === $type ) {
159
  if ( ! is_array( $this->settings[$var] ) ) {
160
  if ( empty( $this->settings[$var] ) ) {
427
  $error = wp_sprintf( _n( 'The following extension in %1$s is invalid: %2$l', 'The following extensions in %1$s are invalid: %2$l', count( $invalid_extensions ), 'better-wp-security' ), $name, $invalid_extensions );
428
  }
429
  }
430
+ } elseif ( is_string( $type ) && 0 === strpos( $type, 'cb-items:' ) ) {
431
+
432
+ list( , $method ) = explode( ':', $type );
433
+
434
+ if ( ! is_array( $this->settings[ $var ] ) ) {
435
+ $error = sprintf( __( 'The %1$s value must be an array.', 'better-wp-security' ), $name );
436
+ } else {
437
+ $invalid_entries = array();
438
+
439
+ foreach ( $this->settings[ $var ] as $index => $entry ) {
440
+
441
+ if ( empty( $entry ) ) {
442
+ unset( $this->settings[ $var ][ $index ] );
443
+ } else {
444
+ $result = $this->{$method}( $entry, $index );
445
+
446
+ if ( false === $result ) {
447
+ $invalid_entries[] = is_string( $entry ) ? $entry : $index;
448
+ } elseif ( is_wp_error( $result ) ) {
449
+ $invalid_entries[] = "'{$index}': {$result->get_error_message()}";
450
+ } else {
451
+ $this->settings[ $var ][ $index ] = $result;
452
+ }
453
+ }
454
+ }
455
+
456
+ if ( ! empty( $invalid_entries ) ) {
457
+ $error = wp_sprintf( _n( 'The following entry in %1$s is invalid: %2$l', 'The following entries in %1$s are invalid: %2$l', count( $invalid_entries ), 'better-wp-security' ), $name, $invalid_entries );
458
+ }
459
+ }
460
+
461
  } else {
462
  /* translators: 1: sanitize type, 2: input name */
463
  $error = sprintf( __( 'An invalid sanitize type of "%1$s" was received for the %2$s input.', 'better-wp-security' ), $type, $name );
core/lockout.php CHANGED
@@ -129,7 +129,7 @@ final class ITSEC_Lockout {
129
 
130
  $host = ITSEC_Lib::get_ip();
131
 
132
- if ( $this->is_host_locked_out( $host ) ) {
133
  $this->execute_lock();
134
  }
135
  }
129
 
130
  $host = ITSEC_Lib::get_ip();
131
 
132
+ if ( $this->is_host_locked_out( $host ) || ITSEC_Lib::is_ip_blacklisted() ) {
133
  $this->execute_lock();
134
  }
135
  }
core/modules/404-detection/class-itsec-four-oh-four.php CHANGED
@@ -10,7 +10,7 @@ class ITSEC_Four_Oh_Four {
10
 
11
  add_filter( 'itsec_lockout_modules', array( $this, 'register_lockout' ) );
12
 
13
- add_action( 'wp', array( $this, 'check_404' ), 9999 );
14
  }
15
 
16
  /**
@@ -29,19 +29,15 @@ class ITSEC_Four_Oh_Four {
29
 
30
  $uri = explode( '?', $_SERVER['REQUEST_URI'] );
31
 
32
- if ( in_array( '/' . ITSEC_Lib::get_request_path(), $this->settings['white_list'] ) ) {
33
- // white listed page.
34
- return;
35
  }
36
 
37
  ITSEC_Log::add_notice( 'four_oh_four', 'found_404', array( 'SERVER' => $_SERVER ) );
38
 
39
- if ( ! in_array( '.' . pathinfo( $uri[0], PATHINFO_EXTENSION ), $this->settings['types'] ) ) {
40
-
41
  $itsec_lockout->do_lockout( 'four_oh_four' );
42
-
43
  }
44
-
45
  }
46
 
47
  /**
10
 
11
  add_filter( 'itsec_lockout_modules', array( $this, 'register_lockout' ) );
12
 
13
+ add_action( 'template_redirect', array( $this, 'check_404' ), 9999 );
14
  }
15
 
16
  /**
29
 
30
  $uri = explode( '?', $_SERVER['REQUEST_URI'] );
31
 
32
+ if ( in_array( '/' . ITSEC_Lib::get_request_path(), $this->settings['white_list'], true ) ) {
33
+ return; // white listed page.
 
34
  }
35
 
36
  ITSEC_Log::add_notice( 'four_oh_four', 'found_404', array( 'SERVER' => $_SERVER ) );
37
 
38
+ if ( ! in_array( '.' . pathinfo( $uri[0], PATHINFO_EXTENSION ), $this->settings['types'], true ) ) {
 
39
  $itsec_lockout->do_lockout( 'four_oh_four' );
 
40
  }
 
41
  }
42
 
43
  /**
core/modules/404-detection/logs.php CHANGED
@@ -4,6 +4,7 @@ final class ITSEC_Four_Oh_Four_Logs {
4
  public function __construct() {
5
  add_filter( 'itsec_logs_prepare_four_oh_four_entry_for_list_display', array( $this, 'filter_entry_for_list_display' ) );
6
  add_filter( 'itsec_logs_prepare_four_oh_four_entry_for_details_display', array( $this, 'filter_entry_for_details_display' ), 10, 4 );
 
7
  }
8
 
9
  public function filter_entry_for_list_display( $entry ) {
@@ -24,5 +25,9 @@ final class ITSEC_Four_Oh_Four_Logs {
24
 
25
  return $details;
26
  }
 
 
 
 
27
  }
28
  new ITSEC_Four_Oh_Four_Logs();
4
  public function __construct() {
5
  add_filter( 'itsec_logs_prepare_four_oh_four_entry_for_list_display', array( $this, 'filter_entry_for_list_display' ) );
6
  add_filter( 'itsec_logs_prepare_four_oh_four_entry_for_details_display', array( $this, 'filter_entry_for_details_display' ), 10, 4 );
7
+ add_filter( 'itsec_logs_prepare_four_oh_four_filter_row_action_for_code', array( $this, 'code_row_action' ), 10, 2 );
8
  }
9
 
10
  public function filter_entry_for_list_display( $entry ) {
25
 
26
  return $details;
27
  }
28
+
29
+ public function code_row_action( $vars, $entry ) {
30
+ return array( 'filters[10]' => "url|{$entry['url']}", 'filters[11]' => 'module|four_oh_four' );
31
+ }
32
  }
33
  new ITSEC_Four_Oh_Four_Logs();
core/modules/global/settings-page.php CHANGED
@@ -73,6 +73,11 @@ final class ITSEC_Global_Settings_Page extends ITSEC_Module_Settings_Page {
73
  true => __( 'Yes' ),
74
  );
75
 
 
 
 
 
 
76
  ?>
77
  <table class="form-table itsec-settings-section">
78
  <tr>
@@ -242,6 +247,15 @@ final class ITSEC_Global_Settings_Page extends ITSEC_Module_Settings_Page {
242
  <p class="description"><?php _e( 'Each error message in iThemes Security has an associated error code that can help diagnose an issue. Changing this setting to "Yes" causes these codes to display. This setting should be left set to "No" unless iThemes Security support requests that you change it.', 'better-wp-security' ); ?></p>
243
  </td>
244
  </tr>
 
 
 
 
 
 
 
 
 
245
  </table>
246
  <?php
247
 
73
  true => __( 'Yes' ),
74
  );
75
 
76
+ $enable_grade_report_options = array(
77
+ false => __( 'No (default)' ),
78
+ true => __( 'Yes' ),
79
+ );
80
+
81
  ?>
82
  <table class="form-table itsec-settings-section">
83
  <tr>
247
  <p class="description"><?php _e( 'Each error message in iThemes Security has an associated error code that can help diagnose an issue. Changing this setting to "Yes" causes these codes to display. This setting should be left set to "No" unless iThemes Security support requests that you change it.', 'better-wp-security' ); ?></p>
248
  </td>
249
  </tr>
250
+ <?php if ( ITSEC_Core::is_pro() ) : ?>
251
+ <tr>
252
+ <th scope="row"><label for="itsec-global-enable_grade_report"><?php _e( 'Enable Grade Report', 'better-wp-security' ); ?></label></th>
253
+ <td>
254
+ <?php $form->add_select( 'enable_grade_report', $enable_grade_report_options ); ?>
255
+ <p class="description"><?php _e( 'The Grade Report feature can help you identify vulnerabilities on the site. Visit the Notification Center to select which users receive emails from this feature.', 'better-wp-security' ); ?></p>
256
+ </td>
257
+ </tr>
258
+ <?php endif; ?>
259
  </table>
260
  <?php
261
 
core/modules/global/settings.php CHANGED
@@ -35,6 +35,7 @@ final class ITSEC_Global_Settings_New extends ITSEC_Settings {
35
  'cron_status' => - 1,
36
  'use_cron' => true,
37
  'cron_test_time' => 0,
 
38
  );
39
  }
40
 
@@ -47,6 +48,17 @@ final class ITSEC_Global_Settings_New extends ITSEC_Settings {
47
  if ( $this->settings['use_cron'] !== $old_settings['use_cron'] ) {
48
  $this->handle_cron_change( $this->settings['use_cron'] );
49
  }
 
 
 
 
 
 
 
 
 
 
 
50
  }
51
 
52
  private function handle_cron_change( $new_use_cron ) {
35
  'cron_status' => - 1,
36
  'use_cron' => true,
37
  'cron_test_time' => 0,
38
+ 'enable_grade_report' => false,
39
  );
40
  }
41
 
48
  if ( $this->settings['use_cron'] !== $old_settings['use_cron'] ) {
49
  $this->handle_cron_change( $this->settings['use_cron'] );
50
  }
51
+
52
+ if ( $this->settings['enable_grade_report'] && ! $old_settings['enable_grade_report'] ) {
53
+ update_site_option( 'itsec-enable-grade-report', true );
54
+ ITSEC_Modules::load_module_file( 'activate.php', 'grade-report' );
55
+ ITSEC_Response::flag_new_notifications_available();
56
+ ITSEC_Response::refresh_page();
57
+ } else if ( ! $this->settings['enable_grade_report'] && $old_settings['enable_grade_report'] ) {
58
+ update_site_option( 'itsec-enable-grade-report', false );
59
+ ITSEC_Modules::load_module_file( 'deactivate.php', 'grade-report' );
60
+ ITSEC_Response::refresh_page();
61
+ }
62
  }
63
 
64
  private function handle_cron_change( $new_use_cron ) {
core/modules/global/validator.php CHANGED
@@ -21,7 +21,7 @@ class ITSEC_Global_Validator extends ITSEC_Validator {
21
 
22
  $this->vars_to_skip_validate_matching_fields = array( 'digest_last_sent', 'digest_messages', 'digest_email', 'email_notifications', 'notification_email', 'backup_email', 'show_new_dashboard_notice' );
23
  $this->set_previous_if_empty( array( 'did_upgrade', 'log_info', 'show_security_check', 'build', 'activation_timestamp', 'lock_file', 'cron_status', 'use_cron', 'cron_test_time' ) );
24
- $this->set_default_if_empty( array( 'log_location', 'nginx_file' ) );
25
  $this->preserve_setting_if_exists( array( 'digest_email', 'email_notifications', 'notification_email', 'backup_email' ) );
26
 
27
 
@@ -31,6 +31,7 @@ class ITSEC_Global_Validator extends ITSEC_Validator {
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' ) );
33
  $this->sanitize_setting( 'bool', 'show_error_codes', __( 'Show Error Codes', 'better-wp-security' ) );
 
34
 
35
  $this->sanitize_setting( 'string', 'lockout_message', __( 'Host Lockout Message', 'better-wp-security' ) );
36
  $this->sanitize_setting( 'string', 'user_lockout_message', __( 'User Lockout Message', 'better-wp-security' ) );
21
 
22
  $this->vars_to_skip_validate_matching_fields = array( 'digest_last_sent', 'digest_messages', 'digest_email', 'email_notifications', 'notification_email', 'backup_email', 'show_new_dashboard_notice' );
23
  $this->set_previous_if_empty( array( 'did_upgrade', 'log_info', 'show_security_check', 'build', 'activation_timestamp', 'lock_file', 'cron_status', 'use_cron', 'cron_test_time' ) );
24
+ $this->set_default_if_empty( array( 'log_location', 'nginx_file', 'enable_grade_report' ) );
25
  $this->preserve_setting_if_exists( array( 'digest_email', 'email_notifications', 'notification_email', 'backup_email' ) );
26
 
27
 
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' ) );
33
  $this->sanitize_setting( 'bool', 'show_error_codes', __( 'Show Error Codes', 'better-wp-security' ) );
34
+ $this->sanitize_setting( 'bool', 'enable_grade_report', __( 'Enable Grade Report', 'better-wp-security' ) );
35
 
36
  $this->sanitize_setting( 'string', 'lockout_message', __( 'Host Lockout Message', 'better-wp-security' ) );
37
  $this->sanitize_setting( 'string', 'user_lockout_message', __( 'User Lockout Message', 'better-wp-security' ) );
core/modules/notification-center/class-notification-center.php CHANGED
@@ -71,6 +71,14 @@ final class ITSEC_Notification_Center {
71
  public function get_notifications() {
72
 
73
  if ( null === $this->notifications ) {
 
 
 
 
 
 
 
 
74
  /**
75
  * Filter the registered notifications.
76
  *
@@ -163,9 +171,10 @@ final class ITSEC_Notification_Center {
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 ) {
@@ -317,27 +326,39 @@ final class ITSEC_Notification_Center {
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
 
@@ -361,6 +382,18 @@ final class ITSEC_Notification_Center {
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
  *
@@ -386,7 +419,7 @@ final class ITSEC_Notification_Center {
386
  $notification_data[] = $data;
387
 
388
  if ( $enforce_unique ) {
389
- $notification_data = array_unique( $notification_data, SORT_REGULAR );
390
  }
391
 
392
  $all_data[ $notification ] = $notification_data;
@@ -445,6 +478,11 @@ final class ITSEC_Notification_Center {
445
 
446
  add_action( 'wp_mail_failed', array( $this, 'capture_mail_fail' ) );
447
 
 
 
 
 
 
448
  $this->_sending_notification = $notification;
449
  $result = $mail->send();
450
  $this->_sending_notification = '';
@@ -460,9 +498,7 @@ final class ITSEC_Notification_Center {
460
  * @param string $error_id
461
  */
462
  public function dismiss_mail_error( $error_id ) {
463
- $errors = ITSEC_Modules::get_setting( 'notification-center', 'mail_errors', array() );
464
- unset( $errors[ $error_id ] );
465
- ITSEC_Modules::set_setting( 'notification-center', 'mail_errors', $errors );
466
  }
467
 
468
  /**
@@ -471,7 +507,10 @@ final class ITSEC_Notification_Center {
471
  * @return array
472
  */
473
  public function get_mail_errors() {
474
- return ITSEC_Modules::get_setting( 'notification-center', 'mail_errors', array() );
 
 
 
475
  }
476
 
477
  /**
@@ -490,15 +529,9 @@ final class ITSEC_Notification_Center {
490
  */
491
  public function capture_mail_fail( $error ) {
492
 
493
- $errors = ITSEC_Modules::get_setting( 'notification-center', 'mail_errors', array() );
494
 
495
- $errors[ uniqid() ] = array(
496
- 'error' => array( 'message' => $error->get_error_message(), 'code' => $error->get_error_code() ),
497
- 'time' => ITSEC_Core::get_current_time_gmt(),
498
- 'notification' => $this->_sending_notification,
499
- );
500
-
501
- ITSEC_Modules::set_setting( 'notification-center', 'mail_errors', $errors );
502
 
503
  if ( ITSEC_Core::is_interactive() ) {
504
  ITSEC_Response::reload_module( 'notification-center' );
@@ -660,6 +693,13 @@ final class ITSEC_Notification_Center {
660
  */
661
  public function send_scheduled_notifications( $notification_slugs, $silent = false ) {
662
 
 
 
 
 
 
 
 
663
  @set_time_limit( 120 );
664
  $sent = array();
665
 
@@ -881,6 +921,10 @@ final class ITSEC_Notification_Center {
881
  return false; // This is an on-demand
882
  }
883
 
 
 
 
 
884
  switch ( $schedule ) {
885
  case self::S_DAILY:
886
  $period = DAY_IN_SECONDS;
71
  public function get_notifications() {
72
 
73
  if ( null === $this->notifications ) {
74
+
75
+ if ( ! did_action( 'itsec_initialized' ) ) {
76
+ ITSEC_Log::add_debug( 'core', 'doing-it-wrong', array(
77
+ 'method' => __METHOD__,
78
+ 'backtrace' => wp_debug_backtrace_summary(),
79
+ ) );
80
+ }
81
+
82
  /**
83
  * Filter the registered notifications.
84
  *
171
 
172
  $schedules = self::get_schedule_order();
173
  $schedule = array(
174
+ 'min' => $schedules[0],
175
+ 'max' => $schedules[ count( $schedules ) - 1 ],
176
+ 'default' => self::S_DAILY,
177
+ 'setting_only' => false,
178
  );
179
 
180
  if ( $args['schedule'] === self::S_CONFIGURABLE ) {
326
  }
327
 
328
  $settings = $this->get_notification_settings( $notification );
329
+
330
+ if ( empty( $settings['recipient_type'] ) || 'custom' === $settings['recipient_type'] ) {
331
+ $contacts = $settings['user_list'];
332
+ } else {
333
+ $contacts = ITSEC_Modules::get_setting( 'notification-center', 'default_recipients' );
334
+ $contacts = isset( $contacts['user_list'] ) ? $contacts['user_list'] : array();
335
+ }
336
 
337
  $addresses = array();
338
 
339
+ $users = array();
340
+ $roles = array();
341
+
342
  foreach ( $contacts as $contact ) {
343
  if ( (string) $contact === (string) intval( $contact ) ) {
344
+ $users[] = get_userdata( $contact );
345
  } else {
346
+ list( , $role ) = explode( ':', $contact, 2 );
347
 
348
  if ( empty( $role ) ) {
349
  continue;
350
  }
351
 
352
+ $roles[] = $role;
353
  }
354
+ }
355
 
356
+ require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-canonical-roles.php' );
357
+ $users = array_merge( $users, ITSEC_Lib_Canonical_Roles::get_users_with_canonical_role( $roles ) );
358
+
359
+ foreach ( $users as $user ) {
360
+ if ( is_object( $user ) && ! empty( $user->user_email ) ) {
361
+ $addresses[] = $user->user_email;
362
  }
363
  }
364
 
382
  return isset( $last_sent[ $notification ] ) ? $last_sent[ $notification ] : 0;
383
  }
384
 
385
+ /**
386
+ * Update the last sent time for a notification.
387
+ *
388
+ * @param string $notification
389
+ */
390
+ public function update_last_sent( $notification ) {
391
+ $setting = ITSEC_Modules::get_setting( 'notification-center', 'last_sent', array() );
392
+ $setting[ $notification ] = ITSEC_Core::get_current_time_gmt();
393
+
394
+ ITSEC_Modules::set_setting( 'notification-center', 'last_sent', $setting );
395
+ }
396
+
397
  /**
398
  * Get the time that the notification should next be sent.
399
  *
419
  $notification_data[] = $data;
420
 
421
  if ( $enforce_unique ) {
422
+ $notification_data = ITSEC_Lib::non_scalar_array_unique( $notification_data );
423
  }
424
 
425
  $all_data[ $notification ] = $notification_data;
478
 
479
  add_action( 'wp_mail_failed', array( $this, 'capture_mail_fail' ) );
480
 
481
+ ITSEC_Log::add_debug( 'notification_center', "send::{$notification}", array(
482
+ 'recipients' => $mail->get_recipients(),
483
+ 'subject' => $mail->get_subject(),
484
+ ) );
485
+
486
  $this->_sending_notification = $notification;
487
  $result = $mail->send();
488
  $this->_sending_notification = '';
498
  * @param string $error_id
499
  */
500
  public function dismiss_mail_error( $error_id ) {
501
+ _deprecated_function( __METHOD__, '4.7.1' );
 
 
502
  }
503
 
504
  /**
507
  * @return array
508
  */
509
  public function get_mail_errors() {
510
+
511
+ _deprecated_function( __METHOD__, '4.7.1' );
512
+
513
+ return array();
514
  }
515
 
516
  /**
529
  */
530
  public function capture_mail_fail( $error ) {
531
 
532
+ ITSEC_Log::add_error( 'notification_center', "send_failed::{$this->_sending_notification}", compact( 'error' ) );
533
 
534
+ ITSEC_Modules::set_setting( 'notification-center', 'last_mail_error', $error->get_error_message() );
 
 
 
 
 
 
535
 
536
  if ( ITSEC_Core::is_interactive() ) {
537
  ITSEC_Response::reload_module( 'notification-center' );
693
  */
694
  public function send_scheduled_notifications( $notification_slugs, $silent = false ) {
695
 
696
+ if ( doing_action( self::CRON_ACTION ) || doing_action( 'init' ) ) {
697
+ ITSEC_Log::add_debug( 'notification_center', 'send_scheduled', array(
698
+ 'notifications' => $notification_slugs,
699
+ 'silent' => $silent,
700
+ ) );
701
+ }
702
+
703
  @set_time_limit( 120 );
704
  $sent = array();
705
 
921
  return false; // This is an on-demand
922
  }
923
 
924
+ if ( ( $config = $this->get_notification( $notification ) ) && is_array( $config['schedule'] ) && ! empty( $config['schedule']['setting_only'] ) ) {
925
+ return false;
926
+ }
927
+
928
  switch ( $schedule ) {
929
  case self::S_DAILY:
930
  $period = DAY_IN_SECONDS;
core/modules/notification-center/debug.php CHANGED
@@ -68,10 +68,10 @@ class ITSEC_Notification_Center_Debug {
68
  <tr>
69
  <td><?php echo esc_html( $slug ); ?></td>
70
  <td><?php echo $scheduled ? date( 'Y-m-d H:i:s', $nc->get_last_sent( $slug ) ) : '–'; ?></td>
71
- <td><?php echo $scheduled ? date( 'Y-m-d H:i:s', $nc->get_next_send_time( $slug ) ) : '–'; ?></td>
72
  <td><?php echo $nc->get_schedule( $slug ); ?></td>
73
  <td>
74
- <?php if ( $scheduled ): ?>
75
  <button class="button itsec__send-notification itsec__send-notification--force" data-id="<?php echo esc_attr( $slug ); ?>">
76
  <?php esc_html_e( 'Force', 'better-wp-security' ) ?>
77
  </button>
68
  <tr>
69
  <td><?php echo esc_html( $slug ); ?></td>
70
  <td><?php echo $scheduled ? date( 'Y-m-d H:i:s', $nc->get_last_sent( $slug ) ) : '–'; ?></td>
71
+ <td><?php echo $scheduled && ( $next = $nc->get_next_send_time( $slug ) ) ? date( 'Y-m-d H:i:s', $next ) : '–'; ?></td>
72
  <td><?php echo $nc->get_schedule( $slug ); ?></td>
73
  <td>
74
+ <?php if ( $scheduled && ( ! is_array( $notification['schedule'] ) || empty( $notification['schedule']['setting_only'] ) ) ): ?>
75
  <button class="button itsec__send-notification itsec__send-notification--force" data-id="<?php echo esc_attr( $slug ); ?>">
76
  <?php esc_html_e( 'Force', 'better-wp-security' ) ?>
77
  </button>
core/modules/notification-center/js/settings-page.js CHANGED
@@ -5,10 +5,8 @@ jQuery( function ( $ ) {
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
- itsecUtil.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
  } )
@@ -35,6 +33,10 @@ jQuery( function ( $ ) {
35
  }
36
  }
37
 
 
 
 
 
38
  itsecSettingsPage.events.on( 'modulesReloaded', initializeHiding );
39
  itsecSettingsPage.events.on( 'moduleReloaded', function ( _, module ) {
40
  if ( 'notification-center' === module ) {
5
  } );
6
 
7
  $( document ).on( 'itsec-dismiss-notice', '.itsec-notification-center-mail-errors-container .notice.itsec-is-dismissible', function () {
8
+ itsecUtil.sendModuleAJAXRequest( 'notification-center', { method: 'dismiss-mail-error' }, function ( r ) {
9
+ if ( r.success ) {
 
 
10
  jQuery( '#itsec-module-card-notification-center' ).removeClass( 'itsec-module-status--warning' );
11
  }
12
  } )
33
  }
34
  }
35
 
36
+ $( document ).on( 'change', '.itsec-notification-center-user-list-type', function ( e ) {
37
+ $( this ).next().toggleClass( 'hidden' );
38
+ } );
39
+
40
  itsecSettingsPage.events.on( 'modulesReloaded', initializeHiding );
41
  itsecSettingsPage.events.on( 'moduleReloaded', function ( _, module ) {
42
  if ( 'notification-center' === module ) {
core/modules/notification-center/logs.php ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Notification_Center_Logs {
4
+
5
+ public function __construct() {
6
+ add_filter( 'itsec_logs_prepare_notification_center_entry_for_list_display', array( $this, 'filter_entry_for_list_display' ), 10, 3 );
7
+ add_filter( 'itsec_logs_prepare_notification_center_entry_for_details_display', array( $this, 'filter_entry_for_details_display' ), 10, 4 );
8
+ }
9
+
10
+ public function filter_entry_for_list_display( $entry, $code, $data ) {
11
+
12
+ $entry['module_display'] = esc_html__( 'Notification Center', 'better-wp-security' );
13
+
14
+ switch ( $code ) {
15
+ case 'send':
16
+ list ( $notification ) = $data;
17
+
18
+ if ( $strings = ITSEC_Core::get_notification_center()->get_notification_strings( $notification ) ) {
19
+ $notification = $strings['label'];
20
+ }
21
+
22
+ $entry['description'] = sprintf( esc_html__( 'Sending %s', 'better-wp-security' ), $notification );
23
+ break;
24
+ case 'send_failed':
25
+ list ( $notification ) = $data;
26
+
27
+ if ( $strings = ITSEC_Core::get_notification_center()->get_notification_strings( $notification ) ) {
28
+ $notification = $strings['label'];
29
+ }
30
+
31
+ $entry['description'] = sprintf( esc_html__( 'Sending %s Failed', 'better-wp-security' ), $notification );
32
+ break;
33
+ case 'send_scheduled':
34
+ $entry['description'] = esc_html__( 'Sending scheduled notifications', 'better-wp-security' );
35
+ break;
36
+ }
37
+
38
+ return $entry;
39
+ }
40
+
41
+ public function filter_entry_for_details_display( $details, $entry, $code, $code_data ) {
42
+
43
+ $details['module']['content'] = esc_html__( 'Notification Center', 'better-wp-security' );
44
+
45
+ switch ( $code ) {
46
+ case 'send':
47
+ list ( $notification ) = $code_data;
48
+
49
+ if ( $strings = ITSEC_Core::get_notification_center()->get_notification_strings( $notification ) ) {
50
+ $notification = $strings['label'];
51
+ }
52
+
53
+ $details['description']['content'] = esc_html__( 'Sending Notification', 'better-wp-security' );
54
+ $details['notification'] = array(
55
+ 'header' => esc_html__( 'Notification', 'better-wp-security' ),
56
+ 'content' => $notification,
57
+ 'order' => 21,
58
+ );
59
+ break;
60
+ case 'send_failed':
61
+ list ( $notification ) = $code_data;
62
+
63
+ if ( $strings = ITSEC_Core::get_notification_center()->get_notification_strings( $notification ) ) {
64
+ $notification = $strings['label'];
65
+ }
66
+
67
+ $details['description']['content'] = esc_html__( 'Sending Notification Failed', 'better-wp-security' );
68
+ $details['notification'] = array(
69
+ 'header' => esc_html__( 'Notification', 'better-wp-security' ),
70
+ 'content' => $notification,
71
+ 'order' => 21,
72
+ );
73
+ $details['error_message'] = array(
74
+ 'header' => esc_html__( 'Error', 'better-wp-security' ),
75
+ 'content' => wp_sprintf( '%l', ITSEC_Response::get_error_strings( $entry['data']['error'] ) ),
76
+ 'order' => 22,
77
+ );
78
+ break;
79
+ case 'send_scheduled':
80
+ $details['description']['content'] = esc_html__( 'Sending Scheduled Notification', 'better-wp-security' );
81
+ $details['notifications'] = array(
82
+ 'header' => esc_html__( 'Notifications', 'better-wp-security' ),
83
+ 'content' => wp_sprintf( '%l', $entry['data']['notifications'] ),
84
+ 'order' => 21,
85
+ );
86
+ break;
87
+ break;
88
+ }
89
+
90
+ return $details;
91
+ }
92
+ }
93
+
94
+ new ITSEC_Notification_Center_Logs();
core/modules/notification-center/settings-page.php CHANGED
@@ -2,7 +2,7 @@
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;
@@ -19,7 +19,7 @@ class ITSEC_Notification_Center_Settings_Page extends ITSEC_Module_Settings_Page
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
 
@@ -39,15 +39,8 @@ class ITSEC_Notification_Center_Settings_Page extends ITSEC_Module_Settings_Page
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
  }
@@ -72,10 +65,31 @@ class ITSEC_Notification_Center_Settings_Page extends ITSEC_Module_Settings_Page
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();
@@ -89,30 +103,20 @@ class ITSEC_Notification_Center_Settings_Page extends ITSEC_Module_Settings_Page
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
- $details = $error['error'];
103
-
104
- if ( is_wp_error( $details ) ) {
105
- $message = $details->get_error_message();
106
- } elseif ( is_array( $error ) && isset( $details['message'] ) && is_string( $details['message'] ) ) {
107
- $message = $details['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
  }
@@ -245,6 +249,34 @@ class ITSEC_Notification_Center_Settings_Page extends ITSEC_Module_Settings_Page
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
 
@@ -252,59 +284,55 @@ class ITSEC_Notification_Center_Settings_Page extends ITSEC_Module_Settings_Page
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
 
2
 
3
  class ITSEC_Notification_Center_Settings_Page extends ITSEC_Module_Settings_Page {
4
 
5
+ private $version = 3;
6
 
7
  /** @var ITSEC_Notification_Center_Validator */
8
  private $validator;
19
 
20
  $this->validator = ITSEC_Modules::get_validator( 'notification-center' );
21
 
22
+ if ( ITSEC_Modules::get_setting( 'notification-center', 'last_mail_error' ) ) {
23
  $this->status = 'warning';
24
  }
25
 
39
 
40
  switch ( $data['method'] ) {
41
  case 'dismiss-mail-error':
42
+ ITSEC_Modules::set_setting( 'notification-center', 'last_mail_error', '' );
43
+ ITSEC_Response::set_success( true );
 
 
 
 
 
 
 
44
  break;
45
  }
46
  }
65
 
66
  <table class="form-table itsec-settings-section">
67
  <tbody>
68
+ <tr>
69
+ <th><label for="itsec-notification-center-from_email"><?php esc_html_e( 'From Email', 'better-wp-security' ); ?></label></th>
70
+ <td>
71
+ <?php $form->add_text( 'from_email' ); ?>
72
+ <p class="description">
73
+ <?php esc_html_e( 'iThemes Security will send notifications from this email address. Leave blank to use the WordPress default.', 'better-wp-security' ); ?>
74
+ </p>
75
+ </td>
76
+ </tr>
77
+ <tr class="itsec-email-contacts-setting">
78
+ <th><label for="itsec-notification-center-default_recipients"><?php esc_html_e( 'Default Recipients', 'better-wp-security' ); ?></label></th>
79
+ <td>
80
+ <?php
81
+ $form->add_input_group( 'default_recipients' );
82
+ $this->render_user_list_fieldset( $form, ITSEC_Notification_Center::R_USER_LIST );
83
+ $form->remove_input_group();
84
+ ?>
85
+ <p class="description">
86
+ <?php esc_html_e( 'Set the default recipients for any admin-facing notifications.', 'better-wp-security' ); ?>
87
+ </p>
88
+ </td>
89
+ </tr>
90
  </tbody>
91
  </table>
92
 
 
93
  <?php
94
 
95
  $notifications = ITSEC_Core::get_notification_center()->get_notifications();
103
  }
104
 
105
  protected function render_mail_errors() {
106
+ if ( ! $message = ITSEC_Modules::get_setting( 'notification-center', 'last_mail_error' ) ) {
 
 
107
  return;
108
  }
109
 
110
+ $link = esc_url( ITSEC_Core::get_logs_page_url( 'notification_center', 'error' ) );
111
  ?>
112
  <div class="itsec-notification-center-mail-errors-container">
113
+ <div class="notice notice-alt notice-error below-h2 itsec-is-dismissible itsec-notification-center-mail-error">
114
+ <?php if ( 'file' !== ITSEC_Modules::get_setting( 'global', 'log_type' ) ): ?>
115
+ <p><?php printf( esc_html__( 'Error while sending notification: %1$s. %2$sView All%3$s.', 'better-wp-security' ), $message, "<a href=\"{$link}\">", '</a>' ); ?></p>
116
+ <?php else: ?>
117
+ <p><?php printf( esc_html__( 'Error while sending notification: %1$s.', 'better-wp-security' ), $message ); ?></p>
118
+ <?php endif; ?>
119
+ </div>
 
 
 
 
 
 
 
 
 
120
  </div>
121
  <?php
122
  }
249
  * @param string $type
250
  */
251
  protected function render_user_list( $slug, $form, $type ) {
252
+ ?>
253
+
254
+ <tr class="itsec-email-contacts-setting">
255
+ <th><?php esc_html_e( 'Recipient', 'better-wp-security' ); ?></th>
256
+ <td>
257
+ <?php $form->add_select( 'recipient_type', array(
258
+ 'class' => 'itsec-notification-center-user-list-type',
259
+ 'value' => array(
260
+ 'default' => esc_html__( 'Default Recipients', 'better-wp-security' ),
261
+ 'custom' => esc_html__( 'Custom', 'better-wp-security' )
262
+ ),
263
+ )
264
+ ); ?>
265
+ <div class="<?php 'default' === $form->get_option( 'recipient_type' ) ? print( 'hidden' ) : null; ?>">
266
+ <?php $this->render_user_list_fieldset( $form, $type ); ?>
267
+ </div>
268
+ </td>
269
+ </tr>
270
+ <?php
271
+ }
272
+
273
+ /**
274
+ * Render the User List fieldset control.
275
+ *
276
+ * @param ITSEC_Form $form
277
+ * @param string $type
278
+ */
279
+ private function render_user_list_fieldset( $form, $type ) {
280
 
281
  $users_and_roles = $this->validator->get_available_admin_users_and_roles();
282
 
284
  $roles = $users_and_roles['roles'];
285
 
286
  natcasesort( $users );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
 
288
+ ?>
289
+ <fieldset>
290
+ <legend class="screen-reader-text"><?php esc_html_e( 'Recipients for this email.', 'better-wp-security' ); ?></legend>
291
+ <p><?php esc_html_e( 'Select which users should be emailed.', 'better-wp-security' ); ?></p>
292
+
293
+ <ul>
294
+ <?php foreach ( $roles as $role => $name ) : ?>
295
+ <li>
296
+ <label>
297
+ <?php $form->add_multi_checkbox( 'user_list', $role ); ?>
298
+ <?php echo esc_html( sprintf( _x( 'All %s users', 'role', 'better-wp-security' ), $name ) ); ?>
299
+ </label>
300
+ </li>
301
+ <?php endforeach; ?>
302
+ </ul>
303
+
304
+ <ul>
305
+ <?php foreach ( $users as $id => $name ) : ?>
306
+ <li>
307
+ <label>
308
+ <?php $form->add_multi_checkbox( 'user_list', $id ); ?>
309
+ <?php echo esc_html( $name ); ?>
310
+ </label>
311
+ </li>
312
+ <?php endforeach; ?>
313
+ </ul>
314
+
315
+ <?php if ( ITSEC_Notification_Center::R_USER_LIST_ADMIN_UPGRADE === $type && $form->get_option( 'previous_emails' ) ): ?>
316
+
317
+ <div class="itsec-notification-center--deprecated-recipients">
318
+ <span><?php esc_html_e( 'Deprecated Recipients', 'better-wp-security' ); ?></span>
319
+ <p class="description">
320
+ <?php esc_html_e( 'The following email recipients are deprecated. Please create new users for these email addresses or remove them.', 'better-wp-security' ); ?>
321
+ </p>
322
  <ul>
323
+ <?php foreach ( $form->get_option( 'previous_emails' ) as $email ): ?>
324
  <li>
325
  <label>
326
+ <?php $form->add_multi_checkbox( 'previous_emails', $email ); ?>
327
+ <?php echo esc_html( $email ); ?>
328
  </label>
329
  </li>
330
  <?php endforeach; ?>
331
  </ul>
332
+ </div>
333
+ <?php endif; ?>
334
+ </fieldset>
335
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
  <?php
337
  }
338
 
core/modules/notification-center/settings.php CHANGED
@@ -18,16 +18,24 @@ class ITSEC_Notification_Center_Settings extends ITSEC_Settings {
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
 
@@ -48,6 +56,8 @@ class ITSEC_Notification_Center_Settings extends ITSEC_Settings {
48
 
49
  $this->settings['notifications'][ $slug ] = $value;
50
  }
 
 
51
  }
52
 
53
  public function refresh_notification_settings( $save = true ) {
@@ -63,39 +73,78 @@ class ITSEC_Notification_Center_Settings extends ITSEC_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
  /**
@@ -129,12 +178,13 @@ class ITSEC_Notification_Center_Settings extends ITSEC_Settings {
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'] ) {
18
 
19
  public function get_defaults() {
20
  return array(
21
+ 'last_sent' => array(),
22
+ 'resend_at' => array(),
23
+ 'data' => array(),
24
+ 'last_mail_error' => '',
25
+ 'notifications' => array(),
26
+ 'admin_emails' => array(),
27
+ 'from_email' => '',
28
+ 'default_recipients' => array(
29
+ 'user_list' => array( 'role:administrator' )
30
+ ),
31
  );
32
  }
33
 
34
  public function load() {
35
+ if ( ! class_exists( 'ITSEC_Notification_Center' ) ) {
36
+ ITSEC_Modules::load_module_file( 'active.php', 'notification-center' );
37
+ }
38
+
39
  $this->settings = ITSEC_Storage::get( $this->get_id() );
40
  $defaults = $this->get_defaults();
41
 
56
 
57
  $this->settings['notifications'][ $slug ] = $value;
58
  }
59
+
60
+ unset( $this->settings['mail_errors'] );
61
  }
62
 
63
  public function refresh_notification_settings( $save = true ) {
73
  }
74
  }
75
 
76
+ public function continue_upgrade( $old_version ) {
77
+
78
  $nc = ITSEC_Core::get_notification_center();
79
 
80
+ if ( $old_version < 4076 ) {
81
+ $nc->clear_notifications_cache();
82
+ $this->refresh_notification_settings( false );
83
 
84
+ $admin_users = array();
85
+ $admin_emails = array();
86
 
87
+ foreach ( $this->settings['admin_emails'] as $admin_email ) {
88
+ $user = get_user_by( 'email', $admin_email );
89
 
90
+ if ( $user && $user->has_cap( 'manage_options' ) ) {
91
+ $admin_users[] = $user->ID;
92
+ } else {
93
+ $admin_emails[] = $admin_email;
94
+ }
95
  }
96
+
97
+ foreach ( $nc->get_notifications() as $slug => $notification ) {
98
+
99
+ if ( ITSEC_Notification_Center::R_USER_LIST_ADMIN_UPGRADE === $notification['recipient'] ) {
100
+ if ( $admin_users ) {
101
+ $this->settings['notifications'][ $slug ]['user_list'] = $admin_users;
102
+ } elseif ( $admin_emails ) {
103
+ $this->settings['notifications'][ $slug ]['user_list'] = array();
104
+ }
105
+
106
+ $this->settings['notifications'][ $slug ]['previous_emails'] = $admin_emails;
107
+ }
108
+ }
109
+
110
+ $this->set_all( $this->settings );
111
  }
112
 
113
+ if ( $old_version < 4101 ) {
114
 
115
+ $settings = $this->get_all();
116
+ $user_lists = array();
117
+
118
+ foreach ( $settings['notifications'] as $slug => $setting ) {
119
+ if ( ! $config = $nc->get_notification( $slug ) ) {
120
+ $settings['notifications'][ $slug ]['recipient_type'] = 'custom';
121
+
122
+ continue;
123
+ }
124
+
125
+ if ( ITSEC_Notification_Center::R_USER_LIST !== $config['recipient'] && ITSEC_Notification_Center::R_USER_LIST_ADMIN_UPGRADE !== $config['recipient'] ) {
126
+ continue;
127
  }
128
 
129
+ $user_lists[ $slug ] = $setting['user_list'];
130
  }
 
131
 
132
+ $unique_lists = ITSEC_Lib::non_scalar_array_unique( $user_lists );
133
+
134
+ if ( 1 === count( $unique_lists ) ) {
135
+ $settings['default_recipients'] = array( 'user_list' => reset( $unique_lists ) );
136
+
137
+ foreach ( $user_lists as $notification_slug => $_ ) {
138
+ $settings['notifications'][ $notification_slug ]['recipient_type'] = 'default';
139
+ }
140
+ } else {
141
+ foreach ( $user_lists as $notification_slug => $_ ) {
142
+ $settings['notifications'][ $notification_slug ]['recipient_type'] = 'custom';
143
+ }
144
+ }
145
+
146
+ $this->set_all( $settings );
147
+ }
148
  }
149
 
150
  /**
178
  }
179
 
180
  if ( ITSEC_Notification_Center::R_USER_LIST === $notification['recipient'] ) {
181
+ $defaults['user_list'] = array( 'role:administrator' );
182
+ $defaults['recipient_type'] = 'default';
183
  }
184
 
185
  if ( ITSEC_Notification_Center::R_USER_LIST_ADMIN_UPGRADE === $notification['recipient'] ) {
186
+ $defaults['user_list'] = array( 'role:administrator' );
187
+ $defaults['recipient_type'] = 'default';
188
  }
189
 
190
  if ( ITSEC_Notification_Center::R_EMAIL_LIST === $notification['recipient'] ) {
core/modules/notification-center/setup.php CHANGED
@@ -2,9 +2,11 @@
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
  /**
@@ -71,6 +73,7 @@ class ITSEC_Notification_Center_Setup {
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' );
@@ -113,10 +116,24 @@ class ITSEC_Notification_Center_Setup {
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
 
2
 
3
  class ITSEC_Notification_Center_Setup {
4
 
5
+ private $old_version;
6
+
7
  public function __construct() {
8
  add_action( 'itsec_modules_do_plugin_uninstall', array( $this, 'execute_uninstall' ) );
9
+ add_action( 'itsec_modules_do_plugin_upgrade', array( $this, 'execute_upgrade' ), -100 );
10
  }
11
 
12
  /**
73
 
74
  ITSEC_Modules::set_settings( 'notification-center', $settings );
75
 
76
+ $this->old_version = $itsec_old_version;
77
  add_action( 'itsec_initialized', array( $this, 'fire_continue_upgrade' ) );
78
  } elseif ( $itsec_old_version < 4077 ) { // Only run this if user is updating from 4076 -> 4077
79
  ITSEC_Modules::load_module_file( 'active.php', 'notification-center' );
116
  ITSEC_Modules::set_settings( 'notification-center', $settings );
117
  }
118
  }
119
+
120
+ if ( $itsec_old_version < 4099 ) {
121
+ ITSEC_Modules::load_module_file( 'active.php', 'notification-center' );
122
+
123
+ $settings = ITSEC_Modules::get_settings( 'notification-center' );
124
+ unset( $settings['mail_errors'] );
125
+ ITSEC_Modules::set_settings( 'notification-center', $settings );
126
+ }
127
+
128
+ if ( $itsec_old_version < 4101 ) {
129
+ $this->old_version = $itsec_old_version;
130
+ add_action( 'itsec_initialized', array( $this, 'fire_continue_upgrade' ) );
131
+ }
132
  }
133
 
134
  public function fire_continue_upgrade() {
135
+ ITSEC_Modules::load_module_file( 'settings.php', 'notification-center' );
136
+ do_action( 'itsec_notification_center_continue_upgrade', $this->old_version );
137
  }
138
  }
139
 
core/modules/notification-center/validator.php CHANGED
@@ -13,11 +13,51 @@ class ITSEC_Notification_Center_Validator extends ITSEC_Validator {
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' ) ) ) {
@@ -36,7 +76,7 @@ class ITSEC_Notification_Center_Validator extends ITSEC_Validator {
36
  $strings = ITSEC_Core::get_notification_center()->get_notification_strings( $notification );
37
 
38
  if ( ITSEC_Notification_Center::R_USER_LIST !== $config['recipient'] && ITSEC_Notification_Center::R_USER_LIST_ADMIN_UPGRADE !== $config['recipient'] ) {
39
- unset( $settings['user_list'] );
40
  } else {
41
  if ( ! is_array( $settings['user_list'] ) ) {
42
  $settings['user_list'] = array();
@@ -71,6 +111,19 @@ class ITSEC_Notification_Center_Validator extends ITSEC_Validator {
71
  $this->set_can_save( false );
72
  }
73
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  }
75
 
76
  if ( ITSEC_Notification_Center::R_EMAIL_LIST !== $config['recipient'] ) {
@@ -241,8 +294,10 @@ class ITSEC_Notification_Center_Validator extends ITSEC_Validator {
241
  $available_roles = array();
242
  $available_users = array();
243
 
 
 
244
  foreach ( $roles->roles as $role => $details ) {
245
- if ( ! empty( $details['capabilities']['manage_options'] ) ) {
246
  $available_roles["role:$role"] = translate_user_role( $details['name'] );
247
 
248
  $users = get_users( array( 'role' => $role ) );
13
  }
14
 
15
  protected function sanitize_settings() {
16
+ $this->vars_to_skip_validate_matching_fields = array( 'last_sent', 'data', 'resend_at', 'admin_emails', 'last_mail_error' );
17
  $this->set_previous_if_empty( array( 'last_sent', 'data', 'resend_at', 'admin_emails' ) );
18
 
19
+ if ( ! isset( $this->settings['last_mail_error'] ) ) {
20
+ $this->settings['last_mail_error'] = $this->previous_settings['last_mail_error'];
21
+ }
22
+
23
+ // We allow an empty email string.
24
+ if ( ! empty( $this->settings['from_email'] ) ) {
25
+ $this->sanitize_setting( 'email', 'from_email', __( 'Admin Email', 'better-wp-security' ) );
26
+ }
27
+
28
+ if ( $this->sanitize_setting( 'array', 'default_recipients', esc_html__( 'Default Recipients', 'better-wp-security' ) ) ) {
29
+ if ( empty( $this->settings['default_recipients']['user_list'] ) ) {
30
+ $this->add_error( new WP_Error(
31
+ 'itsec-validator-notification-center-invalid-type-default_recipients[user_list]-non-empty',
32
+ esc_html__( 'Selecting "Default Recipients" is required.', 'better-wp-security' )
33
+ ) );
34
+
35
+ if ( ITSEC_Core::is_interactive() ) {
36
+ $this->set_can_save( false );
37
+ }
38
+ } else {
39
+ $users_and_roles = $this->get_available_admin_users_and_roles();
40
+ $valid_contacts = $users_and_roles['users'] + $users_and_roles['roles'];
41
+
42
+ $contact_errors = array();
43
+
44
+ foreach ( $this->settings['default_recipients']['user_list'] as $contact ) {
45
+ if ( ! isset( $valid_contacts[ $contact ] ) ) {
46
+ $contact_errors[] = $contact;
47
+ }
48
+ }
49
+
50
+ if ( $contact_errors ) {
51
+ $this->add_error( new WP_Error(
52
+ 'itsec-validator-notification-center-invalid-type-default_recipients[user_list]-invalid-contacts',
53
+ wp_sprintf( esc_html__( 'Unknown Default Recipients contacts, %l.', 'better-wp-security' ), $contact_errors )
54
+ ) );
55
+
56
+ if ( ITSEC_Core::is_interactive() ) {
57
+ $this->set_can_save( false );
58
+ }
59
+ }
60
+ }
61
  }
62
 
63
  if ( ! $this->sanitize_setting( 'array', 'notifications', esc_html__( 'Notifications', 'better-wp-security' ) ) ) {
76
  $strings = ITSEC_Core::get_notification_center()->get_notification_strings( $notification );
77
 
78
  if ( ITSEC_Notification_Center::R_USER_LIST !== $config['recipient'] && ITSEC_Notification_Center::R_USER_LIST_ADMIN_UPGRADE !== $config['recipient'] ) {
79
+ unset( $settings['user_list'], $settings['recipient_type'] );
80
  } else {
81
  if ( ! is_array( $settings['user_list'] ) ) {
82
  $settings['user_list'] = array();
111
  $this->set_can_save( false );
112
  }
113
  }
114
+
115
+ if ( ! isset( $settings['recipient_type'] ) ) {
116
+ $settings['recipient_type'] = 'default';
117
+ } elseif ( ! in_array( $settings['recipient_type'], array( 'default', 'custom' ), true ) ) {
118
+ $this->add_error( new WP_Error(
119
+ 'itsec-validator-notification-center-invalid-type-notifications[recipient_type]-array',
120
+ wp_sprintf( esc_html__( 'Unknown recipient type for %s.', 'better-wp-security' ), $strings['label'] )
121
+ ) );
122
+
123
+ if ( ITSEC_Core::is_interactive() ) {
124
+ $this->set_can_save( false );
125
+ }
126
+ }
127
  }
128
 
129
  if ( ITSEC_Notification_Center::R_EMAIL_LIST !== $config['recipient'] ) {
294
  $available_roles = array();
295
  $available_users = array();
296
 
297
+ require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-canonical-roles.php' );
298
+
299
  foreach ( $roles->roles as $role => $details ) {
300
+ if ( 'administrator' === ITSEC_Lib_Canonical_Roles::get_canonical_role_from_role( $role ) ) {
301
  $available_roles["role:$role"] = translate_user_role( $details['name'] );
302
 
303
  $users = get_users( array( 'role' => $role ) );
core/modules/security-check/scanner.php CHANGED
@@ -48,7 +48,27 @@ final class ITSEC_Security_Check_Scanner {
48
  self::enforce_activation( 'ban-users', __( 'Banned Users', 'better-wp-security' ) );
49
  self::enforce_setting( 'ban-users', 'enable_ban_lists', true, __( 'Enabled the Enable Ban Lists setting in Banned Users.', 'better-wp-security' ) );
50
 
51
- self::enforce_activation( 'backup', __( 'Database Backups', 'better-wp-security' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  self::enforce_activation( 'brute-force', __( 'Local Brute Force Protection', 'better-wp-security' ) );
53
  self::enforce_activation( 'magic-links', __( 'Magic Links', 'better-wp-security' ) );
54
  self::enforce_activation( 'malware-scheduling', __( 'Malware Scan Scheduling', 'better-wp-security' ) );
@@ -59,6 +79,7 @@ final class ITSEC_Security_Check_Scanner {
59
  self::enforce_password_requirement_enabled( 'strength', __( 'Strong Password Enforcement', 'better-wp-security' ) );
60
  self::enforce_activation( 'two-factor', __( 'Two-Factor Authentication', 'better-wp-security' ) );
61
  self::enforce_setting( 'two-factor', 'available_methods', 'all', esc_html__( 'Changed the Authentication Methods Available to Users setting in Two-Factor Authentication to "All Methods".', 'better-wp-security' ) );
 
62
  self::enforce_setting( 'two-factor', 'protect_user_type', 'privileged_users', esc_html__( 'Changed the User Type Protection setting in Two-Factor Authentication to "Privileged Users".', 'better-wp-security' ) );
63
  self::enforce_setting( 'two-factor', 'protect_vulnerable_users', true, esc_html__( 'Enabled the Vulnerable User Protection setting in Two-Factor Authentication.', 'better-wp-security' ) );
64
  self::enforce_setting( 'two-factor', 'protect_vulnerable_site', true, esc_html__( 'Enabled the Vulnerable Site Protection setting in Two-Factor Authentication.', 'better-wp-security' ) );
48
  self::enforce_activation( 'ban-users', __( 'Banned Users', 'better-wp-security' ) );
49
  self::enforce_setting( 'ban-users', 'enable_ban_lists', true, __( 'Enabled the Enable Ban Lists setting in Banned Users.', 'better-wp-security' ) );
50
 
51
+ if ( $backup = ITSEC_Lib::get_backup_plugin() ) {
52
+ if ( ! function_exists( 'get_plugin_data' ) ) {
53
+ require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
54
+ }
55
+
56
+ $name = "'{$backup}'";
57
+
58
+ if ( function_exists( 'get_plugin_data' ) ) {
59
+ $data = get_plugin_data( WP_PLUGIN_DIR . '/' . $backup, false, true );
60
+
61
+ if ( isset( $data['Name'] ) ) {
62
+ $name = $data['Name'];
63
+ }
64
+ }
65
+
66
+ self::$feedback->add_section( 'backup-activation' );
67
+ self::$feedback->add_text( sprintf( __( 'A 3rd-party Backup Plugin, %s, is being used.', 'better-wp-security' ), $name ) );
68
+ } else {
69
+ self::enforce_activation( 'backup', __( 'Database Backups', 'better-wp-security' ) );
70
+ }
71
+
72
  self::enforce_activation( 'brute-force', __( 'Local Brute Force Protection', 'better-wp-security' ) );
73
  self::enforce_activation( 'magic-links', __( 'Magic Links', 'better-wp-security' ) );
74
  self::enforce_activation( 'malware-scheduling', __( 'Malware Scan Scheduling', 'better-wp-security' ) );
79
  self::enforce_password_requirement_enabled( 'strength', __( 'Strong Password Enforcement', 'better-wp-security' ) );
80
  self::enforce_activation( 'two-factor', __( 'Two-Factor Authentication', 'better-wp-security' ) );
81
  self::enforce_setting( 'two-factor', 'available_methods', 'all', esc_html__( 'Changed the Authentication Methods Available to Users setting in Two-Factor Authentication to "All Methods".', 'better-wp-security' ) );
82
+ self::enforce_setting( 'two-factor', 'exclude_type', 'disabled', esc_html__( 'Changed the Disabled Force Two-Factor for Certain Users to "None".', 'better-wp-security' ) );
83
  self::enforce_setting( 'two-factor', 'protect_user_type', 'privileged_users', esc_html__( 'Changed the User Type Protection setting in Two-Factor Authentication to "Privileged Users".', 'better-wp-security' ) );
84
  self::enforce_setting( 'two-factor', 'protect_vulnerable_users', true, esc_html__( 'Enabled the Vulnerable User Protection setting in Two-Factor Authentication.', 'better-wp-security' ) );
85
  self::enforce_setting( 'two-factor', 'protect_vulnerable_site', true, esc_html__( 'Enabled the Vulnerable Site Protection setting in Two-Factor Authentication.', 'better-wp-security' ) );
core/modules/ssl/class-itsec-ssl.php CHANGED
@@ -69,7 +69,7 @@ class ITSEC_SSL {
69
  add_filter( 'script_loader_src', array( $this, 'script_loader_src' ) );
70
  add_filter( 'style_loader_src', array( $this, 'style_loader_src' ) );
71
  add_filter( 'upload_dir', array( $this, 'upload_dir' ) );
72
- } else if ( 'enabled' === $settings['require_ssl'] && ( ! defined( 'WP_CLI' ) || ! WP_CLI ) && 'GET' === $_SERVER['REQUEST_METHOD'] ) {
73
  $this->redirect_to_https();
74
  }
75
  }
@@ -87,7 +87,7 @@ class ITSEC_SSL {
87
  */
88
  public function do_conditional_ssl_redirect() {
89
 
90
- if ( defined( 'WP_CLI' ) && WP_CLI ) {
91
  return;
92
  }
93
 
69
  add_filter( 'script_loader_src', array( $this, 'script_loader_src' ) );
70
  add_filter( 'style_loader_src', array( $this, 'style_loader_src' ) );
71
  add_filter( 'upload_dir', array( $this, 'upload_dir' ) );
72
+ } else if ( 'enabled' === $settings['require_ssl'] && 'cli' !== php_sapi_name() && 'GET' === $_SERVER['REQUEST_METHOD'] ) {
73
  $this->redirect_to_https();
74
  }
75
  }
87
  */
88
  public function do_conditional_ssl_redirect() {
89
 
90
+ if ( 'cli' === php_sapi_name() ) {
91
  return;
92
  }
93
 
core/modules/wordpress-tweaks/class-itsec-wordpress-tweaks.php CHANGED
@@ -128,6 +128,68 @@ final class ITSEC_WordPress_Tweaks {
128
  }
129
  }
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  /**
132
  * Add filter for gettext to change text for the valid_user_login_type setting changes.
133
  *
@@ -203,24 +265,44 @@ final class ITSEC_WordPress_Tweaks {
203
  return $result;
204
  }
205
 
 
 
 
 
 
 
 
 
 
 
206
  // Each of the following endpoints can be restricted based on a simple capability check.
207
  $endpoint_caps = array(
208
  'comments' => 'moderate_comments',
209
  'statuses' => 'edit_posts',
210
- 'taxonomies' => 'edit_terms',
211
  'types' => 'edit_posts',
212
  );
213
 
 
 
 
 
 
214
  foreach ( $endpoint_caps as $endpoint => $cap ) {
215
  if ( $endpoint === $route_parts[2] ) {
216
  if ( current_user_can( $cap ) ) {
217
  return $result;
218
  }
219
 
220
- return new WP_Error( 'itsec_rest_api_access_restricted', __( 'You do not have sufficient permission to access this endpoint. Access to REST API requests is restricted by iThemes Security settings.', 'better-wp-security' ) );
221
  }
222
  }
223
 
 
 
 
 
 
 
224
  if ( 'users' === $route_parts[2] ) {
225
  if ( isset( $route_parts[3] ) && 'me' === $route_parts[3] ) {
226
  // The users/me endpoint has its own permissions checks.
@@ -232,7 +314,7 @@ final class ITSEC_WordPress_Tweaks {
232
  return $result;
233
  }
234
 
235
- return new WP_Error( 'itsec_rest_api_access_restricted', __( 'You do not have sufficient permission to access this endpoint. Access to REST API requests is restricted by iThemes Security settings.', 'better-wp-security' ) );
236
  }
237
 
238
 
@@ -259,7 +341,7 @@ final class ITSEC_WordPress_Tweaks {
259
  if ( current_user_can( $taxonomy->cap->edit_terms ) ) {
260
  return $result;
261
  } else {
262
- return new WP_Error( 'itsec_rest_api_access_restricted', __( 'You do not have sufficient permission to access this endpoint. Access to REST API requests is restricted by iThemes Security settings.', 'better-wp-security' ) );
263
  }
264
  }
265
  }
@@ -281,7 +363,7 @@ final class ITSEC_WordPress_Tweaks {
281
  if ( current_user_can( $post_type->cap->edit_posts ) ) {
282
  return $result;
283
  } else {
284
- return new WP_Error( 'itsec_rest_api_access_restricted', __( 'You do not have sufficient permission to access this endpoint. Access to REST API requests is restricted by iThemes Security settings.', 'better-wp-security' ) );
285
  }
286
  }
287
  }
@@ -294,6 +376,74 @@ final class ITSEC_WordPress_Tweaks {
294
  return $result;
295
  }
296
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
 
298
  public function add_block_tabnapping_script() {
299
  wp_enqueue_script( 'blankshield', plugins_url( 'js/blankshield/blankshield.min.js', __FILE__ ), array(), ITSEC_Core::get_plugin_build(), true );
128
  }
129
  }
130
 
131
+ public function deinit() {
132
+ $this->remove_config_hooks();
133
+
134
+ // Functional code for the valid_user_login_type setting.
135
+ if ( 'email' === $this->settings['valid_user_login_type'] ) {
136
+ remove_action( 'login_init', array( $this, 'add_gettext_filter' ) );
137
+ remove_filter( 'authenticate', array( $this, 'add_gettext_filter' ), 0 );
138
+ add_filter( 'authenticate', 'wp_authenticate_username_password', 20 );
139
+ } else if ( 'username' === $this->settings['valid_user_login_type'] ) {
140
+ remove_action( 'login_init', array( $this, 'add_gettext_filter' ) );
141
+ remove_filter( 'authenticate', array( $this, 'add_gettext_filter' ), 0 );
142
+ add_filter( 'authenticate', 'wp_authenticate_email_password', 20 );
143
+ }
144
+
145
+ // Functional code for the allow_xmlrpc_multiauth setting.
146
+ if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST && ! $this->settings['allow_xmlrpc_multiauth'] ) {
147
+ remove_filter( 'authenticate', array( $this, 'block_multiauth_attempts' ), 0 );
148
+ }
149
+
150
+ //remove wlmanifest link if turned on
151
+ if ( $this->settings['wlwmanifest_header'] ) {
152
+ add_action( 'wp_head', 'wlwmanifest_link' );
153
+ }
154
+
155
+ //remove rsd link from header if turned on
156
+ if ( $this->settings['edituri_header'] ) {
157
+ add_action( 'wp_head', 'rsd_link' );
158
+ }
159
+
160
+ //Disable XML-RPC
161
+ if ( 2 == $this->settings['disable_xmlrpc'] ) {
162
+ remove_filter( 'xmlrpc_enabled', '__return_null' );
163
+ remove_filter( 'bloginfo_url', array( $this, 'remove_pingback_url' ), 10 );
164
+ } else if ( 1 == $this->settings['disable_xmlrpc'] ) { // Disable pingbacks
165
+ remove_filter( 'xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
166
+ }
167
+
168
+ remove_filter( 'rest_dispatch_request', array( $this, 'filter_rest_dispatch_request' ), 10 );
169
+
170
+ //Process remove login errors
171
+ if ( $this->settings['login_errors'] ) {
172
+ remove_filter( 'login_errors', '__return_null' );
173
+ }
174
+
175
+ //Process require unique nicename
176
+ if ( $this->settings['force_unique_nicename'] ) {
177
+ remove_action( 'user_profile_update_errors', array( $this, 'force_unique_nicename' ), 10 );
178
+ }
179
+
180
+ //Process remove extra author archives
181
+ if ( $this->settings['disable_unused_author_pages'] ) {
182
+ remove_action( 'template_redirect', array( $this, 'disable_unused_author_pages' ) );
183
+ }
184
+
185
+ if ( $this->settings['block_tabnapping'] ) {
186
+ remove_action( 'wp_enqueue_scripts', array( $this, 'add_block_tabnapping_script' ) );
187
+ remove_action( 'admin_enqueue_scripts', array( $this, 'add_block_tabnapping_script' ) );
188
+ }
189
+
190
+ remove_filter( 'rest_request_after_callbacks', array( $this, 'filter_taxonomies_response' ), 10, 3 );
191
+ }
192
+
193
  /**
194
  * Add filter for gettext to change text for the valid_user_login_type setting changes.
195
  *
265
  return $result;
266
  }
267
 
268
+ if ( function_exists( 'rest_authorization_required_code' ) ) {
269
+ $code = rest_authorization_required_code();
270
+ } else {
271
+ $code = is_user_logged_in() ? 403 : 401;
272
+ }
273
+
274
+ $error = new WP_Error( 'itsec_rest_api_access_restricted', __( 'You do not have sufficient permission to access this endpoint. Access to REST API requests is restricted by iThemes Security settings.', 'better-wp-security' ), array(
275
+ 'status' => $code,
276
+ ) );
277
+
278
  // Each of the following endpoints can be restricted based on a simple capability check.
279
  $endpoint_caps = array(
280
  'comments' => 'moderate_comments',
281
  'statuses' => 'edit_posts',
 
282
  'types' => 'edit_posts',
283
  );
284
 
285
+ if ( version_compare( $GLOBALS['wp_version'], '4.7.0', '<' ) ) {
286
+ // We need the request_after_callbacks filter to perform this blocking. So fallback to a more general edit_posts capability when this hook isn't available.
287
+ $endpoint_caps['taxonomies'] = 'edit_posts';
288
+ }
289
+
290
  foreach ( $endpoint_caps as $endpoint => $cap ) {
291
  if ( $endpoint === $route_parts[2] ) {
292
  if ( current_user_can( $cap ) ) {
293
  return $result;
294
  }
295
 
296
+ return $error;
297
  }
298
  }
299
 
300
+ if ( 'taxonomies' === $route_parts[2] ) {
301
+ add_filter( 'rest_request_after_callbacks', array( $this, 'filter_taxonomies_response' ), 10, 3 );
302
+
303
+ return $result;
304
+ }
305
+
306
  if ( 'users' === $route_parts[2] ) {
307
  if ( isset( $route_parts[3] ) && 'me' === $route_parts[3] ) {
308
  // The users/me endpoint has its own permissions checks.
314
  return $result;
315
  }
316
 
317
+ return $error;
318
  }
319
 
320
 
341
  if ( current_user_can( $taxonomy->cap->edit_terms ) ) {
342
  return $result;
343
  } else {
344
+ return $error;
345
  }
346
  }
347
  }
363
  if ( current_user_can( $post_type->cap->edit_posts ) ) {
364
  return $result;
365
  } else {
366
+ return $error;
367
  }
368
  }
369
  }
376
  return $result;
377
  }
378
 
379
+ /**
380
+ * Filter the taxonomies response to exclude taxonomies the user does not have edit permission for.
381
+ *
382
+ * @param WP_REST_Response|WP_Error $response
383
+ * @param array $handler
384
+ * @param WP_REST_Request $request
385
+ *
386
+ * @return WP_REST_Response|WP_Error
387
+ */
388
+ public function filter_taxonomies_response( $response, $handler, $request ) {
389
+
390
+ if ( is_wp_error( $response ) ) {
391
+ return $response;
392
+ }
393
+
394
+ $route = strtolower( $request->get_route() );
395
+ $route_parts = explode( '/', trim( $route, '/' ) );
396
+
397
+ if ( 'wp' !== $route_parts[0] || ! isset( $route_parts[2] ) || 'taxonomies' !== $route_parts[2] ) {
398
+ return $response;
399
+ }
400
+
401
+ if ( function_exists( 'rest_authorization_required_code' ) ) {
402
+ $code = rest_authorization_required_code();
403
+ } else {
404
+ $code = is_user_logged_in() ? 403 : 401;
405
+ }
406
+
407
+ $error = new WP_Error( 'itsec_rest_api_access_restricted', __( 'You do not have sufficient permission to access this endpoint. Access to REST API requests is restricted by iThemes Security settings.', 'better-wp-security' ), array(
408
+ 'status' => $code,
409
+ ) );
410
+
411
+ $data = $response->get_data();
412
+
413
+ if ( isset( $route_parts[3] ) ) {
414
+ if ( ! $taxonomy = get_taxonomy( $route_parts[3] ) ) {
415
+ return $response;
416
+ }
417
+
418
+ if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) {
419
+ return $error;
420
+ }
421
+
422
+ return $response;
423
+ }
424
+
425
+ foreach ( $data as $i => $taxonomy_data ) {
426
+ if ( ! isset( $taxonomy_data['slug'] ) ) {
427
+ continue;
428
+ }
429
+
430
+ if ( ! $taxonomy = get_taxonomy( $taxonomy_data['slug'] ) ) {
431
+ continue;
432
+ }
433
+
434
+ if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) {
435
+ unset( $data[ $i ] );
436
+ }
437
+ }
438
+
439
+ if ( ! $data ) {
440
+ return $error;
441
+ }
442
+
443
+ $response->set_data( $data );
444
+
445
+ return $response;
446
+ }
447
 
448
  public function add_block_tabnapping_script() {
449
  wp_enqueue_script( 'blankshield', plugins_url( 'js/blankshield/blankshield.min.js', __FILE__ ), array(), ITSEC_Core::get_plugin_build(), true );
core/response.php CHANGED
@@ -72,7 +72,7 @@ final class ITSEC_Response {
72
 
73
  return $self->errors;
74
  }
75
-
76
  public static function add_warnings( $warnings ) {
77
  foreach ( $warnings as $warning ) {
78
  self::add_warning( $warning );
@@ -199,6 +199,10 @@ final class ITSEC_Response {
199
  self::get_instance()->add_js_function_call( 'reloadAllModules' );
200
  }
201
 
 
 
 
 
202
  public static function regenerate_wp_config() {
203
  $self = self::get_instance();
204
 
@@ -279,16 +283,26 @@ final class ITSEC_Response {
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
 
72
 
73
  return $self->errors;
74
  }
75
+
76
  public static function add_warnings( $warnings ) {
77
  foreach ( $warnings as $warning ) {
78
  self::add_warning( $warning );
199
  self::get_instance()->add_js_function_call( 'reloadAllModules' );
200
  }
201
 
202
+ public static function refresh_page() {
203
+ self::get_instance()->add_js_function_call( 'refreshPage' );
204
+ }
205
+
206
  public static function regenerate_wp_config() {
207
  $self = self::get_instance();
208
 
283
  $added = array_diff( $new, $current );
284
 
285
  if ( $added ) {
286
+ self::flag_new_notifications_available();
 
 
 
 
 
 
287
  }
288
  }
289
 
290
+ public static function flag_new_notifications_available() {
291
+ static $run_count = 0;
292
+
293
+ if ( $run_count++ > 0 ) {
294
+ return;
295
+ }
296
+
297
+ self::reload_module( 'notification-center' );
298
+ self::get_instance()->has_new_notifications = true;
299
+ self::get_instance()->add_info( sprintf(
300
+ esc_html__( 'New notifications available in the %1$sNotification Center%2$s.', 'better-wp-security' ),
301
+ '<a href="#" data-module-link="notification-center">',
302
+ '</a>'
303
+ ) );
304
+ }
305
+
306
  public static function get_raw_data() {
307
  $self = self::get_instance();
308
 
core/setup.php CHANGED
@@ -9,7 +9,6 @@
9
  final class ITSEC_Setup {
10
  public static function handle_activation() {
11
  self::setup_plugin_data();
12
- ITSEC_Core::get_scheduler()->register_events();
13
  }
14
 
15
  public static function handle_deactivation() {
@@ -143,13 +142,11 @@ final class ITSEC_Setup {
143
  ITSEC_Lib::schedule_cron_test();
144
  }
145
 
146
- if ( $build < 4087 ) {
147
- ITSEC_Core::get_scheduler()->register_events();
148
  }
149
 
150
- if ( $build < 4094 ) {
151
- ITSEC_Core::get_scheduler()->register_events();
152
- }
153
 
154
  // Update stored build number.
155
  ITSEC_Modules::set_setting( 'global', 'build', ITSEC_Core::get_plugin_build() );
@@ -202,6 +199,7 @@ final class ITSEC_Setup {
202
 
203
  delete_site_option( 'itsec-storage' );
204
  delete_site_option( 'itsec_active_modules' );
 
205
 
206
  ITSEC_Schema::remove_database_tables();
207
  ITSEC_Lib_Directory::remove( ITSEC_Core::get_storage_dir() );
9
  final class ITSEC_Setup {
10
  public static function handle_activation() {
11
  self::setup_plugin_data();
 
12
  }
13
 
14
  public static function handle_deactivation() {
142
  ITSEC_Lib::schedule_cron_test();
143
  }
144
 
145
+ if ( null === get_site_option( 'itsec-enable-grade-report', null ) ) {
146
+ update_site_option( 'itsec-enable-grade-report', ITSEC_Modules::get_setting( 'global', 'enable_grade_report' ) );
147
  }
148
 
149
+ ITSEC_Core::get_scheduler()->register_events();
 
 
150
 
151
  // Update stored build number.
152
  ITSEC_Modules::set_setting( 'global', 'build', ITSEC_Core::get_plugin_build() );
199
 
200
  delete_site_option( 'itsec-storage' );
201
  delete_site_option( 'itsec_active_modules' );
202
+ delete_site_option( 'itsec-enable-grade-report' );
203
 
204
  ITSEC_Schema::remove_database_tables();
205
  ITSEC_Lib_Directory::remove( ITSEC_Core::get_storage_dir() );
history.txt CHANGED
@@ -788,4 +788,18 @@
788
  7.0.4 - 2018-06-27 - Chris Jean & Timothy Jacobs
789
  Enhancement: Add mitigation for the WordPress Attachment File Traversal and Deletion vulnerability.
790
  Tweak: Fire a WordPress action whenever settings are updated.
791
- Bug Fix: Improved input sanitization on the logs page to prevent triggering warnings.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
788
  7.0.4 - 2018-06-27 - Chris Jean & Timothy Jacobs
789
  Enhancement: Add mitigation for the WordPress Attachment File Traversal and Deletion vulnerability.
790
  Tweak: Fire a WordPress action whenever settings are updated.
791
+ Bug Fix: Improved input sanitization on the logs page to prevent triggering warnings.
792
+ 7.1.0 - 2018-08-15 - Chris Jean & Timothy Jacobs
793
+ New Feature: Allow for globally setting recipients for admin-targeted notifications. All new notifications will default to the recipients in this list. Notifications can be set to use the default list or switch to a custom list.
794
+ Enhancement: Added a setting to enable/disable the Grade Report feature of Pro.
795
+ Tweak: Check if an IP is blacklisted on page load for compatibility with servers that cannot process server configuration level bans immediately.
796
+ Tweak: Display a time diff until the next event on the Debug page.
797
+ Tweak: Use Logging API for tracking Notification Center errors.
798
+ Tweak: Register Scheduler Events whenever the plugin build changes.
799
+ Tweak: Allow for filtering logs by any module recorded.
800
+ Tweak: Account for 3rd-party Backup Plugin in Security Check.
801
+ Bug Fix: 404 detection for plugins that mark is_404 later in the hook sequence.
802
+ Bug Fix: REST API Protection blocked the Taxonomies route for all users.
803
+ Bug Fix: Account for any CLI PHP SAPI instead of just WP-CLI in the SSL Module.
804
+ Bug Fix: Fixed how the Grade Report enable/disable status is stored to fix admin page loading issues on some sites.
805
+ Bug Fix: Fix serialization of closure error when a plugin registering a hook with a closure is in the boot-up stack and the notification center is triggered too early in the cycle.
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.7
5
- Tested up to: 4.9.6
6
- Stable tag: 7.0.4
7
  Requires PHP: 5.2
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
@@ -189,6 +189,21 @@ Free support may be available with the help of the community in the <a href="htt
189
 
190
  == Changelog ==
191
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  = 7.0.4 =
193
  * Enhancement: Add mitigation for the WordPress Attachment File Traversal and Deletion vulnerability.
194
  * Tweak: Fire a WordPress action whenever settings are updated.
@@ -481,5 +496,5 @@ Free support may be available with the help of the community in the <a href="htt
481
 
482
  == Upgrade Notice ==
483
 
484
- = 7.0.4 =
485
- Version 7.0.4 contains an important security bug fix. 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.7
5
+ Tested up to: 4.9.8
6
+ Stable tag: 7.1.0
7
  Requires PHP: 5.2
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
189
 
190
  == Changelog ==
191
 
192
+ = 7.1.0 =
193
+ * New Feature: Allow for globally setting recipients for admin-targeted notifications. All new notifications will default to the recipients in this list. Notifications can be set to use the default list or switch to a custom list.
194
+ * Enhancement: Added a setting to enable/disable the Grade Report feature of Pro.
195
+ * Tweak: Check if an IP is blacklisted on page load for compatibility with servers that cannot process server configuration level bans immediately.
196
+ * Tweak: Display a time diff until the next event on the Debug page.
197
+ * Tweak: Use Logging API for tracking Notification Center errors.
198
+ * Tweak: Register Scheduler Events whenever the plugin build changes.
199
+ * Tweak: Allow for filtering logs by any module recorded.
200
+ * Tweak: Account for 3rd-party Backup Plugin in Security Check.
201
+ * Bug Fix: 404 detection for plugins that mark is_404 later in the hook sequence.
202
+ * Bug Fix: REST API Protection blocked the Taxonomies route for all users.
203
+ * Bug Fix: Account for any CLI PHP SAPI instead of just WP-CLI in the SSL Module.
204
+ * Bug Fix: Fixed how the Grade Report enable/disable status is stored to fix admin page loading issues on some sites.
205
+ * Bug Fix: Fix serialization of closure error when a plugin registering a hook with a closure is in the boot-up stack and the notification center is triggered too early in the cycle.
206
+
207
  = 7.0.4 =
208
  * Enhancement: Add mitigation for the WordPress Attachment File Traversal and Deletion vulnerability.
209
  * Tweak: Fire a WordPress action whenever settings are updated.
496
 
497
  == Upgrade Notice ==
498
 
499
+ = 7.1.0 =
500
+ Version 7.1.0 contains important bug fixes and improvements. It is recommended for all users.