iThemes Security (formerly Better WP Security) - Version 6.8.0

Version Description

  • New Feature: Introduces a scheduling framework for handling events. Cron is now used by default, and will switch to using an alternate scheduling system if it detects an error. To disable this detection set ITSEC_DISABLE_CRON_TEST in your wp-config.php file.
  • Important: The ITSEC_FILE_CHECK_CRON and ITSEC_BACKUP_CRON constants have been deprecated. Use ITSEC_USE_CRON instead.
  • Enhancement: Preserve notification settings when the responsible module is deactivated.
  • Bug Fix: Process 404 lockouts on the 'wp' hook to prevent a headers have already been sent warning message.
  • Bug Fix: Ensure Hide Backend emails are properly sent when activating Hide Backend before saving the Notification Center for the first time.
  • Bug Fix: Prevent warning from being issued on new installs by allowing previous settings to be preserved if they exist.
  • Bug Fix: Better handle WP_Error objects in mail errors that occurred before updating to first patch release.
  • Bug Fix: A non static method was being called statically.
  • Bug Fix: Fix occasional duplicate backups and file scans.
  • Bug Fix: Fixed issue where scheduled events could repeat on sites that do not properly support WordPress's cron system.
  • Bug Fix: Reactivating Away Mode now replaces the active file if you had previously removed it.
  • Bug Fix: Ensure lockouts take effect immediately, even on systems where changes to server configuration files do not take effect immediately.
Download this release

Release Info

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

Code changes from version 6.7.0 to 6.8.0

better-wp-security.php CHANGED
@@ -6,7 +6,7 @@
6
  * Description: Take the guesswork out of WordPress security. iThemes Security offers 30+ ways to lock down WordPress in an easy-to-use WordPress security plugin.
7
  * Author: iThemes
8
  * Author URI: https://ithemes.com
9
- * Version: 6.7.0
10
  * Text Domain: better-wp-security
11
  * Network: True
12
  * License: GPLv2
6
  * Description: Take the guesswork out of WordPress security. iThemes Security offers 30+ ways to lock down WordPress in an easy-to-use WordPress security plugin.
7
  * Author: iThemes
8
  * Author URI: https://ithemes.com
9
+ * Version: 6.8.0
10
  * Text Domain: better-wp-security
11
  * Network: True
12
  * License: GPLv2
core/core.php CHANGED
@@ -25,7 +25,7 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
25
  *
26
  * @access private
27
  */
28
- private $plugin_build = 4078;
29
 
30
  /**
31
  * Used to distinguish between a user modifying settings and the API modifying settings (such as from Sync
@@ -42,6 +42,7 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
42
  $itsec_files,
43
  $itsec_notify,
44
  $notifications,
 
45
  $sync_api,
46
  $plugin_file,
47
  $plugin_dir,
@@ -119,10 +120,16 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
119
  require( $this->plugin_dir . 'core/lib/class-itsec-lib-user-activity.php' );
120
  require( $this->plugin_dir . 'core/lib/class-itsec-lib-password-requirements.php' );
121
 
 
 
 
 
 
122
  $this->itsec_files = ITSEC_Files::get_instance();
123
  $this->itsec_notify = new ITSEC_Notify();
124
  $itsec_logger = new ITSEC_Logger();
125
- $itsec_lockout = new ITSEC_Lockout( $this );
 
126
 
127
  // Handle upgrade if needed.
128
  if ( ITSEC_Modules::get_setting( 'global', 'build' ) < $this->plugin_build ) {
@@ -143,11 +150,8 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
143
 
144
  add_action( 'ithemes_sync_register_verbs', array( $this, 'register_sync_verbs' ) );
145
 
146
- if ( ! wp_next_scheduled( 'itsec_clear_locks' ) ) {
147
- wp_schedule_event( time(), 'daily', 'itsec_clear_locks' );
148
- }
149
-
150
- add_action( 'itsec_clear_locks', array( 'ITSEC_Lib', 'delete_expired_locks' ) );
151
  }
152
 
153
  /**
@@ -168,9 +172,32 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
168
  $pass_requirements->run();
169
  }
170
 
 
 
 
 
171
  do_action( 'itsec_initialized' );
172
  }
173
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  /**
175
  * Retrieve the global instance of the files utility.
176
  *
@@ -209,6 +236,24 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
209
  return self::get_instance()->notifications;
210
  }
211
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  /**
213
  * Retrieve the global instance of the Sync API.
214
  *
@@ -233,6 +278,15 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
233
  $sync_api->register( 'itsec-get-everything', 'Ithemes_Sync_Verb_ITSEC_Get_Everything', dirname( __FILE__ ) . '/sync-verbs/itsec-get-everything.php' );
234
  }
235
 
 
 
 
 
 
 
 
 
 
236
  /**
237
  * Register core modules.
238
  */
25
  *
26
  * @access private
27
  */
28
+ private $plugin_build = 4080;
29
 
30
  /**
31
  * Used to distinguish between a user modifying settings and the API modifying settings (such as from Sync
42
  $itsec_files,
43
  $itsec_notify,
44
  $notifications,
45
+ $scheduler,
46
  $sync_api,
47
  $plugin_file,
48
  $plugin_dir,
120
  require( $this->plugin_dir . 'core/lib/class-itsec-lib-user-activity.php' );
121
  require( $this->plugin_dir . 'core/lib/class-itsec-lib-password-requirements.php' );
122
 
123
+ require( $this->plugin_dir . 'core/lib/class-itsec-scheduler.php' );
124
+ require( $this->plugin_dir . 'core/lib/class-itsec-job.php' );
125
+
126
+ $this->setup_scheduler();
127
+
128
  $this->itsec_files = ITSEC_Files::get_instance();
129
  $this->itsec_notify = new ITSEC_Notify();
130
  $itsec_logger = new ITSEC_Logger();
131
+ $itsec_lockout = new ITSEC_Lockout();
132
+ $itsec_lockout->run();
133
 
134
  // Handle upgrade if needed.
135
  if ( ITSEC_Modules::get_setting( 'global', 'build' ) < $this->plugin_build ) {
150
 
151
  add_action( 'ithemes_sync_register_verbs', array( $this, 'register_sync_verbs' ) );
152
 
153
+ add_action( 'itsec_scheduler_register_events', array( $this, 'register_events' ) );
154
+ add_action( 'itsec_scheduled_clear-locks', array( 'ITSEC_Lib', 'delete_expired_locks' ) );
 
 
 
155
  }
156
 
157
  /**
172
  $pass_requirements->run();
173
  }
174
 
175
+ if ( defined( 'ITSEC_USE_CRON' ) && ITSEC_USE_CRON !== ITSEC_Lib::use_cron() ) {
176
+ ITSEC_Modules::set_setting( 'global', 'use_cron', ITSEC_USE_CRON );
177
+ }
178
+
179
  do_action( 'itsec_initialized' );
180
  }
181
 
182
+ private function setup_scheduler() {
183
+
184
+ $choices = array(
185
+ 'ITSEC_Scheduler_Cron' => $this->plugin_dir . 'core/lib/class-itsec-scheduler-cron.php',
186
+ 'ITSEC_Scheduler_Page_Load' => $this->plugin_dir . 'core/lib/class-itsec-scheduler-page-load.php',
187
+ );
188
+
189
+ if ( ITSEC_Lib::use_cron() ) {
190
+ $class = 'ITSEC_Scheduler_Cron';
191
+ } else {
192
+ $class = 'ITSEC_Scheduler_Page_Load';
193
+ }
194
+
195
+ require_once( $choices[ $class ] );
196
+
197
+ $this->scheduler = new $class();
198
+ self::get_scheduler()->run();
199
+ }
200
+
201
  /**
202
  * Retrieve the global instance of the files utility.
203
  *
236
  return self::get_instance()->notifications;
237
  }
238
 
239
+ /**
240
+ * Set the scheduler to use.
241
+ *
242
+ * @param ITSEC_Scheduler $scheduler
243
+ */
244
+ public static function set_scheduler( ITSEC_Scheduler $scheduler ) {
245
+ self::get_instance()->scheduler = $scheduler;
246
+ }
247
+
248
+ /**
249
+ * Get the scheduler.
250
+ *
251
+ * @return ITSEC_Scheduler
252
+ */
253
+ public static function get_scheduler() {
254
+ return self::get_instance()->scheduler;
255
+ }
256
+
257
  /**
258
  * Retrieve the global instance of the Sync API.
259
  *
278
  $sync_api->register( 'itsec-get-everything', 'Ithemes_Sync_Verb_ITSEC_Get_Everything', dirname( __FILE__ ) . '/sync-verbs/itsec-get-everything.php' );
279
  }
280
 
281
+ /**
282
+ * Register events.
283
+ *
284
+ * @param ITSEC_Scheduler $scheduler
285
+ */
286
+ public function register_events( $scheduler ) {
287
+ $scheduler->schedule( ITSEC_Scheduler::S_DAILY, 'clear-locks' );
288
+ }
289
+
290
  /**
291
  * Register core modules.
292
  */
core/history.txt CHANGED
@@ -597,4 +597,28 @@
597
  3.9.3 - 2017-11-02 - Chris Jean & Timothy Jacobs
598
  Bug Fix: Fixed source of the following warning: "mysql_real_escape_string() expects parameter 1 to be string, object given".
599
  3.9.4 - 2017-11-06 - Chris Jean & Timothy Jacobs
600
- Bug Fix: Don't display file change admin notifications if the Notify Admin setting is not enabled.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
597
  3.9.3 - 2017-11-02 - Chris Jean & Timothy Jacobs
598
  Bug Fix: Fixed source of the following warning: "mysql_real_escape_string() expects parameter 1 to be string, object given".
599
  3.9.4 - 2017-11-06 - Chris Jean & Timothy Jacobs
600
+ Bug Fix: Don't display file change admin notifications if the Notify Admin setting is not enabled.
601
+ 3.9.5 - 2017-11-27 - Chris Jean & Timothy Jacobs
602
+ Enhancement: Preserve notification settings when the responsible module is deactivated.
603
+ Bug Fix: Process 404 lockouts on the 'wp' hook to prevent a headers have already been sent warning message.
604
+ Bug Fix: Ensure Hide Backend emails are properly sent when activating Hide Backend before saving the Notification Center for the first time.
605
+ Bug Fix: Prevent warning from being issued on new installs by allowing previous settings to be preserved if they exist.
606
+ Bug Fix: Better handle WP_Error objects in mail errors that occurred before updating to first patch release.
607
+ Bug Fix: A non static method was being called statically.
608
+ 4.0.0 - 2017-12-07 - Chris Jean & Timothy Jacobs
609
+ New Feature: Introduces a scheduling framework for handling events. Cron is now used by default, and will switch to using an alternate scheduling system if it detects an error. To disable this detection set ITSEC_DISABLE_CRON_TEST in your wp-config.php file.
610
+ Important: The ITSEC_FILE_CHECK_CRON and ITSEC_BACKUP_CRON constants have been deprecated. Use ITSEC_USE_CRON instead.
611
+ Bug Fix: Fix occasional duplicate backups and file scans.
612
+ 4.0.1 - 2017-12-11 - Chris Jean & Timothy Jacobs
613
+ Bug Fix: Fixed issue where scheduled events could repeat on sites that do not properly support WordPress's cron system.
614
+ 4.0.2 - 2017-12-28 - Chris Jean & Timothy Jacobs
615
+ Bug Fix: Make Cron scheduler available in more circumstances.
616
+ Bug Fix: Events with the Twice Daily schedule would not be carried over when switching scheduler strategies.
617
+ Bug Fix: Backup schedules respect the interval chosen.
618
+ Bug Fix: Prevent multiple cron tests from being scheduled at once.
619
+ Bug Fix: Cron test being stuck in a loop preventing a site from switching back to the cron scheduler.
620
+ Bug Fix: Prevent warnings when a single and recurring event were scheduled at the same time.
621
+ 4.0.3 - 2017-01-04 - Chris Jean & Timothy Jacobs
622
+ Bug Fix: Fix scheduling retries for Malware Scans on sites that don't fully support WordPress's cron system.
623
+ Bug Fix: Reactivating Away Mode now replaces the active file if you had previously removed it.
624
+ Bug Fix: Ensure lockouts take effect immediately, even on systems where changes to server configuration files do not take effect immediately.
core/lib.php CHANGED
@@ -1024,6 +1024,37 @@ final class ITSEC_Lib {
1024
  return date_i18n( $format, strtotime( get_date_from_gmt( date( 'Y-m-d H:i:s', $timestamp ) ) ) );
1025
  }
1026
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1027
  public static function array_get( $array, $key, $default = null ) {
1028
 
1029
  if ( ! is_array( $array ) ) {
@@ -1052,4 +1083,47 @@ final class ITSEC_Lib {
1052
 
1053
  return $array;
1054
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1055
  }
1024
  return date_i18n( $format, strtotime( get_date_from_gmt( date( 'Y-m-d H:i:s', $timestamp ) ) ) );
1025
  }
1026
 
1027
+ /**
1028
+ * Get the value of an option directly from the database, bypassing any caching.
1029
+ *
1030
+ * @param string $option
1031
+ *
1032
+ * @return array|mixed
1033
+ */
1034
+ public static function get_uncached_option( $option ) {
1035
+ /** @var $wpdb \wpdb */
1036
+ global $wpdb;
1037
+
1038
+ $storage = array();
1039
+
1040
+ if ( is_multisite() ) {
1041
+ $network_id = get_current_site()->id;
1042
+ $row = $wpdb->get_row( $wpdb->prepare( "SELECT meta_value FROM $wpdb->sitemeta WHERE meta_key = %s AND site_id = %d", $option, $network_id ) );
1043
+
1044
+ if ( is_object( $row ) ) {
1045
+ $storage = maybe_unserialize( $row->meta_value );
1046
+ }
1047
+ } else {
1048
+ $row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", $option ) );
1049
+
1050
+ if ( is_object( $row ) ) {
1051
+ $storage = maybe_unserialize( $row->option_value );
1052
+ }
1053
+ }
1054
+
1055
+ return $storage;
1056
+ }
1057
+
1058
  public static function array_get( $array, $key, $default = null ) {
1059
 
1060
  if ( ! is_array( $array ) ) {
1083
 
1084
  return $array;
1085
  }
1086
+
1087
+ /**
1088
+ * Check if WP Cron appears to be running properly.
1089
+ *
1090
+ * @return bool
1091
+ */
1092
+ public static function is_cron_working() {
1093
+ $working = ITSEC_Modules::get_setting( 'global', 'cron_status' );
1094
+
1095
+ return $working === 1;
1096
+ }
1097
+
1098
+ /**
1099
+ * Should we be using Cron.
1100
+ *
1101
+ * @return bool
1102
+ */
1103
+ public static function use_cron() {
1104
+ return ITSEC_Modules::get_setting( 'global', 'use_cron' );
1105
+ }
1106
+
1107
+ /**
1108
+ * Schedule a test to see if a user should be suggested to enable the Cron scheduler.
1109
+ */
1110
+ public static function schedule_cron_test() {
1111
+
1112
+ if ( defined( 'ITSEC_DISABLE_CRON_TEST' ) && ITSEC_DISABLE_CRON_TEST ) {
1113
+ return;
1114
+ }
1115
+
1116
+ $crons = _get_cron_array();
1117
+
1118
+ foreach ( $crons as $timestamp => $cron ) {
1119
+ if ( isset( $cron['itsec_cron_test'] ) ) {
1120
+ return;
1121
+ }
1122
+ }
1123
+
1124
+ // Get a random time in the next 6-18 hours on a random minute.
1125
+ $time = ITSEC_Core::get_current_time_gmt() + mt_rand( 6, 18 ) * HOUR_IN_SECONDS + mt_rand( 1, 60 ) * MINUTE_IN_SECONDS;
1126
+ wp_schedule_single_event( $time, 'itsec_cron_test', array( $time ) );
1127
+ ITSEC_Modules::set_setting( 'global', 'cron_test_time', $time );
1128
+ }
1129
  }
core/lib/class-itsec-job.php ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Job {
4
+
5
+ /** @var ITSEC_Scheduler */
6
+ private $scheduler;
7
+
8
+ /** @var string */
9
+ private $id;
10
+
11
+ /** @var array */
12
+ private $data;
13
+
14
+ /** @var array */
15
+ private $opts;
16
+
17
+ /**
18
+ * ITSEC_Job constructor.
19
+ *
20
+ * @param ITSEC_Scheduler $scheduler
21
+ * @param string $id
22
+ * @param array $data
23
+ * @param array $opts
24
+ */
25
+ public function __construct( ITSEC_Scheduler $scheduler, $id, $data = array(), $opts = array() ) {
26
+ $this->scheduler = $scheduler;
27
+ $this->id = $id;
28
+ $this->data = $data;
29
+ $this->opts = $opts;
30
+ }
31
+
32
+ /**
33
+ * Reschedule a job in some number of seconds.
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'] ++;
44
+ } else {
45
+ $data['retry_count'] = 1;
46
+ }
47
+
48
+ $this->scheduler->schedule_once( ITSEC_Core::get_current_time_gmt() + $seconds, $this->id, $data );
49
+ }
50
+
51
+ /**
52
+ * Get the retry count for this job.
53
+ *
54
+ * @return int|false
55
+ */
56
+ public function is_retry() {
57
+ $data = $this->get_data();
58
+
59
+ if ( empty( $data['retry_count'] ) ) {
60
+ return false;
61
+ }
62
+
63
+ return $data['retry_count'];
64
+ }
65
+
66
+ /**
67
+ * Get the ID of this job.
68
+ *
69
+ * @return string
70
+ */
71
+ public function get_id() {
72
+ return $this->id;
73
+ }
74
+
75
+ /**
76
+ * Get the data attached to the job.
77
+ *
78
+ * @return array
79
+ */
80
+ public function get_data() {
81
+ return $this->data;
82
+ }
83
+
84
+ /**
85
+ * Is this a single event.
86
+ *
87
+ * @return bool
88
+ */
89
+ public function is_single() {
90
+ return ! empty( $this->opts['single'] );
91
+ }
92
+ }
core/lib/class-itsec-lib-utility.php CHANGED
@@ -189,5 +189,28 @@ if ( ! class_exists( 'ITSEC_Lib_Utility' ) ) {
189
 
190
  return $contents;
191
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  }
193
  }
189
 
190
  return $contents;
191
  }
192
+
193
+ /**
194
+ * Check if an array is associative.
195
+ *
196
+ * @param array $array
197
+ *
198
+ * @return bool
199
+ */
200
+ public static function is_associative_array( $array ) {
201
+ if ( ! is_array( $array ) || empty( $array ) ) {
202
+ return false;
203
+ }
204
+
205
+ $next = 0;
206
+
207
+ foreach ( $array as $k => $v ) {
208
+ if ( $k !== $next ++ ) {
209
+ return true;
210
+ }
211
+ }
212
+
213
+ return false;
214
+ }
215
  }
216
  }
core/lib/class-itsec-scheduler-cron.php ADDED
@@ -0,0 +1,378 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Scheduler_Cron extends ITSEC_Scheduler {
4
+
5
+ const HOOK = 'itsec_cron';
6
+ const OPTION = 'itsec_cron';
7
+
8
+ public function run() {
9
+ add_action( self::HOOK, array( $this, 'process' ), 10, 2 );
10
+ add_filter( 'cron_schedules', array( $this, 'register_cron_schedules' ) );
11
+ }
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
+ );
27
+
28
+ foreach ( $this->custom_schedules as $schedule => $interval ) {
29
+ $schedules[ 'itsec-' . $schedule ] = array(
30
+ 'display' => ucfirst( $schedule ),
31
+ 'interval' => $interval,
32
+ );
33
+ }
34
+
35
+ return $schedules;
36
+ }
37
+
38
+ public function process( $id, $hash = null ) {
39
+
40
+ if ( $hash ) {
41
+ $this->run_single_event_by_hash( $id, $hash );
42
+ } else {
43
+ $this->run_recurring_event( $id );
44
+ }
45
+ }
46
+
47
+ public function run_recurring_event( $id ) {
48
+
49
+ $storage = $this->get_options();
50
+ $data = $storage['recurring'][ $id ]['data'];
51
+
52
+ $job = $this->make_job( $id, $data );
53
+
54
+ if ( $this->is_retry_scheduled( $id, $data ) ) {
55
+ return;
56
+ }
57
+
58
+ $this->call_action( $job );
59
+ }
60
+
61
+ public function run_single_event( $id, $data = array() ) {
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->call_action( $job );
81
+ $this->unschedule_single( $id, $data );
82
+ }
83
+
84
+ public function is_recurring_scheduled( $id ) {
85
+ return (bool) wp_next_scheduled( self::HOOK, array( $id ) );
86
+ }
87
+
88
+ public function is_single_scheduled( $id, $data = array() ) {
89
+ return (bool) wp_next_scheduled( self::HOOK, array( $id, $this->hash_data( $data ) ) );
90
+ }
91
+
92
+ public function schedule( $schedule, $id, $data = array(), $opts = array() ) {
93
+
94
+ if ( ! $this->scheduling_lock() ) {
95
+ return false;
96
+ }
97
+
98
+ if ( $this->is_recurring_scheduled( $id ) ) {
99
+ $this->scheduling_unlock();
100
+
101
+ return false;
102
+ }
103
+
104
+ $options = $this->get_options();
105
+
106
+ $options['recurring'][ $id ] = array( 'data' => $data );
107
+ $this->set_options( $options );
108
+
109
+ $args = array( $id );
110
+
111
+ // Prevent a flood of cron events all occurring at the same time.
112
+ $time = isset( $opts['fire_at'] ) ? $opts['fire_at'] : ITSEC_Core::get_current_time_gmt() + 60 * mt_rand( 1, 30 );
113
+ $scheduled = wp_schedule_event( $time, $this->cron_name_for_schedule( $schedule ), self::HOOK, $args );
114
+ $this->scheduling_unlock();
115
+
116
+ if ( false === $scheduled ) {
117
+ return false;
118
+ }
119
+
120
+ return true;
121
+ }
122
+
123
+ public function schedule_once( $at, $id, $data = array() ) {
124
+
125
+ if ( ! $this->scheduling_lock() ) {
126
+ return false;
127
+ }
128
+
129
+ if ( $this->is_single_scheduled( $id, $data ) ) {
130
+ $this->scheduling_unlock();
131
+
132
+ return false;
133
+ }
134
+
135
+ $hash = $this->hash_data( $data );
136
+ $args = array( $id, $hash );
137
+
138
+ $options = $this->get_options();
139
+ $options['single'][ $id ][ $hash ] = array( 'data' => $data );
140
+ $this->set_options( $options );
141
+
142
+ $scheduled = wp_schedule_single_event( $at, self::HOOK, $args );
143
+ $this->scheduling_unlock();
144
+
145
+ if ( false === $scheduled ) {
146
+ return false;
147
+ }
148
+
149
+ return true;
150
+ }
151
+
152
+ public function unschedule( $id ) {
153
+
154
+ $hash = $this->make_cron_hash( $id );
155
+
156
+ if ( $this->unschedule_by_hash( $hash ) ) {
157
+
158
+ $options = $this->get_options();
159
+ unset( $options['recurring'][ $id ] );
160
+ $this->set_options( $options );
161
+
162
+ return true;
163
+ }
164
+
165
+ return false;
166
+ }
167
+
168
+ public function unschedule_single( $id, $data = array() ) {
169
+ $data_hash = $this->hash_data( $data );
170
+ $hash = $this->make_cron_hash( $id, $data );
171
+
172
+ if ( $this->unschedule_by_hash( $hash ) ) {
173
+
174
+ $options = $this->get_options();
175
+ unset( $options['single'][ $id ][ $data_hash ] );
176
+ $this->set_options( $options );
177
+
178
+ return true;
179
+ }
180
+
181
+ return false;
182
+ }
183
+
184
+ private function unschedule_by_hash( $hash ) {
185
+
186
+ $crons = _get_cron_array();
187
+ $found = false;
188
+
189
+ foreach ( $crons as $timestamp => $hooks ) {
190
+ if ( isset( $hooks[ self::HOOK ][ $hash ] ) ) {
191
+ $found = true;
192
+ unset( $crons[ $timestamp ][ self::HOOK ][ $hash ] );
193
+ break;
194
+ }
195
+ }
196
+
197
+ if ( $found ) {
198
+ _set_cron_array( $crons );
199
+ }
200
+
201
+ return $found;
202
+ }
203
+
204
+ public function get_recurring_events() {
205
+
206
+ $crons = _get_cron_array();
207
+ $options = $this->get_options();
208
+ $events = array();
209
+
210
+ foreach ( $crons as $timestamp => $hooks ) {
211
+
212
+ if ( ! isset( $hooks[ self::HOOK ] ) ) {
213
+ continue;
214
+ }
215
+
216
+ foreach ( $hooks[ self::HOOK ] as $key => $cron_event ) {
217
+
218
+ list( $id ) = $cron_event['args'];
219
+
220
+ if ( ! isset( $options['recurring'][ $id ] ) || isset( $cron_event['args'][1] ) ) {
221
+ continue;
222
+ }
223
+
224
+ $events[] = array(
225
+ 'id' => $id,
226
+ 'data' => $options['recurring'][ $id ]['data'],
227
+ 'fire_at' => $timestamp,
228
+ 'schedule' => $this->get_api_schedule_from_cron_schedule( $cron_event['schedule'] ),
229
+ );
230
+ }
231
+ }
232
+
233
+ return $events;
234
+ }
235
+
236
+ public function get_single_events() {
237
+
238
+ $crons = _get_cron_array();
239
+ $options = $this->get_options();
240
+ $events = array();
241
+
242
+ foreach ( $crons as $timestamp => $hooks ) {
243
+
244
+ if ( ! isset( $hooks[ self::HOOK ] ) ) {
245
+ continue;
246
+ }
247
+
248
+ foreach ( $hooks[ self::HOOK ] as $key => $cron_event ) {
249
+
250
+ $id = $cron_event['args'][0];
251
+
252
+ if ( ! isset( $options['single'][ $id ], $cron_event['args'][1] ) ) {
253
+ continue;
254
+ }
255
+
256
+ $hash = $cron_event['args'][1];
257
+
258
+ if ( ! isset( $options['single'][ $id ][ $hash ] ) ) {
259
+ continue; // Sanity check
260
+ }
261
+
262
+ $events[] = array(
263
+ 'id' => $id,
264
+ 'data' => $options['single'][ $id ][ $hash ]['data'],
265
+ 'fire_at' => $timestamp,
266
+ );
267
+ }
268
+ }
269
+
270
+ return $events;
271
+ }
272
+
273
+ /**
274
+ * Is a retry of the given job scheduled.
275
+ *
276
+ * @param string $id
277
+ * @param array $data
278
+ *
279
+ * @return bool
280
+ */
281
+ private function is_retry_scheduled( $id, $data ) {
282
+ $options = $this->get_options();
283
+
284
+ if ( ! isset( $options['single'][ $id ] ) ) {
285
+ return false;
286
+ }
287
+
288
+ foreach ( $options['single'][ $id ] as $hash => $event ) {
289
+ $maybe_data = $event['data'];
290
+
291
+ if ( ! isset( $maybe_data['retry_count'] ) ) {
292
+ continue;
293
+ }
294
+
295
+ unset( $maybe_data['retry_count'] );
296
+
297
+ if ( $this->hash_data( $maybe_data ) === $this->hash_data( $data ) ) {
298
+ return true;
299
+ }
300
+ }
301
+
302
+ return false;
303
+ }
304
+
305
+ /**
306
+ * Make the Cron hash that WordPress uses to uniquely identify cron events by.
307
+ *
308
+ * @param string $id
309
+ * @param array $data
310
+ *
311
+ * @return string
312
+ */
313
+ private function make_cron_hash( $id, $data = null ) {
314
+
315
+ if ( func_num_args() === 1 ) {
316
+ return md5( serialize( array( $id ) ) );
317
+ }
318
+
319
+ return md5( serialize( array( $id, $this->hash_data( $data ) ) ) );
320
+ }
321
+
322
+ private function cron_name_for_schedule( $schedule ) {
323
+ switch ( $schedule ) {
324
+ case self::S_HOURLY:
325
+ case self::S_DAILY:
326
+ return $schedule;
327
+ case self::S_TWICE_DAILY:
328
+ return 'twicedaily';
329
+ default:
330
+ return "itsec-{$schedule}";
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Get the API schedule name from the Cron schedule name.
336
+ *
337
+ * @param string $cron_schedule
338
+ *
339
+ * @return string
340
+ */
341
+ private function get_api_schedule_from_cron_schedule( $cron_schedule ) {
342
+ $api_schedule = str_replace( 'itsec-', '', $cron_schedule );
343
+
344
+ if ( $api_schedule === 'twicedaily' ) {
345
+ $api_schedule = 'twice-daily';
346
+ }
347
+
348
+ return $api_schedule;
349
+ }
350
+
351
+ private function get_options() {
352
+ return wp_parse_args( get_site_option( self::OPTION, array() ), array(
353
+ 'single' => array(),
354
+ 'recurring' => array(),
355
+ ) );
356
+ }
357
+
358
+ private function set_options( $options ) {
359
+ update_site_option( self::OPTION, $options );
360
+ }
361
+
362
+ public function uninstall() {
363
+
364
+ $crons = _get_cron_array();
365
+
366
+ foreach ( $crons as $timestamp => $args ) {
367
+ unset( $crons[ $timestamp ][ self::HOOK ] );
368
+
369
+ if ( empty( $crons[ $timestamp ] ) ) {
370
+ unset( $crons[ $timestamp ] );
371
+ }
372
+ }
373
+
374
+ _set_cron_array( $crons );
375
+
376
+ delete_site_option( self::OPTION );
377
+ }
378
+ }
core/lib/class-itsec-scheduler-page-load.php ADDED
@@ -0,0 +1,352 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Scheduler_Page_Load extends ITSEC_Scheduler {
4
+
5
+ const OPTION = 'itsec_scheduler_page_load';
6
+
7
+ private $operating_data;
8
+
9
+ public function schedule( $schedule, $id, $data = array(), $opts = array() ) {
10
+
11
+ if ( ! $this->scheduling_lock() ) {
12
+ return false;
13
+ }
14
+
15
+ if ( $this->is_recurring_scheduled( $id ) ) {
16
+ $this->scheduling_unlock();
17
+
18
+ return false;
19
+ }
20
+
21
+ if ( isset( $opts['fire_at'] ) ) {
22
+ $last_fired = $opts['fire_at'];
23
+ } else {
24
+ // Prevent an event stampede
25
+ $last_fired = ITSEC_Core::get_current_time_gmt() + 60 * mt_rand( 1, 30 );
26
+ }
27
+
28
+ $last_fired -= $this->get_schedule_interval( $schedule );
29
+
30
+ $options = $this->operating_data ? $this->operating_data : $this->get_options();
31
+
32
+ $options['recurring'][ $id ] = array(
33
+ 'schedule' => $schedule,
34
+ 'last_fired' => $last_fired,
35
+ 'data' => $data,
36
+ );
37
+
38
+ $set = $this->set_options( $options );
39
+ $this->scheduling_unlock();
40
+
41
+ return $set;
42
+ }
43
+
44
+ public function schedule_once( $at, $id, $data = array() ) {
45
+
46
+ if ( ! $this->scheduling_lock() ) {
47
+ return false;
48
+ }
49
+
50
+ if ( $this->is_single_scheduled( $id, $data ) ) {
51
+ $this->scheduling_unlock();
52
+
53
+ return false;
54
+ }
55
+
56
+ $hash = $this->hash_data( $data );
57
+ $options = $this->operating_data ? $this->operating_data : $this->get_options();
58
+
59
+ if ( ! isset( $options['single'][ $id ] ) ) {
60
+ $options['single'][ $id ] = array();
61
+ }
62
+
63
+ $options['single'][ $id ][ $hash ] = array(
64
+ 'data' => $data,
65
+ 'fire_at' => $at,
66
+ );
67
+
68
+ $set = $this->set_options( $options );
69
+ $this->scheduling_unlock();
70
+
71
+ return $set;
72
+ }
73
+
74
+ public function is_recurring_scheduled( $id ) {
75
+ $options = $this->get_options();
76
+
77
+ return ! empty( $options['recurring'][ $id ] );
78
+ }
79
+
80
+ public function is_single_scheduled( $id, $data = array() ) {
81
+
82
+ $hash = $this->hash_data( $data );
83
+ $options = $this->get_options();
84
+
85
+ if ( empty( $options['single'][ $id ] ) ) {
86
+ return false;
87
+ }
88
+
89
+ if ( empty( $options['single'][ $id ][ $hash ] ) ) {
90
+ return false;
91
+ }
92
+
93
+ return true;
94
+ }
95
+
96
+ public function unschedule( $id ) {
97
+
98
+ $options = $this->operating_data ? $this->operating_data : $this->get_options();
99
+
100
+ if ( isset( $options['recurring'][ $id ] ) ) {
101
+ unset( $options['recurring'][ $id ] );
102
+
103
+ return $this->set_options( $options );
104
+ }
105
+
106
+ return false;
107
+ }
108
+
109
+ public function unschedule_single( $id, $data = array() ) {
110
+
111
+ $options = $this->operating_data ? $this->operating_data : $this->get_options();
112
+ $hash = $this->hash_data( $data );
113
+
114
+ if ( isset( $options['single'][ $id ][ $hash ] ) ) {
115
+ unset( $options['single'][ $id ][ $hash ] );
116
+
117
+ return $this->set_options( $options );
118
+ }
119
+
120
+ return false;
121
+ }
122
+
123
+ public function get_recurring_events() {
124
+ $options = $this->get_options();
125
+ $events = array();
126
+
127
+ foreach ( $options['recurring'] as $id => $event ) {
128
+ $events[] = array(
129
+ 'id' => $id,
130
+ 'data' => $event['data'],
131
+ 'schedule' => $event['schedule'],
132
+ 'fire_at' => $event['last_fired'] + $this->get_schedule_interval( $event['schedule'] ),
133
+ );
134
+ }
135
+
136
+ return $events;
137
+ }
138
+
139
+ public function get_single_events() {
140
+
141
+ $options = $this->get_options();
142
+ $events = array();
143
+
144
+ foreach ( $options['single'] as $id => $hashes ) {
145
+ foreach ( $hashes as $hash => $event ) {
146
+ $events[] = array(
147
+ 'id' => $id,
148
+ 'data' => $event['data'],
149
+ 'fire_at' => $event['fire_at'],
150
+ );
151
+ }
152
+ }
153
+
154
+ return $events;
155
+ }
156
+
157
+ public function run() {
158
+ add_action( 'init', array( $this, 'init' ) );
159
+ }
160
+
161
+ public function init() {
162
+
163
+ if ( ITSEC_Core::is_api_request() ) {
164
+ return;
165
+ }
166
+
167
+ $now = ITSEC_Core::get_current_time_gmt();
168
+ $options = $this->get_options();
169
+
170
+ $to_process = array();
171
+
172
+ foreach ( $options['single'] as $id => $hashes ) {
173
+ foreach ( $hashes as $hash => $event ) {
174
+ if ( $event['fire_at'] < $now ) {
175
+ $to_process[] = array_merge( $event, array( 'id' => $id ) );
176
+ }
177
+ }
178
+ }
179
+
180
+ foreach ( $options['recurring'] as $id => $event ) {
181
+ if ( $this->is_time_to_send( $event['schedule'], $event['last_fired'] ) ) {
182
+ $to_process[] = array_merge( $event, array( 'id' => $id ) );
183
+ }
184
+ }
185
+
186
+ if ( ! $to_process ) {
187
+ return;
188
+ }
189
+
190
+ if ( ! ITSEC_Lib::get_lock( 'scheduler', 120 ) ) {
191
+ return;
192
+ }
193
+
194
+ $raw = $this->operating_data = ITSEC_Lib::get_uncached_option( self::OPTION );
195
+
196
+ foreach ( $to_process as $process ) {
197
+ if ( isset( $process['fire_at'] ) ) {
198
+
199
+ if ( ! isset( $raw['single'][ $process['id'] ][ $this->hash_data( $process['data'] ) ] ) ) {
200
+ continue; // Another process already fired this single event.
201
+ }
202
+
203
+ $event = $raw['single'][ $process['id'] ][ $this->hash_data( $process['data'] ) ];
204
+
205
+ if ( $event['fire_at'] < $now ) {
206
+ $this->run_single_event( $process['id'], $event['data'] );
207
+ }
208
+ } else {
209
+ $event = $raw['recurring'][ $process['id'] ];
210
+
211
+ if ( $this->is_time_to_send( $event['schedule'], $event['last_fired'] ) ) {
212
+ $this->run_recurring_event( $process['id'] );
213
+ $this->update_last_fired( $process['id'] );
214
+ }
215
+ }
216
+ }
217
+
218
+ $this->operating_data = null;
219
+ ITSEC_Lib::release_lock( 'scheduler' );
220
+ }
221
+
222
+ public function run_recurring_event( $id ) {
223
+
224
+ if ( $this->operating_data ) {
225
+ $clear_operating_data = false;
226
+ $storage = $this->operating_data;
227
+ } else {
228
+ $clear_operating_data = true;
229
+ $storage = $this->operating_data = $this->get_options();
230
+ }
231
+
232
+ $event = $storage['recurring'][ $id ];
233
+
234
+ $job = $this->make_job( $id, $event['data'] );
235
+
236
+ if ( $this->is_retry_scheduled( $id, $event['data'] ) ) {
237
+ return;
238
+ }
239
+
240
+ $this->call_action( $job );
241
+
242
+ if ( $clear_operating_data ) {
243
+ $this->operating_data = null;
244
+ }
245
+ }
246
+
247
+ public function run_single_event( $id, $data = array() ) {
248
+
249
+ if ( $this->operating_data ) {
250
+ $clear_operating_data = false;
251
+ $storage = $this->operating_data;
252
+ } else {
253
+ $clear_operating_data = true;
254
+ $storage = $this->operating_data = $this->get_options();
255
+ }
256
+
257
+ $hash = $this->hash_data( $data );
258
+ $event = $storage['single'][ $id ][ $hash ];
259
+
260
+ $job = $this->make_job( $id, $event['data'], array( 'single' => true ) );
261
+
262
+ $this->call_action( $job );
263
+
264
+ $this->unschedule_single( $id, $data );
265
+
266
+ if ( $clear_operating_data ) {
267
+ $this->operating_data = null;
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Update the time the job was last fired to now.
273
+ *
274
+ * @param string $id
275
+ */
276
+ private function update_last_fired( $id ) {
277
+
278
+ $storage = $this->operating_data ? $this->operating_data : $this->get_options();
279
+
280
+ $storage['recurring'][ $id ]['last_fired'] = ITSEC_Core::get_current_time_gmt();
281
+
282
+ $this->set_options( $storage );
283
+ }
284
+
285
+ /**
286
+ * Is a retry of the given job scheduled.
287
+ *
288
+ * @param string $id
289
+ * @param array $data
290
+ *
291
+ * @return bool
292
+ */
293
+ private function is_retry_scheduled( $id, $data ) {
294
+ $options = $this->operating_data ? $this->operating_data : $this->get_options();
295
+
296
+ if ( ! isset( $options['single'][ $id ] ) ) {
297
+ return false;
298
+ }
299
+
300
+ foreach ( $options['single'][ $id ] as $hash => $event ) {
301
+
302
+ $event_data = $event['data'];
303
+
304
+ if ( ! isset( $event_data['retry_count'] ) ) {
305
+ continue;
306
+ }
307
+
308
+ unset( $event_data['retry_count'] );
309
+
310
+ if ( $this->hash_data( $event_data ) === $this->hash_data( $data ) ) {
311
+ return true;
312
+ }
313
+ }
314
+
315
+ return false;
316
+ }
317
+
318
+ private function is_time_to_send( $schedule, $last_sent ) {
319
+
320
+ if ( ! $last_sent ) {
321
+ return true;
322
+ }
323
+
324
+ $period = $this->get_schedule_interval( $schedule );
325
+
326
+ if ( ! $period ) {
327
+ return false;
328
+ }
329
+
330
+ return ( $last_sent + $period ) < ITSEC_Core::get_current_time_gmt();
331
+ }
332
+
333
+ private function get_options() {
334
+ return wp_parse_args( get_site_option( self::OPTION, array() ), array(
335
+ 'single' => array(),
336
+ 'recurring' => array(),
337
+ ) );
338
+ }
339
+
340
+ private function set_options( $options ) {
341
+
342
+ if ( $this->operating_data !== null ) {
343
+ $this->operating_data = $options;
344
+ }
345
+
346
+ return update_site_option( self::OPTION, $options );
347
+ }
348
+
349
+ public function uninstall() {
350
+ delete_site_option( self::OPTION );
351
+ }
352
+ }
core/lib/class-itsec-scheduler.php ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
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';
8
+ const S_DAILY = 'daily';
9
+ const S_WEEKLY = 'weekly';
10
+ const S_MONTHLY = 'monthly';
11
+
12
+ const LOCK_SCHEDULING = 'scheduling';
13
+
14
+ /** @var array */
15
+ protected $custom_schedules = array();
16
+
17
+ /**
18
+ * Schedule a recurring event.
19
+ *
20
+ * Only one event with the given id can be scheduled at a time.
21
+ *
22
+ * @param string $schedule
23
+ * @param string $id
24
+ * @param array $data
25
+ * @param array $opts
26
+ * - fire_at: Manually specify the first time the event should be fired.
27
+ *
28
+ * @return bool
29
+ */
30
+ abstract public function schedule( $schedule, $id, $data = array(), $opts = array() );
31
+
32
+ /**
33
+ * Schedule a single event.
34
+ *
35
+ * Only one event with the given id and same set of data can be scheduled at the same time.
36
+ *
37
+ * Unlike Core's CRON implementation, event if a single event is more than 10 minutes in the future, it cannot be scheduled.
38
+ *
39
+ * @param int $at
40
+ * @param string $id
41
+ * @param array $data
42
+ *
43
+ * @return bool
44
+ */
45
+ abstract public function schedule_once( $at, $id, $data = array() );
46
+
47
+ /**
48
+ * Is a recurring event scheduled.
49
+ *
50
+ * @param string $id
51
+ *
52
+ * @return bool
53
+ */
54
+ abstract public function is_recurring_scheduled( $id );
55
+
56
+ /**
57
+ * Is a single event scheduled with the given data.
58
+ *
59
+ * @param string $id
60
+ * @param array $data
61
+ *
62
+ * @return bool
63
+ */
64
+ abstract public function is_single_scheduled( $id, $data = array() );
65
+
66
+ /**
67
+ * Unschedule a recurring event.
68
+ *
69
+ * @param string $id
70
+ *
71
+ * @return bool
72
+ */
73
+ abstract public function unschedule( $id );
74
+
75
+ /**
76
+ * Unschedule a single event.
77
+ *
78
+ * The data specified needs to be identical to the data the single event was scheduled with.
79
+ *
80
+ * @param string $id
81
+ * @param array $data
82
+ *
83
+ * @return bool
84
+ */
85
+ abstract public function unschedule_single( $id, $data = array() );
86
+
87
+ /**
88
+ * Get all of the scheduled recurring events.
89
+ *
90
+ * Each event is an array with the following properties.
91
+ * - id: The ID the event was scheduled with.
92
+ * - data: The data the event was scheduled with.
93
+ * - fire_at: The next time the event should be fired.
94
+ * - schedule: The selected schedule. See S_HOURLY, etc...
95
+ *
96
+ * @return array
97
+ */
98
+ abstract public function get_recurring_events();
99
+
100
+ /**
101
+ * Get all of the single events.
102
+ *
103
+ * Each event is an array with the following properties.
104
+ * - id: The ID the event was scheduled with.
105
+ * - data: The data the event was scheduled with.
106
+ * - fire_at: The time the event should be fired.
107
+ *
108
+ * @return array
109
+ */
110
+ abstract public function get_single_events();
111
+
112
+ /**
113
+ * Run a recurring event, even if it is not time to.
114
+ *
115
+ * This will _not_ update the last fired time.
116
+ *
117
+ * @param string $id
118
+ *
119
+ * @return void
120
+ */
121
+ abstract public function run_recurring_event( $id );
122
+
123
+ /**
124
+ * Run a single event, even if it is not time to.
125
+ *
126
+ * This will clear the event from the schedule.
127
+ *
128
+ * @param string $id
129
+ * @param array $data
130
+ *
131
+ * @return void
132
+ */
133
+ abstract public function run_single_event( $id, $data = array() );
134
+
135
+ /**
136
+ * Code executed on every page load to setup the scheduler.
137
+ *
138
+ * @return void
139
+ */
140
+ abstract public function run();
141
+
142
+ /**
143
+ * Manually trigger modules to register their scheduled events.
144
+ *
145
+ * @return void
146
+ */
147
+ public function register_events() {
148
+ /**
149
+ * Register scheduled events.
150
+ *
151
+ * Events should be registered in response to a user action, for example activating a module or changing a setting.
152
+ * Occasionally, iThemes Security will manually ask for all events to be scheduled.
153
+ *
154
+ * @param ITSEC_Scheduler $this
155
+ */
156
+ do_action( 'itsec_scheduler_register_events', $this );
157
+ }
158
+
159
+ /**
160
+ * Register a custom schedule.
161
+ *
162
+ * @param string $slug
163
+ * @param int $interval
164
+ */
165
+ public function register_custom_schedule( $slug, $interval ) {
166
+ $this->custom_schedules[ $slug ] = $interval;
167
+ }
168
+
169
+ /**
170
+ * Get a lock to be used for scheduling events.
171
+ *
172
+ * @return bool
173
+ */
174
+ protected function scheduling_lock() {
175
+ return ITSEC_Lib::get_lock( self::LOCK_SCHEDULING, 5 );
176
+ }
177
+
178
+ /**
179
+ * Release the lock used for scheduling events.
180
+ */
181
+ protected function scheduling_unlock() {
182
+ ITSEC_Lib::release_lock( self::LOCK_SCHEDULING );
183
+ }
184
+
185
+ /**
186
+ * Make a job object.
187
+ *
188
+ * @param string $id
189
+ * @param array $data
190
+ * @param array $opts
191
+ *
192
+ * @return ITSEC_Job
193
+ */
194
+ protected function make_job( $id, $data, $opts = array() ) {
195
+ return new ITSEC_Job( $this, $id, $data, $opts );
196
+ }
197
+
198
+ /**
199
+ * Dispatch the action to execute the scheduled job.
200
+ *
201
+ * @param ITSEC_Job $job
202
+ */
203
+ protected final function call_action( ITSEC_Job $job ) {
204
+ /**
205
+ * Fires when a scheduled job should be executed.
206
+ *
207
+ * @param ITSEC_Job $job
208
+ */
209
+ do_action( "itsec_scheduled_{$job->get_id()}", $job );
210
+ }
211
+
212
+ /**
213
+ * Generate a unique hash of the data.
214
+ *
215
+ * @param array $data
216
+ *
217
+ * @return string
218
+ */
219
+ protected function hash_data( $data ) {
220
+ return md5( serialize( $data ) );
221
+ }
222
+
223
+ /**
224
+ * Get the interval for the schedule.
225
+ *
226
+ * @param string $schedule
227
+ *
228
+ * @return int
229
+ */
230
+ protected final function get_schedule_interval( $schedule ) {
231
+ switch ( $schedule ) {
232
+ case self::S_HOURLY:
233
+ return HOUR_IN_SECONDS;
234
+ case self::S_FOUR_DAILY:
235
+ return DAY_IN_SECONDS / 4;
236
+ case self::S_TWICE_DAILY:
237
+ return DAY_IN_SECONDS / 2;
238
+ case self::S_DAILY:
239
+ return DAY_IN_SECONDS;
240
+ case self::S_WEEKLY:
241
+ return WEEK_IN_SECONDS;
242
+ case self::S_MONTHLY:
243
+ return MONTH_IN_SECONDS;
244
+ default:
245
+ return isset( $this->custom_schedules[ $schedule ] ) ? $this->custom_schedules[ $schedule ] : false;
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Run code when the plugin is uninstalled.
251
+ */
252
+ public function uninstall() {
253
+
254
+ }
255
+ }
core/lib/validator.php CHANGED
@@ -104,6 +104,14 @@ abstract class ITSEC_Validator {
104
  }
105
  }
106
 
 
 
 
 
 
 
 
 
107
  final protected function sanitize_setting( $type, $var, $name, $prevent_save_on_error = true, $trim_value = true ) {
108
  $id = $this->get_id();
109
 
104
  }
105
  }
106
 
107
+ final protected function preserve_setting_if_exists( $vars ) {
108
+ foreach ( (array) $vars as $var ) {
109
+ if ( array_key_exists( $var, $this->previous_settings ) && ( ! isset( $this->settings['var'] ) || '' === $this->settings[ $var ] ) ) {
110
+ $this->settings[ $var ] = $this->previous_settings[ $var ];
111
+ }
112
+ }
113
+ }
114
+
115
  final protected function sanitize_setting( $type, $var, $name, $prevent_save_on_error = true, $trim_value = true ) {
116
  $id = $this->get_id();
117
 
core/lockout.php CHANGED
@@ -56,30 +56,21 @@
56
  */
57
  final class ITSEC_Lockout {
58
 
59
- /** @var ITSEC_Core */
60
- private $core;
61
-
62
  private $lockout_modules;
63
 
64
  /**
65
  * ITSEC_Lockout constructor.
66
- *
67
- * @param ITSEC_Core $core
68
  */
69
- public function __construct( $core ) {
70
-
71
- $this->core = $core;
72
  $this->lockout_modules = array(); //array to hold information on modules using this feature
 
73
 
74
- //Run database cleanup daily with cron
75
- if ( ! wp_next_scheduled( 'itsec_purge_lockouts' ) ) {
76
- wp_schedule_event( time(), 'daily', 'itsec_purge_lockouts' );
77
- }
78
-
79
- add_action( 'itsec_purge_lockouts', array( $this, 'purge_lockouts' ) );
80
 
81
  //Check for host lockouts
82
- add_action( 'init', array( $this, 'check_current_user_for_host_lockouts' ) );
83
 
84
  // Ensure that locked out users are prevented from checking logins.
85
  add_filter( 'authenticate', array( $this, 'check_authenticate_lockout' ), 30 );
@@ -132,23 +123,17 @@ final class ITSEC_Lockout {
132
  }
133
 
134
  /**
135
- * Lockout a user on every page load if there host becomes locked.
 
 
 
 
136
  */
137
- public function check_current_user_for_host_lockouts() {
138
-
139
- if ( ! is_user_logged_in() ) {
140
- return;
141
- }
142
 
143
- global $wpdb;
144
-
145
- $host = ITSEC_Lib::get_ip();
146
- $host_check = $wpdb->get_var( $wpdb->prepare( "SELECT `lockout_host` FROM `{$wpdb->base_prefix}itsec_lockouts` WHERE `lockout_active`=1 AND `lockout_expire_gmt` > %s AND `lockout_host` = %s;", array(
147
- date( 'Y-m-d H:i:s', ITSEC_Core::get_current_time_gmt() ),
148
- $host
149
- ) ) );
150
 
151
- if ( $host_check ) {
152
  $this->execute_lock();
153
  }
154
  }
@@ -274,6 +259,24 @@ final class ITSEC_Lockout {
274
  ) );
275
  }
276
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  /**
278
  * This persists a lockout to storage or performs a permanent ban if appropriate.
279
  *
@@ -503,10 +506,22 @@ final class ITSEC_Lockout {
503
  @header( 'Expires: Thu, 22 Jun 1978 00:28:00 GMT' );
504
  @header( 'Pragma: no-cache' );
505
 
506
- die( $message );
 
 
 
507
  }
508
  }
509
 
 
 
 
 
 
 
 
 
 
510
  /**
511
  * Provides a description of lockout configuration for use in module settings.
512
  *
@@ -982,6 +997,15 @@ final class ITSEC_Lockout {
982
  return true;
983
  }
984
 
 
 
 
 
 
 
 
 
 
985
  /**
986
  * Purges lockouts more than 7 days old from the database
987
  *
@@ -1137,8 +1161,7 @@ final class ITSEC_Lockout {
1137
 
1138
  $error_handler->add( $type, $message );
1139
 
1140
- $this->core->show_network_admin_notice( $error_handler );
1141
-
1142
  } else {
1143
 
1144
  add_settings_error( 'itsec', esc_attr( 'settings_updated' ), $message, $type );
56
  */
57
  final class ITSEC_Lockout {
58
 
 
 
 
59
  private $lockout_modules;
60
 
61
  /**
62
  * ITSEC_Lockout constructor.
 
 
63
  */
64
+ public function __construct() {
 
 
65
  $this->lockout_modules = array(); //array to hold information on modules using this feature
66
+ }
67
 
68
+ public function run() {
69
+ add_action( 'itsec_scheduler_register_events', array( $this, 'register_events' ) );
70
+ add_action( 'itsec_scheduled_purge-lockouts', array( $this, 'purge_lockouts' ) );
 
 
 
71
 
72
  //Check for host lockouts
73
+ add_action( 'init', array( $this, 'check_for_host_lockouts' ) );
74
 
75
  // Ensure that locked out users are prevented from checking logins.
76
  add_filter( 'authenticate', array( $this, 'check_authenticate_lockout' ), 30 );
123
  }
124
 
125
  /**
126
+ * On every page load, check if the current host is locked out.
127
+ *
128
+ * When a host becomes locked out, iThemes Security performs a quick ban. This will cause an IP block to be
129
+ * written to the site's server configuration file. This ip block might not immediately take effect, particularly
130
+ * on Nginx systems. So on every page load we check that if the current host is locked out or not.
131
  */
132
+ public function check_for_host_lockouts() {
 
 
 
 
133
 
134
+ $host = ITSEC_Lib::get_ip();
 
 
 
 
 
 
135
 
136
+ if ( $this->is_host_locked_out( $host ) ) {
137
  $this->execute_lock();
138
  }
139
  }
259
  ) );
260
  }
261
 
262
+ /**
263
+ * Check if a given host is locked out.
264
+ *
265
+ * @param string $host
266
+ *
267
+ * @return bool
268
+ */
269
+ public function is_host_locked_out( $host ) {
270
+
271
+ /** @var wpdb $wpdb */
272
+ global $wpdb;
273
+
274
+ return (bool) $wpdb->get_var( $wpdb->prepare(
275
+ "SELECT `lockout_host` FROM `{$wpdb->base_prefix}itsec_lockouts` WHERE `lockout_active`=1 AND `lockout_expire_gmt` > %s AND `lockout_host` = %s;",
276
+ date( 'Y-m-d H:i:s', ITSEC_Core::get_current_time_gmt() ), $host
277
+ ) );
278
+ }
279
+
280
  /**
281
  * This persists a lockout to storage or performs a permanent ban if appropriate.
282
  *
506
  @header( 'Expires: Thu, 22 Jun 1978 00:28:00 GMT' );
507
  @header( 'Pragma: no-cache' );
508
 
509
+ add_filter( 'wp_die_handler', array( $this, 'apply_wp_die_handler' ) );
510
+ add_filter( 'wp_die_ajax_handler', array( $this, 'apply_wp_die_handler' ) );
511
+ add_filter( 'wp_die_xmlrpc_handler', array( $this, 'apply_wp_die_handler' ) );
512
+ wp_die( $message, '', array( 'response' => 403 ) );
513
  }
514
  }
515
 
516
+ /**
517
+ * Apply the Scalar wp die handler to print a message to the screen.
518
+ *
519
+ * @return string
520
+ */
521
+ public function apply_wp_die_handler() {
522
+ return '_scalar_wp_die_handler';
523
+ }
524
+
525
  /**
526
  * Provides a description of lockout configuration for use in module settings.
527
  *
997
  return true;
998
  }
999
 
1000
+ /**
1001
+ * Register the purge lockout event.
1002
+ *
1003
+ * @param ITSEC_Scheduler $scheduler
1004
+ */
1005
+ public function register_events( $scheduler ) {
1006
+ $scheduler->schedule( ITSEC_Scheduler::S_DAILY, 'purge-lockouts' );
1007
+ }
1008
+
1009
  /**
1010
  * Purges lockouts more than 7 days old from the database
1011
  *
1161
 
1162
  $error_handler->add( $type, $message );
1163
 
1164
+ ITSEC_Lib::show_error_message( $error_handler );
 
1165
  } else {
1166
 
1167
  add_settings_error( 'itsec', esc_attr( 'settings_updated' ), $message, $type );
core/modules/404-detection/class-itsec-four-oh-four.php CHANGED
@@ -12,8 +12,7 @@ class ITSEC_Four_Oh_Four {
12
  add_filter( 'itsec_logger_modules', array( $this, 'register_logger' ) );
13
  add_filter( 'itsec_logger_displays', array( $this, 'register_logger_displays' ) );
14
 
15
- add_action( 'wp_head', array( $this, 'check_404' ) );
16
-
17
  }
18
 
19
  /**
12
  add_filter( 'itsec_logger_modules', array( $this, 'register_logger' ) );
13
  add_filter( 'itsec_logger_displays', array( $this, 'register_logger_displays' ) );
14
 
15
+ add_action( 'wp', array( $this, 'check_404' ), 9999 );
 
16
  }
17
 
18
  /**
core/modules/away-mode/activate.php ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once( dirname( __FILE__ ) . '/utilities.php' );
4
+
5
+ ITSEC_Away_Mode_Utilities::create_active_file();
core/modules/backup/activate.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ $settings = ITSEC_Modules::get_settings( 'backup' );
4
+
5
+ if ( $settings['enabled'] && $settings['interval'] > 0 ) {
6
+ ITSEC_Core::get_scheduler()->schedule( 'backup', 'backup' );
7
+ }
core/modules/backup/class-itsec-backup.php CHANGED
@@ -40,36 +40,27 @@ class ITSEC_Backup {
40
  add_filter( 'itsec_notifications', array( $this, 'register_notification' ) );
41
  add_filter( 'itsec_backup_notification_strings', array( $this, 'notification_strings' ) );
42
 
43
- if ( defined( 'ITSEC_BACKUP_CRON' ) && true === ITSEC_BACKUP_CRON ) {
44
- if ( ! wp_next_scheduled( 'itsec_execute_backup_cron' ) ) {
45
- wp_schedule_event( time(), 'daily', 'itsec_execute_backup_cron' );
46
- }
47
-
48
- // When ITSEC_BACKUP_CRON is enabled, skip the regular scheduling system.
49
  return;
50
  }
51
 
 
 
 
52
  if ( ! $this->settings['enabled'] || $this->settings['interval'] <= 0 ) {
53
  // Don't run when scheduled backups aren't enabled or the interval is zero or less.
54
  return;
55
  }
56
 
57
- if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
58
- // Don't run on AJAX requests.
59
- return;
60
- }
61
-
62
- if ( class_exists( 'pb_backupbuddy' ) ) {
63
- // Don't run when BackupBuddy is active.
64
- return;
65
- }
66
-
67
-
68
- $next_run = $this->settings['last_run'] + $this->settings['interval'] * DAY_IN_SECONDS;
69
 
70
- if ( $next_run <= ITSEC_Core::get_current_time_gmt() ) {
71
- add_action( 'init', array( $this, 'do_backup' ), 10, 0 );
72
- }
 
 
73
  }
74
 
75
  /**
@@ -329,6 +320,20 @@ class ITSEC_Backup {
329
 
330
  }
331
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
  /**
333
  * Register the Backup notification email.
334
  *
@@ -340,7 +345,7 @@ class ITSEC_Backup {
340
 
341
  $method = ITSEC_Modules::get_setting( 'backup', 'method' );
342
 
343
- if ( 0 === $method || 1 === $method ) {
344
  $notifications['backup'] = array(
345
  'subject_editable' => true,
346
  'recipient' => ITSEC_Notification_Center::R_EMAIL_LIST,
40
  add_filter( 'itsec_notifications', array( $this, 'register_notification' ) );
41
  add_filter( 'itsec_backup_notification_strings', array( $this, 'notification_strings' ) );
42
 
43
+ if ( class_exists( 'pb_backupbuddy' ) ) {
44
+ // Don't run when BackupBuddy is active.
 
 
 
 
45
  return;
46
  }
47
 
48
+ ITSEC_Core::get_scheduler()->register_custom_schedule( 'backup', DAY_IN_SECONDS * $this->settings['interval'] );
49
+ add_action( 'itsec_scheduler_register_events', array( $this, 'register_events' ) );
50
+
51
  if ( ! $this->settings['enabled'] || $this->settings['interval'] <= 0 ) {
52
  // Don't run when scheduled backups aren't enabled or the interval is zero or less.
53
  return;
54
  }
55
 
56
+ add_action( 'itsec_scheduled_backup', array( $this, 'scheduled_callback' ) );
57
+ }
 
 
 
 
 
 
 
 
 
 
58
 
59
+ /**
60
+ * Called when it is time for the backup to run.
61
+ */
62
+ public function scheduled_callback() {
63
+ $this->do_backup();
64
  }
65
 
66
  /**
320
 
321
  }
322
 
323
+ /**
324
+ * Register the events.
325
+ *
326
+ * @param ITSEC_Scheduler $scheduler
327
+ */
328
+ public function register_events( $scheduler ) {
329
+
330
+ $settings = ITSEC_Modules::get_settings( 'backup' );
331
+
332
+ if ( $settings['enabled'] && $settings['interval'] > 0 ) {
333
+ $scheduler->schedule( 'backup', 'backup' );
334
+ }
335
+ }
336
+
337
  /**
338
  * Register the Backup notification email.
339
  *
345
 
346
  $method = ITSEC_Modules::get_setting( 'backup', 'method' );
347
 
348
+ if ( 0 === $method || 1 === $method ) {
349
  $notifications['backup'] = array(
350
  'subject_editable' => true,
351
  'recipient' => ITSEC_Notification_Center::R_EMAIL_LIST,
core/modules/backup/deactivate.php ADDED
@@ -0,0 +1,2 @@
 
 
1
+ <?php
2
+ ITSEC_Core::get_scheduler()->unschedule( 'backup' );
core/modules/backup/settings-page.php CHANGED
@@ -142,16 +142,14 @@ final class ITSEC_Backup_Settings_Page extends ITSEC_Module_Settings_Page {
142
  <label for="itsec-backup-enabled"><?php _e( 'Enable Scheduled Database Backups', 'better-wp-security' ); ?></label>
143
  </td>
144
  </tr>
145
- <?php if ( ! defined( 'ITSEC_BACKUP_CRON' ) || ! ITSEC_BACKUP_CRON ) : ?>
146
- <tr class="itsec-backup-enabled-content">
147
- <th scope="row"><label for="itsec-backup-interval"><?php _e( 'Backup Interval', 'better-wp-security' ); ?></label></th>
148
- <td>
149
- <?php $form->add_text( 'interval', array( 'class' => 'small-text' ) ); ?>
150
- <label for="itsec-backup-interval"><?php _e( 'Days', 'better-wp-security' ); ?></label>
151
- <p class="description"><?php _e( 'The number of days between database backups.', 'better-wp-security' ); ?></p>
152
- </td>
153
- </tr>
154
- <?php endif; ?>
155
  </table>
156
  <?php
157
 
142
  <label for="itsec-backup-enabled"><?php _e( 'Enable Scheduled Database Backups', 'better-wp-security' ); ?></label>
143
  </td>
144
  </tr>
145
+ <tr class="itsec-backup-enabled-content">
146
+ <th scope="row"><label for="itsec-backup-interval"><?php _e( 'Backup Interval', 'better-wp-security' ); ?></label></th>
147
+ <td>
148
+ <?php $form->add_text( 'interval', array( 'class' => 'small-text' ) ); ?>
149
+ <label for="itsec-backup-interval"><?php _e( 'Days', 'better-wp-security' ); ?></label>
150
+ <p class="description"><?php _e( 'The number of days between database backups.', 'better-wp-security' ); ?></p>
151
+ </td>
152
+ </tr>
 
 
153
  </table>
154
  <?php
155
 
core/modules/backup/settings.php CHANGED
@@ -22,6 +22,17 @@ final class ITSEC_Backup_Settings extends ITSEC_Settings {
22
  'last_run' => 0,
23
  );
24
  }
 
 
 
 
 
 
 
 
 
 
 
25
  }
26
 
27
  ITSEC_Modules::register_settings( new ITSEC_Backup_Settings() );
22
  'last_run' => 0,
23
  );
24
  }
25
+
26
+ protected function handle_settings_changes( $old_settings ) {
27
+
28
+ if ( $old_settings['enabled'] !== $this->settings['enabled'] ) {
29
+ if ( $this->settings['enabled'] ) {
30
+ ITSEC_Core::get_scheduler()->schedule( 'backup', 'backup' );
31
+ } else {
32
+ ITSEC_Core::get_scheduler()->unschedule( 'backup' );
33
+ }
34
+ }
35
+ }
36
  }
37
 
38
  ITSEC_Modules::register_settings( new ITSEC_Backup_Settings() );
core/modules/backup/setup.php CHANGED
@@ -107,6 +107,10 @@ if ( ! class_exists( 'ITSEC_Backup_Setup' ) ) {
107
  if ( $build < 4069 ) {
108
  delete_site_option( 'itsec_backup' );
109
  }
 
 
 
 
110
  }
111
 
112
  }
107
  if ( $build < 4069 ) {
108
  delete_site_option( 'itsec_backup' );
109
  }
110
+
111
+ if ( $build < 4079 ) {
112
+ wp_clear_scheduled_hook( 'itsec_execute_backup_cron' );
113
+ }
114
  }
115
 
116
  }
core/modules/file-change/activate.php ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <?php
2
+
3
+ $split = ITSEC_Modules::get_setting( 'file-change', 'split', false );
4
+ $interval = $split ? ITSEC_Scheduler::S_FOUR_DAILY : ITSEC_Scheduler::S_DAILY;
5
+
6
+ ITSEC_Core::get_scheduler()->schedule( $interval, 'file-change' );
core/modules/file-change/class-itsec-file-change.php CHANGED
@@ -24,14 +24,6 @@ class ITSEC_File_Change {
24
  */
25
  function run() {
26
 
27
- $settings = ITSEC_Modules::get_settings( 'file-change' );
28
- $interval = 86400; //Run daily
29
-
30
- // If we're splitting the file check run it every 6 hours.
31
- if ( isset( $settings['split'] ) && true === $settings['split'] ) {
32
- $interval = 12342;
33
- }
34
-
35
  add_action( 'itsec_execute_file_check_cron', array( $this, 'run_scan' ) ); //Action to execute during a cron run.
36
 
37
  add_filter( 'itsec_logger_displays', array( $this, 'itsec_logger_displays' ) ); //adds logs metaboxes
@@ -40,23 +32,8 @@ class ITSEC_File_Change {
40
  add_filter( 'itsec_notifications', array( $this, 'register_notification' ) );
41
  add_filter( 'itsec_file-change_notification_strings', array( $this, 'register_notification_strings' ) );
42
 
43
-
44
- if (
45
- ( ! defined( 'DOING_AJAX' ) || DOING_AJAX === false ) &&
46
- isset( $settings['last_run'] ) &&
47
- ( ITSEC_Core::get_current_time() - $interval ) > $settings['last_run'] &&
48
- ( ! defined( 'ITSEC_FILE_CHECK_CRON' ) || false === ITSEC_FILE_CHECK_CRON )
49
- ) {
50
-
51
- wp_clear_scheduled_hook( 'itsec_file_check' );
52
- add_action( 'init', array( $this, 'run_scan' ) );
53
-
54
- } elseif ( defined( 'ITSEC_FILE_CHECK_CRON' ) && true === ITSEC_FILE_CHECK_CRON && ! wp_next_scheduled( 'itsec_execute_file_check_cron' ) ) { //Use cron if needed
55
-
56
- wp_schedule_event( time(), 'daily', 'itsec_execute_file_check_cron' );
57
-
58
- }
59
-
60
  }
61
 
62
  public function run_scan() {
@@ -65,6 +42,20 @@ class ITSEC_File_Change {
65
  return ITSEC_File_Change_Scanner::run_scan();
66
  }
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  /**
69
  * Register file change detection for logger
70
  *
24
  */
25
  function run() {
26
 
 
 
 
 
 
 
 
 
27
  add_action( 'itsec_execute_file_check_cron', array( $this, 'run_scan' ) ); //Action to execute during a cron run.
28
 
29
  add_filter( 'itsec_logger_displays', array( $this, 'itsec_logger_displays' ) ); //adds logs metaboxes
32
  add_filter( 'itsec_notifications', array( $this, 'register_notification' ) );
33
  add_filter( 'itsec_file-change_notification_strings', array( $this, 'register_notification_strings' ) );
34
 
35
+ add_action( 'itsec_scheduler_register_events', array( $this, 'register_event' ) );
36
+ add_action( 'itsec_scheduled_file-change', array( $this, 'run_scan' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  }
38
 
39
  public function run_scan() {
42
  return ITSEC_File_Change_Scanner::run_scan();
43
  }
44
 
45
+ /**
46
+ * Register the file change scan event.
47
+ *
48
+ * @param ITSEC_Scheduler $scheduler
49
+ */
50
+ public function register_event( $scheduler ) {
51
+
52
+ // If we're splitting the file check run it every 6 hours.
53
+ $split = ITSEC_Modules::get_setting( 'file-change', 'split', false );
54
+ $interval = $split ? ITSEC_Scheduler::S_FOUR_DAILY : ITSEC_Scheduler::S_DAILY;
55
+
56
+ $scheduler->schedule( $interval, 'file-change' );
57
+ }
58
+
59
  /**
60
  * Register file change detection for logger
61
  *
core/modules/file-change/deactivate.php ADDED
@@ -0,0 +1,2 @@
 
 
1
+ <?php
2
+ ITSEC_Core::get_scheduler()->unschedule( 'file-change' );
core/modules/file-change/settings.php CHANGED
@@ -25,6 +25,16 @@ final class ITSEC_File_Change_Settings extends ITSEC_Settings {
25
  'latest_changes' => array(),
26
  );
27
  }
 
 
 
 
 
 
 
 
 
 
28
  }
29
 
30
  ITSEC_Modules::register_settings( new ITSEC_File_Change_Settings() );
25
  'latest_changes' => array(),
26
  );
27
  }
28
+
29
+ protected function handle_settings_changes( $old_settings ) {
30
+ $split = isset( $old_settings['split'] ) ? $old_settings['split'] : false;
31
+
32
+ if ( $split !== $this->settings['split'] ) {
33
+ ITSEC_Core::get_scheduler()->unschedule( 'file-change' );
34
+ $interval = $this->settings['split'] ? ITSEC_Scheduler::S_FOUR_DAILY : ITSEC_Scheduler::S_DAILY;
35
+ ITSEC_Core::get_scheduler()->schedule( $interval, 'file-change' );
36
+ }
37
+ }
38
  }
39
 
40
  ITSEC_Modules::register_settings( new ITSEC_File_Change_Settings() );
core/modules/file-change/setup.php CHANGED
@@ -149,6 +149,10 @@ if ( ! class_exists( 'ITSEC_File_Change_Setup' ) ) {
149
  }
150
  }
151
 
 
 
 
 
152
  }
153
 
154
  }
149
  }
150
  }
151
 
152
+ if ( $itsec_old_version < 4079 ) {
153
+ wp_clear_scheduled_hook( 'itsec_execute_file_check_cron' );
154
+ }
155
+
156
  }
157
 
158
  }
core/modules/file-change/validator.php CHANGED
@@ -18,7 +18,8 @@ class ITSEC_File_Change_Validator extends ITSEC_Validator {
18
  $this->settings['show_warning'] = $previous_settings['show_warning'];
19
  }
20
 
21
- $this->set_previous_if_empty( array( 'latest_changes', 'email' ) );
 
22
  $this->vars_to_skip_validate_matching_types[] = 'last_chunk';
23
  $this->vars_to_skip_validate_matching_fields[] = 'email';
24
 
18
  $this->settings['show_warning'] = $previous_settings['show_warning'];
19
  }
20
 
21
+ $this->set_previous_if_empty( array( 'latest_changes' ) );
22
+ $this->preserve_setting_if_exists( array( 'email' ) );
23
  $this->vars_to_skip_validate_matching_types[] = 'last_chunk';
24
  $this->vars_to_skip_validate_matching_fields[] = 'email';
25
 
core/modules/global/active.php CHANGED
@@ -11,6 +11,10 @@ function itsec_global_add_notice() {
11
  ITSEC_Core::add_notice( 'itsec_global_show_new_dashboard_notice' );
12
  }
13
 
 
 
 
 
14
  if ( ITSEC_Core::is_temp_disable_modules_set() && ITSEC_Core::current_user_can_manage() ) {
15
  ITSEC_Core::add_notice( 'itsec_show_temp_disable_modules_notice', true );
16
  }
@@ -63,3 +67,100 @@ add_action( 'wp_ajax_itsec-dismiss-notice-brute_force_network', 'itsec_network_b
63
  function itsec_show_temp_disable_modules_notice() {
64
  ITSEC_Lib::show_error_message( esc_html__( 'The ITSEC_DISABLE_MODULES define is set. All iThemes Security protections are disabled. Please make the necessary settings changes and remove the define as quickly as possible.', 'better-wp-security' ) );
65
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  ITSEC_Core::add_notice( 'itsec_global_show_new_dashboard_notice' );
12
  }
13
 
14
+ if ( ! defined( 'ITSEC_USE_CRON' ) && ITSEC_Core::current_user_can_manage() ) {
15
+ ITSEC_Core::add_notice( 'itsec_show_disable_cron_constants_notice' );
16
+ }
17
+
18
  if ( ITSEC_Core::is_temp_disable_modules_set() && ITSEC_Core::current_user_can_manage() ) {
19
  ITSEC_Core::add_notice( 'itsec_show_temp_disable_modules_notice', true );
20
  }
67
  function itsec_show_temp_disable_modules_notice() {
68
  ITSEC_Lib::show_error_message( esc_html__( 'The ITSEC_DISABLE_MODULES define is set. All iThemes Security protections are disabled. Please make the necessary settings changes and remove the define as quickly as possible.', 'better-wp-security' ) );
69
  }
70
+
71
+ function itsec_show_disable_cron_constants_notice() {
72
+
73
+ $check = array( 'ITSEC_BACKUP_CRON', 'ITSEC_FILE_CHECK_CRON' );
74
+ $using = array();
75
+
76
+ foreach ( $check as $constant ) {
77
+ if ( defined( $constant ) && constant( $constant ) ) {
78
+ $using[] = "<span class='code'>{$constant}</span>";
79
+ }
80
+ }
81
+
82
+ if ( $using ) {
83
+ $message = wp_sprintf( esc_html(
84
+ _n( 'The %l define is deprecated. Please use %s instead.', 'The %l defines are deprecated. Please use %s instead.', count( $using ), 'better-wp-security' )
85
+ ), $using, '<span class="code">ITSEC_USE_CRON</span>' );
86
+
87
+ echo "<div class='notice notice-error'><p>{$message}</p></div>";
88
+ }
89
+ }
90
+
91
+ /**
92
+ * On every page load, check if the cron test has successfully fired in time.
93
+ *
94
+ * If not, update the cron status and turn off using cron.
95
+ */
96
+ function itsec_cron_test_fail_safe() {
97
+
98
+ if ( defined( 'ITSEC_DISABLE_CRON_TEST' ) && ITSEC_DISABLE_CRON_TEST ) {
99
+ return;
100
+ }
101
+
102
+ $time = ITSEC_Modules::get_setting( 'global', 'cron_test_time' );
103
+
104
+ if ( ! $time ) {
105
+ if ( ITSEC_Lib::get_lock( 'cron_test_fail_safe' ) ) {
106
+ ITSEC_Lib::schedule_cron_test();
107
+ ITSEC_Lib::release_lock( 'cron_test_fail_safe' );
108
+ }
109
+
110
+ return;
111
+ }
112
+
113
+ $threshold = HOUR_IN_SECONDS + DAY_IN_SECONDS;
114
+
115
+ if ( ITSEC_Core::get_current_time_gmt() <= $time + $threshold + 5 * MINUTE_IN_SECONDS ) {
116
+ return;
117
+ }
118
+
119
+ if ( ! ITSEC_Lib::get_lock( 'cron_test_fail_safe' ) ) {
120
+ return;
121
+ }
122
+
123
+ $uncached = ITSEC_Lib::get_uncached_option( 'itsec-storage' );
124
+ $time = $uncached['global']['cron_test_time'];
125
+
126
+ if ( ITSEC_Core::get_current_time_gmt() > $time + $threshold + 5 * MINUTE_IN_SECONDS ) {
127
+ if ( ( ! defined( 'ITSEC_USE_CRON' ) || ! ITSEC_USE_CRON ) && ITSEC_Lib::use_cron() ) {
128
+ ITSEC_Modules::set_setting( 'global', 'use_cron', false );
129
+ }
130
+
131
+ ITSEC_Modules::set_setting( 'global', 'cron_status', 0 );
132
+ }
133
+
134
+ ITSEC_Lib::schedule_cron_test();
135
+ ITSEC_Lib::release_lock( 'cron_test_fail_safe' );
136
+ }
137
+
138
+ add_action( 'init', 'itsec_cron_test_fail_safe' );
139
+
140
+ /**
141
+ * Callback for testing whether we should suggest the cron scheduler be enabled.
142
+ *
143
+ * @param int $time
144
+ */
145
+ function itsec_cron_test_callback( $time ) {
146
+
147
+ $threshold = HOUR_IN_SECONDS + DAY_IN_SECONDS;
148
+
149
+ if ( empty( $time ) || ITSEC_Core::get_current_time_gmt() > $time + $threshold ) {
150
+ // Disable cron if the user hasn't set the use cron constant to true.
151
+ if ( ( ! defined( 'ITSEC_USE_CRON' ) || ! ITSEC_USE_CRON ) && ITSEC_Lib::use_cron() ) {
152
+ ITSEC_Modules::set_setting( 'global', 'use_cron', false );
153
+ }
154
+
155
+ ITSEC_Modules::set_setting( 'global', 'cron_status', 0 );
156
+ } elseif ( ! ITSEC_Lib::use_cron() ) {
157
+ ITSEC_Modules::set_setting( 'global', 'cron_status', 1 );
158
+ ITSEC_Modules::set_setting( 'global', 'use_cron', true );
159
+ } else {
160
+ ITSEC_Modules::set_setting( 'global', 'cron_status', 1 );
161
+ }
162
+
163
+ ITSEC_Lib::schedule_cron_test();
164
+ }
165
+
166
+ add_action( 'itsec_cron_test', 'itsec_cron_test_callback' );
core/modules/global/settings.php CHANGED
@@ -23,7 +23,7 @@ final class ITSEC_Global_Settings_New extends ITSEC_Settings {
23
  'write_files' => true,
24
  'nginx_file' => ABSPATH . 'nginx.conf',
25
  'infinitewp_compatibility' => false,
26
- 'did_upgrade' => false,
27
  'lock_file' => false,
28
  'proxy_override' => false,
29
  'hide_admin_bar' => false,
@@ -32,6 +32,9 @@ final class ITSEC_Global_Settings_New extends ITSEC_Settings {
32
  'show_security_check' => true,
33
  'build' => 0,
34
  'activation_timestamp' => 0,
 
 
 
35
  );
36
  }
37
 
@@ -40,6 +43,44 @@ final class ITSEC_Global_Settings_New extends ITSEC_Settings {
40
  ITSEC_Response::regenerate_server_config();
41
  ITSEC_Response::regenerate_wp_config();
42
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  }
44
  }
45
 
23
  'write_files' => true,
24
  'nginx_file' => ABSPATH . 'nginx.conf',
25
  'infinitewp_compatibility' => false,
26
+ 'did_upgrade' => false,
27
  'lock_file' => false,
28
  'proxy_override' => false,
29
  'hide_admin_bar' => false,
32
  'show_security_check' => true,
33
  'build' => 0,
34
  'activation_timestamp' => 0,
35
+ 'cron_status' => - 1,
36
+ 'use_cron' => true,
37
+ 'cron_test_time' => 0,
38
  );
39
  }
40
 
43
  ITSEC_Response::regenerate_server_config();
44
  ITSEC_Response::regenerate_wp_config();
45
  }
46
+
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 ) {
53
+ $class = $new_use_cron ? 'ITSEC_Scheduler_Cron' : 'ITSEC_Scheduler_Page_Load';
54
+ $this->handle_scheduler_change( $class );
55
+ }
56
+
57
+ private function handle_scheduler_change( $new_class ) {
58
+ $choices = array(
59
+ 'ITSEC_Scheduler_Cron' => ITSEC_Core::get_core_dir() . 'lib/class-itsec-scheduler-cron.php',
60
+ 'ITSEC_Scheduler_Page_Load' => ITSEC_Core::get_core_dir() . 'lib/class-itsec-scheduler-page-load.php',
61
+ );
62
+
63
+ require_once( $choices[ $new_class ] );
64
+
65
+ /** @var ITSEC_Scheduler $new */
66
+ $new = new $new_class();
67
+ $current = ITSEC_Core::get_scheduler();
68
+
69
+ $new->uninstall();
70
+
71
+ foreach ( $current->get_recurring_events() as $event ) {
72
+ $new->schedule( $event['schedule'], $event['id'], $event['data'], array(
73
+ 'fire_at' => $event['fire_at'],
74
+ ) );
75
+ }
76
+
77
+ foreach ( $current->get_single_events() as $event ) {
78
+ $new->schedule_once( $event['fire_at'], $event['id'], $event['data'] );
79
+ }
80
+
81
+ $new->run();
82
+ ITSEC_Core::set_scheduler( $new );
83
+ $current->uninstall();
84
  }
85
  }
86
 
core/modules/global/validator.php CHANGED
@@ -20,8 +20,9 @@ class ITSEC_Global_Validator extends ITSEC_Validator {
20
 
21
 
22
  $this->vars_to_skip_validate_matching_fields = array( 'digest_last_sent', 'digest_messages', 'digest_email', 'email_notifications', 'notification_email', 'backup_email' );
23
- $this->set_previous_if_empty( array( 'did_upgrade', 'log_info', 'show_new_dashboard_notice', 'show_security_check', 'digest_last_sent', 'digest_messages', 'build', 'activation_timestamp', 'lock_file', 'digest_email', 'email_notifications', 'notification_email', 'backup_email' ) );
24
  $this->set_default_if_empty( array( 'log_location', 'nginx_file' ) );
 
25
 
26
 
27
  $this->sanitize_setting( 'bool', 'write_files', __( 'Write to Files', 'better-wp-security' ) );
20
 
21
 
22
  $this->vars_to_skip_validate_matching_fields = array( 'digest_last_sent', 'digest_messages', 'digest_email', 'email_notifications', 'notification_email', 'backup_email' );
23
+ $this->set_previous_if_empty( array( 'did_upgrade', 'log_info', 'show_new_dashboard_notice', 'show_security_check', '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
 
28
  $this->sanitize_setting( 'bool', 'write_files', __( 'Write to Files', 'better-wp-security' ) );
core/modules/hide-backend/settings.php CHANGED
@@ -15,6 +15,48 @@ final class ITSEC_Hide_Backend_Settings extends ITSEC_Settings {
15
  'post_logout_slug' => '',
16
  );
17
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  }
19
 
20
  ITSEC_Modules::register_settings( new ITSEC_Hide_Backend_Settings() );
15
  'post_logout_slug' => '',
16
  );
17
  }
18
+
19
+ protected function handle_settings_changes( $old_settings ) {
20
+
21
+ if ( $this->settings['enabled'] && $this->settings['slug'] !== $old_settings['slug'] ) {
22
+ $url = get_site_url() . '/' . $this->settings['slug'];
23
+ } elseif ( $this->settings['enabled'] && ! $old_settings['enabled'] ) {
24
+ $url = get_site_url() . '/' . $this->settings['slug'];
25
+ } elseif ( ! $this->settings['enabled'] && $old_settings['enabled'] ) {
26
+ $url = get_site_url() . '/wp-login.php';
27
+ } else {
28
+ return;
29
+ }
30
+
31
+ $this->send_new_login_url( $url );
32
+ }
33
+
34
+ private function send_new_login_url( $url ) {
35
+ if ( ITSEC_Core::doing_data_upgrade() ) {
36
+ // Do not send emails when upgrading data. This prevents spamming users with notifications just because the
37
+ // data was ported from an old version to a new version.
38
+ return;
39
+ }
40
+
41
+ $nc = ITSEC_Core::get_notification_center();
42
+ $nc->clear_notifications_cache();
43
+
44
+ $mail = $nc->mail();
45
+
46
+ $mail->add_header( esc_html__( 'New Login URL', 'better-wp-security' ), esc_html__( 'New Login URL', 'better-wp-security' ) );
47
+ $mail->add_text( ITSEC_Lib::replace_tags( $nc->get_message( 'hide-backend' ), array(
48
+ 'login_url' => '<code>' . esc_url( $url ) . '</code>',
49
+ 'site_title' => get_bloginfo( 'name', 'display' ),
50
+ 'site_url' => $mail->get_display_url(),
51
+ ) ) );
52
+ $mail->add_button( esc_html__( 'Login Now', 'better-wp-security' ), $url );
53
+ $mail->add_footer();
54
+
55
+ $subject = $mail->prepend_site_url_to_subject( $nc->get_subject( 'hide-backend' ) );
56
+ $subject = apply_filters( 'itsec_hide_backend_email_subject', $subject );
57
+ $mail->set_subject( $subject, false );
58
+ $nc->send( 'hide-backend', $mail );
59
+ }
60
  }
61
 
62
  ITSEC_Modules::register_settings( new ITSEC_Hide_Backend_Settings() );
core/modules/hide-backend/validator.php CHANGED
@@ -50,7 +50,6 @@ final class ITSEC_Hide_Backend_Validator extends ITSEC_Validator {
50
  $url = get_site_url() . '/' . $this->settings['slug'];
51
  ITSEC_Response::add_message( sprintf( __( 'The Hide Backend feature is now active. Your new login URL is <strong><code>%1$s</code></strong>. Please note this may be different than what you sent as the URL was sanitized to meet various requirements. A reminder has also been sent to the notification email addresses set in iThemes Security\'s Notification Center.', 'better-wp-security' ), esc_url( $url ) ) );
52
  } else if ( $this->settings['enabled'] && ! $this->previous_settings['enabled'] ) {
53
- ITSEC_Core::get_notification_center()->clear_notifications_cache();
54
  $url = get_site_url() . '/' . $this->settings['slug'];
55
  ITSEC_Response::add_message( sprintf( __( 'The Hide Backend feature is now active. Your new login URL is <strong><code>%1$s</code></strong>. A reminder has also been sent to the notification email addresses set in iThemes Security\'s Notification Center.', 'better-wp-security' ), esc_url( $url ) ) );
56
  } else if ( ! $this->settings['enabled'] && $this->previous_settings['enabled'] ) {
@@ -59,7 +58,6 @@ final class ITSEC_Hide_Backend_Validator extends ITSEC_Validator {
59
  }
60
 
61
  if ( isset( $url ) ) {
62
- $this->send_new_login_url( $url );
63
  ITSEC_Response::prevent_modal_close();
64
  }
65
 
@@ -67,31 +65,6 @@ final class ITSEC_Hide_Backend_Validator extends ITSEC_Validator {
67
  ITSEC_Response::reload_module( $this->get_id() );
68
  }
69
 
70
- private function send_new_login_url( $url ) {
71
- if ( ITSEC_Core::doing_data_upgrade() ) {
72
- // Do not send emails when upgrading data. This prevents spamming users with notifications just because the
73
- // data was ported from an old version to a new version.
74
- return;
75
- }
76
-
77
- $nc = ITSEC_Core::get_notification_center();
78
- $mail = $nc->mail();
79
-
80
- $mail->add_header( esc_html__( 'New Login URL', 'better-wp-security' ), esc_html__( 'New Login URL', 'better-wp-security' ) );
81
- $mail->add_text( ITSEC_Lib::replace_tags( $nc->get_message( 'hide-backend' ), array(
82
- 'login_url' => '<code>' . esc_url( $url ) . '</code>',
83
- 'site_title' => get_bloginfo( 'name', 'display' ),
84
- 'site_url' => $mail->get_display_url(),
85
- ) ) );
86
- $mail->add_button( esc_html__( 'Login Now', 'better-wp-security' ), $url );
87
- $mail->add_footer();
88
-
89
- $subject = $mail->prepend_site_url_to_subject( $nc->get_subject( 'hide-backend' ) );
90
- $subject = apply_filters( 'itsec_hide_backend_email_subject', $subject );
91
- $mail->set_subject( $subject, false );
92
- $nc->send( 'hide-backend', $mail );
93
- }
94
-
95
  /**
96
  * Set HTML content type for email
97
  *
50
  $url = get_site_url() . '/' . $this->settings['slug'];
51
  ITSEC_Response::add_message( sprintf( __( 'The Hide Backend feature is now active. Your new login URL is <strong><code>%1$s</code></strong>. Please note this may be different than what you sent as the URL was sanitized to meet various requirements. A reminder has also been sent to the notification email addresses set in iThemes Security\'s Notification Center.', 'better-wp-security' ), esc_url( $url ) ) );
52
  } else if ( $this->settings['enabled'] && ! $this->previous_settings['enabled'] ) {
 
53
  $url = get_site_url() . '/' . $this->settings['slug'];
54
  ITSEC_Response::add_message( sprintf( __( 'The Hide Backend feature is now active. Your new login URL is <strong><code>%1$s</code></strong>. A reminder has also been sent to the notification email addresses set in iThemes Security\'s Notification Center.', 'better-wp-security' ), esc_url( $url ) ) );
55
  } else if ( ! $this->settings['enabled'] && $this->previous_settings['enabled'] ) {
58
  }
59
 
60
  if ( isset( $url ) ) {
 
61
  ITSEC_Response::prevent_modal_close();
62
  }
63
 
65
  ITSEC_Response::reload_module( $this->get_id() );
66
  }
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  /**
69
  * Set HTML content type for email
70
  *
core/modules/notification-center/settings-page.php CHANGED
@@ -99,12 +99,12 @@ class ITSEC_Notification_Center_Settings_Page extends ITSEC_Module_Settings_Page
99
  <div class="itsec-notification-center-mail-errors-container">
100
  <?php foreach ( $errors as $id => $error ):
101
  $strings = ITSEC_Core::get_notification_center()->get_notification_strings( $error['notification'] );
102
- $error = $error['error'];
103
 
104
- if ( is_wp_error( $error ) ) {
105
- $message = $error->get_error_message();
106
- } elseif ( is_array( $error ) && isset( $error['message'] ) && is_string( $error['message'] ) ) {
107
- $message = $error['message'];
108
  } else {
109
  $message = __( 'Unknown error encountered while sending.', 'better-wp-security' );
110
  }
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
  }
core/modules/notification-center/validator.php CHANGED
@@ -30,8 +30,7 @@ class ITSEC_Notification_Center_Validator extends ITSEC_Validator {
30
  $config = ITSEC_Core::get_notification_center()->get_notification( $notification );
31
 
32
  if ( ! $config ) {
33
- $notifications[ $notification ] = array();
34
- break;
35
  }
36
 
37
  $strings = ITSEC_Core::get_notification_center()->get_notification_strings( $notification );
30
  $config = ITSEC_Core::get_notification_center()->get_notification( $notification );
31
 
32
  if ( ! $config ) {
33
+ continue;
 
34
  }
35
 
36
  $strings = ITSEC_Core::get_notification_center()->get_notification_strings( $notification );
core/modules/salts/utilities.php CHANGED
@@ -1,7 +1,7 @@
1
  <?php
2
 
3
  final class ITSEC_WordPress_Salts_Utilities {
4
- public function generate_new_salts() {
5
  if ( ! ITSEC_Modules::get_setting( 'global', 'write_files' ) ) {
6
  return new WP_Error( 'itsec-wordpress-salts-utilities-write-files-disabled', __( 'The "Write to Files" setting is disabled in Global Settings. In order to use this feature, you must enable the "Write to Files" setting.', 'better-wp-security' ) );
7
  }
@@ -38,12 +38,12 @@ final class ITSEC_WordPress_Salts_Utilities {
38
  if ( empty( $salt ) ) {
39
  $salt = wp_generate_password( 64, true, true );
40
  }
41
-
42
  $salt = str_replace( '$', '\\$', $salt );
43
  $regex = "/(define\s*\(\s*(['\"])$define\\2\s*,\s*)(['\"]).+?\\3(\s*\)\s*;)/";
44
  $config = preg_replace( $regex, "\${1}'$salt'\${4}", $config );
45
  }
46
-
47
  $write_result = ITSEC_Lib_File::write( $config_file_path, $config );
48
 
49
  if ( is_wp_error( $write_result ) ) {
1
  <?php
2
 
3
  final class ITSEC_WordPress_Salts_Utilities {
4
+ public static function generate_new_salts() {
5
  if ( ! ITSEC_Modules::get_setting( 'global', 'write_files' ) ) {
6
  return new WP_Error( 'itsec-wordpress-salts-utilities-write-files-disabled', __( 'The "Write to Files" setting is disabled in Global Settings. In order to use this feature, you must enable the "Write to Files" setting.', 'better-wp-security' ) );
7
  }
38
  if ( empty( $salt ) ) {
39
  $salt = wp_generate_password( 64, true, true );
40
  }
41
+
42
  $salt = str_replace( '$', '\\$', $salt );
43
  $regex = "/(define\s*\(\s*(['\"])$define\\2\s*,\s*)(['\"]).+?\\3(\s*\)\s*;)/";
44
  $config = preg_replace( $regex, "\${1}'$salt'\${4}", $config );
45
  }
46
+
47
  $write_result = ITSEC_Lib_File::write( $config_file_path, $config );
48
 
49
  if ( is_wp_error( $write_result ) ) {
core/setup.php CHANGED
@@ -9,6 +9,7 @@
9
  final class ITSEC_Setup {
10
  public static function handle_activation() {
11
  self::setup_plugin_data();
 
12
  }
13
 
14
  public static function handle_deactivation() {
@@ -90,8 +91,9 @@ final class ITSEC_Setup {
90
  $itsec_modules = ITSEC_Modules::get_instance();
91
  $itsec_modules->run_activation();
92
 
93
-
94
- if ( ! empty( $build ) ) {
 
95
  // Existing install. Perform data upgrades.
96
 
97
  if ( $build < 4031 ) {
@@ -113,6 +115,34 @@ final class ITSEC_Setup {
113
  $itsec_files = ITSEC_Core::get_itsec_files();
114
  $itsec_files->do_activate();
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  // Update stored build number.
117
  ITSEC_Modules::set_setting( 'global', 'build', ITSEC_Core::get_plugin_build() );
118
  }
@@ -125,6 +155,8 @@ final class ITSEC_Setup {
125
  $itsec_files = ITSEC_Core::get_itsec_files();
126
  $itsec_files->do_deactivate();
127
 
 
 
128
  delete_site_option( 'itsec_temp_whitelist_ip' );
129
  delete_site_transient( 'itsec_notification_running' );
130
  delete_site_transient( 'itsec_wp_upload_dir' );
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() {
91
  $itsec_modules = ITSEC_Modules::get_instance();
92
  $itsec_modules->run_activation();
93
 
94
+ if ( empty( $build ) ) {
95
+ ITSEC_Lib::schedule_cron_test();
96
+ } else {
97
  // Existing install. Perform data upgrades.
98
 
99
  if ( $build < 4031 ) {
115
  $itsec_files = ITSEC_Core::get_itsec_files();
116
  $itsec_files->do_activate();
117
 
118
+ if ( $build < 4079 ) {
119
+ ITSEC_Core::get_scheduler()->register_events();
120
+
121
+ wp_clear_scheduled_hook( 'itsec_purge_lockouts' );
122
+ wp_clear_scheduled_hook( 'itsec_clear_locks' );
123
+
124
+ ITSEC_Lib::schedule_cron_test();
125
+ }
126
+
127
+ if ( $build < 4080 ) {
128
+ ITSEC_Core::get_scheduler()->uninstall();
129
+ ITSEC_Core::get_scheduler()->register_events();
130
+
131
+ $crons = _get_cron_array();
132
+
133
+ foreach ( $crons as $timestamp => $args ) {
134
+ unset( $crons[ $timestamp ]['itsec_cron_test'] );
135
+
136
+ if ( empty( $crons[ $timestamp ] ) ) {
137
+ unset( $crons[ $timestamp ] );
138
+ }
139
+ }
140
+
141
+ _set_cron_array( $crons );
142
+
143
+ ITSEC_Lib::schedule_cron_test();
144
+ }
145
+
146
  // Update stored build number.
147
  ITSEC_Modules::set_setting( 'global', 'build', ITSEC_Core::get_plugin_build() );
148
  }
155
  $itsec_files = ITSEC_Core::get_itsec_files();
156
  $itsec_files->do_deactivate();
157
 
158
+ ITSEC_Core::get_scheduler()->uninstall();
159
+
160
  delete_site_option( 'itsec_temp_whitelist_ip' );
161
  delete_site_transient( 'itsec_notification_running' );
162
  delete_site_transient( 'itsec_wp_upload_dir' );
history.txt CHANGED
@@ -696,3 +696,16 @@
696
  New Feature: Introduces the Notification Center, a centralized place to manage and customize email notifications sent by iThemes Security.
697
  Enhancement: Updated queries and prepare statements to account for changes to the esc_sql() function in WordPress 4.8.3.
698
  Bug Fix: Corrected some Javascript and CSS links not generating correctly on Windows servers.
 
 
 
 
 
 
 
 
 
 
 
 
 
696
  New Feature: Introduces the Notification Center, a centralized place to manage and customize email notifications sent by iThemes Security.
697
  Enhancement: Updated queries and prepare statements to account for changes to the esc_sql() function in WordPress 4.8.3.
698
  Bug Fix: Corrected some Javascript and CSS links not generating correctly on Windows servers.
699
+ 6.8.0 - 2018-01-10 - Chris Jean & Timothy Jacobs
700
+ New Feature: Introduces a scheduling framework for handling events. Cron is now used by default, and will switch to using an alternate scheduling system if it detects an error. To disable this detection set ITSEC_DISABLE_CRON_TEST in your wp-config.php file.
701
+ Important: The ITSEC_FILE_CHECK_CRON and ITSEC_BACKUP_CRON constants have been deprecated. Use ITSEC_USE_CRON instead.
702
+ Enhancement: Preserve notification settings when the responsible module is deactivated.
703
+ Bug Fix: Process 404 lockouts on the 'wp' hook to prevent a headers have already been sent warning message.
704
+ Bug Fix: Ensure Hide Backend emails are properly sent when activating Hide Backend before saving the Notification Center for the first time.
705
+ Bug Fix: Prevent warning from being issued on new installs by allowing previous settings to be preserved if they exist.
706
+ Bug Fix: Better handle WP_Error objects in mail errors that occurred before updating to first patch release.
707
+ Bug Fix: A non static method was being called statically.
708
+ Bug Fix: Fix occasional duplicate backups and file scans.
709
+ Bug Fix: Fixed issue where scheduled events could repeat on sites that do not properly support WordPress's cron system.
710
+ Bug Fix: Reactivating Away Mode now replaces the active file if you had previously removed it.
711
+ Bug Fix: Ensure lockouts take effect immediately, even on systems where changes to server configuration files do not take effect immediately.
readme.txt CHANGED
@@ -1,9 +1,9 @@
1
  === iThemes Security (formerly Better WP Security) ===
2
  Contributors: ithemes, chrisjean, gerroald, mattdanner, timothyblynjacobs
3
  Tags: security, security plugin, malware, hack, secure, block, SSL, admin, htaccess, lockdown, login, protect, protection, anti virus, attack, injection, login security, maintenance, permissions, prevention, authentication, administration, password, brute force, ban, permissions, bots, user agents, xml rpc, security log
4
- Requires at least: 4.6
5
- Tested up to: 4.9
6
- Stable tag: 6.7.0
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -188,6 +188,20 @@ Free support may be available with the help of the community in the <a href="htt
188
 
189
  == Changelog ==
190
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  = 6.7.0 =
192
  * New Feature: Introduces the Notification Center, a centralized place to manage and customize email notifications sent by iThemes Security.
193
  * Enhancement: Updated queries and prepare statements to account for changes to the esc_sql() function in WordPress 4.8.3.
@@ -377,5 +391,5 @@ Free support may be available with the help of the community in the <a href="htt
377
 
378
  == Upgrade Notice ==
379
 
380
- = 6.7.0 =
381
- Version 6.7.0 contains important bug fixes. It is recommended for all users.
1
  === iThemes Security (formerly Better WP Security) ===
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.1
6
+ Stable tag: 6.8.0
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
188
 
189
  == Changelog ==
190
 
191
+ = 6.8.0 =
192
+ * New Feature: Introduces a scheduling framework for handling events. Cron is now used by default, and will switch to using an alternate scheduling system if it detects an error. To disable this detection set ITSEC_DISABLE_CRON_TEST in your wp-config.php file.
193
+ * Important: The ITSEC_FILE_CHECK_CRON and ITSEC_BACKUP_CRON constants have been deprecated. Use ITSEC_USE_CRON instead.
194
+ * Enhancement: Preserve notification settings when the responsible module is deactivated.
195
+ * Bug Fix: Process 404 lockouts on the 'wp' hook to prevent a headers have already been sent warning message.
196
+ * Bug Fix: Ensure Hide Backend emails are properly sent when activating Hide Backend before saving the Notification Center for the first time.
197
+ * Bug Fix: Prevent warning from being issued on new installs by allowing previous settings to be preserved if they exist.
198
+ * Bug Fix: Better handle WP_Error objects in mail errors that occurred before updating to first patch release.
199
+ * Bug Fix: A non static method was being called statically.
200
+ * Bug Fix: Fix occasional duplicate backups and file scans.
201
+ * Bug Fix: Fixed issue where scheduled events could repeat on sites that do not properly support WordPress's cron system.
202
+ * Bug Fix: Reactivating Away Mode now replaces the active file if you had previously removed it.
203
+ * Bug Fix: Ensure lockouts take effect immediately, even on systems where changes to server configuration files do not take effect immediately.
204
+
205
  = 6.7.0 =
206
  * New Feature: Introduces the Notification Center, a centralized place to manage and customize email notifications sent by iThemes Security.
207
  * Enhancement: Updated queries and prepare statements to account for changes to the esc_sql() function in WordPress 4.8.3.
391
 
392
  == Upgrade Notice ==
393
 
394
+ = 6.8.0 =
395
+ Version 6.8.0 contains important bug fixes and improvements. It is recommended for all users.