iThemes Security (formerly Better WP Security) - Version 7.4.0

Version Description

  • New: iThemes Security Admin Notices are now conveniently located in the new Security Messages Menu. Check your notices in the Security menu on the WordPress Admin Bar.
  • Enhancement: Add Security Message when a Notification Center email fails to send.
  • Enhancement: Replace Trace IP with IP Tracker Online.
  • Tweak: Remove 'DELETE' method from "System Tweaks -> Filter Request Methods"
Download this release

Release Info

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

Code changes from version 7.3.3 to 7.4.0

Files changed (206) hide show
  1. better-wp-security.php +1 -1
  2. core/admin-pages/css/style.css +1 -1
  3. core/admin-pages/module-settings.php +1 -1
  4. core/admin-pages/page-debug.php +10 -3
  5. core/core.php +34 -60
  6. core/history.txt +6 -0
  7. core/lib.php +39 -2
  8. core/lib/admin-notices/actions/class-itsec-admin-notice-action-callback.php +37 -0
  9. core/lib/admin-notices/actions/class-itsec-admin-notice-action-link.php +47 -0
  10. core/lib/admin-notices/actions/index.php +1 -0
  11. core/lib/admin-notices/actions/interface-itsec-admin-notice-action.php +41 -0
  12. core/lib/admin-notices/class-itsec-admin-notice-callback.php +51 -0
  13. core/lib/admin-notices/class-itsec-admin-notice-context.php +63 -0
  14. core/lib/admin-notices/class-itsec-admin-notice-globally-dismissible.php +75 -0
  15. core/lib/admin-notices/class-itsec-admin-notice-highlighted-log.php +142 -0
  16. core/lib/admin-notices/class-itsec-admin-notice-managers-only.php +45 -0
  17. core/lib/admin-notices/class-itsec-admin-notice-remind-me.php +83 -0
  18. core/lib/admin-notices/class-itsec-admin-notice-screen-blacklist.php +53 -0
  19. core/lib/admin-notices/class-itsec-admin-notice-static.php +52 -0
  20. core/lib/admin-notices/class-itsec-admin-notice-user-dismissible.php +76 -0
  21. core/lib/admin-notices/index.php +1 -0
  22. core/lib/admin-notices/interface-itsec-admin-notice.php +72 -0
  23. core/lib/class-itsec-lib-admin-notices.php +85 -0
  24. core/lib/class-itsec-lib-feature-flags.php +174 -0
  25. core/lib/class-itsec-lib-highlighted-logs.php +200 -0
  26. core/lib/class-itsec-lib-remote-messages.php +34 -8
  27. core/lib/class-itsec-mail.php +8 -0
  28. core/lib/log.php +1 -1
  29. core/modules/core/active.php +12 -4
  30. core/modules/core/class-itsec-admin-notices.php +198 -0
  31. core/modules/core/class-itsec-core-active.php +129 -0
  32. core/modules/core/class-itsec-core-admin.php +52 -2
  33. core/modules/core/class-rest-core-admin-notices-controller.php +201 -0
  34. core/modules/core/entries/admin-notices-api.js +1 -0
  35. core/modules/core/entries/admin-notices-dashboard-admin-bar.js +13 -0
  36. core/modules/core/entries/admin-notices.js +21 -0
  37. core/modules/core/entries/admin-notices/app.js +23 -0
  38. core/modules/core/entries/admin-notices/components/admin-bar/index.js +64 -0
  39. core/modules/core/entries/admin-notices/components/admin-bar/index.php +1 -0
  40. core/modules/core/entries/admin-notices/components/admin-bar/style.scss +52 -0
  41. core/modules/core/entries/admin-notices/components/index.php +1 -0
  42. core/modules/core/entries/admin-notices/components/notice-actions/index.js +108 -0
  43. core/modules/core/entries/admin-notices/components/notice-actions/index.php +1 -0
  44. core/modules/core/entries/admin-notices/components/notice-actions/style.scss +42 -0
  45. core/modules/core/entries/admin-notices/components/notice-list/index.js +21 -0
  46. core/modules/core/entries/admin-notices/components/notice-list/index.php +1 -0
  47. core/modules/core/entries/admin-notices/components/notice-list/style.scss +14 -0
  48. core/modules/core/entries/admin-notices/components/notice/index.js +96 -0
  49. core/modules/core/entries/admin-notices/components/notice/index.php +1 -0
  50. core/modules/core/entries/admin-notices/components/notice/style.scss +104 -0
  51. core/modules/core/entries/admin-notices/components/panel/index.js +87 -0
  52. core/modules/core/entries/admin-notices/components/panel/index.php +1 -0
  53. core/modules/core/entries/admin-notices/components/panel/style.scss +103 -0
  54. core/modules/core/entries/admin-notices/components/toolbar/index.js +74 -0
  55. core/modules/core/entries/admin-notices/components/toolbar/index.php +1 -0
  56. core/modules/core/entries/admin-notices/components/toolbar/style.scss +104 -0
  57. core/modules/core/entries/admin-notices/index.php +1 -0
  58. core/modules/core/entries/admin-notices/store/actions.js +126 -0
  59. core/modules/core/entries/admin-notices/store/controls.js +101 -0
  60. core/modules/core/entries/admin-notices/store/index.js +23 -0
  61. core/modules/core/entries/admin-notices/store/index.php +1 -0
  62. core/modules/core/entries/admin-notices/store/reducers.js +86 -0
  63. core/modules/core/entries/admin-notices/store/resolvers.js +33 -0
  64. core/modules/core/entries/admin-notices/store/selectors.js +55 -0
  65. core/modules/core/entries/admin-notices/style.scss +0 -0
  66. core/modules/core/entries/admin-notices/utils.js +19 -0
  67. core/modules/core/entries/index.php +1 -0
  68. core/modules/core/js/admin-notices.js +73 -0
  69. core/modules/core/settings.php +0 -13
  70. core/modules/core/setup.php +0 -70
  71. core/modules/core/sidebar-widget-backupbuddy-cross-promo.php +1 -1
  72. core/modules/core/sidebar-widget-support.php +2 -2
  73. core/modules/core/validator.php +0 -17
  74. core/modules/feature-flags/index.php +1 -0
  75. core/modules/feature-flags/settings-page.php +90 -0
  76. core/modules/file-change/admin.php +0 -97
  77. core/modules/file-change/class-itsec-file-change.php +14 -0
  78. core/modules/file-change/js/script.js +0 -36
  79. core/modules/file-change/logs.php +21 -6
  80. core/modules/file-change/scanner.php +3 -7
  81. core/modules/file-change/settings-page.php +0 -8
  82. core/modules/file-change/settings.php +0 -2
  83. core/modules/file-change/setup.php +7 -0
  84. core/modules/file-change/validator.php +3 -4
  85. core/modules/global/active.php +2 -64
  86. core/modules/global/notices.php +64 -0
  87. core/modules/global/settings-page.php +1 -0
  88. core/modules/global/settings.php +1 -0
  89. core/modules/global/validator.php +3 -2
  90. core/modules/ipcheck/active.php +1 -1
  91. core/modules/ipcheck/class-itsec-ipcheck.php +7 -183
  92. core/modules/ipcheck/settings-page.php +2 -1
  93. core/modules/ipcheck/settings.php +1 -1
  94. core/modules/ipcheck/utilities.php +203 -18
  95. core/modules/malware/class-itsec-malware-scan-results-template.php +4 -0
  96. core/modules/malware/class-itsec-malware-scanner.php +5 -0
  97. core/modules/malware/class-itsec-malware.php +6 -1
  98. core/modules/malware/js/malware.js +35 -19
  99. core/modules/malware/js/settings-page.js +16 -0
  100. core/modules/malware/logs.php +1 -1
  101. core/modules/notification-center/class-notification-center.php +11 -0
  102. core/modules/notification-center/logs.php +28 -1
  103. core/modules/security-check/scanner.php +2 -2
  104. core/modules/system-tweaks/config-generators.php +2 -2
  105. core/modules/system-tweaks/settings-page.php +1 -1
  106. core/package.json +114 -0
  107. core/packages/components/index.php +1 -0
  108. core/packages/components/src/async-select/index.js +30 -0
  109. core/packages/components/src/async-select/index.php +1 -0
  110. core/packages/components/src/close-button/index.js +19 -0
  111. core/packages/components/src/close-button/index.php +1 -0
  112. core/packages/components/src/close-button/style.scss +16 -0
  113. core/packages/components/src/hover-detector/index.js +124 -0
  114. core/packages/components/src/hover-detector/index.php +1 -0
  115. core/packages/components/src/index.js +8 -0
  116. core/packages/components/src/index.php +1 -0
  117. core/packages/components/src/loader/icon.svg +19 -0
  118. core/packages/components/src/loader/index.js +13 -0
  119. core/packages/components/src/loader/index.php +1 -0
  120. core/packages/components/src/loader/logo.svg +14 -0
  121. core/packages/components/src/loader/style.scss +21 -0
  122. core/packages/components/src/log-modal/index.js +68 -0
  123. core/packages/components/src/log-modal/index.php +1 -0
  124. core/packages/components/src/log-modal/style.scss +20 -0
  125. core/packages/components/src/malware-scan-results/blacklist-details.js +41 -0
  126. core/packages/components/src/malware-scan-results/index.js +42 -0
  127. core/packages/components/src/malware-scan-results/index.php +1 -0
  128. core/packages/components/src/malware-scan-results/malware-details.js +103 -0
  129. core/packages/components/src/malware-scan-results/style.scss +93 -0
  130. core/packages/components/src/malware-scan-results/system-error-details.js +30 -0
  131. core/packages/components/src/malware-scan-results/wp-error-details.js +33 -0
  132. core/packages/components/src/malware-scan-results/wrapped-section.js +62 -0
  133. core/packages/components/src/print-r/index.js +102 -0
  134. core/packages/components/src/print-r/index.php +1 -0
  135. core/packages/components/src/print-r/style.scss +11 -0
  136. core/packages/components/src/site-scan-results/blacklist-details.js +52 -0
  137. core/packages/components/src/site-scan-results/detail.js +9 -0
  138. core/packages/components/src/site-scan-results/details.js +7 -0
  139. core/packages/components/src/site-scan-results/index.js +37 -0
  140. core/packages/components/src/site-scan-results/index.php +1 -0
  141. core/packages/components/src/site-scan-results/known-vulnerabilities.js +30 -0
  142. core/packages/components/src/site-scan-results/malware-details.js +64 -0
  143. core/packages/components/src/site-scan-results/style.scss +89 -0
  144. core/packages/components/src/site-scan-results/system-error-details.js +24 -0
  145. core/packages/components/src/site-scan-results/wp-error-details.js +33 -0
  146. core/packages/components/src/site-scan-results/wrapped-section.js +67 -0
  147. core/packages/hocs/index.php +1 -0
  148. core/packages/hocs/src/index.js +5 -0
  149. core/packages/hocs/src/index.php +1 -0
  150. core/packages/hocs/src/with-debounce-handler.js +57 -0
  151. core/packages/hocs/src/with-interval.js +46 -0
  152. core/packages/hocs/src/with-prop-change-callback.js +43 -0
  153. core/packages/hocs/src/with-props.js +22 -0
  154. core/packages/hocs/src/with-width.js +64 -0
  155. core/packages/index.php +1 -0
  156. core/packages/style-guide/index.php +1 -0
  157. core/packages/style-guide/src/animations.scss +9 -0
  158. core/packages/style-guide/src/breakpoints.scss +6 -0
  159. core/packages/style-guide/src/colors.js +13 -0
  160. core/packages/style-guide/src/colors.scss +89 -0
  161. core/packages/style-guide/src/index.js +1 -0
  162. core/packages/style-guide/src/index.php +1 -0
  163. core/packages/style-guide/src/mixins.scss +76 -0
  164. core/packages/utils/index.php +1 -0
  165. core/packages/utils/src/error-response.js +35 -0
  166. core/packages/utils/src/index.js +165 -0
  167. core/packages/utils/src/index.php +1 -0
  168. core/packages/utils/src/test/index.js +41 -0
  169. core/packages/utils/src/test/index.php +1 -0
  170. core/packages/utils/src/wp-error.js +127 -0
  171. core/packages/webpack/index.php +1 -0
  172. core/packages/webpack/src/babel.js +15 -0
  173. core/packages/webpack/src/config/index.js +211 -0
  174. core/packages/webpack/src/config/index.php +1 -0
  175. core/packages/webpack/src/custom-templated-path-webpack-plugin/index.js +52 -0
  176. core/packages/webpack/src/custom-templated-path-webpack-plugin/index.php +1 -0
  177. core/packages/webpack/src/dynamic-public-path/index.js +25 -0
  178. core/packages/webpack/src/dynamic-public-path/index.php +1 -0
  179. core/packages/webpack/src/dynamic-public-path/loader.js +10 -0
  180. core/packages/webpack/src/index.php +1 -0
  181. core/packages/webpack/src/manifest/index.js +80 -0
  182. core/packages/webpack/src/manifest/index.php +1 -0
  183. core/packages/webpack/src/manifest/json-to-php.php +20 -0
  184. core/packages/webpack/src/split-chunk-name/index.js +92 -0
  185. core/packages/webpack/src/split-chunk-name/index.php +1 -0
  186. core/packages/webpack/src/style-only-entry-plugin/index.js +41 -0
  187. core/packages/webpack/src/style-only-entry-plugin/index.php +1 -0
  188. core/packages/webpack/src/wp-externals/index.js +39 -0
  189. core/packages/webpack/src/wp-externals/index.php +1 -0
  190. core/setup.php +13 -0
  191. dist/core/admin-notices-api.min.js +1 -0
  192. dist/core/admin-notices-dashboard-admin-bar.min.css +10 -0
  193. dist/core/admin-notices-dashboard-admin-bar.min.js +7 -0
  194. dist/core/admin-notices.min.css +11 -0
  195. dist/core/admin-notices.min.js +7 -0
  196. dist/core/index.php +1 -0
  197. dist/core/packages/components/index.php +1 -0
  198. dist/core/packages/components/site-scan-results/index.php +1 -0
  199. dist/core/packages/components/site-scan-results/style.min.css +2 -0
  200. dist/core/packages/index.php +1 -0
  201. dist/index.php +1 -0
  202. dist/manifest.php +108 -0
  203. history.txt +5 -0
  204. package.json +114 -0
  205. readme.txt +10 -4
  206. webpack.config.js +2 -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: 7.3.3
10
  * Text Domain: better-wp-security
11
  * Network: True
12
  * License: GPLv2
6
  * Description: Take the guesswork out of WordPress security. iThemes Security offers 30+ ways to lock down WordPress in an easy-to-use WordPress security plugin.
7
  * Author: iThemes
8
  * Author URI: https://ithemes.com
9
+ * Version: 7.4.0
10
  * Text Domain: better-wp-security
11
  * Network: True
12
  * License: GPLv2
core/admin-pages/css/style.css CHANGED
@@ -34,7 +34,7 @@
34
  cursor: inherit;
35
  }
36
 
37
- ul {
38
  list-style: outside;
39
  padding-left: 1em;
40
  }
34
  cursor: inherit;
35
  }
36
 
37
+ .itsec-module-settings-container ul {
38
  list-style: outside;
39
  padding-left: 1em;
40
  }
core/admin-pages/module-settings.php CHANGED
@@ -36,7 +36,7 @@ class ITSEC_Module_Settings_Page {
36
  * @access protected
37
  * @var string
38
  */
39
- protected $type = 'recommended'; // "additional" or "recommended"
40
 
41
  /**
42
  * Whether the settings require resaving after activation in order to fully-activate the module.
36
  * @access protected
37
  * @var string
38
  */
39
+ protected $type = 'recommended'; // "advanced" or "recommended"
40
 
41
  /**
42
  * Whether the settings require resaving after activation in order to fully-activate the module.
core/admin-pages/page-debug.php CHANGED
@@ -225,7 +225,9 @@ final class ITSEC_Debug_Page {
225
  <th><?php esc_html_e( 'ID', 'better-wp-security' ) ?></th>
226
  <th><?php esc_html_e( 'Fire At', 'better-wp-security' ) ?></th>
227
  <th><?php esc_html_e( 'Schedule', 'better-wp-security' ) ?></th>
228
- <th><button class="button-link" id="itsec-events-data-toggle"><?php esc_html_e( 'Data', 'better-wp-security' ) ?></button></th>
 
 
229
  <th></th>
230
  </tr>
231
  </thead>
@@ -235,7 +237,9 @@ final class ITSEC_Debug_Page {
235
  <td><?php echo esc_html( $event['id'] ); ?></td>
236
  <td><?php echo date( 'Y-m-d H:i:s', $event['fire_at'] ); ?> (<?php echo esc_html( human_time_diff( $event['fire_at'] ) ) ?>)</td>
237
  <td><?php echo isset( $event['schedule'] ) ? $event['schedule'] : '–'; ?></td>
238
- <td><div class="hidden itsec-events-data"><?php $event['data'] ? ITSEC_Lib::print_r( $event['data'] ) : print( '–' ); ?></div></td>
 
 
239
  <td>
240
  <button class="button" data-id="<?php echo esc_attr( $event['id'] ); ?>"
241
  data-data="<?php echo isset( $event['schedule'] ) ? '' : esc_attr( $event['hash'] ); ?>">
@@ -310,6 +314,8 @@ final class ITSEC_Debug_Page {
310
  'ITSEC_DISABLE_INACTIVE_USER_CHECK',
311
  );
312
 
 
 
313
  $info['iThemes Security'] = array(
314
  'Build' => ITSEC_Core::get_plugin_build(),
315
  'Pro' => ITSEC_Core::is_pro(),
@@ -317,6 +323,7 @@ final class ITSEC_Debug_Page {
317
  'Cron' => ITSEC_Lib::use_cron(),
318
  'Cron Status' => ITSEC_Lib::is_cron_working(),
319
  'Scheduler' => get_class( ITSEC_Core::get_scheduler() ),
 
320
  );
321
 
322
  foreach ( $defines as $define ) {
@@ -422,4 +429,4 @@ final class ITSEC_Debug_Page {
422
  }
423
  }
424
 
425
- new ITSEC_Debug_Page();
225
  <th><?php esc_html_e( 'ID', 'better-wp-security' ) ?></th>
226
  <th><?php esc_html_e( 'Fire At', 'better-wp-security' ) ?></th>
227
  <th><?php esc_html_e( 'Schedule', 'better-wp-security' ) ?></th>
228
+ <th>
229
+ <button class="button-link" id="itsec-events-data-toggle"><?php esc_html_e( 'Data', 'better-wp-security' ) ?></button>
230
+ </th>
231
  <th></th>
232
  </tr>
233
  </thead>
237
  <td><?php echo esc_html( $event['id'] ); ?></td>
238
  <td><?php echo date( 'Y-m-d H:i:s', $event['fire_at'] ); ?> (<?php echo esc_html( human_time_diff( $event['fire_at'] ) ) ?>)</td>
239
  <td><?php echo isset( $event['schedule'] ) ? $event['schedule'] : '–'; ?></td>
240
+ <td>
241
+ <div class="hidden itsec-events-data"><?php $event['data'] ? ITSEC_Lib::print_r( $event['data'] ) : print( '–' ); ?></div>
242
+ </td>
243
  <td>
244
  <button class="button" data-id="<?php echo esc_attr( $event['id'] ); ?>"
245
  data-data="<?php echo isset( $event['schedule'] ) ? '' : esc_attr( $event['hash'] ); ?>">
314
  'ITSEC_DISABLE_INACTIVE_USER_CHECK',
315
  );
316
 
317
+ ITSEC_Lib::load( 'feature-flags' );
318
+
319
  $info['iThemes Security'] = array(
320
  'Build' => ITSEC_Core::get_plugin_build(),
321
  'Pro' => ITSEC_Core::is_pro(),
323
  'Cron' => ITSEC_Lib::use_cron(),
324
  'Cron Status' => ITSEC_Lib::is_cron_working(),
325
  'Scheduler' => get_class( ITSEC_Core::get_scheduler() ),
326
+ 'Features' => wp_sprintf( '%l', ITSEC_Lib_Feature_Flags::get_enabled() ),
327
  );
328
 
329
  foreach ( $defines as $define ) {
429
  }
430
  }
431
 
432
+ new ITSEC_Debug_Page();
core/core.php CHANGED
@@ -24,7 +24,7 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
24
  *
25
  * @access private
26
  */
27
- private $plugin_build = 4113;
28
 
29
  /**
30
  * Used to distinguish between a user modifying settings and the API modifying settings (such as from Sync
@@ -159,11 +159,6 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
159
  $this->setup_scheduler();
160
  ITSEC_Modules::run_active_modules();
161
 
162
- //Admin bar links
163
- if ( ! ITSEC_Modules::get_setting( 'global', 'hide_admin_bar' ) ) {
164
- add_action( 'admin_bar_menu', array( $this, 'modify_admin_bar' ), 99 );
165
- }
166
-
167
  $this->login_interstitial = new ITSEC_Lib_Login_Interstitial();
168
  $this->login_interstitial->run();
169
 
@@ -343,10 +338,9 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
343
  ITSEC_Modules::register_module( 'system-tweaks', "$path/modules/system-tweaks" );
344
  ITSEC_Modules::register_module( 'wordpress-salts', "$path/modules/salts", 'always-active' );
345
  ITSEC_Modules::register_module( 'wordpress-tweaks', "$path/modules/wordpress-tweaks", 'default-active' );
346
-
347
  ITSEC_Modules::register_module( 'file-writing', "$path/modules/file-writing", 'always-active' );
348
-
349
  ITSEC_Modules::register_module( 'malware', "$path/modules/malware", 'always-active' );
 
350
 
351
  if ( ! ITSEC_Core::is_pro() ) {
352
  ITSEC_Modules::register_module( 'pro-module-upsells', "$path/modules/pro", 'always-active' );
@@ -404,58 +398,6 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
404
  return $meta;
405
  }
406
 
407
- /**
408
- * Add admin bar items
409
- *
410
- * @since 4.0
411
- *
412
- * @param WP_Admin_Bar $wp_admin_bar
413
- *
414
- * @return void
415
- */
416
- public function modify_admin_bar( $wp_admin_bar ) {
417
-
418
- if ( ! ITSEC_Core::current_user_can_manage() ) {
419
- return;
420
- }
421
-
422
- // Add the Parent link.
423
- $wp_admin_bar->add_node(
424
- array(
425
- 'title' => __( 'Security', 'better-wp-security' ),
426
- 'href' => self::get_settings_page_url(),
427
- 'id' => 'itsec_admin_bar_menu',
428
- )
429
- );
430
-
431
- $wp_admin_bar->add_node(
432
- array(
433
- 'parent' => 'itsec_admin_bar_menu',
434
- 'title' => __( 'Settings', 'better-wp-security' ),
435
- 'href' => self::get_settings_page_url(),
436
- 'id' => 'itsec_admin_bar_settings',
437
- )
438
- );
439
-
440
- $wp_admin_bar->add_node(
441
- array(
442
- 'parent' => 'itsec_admin_bar_menu',
443
- 'title' => __( 'Security Check', 'better-wp-security' ),
444
- 'href' => self::get_security_check_page_url(),
445
- 'id' => 'itsec_admin_bar_security_check',
446
- )
447
- );
448
-
449
- $wp_admin_bar->add_node(
450
- array(
451
- 'parent' => 'itsec_admin_bar_menu',
452
- 'title' => __( 'Logs', 'better-wp-security' ),
453
- 'href' => self::get_logs_page_url(),
454
- 'id' => 'itsec_admin_bar_logs',
455
- )
456
- );
457
- }
458
-
459
  /**
460
  * Dispatch a request to upgrade the data schema to another version.
461
  *
@@ -495,6 +437,8 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
495
  * @param bool $all_pages Display the notice on all pages or only on ITSEC, plugins, and upgrade page.
496
  */
497
  public static function add_notice( $callback, $all_pages = false ) {
 
 
498
  global $pagenow, $plugin_page;
499
 
500
  if ( ! $all_pages && ! in_array( $pagenow, array( 'plugins.php', 'update-core.php' ) ) && ( ! isset( $plugin_page ) || ! in_array( $plugin_page, array( 'itsec', 'itsec-logs' ) ) ) ) {
@@ -576,6 +520,36 @@ if ( ! class_exists( 'ITSEC_Core' ) ) {
576
  return is_dir( self::get_plugin_dir() . 'pro' );
577
  }
578
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
579
  /**
580
  * Get the current local timestamp.
581
  *
24
  *
25
  * @access private
26
  */
27
+ private $plugin_build = 4114;
28
 
29
  /**
30
  * Used to distinguish between a user modifying settings and the API modifying settings (such as from Sync
159
  $this->setup_scheduler();
160
  ITSEC_Modules::run_active_modules();
161
 
 
 
 
 
 
162
  $this->login_interstitial = new ITSEC_Lib_Login_Interstitial();
163
  $this->login_interstitial->run();
164
 
338
  ITSEC_Modules::register_module( 'system-tweaks', "$path/modules/system-tweaks" );
339
  ITSEC_Modules::register_module( 'wordpress-salts', "$path/modules/salts", 'always-active' );
340
  ITSEC_Modules::register_module( 'wordpress-tweaks', "$path/modules/wordpress-tweaks", 'default-active' );
 
341
  ITSEC_Modules::register_module( 'file-writing', "$path/modules/file-writing", 'always-active' );
 
342
  ITSEC_Modules::register_module( 'malware', "$path/modules/malware", 'always-active' );
343
+ ITSEC_Modules::register_module( 'feature-flags', "$path/modules/feature-flags", 'always-active' );
344
 
345
  if ( ! ITSEC_Core::is_pro() ) {
346
  ITSEC_Modules::register_module( 'pro-module-upsells', "$path/modules/pro", 'always-active' );
398
  return $meta;
399
  }
400
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401
  /**
402
  * Dispatch a request to upgrade the data schema to another version.
403
  *
437
  * @param bool $all_pages Display the notice on all pages or only on ITSEC, plugins, and upgrade page.
438
  */
439
  public static function add_notice( $callback, $all_pages = false ) {
440
+ _deprecated_function( __METHOD__, '6.0.0', 'ITSEC_Lib_Admin_Notices::register' );
441
+
442
  global $pagenow, $plugin_page;
443
 
444
  if ( ! $all_pages && ! in_array( $pagenow, array( 'plugins.php', 'update-core.php' ) ) && ( ! isset( $plugin_page ) || ! in_array( $plugin_page, array( 'itsec', 'itsec-logs' ) ) ) ) {
520
  return is_dir( self::get_plugin_dir() . 'pro' );
521
  }
522
 
523
+ /**
524
+ * Is this an actively licensed Pro installation.
525
+ *
526
+ * @return bool
527
+ */
528
+ public static function is_licensed() {
529
+ if ( ! self::is_pro() ) {
530
+ return false;
531
+ }
532
+
533
+ if ( ! isset( $GLOBALS['ithemes_updater_path'] ) ) {
534
+ return false;
535
+ }
536
+
537
+ include_once( $GLOBALS['ithemes_updater_path'] . '/keys.php' );
538
+ include_once( $GLOBALS['ithemes_updater_path'] . '/packages.php' );
539
+
540
+ if ( ! class_exists( 'Ithemes_Updater_Packages' ) ) {
541
+ return false;
542
+ }
543
+
544
+ $package_details = Ithemes_Updater_Packages::get_full_details();
545
+
546
+ if ( empty( $package_details['packages']['ithemes-security-pro/ithemes-security-pro.php']['status'] ) ) {
547
+ return false;
548
+ }
549
+
550
+ return 'active' === $package_details['packages']['ithemes-security-pro/ithemes-security-pro.php']['status'];
551
+ }
552
+
553
  /**
554
  * Get the current local timestamp.
555
  *
core/history.txt CHANGED
@@ -798,3 +798,9 @@
798
  Bug Fix: Resolve warning when a user is set to "No Role".
799
  5.1.4 - 2019-03-22 - Chris Jean & Timothy Jacobs
800
  Bug Fix: Hide backend bypass.
 
 
 
 
 
 
798
  Bug Fix: Resolve warning when a user is set to "No Role".
799
  5.1.4 - 2019-03-22 - Chris Jean & Timothy Jacobs
800
  Bug Fix: Hide backend bypass.
801
+ 5.2.0 - 2019-05-30 - Chris Jean & Timothy Jacobs
802
+ New: iThemes Security Admin Notices are now conveniently located in the new Security Messages Menu. Check your notices in the Security menu on the WordPress Admin Bar.
803
+ Tweak: Remove 'DELETE' method from "System Tweaks -> Filter Request Methods"
804
+ 5.2.1 - 2019-06-06 - Chris Jean & Timothy Jacobs
805
+ Enhancement: Add Security Message when a Notification Center email fails to send.
806
+ Enhancement: Replace Trace IP with IP Tracker Online.
core/lib.php CHANGED
@@ -587,9 +587,9 @@ final class ITSEC_Lib {
587
  */
588
  public static function get_trace_ip_link( $ip = false ) {
589
  if ( empty( $ip ) ) {
590
- return 'http://www.traceip.net/';
591
  } else {
592
- return 'http://www.traceip.net/?query=' . urlencode( $ip );
593
  }
594
  }
595
 
@@ -1161,6 +1161,34 @@ final class ITSEC_Lib {
1161
  return $array;
1162
  }
1163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1164
  /**
1165
  * Insert an element after a given key.
1166
  *
@@ -1701,4 +1729,13 @@ final class ITSEC_Lib {
1701
  public static function str_ends_with( $haystack, $needle ) {
1702
  return '' === $needle || substr_compare( $haystack, $needle, - strlen( $needle ) ) === 0;
1703
  }
 
 
 
 
 
 
 
 
 
1704
  }
587
  */
588
  public static function get_trace_ip_link( $ip = false ) {
589
  if ( empty( $ip ) ) {
590
+ return 'https://www.iptrackeronline.com/ithemes.php';
591
  } else {
592
+ return 'http://www.iptrackeronline.com/ithemes.php?ip_address=' . urlencode( $ip );
593
  }
594
  }
595
 
1161
  return $array;
1162
  }
1163
 
1164
+ /**
1165
+ * Inserts a new key/value before the key in the array.
1166
+ *
1167
+ * @param string $key The key to insert before.
1168
+ * @param array $array An array to insert in to.
1169
+ * @param string $new_key The key to insert.
1170
+ * @param mixed $new_value The value to insert.
1171
+ *
1172
+ * @return array
1173
+ */
1174
+ public static function array_insert_before( $key, $array, $new_key, $new_value) {
1175
+ if ( array_key_exists( $key, $array ) ) {
1176
+ $new = array();
1177
+ foreach ( $array as $k => $value ) {
1178
+ if ( $k === $key ) {
1179
+ $new[ $new_key ] = $new_value;
1180
+ }
1181
+ $new[ $k ] = $value;
1182
+ }
1183
+
1184
+ return $new;
1185
+ }
1186
+
1187
+ $array[ $new_key ] = $new_value;
1188
+
1189
+ return $array;
1190
+ }
1191
+
1192
  /**
1193
  * Insert an element after a given key.
1194
  *
1729
  public static function str_ends_with( $haystack, $needle ) {
1730
  return '' === $needle || substr_compare( $haystack, $needle, - strlen( $needle ) ) === 0;
1731
  }
1732
+
1733
+ /**
1734
+ * Load a library class definition.
1735
+ *
1736
+ * @param string $name
1737
+ */
1738
+ public static function load( $name ) {
1739
+ require_once( dirname( __FILE__ ) . "/lib/class-itsec-lib-{$name}.php" );
1740
+ }
1741
  }
core/lib/admin-notices/actions/class-itsec-admin-notice-action-callback.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Admin_Notice_Action_Callback implements ITSEC_Admin_Notice_Action {
4
+
5
+ private $style;
6
+ private $title;
7
+ private $callable;
8
+
9
+ /**
10
+ * ITSEC_Admin_Notice_Action_Button constructor.
11
+ *
12
+ * @param string $style
13
+ * @param string $title
14
+ * @param callable $callable
15
+ */
16
+ public function __construct( $style, $title, $callable ) {
17
+ $this->style = $style;
18
+ $this->title = $title;
19
+ $this->callable = $callable;
20
+ }
21
+
22
+ public function handle( WP_User $user, array $data ) {
23
+ return call_user_func( $this->callable, $user, $data );
24
+ }
25
+
26
+ public function get_title() {
27
+ return $this->title;
28
+ }
29
+
30
+ public function get_style() {
31
+ return $this->style;
32
+ }
33
+
34
+ public function get_uri() {
35
+ return '';
36
+ }
37
+ }
core/lib/admin-notices/actions/class-itsec-admin-notice-action-link.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ final class ITSEC_Admin_Notice_Action_Link implements ITSEC_Admin_Notice_Action {
4
+
5
+ /** @var string */
6
+ private $uri;
7
+
8
+ /** @var string */
9
+ private $title;
10
+
11
+ /** @var string */
12
+ private $style;
13
+
14
+ /** @var callable|null */
15
+ private $callback;
16
+
17
+ /**
18
+ * ITSEC_Admin_Notice_Action_Link constructor.
19
+ *
20
+ * @param string $uri
21
+ * @param string $title
22
+ * @param string $style
23
+ * @param callable|null $callback
24
+ */
25
+ public function __construct( $uri, $title, $style = self::S_LINK, $callback = null ) {
26
+ $this->uri = $uri;
27
+ $this->title = $title;
28
+ $this->style = $style;
29
+ $this->callback = $callback;
30
+ }
31
+
32
+ public function handle( WP_User $user, array $data ) {
33
+ return $this->callback ? call_user_func( $this->callback, $user, $data ) : null;
34
+ }
35
+
36
+ public function get_title() {
37
+ return $this->title;
38
+ }
39
+
40
+ public function get_style() {
41
+ return $this->style;
42
+ }
43
+
44
+ public function get_uri() {
45
+ return $this->uri;
46
+ }
47
+ }
core/lib/admin-notices/actions/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/lib/admin-notices/actions/interface-itsec-admin-notice-action.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ interface ITSEC_Admin_Notice_Action {
4
+
5
+ const S_LINK = 'link';
6
+ const S_BUTTON = 'button';
7
+ const S_PRIMARY = 'primary';
8
+ const S_CLOSE = 'close';
9
+
10
+ /**
11
+ * Handle the action.
12
+ *
13
+ * @param WP_User $user
14
+ * @param array $data
15
+ *
16
+ * @return WP_Error|null
17
+ */
18
+ public function handle( WP_User $user, array $data );
19
+
20
+ /**
21
+ * Get the title of this action.
22
+ *
23
+ * @return string
24
+ */
25
+ public function get_title();
26
+
27
+ /**
28
+ * Get the action presentation style.
29
+ *
30
+ * @return string
31
+ */
32
+ public function get_style();
33
+
34
+ /**
35
+ * Get the URI that should be navigated to when triggering this action.
36
+ *
37
+ * @return string URI or empty string
38
+ */
39
+ public function get_uri();
40
+ }
41
+
core/lib/admin-notices/class-itsec-admin-notice-callback.php ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Admin_Notice_Callback implements ITSEC_Admin_Notice {
4
+ private $id;
5
+ private $message;
6
+ private $title;
7
+ private $severity;
8
+
9
+ /**
10
+ * ITSEC_Admin_Notice_Concrete constructor.
11
+ *
12
+ * @param string $id Globally unique notice ID.
13
+ * @param callable $message Callable to return the message to display to the user.
14
+ * @param callable $title Callable to return the title.
15
+ * @param string $severity The message's severity.
16
+ */
17
+ public function __construct( $id, $message, $title = null, $severity = self::S_INFO ) {
18
+ $this->id = $id;
19
+ $this->message = $message;
20
+ $this->title = $title;
21
+ $this->severity = $severity;
22
+ }
23
+
24
+ public function get_id() {
25
+ return $this->id;
26
+ }
27
+
28
+ public function get_title() {
29
+ return $this->title ? call_user_func( $this->title ) : '';
30
+ }
31
+
32
+ public function get_message() {
33
+ return call_user_func( $this->message );
34
+ }
35
+
36
+ public function get_severity() {
37
+ return $this->severity;
38
+ }
39
+
40
+ public function get_meta() {
41
+ return array();
42
+ }
43
+
44
+ public function show_for_context( ITSEC_Admin_Notice_Context $context ) {
45
+ return true;
46
+ }
47
+
48
+ public function get_actions() {
49
+ return array();
50
+ }
51
+ }
core/lib/admin-notices/class-itsec-admin-notice-context.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ final class ITSEC_Admin_Notice_Context {
4
+
5
+ const AJAX = 'ajax';
6
+ const ADMIN_ACTION = 'admin-action';
7
+ const FRONT = 'front';
8
+
9
+ /** @var WP_User */
10
+ private $user;
11
+
12
+ /** @var string */
13
+ private $screen_id;
14
+
15
+ /**
16
+ * ITSEC_Admin_Notice_Context constructor.
17
+ *
18
+ * @param WP_User $user
19
+ * @param string $screen_id
20
+ */
21
+ public function __construct( WP_User $user, $screen_id ) {
22
+ $this->user = $user;
23
+ $this->screen_id = $screen_id;
24
+ }
25
+
26
+ /**
27
+ * Create the context from the globally defined state.
28
+ *
29
+ * @return ITSEC_Admin_Notice_Context
30
+ * @throws Exception
31
+ */
32
+ public static function from_global_state() {
33
+ if ( function_exists( 'get_current_screen' ) ) {
34
+ if ( ! $screen = get_current_screen() ) {
35
+ throw new Exception( 'Cannot instantiate a notice context from global state before the WP_Screen object is available.' );
36
+ }
37
+
38
+ $screen_id = $screen->id;
39
+ } else {
40
+ $screen_id = self::FRONT;
41
+ }
42
+
43
+ return new self( wp_get_current_user(), $screen_id );
44
+ }
45
+
46
+ /**
47
+ * Get the user the notice is being interacted with.
48
+ *
49
+ * @return WP_User
50
+ */
51
+ public function get_user() {
52
+ return $this->user;
53
+ }
54
+
55
+ /**
56
+ * Get the ID of the screen being displayed.
57
+ *
58
+ * @return string
59
+ */
60
+ public function get_screen_id() {
61
+ return $this->screen_id;
62
+ }
63
+ }
core/lib/admin-notices/class-itsec-admin-notice-globally-dismissible.php ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Admin_Notice_Globally_Dismissible implements ITSEC_Admin_Notice {
4
+ /** @var ITSEC_Admin_Notice */
5
+ private $notice;
6
+
7
+ /**
8
+ * ITSEC_Admin_Notice_Dismissible constructor.
9
+ *
10
+ * @param ITSEC_Admin_Notice $notice
11
+ */
12
+ public function __construct( ITSEC_Admin_Notice $notice ) { $this->notice = $notice; }
13
+
14
+ public function get_id() {
15
+ return $this->notice->get_id();
16
+ }
17
+
18
+ public function get_title() {
19
+ return $this->notice->get_title();
20
+ }
21
+
22
+ public function get_message() {
23
+ return $this->notice->get_message();
24
+ }
25
+
26
+ public function get_severity() {
27
+ return $this->notice->get_severity();
28
+ }
29
+
30
+ public function get_meta() {
31
+ return $this->notice->get_meta();
32
+ }
33
+
34
+ public function show_for_context( ITSEC_Admin_Notice_Context $context ) {
35
+ $dismissed = $this->get_storage();
36
+
37
+ if ( in_array( $this->get_id(), $dismissed, true ) ) {
38
+ return false;
39
+ }
40
+
41
+ return $this->notice->show_for_context( $context );
42
+ }
43
+
44
+ public function get_actions() {
45
+ return array_merge( $this->notice->get_actions(), array(
46
+ 'dismiss' => new ITSEC_Admin_Notice_Action_Callback(
47
+ ITSEC_Admin_Notice_Action::S_CLOSE,
48
+ esc_html__( 'Dismiss', 'better-wp-security' ),
49
+ array( $this, '_handle_dismiss' )
50
+ ),
51
+ ) );
52
+ }
53
+
54
+ public function _handle_dismiss( WP_User $user, array $data ) {
55
+ $dismissed = $this->get_storage();
56
+ $dismissed[] = $this->get_id();
57
+ $this->save_storage( $dismissed );
58
+
59
+ return null;
60
+ }
61
+
62
+ private function get_storage() {
63
+ $dismissed = get_site_option( 'itsec_dismissed_notices', array() );
64
+
65
+ if ( ! is_array( $dismissed ) ) {
66
+ $dismissed = array();
67
+ }
68
+
69
+ return $dismissed;
70
+ }
71
+
72
+ private function save_storage( $storage ) {
73
+ update_site_option( 'itsec_dismissed_notices', $storage );
74
+ }
75
+ }
core/lib/admin-notices/class-itsec-admin-notice-highlighted-log.php ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Admin_Notice_Highlighted_Log implements ITSEC_Admin_Notice {
4
+
5
+ private $highlight_id;
6
+ private $log;
7
+ private $filtered;
8
+
9
+ /**
10
+ * ITSEC_Admin_Notice_Highlighted_Log constructor.
11
+ *
12
+ * @param string|int $id_or_slug
13
+ * @param array $log_item
14
+ */
15
+ public function __construct( $id_or_slug, array $log_item ) {
16
+ $this->highlight_id = $id_or_slug;
17
+ $this->log = $log_item;
18
+ }
19
+
20
+ public function get_id() {
21
+ return "highlighted-log-{$this->highlight_id}";
22
+ }
23
+
24
+ public function get_title() {
25
+ ITSEC_Modules::load_module_file( 'logs.php' );
26
+
27
+ return apply_filters( "itsec_highlighted_log_{$this->highlight_id}_notice_title", '', $this->log );
28
+ }
29
+
30
+ public function get_message() {
31
+ ITSEC_Modules::load_module_file( 'logs.php' );
32
+ $filtered = $this->get_filtered_log();
33
+
34
+ $message = '<strong>' . esc_html( $filtered['module_display'] ) . '</strong>: ' . $filtered['description'];
35
+
36
+ return apply_filters( "itsec_highlighted_log_{$this->highlight_id}_notice_message", $message, $this->log );
37
+ }
38
+
39
+ public function get_severity() {
40
+ switch ( $this->log['type'] ) {
41
+ case 'critical-issue':
42
+ case 'fatal':
43
+ return self::S_ERROR;
44
+ case 'error':
45
+ case 'warning':
46
+ return self::S_WARN;
47
+ case 'action':
48
+ return self::S_SUCCESS;
49
+ default:
50
+ return self::S_INFO;
51
+ }
52
+ }
53
+
54
+ public function get_meta() {
55
+ $filtered = $this->get_filtered_log();
56
+
57
+ return array(
58
+ self::M_CREATED_AT => array(
59
+ 'label' => esc_html__( 'Created At', 'better-wp-security' ),
60
+ 'value' => $this->log['init_timestamp'],
61
+ 'formatted' => sprintf(
62
+ '%s – %s ago',
63
+ date_i18n( 'M d, Y g:i A', strtotime( $this->log['init_timestamp'] ) ),
64
+ human_time_diff( strtotime( $this->log['init_timestamp'] ) )
65
+ ),
66
+ ),
67
+ 'module' => array(
68
+ 'label' => esc_html__( 'Module', 'better-wp-security' ),
69
+ 'value' => $this->log['module'],
70
+ 'formatted' => $filtered['module_display'],
71
+ ),
72
+ 'description' => array(
73
+ 'label' => esc_html__( 'Description', 'better-wp-security' ),
74
+ 'value' => $this->log['code'],
75
+ 'formatted' => $filtered['description'],
76
+ ),
77
+ );
78
+ }
79
+
80
+ public function show_for_context( ITSEC_Admin_Notice_Context $context ) {
81
+ return true;
82
+ }
83
+
84
+ public function get_actions() {
85
+ $actions = array(
86
+ 'dismiss' => new ITSEC_Admin_Notice_Action_Callback(
87
+ ITSEC_Admin_Notice_Action::S_CLOSE,
88
+ esc_html__( 'Dismiss', 'better-wp-security' ),
89
+ array( $this, '_handle_dismiss' )
90
+ ),
91
+ 'view' => new ITSEC_Admin_Notice_Action_Link(
92
+ add_query_arg( 'id', $this->log['id'], ITSEC_Core::get_logs_page_url() ),
93
+ esc_html__( 'View Details', 'better-wp-security' ),
94
+ ITSEC_Admin_Notice_Action::S_LINK,
95
+ array( $this, '_handle_dismiss' )
96
+ )
97
+ );
98
+
99
+ if ( ! is_numeric( $this->highlight_id ) ) {
100
+ $actions['mute'] = new ITSEC_Admin_Notice_Action_Callback(
101
+ ITSEC_Admin_Notice_Action::S_BUTTON,
102
+ esc_html__( 'Dismiss Permanently', 'better-wp-security' ),
103
+ array( $this, '_handle_mute' )
104
+ );
105
+ }
106
+
107
+ return $actions;
108
+ }
109
+
110
+ public function _handle_mute( WP_User $user, array $data ) {
111
+ $this->_handle_dismiss( $user, $data );
112
+ ITSEC_Lib_Highlighted_Logs::mute( $this->highlight_id );
113
+ }
114
+
115
+ public function _handle_dismiss( WP_User $user, array $data ) {
116
+ ITSEC_Lib_Highlighted_Logs::dismiss_highlight( $this->highlight_id );
117
+ }
118
+
119
+ private function get_filtered_log() {
120
+ if ( $this->filtered ) {
121
+ return $this->filtered;
122
+ }
123
+
124
+ $item = $this->log;
125
+
126
+ if ( false === strpos( $item['code'], '::' ) ) {
127
+ $code = $item['code'];
128
+ $data = array();
129
+ } else {
130
+ list( $code, $data ) = explode( '::', $item['code'], 2 );
131
+ $data = explode( ',', $data );
132
+ }
133
+
134
+ $item['description'] = $item['code'];
135
+ $item['module_display'] = $item['module'];
136
+
137
+ ITSEC_Modules::load_module_file( 'logs.php' );
138
+ $item = apply_filters( "itsec_logs_prepare_{$item['module']}_entry_for_list_display", $item, $code, $data );
139
+
140
+ return $this->filtered = $item;
141
+ }
142
+ }
core/lib/admin-notices/class-itsec-admin-notice-managers-only.php ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Admin_Notice_Managers_Only implements ITSEC_Admin_Notice {
4
+ /** @var ITSEC_Admin_Notice */
5
+ private $notice;
6
+
7
+ /**
8
+ * ITSEC_Admin_Notice_Admin_Only constructor.
9
+ *
10
+ * @param ITSEC_Admin_Notice $notice
11
+ */
12
+ public function __construct( ITSEC_Admin_Notice $notice ) { $this->notice = $notice; }
13
+
14
+ public function get_id() {
15
+ return $this->notice->get_id();
16
+ }
17
+
18
+ public function get_title() {
19
+ return $this->notice->get_title();
20
+ }
21
+
22
+ public function get_message() {
23
+ return $this->notice->get_message();
24
+ }
25
+
26
+ public function get_severity() {
27
+ return $this->notice->get_severity();
28
+ }
29
+
30
+ public function get_meta() {
31
+ return $this->notice->get_meta();
32
+ }
33
+
34
+ public function show_for_context( ITSEC_Admin_Notice_Context $context ) {
35
+ if ( ! user_can( $context->get_user()->ID, ITSEC_Core::get_required_cap() ) ) {
36
+ return false;
37
+ }
38
+
39
+ return $this->notice->show_for_context( $context );
40
+ }
41
+
42
+ public function get_actions() {
43
+ return $this->notice->get_actions();
44
+ }
45
+ }
core/lib/admin-notices/class-itsec-admin-notice-remind-me.php ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Admin_Notice_Remind_Me implements ITSEC_Admin_Notice {
4
+ /** @var ITSEC_Admin_Notice */
5
+ private $notice;
6
+
7
+ /** @var int */
8
+ private $ttl;
9
+
10
+ /**
11
+ * ITSEC_Admin_Notice_Remind_Me constructor.
12
+ *
13
+ * @param ITSEC_Admin_Notice $notice
14
+ * @param int $ttl
15
+ */
16
+ public function __construct( ITSEC_Admin_Notice $notice, $ttl ) {
17
+ $this->notice = $notice;
18
+ $this->ttl = $ttl;
19
+ }
20
+
21
+ public function get_id() {
22
+ return $this->notice->get_id();
23
+ }
24
+
25
+ public function get_title() {
26
+ return $this->notice->get_title();
27
+ }
28
+
29
+ public function get_message() {
30
+ return $this->notice->get_message();
31
+ }
32
+
33
+ public function get_meta() {
34
+ return $this->notice->get_meta();
35
+ }
36
+
37
+ public function get_severity() {
38
+ return $this->notice->get_severity();
39
+ }
40
+
41
+ public function show_for_context( ITSEC_Admin_Notice_Context $context ) {
42
+ $storage = $this->get_storage();
43
+
44
+ if ( isset( $storage[ $this->get_id() ] ) && $storage[ $this->get_id() ] + $this->ttl > ITSEC_Core::get_current_time_gmt() ) {
45
+ return false;
46
+ }
47
+
48
+ return $this->notice->show_for_context( $context );
49
+ }
50
+
51
+ public function get_actions() {
52
+ return array_merge( $this->notice->get_actions(), array(
53
+ 'remind_me' => new ITSEC_Admin_Notice_Action_Callback(
54
+ ITSEC_Admin_Notice_Action::S_CLOSE,
55
+ esc_html__( 'Remind Me Later', 'better-wp-security' ),
56
+ array( $this, '_handle_remind_me' )
57
+ )
58
+ ) );
59
+ }
60
+
61
+ public function _handle_remind_me() {
62
+ $storage = $this->get_storage();
63
+
64
+ $storage[ $this->get_id() ] = ITSEC_Core::get_current_time_gmt();
65
+ $this->save_storage( $storage );
66
+
67
+ return null;
68
+ }
69
+
70
+ private function get_storage() {
71
+ $dismissed = get_site_option( 'itsec_remind_me_notices', array() );
72
+
73
+ if ( ! is_array( $dismissed ) ) {
74
+ $dismissed = array();
75
+ }
76
+
77
+ return $dismissed;
78
+ }
79
+
80
+ private function save_storage( $storage ) {
81
+ update_site_option( 'itsec_remind_me_notices', $storage );
82
+ }
83
+ }
core/lib/admin-notices/class-itsec-admin-notice-screen-blacklist.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Admin_Notice_Screen_Blacklist implements ITSEC_Admin_Notice {
4
+
5
+ /** @var ITSEC_Admin_Notice */
6
+ private $notice;
7
+
8
+ /** @var string[] */
9
+ private $blacklist;
10
+
11
+ /**
12
+ * ITSEC_Admin_Notice_Screen_Blacklist constructor.
13
+ *
14
+ * @param ITSEC_Admin_Notice $notice
15
+ * @param string[] $blacklist Screen IDs to blacklist.
16
+ */
17
+ public function __construct( ITSEC_Admin_Notice $notice, array $blacklist ) {
18
+ $this->notice = $notice;
19
+ $this->blacklist = $blacklist;
20
+ }
21
+
22
+ public function get_id() {
23
+ return $this->notice->get_id();
24
+ }
25
+
26
+ public function get_title() {
27
+ return $this->notice->get_title();
28
+ }
29
+
30
+ public function get_message() {
31
+ return $this->notice->get_message();
32
+ }
33
+
34
+ public function get_severity() {
35
+ return $this->notice->get_severity();
36
+ }
37
+
38
+ public function get_meta() {
39
+ return $this->notice->get_meta();
40
+ }
41
+
42
+ public function show_for_context( ITSEC_Admin_Notice_Context $context ) {
43
+ if ( in_array( $context->get_screen_id(), $this->blacklist, true ) ) {
44
+ return false;
45
+ }
46
+
47
+ return $this->notice->show_for_context( $context );
48
+ }
49
+
50
+ public function get_actions() {
51
+ return $this->notice->get_actions();
52
+ }
53
+ }
core/lib/admin-notices/class-itsec-admin-notice-static.php ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Admin_Notice_Static implements ITSEC_Admin_Notice {
4
+
5
+ private $id;
6
+ private $message;
7
+ private $severity;
8
+ private $title;
9
+
10
+ /**
11
+ * ITSEC_Admin_Notice_Concrete constructor.
12
+ *
13
+ * @param string $id Globally unique notice ID.
14
+ * @param string $message The message to display to the user.
15
+ * @param string $title
16
+ * @param string $severity The message's severity.
17
+ */
18
+ public function __construct( $id, $message, $title = '', $severity = self::S_INFO ) {
19
+ $this->id = $id;
20
+ $this->message = $message;
21
+ $this->severity = $severity;
22
+ $this->title = $title;
23
+ }
24
+
25
+ public function get_id() {
26
+ return $this->id;
27
+ }
28
+
29
+ public function get_title() {
30
+ return $this->title;
31
+ }
32
+
33
+ public function get_message() {
34
+ return $this->message;
35
+ }
36
+
37
+ public function get_severity() {
38
+ return $this->severity;
39
+ }
40
+
41
+ public function get_meta() {
42
+ return array();
43
+ }
44
+
45
+ public function show_for_context( ITSEC_Admin_Notice_Context $context ) {
46
+ return true;
47
+ }
48
+
49
+ public function get_actions() {
50
+ return array();
51
+ }
52
+ }
core/lib/admin-notices/class-itsec-admin-notice-user-dismissible.php ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Admin_Notice_User_Dismissible implements ITSEC_Admin_Notice {
4
+ /** @var ITSEC_Admin_Notice */
5
+ private $notice;
6
+
7
+ /**
8
+ * ITSEC_Admin_Notice_Dismissible constructor.
9
+ *
10
+ * @param ITSEC_Admin_Notice $notice
11
+ */
12
+ public function __construct( ITSEC_Admin_Notice $notice ) { $this->notice = $notice; }
13
+
14
+ public function get_id() {
15
+ return $this->notice->get_id();
16
+ }
17
+
18
+ public function get_title() {
19
+ return $this->notice->get_title();
20
+ }
21
+
22
+ public function get_message() {
23
+ return $this->notice->get_message();
24
+ }
25
+
26
+ public function get_severity() {
27
+ return $this->notice->get_severity();
28
+ }
29
+
30
+ public function get_meta() {
31
+ return $this->notice->get_meta();
32
+ }
33
+
34
+ public function show_for_context( ITSEC_Admin_Notice_Context $context ) {
35
+ $dismissed = $this->get_storage( $context->get_user() );
36
+
37
+
38
+ if ( in_array( $this->get_id(), $dismissed, true ) ) {
39
+ return false;
40
+ }
41
+
42
+ return $this->notice->show_for_context( $context );
43
+ }
44
+
45
+ public function get_actions() {
46
+ return array_merge( $this->notice->get_actions(), array(
47
+ 'dismiss' => new ITSEC_Admin_Notice_Action_Callback(
48
+ ITSEC_Admin_Notice_Action::S_CLOSE,
49
+ esc_html__( 'Dismiss', 'better-wp-security' ),
50
+ array( $this, '_handle_dismiss' )
51
+ ),
52
+ ) );
53
+ }
54
+
55
+ public function _handle_dismiss( WP_User $user, array $data ) {
56
+ $dismissed = $this->get_storage( $user );
57
+ $dismissed[] = $this->get_id();
58
+ $this->save_storage( $user, $dismissed );
59
+
60
+ return true;
61
+ }
62
+
63
+ private function get_storage( WP_User $user ) {
64
+ $dismissed = get_user_meta( $user->ID, '_itsec_dismissed_notices', array() );
65
+
66
+ if ( ! is_array( $dismissed ) ) {
67
+ $dismissed = array();
68
+ }
69
+
70
+ return $dismissed;
71
+ }
72
+
73
+ private function save_storage( WP_User $user, $storage ) {
74
+ update_user_meta( $user->ID, '_itsec_dismissed_notices', $storage );
75
+ }
76
+ }
core/lib/admin-notices/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/lib/admin-notices/interface-itsec-admin-notice.php ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ interface ITSEC_Admin_Notice {
4
+
5
+ const S_ERROR = 'error';
6
+ const S_WARN = 'warning';
7
+ const S_INFO = 'info';
8
+ const S_SUCCESS = 'success';
9
+
10
+ const M_CREATED_AT = 'created_at';
11
+
12
+ /**
13
+ * Get a unique id for this notice.
14
+ *
15
+ * @return string|int
16
+ */
17
+ public function get_id();
18
+
19
+ /**
20
+ * Get the title or headline for a notice.
21
+ *
22
+ * May be an empty string.
23
+ *
24
+ * @return string
25
+ */
26
+ public function get_title();
27
+
28
+ /**
29
+ * Get the message to display in the notice.
30
+ *
31
+ * May contain HTML.
32
+ *
33
+ * @return string
34
+ */
35
+ public function get_message();
36
+
37
+ /**
38
+ * Get meta information.
39
+ *
40
+ * Expected format:
41
+ * Array of the following keyed by a unique slug.
42
+ * - string $label
43
+ * - string $value
44
+ * - string $formatted
45
+ *
46
+ * @return array[]
47
+ */
48
+ public function get_meta();
49
+
50
+ /**
51
+ * Get the severity level of the notice.
52
+ *
53
+ * @return string
54
+ */
55
+ public function get_severity();
56
+
57
+ /**
58
+ * Should the notice be displayed to the given user.
59
+ *
60
+ * @param ITSEC_Admin_Notice_Context $context
61
+ *
62
+ * @return bool
63
+ */
64
+ public function show_for_context( ITSEC_Admin_Notice_Context $context );
65
+
66
+ /**
67
+ * Get a list of all the available actions that can be taken on this notice.
68
+ *
69
+ * @return ITSEC_Admin_Notice_Action[]
70
+ */
71
+ public function get_actions();
72
+ }
core/lib/class-itsec-lib-admin-notices.php ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once( dirname( __FILE__ ) . '/admin-notices/class-itsec-admin-notice-context.php' );
4
+ require_once( dirname( __FILE__ ) . '/admin-notices/interface-itsec-admin-notice.php' );
5
+ require_once( dirname( __FILE__ ) . '/admin-notices/class-itsec-admin-notice-callback.php' );
6
+ require_once( dirname( __FILE__ ) . '/admin-notices/class-itsec-admin-notice-globally-dismissible.php' );
7
+ require_once( dirname( __FILE__ ) . '/admin-notices/class-itsec-admin-notice-highlighted-log.php' );
8
+ require_once( dirname( __FILE__ ) . '/admin-notices/class-itsec-admin-notice-managers-only.php' );
9
+ require_once( dirname( __FILE__ ) . '/admin-notices/class-itsec-admin-notice-remind-me.php' );
10
+ require_once( dirname( __FILE__ ) . '/admin-notices/class-itsec-admin-notice-screen-blacklist.php' );
11
+ require_once( dirname( __FILE__ ) . '/admin-notices/class-itsec-admin-notice-static.php' );
12
+ require_once( dirname( __FILE__ ) . '/admin-notices/class-itsec-admin-notice-user-dismissible.php' );
13
+
14
+ require_once( dirname( __FILE__ ) . '/admin-notices/actions/interface-itsec-admin-notice-action.php' );
15
+ require_once( dirname( __FILE__ ) . '/admin-notices/actions/class-itsec-admin-notice-action-link.php' );
16
+ require_once( dirname( __FILE__ ) . '/admin-notices/actions/class-itsec-admin-notice-action-callback.php' );
17
+
18
+ class ITSEC_Lib_Admin_Notices {
19
+
20
+ const META = '_itsec_dismissed_notices';
21
+
22
+ /** @var ITSEC_Admin_Notice[] */
23
+ private static $notices = array();
24
+
25
+ /** @var bool */
26
+ private static $initialized = false;
27
+
28
+ /**
29
+ * Register a notice to be displayed.
30
+ *
31
+ * @param ITSEC_Admin_Notice $notice
32
+ */
33
+ public static function register( ITSEC_Admin_Notice $notice ) {
34
+ self::$notices[] = $notice;
35
+ }
36
+
37
+ /**
38
+ * Get all notices for the given user.
39
+ *
40
+ * @param ITSEC_Admin_Notice_Context $context
41
+ *
42
+ * @return ITSEC_Admin_Notice[]
43
+ */
44
+ public static function get_notices( ITSEC_Admin_Notice_Context $context = null ) {
45
+ self::initialize();
46
+
47
+ $context = $context ? $context : ITSEC_Admin_Notice_Context::from_global_state();
48
+ $notices = array();
49
+
50
+ foreach ( self::$notices as $notice ) {
51
+ if ( $notice->show_for_context( $context ) ) {
52
+ $notices[] = $notice;
53
+ }
54
+ }
55
+
56
+ return $notices;
57
+ }
58
+
59
+ /**
60
+ * Lazily initialize the admin notices lib.
61
+ *
62
+ * Will load the notices.php file for all active modules, and create notices
63
+ * for each highlighted log item.
64
+ */
65
+ private static function initialize() {
66
+ if ( ! self::$initialized ) {
67
+ ITSEC_Lib::load( 'highlighted-logs' );
68
+
69
+ foreach ( ITSEC_Lib_Highlighted_Logs::get_highlights() as $id => $highlight ) {
70
+ self::register(
71
+ new ITSEC_Admin_Notice_Managers_Only(
72
+ new ITSEC_Admin_Notice_Screen_Blacklist(
73
+ new ITSEC_Admin_Notice_Highlighted_Log( $id, $highlight ),
74
+ array( 'security_page_itsec-logs' )
75
+ )
76
+ )
77
+ );
78
+ }
79
+
80
+ ITSEC_Modules::load_module_file( 'notices.php', ':active' );
81
+
82
+ self::$initialized = true;
83
+ }
84
+ }
85
+ }
core/lib/class-itsec-lib-feature-flags.php ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Lib_Feature_Flags {
4
+
5
+ /** @var bool */
6
+ private static $loaded = false;
7
+
8
+ /** @var array */
9
+ private static $flags = array();
10
+
11
+ /**
12
+ * Register a feature flag.
13
+ *
14
+ * @param string $name
15
+ * @param array $args
16
+ */
17
+ public static function register_flag( $name, $args = array() ) {
18
+ self::$flags[ $name ] = wp_parse_args( $args, array(
19
+ 'rate' => false,
20
+ 'remote' => false,
21
+ 'title' => '',
22
+ 'description' => '',
23
+ ) );
24
+ }
25
+
26
+ /**
27
+ * Get a list of all the available feature flags.
28
+ *
29
+ * @return array
30
+ */
31
+ public static function get_available_flags() {
32
+ self::load();
33
+
34
+ $flags = array();
35
+
36
+ foreach ( self::$flags as $flag => $_ ) {
37
+ $flags[ $flag ] = self::get_flag_config( $flag );
38
+ }
39
+
40
+ return $flags;
41
+ }
42
+
43
+ /**
44
+ * Get a list of all the enabled feature flags.
45
+ *
46
+ * @return string[]
47
+ */
48
+ public static function get_enabled() {
49
+ $enabled = array();
50
+
51
+ foreach ( self::get_available_flags() as $flag => $_ ) {
52
+ if ( self::is_enabled( $flag ) ) {
53
+ $enabled[] = $flag;
54
+ }
55
+ }
56
+
57
+ return $enabled;
58
+ }
59
+
60
+ /**
61
+ * Check if a flag is enabled.
62
+ *
63
+ * @param string $flag
64
+ *
65
+ * @return bool
66
+ */
67
+ public static function is_enabled( $flag ) {
68
+ if ( ! $config = self::get_flag_config( $flag ) ) {
69
+ return false;
70
+ }
71
+
72
+ if ( defined( 'ITSEC_FF_' . $flag ) ) {
73
+ // A constant overrules everything.
74
+ return (bool) constant( 'ITSEC_FF_' . $flag );
75
+ }
76
+
77
+ $flags = ITSEC_Modules::get_setting( 'global', 'feature_flags' );
78
+
79
+ if ( ! empty( $flags[ $flag ]['enabled'] ) ) {
80
+ // If the flag is set as enabled, then enable it.
81
+ return true;
82
+ }
83
+
84
+ // If this is a gradual roll-out.
85
+ if ( $rate = $config['rate'] ) {
86
+ // If the flag has been manually disabled with `ITSEC_Lib_Feature_Flags::disable()`, then exclude them from the feature.
87
+ if ( isset( $flags[ $flag ]['enabled'] ) && ! $flags[ $flag ]['enabled'] && ! isset( $flags[ $flag ]['rate'] ) ) {
88
+ return false;
89
+ }
90
+
91
+ // If the rice haven't been rolled, or the rate has changed since the last run, roll the dice.
92
+ if ( ! isset( $flags[ $flag ]['rate'] ) || $flags[ $flag ]['rate'] !== $rate ) {
93
+ $enabled = mt_rand( 1, 100 ) <= $rate;
94
+
95
+ $flags[ $flag ] = array(
96
+ 'enabled' => $enabled,
97
+ 'time' => ITSEC_Core::get_current_time_gmt(),
98
+ 'rate' => $rate,
99
+ );
100
+
101
+ ITSEC_Modules::set_setting( 'global', 'feature_flags', $flags );
102
+
103
+ if ( $enabled ) {
104
+ return true;
105
+ }
106
+ }
107
+ }
108
+
109
+ return false;
110
+ }
111
+
112
+ /**
113
+ * Manually enable a feature flag.
114
+ *
115
+ * @param string $flag
116
+ */
117
+ public static function enable( $flag ) {
118
+ $flags = ITSEC_Modules::get_setting( 'global', 'feature_flags' );
119
+
120
+ $flags[ $flag ] = array(
121
+ 'enabled' => true,
122
+ 'time' => ITSEC_Core::get_current_time_gmt(),
123
+ );
124
+
125
+ ITSEC_Modules::set_setting( 'global', 'feature_flags', $flags );
126
+ }
127
+
128
+ /**
129
+ * Manually disable a feature flag.
130
+ *
131
+ * @param string $flag
132
+ */
133
+ public static function disable( $flag ) {
134
+ $flags = ITSEC_Modules::get_setting( 'global', 'feature_flags' );
135
+
136
+ $flags[ $flag ] = array(
137
+ 'enabled' => false,
138
+ 'time' => ITSEC_Core::get_current_time_gmt(),
139
+ );
140
+
141
+ ITSEC_Modules::set_setting( 'global', 'feature_flags', $flags );
142
+ }
143
+
144
+ /**
145
+ * Get the flag configuration.
146
+ *
147
+ * @param string $flag
148
+ *
149
+ * @return array|null
150
+ */
151
+ public static function get_flag_config( $flag ) {
152
+ self::load();
153
+
154
+
155
+ if ( ! isset( self::$flags[ $flag ] ) ) {
156
+ return null;
157
+ }
158
+
159
+ $config = self::$flags[ $flag ];
160
+
161
+ if ( $config['remote'] && $remote = ITSEC_Lib_Remote_Messages::get_feature( $flag ) ) {
162
+ $config = array_merge( $config, $remote );
163
+ }
164
+
165
+ return $config;
166
+ }
167
+
168
+ private static function load() {
169
+ if ( ! self::$loaded ) {
170
+ ITSEC_Modules::load_module_file( 'feature-flags.php', ':active' );
171
+ self::$loaded = true;
172
+ }
173
+ }
174
+ }
core/lib/class-itsec-lib-highlighted-logs.php ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Lib_Highlighted_Logs {
4
+
5
+ const OPTION = 'itsec_highlighted_logs';
6
+
7
+ /** @var array */
8
+ private static $dynamics = array();
9
+
10
+ /** @var bool */
11
+ private static $initialized = false;
12
+
13
+ /**
14
+ * Is the highlighted logs library supported.
15
+ *
16
+ * This can only work when log items are stored in the database.
17
+ *
18
+ * @return bool
19
+ */
20
+ public static function is_supported() {
21
+ return 'file' !== ITSEC_Modules::get_setting( 'global', 'log_type' );
22
+ }
23
+
24
+ /**
25
+ * Register a dynamic highlight.
26
+ *
27
+ * Dynamic highlights display the latest item that matches the given query.
28
+ * Only log items that are more recent since the dynamic highlight has been
29
+ * dismissed are included.
30
+ *
31
+ * @param string $slug Unique identifier for this highlight.
32
+ * @param array $query Filters list.
33
+ */
34
+ public static function register_dynamic_highlight( $slug, array $query ) {
35
+ self::$dynamics[ $slug ] = $query;
36
+ }
37
+
38
+ /**
39
+ * Highlight an individual log item.
40
+ *
41
+ * @param int $id The log item id.
42
+ *
43
+ * @return bool
44
+ */
45
+ public static function highlight( $id ) {
46
+ $storage = self::get_storage();
47
+
48
+ $storage['highlighted'][ $id ] = true;
49
+
50
+ return self::save_storage( $storage );
51
+ }
52
+
53
+ /**
54
+ * Get a list of all the log items that are highlighted.
55
+ *
56
+ * The full log data entry is not available, just the summary columns
57
+ *
58
+ * @return array[]
59
+ */
60
+ public static function get_highlights() {
61
+ $storage = self::get_storage();
62
+
63
+ $items = array();
64
+
65
+ foreach ( self::get_dynamics() as $slug => $highlight ) {
66
+ if ( isset( $storage['muted'][ $slug ] ) ) {
67
+ continue;
68
+ }
69
+
70
+ $filters = $highlight;
71
+
72
+ if ( isset( $storage['markers'][ $slug ] ) ) {
73
+ $filters['__min_timestamp'] = $storage['markers'][ $slug ];
74
+ }
75
+
76
+ if ( $entries = ITSEC_Log::get_entries( $filters, 1, 1, 'id', 'DESC', 'all' ) ) {
77
+ $items[ $slug ] = $entries[0];
78
+ }
79
+ }
80
+
81
+ if ( $storage['highlighted'] ) {
82
+ $entries = ITSEC_Log::get_entries( array( 'id' => array_flip( $storage['highlighted'] ) ) );
83
+
84
+ $items = array_merge( $items, $entries );
85
+ }
86
+
87
+ return $items;
88
+ }
89
+
90
+ /**
91
+ * Dismiss a highlighted log item.
92
+ *
93
+ * @param int|string $id_or_slug Either the log id or the dynamic highlight slug.
94
+ *
95
+ * @return bool
96
+ */
97
+ public static function dismiss_highlight( $id_or_slug ) {
98
+ $storage = self::get_storage();
99
+
100
+ if ( is_int( $id_or_slug ) ) {
101
+ if ( isset( $storage['highlighted'][ $id_or_slug ] ) ) {
102
+ unset( $storage['highlighted'][ $id_or_slug ] );
103
+
104
+ return self::save_storage( $storage );
105
+ }
106
+
107
+ return true;
108
+ }
109
+
110
+ $storage['markers'][ $id_or_slug ] = ITSEC_Core::get_current_time_gmt();
111
+
112
+ return self::save_storage( $storage );
113
+ }
114
+
115
+ /**
116
+ * Mute a dynamic highlighted log.
117
+ *
118
+ * Muted items won't ever be returned in {@see ITSEC_Lib_Highlighted_Logs::get_highlights()}.
119
+ *
120
+ * @param string $slug Dynamic highlight slug.
121
+ *
122
+ * @return bool
123
+ */
124
+ public static function mute( $slug ) {
125
+ $storage = self::get_storage();
126
+
127
+ $storage['muted'][ $slug ] = ITSEC_Core::get_current_time_gmt();
128
+
129
+ return self::save_storage( $storage );
130
+ }
131
+
132
+ /**
133
+ * Unmute a dynamic highlighted log.
134
+ *
135
+ * @param string $slug Dynamic highlight slug.
136
+ *
137
+ * @return bool
138
+ */
139
+ public static function unmute( $slug ) {
140
+ $storage = self::get_storage();
141
+
142
+ unset( $storage['muted'][ $slug ] );
143
+
144
+ return self::save_storage( $storage );
145
+ }
146
+
147
+ /**
148
+ * Is the given dynamic highlight muted.
149
+ *
150
+ * @param string $slug
151
+ *
152
+ * @return bool
153
+ */
154
+ public static function is_muted( $slug ) {
155
+ $storage = self::get_storage();
156
+
157
+ return ! empty( $storage['muted'][ $slug ] );
158
+ }
159
+
160
+ /**
161
+ * Get a list of all the registered dynamic highlights.
162
+ *
163
+ * On first call, this will fire an action to register the highlights.
164
+ *
165
+ * @return array
166
+ */
167
+ public static function get_dynamics() {
168
+ if ( ! self::$initialized ) {
169
+ do_action( 'itsec_register_highlighted_logs' );
170
+
171
+ self::$initialized = true;
172
+ }
173
+
174
+ return self::$dynamics;
175
+ }
176
+
177
+ /**
178
+ * Get the storage data.
179
+ *
180
+ * @return array
181
+ */
182
+ private static function get_storage() {
183
+ return get_site_option( self::OPTION, array(
184
+ 'highlighted' => array(),
185
+ 'markers' => array(),
186
+ 'muted' => array(),
187
+ ) );
188
+ }
189
+
190
+ /**
191
+ * Update the storage data.
192
+ *
193
+ * @param array $storage
194
+ *
195
+ * @return bool
196
+ */
197
+ private static function save_storage( array $storage ) {
198
+ return update_site_option( self::OPTION, $storage );
199
+ }
200
+ }
core/lib/class-itsec-lib-remote-messages.php CHANGED
@@ -29,6 +29,12 @@ class ITSEC_Lib_Remote_Messages {
29
  return in_array( $action, self::get_actions(), true );
30
  }
31
 
 
 
 
 
 
 
32
  public static function get_raw_messages() {
33
  $response = self::get_response();
34
 
@@ -88,6 +94,7 @@ class ITSEC_Lib_Remote_Messages {
88
  'ttl' => HOUR_IN_SECONDS,
89
  'messages' => array(),
90
  'actions' => array(),
 
91
  ) );
92
 
93
  $sanitized = array(
@@ -132,12 +139,7 @@ class ITSEC_Lib_Remote_Messages {
132
  return self::$_response;
133
  }
134
 
135
- $data = get_site_option( self::OPTION, array() );
136
- $data = wp_parse_args( $data, array(
137
- 'response' => array(),
138
- 'requested' => 0,
139
- 'ttl' => 0,
140
- ) );
141
 
142
  if ( ! $data['response'] ) {
143
  self::schedule_check();
@@ -150,8 +152,21 @@ class ITSEC_Lib_Remote_Messages {
150
  $events = ITSEC_Core::get_scheduler()->get_single_events();
151
 
152
  foreach ( $events as $event ) {
153
- if ( self::EVENT === $event['id'] && $event['fire_at'] + HOUR_IN_SECONDS > ITSEC_Core::get_current_time_gmt() ) {
154
- return self::$_response = $data['response'];
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  }
156
  }
157
 
@@ -161,6 +176,17 @@ class ITSEC_Lib_Remote_Messages {
161
  return self::$_response = $data['response'];
162
  }
163
 
 
 
 
 
 
 
 
 
 
 
 
164
  private static function schedule_check() {
165
  $s = ITSEC_Core::get_scheduler();
166
 
29
  return in_array( $action, self::get_actions(), true );
30
  }
31
 
32
+ public static function get_feature( $flag ) {
33
+ $response = self::get_response();
34
+
35
+ return isset( $response['features'][ $flag ] ) ? $response['features'][ $flag ] : null;
36
+ }
37
+
38
  public static function get_raw_messages() {
39
  $response = self::get_response();
40
 
94
  'ttl' => HOUR_IN_SECONDS,
95
  'messages' => array(),
96
  'actions' => array(),
97
+ 'features' => array(),
98
  ) );
99
 
100
  $sanitized = array(
139
  return self::$_response;
140
  }
141
 
142
+ $data = self::get_stored_response();
 
 
 
 
 
143
 
144
  if ( ! $data['response'] ) {
145
  self::schedule_check();
152
  $events = ITSEC_Core::get_scheduler()->get_single_events();
153
 
154
  foreach ( $events as $event ) {
155
+ // If we are less than an hour late for processing the refresh, return the stale data.
156
+ if ( self::EVENT === $event['id'] ) {
157
+ if ( $event['fire_at'] + HOUR_IN_SECONDS > ITSEC_Core::get_current_time_gmt() ) {
158
+ return self::$_response = $data['response'];
159
+ }
160
+
161
+ // If its been more than a day, call the API right now.
162
+ if ( $event['fire_at'] + DAY_IN_SECONDS > ITSEC_Core::get_current_time_gmt() ) {
163
+ ITSEC_Core::get_scheduler()->run_single_event( self::EVENT );
164
+ $data = self::get_stored_response();
165
+
166
+ if ( $data['requested'] === ITSEC_Core::get_current_time_gmt() ) {
167
+ return self::$_response = $data['response'];
168
+ }
169
+ }
170
  }
171
  }
172
 
176
  return self::$_response = $data['response'];
177
  }
178
 
179
+ private static function get_stored_response() {
180
+ $data = get_site_option( self::OPTION, array() );
181
+ $data = wp_parse_args( $data, array(
182
+ 'response' => array(),
183
+ 'requested' => 0,
184
+ 'ttl' => 0,
185
+ ) );
186
+
187
+ return $data;
188
+ }
189
+
190
  private static function schedule_check() {
191
  $s = ITSEC_Core::get_scheduler();
192
 
core/lib/class-itsec-mail.php CHANGED
@@ -469,6 +469,14 @@ final class ITSEC_Mail {
469
  $this->add_html( $deferred, $group );
470
  }
471
 
 
 
 
 
 
 
 
 
472
  /**
473
  * Include debug info in the email.
474
  *
469
  $this->add_html( $deferred, $group );
470
  }
471
 
472
+ public function insert_before( $identifier, $html ) {
473
+ $this->groups = ITSEC_Lib::array_insert_before( $identifier, $this->groups, count( $this->groups ), $html );
474
+ }
475
+
476
+ public function insert_after( $identifier, $html ) {
477
+ $this->groups = ITSEC_Lib::array_insert_after( $identifier, $this->groups, count( $this->groups ), $html );
478
+ }
479
+
480
  /**
481
  * Include debug info in the email.
482
  *
core/lib/log.php CHANGED
@@ -221,7 +221,7 @@ final class ITSEC_Log {
221
 
222
  $entries = ITSEC_Log_Util::get_entries( array( 'id' => $id ), 0, 1, 'id', 'DESC', 'all' );
223
 
224
- return $entries[0];
225
  }
226
 
227
  public static function get_number_of_entries( $filters = array() ) {
221
 
222
  $entries = ITSEC_Log_Util::get_entries( array( 'id' => $id ), 0, 1, 'id', 'DESC', 'all' );
223
 
224
+ return isset( $entries[0] ) ? $entries[0] : array();
225
  }
226
 
227
  public static function get_number_of_entries( $filters = array() ) {
core/modules/core/active.php CHANGED
@@ -1,5 +1,13 @@
1
  <?php
2
- // Set up Content Directory Admin
3
- require_once( 'class-itsec-core-admin.php' );
4
- $itsec_core_admin = new ITSEC_Core_Admin();
5
- $itsec_core_admin->run();
 
 
 
 
 
 
 
 
1
  <?php
2
+ require_once( dirname( __FILE__ ) . '/class-itsec-core-active.php' );
3
+ require_once( dirname( __FILE__ ) . '/class-itsec-core-admin.php' );
4
+ require_once( dirname( __FILE__ ) . '/class-itsec-admin-notices.php' );
5
+
6
+ $active = new ITSEC_Core_Active();
7
+ $active->run();
8
+
9
+ $admin = new ITSEC_Core_Admin();
10
+ $admin->run();
11
+
12
+ $notices = new ITSEC_Admin_Notices();
13
+ $notices->run();
core/modules/core/class-itsec-admin-notices.php ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Admin_Notices {
4
+ const ACTION = 'itsec-admin-notice';
5
+
6
+ /** @var WP_Error[] */
7
+ private $errors = array();
8
+
9
+ public function run() {
10
+ add_action( 'rest_api_init', array( $this, 'rest_api_init' ) );
11
+ add_action( 'wp_ajax_' . self::ACTION, array( $this, 'handle_ajax' ) );
12
+
13
+ if ( isset( $_GET['action'] ) && self::ACTION === $_GET['action'] ) {
14
+ add_action( 'admin_init', array( $this, 'handle_admin_action' ) );
15
+ }
16
+
17
+ if ( ITSEC_Modules::get_setting( 'global', 'hide_admin_bar' ) || version_compare( get_bloginfo( 'version' ), '5.0', '<' ) ) {
18
+ if ( is_multisite() ) {
19
+ add_action( 'network_admin_notices', array( $this, 'display_notices' ) );
20
+ } else {
21
+ add_action( 'admin_notices', array( $this, 'display_notices' ) );
22
+ }
23
+ }
24
+ }
25
+
26
+ public function rest_api_init() {
27
+ require_once( dirname( __FILE__ ) . '/class-rest-core-admin-notices-controller.php' );
28
+
29
+ $controller = new ITSEC_REST_Core_Admin_Notices_Controller();
30
+ $controller->register_routes();
31
+ }
32
+
33
+ public function display_notices() {
34
+ foreach ( $this->errors as $error ) {
35
+ echo '<div class="notice notice-error is-dismissible"><p>' . $error->get_error_message() . '</p></div>';
36
+ }
37
+
38
+ $hide = array( 'dashboard_page_itsec-dashboard', 'index_page_itsec-dashboard' );
39
+
40
+ if ( in_array( get_current_screen()->id, $hide, true ) ) {
41
+ return;
42
+ }
43
+
44
+ ITSEC_Lib::load( 'admin-notices' );
45
+ $notices = ITSEC_Lib_Admin_Notices::get_notices();
46
+
47
+ if ( ! $notices ) {
48
+ return;
49
+ }
50
+
51
+ $nonce = wp_create_nonce( self::ACTION );
52
+
53
+ wp_enqueue_script( 'itsec-admin-notices', plugin_dir_url( __FILE__ ) . 'js/admin-notices.js', array( 'jquery', 'wp-util' ) );
54
+ wp_localize_script( 'itsec-admin-notices', 'ITSECAdminNotices', array(
55
+ 'nonce' => $nonce,
56
+ ) );
57
+
58
+ foreach ( $notices as $notice ) {
59
+ $data = 'data-id="' . esc_attr( $notice->get_id() ) . '"';
60
+ $classes = array(
61
+ 'itsec-notice',
62
+ 'notice',
63
+ 'notice-' . esc_attr( $notice->get_severity() ),
64
+ );
65
+
66
+ foreach ( $notice->get_actions() as $slug => $action ) {
67
+ if ( ITSEC_Admin_Notice_Action::S_CLOSE === $action->get_style() ) {
68
+ $classes[] = 'is-dismissible';
69
+ $data .= ' data-close="' . esc_attr( $slug ) . '"';
70
+ break;
71
+ }
72
+ }
73
+
74
+ echo '<div class="' . implode( ' ', $classes ) . '"' . $data . '>';
75
+ $html = trim( $notice->get_title() . ' ' . $notice->get_message() );
76
+
77
+ foreach ( $notice->get_actions() as $slug => $action ) {
78
+ if ( ITSEC_Admin_Notice_Action::S_CLOSE === $action->get_style() ) {
79
+ continue;
80
+ }
81
+
82
+ $html .= ' ';
83
+
84
+ if ( $action->get_uri() ) {
85
+ switch ( $action->get_style() ) {
86
+ case ITSEC_Admin_Notice_Action::S_BUTTON:
87
+ $class = 'button';
88
+ break;
89
+ case ITSEC_Admin_Notice_Action::S_PRIMARY:
90
+ $class = 'button button-primary';
91
+ break;
92
+ default:
93
+ $class = '';
94
+ break;
95
+ }
96
+
97
+ $href = add_query_arg( array(
98
+ 'action' => self::ACTION,
99
+ 'notice_id' => $notice->get_id(),
100
+ 'itsec_action' => $slug,
101
+ 'nonce' => $nonce,
102
+ ), $action->get_uri() );
103
+
104
+ $html .= '<a href="' . esc_url( $href ) . '" class="' . esc_attr( $class ) . '">';
105
+ $html .= $action->get_title();
106
+ $html .= '</a>';
107
+ } else {
108
+ switch ( $action->get_style() ) {
109
+ case ITSEC_Admin_Notice_Action::S_BUTTON:
110
+ $class = 'button';
111
+ break;
112
+ case ITSEC_Admin_Notice_Action::S_PRIMARY:
113
+ $class = 'button button-primary';
114
+ break;
115
+ case ITSEC_Admin_Notice_Action::S_LINK:
116
+ $class = 'button-link';
117
+ break;
118
+ default:
119
+ $class = '';
120
+ break;
121
+ }
122
+
123
+ $html .= '<button data-action="' . esc_attr( $slug ) . '" class="' . esc_attr( $class ) . '">';
124
+ $html .= $action->get_title();
125
+ $html .= '</button>';
126
+ }
127
+ }
128
+
129
+ echo wpautop( $html );
130
+
131
+ echo '</div>';
132
+ }
133
+ }
134
+
135
+ public function handle_ajax() {
136
+ $error = $this->handle_action( $_POST );
137
+
138
+ if ( is_wp_error( $error ) ) {
139
+ wp_send_json_error( $error );
140
+ }
141
+
142
+ wp_send_json_success();
143
+ }
144
+
145
+ public function handle_admin_action() {
146
+ $error = $this->handle_action( $_GET );
147
+
148
+ if ( is_wp_error( $error ) ) {
149
+ $this->errors[] = $error;
150
+ }
151
+ }
152
+
153
+ private function handle_action( $request ) {
154
+ if ( ! isset( $request['notice_id'], $request['itsec_action'], $request['nonce'] ) ) {
155
+ return new WP_Error( 'itsec-admin-notices.invalid-request-format', esc_html__( 'Invalid request format.', 'better-wp-security' ) );
156
+ }
157
+
158
+ if ( ! wp_verify_nonce( $request['nonce'], self::ACTION ) ) {
159
+ return new WP_Error( 'itsec-admin-notices.invalid-nonce', esc_html__( 'Request Expired. Please refresh and try again.', 'better-wp-security' ) );
160
+ }
161
+
162
+ ITSEC_Lib::load( 'admin-notices' );
163
+ $notices = ITSEC_Lib_Admin_Notices::get_notices( new ITSEC_Admin_Notice_Context(
164
+ wp_get_current_user(),
165
+ wp_doing_ajax() ? ITSEC_Admin_Notice_Context::AJAX : ITSEC_Admin_Notice_Context::ADMIN_ACTION
166
+ ) );
167
+
168
+ $notice = null;
169
+ foreach ( $notices as $maybe_notice ) {
170
+ if ( (string) $maybe_notice->get_id() === $request['notice_id'] ) {
171
+ $notice = $maybe_notice;
172
+ break;
173
+ }
174
+ }
175
+
176
+ if ( ! $notice ) {
177
+ return new WP_Error( 'itsec-admin-notices.invalid-notice', esc_html__( 'Notice not found.', 'better-wp-security' ) );
178
+ }
179
+
180
+ $actions = $notice->get_actions();
181
+
182
+ if ( ! isset( $actions[ $request['itsec_action'] ] ) ) {
183
+ return new WP_Error( 'itsec-admin-notices.invalid-action', esc_html__( 'Action not found.', 'better-wp-security' ) );
184
+ }
185
+
186
+ $data = $request;
187
+
188
+ unset( $data['notice_id'], $data['itsec_action'], $data['nonce'], $data['action'] );
189
+
190
+ $error = $actions[ $request['itsec_action'] ]->handle( wp_get_current_user(), $data );
191
+
192
+ if ( is_wp_error( $error ) ) {
193
+ return $error;
194
+ }
195
+
196
+ return null;
197
+ }
198
+ }
core/modules/core/class-itsec-core-active.php ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Core_Active {
4
+
5
+ public function run() {
6
+ add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts' ), 0 );
7
+ add_action( 'login_enqueue_scripts', array( $this, 'register_scripts' ), 0 );
8
+ add_action( 'admin_enqueue_scripts', array( $this, 'register_scripts' ), 0 );
9
+ }
10
+
11
+ public function register_scripts() {
12
+ $dir = ITSEC_Core::get_plugin_dir() . 'dist/';
13
+ $script_debug = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
14
+
15
+ if ( $script_debug && file_exists( $dir . 'manifest-dev.php' ) ) {
16
+ $manifest = require $dir . 'manifest-dev.php';
17
+ } else {
18
+ $manifest = require $dir . 'manifest.php';
19
+ }
20
+
21
+ foreach ( $manifest as $name => $config ) {
22
+ foreach ( $config['files'] as $file ) {
23
+ $handle = $this->name_to_handle( $name );
24
+
25
+ if ( $script_debug && file_exists( $dir . $file ) ) {
26
+ $path = 'dist/' . $file;
27
+ $is_debug = true;
28
+ } else {
29
+ $path = 'dist/' . str_replace( '.', '.min.', $file );
30
+ $is_debug = false;
31
+ }
32
+
33
+ $is_css = ITSEC_Lib::str_ends_with( $file, '.css' );
34
+ $is_js = ! $is_css;
35
+
36
+ if ( $is_debug ) {
37
+ $version = filemtime( $dir . $file );
38
+ } elseif ( $is_js && isset( $config['contentHash']['javascript'] ) ) {
39
+ $version = $config['contentHash']['javascript'];
40
+ } elseif ( $is_css && isset( $config['contentHash']['css/mini-extract'] ) ) {
41
+ $version = $config['contentHash']['css/mini-extract'];
42
+ } else {
43
+ $version = $config['hash'];
44
+ }
45
+
46
+ $deps = $is_js ? $config['dependencies'] : array();
47
+
48
+ if ( $is_css && in_array( 'wp-components', $config['dependencies'], true ) ) {
49
+ $deps[] = 'wp-components';
50
+ }
51
+
52
+ foreach ( $deps as $i => $dep ) {
53
+ if ( ! ITSEC_Lib::str_starts_with( $dep, '@ithemes/security.' ) ) {
54
+ continue;
55
+ }
56
+
57
+ $parts = explode( '.', $dep );
58
+
59
+ $deps[ $i ] = $this->name_to_handle( "{$parts[1]}/{$parts[2]}" );
60
+ }
61
+
62
+ if ( ! $is_debug ) {
63
+ foreach ( array_reverse( $config['vendors'] ) as $vendor ) {
64
+ if ( ! isset( $manifest[ $vendor ] ) ) {
65
+ continue;
66
+ }
67
+
68
+ if ( $is_js && $this->has_js( $manifest[ $vendor ]['files'] ) ) {
69
+ $deps[] = $this->name_to_handle( $vendor );
70
+ } elseif ( $is_css && $this->has_css( $manifest[ $vendor ]['files'] ) ) {
71
+ $deps[] = $this->name_to_handle( $vendor );
72
+ }
73
+ }
74
+ }
75
+
76
+ if ( $is_css ) {
77
+ wp_register_style(
78
+ $handle,
79
+ plugins_url( $path, ITSEC_Core::get_plugin_file() ),
80
+ $deps,
81
+ $version
82
+ );
83
+ } else {
84
+ wp_register_script(
85
+ $handle,
86
+ plugins_url( $path, ITSEC_Core::get_plugin_file() ),
87
+ $deps,
88
+ $version
89
+ );
90
+ }
91
+
92
+ if ( function_exists( 'wp_set_script_translations' ) && ! ITSEC_Core::is_pro() && in_array( 'wp-i18n', $deps, true ) ) {
93
+ wp_set_script_translations( $handle, 'better-wp-security' );
94
+ }
95
+
96
+ if ( $is_js && ! empty( $config['runtime'] ) ) {
97
+ $public_path = esc_js( trailingslashit( plugins_url( 'dist', ITSEC_Core::get_plugin_file() ) ) );
98
+ wp_add_inline_script( $handle, "window.itsecWebpackPublicPath = window.itsecWebpackPublicPath || '{$public_path}';", 'before' );
99
+ }
100
+ }
101
+ }
102
+ }
103
+
104
+ private function has_js( $files ) {
105
+ foreach ( $files as $file ) {
106
+ if ( ITSEC_Lib::str_ends_with( $file, '.js' ) ) {
107
+ return true;
108
+ }
109
+ }
110
+
111
+ return false;
112
+ }
113
+
114
+ private function has_css( $files ) {
115
+ foreach ( $files as $file ) {
116
+ if ( ITSEC_Lib::str_ends_with( $file, '.css' ) ) {
117
+ return true;
118
+ }
119
+ }
120
+
121
+ return false;
122
+ }
123
+
124
+ private function name_to_handle( $name ) {
125
+ $name = str_replace( '/dist/', '/entry/', $name );
126
+
127
+ return 'itsec-' . str_replace( '/', '-', $name );
128
+ }
129
+ }
core/modules/core/class-itsec-core-admin.php CHANGED
@@ -2,17 +2,67 @@
2
 
3
  class ITSEC_Core_Admin {
4
 
5
- function run() {
6
- add_filter( 'itsec_meta_links', array( $this, 'add_plugin_meta_links' ) );
 
 
 
 
7
 
8
  add_action( 'itsec-settings-page-init', array( $this, 'init_settings_page' ) );
9
  add_action( 'itsec-logs-page-init', array( $this, 'init_settings_page' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  }
11
 
12
  public function init_settings_page() {
13
  if ( ! class_exists( 'backupbuddy_api' ) ) {
14
  require_once( dirname( __FILE__ ) . '/sidebar-widget-backupbuddy-cross-promo.php' );
15
  }
 
 
 
 
 
16
  require_once( dirname( __FILE__ ) . '/sidebar-widget-pro-upsell.php' );
17
  require_once( dirname( __FILE__ ) . '/sidebar-widget-sync-cross-promo.php' );
18
  require_once( dirname( __FILE__ ) . '/sidebar-widget-mail-list-signup.php' );
2
 
3
  class ITSEC_Core_Admin {
4
 
5
+ public function run() {
6
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_notices' ) );
7
+ add_action( 'itsec_dashboard_enqueue_scripts', array( $this, 'enqueue_dashboard_notices_integration' ) );
8
+
9
+ add_action( 'admin_bar_menu', array( $this, 'admin_bar' ), 9999 );
10
+ add_action( 'admin_footer', array( $this, 'render_notices_root' ) );
11
 
12
  add_action( 'itsec-settings-page-init', array( $this, 'init_settings_page' ) );
13
  add_action( 'itsec-logs-page-init', array( $this, 'init_settings_page' ) );
14
+
15
+ if ( ! ITSEC_Core::is_pro() ) {
16
+ add_filter( 'itsec_meta_links', array( $this, 'add_plugin_meta_links' ) );
17
+ }
18
+ }
19
+
20
+ public function enqueue_admin_notices() {
21
+ if ( $this->should_render_admin_notices() ) {
22
+ wp_enqueue_script( 'itsec-core-admin-notices' );
23
+ wp_enqueue_style( 'itsec-core-admin-notices' );
24
+ }
25
+ }
26
+
27
+ public function enqueue_dashboard_notices_integration() {
28
+ wp_enqueue_script( 'itsec-core-admin-notices-dashboard-admin-bar' );
29
+ wp_enqueue_style( 'itsec-core-admin-notices-dashboard-admin-bar' );
30
+ wp_enqueue_style( 'itsec-core-admin-notices' );
31
+ }
32
+
33
+ public function render_notices_root() {
34
+ if ( $this->should_render_admin_notices() ) {
35
+ echo '<div id="itsec-admin-notices-root"></div>';
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Register the admin bar menu.
41
+ *
42
+ * @param WP_Admin_Bar $wp_admin_bar
43
+ */
44
+ public function admin_bar( $wp_admin_bar ) {
45
+ if ( is_admin() && $this->should_render_admin_notices() ) {
46
+ $wp_admin_bar->add_node( array(
47
+ 'title' => __( 'Security', 'better-wp-security' ),
48
+ 'id' => 'itsec_admin_bar_menu',
49
+ ) );
50
+ }
51
+ }
52
+
53
+ private function should_render_admin_notices() {
54
+ return ITSEC_Core::current_user_can_manage() && ! ITSEC_Modules::get_setting( 'global', 'hide_admin_bar' );
55
  }
56
 
57
  public function init_settings_page() {
58
  if ( ! class_exists( 'backupbuddy_api' ) ) {
59
  require_once( dirname( __FILE__ ) . '/sidebar-widget-backupbuddy-cross-promo.php' );
60
  }
61
+
62
+ if ( ITSEC_Core::is_pro() ) {
63
+ return;
64
+ }
65
+
66
  require_once( dirname( __FILE__ ) . '/sidebar-widget-pro-upsell.php' );
67
  require_once( dirname( __FILE__ ) . '/sidebar-widget-sync-cross-promo.php' );
68
  require_once( dirname( __FILE__ ) . '/sidebar-widget-mail-list-signup.php' );
core/modules/core/class-rest-core-admin-notices-controller.php ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_REST_Core_Admin_Notices_Controller extends WP_REST_Controller {
4
+
5
+ public function register_routes() {
6
+ register_rest_route( 'ithemes-security/v1', 'admin-notices', array(
7
+ 'methods' => WP_REST_Server::READABLE,
8
+ 'callback' => array( $this, 'get_items' ),
9
+ 'permission_callback' => array( $this, 'get_items_permissions_check' ),
10
+ ) );
11
+
12
+ register_rest_route( 'ithemes-security/v1', 'admin-notices/(?P<notice>[\w\-]+)/(?P<action>[\w\-]+)', array(
13
+ 'methods' => WP_REST_Server::EDITABLE,
14
+ 'callback' => array( $this, 'update_item' ),
15
+ 'permission_callback' => array( $this, 'update_item_permissions_check' ),
16
+ ) );
17
+
18
+ register_rest_route( 'ithemes-security/v1', 'admin-notices/settings', array(
19
+ array(
20
+ 'methods' => WP_REST_Server::READABLE,
21
+ 'callback' => array( $this, 'get_settings' ),
22
+ 'permission_callback' => array( 'ITSEC_Core', 'current_user_can_manage' ),
23
+ ),
24
+ array(
25
+ 'methods' => WP_REST_Server::EDITABLE,
26
+ 'callback' => array( $this, 'update_settings' ),
27
+ 'permission_callback' => array( 'ITSEC_Core', 'current_user_can_manage' ),
28
+ ),
29
+ 'schema' => array( $this, 'get_settings_schema' ),
30
+ ) );
31
+ }
32
+
33
+ public function get_items_permissions_check( $request ) {
34
+ if ( ! is_user_logged_in() ) {
35
+ return new WP_Error( 'not_logged_in', esc_html__( 'You must be logged-in to view notices.', 'better-wp-security' ), array(
36
+ 'status' => WP_Http::UNAUTHORIZED,
37
+ ) );
38
+ }
39
+
40
+ return true;
41
+ }
42
+
43
+ public function get_items( $request ) {
44
+ ITSEC_Lib::load( 'admin-notices' );
45
+
46
+ $prepared = array();
47
+ $context = new ITSEC_Admin_Notice_Context( wp_get_current_user(), 'rest' );
48
+
49
+ foreach ( ITSEC_Lib_Admin_Notices::get_notices( $context ) as $notice ) {
50
+ $prepared[] = $this->prepare_item_for_response( $notice, $request );
51
+ }
52
+
53
+ return $prepared;
54
+ }
55
+
56
+ public function prepare_item_for_response( $item, $request ) {
57
+ if ( ! $item instanceof ITSEC_Admin_Notice ) {
58
+ return array();
59
+ }
60
+
61
+ $notice = array(
62
+ 'id' => $item->get_id(),
63
+ 'title' => $item->get_title(),
64
+ 'message' => $item->get_message(),
65
+ 'severity' => $item->get_severity(),
66
+ 'meta' => $item->get_meta(),
67
+ 'actions' => array(),
68
+ );
69
+
70
+ foreach ( $item->get_actions() as $slug => $action ) {
71
+ if ( $uri = $action->get_uri() ) {
72
+ $uri = add_query_arg( array(
73
+ 'action' => ITSEC_Admin_Notices::ACTION,
74
+ 'notice_id' => $item->get_id(),
75
+ 'itsec_action' => $slug,
76
+ 'nonce' => wp_create_nonce( ITSEC_Admin_Notices::ACTION ),
77
+ ), $uri );
78
+ }
79
+
80
+ $notice['actions'][ $slug ] = array(
81
+ 'title' => $action->get_title(),
82
+ 'style' => $action->get_style(),
83
+ 'uri' => $uri,
84
+ );
85
+ }
86
+
87
+ return $notice;
88
+ }
89
+
90
+ public function update_item( $request ) {
91
+
92
+ $notice = $this->get_notice_for_request( $request );
93
+
94
+ if ( ! $notice ) {
95
+ return new WP_REST_Response( null, 500 );
96
+ }
97
+
98
+ $actions = $notice->get_actions();
99
+ $action = $actions[ $request['action'] ];
100
+
101
+ $error = $action->handle( wp_get_current_user(), $request->get_json_params() ? $request->get_json_params() : $request->get_body_params() );
102
+
103
+ if ( is_wp_error( $error ) ) {
104
+ return $error;
105
+ }
106
+
107
+ return null;
108
+ }
109
+
110
+ public function update_item_permissions_check( $request ) {
111
+ if ( ! is_user_logged_in() ) {
112
+ return new WP_Error( 'not_logged_in', esc_html__( 'You must be logged-in to view notices.', 'better-wp-security' ) );
113
+ }
114
+
115
+ if ( ! $notice = $this->get_notice_for_request( $request ) ) {
116
+ return new WP_Error( 'notice_not_found', esc_html__( 'Notice not found.', 'better-wp-security' ), array(
117
+ 'status' => WP_Http::NOT_FOUND,
118
+ ) );
119
+ }
120
+
121
+ if ( ! array_key_exists( $request['action'], $notice->get_actions() ) ) {
122
+ return new WP_Error( 'action_not_found', esc_html__( 'Action not found.', 'better-wp-security' ), array(
123
+ 'status' => WP_Http::NOT_FOUND,
124
+ ) );
125
+ }
126
+
127
+ return true;
128
+ }
129
+
130
+ private function get_notice_for_request( WP_REST_Request $request ) {
131
+ ITSEC_Lib::load( 'admin-notices' );
132
+
133
+ $context = new ITSEC_Admin_Notice_Context( wp_get_current_user(), 'rest' );
134
+
135
+ foreach ( ITSEC_Lib_Admin_Notices::get_notices( $context ) as $notice ) {
136
+ if ( $notice->get_id() === $request['notice'] ) {
137
+ return $notice;
138
+ }
139
+ }
140
+
141
+ return null;
142
+ }
143
+
144
+ public function get_settings( WP_REST_Request $request ) {
145
+ ITSEC_Lib::load( 'highlighted-logs' );
146
+
147
+ $settings = array(
148
+ 'muted_highlights' => array(),
149
+ );
150
+
151
+ foreach ( ITSEC_Lib_Highlighted_Logs::get_dynamics() as $slug => $highlight ) {
152
+ $settings['muted_highlights'][ $slug ] = ITSEC_Lib_Highlighted_Logs::is_muted( $slug );
153
+ }
154
+
155
+ return new WP_REST_Response( $settings );
156
+ }
157
+
158
+ public function update_settings( WP_REST_Request $request ) {
159
+ ITSEC_Lib::load( 'highlighted-logs' );
160
+
161
+ if ( $highlights = $request['muted_highlights'] ) {
162
+ foreach ( $highlights as $highlight => $muted ) {
163
+ if ( $muted === ITSEC_Lib_Highlighted_Logs::is_muted( $highlight ) ) {
164
+ continue;
165
+ }
166
+
167
+ if ( $muted ) {
168
+ ITSEC_Lib_Highlighted_Logs::mute( $highlight );
169
+ } else {
170
+ ITSEC_Lib_Highlighted_Logs::unmute( $highlight );
171
+ }
172
+ }
173
+ }
174
+
175
+ return $this->get_settings( $request );
176
+ }
177
+
178
+ public function get_settings_schema() {
179
+ ITSEC_Lib::load( 'highlighted-logs' );
180
+
181
+ $muted_highlights = array();
182
+
183
+ foreach ( ITSEC_Lib_Highlighted_Logs::get_dynamics() as $slug => $query ) {
184
+ $muted_highlights[ $slug ] = array(
185
+ 'type' => 'boolean',
186
+ );
187
+ }
188
+
189
+ return array(
190
+ 'title' => 'itsec-admin-notices-settings',
191
+ 'type' => 'object',
192
+ 'properties' => array(
193
+ 'muted_highlights' => array(
194
+ 'type' => 'object',
195
+ 'properties' => $muted_highlights,
196
+ 'additionalProperties' => false,
197
+ )
198
+ )
199
+ );
200
+ }
201
+ }
core/modules/core/entries/admin-notices-api.js ADDED
@@ -0,0 +1 @@
 
1
+ import './admin-notices/store';
core/modules/core/entries/admin-notices-dashboard-admin-bar.js ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { registerPlugin } from '@wordpress/plugins';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import '@ithemes/security.core.admin-notices-api';
10
+ import AdminBar from './admin-notices/components/admin-bar';
11
+ registerPlugin( 'itsec-admin-notices-dashboard-admin-bar', {
12
+ render: AdminBar,
13
+ } );
core/modules/core/entries/admin-notices.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { setLocaleData } from '@wordpress/i18n';
5
+ import { render } from '@wordpress/element';
6
+ import domReady from '@wordpress/dom-ready';
7
+
8
+ // Silence warnings until JS i18n is stable.
9
+ setLocaleData( { '': {} }, 'better-wp-security' );
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import App from './admin-notices/app.js';
15
+
16
+ domReady( () => {
17
+ const containerEl = document.getElementById( 'wp-admin-bar-itsec_admin_bar_menu' );
18
+ const portalEl = document.getElementById( 'itsec-admin-notices-root' );
19
+
20
+ return render( <App portalEl={ portalEl } />, containerEl );
21
+ } );
core/modules/core/entries/admin-notices/app.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { createPortal } from '@wordpress/element';
5
+ import { Popover, SlotFillProvider } from '@wordpress/components';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import '@ithemes/security.core.admin-notices-api';
11
+ import Toolbar from './components/toolbar';
12
+ import './style.scss';
13
+
14
+ function App( { portalEl } ) {
15
+ return (
16
+ <SlotFillProvider>
17
+ { createPortal( <Popover.Slot />, portalEl ) }
18
+ <Toolbar />
19
+ </SlotFillProvider>
20
+ );
21
+ }
22
+
23
+ export default App;
core/modules/core/entries/admin-notices/components/admin-bar/index.js ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import classnames from 'classnames';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { Button, Dashicon, Popover } from '@wordpress/components';
10
+ import { __ } from '@wordpress/i18n';
11
+ import { compose, withState } from '@wordpress/compose';
12
+ import { withSelect } from '@wordpress/data';
13
+
14
+ /**
15
+ * Internal dependencies
16
+ */
17
+ import { AdminBarFill } from '@ithemes/security.dashboard.api';
18
+ import { doesElementBelongToPanel } from '../../utils';
19
+ import Panel from '../panel';
20
+ import './style.scss';
21
+
22
+ function AdminBar( { notices, noticesLoaded, isToggled, setState } ) {
23
+ return (
24
+ <AdminBarFill>
25
+ <div className="itsec-admin-bar__admin-notices">
26
+ <div className={ classnames( 'itsec-admin-bar-admin-notices__trigger', { 'itsec-admin-bar-admin-notices__trigger--has-notices': notices.length > 0 } ) }>
27
+ <Button aria-expanded={ isToggled } onClick={ () => setState( { isToggled: ! isToggled } ) }>
28
+ <Dashicon icon="megaphone" size={ 15 } />
29
+ { __( 'Notifications', 'better-wp-security' ) }
30
+ </Button>
31
+ { isToggled && (
32
+ <Popover
33
+ className="itsec-admin-bar-admin-notices__content"
34
+ expandOnMobile
35
+ focusOnMount="container"
36
+ position="bottom left"
37
+ headerTitle={ __( 'Notifications', 'better-wp-security' ) }
38
+ onClose={ () => setState( { isToggled: false } ) }
39
+ onClickOutside={ ( e ) => {
40
+ if (
41
+ e.target.id !== 'itsec-admin-notices-toolbar-trigger' &&
42
+ e.target.parentNode.id !== 'itsec-admin-notices-toolbar-trigger' &&
43
+ ! doesElementBelongToPanel( e.target )
44
+ ) {
45
+ setState( { isToggled: false } );
46
+ }
47
+ } }
48
+ >
49
+ <Panel notices={ notices } loaded={ noticesLoaded } close={ () => setState( { isToggled: false } ) } />
50
+ </Popover>
51
+ ) }
52
+ </div>
53
+ </div>
54
+ </AdminBarFill>
55
+ );
56
+ }
57
+
58
+ export default compose( [
59
+ withSelect( ( select ) => ( {
60
+ notices: select( 'ithemes-security/admin-notices' ).getNotices(),
61
+ noticesLoaded: select( 'ithemes-security/admin-notices' ).areNoticesLoaded(),
62
+ } ) ),
63
+ withState( { isToggled: false } ),
64
+ ] )( AdminBar );
core/modules/core/entries/admin-notices/components/admin-bar/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/modules/core/entries/admin-notices/components/admin-bar/style.scss ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "colors.scss";
2
+ @import "breakpoints.scss";
3
+
4
+ .itsec-admin-bar__admin-notices {
5
+ margin-left: auto;
6
+
7
+ & .itsec-admin-bar-admin-notices__trigger > .components-button {
8
+ color: $main-blue;
9
+
10
+ &:hover {
11
+ color: lighten($main-blue, 20%);
12
+ }
13
+
14
+ & svg {
15
+ margin-right: .5em
16
+ }
17
+ }
18
+
19
+ & .itsec-admin-bar-admin-notices__trigger .components-button {
20
+ position: relative;
21
+ }
22
+
23
+ & .itsec-admin-bar-admin-notices__trigger .components-button::before {
24
+ content: '';
25
+ background: #d8514f;
26
+ height: 5px;
27
+ width: 5px;
28
+ border-radius: 5px;
29
+ vertical-align: middle;
30
+ border: 1px solid #f1f1f1;
31
+ z-index: 1;
32
+ position: absolute;
33
+ left: 4px;
34
+ top: 3px;
35
+ opacity: 0;
36
+ transition: opacity 1000ms ease-in-out;
37
+ }
38
+
39
+ & .itsec-admin-bar-admin-notices__trigger--has-notices .components-button::before {
40
+ opacity: 1;
41
+ }
42
+ }
43
+
44
+ .itsec-admin-bar-admin-notices__content .components-popover__content {
45
+ box-shadow: rgba(0, 0, 0, 0.19) 0 4px 5px;
46
+ }
47
+
48
+ @media screen and (max-width: $small) {
49
+ .itsec-admin-bar .itsec-admin-bar__admin-notices {
50
+ margin-left: 0;
51
+ }
52
+ }
core/modules/core/entries/admin-notices/components/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/modules/core/entries/admin-notices/components/notice-actions/index.js ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { isEmpty, map } from 'lodash';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { Dropdown, NavigableMenu, IconButton, Button, Spinner } from '@wordpress/components';
10
+ import { __ } from '@wordpress/i18n';
11
+ import { compose } from '@wordpress/compose';
12
+ import { withDispatch, withSelect } from '@wordpress/data';
13
+
14
+ /**
15
+ * Internal dependencies
16
+ */
17
+ import './style.scss';
18
+
19
+ function NoticeActions( { notice, doAction, inProgress } ) {
20
+ const actions = [];
21
+
22
+ for ( const slug in notice.actions ) {
23
+ if ( ! notice.actions.hasOwnProperty( slug ) ) {
24
+ continue;
25
+ }
26
+
27
+ const action = notice.actions[ slug ];
28
+
29
+ if ( action.style === 'close' ) {
30
+ actions.push( (
31
+ <li key={ slug } >
32
+ <IconButton icon="dismiss" label={ action.title } onClick={ () => doAction( notice.id, slug ) } isBusy={ inProgress.includes( slug ) } />
33
+ </li>
34
+ ) );
35
+ }
36
+ }
37
+
38
+ const generic = getGenericActions( notice );
39
+
40
+ if ( ! isEmpty( generic ) ) {
41
+ actions.push( (
42
+ <li key="more">
43
+ <Dropdown
44
+ position="bottom right"
45
+ className="itsec-admin-notice-list-actions__more-menu"
46
+ contentClassName="itsec-admin-notice-list-actions__more-menu-items"
47
+ renderToggle={ ( { isOpen, onToggle } ) => (
48
+ <IconButton
49
+ icon="ellipsis"
50
+ label={ __( 'More Actions', 'better-wp-security' ) }
51
+ onClick={ onToggle }
52
+ aria-haspopup={ true }
53
+ aria-expanded={ isOpen }
54
+ />
55
+ ) }
56
+ renderContent={ () => (
57
+ <NavigableMenu role="menu">
58
+ { map( generic, ( action, slug ) => (
59
+ action.uri ?
60
+ <Button key={ slug } href={ action.uri }>{ action.title }</Button> :
61
+ <Button key={ slug } onClick={ () => doAction( notice.id, slug ) } disabled={ inProgress.includes( slug ) }>
62
+ { action.title }
63
+ { inProgress.includes( slug ) && <Spinner /> }
64
+ </Button>
65
+ ) ) }
66
+ </NavigableMenu>
67
+ ) }
68
+ />
69
+
70
+ </li>
71
+ ) );
72
+ }
73
+
74
+ return <ul className="itsec-admin-notice-list-actions">{ actions }</ul>;
75
+ }
76
+
77
+ export default compose( [
78
+ withDispatch( ( dispatch ) => ( {
79
+ doAction: dispatch( 'ithemes-security/admin-notices' ).doNoticeAction,
80
+ } ) ),
81
+ withSelect( ( select, ownProps ) => ( {
82
+ inProgress: select( 'ithemes-security/admin-notices' ).getInProgressActions( ownProps.notice.id ),
83
+ } ) ),
84
+ ] )( NoticeActions );
85
+
86
+ function getGenericActions( notice ) {
87
+ const generic = {};
88
+
89
+ for ( const slug in notice.actions ) {
90
+ if ( ! notice.actions.hasOwnProperty( slug ) ) {
91
+ continue;
92
+ }
93
+
94
+ const action = notice.actions[ slug ];
95
+
96
+ if ( action.style === 'close' ) {
97
+ continue;
98
+ }
99
+
100
+ if ( action.style === 'primary' ) {
101
+ continue;
102
+ }
103
+
104
+ generic[ slug ] = action;
105
+ }
106
+
107
+ return generic;
108
+ }
core/modules/core/entries/admin-notices/components/notice-actions/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/modules/core/entries/admin-notices/components/notice-actions/style.scss ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "colors.scss";
2
+
3
+ .itsec-admin-notice-list-actions {
4
+ & .components-icon-button {
5
+ padding: 2px !important;
6
+
7
+ &:hover {
8
+ background-color: darken(#E5EAEE, 10) !important;
9
+ box-shadow: none !important;
10
+ }
11
+ }
12
+ }
13
+
14
+ .itsec-admin-notice-list-actions__more-menu-items .components-popover__content > div {
15
+ overflow: hidden;
16
+ padding: 7px 0;
17
+ }
18
+
19
+ .itsec-admin-notice-list-actions__more-menu-items .components-button {
20
+ width: 100%;
21
+ min-height: 38px;
22
+ box-sizing: border-box;
23
+ padding: 10px;
24
+ color: #0073aa;
25
+ justify-content: space-between;
26
+ align-items: center;
27
+
28
+ &:focus {
29
+ outline-offset: -2px;
30
+ outline: 1px dotted #555d66;
31
+ box-shadow: none;
32
+ }
33
+
34
+ &:hover {
35
+ color: #191e23;
36
+ background: #f3f4f5;
37
+ }
38
+
39
+ & .components-spinner {
40
+ margin: 0;
41
+ }
42
+ }
core/modules/core/entries/admin-notices/components/notice-list/index.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import Notice from '../notice';
5
+ import NoticeActions from '../notice-actions';
6
+ import './style.scss';
7
+
8
+ function NoticeList( { notices } ) {
9
+ return (
10
+ <ul className="itsec-admin-notice-list">
11
+ { notices.map( ( notice ) => (
12
+ <li className="itsec-admin-notice-list-item-container" key={ notice.id }>
13
+ <NoticeActions notice={ notice } />
14
+ <Notice notice={ notice } noticeId={ notice.id } />
15
+ </li>
16
+ ) ) }
17
+ </ul>
18
+ );
19
+ }
20
+
21
+ export default NoticeList;
core/modules/core/entries/admin-notices/components/notice-list/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/modules/core/entries/admin-notices/components/notice-list/style.scss ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .itsec-admin-notice-list {
2
+ margin: 0;
3
+ }
4
+
5
+ .itsec-admin-notice-list-item-container {
6
+ display: grid;
7
+ grid-template: auto / min-content 1fr;
8
+ grid-gap: 1em;
9
+ margin-bottom: 2em;
10
+
11
+ &:last-child {
12
+ margin-bottom: 0;
13
+ }
14
+ }
core/modules/core/entries/admin-notices/components/notice/index.js ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { isEmpty, size, map } from 'lodash';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { Fragment } from '@wordpress/element';
10
+ import { autop } from '@wordpress/autop';
11
+ import { Button } from '@wordpress/components';
12
+
13
+ /**
14
+ * Internal dependencies
15
+ */
16
+ import './style.scss';
17
+
18
+ /**
19
+ * Notice Component.
20
+ *
21
+ * @param {string|number} noticeId
22
+ * @param {Object} notice
23
+ * @param {string} notice.severity
24
+ * @param {string} notice.title
25
+ * @param {string} notice.message
26
+ * @param {Object} notice.meta
27
+ * @param {Array.<{title: string, style: string, uri: string}>} notice.actions
28
+ * @return {Component} Notice component.
29
+ */
30
+ export default function Notice( { notice } ) {
31
+ return (
32
+ <article className={ `itsec-admin-notice itsec-admin-notice--severity-${ notice.severity }` }>
33
+ <header className="itsec-admin-notice__header">
34
+ <div className="itsec-admin-notice__header-inset">
35
+ <h4 dangerouslySetInnerHTML={ { __html: notice.title || formatMessage( notice.message, notice ) } } />
36
+ { map( notice.actions, ( action, slug ) => ( action.style === 'primary' && (
37
+ <Button key={ slug } href={ action.uri }>{ action.title }</Button>
38
+ ) ) ) }
39
+ </div>
40
+ </header>
41
+
42
+ { notice.title && notice.message && (
43
+ <section className="itsec-admin-notice__message" dangerouslySetInnerHTML={ { __html: autop( formatMessage( notice.message, notice ) ) } } />
44
+ ) }
45
+
46
+ { hasMeta( notice ) && (
47
+ <dl className="itsec-admin-notice__meta">
48
+ { map( notice.meta, ( meta, key ) => (
49
+ key !== 'created_at' && (
50
+ <Fragment key={ key }>
51
+ <dt>{ meta.label }</dt>
52
+ <dd>{ meta.formatted }</dd>
53
+ </Fragment>
54
+ )
55
+ ) ) }
56
+ </dl>
57
+ ) }
58
+
59
+ { notice.meta.created_at && (
60
+ <footer className="itsec-admin-notice__footer">
61
+ <time dateTime={ notice.meta.created_at.value }>
62
+ { notice.meta.created_at.formatted }
63
+ </time>
64
+ </footer>
65
+ ) }
66
+ </article>
67
+ );
68
+ }
69
+
70
+ function hasMeta( notice ) {
71
+ if ( isEmpty( notice.meta ) ) {
72
+ return false;
73
+ }
74
+
75
+ if ( size( notice.meta ) === 1 && notice.meta.hasOwnProperty( 'created_at' ) ) {
76
+ return false;
77
+ }
78
+
79
+ return true;
80
+ }
81
+
82
+ function formatMessage( message, notice ) {
83
+ for ( const action in notice.actions ) {
84
+ if ( ! notice.actions.hasOwnProperty( action ) ) {
85
+ continue;
86
+ }
87
+
88
+ if ( notice.actions[ action ].uri === '' ) {
89
+ continue;
90
+ }
91
+
92
+ message = message.replace( '{{ $' + action + ' }}', notice.actions[ action ].uri );
93
+ }
94
+
95
+ return message;
96
+ }
core/modules/core/entries/admin-notices/components/notice/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/modules/core/entries/admin-notices/components/notice/style.scss ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "colors.scss";
2
+ @import "mixins.scss";
3
+
4
+ $border-radius: 3px;
5
+ $side-padding: 20px;
6
+
7
+ .itsec-admin-notice {
8
+ background: white;
9
+ border-radius: $border-radius;
10
+ box-shadow: 0 0 5px rgba(211, 211, 211, 0.35);
11
+ }
12
+
13
+ .itsec-admin-notice__header {
14
+ background: #eeecec;
15
+ padding: $side-padding / 2;
16
+ border-top-left-radius: $border-radius;
17
+ border-top-right-radius: $border-radius;
18
+
19
+ & .components-button {
20
+ margin-top: 1em;
21
+
22
+ @include bordered-button('blue');
23
+ }
24
+
25
+ .itsec-admin-notice--severity-error & {
26
+ background: $alert-red;
27
+
28
+ & .components-button {
29
+ @include bordered-button('red');
30
+ }
31
+ }
32
+
33
+ .itsec-admin-notice--severity-warning & {
34
+ background: $alert-orange;
35
+
36
+ & .components-button {
37
+ @include bordered-button('orange');
38
+ }
39
+ }
40
+
41
+ .itsec-admin-notice--severity-info & {
42
+ background: $alert-blue;
43
+ }
44
+
45
+ .itsec-admin-notice--severity-success & {
46
+ background: $alert-green;
47
+
48
+ & .components-button {
49
+ @include bordered-button('green');
50
+ }
51
+ }
52
+ }
53
+
54
+ .itsec-admin-notice .itsec-admin-notice__header:last-child {
55
+ border-bottom-left-radius: $border-radius;
56
+ border-bottom-right-radius: $border-radius;
57
+ }
58
+
59
+ .itsec-admin-notice__header .itsec-admin-notice__header-inset {
60
+ background: white;
61
+ border-radius: 5px;
62
+ padding: $side-padding / 2;
63
+ }
64
+
65
+ .itsec-admin-notice__header h4 {
66
+ font-size: 14px;
67
+ margin: 0;
68
+ }
69
+
70
+ .itsec-admin-notice__message {
71
+ padding: 10px $side-padding;
72
+
73
+ & p:first-child {
74
+ margin-top: 0;
75
+ }
76
+
77
+ & p:last-child {
78
+ margin-bottom: 0;
79
+ }
80
+ }
81
+
82
+ .itsec-admin-notice__meta {
83
+ background: #eeecec;
84
+ padding: 5px $side-padding;
85
+ margin: 0;
86
+ display: grid;
87
+ grid-template: auto / min-content 1fr;
88
+ grid-gap: 5px 20px;
89
+
90
+ & dt,
91
+ & dd {
92
+ margin: 0;
93
+ }
94
+
95
+ & dt {
96
+ text-transform: uppercase;
97
+ color: darken(#444444, 25);
98
+ }
99
+ }
100
+
101
+ .itsec-admin-notice__footer {
102
+ padding: 5px $side-padding;
103
+ font-size: 11px;
104
+ }
core/modules/core/entries/admin-notices/components/panel/index.js ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import classnames from 'classnames';
5
+ import { get, size } from 'lodash';
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { __ } from '@wordpress/i18n';
10
+ import { IconButton, FormToggle } from '@wordpress/components';
11
+ import { compose, withState } from '@wordpress/compose';
12
+ import { withSelect, withDispatch } from '@wordpress/data';
13
+
14
+ /**
15
+ * Internal dependencies
16
+ */
17
+ import NoticeList from '../notice-list';
18
+ import './style.scss';
19
+
20
+ function getAvailableHighlights() {
21
+ return [
22
+ {
23
+ slug: 'file-change-report',
24
+ label: __( 'File Change Report', 'better-wp-security' ),
25
+ },
26
+ {
27
+ slug: 'notification-center-send-failed',
28
+ label: __( 'Notification Center Errors', 'better-wp-security' ),
29
+ },
30
+ {
31
+ slug: 'malware-scan-report',
32
+ label: __( 'Malware Scan Report', 'better-wp-security' ),
33
+ },
34
+ {
35
+ slug: 'malware-scan-failed',
36
+ label: __( 'Malware Scan Failed', 'better-wp-security' ),
37
+ },
38
+ ];
39
+ }
40
+
41
+ function Panel( { notices, loaded, mutedHighlights, mutedHighlightUpdatesInFlight, updateMutedHighlight, isConfiguring, setState } ) {
42
+ return (
43
+ <div className={ classnames( 'itsec-admin-notice-panel', {
44
+ 'itsec-admin-notice-panel--is-configuring': isConfiguring,
45
+ } ) }>
46
+ <IconButton icon="admin-generic" label={ __( 'Configure', 'better-wp-security' ) }
47
+ className="itsec-admin-notice-panel__configure-trigger"
48
+ style={ { opacity: size( mutedHighlights ) > 0 ? 1 : 0 } }
49
+ onClick={ () => setState( { isConfiguring: ! isConfiguring } ) } />
50
+ <header className="itsec-admin-notice-panel__header">
51
+ <h3>{ __( 'Security Admin Messages', 'better-wp-security' ) }</h3>
52
+ <p>{ __( 'Important notices from iThemes Security', 'better-wp-security' ) }</p>
53
+ </header>
54
+ { isConfiguring && (
55
+ <ul className="itsec-admin-notice-panel__configure-highlighted-logs">
56
+ { getAvailableHighlights().map( ( { slug, label } ) => (
57
+ mutedHighlights[ slug ] !== undefined && (
58
+ <li>
59
+ <label htmlFor={ `itsec-mute-highlight-${ slug }` }>{ label }</label>
60
+ <FormToggle id={ `itsec-mute-highlight-${ slug }` }
61
+ disabled={ ! loaded || mutedHighlightUpdatesInFlight[ slug ] }
62
+ checked={ ! get( mutedHighlightUpdatesInFlight, [ slug, 'mute' ], mutedHighlights[ slug ] ) }
63
+ onChange={ () => updateMutedHighlight( slug, ! mutedHighlights[ slug ] ) }
64
+ />
65
+ </li>
66
+ )
67
+ ) ) }
68
+ </ul>
69
+ ) }
70
+ { notices.length > 0 ?
71
+ <NoticeList notices={ notices } /> :
72
+ loaded && <span>{ __( 'No notices at the moment.', 'better-wp-security' ) }</span>
73
+ }
74
+ </div>
75
+ );
76
+ }
77
+
78
+ export default compose( [
79
+ withState( { isConfiguring: false, checked: {} } ),
80
+ withSelect( ( select ) => ( {
81
+ mutedHighlights: select( 'ithemes-security/admin-notices' ).getMutedHighlights(),
82
+ mutedHighlightUpdatesInFlight: select( 'ithemes-security/admin-notices' ).getMutedHighlightUpdatesInFlight(),
83
+ } ) ),
84
+ withDispatch( ( dispatch ) => ( {
85
+ updateMutedHighlight: dispatch( 'ithemes-security/admin-notices' ).updateMutedHighlight,
86
+ } ) ),
87
+ ] )( Panel );
core/modules/core/entries/admin-notices/components/panel/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/modules/core/entries/admin-notices/components/panel/style.scss ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "colors.scss";
2
+ @import "animations.scss";
3
+
4
+ .itsec-admin-bar-admin-notices__content.components-popover::after {
5
+ border-color: #E5EAEE !important;
6
+ }
7
+
8
+ .itsec-admin-notice-panel {
9
+ padding: 1em;
10
+ background: #E5EAEE;
11
+ width: 420px;
12
+ box-sizing: border-box;
13
+
14
+ & .itsec-admin-notice-panel__header {
15
+ border-bottom: 1px solid $light-blue;
16
+ margin-bottom: 1em;
17
+
18
+ h3 {
19
+ color: $main-blue;
20
+ text-align: center;
21
+ }
22
+
23
+ p {
24
+ text-align: center;
25
+ font-style: oblique;
26
+ }
27
+ }
28
+
29
+ .is-mobile & {
30
+ width: 100%;
31
+ overflow-x: auto;
32
+ height: 100%;
33
+
34
+ & header {
35
+ & h3 {
36
+ display: none;
37
+ }
38
+
39
+ & p {
40
+ margin-top: 0;
41
+ }
42
+ }
43
+ }
44
+
45
+ & .itsec-admin-notice-panel__configure-trigger.components-icon-button {
46
+ padding: 5px;
47
+ height: 30px;
48
+ position: absolute;
49
+ right: 1em;
50
+ top: 1em;
51
+ transition: opacity 400ms;
52
+
53
+ &:hover {
54
+ opacity: .5;
55
+ box-shadow: none !important;
56
+ }
57
+ }
58
+
59
+ &.itsec-admin-notice-panel--is-configuring .itsec-admin-notice-panel__configure-trigger.components-icon-button {
60
+ color: $main-blue;
61
+
62
+ &:hover {
63
+ color: $main-blue;
64
+ }
65
+ }
66
+
67
+ & .itsec-admin-notice-panel__configure-highlighted-logs {
68
+ border-bottom: 1px solid #7ABEED;
69
+ margin-bottom: 1em;
70
+ padding-bottom: 1em;
71
+
72
+ & li {
73
+ display: flex;
74
+ justify-content: space-between;
75
+ border-bottom: 1px solid #ccc;
76
+ margin-bottom: 1em;
77
+ padding-bottom: 1em;
78
+ padding-top: 0;
79
+ margin-top: 0;
80
+
81
+ &:last-child {
82
+ border-bottom: none;
83
+ margin-bottom: 0;
84
+ padding-bottom: 0;
85
+ }
86
+
87
+ & label {
88
+ margin-left: calc(24px + 1em);
89
+ font-size: 14px;
90
+ font-weight: bold;
91
+ }
92
+
93
+ & .components-form-toggle.is-checked .components-form-toggle__track {
94
+ background-color: $main-blue;
95
+ border-color: $main-blue;
96
+ }
97
+
98
+ & .components-form-toggle__input:disabled + .components-form-toggle__track {
99
+ opacity: .5;
100
+ }
101
+ }
102
+ }
103
+ }
core/modules/core/entries/admin-notices/components/toolbar/index.js ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import classnames from 'classnames';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { Fragment } from '@wordpress/element';
10
+ import { __ } from '@wordpress/i18n';
11
+ import { Popover, Button } from '@wordpress/components';
12
+ import { compose, withState } from '@wordpress/compose';
13
+
14
+ /**
15
+ * Internal dependencies
16
+ */
17
+ import { doesElementBelongToPanel } from '../../utils';
18
+ import Panel from '../panel';
19
+ import './style.scss';
20
+ import { withSelect } from '@wordpress/data';
21
+
22
+ function Toolbar( { notices, noticesLoaded, isToggled, setState } ) {
23
+ return (
24
+ <Fragment>
25
+ <Button
26
+ id="itsec-admin-notices-toolbar-trigger"
27
+ className={ classnames( 'ab-item ab-empty-item', {
28
+ 'itsec-admin-notices-toolbar--has-notices': notices.length > 0,
29
+ } ) }
30
+ onClick={ () => setState( { isToggled: ! isToggled } ) }
31
+ aria-expanded={ isToggled }>
32
+ <span className="it-icon-itsec" />
33
+ <span className="itsec-toolbar-text">{ __( 'Security', 'better-wp-security' ) }</span>
34
+ { notices.length > 0 && (
35
+ <span className="itsec-admin-notices-toolbar-bubble">
36
+ <span className="itsec-admin-notices-toolbar-bubble__count">
37
+ { notices.length }
38
+ </span>
39
+ </span>
40
+ ) }
41
+ </Button>
42
+ { isToggled && (
43
+ <Popover
44
+ className="itsec-admin-notices-toolbar__popover"
45
+ noArrow
46
+ expandOnMobile
47
+ focusOnMount="container"
48
+ position="bottom center"
49
+ headerTitle={ __( 'Security', 'better-wp-security' ) }
50
+ onClose={ () => setState( { isToggled: false } ) }
51
+ onClickOutside={ ( e ) => {
52
+ if (
53
+ e.target.id !== 'itsec-admin-notices-toolbar-trigger' &&
54
+ e.target.parentNode.id !== 'itsec-admin-notices-toolbar-trigger' &&
55
+ ! doesElementBelongToPanel( e.target )
56
+ ) {
57
+ setState( { isToggled: false } );
58
+ }
59
+ } }
60
+ >
61
+ <Panel notices={ notices } loaded={ noticesLoaded } close={ () => setState( { isToggled: false } ) } />
62
+ </Popover>
63
+ ) }
64
+ </Fragment>
65
+ );
66
+ }
67
+
68
+ export default compose( [
69
+ withSelect( ( select ) => ( {
70
+ notices: select( 'ithemes-security/admin-notices' ).getNotices(),
71
+ noticesLoaded: select( 'ithemes-security/admin-notices' ).areNoticesLoaded(),
72
+ } ) ),
73
+ withState( { isToggled: false } ),
74
+ ] )( Toolbar );
core/modules/core/entries/admin-notices/components/toolbar/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/modules/core/entries/admin-notices/components/toolbar/style.scss ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "colors.scss";
2
+ @import "breakpoints.scss";
3
+
4
+ #wpadminbar #wp-admin-bar-itsec_admin_bar_menu .ab-sub-wrapper {
5
+ background: transparent;
6
+ box-shadow: none;
7
+ }
8
+
9
+ #wpadminbar .itsec-admin-notices-toolbar-bubble {
10
+ display: inline-block;
11
+ vertical-align: top;
12
+ margin: 8px 0 0 5px;
13
+ padding: 0 5px;
14
+ min-width: 7px;
15
+ height: 17px;
16
+ border-radius: 11px;
17
+ font-size: 9px;
18
+ line-height: 17px;
19
+ text-align: center;
20
+ z-index: 26;
21
+ background-color: #ca4a1f;
22
+ color: #fff;
23
+ animation: itsec-admin-notices-toolbar-bubble-fade-in 400ms;
24
+
25
+ & .itsec-admin-notices-toolbar-bubble__count {
26
+ line-height: 17px;
27
+ font-size: 9px;
28
+ }
29
+ }
30
+
31
+ @keyframes itsec-admin-notices-toolbar-bubble-fade-in {
32
+ from {
33
+ opacity: 0;
34
+ }
35
+
36
+ to {
37
+ opacity: 1;
38
+ }
39
+ }
40
+
41
+ #itsec-admin-notices-root .itsec-admin-notice-panel {
42
+ border: 1px solid #e2e4e7;
43
+ box-shadow: 0 3px 30px rgba(25, 30, 35, .1);
44
+ }
45
+
46
+ #itsec-admin-notices-root .components-popover:not(.is-mobile).is-bottom {
47
+ z-index: 99999;
48
+ }
49
+
50
+ #itsec-admin-notices-root .itsec-admin-notices-toolbar__popover .components-popover__content {
51
+ box-shadow: rgba(0, 0, 0, 0.19) 0 4px 5px;
52
+ }
53
+
54
+ #wp-admin-bar-itsec_admin_bar_menu .components-button {
55
+ cursor: pointer;
56
+ }
57
+
58
+ #wp-admin-bar-itsec_admin_bar_menu .it-icon-itsec {
59
+ display: none;
60
+ margin-top: 8px;
61
+ color: rgba(240, 245, 250, 0.6);
62
+ height: 20px;
63
+ width: 52px;
64
+ line-height: 32px;
65
+ font-size: 32px;
66
+ text-align: center;
67
+ }
68
+
69
+ @media screen and (max-width: 782px) {
70
+ #wp-toolbar > ul > li#wp-admin-bar-itsec_admin_bar_menu {
71
+ display: list-item;
72
+
73
+ & .it-icon-itsec {
74
+ display: inline-block;
75
+ }
76
+
77
+ & .itsec-toolbar-text {
78
+ display: none;
79
+ }
80
+
81
+ & .itsec-admin-notices-toolbar-bubble {
82
+ display: none;
83
+ }
84
+
85
+ & .itsec-admin-notices-toolbar--has-notices {
86
+ position: relative;
87
+
88
+ &::before {
89
+ content: '';
90
+ background: #d8514f;
91
+ height: 5px;
92
+ width: 5px;
93
+ border-radius: 5px;
94
+ vertical-align: middle;
95
+ border: 1px solid #23282d;
96
+ z-index: 1;
97
+ position: absolute;
98
+ left: 4px;
99
+ top: 3px;
100
+ animation: itsec-admin-notices-toolbar-bubble-fade-in 400ms;
101
+ }
102
+ }
103
+ }
104
+ }
core/modules/core/entries/admin-notices/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/modules/core/entries/admin-notices/store/actions.js ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import { apiFetch } from './controls';
5
+
6
+ export function receiveNotices( notices ) {
7
+ return {
8
+ type: RECEIVE_NOTICES,
9
+ notices,
10
+ };
11
+ }
12
+
13
+ export function startNoticeAction( noticeId, actionId ) {
14
+ return {
15
+ type: START_NOTICE_ACTION,
16
+ noticeId,
17
+ actionId,
18
+ };
19
+ }
20
+
21
+ export function finishNoticeAction( noticeId, actionId, response ) {
22
+ return {
23
+ type: FINISH_NOTICE_ACTION,
24
+ noticeId,
25
+ actionId,
26
+ response,
27
+ };
28
+ }
29
+
30
+ export function failedNoticeAction( noticeId, actionId, error ) {
31
+ return {
32
+ type: FAILED_NOTICE_ACTION,
33
+ noticeId,
34
+ actionId,
35
+ error,
36
+ };
37
+ }
38
+
39
+ export function receiveMutedHighlights( mutedHighlights ) {
40
+ return {
41
+ type: RECEIVE_MUTED_HIGHLIGHTS,
42
+ mutedHighlights,
43
+ };
44
+ }
45
+
46
+ export function startUpdateMutedHighlight( slug, mute ) {
47
+ return {
48
+ type: START_UPDATE_MUTED_HIGHLIGHT,
49
+ slug,
50
+ mute,
51
+ };
52
+ }
53
+
54
+ export function finishUpdateMutedHighlight( slug, mute ) {
55
+ return {
56
+ type: FINISH_UPDATE_MUTED_HIGHLIGHT,
57
+ slug,
58
+ mute,
59
+ };
60
+ }
61
+
62
+ export function failedUpdateMutedHighlight( slug, mute, error ) {
63
+ return {
64
+ type: FAILED_UPDATE_MUTED_HIGHLIGHT,
65
+ slug,
66
+ mute,
67
+ error,
68
+ };
69
+ }
70
+
71
+ export function* doNoticeAction( noticeId, actionId, payload = {} ) {
72
+ yield startNoticeAction( noticeId, actionId );
73
+
74
+ let response;
75
+
76
+ try {
77
+ response = yield apiFetch( {
78
+ path: `/ithemes-security/v1/admin-notices/${ noticeId }/${ actionId }`,
79
+ method: 'POST',
80
+ data: payload,
81
+ } );
82
+ } catch ( e ) {
83
+ yield failedNoticeAction( noticeId, actionId, e );
84
+
85
+ return e;
86
+ }
87
+
88
+ yield finishNoticeAction( noticeId, actionId, response );
89
+
90
+ return response;
91
+ }
92
+
93
+ export function* updateMutedHighlight( slug, muted ) {
94
+ yield startUpdateMutedHighlight( slug, muted );
95
+
96
+ let response;
97
+
98
+ try {
99
+ response = yield apiFetch( {
100
+ path: '/ithemes-security/v1/admin-notices/settings',
101
+ method: 'PUT',
102
+ data: {
103
+ muted_highlights: {
104
+ [ slug ]: muted,
105
+ },
106
+ },
107
+ } );
108
+ } catch ( e ) {
109
+ yield failedUpdateMutedHighlight( slug, muted, e );
110
+
111
+ return e;
112
+ }
113
+
114
+ yield finishUpdateMutedHighlight( slug, muted );
115
+
116
+ return response;
117
+ }
118
+
119
+ export const RECEIVE_NOTICES = 'RECEIVE_NOTICES';
120
+ export const START_NOTICE_ACTION = 'START_NOTICE_ACTION';
121
+ export const FINISH_NOTICE_ACTION = 'FINISH_NOTICE_ACTION';
122
+ export const FAILED_NOTICE_ACTION = 'FAILED_NOTICE_ACTION';
123
+ export const RECEIVE_MUTED_HIGHLIGHTS = 'RECEIVE_MUTED_HIGHLIGHTS';
124
+ export const START_UPDATE_MUTED_HIGHLIGHT = 'START_UPDATE_MUTED_HIGHLIGHT';
125
+ export const FINISH_UPDATE_MUTED_HIGHLIGHT = 'FINISH_UPDATE_MUTED_HIGHLIGHT';
126
+ export const FAILED_UPDATE_MUTED_HIGHLIGHT = 'FAILED_UPDATE_MUTED_HIGHLIGHT';
core/modules/core/entries/admin-notices/store/controls.js ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { uniqueId } from 'lodash';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { select as selectData, dispatch as dispatchData } from '@wordpress/data';
10
+ import { default as triggerApiFetch } from '@wordpress/api-fetch';
11
+
12
+ /**
13
+ * Internal dependencies
14
+ */
15
+ import responseToError from '@ithemes/security-utils';
16
+
17
+ /**
18
+ * Trigger an API Fetch request.
19
+ *
20
+ * @param {Object} request API Fetch Request Object.
21
+ * @return {Object} control descriptor.
22
+ */
23
+ export function apiFetch( request ) {
24
+ return {
25
+ type: 'API_FETCH',
26
+ request,
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Calls a selector using the current state.
32
+ * @param {string} selectorName Selector name.
33
+ * @param {Array} args Selector arguments.
34
+ *
35
+ * @return {Object} control descriptor.
36
+ */
37
+ export function select( selectorName, ...args ) {
38
+ return {
39
+ type: 'SELECT',
40
+ selectorName,
41
+ args,
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Yields action objects used in signalling that a notice is to be created.
47
+ *
48
+ * @see @wordpress/notices#createNotice()
49
+ *
50
+ * @param {?string} status Notice status.
51
+ * Defaults to `info`.
52
+ * @param {string} content Notice message.
53
+ * @param {?Object} options Notice options.
54
+ * @param {?string} options.context Context under which to
55
+ * group notice.
56
+ * @param {?string} options.id Identifier for notice.
57
+ * Automatically assigned
58
+ * if not specified.
59
+ * @param {?boolean} options.isDismissible Whether the notice can
60
+ * be dismissed by user.
61
+ * Defaults to `true`.
62
+ * @param {?number} options.autoDismiss Whether the notice should
63
+ * by automatically dismissed
64
+ * after x milliseconds.
65
+ * Defaults to `false`.
66
+ * @param {?Array<WPNoticeAction>} options.actions User actions to be
67
+ * presented with notice.
68
+ *
69
+ * @return {Object} control descriptor.
70
+ */
71
+ export function createNotice( status = 'info', content, options = {} ) {
72
+ return {
73
+ type: 'CREATE_NOTICE',
74
+ status,
75
+ content,
76
+ options: {
77
+ context: 'ithemes-security',
78
+ ...options,
79
+ },
80
+ };
81
+ }
82
+
83
+ const controls = {
84
+ API_FETCH( { request } ) {
85
+ return triggerApiFetch( request ).catch( responseToError );
86
+ },
87
+
88
+ SELECT( { selectorName, args } ) {
89
+ return selectData( 'ithemes-security/admin-notices' )[ selectorName ]( ...args );
90
+ },
91
+ CREATE_NOTICE( { status, content, options } ) {
92
+ if ( options.autoDismiss ) {
93
+ options.id = options.id || uniqueId( 'itsec-auto-dismiss-' );
94
+ setTimeout( () => dispatchData( 'core/notices' ).removeNotice( options.id, options.context ), options.autoDismiss );
95
+ }
96
+
97
+ dispatchData( 'core/notices' ).createNotice( status, content, options );
98
+ },
99
+ };
100
+
101
+ export default controls;
core/modules/core/entries/admin-notices/store/index.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { registerStore } from '@wordpress/data';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import controls from './controls';
10
+ import * as actions from './actions';
11
+ import * as selectors from './selectors';
12
+ import adminNotices from './reducers';
13
+ import * as resolvers from './resolvers';
14
+
15
+ const store = registerStore( 'ithemes-security/admin-notices', {
16
+ controls,
17
+ actions,
18
+ selectors,
19
+ resolvers,
20
+ reducer: adminNotices,
21
+ } );
22
+
23
+ export default store;
core/modules/core/entries/admin-notices/store/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/modules/core/entries/admin-notices/store/reducers.js ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { omit } from 'lodash';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import {
10
+ FAILED_NOTICE_ACTION,
11
+ FAILED_UPDATE_MUTED_HIGHLIGHT,
12
+ FINISH_NOTICE_ACTION,
13
+ FINISH_UPDATE_MUTED_HIGHLIGHT,
14
+ RECEIVE_MUTED_HIGHLIGHTS,
15
+ RECEIVE_NOTICES,
16
+ START_NOTICE_ACTION,
17
+ START_UPDATE_MUTED_HIGHLIGHT,
18
+ } from './actions';
19
+
20
+ const DEFAULT_STATE = {
21
+ notices: [],
22
+ doingActions: {},
23
+ mutedHighlights: {},
24
+ mutedHighlightUpdatesInFlight: {},
25
+ };
26
+
27
+ export default function adminNotices( state = DEFAULT_STATE, action ) {
28
+ switch ( action.type ) {
29
+ case RECEIVE_NOTICES:
30
+ return {
31
+ ...state,
32
+ notices: [
33
+ ...action.notices,
34
+ ],
35
+ };
36
+ case START_NOTICE_ACTION:
37
+ return {
38
+ ...state,
39
+ doingActions: {
40
+ ...state.doingActions,
41
+ [ action.noticeId ]: [
42
+ ...( state.doingActions[ action.noticeId ] || [] ),
43
+ action.actionId,
44
+ ],
45
+ },
46
+ };
47
+ case FINISH_NOTICE_ACTION:
48
+ case FAILED_NOTICE_ACTION:
49
+ return {
50
+ ...state,
51
+ doingActions: {
52
+ ...state.doingActions,
53
+ [ action.noticeId ]: ( state.doingActions[ action.noticeId ] || [] ).filter( ( actionId ) => actionId !== action.actionId ),
54
+ },
55
+ };
56
+ case RECEIVE_MUTED_HIGHLIGHTS:
57
+ return {
58
+ ...state,
59
+ mutedHighlights: action.mutedHighlights,
60
+ };
61
+ case START_UPDATE_MUTED_HIGHLIGHT:
62
+ return {
63
+ ...state,
64
+ mutedHighlightUpdatesInFlight: {
65
+ ...state.mutedHighlightUpdatesInFlight,
66
+ [ action.slug ]: { mute: action.mute },
67
+ },
68
+ };
69
+ case FINISH_UPDATE_MUTED_HIGHLIGHT:
70
+ return {
71
+ ...state,
72
+ mutedHighlightUpdatesInFlight: omit( state.mutedHighlightUpdatesInFlight, action.slug ),
73
+ mutedHighlights: {
74
+ ...state.mutedHighlights,
75
+ [ action.slug ]: action.mute,
76
+ },
77
+ };
78
+ case FAILED_UPDATE_MUTED_HIGHLIGHT:
79
+ return {
80
+ ...state,
81
+ mutedHighlightUpdatesInFlight: omit( state.mutedHighlightUpdatesInFlight, action.slug ),
82
+ };
83
+ default:
84
+ return state;
85
+ }
86
+ }
core/modules/core/entries/admin-notices/store/resolvers.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { isEmpty } from 'lodash';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import { apiFetch } from './controls';
10
+ import { FINISH_NOTICE_ACTION, FINISH_UPDATE_MUTED_HIGHLIGHT, receiveMutedHighlights, receiveNotices } from './actions';
11
+
12
+ const getNotices = {
13
+ *fulfill() {
14
+ const notices = yield apiFetch( {
15
+ path: '/ithemes-security/v1/admin-notices',
16
+ } );
17
+
18
+ yield receiveNotices( notices );
19
+ },
20
+ shouldInvalidate( action ) {
21
+ return action.type === FINISH_NOTICE_ACTION || action.type === FINISH_UPDATE_MUTED_HIGHLIGHT;
22
+ },
23
+ };
24
+
25
+ export { getNotices as getNotices };
26
+
27
+ export function *getMutedHighlights() {
28
+ const settings = yield apiFetch( {
29
+ path: '/ithemes-security/v1/admin-notices/settings',
30
+ } );
31
+
32
+ yield receiveMutedHighlights( isEmpty( settings.muted_highlights ) ? {} : settings.muted_highlights );
33
+ }
core/modules/core/entries/admin-notices/store/selectors.js ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { select } from '@wordpress/data';
5
+
6
+ /**
7
+ * Returns true if resolution is in progress for the core selector of the given
8
+ * name and arguments.
9
+ *
10
+ * @param {string} selectorName Core data selector name.
11
+ * @param {...*} args Arguments passed to selector.
12
+ *
13
+ * @return {boolean} Whether resolution is in progress.
14
+ */
15
+ export function isResolving( selectorName, ...args ) {
16
+ return select( 'core/data' ).isResolving( 'ithemes-security/admin-notices', selectorName, args );
17
+ }
18
+
19
+ export function isResolved( selectorName, ...args ) {
20
+ return select( 'core/data' ).hasFinishedResolution( 'ithemes-security/admin-notices', selectorName, args );
21
+ }
22
+
23
+ export function getNotices( state ) {
24
+ return state.notices;
25
+ }
26
+
27
+ export function areNoticesLoaded() {
28
+ return isResolved( 'getNotices' );
29
+ }
30
+
31
+ export function isDoingAction( state, noticeId, actionId = '' ) {
32
+ if ( ! state.doingActions[ noticeId ] ) {
33
+ return false;
34
+ }
35
+
36
+ if ( actionId === '' ) {
37
+ return true;
38
+ }
39
+
40
+ return state.doingActions[ noticeId ].includes( actionId );
41
+ }
42
+
43
+ const DEFAULT_IN_PROGRESS = [];
44
+
45
+ export function getInProgressActions( state, noticeId ) {
46
+ return state.doingActions[ noticeId ] || DEFAULT_IN_PROGRESS;
47
+ }
48
+
49
+ export function getMutedHighlights( state ) {
50
+ return state.mutedHighlights;
51
+ }
52
+
53
+ export function getMutedHighlightUpdatesInFlight( state ) {
54
+ return state.mutedHighlightUpdatesInFlight;
55
+ }
core/modules/core/entries/admin-notices/style.scss ADDED
File without changes
core/modules/core/entries/admin-notices/utils.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Check if the given element belongs to the notices panel even if it is outside of the DOM tree.
3
+ *
4
+ * @param {HTMLElement} element
5
+ * @return {boolean} If the element is protected.
6
+ */
7
+ export function doesElementBelongToPanel( element ) {
8
+ let node = element.parentNode;
9
+
10
+ while ( node !== null ) {
11
+ if ( node.classList && node.classList.contains( 'itsec-admin-notice-list-actions__more-menu-items' ) ) {
12
+ return true;
13
+ }
14
+
15
+ node = node.parentNode;
16
+ }
17
+
18
+ return false;
19
+ }
core/modules/core/entries/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/modules/core/js/admin-notices.js ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ( function( $, wp, config ) {
2
+ $( function() {
3
+ $( '.itsec-notice .notice-dismiss' ).off( 'click.wp-dismiss-notice' );
4
+
5
+ $( document ).on( 'click', '.itsec-notice .notice-dismiss', function( e ) {
6
+ const $this = $( this ),
7
+ $notice = $this.parent( '.itsec-notice' );
8
+
9
+ $notice.fadeTo( 100, 0, function() {
10
+ $notice.slideUp( 100 );
11
+ } );
12
+
13
+ ajax( $notice, $notice.data( 'close' ) );
14
+ } );
15
+
16
+ $( document ).on( 'click', '.itsec-notice [data-action]', function( e ) {
17
+ const $this = $( this ),
18
+ $notice = $this.parent( '.itsec-notice' );
19
+
20
+ $this.prop( 'disabled', true );
21
+
22
+ ajax( $notice, action ).always( function() {
23
+ $this.prop( 'disabled', false );
24
+ } );
25
+ } );
26
+ } );
27
+
28
+ function ajax( $notice, action ) {
29
+ return wp.ajax.post( 'itsec-admin-notice', {
30
+ itsec_action: action,
31
+ notice_id : $notice.data( 'id' ),
32
+ nonce : config.nonce,
33
+ } )
34
+ .done( function() {
35
+ if ( $notice.css( 'opacity' ) !== '1' ) {
36
+ if ( $notice.css( 'opacity' ) === '0' ) {
37
+ $notice.remove();
38
+ } else {
39
+ setTimeout( function() {
40
+ $notice.remove();
41
+ }, 100 );
42
+ }
43
+ } else {
44
+ $notice.fadeTo( 100, 0, function() {
45
+ $notice.slideUp( 100, function() {
46
+ $notice.remove();
47
+ } );
48
+ } );
49
+ }
50
+ } )
51
+ .fail( function( response ) {
52
+ if ( response.message ) {
53
+ alert( response.message );
54
+ } else if ( Array.isArray( response ) ) {
55
+ const messages = [];
56
+
57
+ for ( let i = 0; i < response.length; i++ ) {
58
+ messages.push( response[ i ].message );
59
+ }
60
+
61
+ alert( messages.join( ' ' ) );
62
+ } else {
63
+ alert( 'An unexpected error occurred.' );
64
+ }
65
+
66
+ if ( $notice.css( 'opacity' ) !== '1' ) {
67
+ $notice.slideDown( 100, function() {
68
+ $notice.fadeTo( 100, 1 );
69
+ } );
70
+ }
71
+ } );
72
+ }
73
+ } )( jQuery, wp, window[ 'ITSECAdminNotices' ] );
core/modules/core/settings.php DELETED
@@ -1,13 +0,0 @@
1
- <?php
2
-
3
- final class ITSEC_Core_Settings extends ITSEC_Settings {
4
- public function get_id() {
5
- return 'core';
6
- }
7
-
8
- public function get_defaults() {
9
- return array();
10
- }
11
- }
12
-
13
- ITSEC_Modules::register_settings( new ITSEC_Core_Settings() );
 
 
 
 
 
 
 
 
 
 
 
 
 
core/modules/core/setup.php DELETED
@@ -1,70 +0,0 @@
1
- <?php
2
-
3
- if ( ! class_exists( 'ITSEC_Core_Setup' ) ) {
4
-
5
- class ITSEC_Core_Setup {
6
-
7
- public function __construct() {
8
-
9
- add_action( 'itsec_modules_do_plugin_activation', array( $this, 'execute_activate' ) );
10
- add_action( 'itsec_modules_do_plugin_deactivation', array( $this, 'execute_deactivate' ) );
11
- add_action( 'itsec_modules_do_plugin_uninstall', array( $this, 'execute_uninstall' ) );
12
- add_action( 'itsec_modules_do_plugin_upgrade', array( $this, 'execute_upgrade' ), null, 2 );
13
-
14
- }
15
-
16
- /**
17
- * Execute module activation.
18
- *
19
- * @since 4.0
20
- *
21
- * @return void
22
- */
23
- public function execute_activate() {}
24
-
25
- /**
26
- * Execute module deactivation
27
- *
28
- * @return void
29
- */
30
- public function execute_deactivate() {
31
-
32
- delete_site_option( 'itsec_free_just_activated' );
33
-
34
- }
35
-
36
- /**
37
- * Execute module uninstall
38
- *
39
- * @return void
40
- */
41
- public function execute_uninstall() {
42
-
43
- $this->execute_deactivate();
44
-
45
- }
46
-
47
- /**
48
- * Execute module upgrade
49
- *
50
- * @return void
51
- */
52
- public function execute_upgrade( $build ) {
53
- if ( $build < 4069 ) {
54
- delete_site_option( 'itsec_free_just_activated' );
55
- }
56
-
57
- if ( $build < 4076 ) {
58
- $digest = wp_next_scheduled( 'itsec_digest_email' );
59
-
60
- if ( $digest ) {
61
- wp_unschedule_event( $digest, 'itsec_digest_email' );
62
- }
63
- }
64
- }
65
-
66
- }
67
-
68
- }
69
-
70
- new ITSEC_Core_Setup();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/modules/core/sidebar-widget-backupbuddy-cross-promo.php CHANGED
@@ -4,7 +4,7 @@ class ITSEC_Settings_Page_Sidebar_Widget_BackupBuddy_Cross_Promo extends ITSEC_S
4
  public function __construct() {
5
  $this->id = 'backupbuddy-cross-promo';
6
  $this->title = __( 'Complete Your Security Strategy With BackupBuddy', 'better-wp-security' );
7
- $this->priority = 7;
8
 
9
  parent::__construct();
10
  }
4
  public function __construct() {
5
  $this->id = 'backupbuddy-cross-promo';
6
  $this->title = __( 'Complete Your Security Strategy With BackupBuddy', 'better-wp-security' );
7
+ $this->priority = ITSEC_Core::is_pro() ? 11 : 7;
8
 
9
  parent::__construct();
10
  }
core/modules/core/sidebar-widget-support.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- class ITSEC_Settings_Page_Sidebar_Widget_Support extends ITSEC_Settings_Page_Sidebar_Widget {
4
  public function __construct() {
5
  $this->id = 'support';
6
  $this->title = __( 'Need Help Securing Your Site?', 'better-wp-security' );
@@ -17,4 +17,4 @@ class ITSEC_Settings_Page_Sidebar_Widget_Support extends ITSEC_Settings_Page_Sid
17
  }
18
 
19
  }
20
- new ITSEC_Settings_Page_Sidebar_Widget_Support();
1
  <?php
2
 
3
+ class ITSEC_Settings_Page_Sidebar_Widget_Free_Support extends ITSEC_Settings_Page_Sidebar_Widget {
4
  public function __construct() {
5
  $this->id = 'support';
6
  $this->title = __( 'Need Help Securing Your Site?', 'better-wp-security' );
17
  }
18
 
19
  }
20
+ new ITSEC_Settings_Page_Sidebar_Widget_Free_Support();
core/modules/core/validator.php DELETED
@@ -1,17 +0,0 @@
1
- <?php
2
-
3
- class ITSEC_Core_Validator extends ITSEC_Validator {
4
- public function get_id() {
5
- return 'core';
6
- }
7
-
8
- protected function preprocess_settings() {
9
-
10
- }
11
-
12
- protected function validate_settings() {
13
-
14
- }
15
- }
16
-
17
- ITSEC_Modules::register_validator( new ITSEC_Core_Validator() );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/modules/feature-flags/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/modules/feature-flags/settings-page.php ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Feature_Flags_Settings_Page extends ITSEC_Module_Settings_Page {
4
+
5
+ public function __construct() {
6
+ parent::__construct();
7
+
8
+ $this->id = 'feature-flags';
9
+ $this->title = esc_html__( 'Feature Flags', 'better-wp-security' );
10
+ $this->description = esc_html__( 'Toggle feature flags.', 'better-wp-security' );
11
+ $this->type = 'advanced';
12
+ }
13
+
14
+ public function register() {
15
+ ITSEC_Lib::load( 'feature-flags' );
16
+
17
+ if ( ITSEC_Lib_Feature_Flags::get_available_flags() ) {
18
+ parent::register();
19
+ }
20
+ }
21
+
22
+ protected function render_description( $form ) {
23
+ echo '<p>';
24
+ echo esc_html__( 'Toggle feature flags.', 'better-wp-security' );
25
+ echo '</p>';
26
+ }
27
+
28
+ protected function render_settings( $form ) {
29
+ ITSEC_Lib::load( 'feature-flags' );
30
+
31
+ foreach ( ITSEC_Lib_Feature_Flags::get_available_flags() as $flag => $config ) {
32
+ if ( is_callable( $config['title'] ) ) {
33
+ $title = call_user_func( $config['title'] );
34
+ } elseif ( $config['title'] ) {
35
+ $title = $config['title'];
36
+ } else {
37
+ $title = ucwords( str_replace( '_', ' ', $flag ) );
38
+ }
39
+
40
+ if ( is_callable( $config['description'] ) ) {
41
+ $description = call_user_func( $config['description'] );
42
+ } else {
43
+ $description = $config['description'];
44
+ }
45
+
46
+ $form->set_option( $flag, ITSEC_Lib_Feature_Flags::is_enabled( $flag ) );
47
+
48
+ $cb_opts = array();
49
+
50
+ if ( defined( 'ITSEC_FF_' . $flag ) ) {
51
+ $cb_opts['disabled'] = true;
52
+ }
53
+ ?>
54
+ <table class="form-table itsec-settings-section">
55
+ <tbody>
56
+ <tr>
57
+ <th scope="row">
58
+ <label for="itsec-feature-flags-<?php echo esc_attr( $flag ); ?>"><?php echo $title; ?></label>
59
+ </th>
60
+ <td>
61
+ <?php $form->add_checkbox( $flag, $cb_opts ); ?>
62
+ <?php if ( $description ): ?>
63
+ <p class="description"><?php echo $description; ?></p>
64
+ <?php endif; ?>
65
+ </td>
66
+ </tr>
67
+ </tbody>
68
+ </table>
69
+ <?php
70
+ }
71
+ }
72
+
73
+ public function handle_form_post( $data ) {
74
+ ITSEC_Lib::load( 'feature-flags' );
75
+
76
+ foreach ( $data as $flag => $enabled ) {
77
+ if ( defined( 'ITSEC_FF_' . $flag ) ) {
78
+ continue;
79
+ }
80
+
81
+ if ( $enabled ) {
82
+ ITSEC_Lib_Feature_Flags::enable( $flag );
83
+ } else {
84
+ ITSEC_Lib_Feature_Flags::disable( $flag );
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+ new ITSEC_Feature_Flags_Settings_Page();
core/modules/file-change/admin.php CHANGED
@@ -2,19 +2,6 @@
2
 
3
  final class ITSEC_File_Change_Admin {
4
 
5
- const AJAX = 'itsec_file_change_dismiss_warning';
6
-
7
- private $script_version = 2;
8
- private $dismiss_nonce;
9
-
10
-
11
- public function __construct() {
12
-
13
- if ( ITSEC_Modules::get_setting( 'file-change', 'show_warning' ) ) {
14
- add_action( 'init', array( $this, 'init' ) );
15
- }
16
- }
17
-
18
  public static function enqueue_scanner() {
19
  $logs_page_url = ITSEC_Core::get_logs_page_url( 'file_change' );
20
 
@@ -29,90 +16,6 @@ final class ITSEC_File_Change_Admin {
29
  'already_running' => sprintf( __( 'A scan is already in progress. Please check the <a href="%s" target="_blank" rel="noopener noreferrer">logs page</a> at a later time for the results of the scan.', 'better-wp-security' ), esc_url( $logs_page_url ) ),
30
  ) );
31
  }
32
-
33
- public function init() {
34
-
35
- if ( ! ITSEC_Core::current_user_can_manage() ) {
36
- return;
37
- }
38
-
39
- add_action( 'wp_ajax_' . self::AJAX, array( $this, 'dismiss_file_change_warning_ajax' ) );
40
-
41
- if ( ! empty( $_GET['file_change_dismiss_warning'] ) ) {
42
- $this->dismiss_file_change_warning();
43
- } else {
44
- add_action( 'admin_print_scripts', array( $this, 'add_scripts' ) );
45
- $this->dismiss_nonce = wp_create_nonce( 'itsec-file-change-dismiss-warning' );
46
-
47
- if ( is_multisite() ) {
48
- add_action( 'network_admin_notices', array( $this, 'show_file_change_warning' ) );
49
- } else {
50
- add_action( 'admin_notices', array( $this, 'show_file_change_warning' ) );
51
- }
52
- }
53
- }
54
-
55
- public function add_scripts() {
56
- $vars = array(
57
- 'ajax_action' => self::AJAX,
58
- 'ajax_nonce' => $this->dismiss_nonce
59
- );
60
-
61
- wp_enqueue_script( 'itsec-file-change-script', plugins_url( 'js/script.js', __FILE__ ), array( 'jquery', 'common' ), $this->script_version, true );
62
- wp_localize_script( 'itsec-file-change-script', 'itsec_file_change', $vars );
63
- }
64
-
65
- public function dismiss_file_change_warning_ajax() {
66
- if ( ! wp_verify_nonce( $_REQUEST['nonce'], 'itsec-file-change-dismiss-warning' ) ) {
67
- wp_send_json_error( array(
68
- 'message' => __( 'Request expired. Please refresh and try again.', 'better-wp-security' ),
69
- ) );
70
- }
71
-
72
- $status = ITSEC_Modules::set_setting( 'file-change', 'show_warning', false );
73
-
74
- if ( ! $status || empty( $status['saved'] ) ) {
75
- wp_send_json_error( array(
76
- 'message' => __( 'Failed to dismiss warning.', 'better-wp-security' ),
77
- ) );
78
- }
79
-
80
- wp_send_json_success( array(
81
- 'message' => __( 'Warning dismissed.', 'better-wp-security' ),
82
- ) );
83
- }
84
-
85
- public function dismiss_file_change_warning() {
86
- if ( empty( $_REQUEST['nonce'] ) || ! wp_verify_nonce( $_REQUEST['nonce'], 'itsec-file-change-dismiss-warning' ) ) {
87
- return;
88
- }
89
-
90
- ITSEC_Modules::set_setting( 'file-change', 'show_warning', false );
91
- }
92
-
93
- public function show_file_change_warning() {
94
-
95
- $args = array(
96
- 'file_change_dismiss_warning' => '1',
97
- 'nonce' => $this->dismiss_nonce,
98
- );
99
-
100
- if ( $log_id = ITSEC_Modules::get_setting( 'file-change', 'last_scan' ) ) {
101
- $args['id'] = $log_id;
102
- }
103
-
104
- $logs_url = add_query_arg( $args, ITSEC_Core::get_logs_page_url() );
105
- $message = sprintf(
106
- esc_html__( 'iThemes Security noticed file changes in your WordPress site. Please %1$s review the logs %2$s to make sure your system has not been compromised.', 'better-wp-security' ),
107
- '<a href="' . esc_url( $logs_url ) . '">',
108
- '</a>'
109
- );
110
- ?>
111
- <div id="itsec-file-change-warning-dialog" class="notice notice-error is-dismissible">
112
- <p><?php echo $message; ?></p>
113
- </div>
114
- <?php
115
- }
116
  }
117
 
118
  new ITSEC_File_Change_Admin();
2
 
3
  final class ITSEC_File_Change_Admin {
4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  public static function enqueue_scanner() {
6
  $logs_page_url = ITSEC_Core::get_logs_page_url( 'file_change' );
7
 
16
  'already_running' => sprintf( __( 'A scan is already in progress. Please check the <a href="%s" target="_blank" rel="noopener noreferrer">logs page</a> at a later time for the results of the scan.', 'better-wp-security' ), esc_url( $logs_page_url ) ),
17
  ) );
18
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  }
20
 
21
  new ITSEC_File_Change_Admin();
core/modules/file-change/class-itsec-file-change.php CHANGED
@@ -38,6 +38,10 @@ class ITSEC_File_Change {
38
  add_action( 'itsec_scheduled_file-change-fast', array( $this, 'run_scan' ) );
39
  ITSEC_Core::get_scheduler()->register_loop( 'file-change', ITSEC_Scheduler::S_DAILY, 60 );
40
  ITSEC_Core::get_scheduler()->register_loop( 'file-change-fast', ITSEC_Scheduler::S_DAILY, 0 );
 
 
 
 
41
  }
42
 
43
  public function run_scan( $job ) {
@@ -175,6 +179,16 @@ class ITSEC_File_Change {
175
  return $response;
176
  }
177
 
 
 
 
 
 
 
 
 
 
 
178
  /**
179
  * Get the latest change list.
180
  *
38
  add_action( 'itsec_scheduled_file-change-fast', array( $this, 'run_scan' ) );
39
  ITSEC_Core::get_scheduler()->register_loop( 'file-change', ITSEC_Scheduler::S_DAILY, 60 );
40
  ITSEC_Core::get_scheduler()->register_loop( 'file-change-fast', ITSEC_Scheduler::S_DAILY, 0 );
41
+
42
+ if ( ITSEC_Modules::get_setting( 'file-change', 'notify_admin' ) ) {
43
+ add_action( 'itsec_register_highlighted_logs', array( $this, 'register_highlight' ) );
44
+ }
45
  }
46
 
47
  public function run_scan( $job ) {
179
  return $response;
180
  }
181
 
182
+ /**
183
+ * Register a highlighted log whenever File Change finds changed files.
184
+ */
185
+ public function register_highlight() {
186
+ ITSEC_Lib_Highlighted_Logs::register_dynamic_highlight( 'file-change-report', array(
187
+ 'module' => 'file_change',
188
+ 'code' => 'changes-found::%',
189
+ ) );
190
+ }
191
+
192
  /**
193
  * Get the latest change list.
194
  *
core/modules/file-change/js/script.js DELETED
@@ -1,36 +0,0 @@
1
- jQuery( document ).ready( function ( $ ) {
2
-
3
- var $notice = $( '#itsec-file-change-warning-dialog' ),
4
- $button = $('.notice-dismiss', $notice);
5
-
6
- $button.off( 'click.wp-dismiss-notice' );
7
-
8
- $button.click( function ( e ) {
9
- e.preventDefault();
10
-
11
- var $button = $( this );
12
-
13
- $button.prop( 'disabled', true );
14
- $button.css( 'opacity', .5 );
15
-
16
- var data = {
17
- action: itsec_file_change.ajax_action,
18
- nonce : itsec_file_change.ajax_nonce,
19
- };
20
-
21
- $.post( ajaxurl, data, function ( response ) {
22
- $button.prop( 'disabled', false );
23
- $button.css( 'opacity', 1 );
24
-
25
- if ( response.success ) {
26
- $notice.fadeTo( 100, 0, function () {
27
- $notice.slideUp( 100, function () {
28
- $notice.remove();
29
- } );
30
- } );
31
- } else {
32
- alert( response.data.message )
33
- }
34
- } );
35
- } );
36
- } );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/modules/file-change/logs.php CHANGED
@@ -4,6 +4,8 @@ final class ITSEC_File_Change_Logs {
4
  public function __construct() {
5
  add_filter( 'itsec_logs_prepare_file_change_entry_for_list_display', array( $this, 'filter_entry_for_list_display' ), 10, 3 );
6
  add_filter( 'itsec_logs_prepare_file_change_entry_for_details_display', array( $this, 'filter_entry_for_details_display' ), 10, 4 );
 
 
7
  }
8
 
9
  public function filter_entry_for_list_display( $entry, $code, $code_data ) {
@@ -11,9 +13,9 @@ final class ITSEC_File_Change_Logs {
11
 
12
  if ( 'scan' === $code && 'process-start' === $entry['type'] ) {
13
  $entry['description'] = esc_html__( 'Scan Performance', 'better-wp-security' );
14
- } else if ( 'no-changes-found' === $code ) {
15
  $entry['description'] = esc_html__( 'No Changes Found', 'better-wp-security' );
16
- } else if ( 'changes-found' === $code ) {
17
  if ( isset( $code_data[0] ) ) {
18
  $entry['description'] = sprintf( esc_html__( '%1$d Added, %2$d Removed, %3$d Changed', 'better-wp-security' ), $code_data[0], $code_data[1], $code_data[2] );
19
  } else {
@@ -44,7 +46,7 @@ final class ITSEC_File_Change_Logs {
44
  } elseif ( 'recovery-scheduled' === $code ) {
45
  $entry['description'] = esc_html__( 'Recovery Scheduled', 'better-wp-security' );
46
  } elseif ( 'file-scan-aborted' === $code ) {
47
- if ( ! empty( $code_data[0] ) ) {
48
  if ( $user = get_userdata( $code_data[0] ) ) {
49
  $by = $user->display_name;
50
  } else {
@@ -71,7 +73,7 @@ final class ITSEC_File_Change_Logs {
71
  public function filter_entry_for_details_display( $details, $entry, $code, $code_data ) {
72
  $entry = $this->filter_entry_for_list_display( $entry, $code, $code_data );
73
 
74
- $details['module']['content'] = $entry['module_display'];
75
  $details['description']['content'] = $entry['description'];
76
 
77
  if ( 'changes-found' === $code || 'no-changes-found' === $code ) {
@@ -94,9 +96,9 @@ final class ITSEC_File_Change_Logs {
94
  );
95
 
96
  foreach ( $types as $type => $header ) {
97
- $details[$type] = array(
98
  'header' => $header,
99
- 'content' => '<pre>' . implode( "\n", array_keys( $entry['data'][$type] ) ) . '</pre>',
100
  );
101
  }
102
  }
@@ -105,5 +107,18 @@ final class ITSEC_File_Change_Logs {
105
 
106
  return $details;
107
  }
 
 
 
 
 
 
 
 
 
 
 
 
108
  }
 
109
  new ITSEC_File_Change_Logs();
4
  public function __construct() {
5
  add_filter( 'itsec_logs_prepare_file_change_entry_for_list_display', array( $this, 'filter_entry_for_list_display' ), 10, 3 );
6
  add_filter( 'itsec_logs_prepare_file_change_entry_for_details_display', array( $this, 'filter_entry_for_details_display' ), 10, 4 );
7
+ add_filter( 'itsec_highlighted_log_file-change-report_notice_title', array( $this, 'filter_highlight_title' ), 10, 2 );
8
+ add_filter( 'itsec_highlighted_log_file-change-report_notice_message', array( $this, 'filter_highlight_message' ), 10, 2 );
9
  }
10
 
11
  public function filter_entry_for_list_display( $entry, $code, $code_data ) {
13
 
14
  if ( 'scan' === $code && 'process-start' === $entry['type'] ) {
15
  $entry['description'] = esc_html__( 'Scan Performance', 'better-wp-security' );
16
+ } elseif ( 'no-changes-found' === $code ) {
17
  $entry['description'] = esc_html__( 'No Changes Found', 'better-wp-security' );
18
+ } elseif ( 'changes-found' === $code ) {
19
  if ( isset( $code_data[0] ) ) {
20
  $entry['description'] = sprintf( esc_html__( '%1$d Added, %2$d Removed, %3$d Changed', 'better-wp-security' ), $code_data[0], $code_data[1], $code_data[2] );
21
  } else {
46
  } elseif ( 'recovery-scheduled' === $code ) {
47
  $entry['description'] = esc_html__( 'Recovery Scheduled', 'better-wp-security' );
48
  } elseif ( 'file-scan-aborted' === $code ) {
49
+ if ( ! empty( $code_data[0] ) ) {
50
  if ( $user = get_userdata( $code_data[0] ) ) {
51
  $by = $user->display_name;
52
  } else {
73
  public function filter_entry_for_details_display( $details, $entry, $code, $code_data ) {
74
  $entry = $this->filter_entry_for_list_display( $entry, $code, $code_data );
75
 
76
+ $details['module']['content'] = $entry['module_display'];
77
  $details['description']['content'] = $entry['description'];
78
 
79
  if ( 'changes-found' === $code || 'no-changes-found' === $code ) {
96
  );
97
 
98
  foreach ( $types as $type => $header ) {
99
+ $details[ $type ] = array(
100
  'header' => $header,
101
+ 'content' => '<pre>' . implode( "\n", array_keys( $entry['data'][ $type ] ) ) . '</pre>',
102
  );
103
  }
104
  }
107
 
108
  return $details;
109
  }
110
+
111
+ public function filter_highlight_title( $title, $entry ) {
112
+ return esc_html__( 'iThemes Security noticed file changes in your WordPress site.', 'better-wp-security' );
113
+ }
114
+
115
+ public function filter_highlight_message( $title, $entry ) {
116
+ return sprintf(
117
+ esc_html__( 'Please %1$sreview the logs%2$s to make sure your system has not been compromised.', 'better-wp-security' ),
118
+ '<a href="{{ $view }}">',
119
+ '</a>'
120
+ );
121
+ }
122
  }
123
+
124
  new ITSEC_File_Change_Logs();
core/modules/file-change/scanner.php CHANGED
@@ -159,9 +159,9 @@ class ITSEC_File_Change_Scanner {
159
  * Is there a scan scheduled.
160
  *
161
  * @param ITSEC_Scheduler $scheduler The scheduler to use.
162
- * @param bool $user_initiated Whether the user initiated scan is running or the scheduled loop scan.
163
  * Null to check either.
164
- *
165
  * @return bool Is it scheduled.
166
  */
167
  private static function is_scheduled( $scheduler, $user_initiated = null ) {
@@ -796,10 +796,6 @@ class ITSEC_File_Change_Scanner {
796
  ITSEC_Modules::set_setting( 'file-change', 'last_scan', $found_changes ? $id : 0 );
797
  update_site_option( 'itsec_file_change_latest', $list );
798
 
799
- if ( $found_changes && $this->settings['notify_admin'] ) {
800
- ITSEC_Modules::set_setting( 'file-change', 'show_warning', true );
801
- }
802
-
803
  if ( $process = $storage->get( 'process' ) ) {
804
  ITSEC_Log::add_process_stop( $process );
805
  }
@@ -1144,4 +1140,4 @@ class ITSEC_File_Change_Scanner {
1144
 
1145
  return $rows;
1146
  }
1147
- }
159
  * Is there a scan scheduled.
160
  *
161
  * @param ITSEC_Scheduler $scheduler The scheduler to use.
162
+ * @param bool $user_initiated Whether the user initiated scan is running or the scheduled loop scan.
163
  * Null to check either.
164
+ *
165
  * @return bool Is it scheduled.
166
  */
167
  private static function is_scheduled( $scheduler, $user_initiated = null ) {
796
  ITSEC_Modules::set_setting( 'file-change', 'last_scan', $found_changes ? $id : 0 );
797
  update_site_option( 'itsec_file_change_latest', $list );
798
 
 
 
 
 
799
  if ( $process = $storage->get( 'process' ) ) {
800
  ITSEC_Log::add_process_stop( $process );
801
  }
1140
 
1141
  return $rows;
1142
  }
1143
+ }
core/modules/file-change/settings-page.php CHANGED
@@ -132,14 +132,6 @@ final class ITSEC_File_Change_Settings_Page extends ITSEC_Module_Settings_Page {
132
  <label for="itsec-file-change-types"><?php _e( 'File types listed here will not be checked for changes. While it is possible to change files such as images it is quite rare and nearly all known WordPress attacks exploit php, js and other text files.', 'better-wp-security' ); ?></label>
133
  </td>
134
  </tr>
135
- <tr>
136
- <th scope="row"><label for="itsec-file-change-notify_admin"><?php _e( 'Display File Change Admin Warning', 'better-wp-security' ); ?></label></th>
137
- <td>
138
- <?php $form->add_checkbox( 'notify_admin' ); ?>
139
- <label for="itsec-file-change-notify_admin"><?php _e( 'Display file change admin warning', 'better-wp-security' ); ?></label>
140
- <p class="description"><?php _e( 'Disabling this feature will prevent the file change warning from displaying to the site administrator in the WordPress Dashboard. Note that disabling both the error message and the email notification will result in no notifications of file changes. The only way you will be able to tell is by manually checking the log files.', 'better-wp-security' ); ?></p>
141
- </td>
142
- </tr>
143
  <?php do_action( 'itsec-file-change-settings-form', $form ); ?>
144
  </table>
145
  <?php
132
  <label for="itsec-file-change-types"><?php _e( 'File types listed here will not be checked for changes. While it is possible to change files such as images it is quite rare and nearly all known WordPress attacks exploit php, js and other text files.', 'better-wp-security' ); ?></label>
133
  </td>
134
  </tr>
 
 
 
 
 
 
 
 
135
  <?php do_action( 'itsec-file-change-settings-form', $form ); ?>
136
  </table>
137
  <?php
core/modules/file-change/settings.php CHANGED
@@ -19,8 +19,6 @@ final class ITSEC_File_Change_Settings extends ITSEC_Settings {
19
  // Video
20
  '.asf', '.avi', '.mkv', '.mov', '.mp4', '.mpe', '.mpeg', '.mpg', '.ogv', '.qt', '.rm', '.vob', '.webm', '.wm', '.wmv',
21
  ),
22
- 'notify_admin' => true,
23
- 'show_warning' => false,
24
  'expected_hashes' => array(),
25
  'last_scan' => 0,
26
  );
19
  // Video
20
  '.asf', '.avi', '.mkv', '.mov', '.mp4', '.mpe', '.mpeg', '.mpg', '.ogv', '.qt', '.rm', '.vob', '.webm', '.wm', '.wmv',
21
  ),
 
 
22
  'expected_hashes' => array(),
23
  'last_scan' => 0,
24
  );
core/modules/file-change/setup.php CHANGED
@@ -317,6 +317,13 @@ if ( ! class_exists( 'ITSEC_File_Change_Setup' ) ) {
317
  ITSEC_File_Change::make_progress_storage()->clear();
318
  ITSEC_File_Change_Scanner::schedule_start( false );
319
  }
 
 
 
 
 
 
 
320
  }
321
 
322
  /**
317
  ITSEC_File_Change::make_progress_storage()->clear();
318
  ITSEC_File_Change_Scanner::schedule_start( false );
319
  }
320
+
321
+ if ( $itsec_old_version > 4114 ) {
322
+ if ( ! ITSEC_Modules::get_setting( 'file-change', 'notify_admin' ) ) {
323
+ ITSEC_Lib::load( 'highlighted-logs' );
324
+ ITSEC_Lib_Highlighted_Logs::mute( 'file-change-report' );
325
+ }
326
+ }
327
  }
328
 
329
  /**
core/modules/file-change/validator.php CHANGED
@@ -9,13 +9,12 @@ class ITSEC_File_Change_Validator extends ITSEC_Validator {
9
 
10
  unset( $this->settings['latest_changes'] );
11
 
12
- $this->set_previous_if_empty( array( 'show_warning', 'expected_hashes', 'last_scan' ) );
13
- $this->preserve_setting_if_exists( array( 'email', 'split', 'last_run', 'last_chunk', 'method' ) );
14
- $this->vars_to_skip_validate_matching_fields = array( 'email', 'split', 'last_run', 'last_chunk', 'method', 'latest_changes' );
15
 
16
  $this->sanitize_setting( 'newline-separated-array', 'file_list', __( 'Files and Folders List', 'better-wp-security' ) );
17
  $this->sanitize_setting( 'newline-separated-extensions', 'types', __( 'Ignore File Types', 'better-wp-security' ) );
18
- $this->sanitize_setting( 'bool', 'notify_admin', __( 'Display File Change Admin Warning', 'better-wp-security' ) );
19
 
20
  $this->settings = apply_filters( 'itsec-file-change-sanitize-settings', $this->settings );
21
  }
9
 
10
  unset( $this->settings['latest_changes'] );
11
 
12
+ $this->set_previous_if_empty( array( 'expected_hashes', 'last_scan' ) );
13
+ $this->preserve_setting_if_exists( array( 'email', 'split', 'last_run', 'last_chunk', 'method', 'notify_admin' ) );
14
+ $this->vars_to_skip_validate_matching_fields = array( 'email', 'split', 'last_run', 'last_chunk', 'method', 'latest_changes', 'show_warning', 'notify_admin' );
15
 
16
  $this->sanitize_setting( 'newline-separated-array', 'file_list', __( 'Files and Folders List', 'better-wp-security' ) );
17
  $this->sanitize_setting( 'newline-separated-extensions', 'types', __( 'Ignore File Types', 'better-wp-security' ) );
 
18
 
19
  $this->settings = apply_filters( 'itsec-file-change-sanitize-settings', $this->settings );
20
  }
core/modules/global/active.php CHANGED
@@ -3,69 +3,7 @@
3
  function itsec_global_filter_whitelisted_ips( $whitelisted_ips ) {
4
  return array_merge( $whitelisted_ips, ITSEC_Modules::get_setting( 'global', 'lockout_white_list', array() ) );
5
  }
6
- add_action( 'itsec_white_ips', 'itsec_global_filter_whitelisted_ips', 0 );
7
-
8
-
9
- function itsec_global_add_notice() {
10
-
11
- if ( ! defined( 'ITSEC_USE_CRON' ) && ITSEC_Core::current_user_can_manage() ) {
12
- ITSEC_Core::add_notice( 'itsec_show_disable_cron_constants_notice' );
13
- }
14
-
15
- if ( ITSEC_Core::is_temp_disable_modules_set() && ITSEC_Core::current_user_can_manage() ) {
16
- ITSEC_Core::add_notice( 'itsec_show_temp_disable_modules_notice', true );
17
- }
18
-
19
- }
20
- add_action( 'admin_init', 'itsec_global_add_notice', 0 );
21
-
22
- function itsec_network_brute_force_add_notice() {
23
- if ( ITSEC_Modules::get_setting( 'network-brute-force', 'api_nag' ) && current_user_can( ITSEC_Core::get_required_cap() ) ) {
24
- ITSEC_Core::add_notice( 'itsec_network_brute_force_show_notice' );
25
- }
26
- }
27
- add_action( 'admin_init', 'itsec_network_brute_force_add_notice' );
28
-
29
- function itsec_network_brute_force_show_notice() {
30
- echo '<div id="itsec-notice-network-brute-force" class="updated itsec-notice"><span class="it-icon-itsec"></span>'
31
- . __( 'New! Take your site security to the next level by activating iThemes Brute Force Network Protection.', 'better-wp-security' )
32
- . '<a class="itsec-notice-button" href="' . esc_url( wp_nonce_url( add_query_arg( array( 'module' => 'network-brute-force', 'enable' => 'network-brute-force' ), ITSEC_Core::get_settings_page_url() ), 'itsec-enable-network-brute-force', 'itsec-enable-nonce' ) ) . '" onclick="document.location.href=\'?itsec_no_api_nag=off&_wpnonce=' . wp_create_nonce( 'itsec-nag' ) . '\';">' . __( 'Get Free API Key', 'better-wp-security' ) . '</a>'
33
- . '<button class="itsec-notice-hide" data-nonce="' . wp_create_nonce( 'dismiss-brute-force-network-notice' ) . '" data-source="brute_force_network">&times;</button>'
34
- . '</div>';
35
- }
36
-
37
- function itsec_network_brute_force_dismiss_notice() {
38
- if ( wp_verify_nonce( $_REQUEST['notice_nonce'], 'dismiss-brute-force-network-notice' ) ) {
39
- ITSEC_Modules::set_setting( 'network-brute-force', 'api_nag', false );
40
- wp_send_json_success();
41
- }
42
- wp_send_json_error();
43
- }
44
- add_action( 'wp_ajax_itsec-dismiss-notice-brute_force_network', 'itsec_network_brute_force_dismiss_notice' );
45
-
46
- function itsec_show_temp_disable_modules_notice() {
47
- 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' ) );
48
- }
49
-
50
- function itsec_show_disable_cron_constants_notice() {
51
-
52
- $check = array( 'ITSEC_BACKUP_CRON', 'ITSEC_FILE_CHECK_CRON' );
53
- $using = array();
54
-
55
- foreach ( $check as $constant ) {
56
- if ( defined( $constant ) && constant( $constant ) ) {
57
- $using[] = "<span class='code'>{$constant}</span>";
58
- }
59
- }
60
-
61
- if ( $using ) {
62
- $message = wp_sprintf( esc_html(
63
- _n( 'The %l define is deprecated. Please use %s instead.', 'The %l defines are deprecated. Please use %s instead.', count( $using ), 'better-wp-security' )
64
- ), $using, '<span class="code">ITSEC_USE_CRON</span>' );
65
-
66
- echo "<div class='notice notice-error'><p>{$message}</p></div>";
67
- }
68
- }
69
 
70
  /**
71
  * On every page load, check if the cron test has successfully fired in time.
@@ -176,4 +114,4 @@ function itsec_basename_attachment_thumbs( $data ) {
176
  return $data;
177
  }
178
 
179
- add_filter( 'wp_update_attachment_metadata', 'itsec_basename_attachment_thumbs' );
3
  function itsec_global_filter_whitelisted_ips( $whitelisted_ips ) {
4
  return array_merge( $whitelisted_ips, ITSEC_Modules::get_setting( 'global', 'lockout_white_list', array() ) );
5
  }
6
+ add_filter( 'itsec_white_ips', 'itsec_global_filter_whitelisted_ips', 0 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
  /**
9
  * On every page load, check if the cron test has successfully fired in time.
114
  return $data;
115
  }
116
 
117
+ add_filter( 'wp_update_attachment_metadata', 'itsec_basename_attachment_thumbs' );
core/modules/global/notices.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class ITSEC_Admin_Notice_Network_Brute_Force_Promo implements ITSEC_Admin_Notice {
4
+ public function get_id() {
5
+ return 'network-brute-force-promo';
6
+ }
7
+
8
+ public function get_title() {
9
+ return '';
10
+ }
11
+
12
+ public function get_message() {
13
+ return esc_html__( 'New! Take your site security to the next level by activating iThemes Brute Force Network Protection.', 'better-wp-security' );
14
+ }
15
+
16
+ public function get_severity() {
17
+ return self::S_INFO;
18
+ }
19
+
20
+ public function get_meta() {
21
+ return array();
22
+ }
23
+
24
+ public function show_for_context( ITSEC_Admin_Notice_Context $context ) {
25
+ return true;
26
+ }
27
+
28
+ public function get_actions() {
29
+ $url = ITSEC_Core::get_settings_module_url( 'network-brute-force' );
30
+
31
+ if ( ! ITSEC_Modules::is_active( 'network-brute-force' ) ) {
32
+ $url = add_query_arg( 'enable', 'network-brute-force', $url );
33
+ $url = wp_nonce_url( $url, 'itsec-enable-network-brute-force', 'itsec-enable-nonce' );
34
+ }
35
+
36
+ return array(
37
+ 'register' => new ITSEC_Admin_Notice_Action_Link(
38
+ $url,
39
+ esc_html__( 'Get Free API Key', 'better-wp-security' ),
40
+ ITSEC_Admin_Notice_Action::S_PRIMARY
41
+ ),
42
+ );
43
+ }
44
+ }
45
+
46
+ if ( ITSEC_Core::is_temp_disable_modules_set() ) {
47
+ ITSEC_Lib_Admin_Notices::register(
48
+ new ITSEC_Admin_Notice_Managers_Only(
49
+ new ITSEC_Admin_Notice_Static(
50
+ 'disable-modules', 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' ), '', ITSEC_Admin_Notice::S_WARN
51
+ )
52
+ )
53
+ );
54
+ }
55
+
56
+ if ( ! ITSEC_Modules::is_active( 'network-brute-force' ) || ! ITSEC_Modules::get_setting( 'network-brute-force', 'api_secret' ) ) {
57
+ ITSEC_Lib_Admin_Notices::register(
58
+ new ITSEC_Admin_Notice_Globally_Dismissible(
59
+ new ITSEC_Admin_Notice_Managers_Only(
60
+ new ITSEC_Admin_Notice_Network_Brute_Force_Promo()
61
+ )
62
+ )
63
+ );
64
+ }
core/modules/global/settings-page.php CHANGED
@@ -300,6 +300,7 @@ final class ITSEC_Global_Settings_Page extends ITSEC_Module_Settings_Page {
300
  <td>
301
  <?php $form->add_checkbox( 'hide_admin_bar' ); ?>
302
  <label for="itsec-global-hide_admin_bar"><?php _e( 'Hide security menu in admin bar.', 'better-wp-security' ); ?></label>
 
303
  </td>
304
  </tr>
305
  <tr>
300
  <td>
301
  <?php $form->add_checkbox( 'hide_admin_bar' ); ?>
302
  <label for="itsec-global-hide_admin_bar"><?php _e( 'Hide security menu in admin bar.', 'better-wp-security' ); ?></label>
303
+ <p class="description"><?php esc_html_e( 'Remove the Security Messages Menu from the admin bar and receive the messages as traditional WordPress Admin Notices.', 'better-wp-security' ); ?></p>
304
  </td>
305
  </tr>
306
  <tr>
core/modules/global/settings.php CHANGED
@@ -39,6 +39,7 @@ final class ITSEC_Global_Settings_New extends ITSEC_Settings {
39
  'cron_test_time' => 0,
40
  'enable_grade_report' => false,
41
  'server_ips' => array(),
 
42
  );
43
  }
44
 
39
  'cron_test_time' => 0,
40
  'enable_grade_report' => false,
41
  'server_ips' => array(),
42
+ 'feature_flags' => array(),
43
  );
44
  }
45
 
core/modules/global/validator.php CHANGED
@@ -19,8 +19,8 @@ class ITSEC_Global_Validator extends ITSEC_Validator {
19
  }
20
 
21
 
22
- $this->vars_to_skip_validate_matching_fields = array( 'digest_last_sent', 'digest_messages', 'digest_email', 'email_notifications', 'notification_email', 'backup_email', 'show_new_dashboard_notice', 'proxy_override', 'proxy', 'proxy_header', 'server_ips', 'initial_build' );
23
- $this->set_previous_if_empty( array( 'did_upgrade', 'log_info', 'show_security_check', 'build', 'activation_timestamp', 'lock_file', 'cron_status', 'use_cron', 'cron_test_time', 'proxy', 'proxy_header', 'server_ips', 'initial_build' ) );
24
  $this->set_default_if_empty( array( 'log_location', 'nginx_file', 'enable_grade_report' ) );
25
  $this->preserve_setting_if_exists( array( 'digest_email', 'email_notifications', 'notification_email', 'backup_email', 'proxy_override' ) );
26
 
@@ -60,6 +60,7 @@ class ITSEC_Global_Validator extends ITSEC_Validator {
60
  $this->settings['community_lockout_message'] = trim( wp_kses( $this->settings['community_lockout_message'], $allowed_tags ) );
61
 
62
  $this->sanitize_setting( 'newline-separated-ips', 'server_ips', __( 'Server IPs', 'better-wp-security' ) );
 
63
  }
64
 
65
  public function get_proxy_types() {
19
  }
20
 
21
 
22
+ $this->vars_to_skip_validate_matching_fields = array( 'digest_last_sent', 'digest_messages', 'digest_email', 'email_notifications', 'notification_email', 'backup_email', 'show_new_dashboard_notice', 'proxy_override', 'proxy', 'proxy_header', 'server_ips', 'initial_build', 'feature_flags' );
23
+ $this->set_previous_if_empty( array( 'did_upgrade', 'log_info', 'show_security_check', 'build', 'activation_timestamp', 'lock_file', 'cron_status', 'use_cron', 'cron_test_time', 'proxy', 'proxy_header', 'server_ips', 'initial_build', 'feature_flags' ) );
24
  $this->set_default_if_empty( array( 'log_location', 'nginx_file', 'enable_grade_report' ) );
25
  $this->preserve_setting_if_exists( array( 'digest_email', 'email_notifications', 'notification_email', 'backup_email', 'proxy_override' ) );
26
 
60
  $this->settings['community_lockout_message'] = trim( wp_kses( $this->settings['community_lockout_message'], $allowed_tags ) );
61
 
62
  $this->sanitize_setting( 'newline-separated-ips', 'server_ips', __( 'Server IPs', 'better-wp-security' ) );
63
+ $this->sanitize_setting( 'array', 'feature_flags', __( 'Feature Flags', 'better-wp-security' ) );
64
  }
65
 
66
  public function get_proxy_types() {
core/modules/ipcheck/active.php CHANGED
@@ -1,5 +1,5 @@
1
  <?php
2
 
3
  require_once( 'class-itsec-ipcheck.php' );
4
- $itsec_ip_check = new ITSEC_IPCheck( ITSEC_Core::get_instance() );
5
  $itsec_ip_check->run();
1
  <?php
2
 
3
  require_once( 'class-itsec-ipcheck.php' );
4
+ $itsec_ip_check = new ITSEC_IPCheck();
5
  $itsec_ip_check->run();
core/modules/ipcheck/class-itsec-ipcheck.php CHANGED
@@ -5,25 +5,16 @@
5
  *
6
  * Provides static calls to the iThemes IPCheck API
7
  *
8
- * @package iThemes_Security
9
- *
10
  * @since 4.5
11
  *
 
 
12
  */
13
  class ITSEC_IPCheck {
14
- private $endpoint = 'http://ipcheck-api.ithemes.com/?action=';
15
- private $settings;
16
-
17
  public function run() {
18
  add_filter( 'authenticate', array( $this, 'filter_authenticate' ), 10000, 3 ); // Set a very late priority so that we run after actual authentication takes place.
19
  }
20
 
21
- private function load_settings() {
22
- if ( ! isset( $this->settings ) ) {
23
- $this->settings = ITSEC_Modules::get_settings( 'network-brute-force' );
24
- }
25
- }
26
-
27
  public function filter_authenticate( $user, $username, $password ) {
28
  /** @var $itsec_lockout ITSEC_Lockout */
29
  global $itsec_lockout;
@@ -33,187 +24,20 @@ class ITSEC_IPCheck {
33
  return $user;
34
  }
35
 
36
- $this->load_settings();
 
 
37
 
38
  if ( is_wp_error( $user ) || null == $user ) {
39
- if ( $this->report_ip() && $this->settings['enable_ban'] ) {
40
  ITSEC_Log::add_notice( 'ipcheck', 'failed-login-by-blocked-ip', array( 'details' => ITSEC_Lib::get_login_details() ) );
41
  $itsec_lockout->execute_lock( array( 'network_lock' => true ) );
42
  }
43
- } else if ( $this->settings['enable_ban'] && $this->is_ip_banned() ) {
44
  ITSEC_Log::add_critical_issue( 'ipcheck', 'successful-login-by-blocked-ip', array( 'details' => ITSEC_Lib::get_login_details() ) );
45
  $itsec_lockout->execute_lock( array( 'network_lock' => true ) );
46
  }
47
 
48
  return $user;
49
  }
50
-
51
- /**
52
- * Check visitor IP to see if it is banned by IPCheck.
53
- *
54
- * @since 3.0.0
55
- *
56
- * @return bool true if banned, false otherwise.
57
- */
58
- private function is_ip_banned() {
59
- return $this->get_server_response( 'check-ip' );
60
- }
61
-
62
- /**
63
- * Report visitor IP for blacklistable-offense to IPCheck.
64
- *
65
- * @since 3.0.0
66
- *
67
- * @return bool true if banned, false otherwise.
68
- */
69
- private function report_ip() {
70
- return $this->get_server_response( 'report-ip' );
71
- }
72
-
73
- private function get_server_response( $action ) {
74
- $this->load_settings();
75
-
76
- if ( empty( $this->settings['api_key'] ) || empty( $this->settings['api_secret'] ) ) {
77
- return false;
78
- }
79
-
80
-
81
- $ip = ITSEC_Lib::get_ip();
82
-
83
- require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-ip-tools.php' );
84
-
85
- if ( ! ITSEC_Lib_IP_Tools::validate( $ip ) || ITSEC_Lib::is_ip_whitelisted( $ip ) ) {
86
- return false;
87
- }
88
-
89
-
90
- $cache = $this->get_cache( $ip );
91
-
92
- if ( 'check-ip' === $action ) {
93
- if ( $cache['cache_ttl'] >= ITSEC_Core::get_current_time_gmt() ) {
94
- return $cache['block'];
95
- }
96
- } else if ( 'report-ip' === $action ) {
97
- if ( $cache['report_ttl'] >= ITSEC_Core::get_current_time_gmt() ) {
98
- return $cache['block'];
99
- }
100
- }
101
-
102
-
103
- $args = json_encode(
104
- array(
105
- 'apikey' => $this->settings['api_key'],
106
- 'behavior' => 'brute-force-login',
107
- 'ip' => $ip,
108
- 'site' => home_url( '', 'http' ),
109
- 'timestamp' => ITSEC_Core::get_current_time_gmt(),
110
- )
111
- );
112
-
113
- $request = array(
114
- 'body' => array(
115
- 'request' => $args,
116
- 'signature' => $this->hmac_sha1( $this->settings['api_secret'], $action . $args ),
117
- ),
118
- );
119
-
120
-
121
- $response = wp_remote_post( $this->endpoint . $action, $request );
122
-
123
- if ( is_wp_error( $response ) || ! isset( $response['body'] ) ) {
124
- return false;
125
- }
126
-
127
-
128
- $response = json_decode( $response['body'], true );
129
-
130
- if ( ! is_array( $response ) || ! isset( $response['success'] ) || ! $response['success'] ) {
131
- return false;
132
- }
133
-
134
-
135
- $this->set_cache( $ip, $response );
136
-
137
- $cache_seconds = isset( $response['cache_ttl'] ) ? absint( $response['cache_ttl'] ) : 3600;
138
-
139
- if ( isset( $response['block'] ) && $response['block'] ) {
140
- $data = array(
141
- 'expires' => date( 'Y-m-d H:i:s', ITSEC_Core::get_current_time() + $cache_seconds ),
142
- 'expires_gmt' => date( 'Y-m-d H:i:s', ITSEC_Core::get_current_time_gmt() + $cache_seconds ),
143
- 'type' => 'host',
144
- );
145
-
146
- ITSEC_Log::add_action( 'ipcheck', 'ip-blocked', $data );
147
-
148
- return true;
149
- }
150
-
151
- return false;
152
- }
153
-
154
- private function set_cache( $ip, $response ) {
155
- $cache = $this->get_cache( $ip );
156
- $time = ITSEC_Core::get_current_time_gmt();
157
-
158
- if ( isset( $response['block'] ) ) {
159
- $cache['block'] = (boolean) $response['block'];
160
- }
161
-
162
- if ( isset( $response['cache_ttl'] ) ) {
163
- $cache['cache_ttl'] = intval( $response['cache_ttl'] ) + $time;
164
- } else if ( 0 === $cache['cache_ttl'] ) {
165
- $cache['cache_ttl'] = $time + HOUR_IN_SECONDS;
166
- }
167
-
168
- if ( isset( $response['report_ttl'] ) ) {
169
- $cache['report_ttl'] = intval( $response['report_ttl'] ) + $time;
170
- }
171
-
172
- $transient_time = max( $cache['cache_ttl'], $cache['report_ttl'] ) - $time;
173
-
174
-
175
- set_site_transient( "itsec_ipcheck_$ip", $cache, $transient_time );
176
- }
177
-
178
- private function get_cache( $ip ) {
179
- $cache = get_site_transient( "itsec_ipcheck_$ip" );
180
-
181
- $defaults = array(
182
- 'block' => false,
183
- 'cache_ttl' => 0,
184
- 'report_ttl' => 0,
185
- );
186
-
187
- if ( ! is_array( $cache ) ) {
188
- return $defaults;
189
- }
190
-
191
- return array_merge( $defaults, $cache );
192
- }
193
-
194
- /**
195
- * Calculates the HMAC of a string using SHA1.
196
- *
197
- * there is a native PHP hmac function, but we use this one for
198
- * the widest compatibility with older PHP versions
199
- *
200
- * @param string $key the shared secret key used to generate the mac
201
- * @param string $data data to be signed
202
- *
203
- *
204
- * @return string base64 encoded hmac
205
- */
206
- private function hmac_sha1( $key, $data ) {
207
- if ( strlen( $key ) > 64 ) {
208
- $key = pack( 'H*', sha1( $key ) );
209
- }
210
-
211
- $key = str_pad( $key, 64, chr( 0x00 ) );
212
- $ipad = str_repeat( chr( 0x36 ), 64 );
213
- $opad = str_repeat( chr( 0x5c ), 64 );
214
- $hmac = pack( 'H*', sha1( ( $key ^ $opad ) . pack( 'H*', sha1( ( $key ^ $ipad ) . $data ) ) ) );
215
-
216
- return base64_encode( $hmac );
217
- }
218
-
219
  }
5
  *
6
  * Provides static calls to the iThemes IPCheck API
7
  *
 
 
8
  * @since 4.5
9
  *
10
+ * @package iThemes_Security
11
+ *
12
  */
13
  class ITSEC_IPCheck {
 
 
 
14
  public function run() {
15
  add_filter( 'authenticate', array( $this, 'filter_authenticate' ), 10000, 3 ); // Set a very late priority so that we run after actual authentication takes place.
16
  }
17
 
 
 
 
 
 
 
18
  public function filter_authenticate( $user, $username, $password ) {
19
  /** @var $itsec_lockout ITSEC_Lockout */
20
  global $itsec_lockout;
24
  return $user;
25
  }
26
 
27
+ require_once( dirname( __FILE__ ) . '/utilities.php' );
28
+
29
+ $enable_ban = ITSEC_Modules::get_setting( 'network-brute-force', 'enable_ban' );
30
 
31
  if ( is_wp_error( $user ) || null == $user ) {
32
+ if ( ITSEC_Network_Brute_Force_Utilities::report_ip() && $enable_ban ) {
33
  ITSEC_Log::add_notice( 'ipcheck', 'failed-login-by-blocked-ip', array( 'details' => ITSEC_Lib::get_login_details() ) );
34
  $itsec_lockout->execute_lock( array( 'network_lock' => true ) );
35
  }
36
+ } elseif ( $enable_ban && ITSEC_Network_Brute_Force_Utilities::is_ip_banned() ) {
37
  ITSEC_Log::add_critical_issue( 'ipcheck', 'successful-login-by-blocked-ip', array( 'details' => ITSEC_Lib::get_login_details() ) );
38
  $itsec_lockout->execute_lock( array( 'network_lock' => true ) );
39
  }
40
 
41
  return $user;
42
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  }
core/modules/ipcheck/settings-page.php CHANGED
@@ -50,6 +50,7 @@ final class ITSEC_Network_Brute_Force_Settings_Page extends ITSEC_Module_Setting
50
  }
51
 
52
  protected function render_settings( $form ) {
 
53
  $settings = $form->get_options();
54
 
55
  ?>
@@ -72,7 +73,7 @@ final class ITSEC_Network_Brute_Force_Settings_Page extends ITSEC_Module_Setting
72
  <th scope="row"><label for="itsec-network-brute-force-updates_optin"><?php _e( 'Receive Email Updates', 'better-wp-security' ); ?></label></th>
73
  <td>
74
  <?php $form->add_checkbox( 'updates_optin' ); ?>
75
- <label for="itsec-network-brute-force-updates_optin"><?php _e( 'Receive email updates about WordPress Security from iThemes.', 'better-wp-security' ); ?></label>
76
  </td>
77
  </tr>
78
  </table>
50
  }
51
 
52
  protected function render_settings( $form ) {
53
+ $form->set_options( array( 'updates_optin' => false ) );
54
  $settings = $form->get_options();
55
 
56
  ?>
73
  <th scope="row"><label for="itsec-network-brute-force-updates_optin"><?php _e( 'Receive Email Updates', 'better-wp-security' ); ?></label></th>
74
  <td>
75
  <?php $form->add_checkbox( 'updates_optin' ); ?>
76
+ <label for="itsec-network-brute-force-updates_optin"><?php _e( 'Receive email updates about WordPress Security and marketing news from iThemes.', 'better-wp-security' ); ?></label>
77
  </td>
78
  </tr>
79
  </table>
core/modules/ipcheck/settings.php CHANGED
@@ -10,7 +10,7 @@ final class ITSEC_Network_Brute_Force_Settings extends ITSEC_Settings {
10
  'api_key' => '',
11
  'api_secret' => '',
12
  'enable_ban' => true,
13
- 'updates_optin' => true,
14
  'api_nag' => true,
15
  );
16
  }
10
  'api_key' => '',
11
  'api_secret' => '',
12
  'enable_ban' => true,
13
+ 'updates_optin' => false,
14
  'api_nag' => true,
15
  );
16
  }
core/modules/ipcheck/utilities.php CHANGED
@@ -1,7 +1,179 @@
1
  <?php
2
 
3
  final class ITSEC_Network_Brute_Force_Utilities {
4
- private static $network_endpoint = 'http://ipcheck-api.ithemes.com/';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  /**
7
  * Retrieve an API key from the IPCheck server
@@ -20,15 +192,10 @@ final class ITSEC_Network_Brute_Force_Utilities {
20
  return new WP_Error( 'itsec-network-brute-force-utilities-get-api-key-bad-email', sprintf( __( 'The supplied email address (%s) is invalid. A valid email address is required in order to sign up for the Network Bruteforce Protection by iThemes.', 'better-wp-security' ), $email ) );
21
  }
22
 
23
- $args = array(
24
- 'action' => 'request-key',
25
- 'email' => $email,
26
- 'optin' => $optin,
27
- );
28
-
29
- $url = add_query_arg( $args, self::$network_endpoint );
30
-
31
- $response = wp_remote_get( $url );
32
 
33
  if ( is_wp_error( $response ) ) {
34
  return $response;
@@ -40,10 +207,17 @@ final class ITSEC_Network_Brute_Force_Utilities {
40
 
41
  $body = json_decode( $response['body'], true );
42
 
43
- if ( ! is_array( $body ) || ! isset( $body['apikey'] ) ) {
44
  return new WP_Error( 'itsec-network-brute-force-utilities-get-api-key-bad-response', __( 'An unknown error prevented the API key request from succeeding. The request for an API key returned an unrecognized response. Please wait a few minutes and try again.', 'better-wp-security' ) );
45
  }
46
 
 
 
 
 
 
 
 
47
  $key = trim( sanitize_text_field( $body['apikey'] ) );
48
 
49
  if ( empty( $key ) ) {
@@ -69,15 +243,10 @@ final class ITSEC_Network_Brute_Force_Utilities {
69
  return new WP_Error( 'itsec-network-brute-force-utilities-activate-api-key-empty-key', __( 'An unknown error prevented the API key secret request from succeeding. The request for an API key submitted an empty key. Please wait a few minutes and try again.', 'better-wp-security' ) );
70
  }
71
 
72
- $args = array(
73
- 'action' => 'activate-key',
74
  'apikey' => $api_key,
75
  'site' => home_url( '', 'http' ),
76
- );
77
-
78
- $url = add_query_arg( $args, self::$network_endpoint );
79
-
80
- $response = wp_remote_get( $url );
81
 
82
  if ( is_wp_error( $response ) ) {
83
  return $response;
@@ -94,6 +263,7 @@ final class ITSEC_Network_Brute_Force_Utilities {
94
  if ( ! empty( $body['error'] ) && ! empty( $body['error']['message'] ) ) {
95
  return new WP_Error( 'itsec-network-brute-force-utilities-activate-api-key-error-response', sprintf( __( 'There was an error returned from the Network Brute Force Protection API: %1$s', 'better-wp-security' ), $body['error']['message'] ) );
96
  }
 
97
  return new WP_Error( 'itsec-network-brute-force-utilities-activate-api-key-bad-response', __( 'An unknown error prevented the API key secret request from succeeding. The request for an API key secret returned an unrecognized response. Please wait a few minutes and try again.', 'better-wp-security' ) );
98
  }
99
 
@@ -105,4 +275,19 @@ final class ITSEC_Network_Brute_Force_Utilities {
105
 
106
  return $secret;
107
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  }
1
  <?php
2
 
3
  final class ITSEC_Network_Brute_Force_Utilities {
4
+ private static $network_endpoint = 'https://ipcheck-api.ithemes.com/';
5
+
6
+ /**
7
+ * Check visitor IP to see if it is banned by IPCheck.
8
+ *
9
+ * @param string $ip
10
+ *
11
+ * @return bool true if banned, false otherwise.
12
+ */
13
+ public static function is_ip_banned( $ip = '' ) {
14
+ if ( ! $ip ) {
15
+ $ip = ITSEC_Lib::get_ip();
16
+ }
17
+
18
+ require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-ip-tools.php' );
19
+
20
+ if ( ! ITSEC_Lib_IP_Tools::validate( $ip ) || ITSEC_Lib::is_ip_whitelisted( $ip ) ) {
21
+ return false;
22
+ }
23
+
24
+ return self::get_server_response( 'check-ip', $ip );
25
+ }
26
+
27
+ /**
28
+ * Report visitor IP for blacklistable-offense to IPCheck.
29
+ *
30
+ * @param string $ip
31
+ *
32
+ * @return bool true if banned, false otherwise.
33
+ */
34
+ public static function report_ip( $ip = '' ) {
35
+ if ( ! $ip ) {
36
+ $ip = ITSEC_Lib::get_ip();
37
+ }
38
+
39
+ require_once( ITSEC_Core::get_core_dir() . '/lib/class-itsec-lib-ip-tools.php' );
40
+
41
+ if ( ! ITSEC_Lib_IP_Tools::validate( $ip ) || ITSEC_Lib::is_ip_whitelisted( $ip ) ) {
42
+ return false;
43
+ }
44
+
45
+ return self::get_server_response( 'report-ip', $ip );
46
+ }
47
+
48
+ private static function get_server_response( $action, $ip ) {
49
+ $api_key = ITSEC_Modules::get_setting( 'network-brute-force', 'api_key' );
50
+ $api_secret = ITSEC_Modules::get_setting( 'network-brute-force', 'api_secret' );
51
+
52
+ if ( ! $api_key || ! $api_secret ) {
53
+ return false;
54
+ }
55
+
56
+ $cache = self::get_cache( $ip );
57
+
58
+ if ( 'check-ip' === $action ) {
59
+ if ( $cache['cache_ttl'] >= ITSEC_Core::get_current_time_gmt() ) {
60
+ return $cache['block'];
61
+ }
62
+ } elseif ( 'report-ip' === $action ) {
63
+ if ( $cache['report_ttl'] >= ITSEC_Core::get_current_time_gmt() ) {
64
+ return $cache['block'];
65
+ }
66
+ }
67
+
68
+ $args = json_encode( array(
69
+ 'apikey' => $api_key,
70
+ 'behavior' => 'brute-force-login',
71
+ 'ip' => $ip,
72
+ 'site' => home_url( '', 'http' ),
73
+ 'timestamp' => ITSEC_Core::get_current_time_gmt(),
74
+ ) );
75
+
76
+ $response = self::call_api( $action, array(), array(
77
+ 'method' => 'POST',
78
+ 'body' => array(
79
+ 'request' => $args,
80
+ 'signature' => self::hmac_sha1( $api_secret, $action . $args ),
81
+ ),
82
+ ) );
83
+
84
+ if ( is_wp_error( $response ) || ! isset( $response['body'] ) ) {
85
+ return false;
86
+ }
87
+
88
+ $response = json_decode( $response['body'], true );
89
+
90
+ if ( ! is_array( $response ) || empty( $response['success'] ) ) {
91
+ return false;
92
+ }
93
+
94
+ self::set_cache( $ip, $response );
95
+
96
+ $cache_seconds = isset( $response['cache_ttl'] ) ? absint( $response['cache_ttl'] ) : 3600;
97
+
98
+ if ( ! empty( $response['block'] ) ) {
99
+ $data = array(
100
+ 'expires' => date( 'Y-m-d H:i:s', ITSEC_Core::get_current_time() + $cache_seconds ),
101
+ 'expires_gmt' => date( 'Y-m-d H:i:s', ITSEC_Core::get_current_time_gmt() + $cache_seconds ),
102
+ 'type' => 'host',
103
+ );
104
+
105
+ ITSEC_Log::add_action( 'ipcheck', 'ip-blocked', $data );
106
+
107
+ return true;
108
+ }
109
+
110
+ return false;
111
+ }
112
+
113
+ private static function set_cache( $ip, $response ) {
114
+ $cache = self::get_cache( $ip );
115
+ $time = ITSEC_Core::get_current_time_gmt();
116
+
117
+ if ( isset( $response['block'] ) ) {
118
+ $cache['block'] = (boolean) $response['block'];
119
+ }
120
+
121
+ if ( isset( $response['cache_ttl'] ) ) {
122
+ $cache['cache_ttl'] = intval( $response['cache_ttl'] ) + $time;
123
+ } elseif ( 0 === $cache['cache_ttl'] ) {
124
+ $cache['cache_ttl'] = $time + HOUR_IN_SECONDS;
125
+ }
126
+
127
+ if ( isset( $response['report_ttl'] ) ) {
128
+ $cache['report_ttl'] = intval( $response['report_ttl'] ) + $time;
129
+ }
130
+
131
+ $transient_time = max( $cache['cache_ttl'], $cache['report_ttl'] ) - $time;
132
+
133
+
134
+ set_site_transient( "itsec_ipcheck_$ip", $cache, $transient_time );
135
+ }
136
+
137
+ private static function get_cache( $ip ) {
138
+ $cache = get_site_transient( "itsec_ipcheck_$ip" );
139
+
140
+ $defaults = array(
141
+ 'block' => false,
142
+ 'cache_ttl' => 0,
143
+ 'report_ttl' => 0,
144
+ );
145
+
146
+ if ( ! is_array( $cache ) ) {
147
+ return $defaults;
148
+ }
149
+
150
+ return array_merge( $defaults, $cache );
151
+ }
152
+
153
+ /**
154
+ * Calculates the HMAC of a string using SHA1.
155
+ *
156
+ * there is a native PHP hmac function, but we use this one for
157
+ * the widest compatibility with older PHP versions
158
+ *
159
+ * @param string $key the shared secret key used to generate the mac
160
+ * @param string $data data to be signed
161
+ *
162
+ *
163
+ * @return string base64 encoded hmac
164
+ */
165
+ private static function hmac_sha1( $key, $data ) {
166
+ if ( strlen( $key ) > 64 ) {
167
+ $key = pack( 'H*', sha1( $key ) );
168
+ }
169
+
170
+ $key = str_pad( $key, 64, chr( 0x00 ) );
171
+ $ipad = str_repeat( chr( 0x36 ), 64 );
172
+ $opad = str_repeat( chr( 0x5c ), 64 );
173
+ $hmac = pack( 'H*', sha1( ( $key ^ $opad ) . pack( 'H*', sha1( ( $key ^ $ipad ) . $data ) ) ) );
174
+
175
+ return base64_encode( $hmac );
176
+ }
177
 
178
  /**
179
  * Retrieve an API key from the IPCheck server
192
  return new WP_Error( 'itsec-network-brute-force-utilities-get-api-key-bad-email', sprintf( __( 'The supplied email address (%s) is invalid. A valid email address is required in order to sign up for the Network Bruteforce Protection by iThemes.', 'better-wp-security' ), $email ) );
193
  }
194
 
195
+ $response = self::call_api( 'request-key', array(
196
+ 'email' => $email,
197
+ 'optin' => $optin,
198
+ ) );
 
 
 
 
 
199
 
200
  if ( is_wp_error( $response ) ) {
201
  return $response;
207
 
208
  $body = json_decode( $response['body'], true );
209
 
210
+ if ( ! is_array( $body ) ) {
211
  return new WP_Error( 'itsec-network-brute-force-utilities-get-api-key-bad-response', __( 'An unknown error prevented the API key request from succeeding. The request for an API key returned an unrecognized response. Please wait a few minutes and try again.', 'better-wp-security' ) );
212
  }
213
 
214
+ if ( isset( $body['error']['message'] ) ) {
215
+ return new WP_Error(
216
+ 'itsec-network-brute-force-utilities-get-api-key-' . ( isset( $body['error']['type'] ) ? $body['error']['type'] : 'unknown' ),
217
+ sprintf( __( 'There was an error returned from the Network Brute Force Protection API: %1$s', 'better-wp-security' ), $body['error']['message'] )
218
+ );
219
+ }
220
+
221
  $key = trim( sanitize_text_field( $body['apikey'] ) );
222
 
223
  if ( empty( $key ) ) {
243
  return new WP_Error( 'itsec-network-brute-force-utilities-activate-api-key-empty-key', __( 'An unknown error prevented the API key secret request from succeeding. The request for an API key submitted an empty key. Please wait a few minutes and try again.', 'better-wp-security' ) );
244
  }
245
 
246
+ $response = self::call_api( 'activate-key', array(
 
247
  'apikey' => $api_key,
248
  'site' => home_url( '', 'http' ),
249
+ ) );
 
 
 
 
250
 
251
  if ( is_wp_error( $response ) ) {
252
  return $response;
263
  if ( ! empty( $body['error'] ) && ! empty( $body['error']['message'] ) ) {
264
  return new WP_Error( 'itsec-network-brute-force-utilities-activate-api-key-error-response', sprintf( __( 'There was an error returned from the Network Brute Force Protection API: %1$s', 'better-wp-security' ), $body['error']['message'] ) );
265
  }
266
+
267
  return new WP_Error( 'itsec-network-brute-force-utilities-activate-api-key-bad-response', __( 'An unknown error prevented the API key secret request from succeeding. The request for an API key secret returned an unrecognized response. Please wait a few minutes and try again.', 'better-wp-security' ) );
268
  }
269
 
275
 
276
  return $secret;
277
  }
278
+
279
+ private static function call_api( $action, $query = array(), $args = array() ) {
280
+
281
+ $url = self::$network_endpoint;
282
+ $url = add_query_arg( 'action', $action, $url );
283
+
284
+ if ( $query ) {
285
+ $url = add_query_arg( $query, $url );
286
+ }
287
+
288
+ $url = apply_filters( 'itsec_ipcheck_api_request_url', $url, $action, $query, $args );
289
+ $args = apply_filters( 'itsec_ipcheck_api_request_args', $args, $url, $action, $query );
290
+
291
+ return wp_remote_request( $url, $args );
292
+ }
293
  }
core/modules/malware/class-itsec-malware-scan-results-template.php CHANGED
@@ -3,6 +3,10 @@
3
  class ITSEC_Malware_Scan_Results_Template {
4
  public static function get_html( $results, $show_error_details = false ) {
5
 
 
 
 
 
6
  if ( is_wp_error( $results ) && ( $edata = $results->get_error_data() ) && ! empty( $edata['itsec_site'] ) ) {
7
  $site_url = $edata['itsec_site']['url'];
8
  } elseif ( is_array( $results ) && ! empty( $results['itsec_site'] ) ) {
3
  class ITSEC_Malware_Scan_Results_Template {
4
  public static function get_html( $results, $show_error_details = false ) {
5
 
6
+ if ( ( $html = apply_filters( 'itsec_pre_malware_scanner_get_html', null, $results, $show_error_details ) ) !== null ) {
7
+ return $html;
8
+ }
9
+
10
  if ( is_wp_error( $results ) && ( $edata = $results->get_error_data() ) && ! empty( $edata['itsec_site'] ) ) {
11
  $site_url = $edata['itsec_site']['url'];
12
  } elseif ( is_array( $results ) && ! empty( $results['itsec_site'] ) ) {
core/modules/malware/class-itsec-malware-scanner.php CHANGED
@@ -4,6 +4,11 @@ final class ITSEC_Malware_Scanner {
4
  protected static $transient_name = 'itsec_cached_sucuri_scan';
5
 
6
  public static function scan( $site_id = 0 ) {
 
 
 
 
 
7
  $process_id = ITSEC_Log::add_process_start( 'malware', 'scan', compact( 'site_id' ) );
8
 
9
  if ( $site_id && ! is_main_site( $site_id ) ) {
4
  protected static $transient_name = 'itsec_cached_sucuri_scan';
5
 
6
  public static function scan( $site_id = 0 ) {
7
+
8
+ if ( ( $results = apply_filters( 'itsec_pre_malware_scanner_scan', null, $site_id ) ) !== null ) {
9
+ return $results;
10
+ }
11
+
12
  $process_id = ITSEC_Log::add_process_start( 'malware', 'scan', compact( 'site_id' ) );
13
 
14
  if ( $site_id && ! is_main_site( $site_id ) ) {
core/modules/malware/class-itsec-malware.php CHANGED
@@ -5,6 +5,7 @@ class ITSEC_Malware {
5
  function run() {
6
  add_action( 'ithemes_sync_register_verbs', array( $this, 'register_sync_verbs' ) );
7
  add_filter( 'itsec-filter-itsec-get-everything-verbs', array( $this, 'register_sync_get_everything_verbs' ) );
 
8
  }
9
 
10
  /**
@@ -24,7 +25,7 @@ class ITSEC_Malware {
24
  *
25
  * @since 3.6.0
26
  *
27
- * @param array Array of verbs.
28
  *
29
  * @return array Array of verbs.
30
  */
@@ -34,4 +35,8 @@ class ITSEC_Malware {
34
  return $verbs;
35
  }
36
 
 
 
 
 
37
  }
5
  function run() {
6
  add_action( 'ithemes_sync_register_verbs', array( $this, 'register_sync_verbs' ) );
7
  add_filter( 'itsec-filter-itsec-get-everything-verbs', array( $this, 'register_sync_get_everything_verbs' ) );
8
+ add_action( 'admin_enqueue_scripts', array( $this, 'register_scripts' ) );
9
  }
10
 
11
  /**
25
  *
26
  * @since 3.6.0
27
  *
28
+ * @param array Array of verbs.
29
  *
30
  * @return array Array of verbs.
31
  */
35
  return $verbs;
36
  }
37
 
38
+ public function register_scripts() {
39
+ wp_register_script( 'itsec-malware-scan', plugin_dir_url( __FILE__ ) . 'js/malware.js', array( 'jquery' ), 3 );
40
+ }
41
+
42
  }
core/modules/malware/js/malware.js CHANGED
@@ -5,18 +5,19 @@
5
  init: function() {
6
  this.bindEvents();
7
  },
8
-
9
  bindEvents: function() {
10
  $('#itsec-malware-scan').on('click', this.startScan);
11
  $(document).on('click', '.itsec-malware-scan-results .itsec-malware-scan-toggle-details', this.toggleDetails);
 
12
  },
13
-
14
  toggleDetails: function( event ) {
15
  event.preventDefault();
16
-
17
  var $container = $(this).parents('.itsec-malware-scan-results-section');
18
  var $details = $container.find('.itsec-malware-scan-details');
19
-
20
  if ( $details.is(':visible') ) {
21
  $(this).html('Show Details');
22
  $details.hide();
@@ -25,21 +26,36 @@
25
  $details.show();
26
  }
27
  },
28
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  startScan: function( event ) {
30
  event.preventDefault();
31
-
32
  itsecMalwareScanData.originalSubmitButtonText = $(this).val();
33
-
34
  $(this)
35
  .prop( 'disabled', true )
36
  .val( itsecMalwareScanData.clickedButtonText );
37
-
38
  var postData = {
39
  action: 'itsec_malware_scan',
40
  _wpnonce: itsecMalwareScanData.nonce
41
  };
42
-
43
  $.ajax( ajaxurl, {
44
  type: 'POST',
45
  data: postData,
@@ -48,10 +64,10 @@
48
  timeout: 0
49
  });
50
  },
51
-
52
  handleSuccessResponse: function( data, status, jqXHR ) {
53
  $('#itsec-malware-scan').hide();
54
-
55
  if ( 'string' !== typeof data ) {
56
  itsecMalware.showError( itsecMalwareScanData.errorMessages.parseError );
57
  } else if ( '-1' === data ) {
@@ -62,32 +78,32 @@
62
  $('.itsec-malware-scan-results-wrapper').html( data );
63
  }
64
  },
65
-
66
  handleErrorResponse: function( jqXHR, status, exception ) {
67
  $('#itsec-malware-scan').hide();
68
-
69
  var message = itsecMalwareScanData.errorMessages.ajaxUnknown;
70
-
71
  if ( 'timeout' === status ) {
72
  message = itsecMalwareScanData.errorMessages.ajaxTimeout;
73
  } else if ( 'parsererror' === status ) {
74
  message = itsecMalwareScanData.errorMessages.parseError;
75
  }
76
-
77
  itsecMalware.showError( message, '(' + status + ') ' + exception )
78
  },
79
-
80
  showError: function( message, replacement ) {
81
  if ( 'string' === typeof replacement ) {
82
  message = message.replace( '%1$s', replacement );
83
  }
84
-
85
  message = '<div class="error inline"><p><strong>' + message + '</strong></p></div>';
86
-
87
  $('.itsec-malware-scan-results-wrapper').html( message );
88
  }
89
  };
90
-
91
  $(document).ready(function() {
92
  itsecMalware.init();
93
  });
5
  init: function() {
6
  this.bindEvents();
7
  },
8
+
9
  bindEvents: function() {
10
  $('#itsec-malware-scan').on('click', this.startScan);
11
  $(document).on('click', '.itsec-malware-scan-results .itsec-malware-scan-toggle-details', this.toggleDetails);
12
+ $(document).on('click', '.itsec-site-scan-toggle-details', this.toggleSiteScanDetails );
13
  },
14
+
15
  toggleDetails: function( event ) {
16
  event.preventDefault();
17
+
18
  var $container = $(this).parents('.itsec-malware-scan-results-section');
19
  var $details = $container.find('.itsec-malware-scan-details');
20
+
21
  if ( $details.is(':visible') ) {
22
  $(this).html('Show Details');
23
  $details.hide();
26
  $details.show();
27
  }
28
  },
29
+
30
+ toggleSiteScanDetails: function( e ) {
31
+ e.preventDefault();
32
+
33
+ var $container = $( this ).parents( '.itsec-site-scan-results-section' );
34
+ var $details = $container.find( '.itsec-site-scan__details' );
35
+
36
+ if ( $details.hasClass( 'hidden' ) ) {
37
+ $( this ).text( 'Hide Details' ).attr( 'aria-expanded', true );
38
+ $details.removeClass( 'hidden' );
39
+ } else {
40
+ $( this ).text( 'Show Details' ).attr( 'aria-expanded', false );
41
+ $details.addClass( 'hidden' );
42
+ }
43
+ },
44
+
45
  startScan: function( event ) {
46
  event.preventDefault();
47
+
48
  itsecMalwareScanData.originalSubmitButtonText = $(this).val();
49
+
50
  $(this)
51
  .prop( 'disabled', true )
52
  .val( itsecMalwareScanData.clickedButtonText );
53
+
54
  var postData = {
55
  action: 'itsec_malware_scan',
56
  _wpnonce: itsecMalwareScanData.nonce
57
  };
58
+
59
  $.ajax( ajaxurl, {
60
  type: 'POST',
61
  data: postData,
64
  timeout: 0
65
  });
66
  },
67
+
68
  handleSuccessResponse: function( data, status, jqXHR ) {
69
  $('#itsec-malware-scan').hide();
70
+
71
  if ( 'string' !== typeof data ) {
72
  itsecMalware.showError( itsecMalwareScanData.errorMessages.parseError );
73
  } else if ( '-1' === data ) {
78
  $('.itsec-malware-scan-results-wrapper').html( data );
79
  }
80
  },
81
+
82
  handleErrorResponse: function( jqXHR, status, exception ) {
83
  $('#itsec-malware-scan').hide();
84
+
85
  var message = itsecMalwareScanData.errorMessages.ajaxUnknown;
86
+
87
  if ( 'timeout' === status ) {
88
  message = itsecMalwareScanData.errorMessages.ajaxTimeout;
89
  } else if ( 'parsererror' === status ) {
90
  message = itsecMalwareScanData.errorMessages.parseError;
91
  }
92
+
93
  itsecMalware.showError( message, '(' + status + ') ' + exception )
94
  },
95
+
96
  showError: function( message, replacement ) {
97
  if ( 'string' === typeof replacement ) {
98
  message = message.replace( '%1$s', replacement );
99
  }
100
+
101
  message = '<div class="error inline"><p><strong>' + message + '</strong></p></div>';
102
+
103
  $('.itsec-malware-scan-results-wrapper').html( message );
104
  }
105
  };
106
+
107
  $(document).ready(function() {
108
  itsecMalware.init();
109
  });
core/modules/malware/js/settings-page.js CHANGED
@@ -9,6 +9,7 @@
9
  bindEvents: function() {
10
  $( document ).on( 'click', '#itsec-malware-scan-start', this.startScan );
11
  $( document ).on( 'click', '.itsec-malware-scan-results-wrapper .itsec-malware-scan-toggle-details', this.toggleDetails );
 
12
  },
13
 
14
  toggleDetails: function( e ) {
@@ -26,6 +27,21 @@
26
  }
27
  },
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  startScan: function( e ) {
30
  e.preventDefault();
31
 
9
  bindEvents: function() {
10
  $( document ).on( 'click', '#itsec-malware-scan-start', this.startScan );
11
  $( document ).on( 'click', '.itsec-malware-scan-results-wrapper .itsec-malware-scan-toggle-details', this.toggleDetails );
12
+ $( document ).on('click', '.itsec-site-scan-toggle-details', this.toggleSiteScanDetails );
13
  },
14
 
15
  toggleDetails: function( e ) {
27
  }
28
  },
29
 
30
+ toggleSiteScanDetails: function(e) {
31
+ e.preventDefault();
32
+
33
+ var $container = $( this ).parents( '.itsec-site-scan-results-section' );
34
+ var $details = $container.find( '.itsec-site-scan__details' );
35
+
36
+ if ( $details.is( ':visible' ) ) {
37
+ $( this ).text( 'Show Details' ).attr( 'aria-expanded', false );
38
+ $details.hide();
39
+ } else {
40
+ $( this ).text( 'Hide Details' ).attr( 'aria-expanded', true );
41
+ $details.show();
42
+ }
43
+ },
44
+
45
  startScan: function( e ) {
46
  e.preventDefault();
47
 
core/modules/malware/logs.php CHANGED
@@ -60,7 +60,7 @@ final class ITSEC_Malware_Logs {
60
  }
61
 
62
  public function enqueue() {
63
- wp_enqueue_script( 'itsec-malware-scan-logs', plugin_dir_url( __FILE__ ) . 'js/malware.js', array( 'jquery' ), 3 );
64
  }
65
  }
66
  new ITSEC_Malware_Logs();
60
  }
61
 
62
  public function enqueue() {
63
+ wp_enqueue_script( 'itsec-malware-scan' );
64
  }
65
  }
66
  new ITSEC_Malware_Logs();
core/modules/notification-center/class-notification-center.php CHANGED
@@ -564,6 +564,7 @@ final class ITSEC_Notification_Center {
564
  public function run() {
565
  add_action( 'itsec_change_admin_user_id', array( $this, 'update_notification_user_id_on_admin_change' ) );
566
  add_action( 'itsec_module_settings_after_title', array( $this, 'display_notification_center_link_for_module' ) );
 
567
  $this->setup_scheduling();
568
  }
569
 
@@ -646,6 +647,16 @@ final class ITSEC_Notification_Center {
646
  }
647
  }
648
 
 
 
 
 
 
 
 
 
 
 
649
  /**
650
  * Setup scheduling actions.
651
  */
564
  public function run() {
565
  add_action( 'itsec_change_admin_user_id', array( $this, 'update_notification_user_id_on_admin_change' ) );
566
  add_action( 'itsec_module_settings_after_title', array( $this, 'display_notification_center_link_for_module' ) );
567
+ add_action( 'itsec_register_highlighted_logs', array( $this, 'register_highlighted_log' ) );
568
  $this->setup_scheduling();
569
  }
570
 
647
  }
648
  }
649
 
650
+ /**
651
+ * Register a highlighted log for mail send errors.
652
+ */
653
+ public function register_highlighted_log() {
654
+ ITSEC_Lib_Highlighted_Logs::register_dynamic_highlight( 'notification-center-send-failed', array(
655
+ 'module' => 'notification_center',
656
+ 'code' => 'send_failed::%'
657
+ ) );
658
+ }
659
+
660
  /**
661
  * Setup scheduling actions.
662
  */
core/modules/notification-center/logs.php CHANGED
@@ -5,6 +5,8 @@ class ITSEC_Notification_Center_Logs {
5
  public function __construct() {
6
  add_filter( 'itsec_logs_prepare_notification_center_entry_for_list_display', array( $this, 'filter_entry_for_list_display' ), 10, 3 );
7
  add_filter( 'itsec_logs_prepare_notification_center_entry_for_details_display', array( $this, 'filter_entry_for_details_display' ), 10, 4 );
 
 
8
  }
9
 
10
  public function filter_entry_for_list_display( $entry, $code, $data ) {
@@ -89,6 +91,31 @@ class ITSEC_Notification_Center_Logs {
89
 
90
  return $details;
91
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  }
93
 
94
- new ITSEC_Notification_Center_Logs();
5
  public function __construct() {
6
  add_filter( 'itsec_logs_prepare_notification_center_entry_for_list_display', array( $this, 'filter_entry_for_list_display' ), 10, 3 );
7
  add_filter( 'itsec_logs_prepare_notification_center_entry_for_details_display', array( $this, 'filter_entry_for_details_display' ), 10, 4 );
8
+ add_filter( 'itsec_highlighted_log_notification-center-send-failed_notice_title', array( $this, 'filter_highlight_title' ), 10, 2 );
9
+ add_filter( 'itsec_highlighted_log_notification-center-send-failed_notice_message', array( $this, 'filter_highlight_message' ), 10, 2 );
10
  }
11
 
12
  public function filter_entry_for_list_display( $entry, $code, $data ) {
91
 
92
  return $details;
93
  }
94
+
95
+ public function filter_highlight_title( $title, $entry ) {
96
+ if ( false === strpos( $entry['code'], '::' ) ) {
97
+ $data = array();
98
+ } else {
99
+ list( , $data ) = explode( '::', $entry['code'], 2 );
100
+ $data = explode( ',', $data );
101
+ }
102
+
103
+ if ( empty( $data ) ) {
104
+ return esc_html__( 'Failed sending notification.', 'better-wp-security' );
105
+ }
106
+
107
+ list( $notification ) = $data;
108
+
109
+ if ( $strings = ITSEC_Core::get_notification_center()->get_notification_strings( $notification ) ) {
110
+ $notification = $strings['label'];
111
+ }
112
+
113
+ return sprintf( esc_html__( 'Failed sending %s notification.', 'better-wp-security' ), $notification );
114
+ }
115
+
116
+ public function filter_highlight_message( $message, $entry ) {
117
+ return wp_sprintf( '%l', ITSEC_Response::get_error_strings( $entry['data']['error'] ) );
118
+ }
119
  }
120
 
121
+ new ITSEC_Notification_Center_Logs();
core/modules/security-check/scanner.php CHANGED
@@ -123,9 +123,9 @@ final class ITSEC_Security_Check_Scanner {
123
  'style_class' => 'regular-text',
124
  ) );
125
  self::$feedback->add_input( 'select', 'updates_optin', array(
126
- 'format' => __( 'Receive email updates about WordPress Security from iThemes: %1$s', 'better-wp-security' ),
127
  'options' => array( 'true' => __( 'Yes', 'better-wp-security' ), 'false' => __( 'No', 'better-wp-security' ) ),
128
- 'value' => 'true',
129
  ) );
130
  self::$feedback->add_input( 'hidden', 'method', array(
131
  'value' => 'activate-network-brute-force',
123
  'style_class' => 'regular-text',
124
  ) );
125
  self::$feedback->add_input( 'select', 'updates_optin', array(
126
+ 'format' => __( 'Receive email updates about WordPress Security and marketing news from iThemes: %1$s', 'better-wp-security' ),
127
  'options' => array( 'true' => __( 'Yes', 'better-wp-security' ), 'false' => __( 'No', 'better-wp-security' ) ),
128
+ 'value' => 'false',
129
  ) );
130
  self::$feedback->add_input( 'hidden', 'method', array(
131
  'value' => 'activate-network-brute-force',
core/modules/system-tweaks/config-generators.php CHANGED
@@ -112,7 +112,7 @@ final class ITSEC_System_Tweaks_Config_Generators {
112
  if ( $input['request_methods'] ) {
113
  $rewrites .= "\n";
114
  $rewrites .= "\t\t# " . __( 'Filter Request Methods - Security > Settings > System Tweaks > Request Methods', 'better-wp-security' ) . "\n";
115
- $rewrites .= "\t\tRewriteCond %{REQUEST_METHOD} ^(TRACE|DELETE|TRACK) [NC]\n";
116
  $rewrites .= "\t\tRewriteRule ^.* - [F]\n";
117
  }
118
 
@@ -240,7 +240,7 @@ final class ITSEC_System_Tweaks_Config_Generators {
240
  if ( $input['request_methods'] ) {
241
  $modification .= "\n";
242
  $modification .= "\t# " . __( 'Filter Request Methods - Security > Settings > System Tweaks > Request Methods', 'better-wp-security' ) . "\n";
243
- $modification .= "\tif ( \$request_method ~* ^(TRACE|DELETE|TRACK)$ ) { return 403; }\n";
244
  }
245
 
246
  // Process suspicious query rules
112
  if ( $input['request_methods'] ) {
113
  $rewrites .= "\n";
114
  $rewrites .= "\t\t# " . __( 'Filter Request Methods - Security > Settings > System Tweaks > Request Methods', 'better-wp-security' ) . "\n";
115
+ $rewrites .= "\t\tRewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK) [NC]\n";
116
  $rewrites .= "\t\tRewriteRule ^.* - [F]\n";
117
  }
118
 
240
  if ( $input['request_methods'] ) {
241
  $modification .= "\n";
242
  $modification .= "\t# " . __( 'Filter Request Methods - Security > Settings > System Tweaks > Request Methods', 'better-wp-security' ) . "\n";
243
+ $modification .= "\tif ( \$request_method ~* ^(TRACE|TRACK)$ ) { return 403; }\n";
244
  }
245
 
246
  // Process suspicious query rules
core/modules/system-tweaks/settings-page.php CHANGED
@@ -45,7 +45,7 @@ final class ITSEC_System_Tweaks_Settings_Page extends ITSEC_Module_Settings_Page
45
  <td>
46
  <?php $form->add_checkbox( 'request_methods' ); ?>
47
  <label for="itsec-system-tweaks-request_methods"><?php esc_html_e( 'Filter Request Methods', 'better-wp-security' ); ?></label>
48
- <p class="description"><?php printf( wp_kses( __( 'Filter out hits with the trace, delete, or track request methods. This should not be enabled if you use the <a href="%s">WordPress REST API</a>.', 'better-wp-security' ), array( 'a' => array( 'href' => array() ) ) ), esc_url( 'https://wordpress.org/plugins/rest-api/' ) ); ?></p>
49
  </td>
50
  </tr>
51
  <tr>
45
  <td>
46
  <?php $form->add_checkbox( 'request_methods' ); ?>
47
  <label for="itsec-system-tweaks-request_methods"><?php esc_html_e( 'Filter Request Methods', 'better-wp-security' ); ?></label>
48
+ <p class="description"><?php esc_html_e( 'Filter out hits with the trace or track request methods.', 'better-wp-security' ); ?></p>
49
  </td>
50
  </tr>
51
  <tr>
core/package.json ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ithemes-security",
3
+ "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.",
4
+ "version": "5.6.0",
5
+ "author": "iThemes",
6
+ "browserslist": [
7
+ "extends @wordpress/browserslist-config"
8
+ ],
9
+ "dependencies": {
10
+ "@wordpress/a11y": "*",
11
+ "@wordpress/api-fetch": "*",
12
+ "@wordpress/autop": "^2.2.0",
13
+ "@wordpress/components": "^7.0.8",
14
+ "@wordpress/compose": "^3.0.1",
15
+ "@wordpress/data": "^4.2.1",
16
+ "@wordpress/date": "^3.0.1",
17
+ "@wordpress/dom-ready": "*",
18
+ "@wordpress/element": "*",
19
+ "@wordpress/hooks": "*",
20
+ "@wordpress/html-entities": "*",
21
+ "@wordpress/i18n": "*",
22
+ "@wordpress/is-shallow-equal": "*",
23
+ "@wordpress/keycodes": "^2.0.6",
24
+ "@wordpress/notices": "*",
25
+ "@wordpress/plugins": "^2.2.0",
26
+ "@wordpress/redux-routine": "*",
27
+ "@wordpress/rich-text": "^3.0.7",
28
+ "@wordpress/url": "*",
29
+ "@wordpress/viewport": "*",
30
+ "classnames": "^2.2.6",
31
+ "contrast": "^1.0.1",
32
+ "li": "^1.3.0",
33
+ "lodash": "^4.17.11",
34
+ "memize": "^1.0.5",
35
+ "react": "^16.6.3",
36
+ "react-error-boundary": "^1.2.3",
37
+ "react-grid-layout": "^0.16.6",
38
+ "react-select": "^2.4.1",
39
+ "react-transition-group": "^2.0.0",
40
+ "recharts": "^1.5.0",
41
+ "rememo": "^3.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@babel/core": "^7.3.3",
45
+ "@babel/plugin-proposal-class-properties": "^7.3.3",
46
+ "@babel/plugin-syntax-dynamic-import": "^7.2.0",
47
+ "@babel/plugin-transform-react-jsx": "^7.3.0",
48
+ "@babel/runtime-corejs2": "^7.3.1",
49
+ "@wordpress/babel-plugin-import-jsx-pragma": "^1.1.3",
50
+ "@wordpress/babel-preset-default": "^3.0.2",
51
+ "@wordpress/browserslist-config": "^2.2.3",
52
+ "@wordpress/custom-templated-path-webpack-plugin": "^1.2.0",
53
+ "@wordpress/jest-preset-default": "^3.0.0",
54
+ "@wordpress/scripts": "^2.4.4",
55
+ "acorn": "^6.0.5",
56
+ "autoprefixer": "^9.4.7",
57
+ "babel-loader": "^8.0.5",
58
+ "clean-webpack-plugin": "^2.0.2",
59
+ "concurrently": "^4.1.0",
60
+ "css-loader": "^2.1.0",
61
+ "escape-string-regexp": "^2.0.0",
62
+ "eslint-config-wordpress": "2.0.0",
63
+ "eslint-plugin-jest": "^22.3.0",
64
+ "eslint-plugin-jsx-a11y": "^6.2.1",
65
+ "eslint-plugin-react": "^7.12.4",
66
+ "eslint-plugin-wordpress": "git://github.com/WordPress-Coding-Standards/eslint-plugin-wordpress.git#1774343f6226052a46b081e01db3fca8793cc9f1",
67
+ "glob": "^7.1.3",
68
+ "husky": "^1.3.1",
69
+ "jest": "^24.1.0",
70
+ "loader-utils": "^1.2.3",
71
+ "mini-css-extract-plugin": "^0.5.0",
72
+ "node-sass": "^4.12.0",
73
+ "postcss-loader": "^3.0.0",
74
+ "raw-loader": "^1.0.0",
75
+ "readdirp": "^2.2.1",
76
+ "rimraf": "^2.6.3",
77
+ "sass-loader": "^7.1.0",
78
+ "style-loader": "^0.23.1",
79
+ "svg-react-loader": "github:woutervanvliet/svg-react-loader",
80
+ "webpack": "^4.29.5",
81
+ "webpack-cli": "^3.2.3",
82
+ "webpack-filter-warnings-plugin": "^1.2.1",
83
+ "webpack-manifest-plugin": "^2.0.4",
84
+ "webpack-sources": "latest",
85
+ "webpack-webstorm-debugger-script": "^1.0.1"
86
+ },
87
+ "directories": {},
88
+ "homepage": "https://ithemes.com/security",
89
+ "husky": {
90
+ "hooks": {
91
+ "pre-commit": "node bin/pre-commit"
92
+ }
93
+ },
94
+ "license": "GPL-2.0-or-later",
95
+ "private": true,
96
+ "repository": {
97
+ "type": "git",
98
+ "url": "git+ssh://git@bitbucket.org/ithemes/ithemes-security-pro.git"
99
+ },
100
+ "scripts": {
101
+ "build": "NODE_ENV=production ./node_modules/.bin/webpack",
102
+ "clean": "node ./bin/clean.js ",
103
+ "dev": "./node_modules/.bin/webpack",
104
+ "lint": "concurrently \"npm run lint-js\"",
105
+ "lint-js": "wp-scripts lint-js .",
106
+ "lint-js:fix": "wp-scripts lint-js . --fix",
107
+ "test": "concurrently \"npm run lint-js && npm run test-unit\"",
108
+ "test-unit": "wp-scripts test-unit-js --config tests/js/unit/jest.config.json",
109
+ "test-unit:coverage": "npm run test-unit -- --coverage",
110
+ "test-unit:update": "npm run test-unit -- --updateSnapshot",
111
+ "test-unit:watch": "npm run test-unit -- --watch",
112
+ "watch": "./node_modules/.bin/webpack --watch"
113
+ }
114
+ }
core/packages/components/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/components/src/async-select/index.js ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { lazy, Suspense } from 'react';
5
+ import ErrorBoundary from 'react-error-boundary';
6
+
7
+ /**
8
+ * WordPress dependencies
9
+ */
10
+ import { Spinner } from '@wordpress/components';
11
+ import { __ } from '@wordpress/i18n';
12
+
13
+ /**
14
+ * Async dependencies
15
+ */
16
+ const Select = lazy( () => import( 'react-select/lib/Async' ) );
17
+
18
+ function LoadError() {
19
+ return ( <span>{ __( 'Error when loading. Please refresh.', 'better-wp-security' ) }</span> );
20
+ }
21
+
22
+ export default function AsyncSelect( { addErrorBoundary = true, ...rest } ) {
23
+ const s = (
24
+ <Suspense fallback={ <Spinner /> }>
25
+ <Select { ...rest } />
26
+ </Suspense>
27
+ );
28
+
29
+ return ( addErrorBoundary ? <ErrorBoundary FallbackComponent={ LoadError }>{ s }</ErrorBoundary> : s );
30
+ }
core/packages/components/src/async-select/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/components/src/close-button/index.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { IconButton } from '@wordpress/components';
5
+ import { __ } from '@wordpress/i18n';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import './style.scss';
11
+
12
+ export default function CloseButton( { close } ) {
13
+ return (
14
+ <IconButton className="itsec-close-button" icon="no-alt" onClick={ ( e ) => {
15
+ e.preventDefault();
16
+ close();
17
+ } } tooltip={ false } label={ __( 'Close', 'better-wp-security' ) } />
18
+ );
19
+ }
core/packages/components/src/close-button/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/components/src/close-button/style.scss ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .itsec-close-button.components-icon-button {
2
+ padding: 0;
3
+ height: 20px;
4
+ position: absolute;
5
+ right: 1em;
6
+ top: 1em;
7
+
8
+ &:hover {
9
+ opacity: .5;
10
+ box-shadow: none !important;
11
+ }
12
+
13
+ .components-popover.is-mobile & {
14
+ display: none;
15
+ }
16
+ }
core/packages/components/src/hover-detector/index.js ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { Component } from '@wordpress/element';
5
+
6
+ const noop = () => {};
7
+
8
+ export default class extends Component {
9
+ constructor( props ) {
10
+ super( props );
11
+
12
+ this.state = {
13
+ isHovering: false,
14
+ };
15
+
16
+ this.onMouseEnter = this.onMouseEnter.bind( this );
17
+ this.onMouseLeave = this.onMouseLeave.bind( this );
18
+ this.onMouseOver = this.onMouseOver.bind( this );
19
+ this.onMouseOut = this.onMouseOut.bind( this );
20
+ this.setIsHovering = this.setIsHovering.bind( this );
21
+ this.unsetIsHovering = this.unsetIsHovering.bind( this );
22
+ this.componentWillUnmount = this.componentWillUnmount.bind( this );
23
+
24
+ this.timerIds = [];
25
+ }
26
+
27
+ static displayName = 'HoverDetector';
28
+
29
+ static defaultProps = {
30
+ hoverDelayInMs: 0,
31
+ hoverOffDelayInMs: 0,
32
+ onHoverChanged: noop,
33
+ onMouseEnter: ( { setIsHovering } ) => setIsHovering(),
34
+ onMouseLeave: ( { unsetIsHovering } ) => unsetIsHovering(),
35
+ onMouseOver: noop,
36
+ onMouseOut: noop,
37
+ shouldDecorateChildren: true,
38
+ };
39
+
40
+ onMouseEnter( e ) {
41
+ this.props.onMouseEnter( {
42
+ e,
43
+ setIsHovering: this.setIsHovering,
44
+ unsetIsHovering: this.unsetIsHovering,
45
+ } );
46
+ }
47
+
48
+ onMouseLeave( e ) {
49
+ this.props.onMouseLeave( {
50
+ e,
51
+ setIsHovering: this.setIsHovering,
52
+ unsetIsHovering: this.unsetIsHovering,
53
+ } );
54
+ }
55
+
56
+ onMouseOver( e ) {
57
+ this.props.onMouseOver( {
58
+ e,
59
+ setIsHovering: this.setIsHovering,
60
+ unsetIsHovering: this.unsetIsHovering,
61
+ } );
62
+ }
63
+
64
+ onMouseOut( e ) {
65
+ this.props.onMouseOut( {
66
+ e,
67
+ setIsHovering: this.setIsHovering,
68
+ unsetIsHovering: this.unsetIsHovering,
69
+ } );
70
+ }
71
+
72
+ componentWillUnmount() {
73
+ this.clearTimers();
74
+ }
75
+
76
+ setIsHovering() {
77
+ this.clearTimers();
78
+
79
+ const hoverScheduleId = setTimeout( () => {
80
+ const newState = { isHovering: true };
81
+ this.setState( newState, () => {
82
+ this.props.onHoverChanged( newState );
83
+ } );
84
+ }, this.props.hoverDelayInMs );
85
+
86
+ this.timerIds.push( hoverScheduleId );
87
+ }
88
+
89
+ unsetIsHovering() {
90
+ this.clearTimers();
91
+
92
+ const hoverOffScheduleId = setTimeout( () => {
93
+ const newState = { isHovering: false };
94
+ this.setState( newState, () => {
95
+ this.props.onHoverChanged( newState );
96
+ } );
97
+ }, this.props.hoverOffDelayInMs );
98
+
99
+ this.timerIds.push( hoverOffScheduleId );
100
+ }
101
+
102
+ clearTimers() {
103
+ const ids = this.timerIds;
104
+ while ( ids.length ) {
105
+ clearTimeout( ids.pop() );
106
+ }
107
+ }
108
+
109
+ render() {
110
+ const { children, className } = this.props;
111
+
112
+ return (
113
+ <div { ...{
114
+ className,
115
+ onMouseEnter: this.onMouseEnter,
116
+ onMouseLeave: this.onMouseLeave,
117
+ onMouseOver: this.onMouseOver,
118
+ onMouseOut: this.onMouseOut,
119
+ } }>
120
+ { children }
121
+ </div>
122
+ );
123
+ }
124
+ }
core/packages/components/src/hover-detector/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/components/src/index.js ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ export { default as LogModal } from './log-modal';
2
+ export { default as MalwareScanResults } from './malware-scan-results';
3
+ export { default as SiteScanResults } from './site-scan-results';
4
+ export { default as PrintR } from './print-r';
5
+ export { default as AsyncSelect } from './async-select';
6
+ export { default as HoverDetector } from './hover-detector';
7
+ export { default as CloseButton } from './close-button';
8
+ export { default as Loader } from './loader';
core/packages/components/src/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/components/src/loader/icon.svg ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="200px" viewBox="0 0 580.58 684.67">
2
+ <defs>
3
+ <style>
4
+ .path{ stroke-dasharray: 2500; stroke-dashoffset: 2500; animation: itsec-loader__dash-animation 3000ms linear infinite; fill:none; stroke:#1072ba; stroke-miterlimit:10; stroke-width:15px; }
5
+ .path-outer { animation: itsec-loader__dash-animation 3000ms linear infinite }
6
+ </style>
7
+ </defs>
8
+ <title>itsec-loader</title>
9
+ <g>
10
+ <g>
11
+ <path class="path"
12
+ d="M522.48,126.08a4.71,4.71,0,0,0-3.32-4.33C471.9,109.4,391,93.14,292.66,93.14h-3.91A941.87,941.87,0,0,0,61.45,122a4.7,4.7,0,0,0-3.34,4.32l0,137.93c-.08,1.41-3.69,139.49,87.31,244.83,61.84,71.63,114.77,102.51,141.77,114.83a8.69,8.69,0,0,0,6.29,0c27-12.28,79.87-43.22,141.76-114.84,90.92-105.34,87.32-243.42,87.3-244.59Z"/>
13
+ </g>
14
+ <g>
15
+ <path class="path path-outer"
16
+ d="M573,271.88V47.17a4.82,4.82,0,0,0-3.08-4.31c-12.19-4-59.81-18.37-128.6-22.33a3,3,0,0,0-3.25,3.08v39.8a2.7,2.7,0,0,1-3.22,2.75c-22.38-3.45-47-6.37-73.42-8.33a3.55,3.55,0,0,1-3.25-3.48V15.74A3.82,3.82,0,0,0,355,12.06c-9.59-1.19-39-4.56-66.6-4.56-24,0-52.92,3.84-62.45,5.21a3.87,3.87,0,0,0-3.22,3.74l0,38.09a3.56,3.56,0,0,1-3.25,3.5c-26.44,2-51,5-73.38,8.39a2.69,2.69,0,0,1-3.21-2.75V23.61a3,3,0,0,0-3.25-3.08c-68.84,4-116.69,18.33-128.92,22.34a4.8,4.8,0,0,0-3.07,4.3V271.88S1.17,423,103.87,541.9C194.8,647.22,270.84,672.42,287,676.81a14.24,14.24,0,0,0,6.33,0c16.2-4.39,92.25-29.57,183.14-134.92C579.2,423,573,271.88,573,271.88Z"/>
17
+ </g>
18
+ </g>
19
+ </svg>
core/packages/components/src/loader/index.js ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Internal Dependencies
3
+ */
4
+ import Logo from './logo.svg';
5
+ import './style.scss';
6
+
7
+ export default function Loader() {
8
+ return (
9
+ <div className="itsec-loader">
10
+ <Logo />
11
+ </div>
12
+ );
13
+ }
core/packages/components/src/loader/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/components/src/loader/logo.svg ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="200px" viewBox="0 0 203.31 240.72">
2
+ <defs>
3
+ <style>.path{fill:#1072ba;}.path{fill:#69c;}</style>
4
+ </defs>
5
+ <g>
6
+ <g>
7
+ <path class="path"
8
+ d="M203.28,95V14.26a1.73,1.73,0,0,0-1.11-1.55c-4.38-1.44-21.5-6.6-46.22-8a1.1,1.1,0,0,0-1.17,1.11V20.1a1,1,0,0,1-1.16,1c-8-1.24-16.88-2.29-26.39-3a1.27,1.27,0,0,1-1.17-1.25V3a1.37,1.37,0,0,0-1.16-1.32A211.61,211.61,0,0,0,101,0,178.14,178.14,0,0,0,78.51,1.87a1.4,1.4,0,0,0-1.16,1.35V16.91a1.28,1.28,0,0,1-1.16,1.26c-9.51.72-18.34,1.78-26.38,3a1,1,0,0,1-1.16-1V5.79a1.1,1.1,0,0,0-1.17-1.11c-24.74,1.43-41.94,6.59-46.34,8A1.73,1.73,0,0,0,0,14.26V95s-2.3,54.32,34.61,97.07c32.69,37.86,60,46.91,65.84,48.5a5.16,5.16,0,0,0,2.28,0c5.82-1.58,33.16-10.63,65.83-48.5C205.51,149.35,203.28,95,203.28,95Z"/>
9
+ <path class="path" d="M101.38,68.5a21.75,21.75,0,1,0,21.7,21.81A21.77,21.77,0,0,0,101.38,68.5Z"/>
10
+ <path class="path"
11
+ d="M182.44,87V42.75A1.92,1.92,0,0,0,181,41a385.2,385.2,0,0,0-78.26-8.05h-1.66a398.43,398.43,0,0,0-78.71,8.21,1.9,1.9,0,0,0-1.45,1.78l0,44.2v.24c0,3.27.09,54.82,33.38,93.35,18.71,21.67,35,33.81,46.15,40.44a2.79,2.79,0,0,0,2.56,0c11.14-6.61,27.36-18.74,46.12-40.45C183.22,141.12,182.46,87.63,182.44,87Zm-50.57,79.1c-2.69,3.13-5.32,6-7.87,8.69-.59.61-1.07.41-1.07-.44V155.78c0-14.05,13.64-21.19,18.14-23.17a2.49,2.49,0,0,0,1.42-2.11V116.39a1,1,0,0,0-1.41-.94c-37.93,15.49-72.25,3-79.51,0a.93.93,0,0,0-1.41.91V130.5a2.37,2.37,0,0,0,1.44,2.06c14.94,5.72,18,15.46,18,23.4v18.25c0,.85-.48,1-1.06.43-2.51-2.63-5.09-5.46-7.76-8.53-28.11-32.58-27.95-78.24-27.93-78.7V61.47a1.86,1.86,0,0,1,1.51-1.77,374.15,374.15,0,0,1,56.51-4.51h1.69a364.08,364.08,0,0,1,55.75,4.32,1.86,1.86,0,0,1,1.52,1.78v25.3C159.83,87.44,160.12,133.44,131.87,166.11Z"/>
12
+ </g>
13
+ </g>
14
+ </svg>
core/packages/components/src/loader/style.scss ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .itsec-loader {
2
+ margin: 0 auto;
3
+ display: flex;
4
+ align-items: center;
5
+ justify-content: center;
6
+ height: 100%;
7
+
8
+ & svg {
9
+ animation: itsec-loader__grow 3.0s infinite ease-in;
10
+ }
11
+ }
12
+
13
+ @keyframes itsec-loader__grow {
14
+ 0% {
15
+ transform: scale(0);
16
+ }
17
+ 100% {
18
+ transform: scale(1.0);
19
+ opacity: 0;
20
+ }
21
+ }
core/packages/components/src/log-modal/index.js ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { RawHTML, Component } from '@wordpress/element';
5
+ import { Modal } from '@wordpress/components';
6
+ import { __ } from '@wordpress/i18n';
7
+
8
+ /**
9
+ * Internal dependencies
10
+ */
11
+ import './style.scss';
12
+
13
+ export default class LogModal extends Component {
14
+ static #html = {};
15
+
16
+ state = {
17
+ html: null,
18
+ };
19
+
20
+ componentDidMount() {
21
+ this.fetchHtml();
22
+ }
23
+
24
+ shouldComponentUpdate( nextProps, nextState ) {
25
+ return this.props.id !== nextProps.id || this.state.html !== nextState.html;
26
+ }
27
+
28
+ componentDidUpdate( prevProps ) {
29
+ if ( prevProps.id !== this.props.id ) {
30
+ this.fetchHtml();
31
+ }
32
+ }
33
+
34
+ fetchHtml = () => {
35
+ if ( LogModal.#html[ this.props.id ] ) {
36
+ this.setState( { html: LogModal.#html[ this.props.id ] } );
37
+ }
38
+
39
+ const form = new FormData();
40
+ form.set( 'id', this.props.id );
41
+ form.set( 'nonce', this.props.nonce );
42
+ form.set( 'action', 'itsec_logs_page' );
43
+
44
+ fetch( this.props.ajaxurl, {
45
+ method: 'POST',
46
+ credentials: 'same-origin',
47
+ body: form,
48
+ } ).then( ( response ) => response.json() ).then( ( response ) => {
49
+ LogModal.#html[ this.props.id ] = response.response;
50
+ this.setState( { html: response.response } );
51
+ } );
52
+ };
53
+
54
+ render() {
55
+ return (
56
+ <Modal
57
+ title={ __( 'Log Details', 'better-wp-security' ) }
58
+ overlayClassName="itsec-log-modal"
59
+ onRequestClose={ this.props.onClose }>
60
+ <div className="itsec-log-modal__content">
61
+ { this.state.html ?
62
+ <RawHTML>{ this.state.html }</RawHTML> :
63
+ <span>{ __( 'Loading', 'better-wp-security' ) }</span> }
64
+ </div>
65
+ </Modal>
66
+ );
67
+ }
68
+ }
core/packages/components/src/log-modal/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/components/src/log-modal/style.scss ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .itsec-log-modal {
2
+ & .components-modal__frame {
3
+ min-width: 80%;
4
+ height: 100%;
5
+ }
6
+
7
+ & .itsec-log-modal__content {
8
+ margin: 2em 1em;
9
+
10
+ & .itsec-log-raw-details-toggle {
11
+ display: none;
12
+ }
13
+
14
+ & tr:first-child th,
15
+ & tr:first-child td {
16
+ margin-top: 0;
17
+ padding-top: 0;
18
+ }
19
+ }
20
+ }
core/packages/components/src/malware-scan-results/blacklist-details.js ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { has, get } from 'lodash';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { __ } from '@wordpress/i18n';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import WrappedSection from './wrapped-section';
15
+
16
+ function BlacklistDetails( { results } ) {
17
+ const status = has( results, [ 'BLACKLIST', 'WARN' ] ) ? 'warn' : 'clean';
18
+
19
+ return (
20
+ <WrappedSection type="malware" status={ status } description={ __( 'Blacklist', 'better-wp-security' ) }>
21
+ <ul>
22
+ { get( results, [ 'BLACKLIST', 'WARN' ], [] ).map( ( entry, i ) => (
23
+ <li className="itsec-malware-scan-warn" key={ i }>
24
+ <span>
25
+ <a href={ entry[ 1 ] }>{ entry[ 0 ] }</a>
26
+ </span>
27
+ </li>
28
+ ) ) }
29
+ { get( results, [ 'BLACKLIST', 'INFO' ], [] ).map( ( entry, i ) => (
30
+ <li className="itsec-malware-scan-clean" key={ i }>
31
+ <span>
32
+ <a href={ entry[ 1 ] }>{ entry[ 0 ] }</a>
33
+ </span>
34
+ </li>
35
+ ) ) }
36
+ </ul>
37
+ </WrappedSection>
38
+ );
39
+ }
40
+
41
+ export default BlacklistDetails;
core/packages/components/src/malware-scan-results/index.js ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { __, sprintf } from '@wordpress/i18n';
5
+ import { Fragment } from '@wordpress/element';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import { castWPError, isWPError } from '@ithemes/security-utils';
11
+ import WPErrorDetails from './wp-error-details';
12
+ import SystemErrorDetails from './system-error-details';
13
+ import MalwareDetails from './malware-details';
14
+ import BlacklistDetails from './blacklist-details';
15
+ import './style.scss';
16
+
17
+ function MalwareScanResults( { results, showErrorDetails = true } ) {
18
+ let siteUrl;
19
+
20
+ if ( isWPError( results ) ) {
21
+ const errorData = castWPError( results ).getErrorData();
22
+ siteUrl = ( errorData && errorData.itsec_site ) ? errorData.itsec_site.url : undefined;
23
+ } else if ( results.itsec_site ) {
24
+ siteUrl = results.itsec_site.url;
25
+ }
26
+
27
+ return (
28
+ <div className="itsec-malware-scan-results">
29
+ { siteUrl && <h4>{ sprintf( __( 'Site: %s', 'better-wp-security' ), siteUrl ) }</h4> }
30
+
31
+ { isWPError( results ) ? <WPErrorDetails results={ results } showErrorDetails={ showErrorDetails } /> : (
32
+ <Fragment>
33
+ <SystemErrorDetails results={ results } />
34
+ <MalwareDetails results={ results } />
35
+ <BlacklistDetails results={ results } />
36
+ </Fragment>
37
+ ) }
38
+ </div>
39
+ );
40
+ }
41
+
42
+ export default MalwareScanResults;
core/packages/components/src/malware-scan-results/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/components/src/malware-scan-results/malware-details.js ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { has } from 'lodash';
5
+ import { decodeEntities } from '@wordpress/html-entities';
6
+
7
+ /**
8
+ * WordPress dependencies
9
+ */
10
+ import { __, sprintf } from '@wordpress/i18n';
11
+ import { Fragment } from '@wordpress/element';
12
+
13
+ /**
14
+ * Internal dependencies
15
+ */
16
+ import WrappedSection from './wrapped-section';
17
+
18
+ function MalwareDetails( { results } ) {
19
+ if ( ! has( results, [ 'MALWARE', 'WARN' ] ) ) {
20
+ return <WrappedSection type="malware" status="clean" description={ __( 'Malware', 'better-wp-security' ) } />;
21
+ }
22
+
23
+ return (
24
+ <WrappedSection type="malware" status="warn" description={ __( 'Malware', 'better-wp-security' ) }>
25
+ <ul>
26
+ { results.MALWARE.WARN.map( ( entry, i ) => {
27
+ let [ message, url ] = entry[ 0 ].split( ':', 2 );
28
+ message = message.trim();
29
+ url = url.trim();
30
+
31
+ switch ( message.toLowerCase() ) {
32
+ case 'security warning in the url':
33
+ message = __( 'Security warning in the URL', 'better-wp-security' );
34
+ break;
35
+ case 'malware found on url':
36
+ message = __( 'Malware found on URL', 'better-wp-security' );
37
+ break;
38
+ }
39
+
40
+ let payload;
41
+
42
+ if ( url.substring( 0, 5 ) === '&amp;' ) {
43
+ payload = decodeEntities( url );
44
+ url = results.itsec_site ? results.itsec_site.url : '/';
45
+ }
46
+
47
+ let details;
48
+ const parts = entry[ 1 ].split( '\n', 2 );
49
+
50
+ if ( parts[ 1 ] ) {
51
+ const detailsMatch = parts[ 0 ].match( /(.+)\. Details: (.+)/ );
52
+
53
+ if ( detailsMatch ) {
54
+ let type = detailsMatch[ 1 ];
55
+ const docsUrl = detailsMatch[ 2 ];
56
+
57
+ if ( '*Known Spam detected' === type ) {
58
+ type = __( '*Known Spam detected', 'better-wp-security' );
59
+ }
60
+
61
+ details = (
62
+ <Fragment>
63
+ <br />
64
+ { sprintf( __( 'Type: %s', 'better-wp-security' ), type ) }
65
+ <br />
66
+ { __( 'Documentation:', 'better-wp-security' ) }
67
+ <a href={ docsUrl } target="_blank" rel="noopener noreferrer">{ docsUrl }</a>
68
+ </Fragment>
69
+ );
70
+ }
71
+
72
+ const payloadMatch = decodeEntities( parts[ 1 ].trim() ).match( /<div id='HiddenDiv'>(.+)<\/div>/ );
73
+
74
+ if ( payloadMatch ) {
75
+ payload = payloadMatch[ 1 ];
76
+ }
77
+ }
78
+
79
+ return (
80
+ <li className="itsec-malware-scan-warn" key={ i }>
81
+ <span>
82
+ { message }
83
+ <br />
84
+ { __( 'Infected URL:', 'better-wp-security' ) }
85
+ <a href={ url } target="_blank" rel="noopener noreferrer">{ url }</a>
86
+ { details }
87
+ { payload && (
88
+ <Fragment>
89
+ <br />
90
+ { __( 'Payload:', 'better-wp-security' ) }
91
+ <pre>{ payload }</pre>
92
+ </Fragment>
93
+ ) }
94
+ </span>
95
+ </li>
96
+ );
97
+ } ) }
98
+ </ul>
99
+ </WrappedSection>
100
+ );
101
+ }
102
+
103
+ export default MalwareDetails;
core/packages/components/src/malware-scan-results/style.scss ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .itsec-malware-scan-results {
2
+ & h4 {
3
+ margin-top: 5px;
4
+ }
5
+
6
+ & ul {
7
+ margin-left: 20px;
8
+ }
9
+
10
+ & ul li {
11
+ line-height: 16px;
12
+ list-style: outside none disc;
13
+ }
14
+
15
+ & .itsec-malware-scan-details pre {
16
+ background-color: #eaeaea;
17
+ padding: 1em;
18
+ white-space: pre-wrap;
19
+ }
20
+
21
+ & .itsec-malware-scan-results-section {
22
+ border: 1px solid #ddd;
23
+ border-bottom-color: transparent;
24
+ padding: 0 1em;
25
+ }
26
+
27
+ & .itsec-malware-scan-results-section:last-child {
28
+ border-bottom-color: #ddd;
29
+ }
30
+
31
+ & .itsec-malware-scan-clean,
32
+ & .itsec-malware-scan-warn,
33
+ & .itsec-malware-scan-error {
34
+ padding: 2px 6px;
35
+ color: #fff;
36
+ margin-right: 1em;
37
+ width: 60px;
38
+ text-align: center;
39
+ }
40
+
41
+ & span.itsec-malware-scan-clean,
42
+ & span.itsec-malware-scan-warn,
43
+ & span.itsec-malware-scan-error {
44
+ display: inline-block;
45
+ }
46
+
47
+ & .itsec-malware-scan-clean {
48
+ background: #7ad03a;
49
+ }
50
+
51
+ & .itsec-malware-scan-warn,
52
+ & .itsec-malware-scan-error {
53
+ background: #dd3d36;
54
+ }
55
+
56
+ & .itsec-malware-scan-details .itsec-malware-scan-warn,
57
+ & .itsec-malware-scan-details .itsec-malware-scan-error,
58
+ & .itsec-malware-scan-details .itsec-malware-scan-clean {
59
+ background: none;
60
+ padding: 0;
61
+ color: inherit;
62
+ margin: 0;
63
+ width: 100%;
64
+ text-align: left;
65
+ }
66
+
67
+ & .itsec-malware-scan-details .itsec-malware-scan-warn,
68
+ & .itsec-malware-scan-details .itsec-malware-scan-error {
69
+ color: #dd3d36;
70
+ }
71
+
72
+ & .itsec-malware-scan-details .itsec-malware-scan-clean {
73
+ color: #7ad03a;
74
+ }
75
+
76
+ & .itsec-malware-scan-results-section .itsec-malware-scan-details li {
77
+ margin-bottom: .25em;
78
+ padding-bottom: .5em;
79
+ border-bottom: 1px solid #ddd;
80
+ }
81
+
82
+ & .itsec-malware-scan-results-section .itsec-malware-scan-details li:last-child {
83
+ border: none;
84
+ }
85
+
86
+ & .itsec-malware-scan-results-section .itsec-malware-scan-details li span {
87
+ color: #444;
88
+ }
89
+
90
+ & .itsec-malware-scan-toggle-details {
91
+ margin-left: 1em;
92
+ }
93
+ }
core/packages/components/src/malware-scan-results/system-error-details.js ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { has } from 'lodash';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { __ } from '@wordpress/i18n';
10
+
11
+ /**
12
+ * Internal dependencies.
13
+ */
14
+ import WrappedSection from './wrapped-section';
15
+
16
+ function SystemErrorDetails( { results } ) {
17
+ return has( results, [ 'SYSTEM', 'ERROR' ] ) && (
18
+ <WrappedSection type="system-error" status="error" description={ __( 'The scan failed to properly scan the site.', 'better-wp-security' ) }>
19
+ <ul>
20
+ { results.SYSTEM.ERROR.map( ( entry, i ) => (
21
+ <li className="itsec-malware-scan-error" key={ i }>
22
+ <span>{ entry }</span>
23
+ </li>
24
+ ) ) }
25
+ </ul>
26
+ </WrappedSection>
27
+ );
28
+ }
29
+
30
+ export default SystemErrorDetails;
core/packages/components/src/malware-scan-results/wp-error-details.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { __, sprintf } from '@wordpress/i18n';
5
+ import { Fragment } from '@wordpress/element';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import WrappedSection from './wrapped-section';
11
+ import { castWPError } from '@ithemes/security-utils';
12
+
13
+ function WPErrorDetails( { results, showErrorDetails = false } ) {
14
+ const wpError = castWPError( results );
15
+
16
+ return (
17
+ <WrappedSection status="error" description={ __( 'The scan failed to properly scan the site.', 'better-wp-security' ) }>
18
+ <p>{ sprintf( __( 'Error Message: %s', 'better-wp-security' ), wpError.getErrorMessage() ) }</p>
19
+ <p>{ sprintf( __( 'Error Code: %s', 'better-wp-security' ), wpError.getErrorCode() ) }</p>
20
+
21
+ { showErrorDetails && wpError.getErrorData() && (
22
+ <Fragment>
23
+ <p>{ __( 'If you contact support about this error, please provide the following debug details:', 'better-wp-security' ) }</p>
24
+ <pre>
25
+ { JSON.stringify( { code: wpError.getErrorCode(), data: wpError.getErrorData() }, null, 2 ) }
26
+ </pre>
27
+ </Fragment>
28
+ ) }
29
+ </WrappedSection>
30
+ );
31
+ }
32
+
33
+ export default WPErrorDetails;
core/packages/components/src/malware-scan-results/wrapped-section.js ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import classnames from 'classnames';
5
+ import { isEmpty } from 'lodash';
6
+
7
+ /**
8
+ * WordPress dependencies
9
+ */
10
+ import { __ } from '@wordpress/i18n';
11
+ import { Fragment } from '@wordpress/element';
12
+ import { Button } from '@wordpress/components';
13
+ import { compose, withState, withInstanceId } from '@wordpress/compose';
14
+
15
+ function WrappedSection( { type, status, description, isShowing, setState, instanceId, children } ) {
16
+ let statusText;
17
+
18
+ switch ( status ) {
19
+ case 'clean':
20
+ statusText = __( 'Clean', 'better-wp-security' );
21
+ break;
22
+ case 'warn':
23
+ statusText = __( 'Warn', 'better-wp-security' );
24
+ break;
25
+ case 'error':
26
+ statusText = __( 'Error', 'better-wp-security' );
27
+ break;
28
+ default:
29
+ statusText = status;
30
+ break;
31
+ }
32
+
33
+ const statusEl = ( <span className={ `itsec-malware-scan-${ status }` }>{ statusText }</span> );
34
+
35
+ return (
36
+ <div className={ classnames( 'itsec-malware-scan-results-section', `itsec-malware-scan-results-${ type }-section` ) }>
37
+ { isEmpty( children ) ? ( <p>{ statusEl } { description }</p> ) : (
38
+ <Fragment>
39
+ <p>
40
+ { statusEl }
41
+ { description }
42
+ <Button isLink className="itsec-malware-scan-toggle-details" onClick={ () => setState( { isShowing: ! isShowing } ) }
43
+ aria-expanded={ isShowing } aria-controls={ `itsec-malware-scan-details--${ instanceId }` }>
44
+ { isShowing ?
45
+ __( 'Hide Details', 'better-wp-security' ) :
46
+ __( 'Show Details', 'better-wp-security' )
47
+ }
48
+ </Button>
49
+ </p>
50
+ <div className="itsec-malware-scan-details" id={ `itsec-malware-scan-details--${ instanceId }` } style={ { display: isShowing ? 'block' : 'none' } }>
51
+ { children }
52
+ </div>
53
+ </Fragment>
54
+ ) }
55
+ </div>
56
+ );
57
+ }
58
+
59
+ export default compose( [
60
+ withState( { isShowing: false } ),
61
+ withInstanceId,
62
+ ] )( WrappedSection );
core/packages/components/src/print-r/index.js ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { isString, isBoolean, isNumber, isArray, isPlainObject, keys, forEach, toString, cloneDeep, size } from 'lodash';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { Fragment } from '@wordpress/element';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import './style.scss';
15
+
16
+ export default function PrintR( { json } ) {
17
+ return (
18
+ <pre className="itsec-component-print-r">
19
+ { inspectDive( cloneDeep( json ) ) }
20
+ </pre>
21
+ );
22
+ }
23
+
24
+ function inspectDive( data, maxDepth = 10, depth = 0, showArrayHeader = true ) {
25
+ if ( isString( data ) ) {
26
+ if ( data.length === 0 ) {
27
+ return <strong>[empty string]</strong>;
28
+ }
29
+
30
+ return data;
31
+ }
32
+
33
+ if ( isNumber( data ) ) {
34
+ return <strong>{ `[number] ${ data }` }</strong>;
35
+ }
36
+
37
+ if ( isBoolean( data ) ) {
38
+ return <strong>{ data ? '[boolean] true' : '[boolean] false' }</strong>;
39
+ }
40
+
41
+ if ( data === null || data === undefined ) {
42
+ return <strong>null</strong>;
43
+ }
44
+
45
+ if ( isArray( data ) || isPlainObject( data ) ) {
46
+ const retval = [];
47
+
48
+ if ( showArrayHeader ) {
49
+ retval.push( <strong key="header">{ 'Array' }</strong> );
50
+ }
51
+
52
+ if ( 0 === size( data ) ) {
53
+ retval.push( '()' );
54
+
55
+ return retval;
56
+ }
57
+
58
+ if ( depth === maxDepth ) {
59
+ retval.push( `(${ data.length })` );
60
+
61
+ return retval;
62
+ }
63
+
64
+ let maxLength = 0;
65
+
66
+ for ( const key of keys( data ) ) {
67
+ if ( key.length > maxLength ) {
68
+ maxLength = key.length;
69
+ }
70
+ }
71
+
72
+ const padding = pad( depth );
73
+ forEach( data, ( value, key ) => {
74
+ retval.push(
75
+ <Fragment key={ key }>
76
+ { '\n' }
77
+ { padding }
78
+ { key }
79
+ { pad( maxLength - toString( key ).length, ' ' ) }
80
+ { ' ' }
81
+ <strong>=&gt;</strong>
82
+ { ' ' }
83
+ { inspectDive( value, maxDepth, depth + 1 ) }
84
+ </Fragment>
85
+ );
86
+ } );
87
+
88
+ return retval;
89
+ }
90
+
91
+ return <strong>[*]</strong>;
92
+ }
93
+
94
+ function pad( depth, padding = ' ' ) {
95
+ let ret = '';
96
+
97
+ for ( let i = 0; i <= depth; i++ ) {
98
+ ret += padding;
99
+ }
100
+
101
+ return ret;
102
+ }
core/packages/components/src/print-r/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/components/src/print-r/style.scss ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ .itsec-component-print-r {
2
+ background: #f1f1f1;
3
+ overflow: scroll;
4
+ padding: 1em;
5
+ color: black;
6
+ font-family: "Courier New", Courier, monospace;
7
+ font-size: 12px;
8
+ white-space: pre-wrap;
9
+ text-align: left;
10
+ max-width: 100%;
11
+ }
core/packages/components/src/site-scan-results/blacklist-details.js ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { get } from 'lodash';
5
+ import memize from 'memize';
6
+
7
+ /**
8
+ * WordPress dependencies
9
+ */
10
+ import { __, sprintf } from '@wordpress/i18n';
11
+
12
+ /**
13
+ * Internal dependencies
14
+ */
15
+ import WrappedSection from './wrapped-section';
16
+ import Detail from './detail';
17
+
18
+ const sortBlacklist = memize( ( blacklist ) => {
19
+ return [ ...blacklist ].sort( ( a, b ) => {
20
+ if ( a.status === 'blacklisted' && b.status !== 'blacklisted' ) {
21
+ return -1;
22
+ }
23
+
24
+ if ( a.status !== 'blacklisted' && b.status === 'blacklisted' ) {
25
+ return 1;
26
+ }
27
+
28
+ return 0;
29
+ } );
30
+ } );
31
+
32
+ function BlacklistDetails( { results } ) {
33
+ const blacklist = sortBlacklist( results.entries.blacklist );
34
+ const status = get( blacklist, [ 0, 'status' ] ) === 'blacklisted' ? 'warn' : 'clean';
35
+
36
+ return (
37
+ <WrappedSection type="malware" status={ status } description={ __( 'Blacklist', 'better-wp-security' ) }>
38
+ { blacklist.map( ( entry, i ) => (
39
+ <Detail key={ i } status={ entry.status === 'blacklisted' ? 'warn' : 'clean' }>
40
+ <a href={ entry.report_details }>
41
+ { entry.status === 'blacklisted' ?
42
+ sprintf( __( 'Domain blacklisted by %s', 'better-wp-security' ), entry.vendor.label ) :
43
+ sprintf( __( 'Domain clean by %s', 'better-wp-security' ), entry.vendor.label )
44
+ }
45
+ </a>
46
+ </Detail>
47
+ ) ) }
48
+ </WrappedSection>
49
+ );
50
+ }
51
+
52
+ export default BlacklistDetails;
core/packages/components/src/site-scan-results/detail.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ export default function Detail( { status, children } ) {
2
+ return (
3
+ <li className={ `itsec-site-scan__detail itsec-site-scan__detail--${ status }` }>
4
+ <span>
5
+ { children }
6
+ </span>
7
+ </li>
8
+ );
9
+ }
core/packages/components/src/site-scan-results/details.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ export default function Details( { id, isVisible, children } ) {
2
+ return (
3
+ <div className="itsec-site-scan__details" id={ id } style={ { display: isVisible ? 'block' : 'none' } }>
4
+ <ul>{ children }</ul>
5
+ </div>
6
+ );
7
+ }
core/packages/components/src/site-scan-results/index.js ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { __, sprintf } from '@wordpress/i18n';
5
+ import { Fragment } from '@wordpress/element';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import { isWPError } from '@ithemes/security-utils';
11
+ import WPErrorDetails from './wp-error-details';
12
+ import SystemErrorDetails from './system-error-details';
13
+ import MalwareDetails from './malware-details';
14
+ import BlacklistDetails from './blacklist-details';
15
+ import KnownVulnerabilities from './known-vulnerabilities';
16
+ import './style.scss';
17
+
18
+ function SiteScanResults( { results, showSiteUrl = true, showErrorDetails = true } ) {
19
+ const siteUrl = results.url;
20
+
21
+ return (
22
+ <div className="itsec-site-scan-results">
23
+ { showSiteUrl && siteUrl && <h4>{ sprintf( __( 'Site: %s', 'better-wp-security' ), siteUrl ) }</h4> }
24
+
25
+ { isWPError( results ) ? <WPErrorDetails results={ results } showErrorDetails={ showErrorDetails } /> : (
26
+ <Fragment>
27
+ <SystemErrorDetails results={ results } />
28
+ <KnownVulnerabilities results={ results } />
29
+ <MalwareDetails results={ results } />
30
+ <BlacklistDetails results={ results } />
31
+ </Fragment>
32
+ ) }
33
+ </div>
34
+ );
35
+ }
36
+
37
+ export default SiteScanResults;
core/packages/components/src/site-scan-results/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/components/src/site-scan-results/known-vulnerabilities.js ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import WrappedSection from './wrapped-section';
10
+ import Detail from './detail';
11
+
12
+ function KnownVulnerabilities( { results } ) {
13
+ if ( ! results.entries.vulnerabilities.length ) {
14
+ return <WrappedSection type="vulnerabilities" status="clean" description={ __( 'Known Vulnerabilities', 'better-wp-security' ) } />;
15
+ }
16
+
17
+ return (
18
+ <WrappedSection type="vulnerabilities" status="warn" description={ __( 'Known Vulnerabilities', 'better-wp-security' ) }>
19
+ { results.entries.vulnerabilities.map( ( entry, i ) => {
20
+ return entry.issues.map( ( issue, j ) => (
21
+ <Detail key={ `${ i }-${ j }` } status="warn">
22
+ <a href={ entry.link } target={ '_blank' }>{ issue.title }</a>
23
+ </Detail>
24
+ ) );
25
+ } ) }
26
+ </WrappedSection>
27
+ );
28
+ }
29
+
30
+ export default KnownVulnerabilities;
core/packages/components/src/site-scan-results/malware-details.js ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { __, sprintf } from '@wordpress/i18n';
5
+ import { Fragment } from '@wordpress/element';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import WrappedSection from './wrapped-section';
11
+ import Detail from './detail';
12
+
13
+ function MalwareDetails( { results } ) {
14
+ if ( ! results.entries.malware.length ) {
15
+ return <WrappedSection type="malware" status="clean" description={ __( 'Malware', 'better-wp-security' ) } />;
16
+ }
17
+
18
+ return (
19
+ <WrappedSection type="malware" status="warn" description={ __( 'Malware', 'better-wp-security' ) }>
20
+ { results.entries.malware.map( ( entry, i ) => {
21
+ let location = entry.location;
22
+ const message = entry.message,
23
+ documentation = entry.documentation,
24
+ payload = entry.payload,
25
+ type = entry.type;
26
+
27
+ if ( ! location.match( /https?:\/\// ) ) {
28
+ location = false;
29
+ }
30
+
31
+ return (
32
+ <Detail key={ i } status="warn">
33
+ { message }
34
+ { location && (
35
+ <Fragment>
36
+ <br />
37
+ { __( 'Infected URL:', 'better-wp-security' ) }
38
+ { location && <a href={ location } target="_blank" rel="noopener noreferrer">{ location }</a> }
39
+ </Fragment>
40
+ ) }
41
+ { type.slug !== 'other' && (
42
+ <Fragment>
43
+ <br />
44
+ { sprintf( __( 'Type: %s', 'better-wp-security' ), type.label ) }
45
+ <br />
46
+ { __( 'Documentation:', 'better-wp-security' ) }
47
+ <a href={ documentation } target="_blank" rel="noopener noreferrer">{ documentation }</a>
48
+ </Fragment>
49
+ ) }
50
+ { payload && (
51
+ <Fragment>
52
+ <br />
53
+ { __( 'Payload:', 'better-wp-security' ) }
54
+ <pre>{ payload }</pre>
55
+ </Fragment>
56
+ ) }
57
+ </Detail>
58
+ );
59
+ } ) }
60
+ </WrappedSection>
61
+ );
62
+ }
63
+
64
+ export default MalwareDetails;
core/packages/components/src/site-scan-results/style.scss ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $clean: #7ad03a;
2
+ $warn: #dd3d36;
3
+ $error: #dd3d36;
4
+
5
+ .itsec-site-scan-results {
6
+ & h4 {
7
+ margin-top: 5px;
8
+ }
9
+
10
+ & .itsec-site-scan-details pre {
11
+ background-color: #eaeaea;
12
+ padding: 1em;
13
+ white-space: pre-wrap;
14
+ }
15
+
16
+ & .itsec-site-scan-results-section {
17
+ border: 1px solid #ddd;
18
+ border-bottom-color: transparent;
19
+ padding: 1em;
20
+
21
+ & > p {
22
+ margin: 0;
23
+ }
24
+ }
25
+
26
+ & .itsec-site-scan-results-section:last-child {
27
+ border-bottom-color: #ddd;
28
+ }
29
+
30
+ & .itsec-site-scan__status {
31
+ display: inline-block;
32
+ padding: 2px 6px;
33
+ color: #fff;
34
+ margin-right: 1em;
35
+ width: 60px;
36
+ text-align: center;
37
+
38
+ &.itsec-site-scan__status--clean {
39
+ background: $clean;
40
+ }
41
+
42
+ &.itsec-site-scan__status--warn {
43
+ background: $warn;
44
+ }
45
+
46
+ &.itsec-site-scan__status--error {
47
+ background: $error;
48
+ }
49
+ }
50
+
51
+ & .itsec-site-scan__details {
52
+ & ul {
53
+ margin-left: 2.5em;
54
+ list-style: disc outside;
55
+ }
56
+
57
+ & .itsec-site-scan__detail {
58
+ width: 100%;
59
+ margin-bottom: .25em;
60
+ padding-bottom: .5em;
61
+ color: inherit;
62
+ border-bottom: 1px solid #ddd;
63
+
64
+ &:last-child {
65
+ border: none;
66
+ }
67
+
68
+ &.itsec-site-scan__detail--warn {
69
+ color: $warn;
70
+ }
71
+
72
+ &.itsec-site-scan__detail--error {
73
+ color: $error;
74
+ }
75
+
76
+ &.itsec-site-scan__detail--clean {
77
+ color: $clean;
78
+ }
79
+
80
+ & span {
81
+ color: #444;
82
+ }
83
+ }
84
+ }
85
+
86
+ & .itsec-site-scan-toggle-details {
87
+ margin-left: 1em;
88
+ }
89
+ }
core/packages/components/src/site-scan-results/system-error-details.js ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ /**
7
+ * Internal dependencies.
8
+ */
9
+ import WrappedSection from './wrapped-section';
10
+ import Detail from './detail';
11
+
12
+ function SystemErrorDetails( { results } ) {
13
+ return results.errors.length > 0 && (
14
+ <WrappedSection type="system-error" status="error" description={ __( 'The scan failed to properly scan the site.', 'better-wp-security' ) }>
15
+ { results.errors.map( ( entry, i ) => (
16
+ <Detail key={ i } status="error">
17
+ { entry.message }
18
+ </Detail>
19
+ ) ) }
20
+ </WrappedSection>
21
+ );
22
+ }
23
+
24
+ export default SystemErrorDetails;
core/packages/components/src/site-scan-results/wp-error-details.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { __, sprintf } from '@wordpress/i18n';
5
+ import { Fragment } from '@wordpress/element';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import WrappedSection from './wrapped-section';
11
+ import { castWPError } from '@ithemes/security-utils';
12
+
13
+ function WPErrorDetails( { results, showErrorDetails = false } ) {
14
+ const wpError = castWPError( results );
15
+
16
+ return (
17
+ <WrappedSection status="error" description={ __( 'The scan failed to properly scan the site.', 'better-wp-security' ) }>
18
+ <p>{ sprintf( __( 'Error Message: %s', 'better-wp-security' ), wpError.getErrorMessage() ) }</p>
19
+ <p>{ sprintf( __( 'Error Code: %s', 'better-wp-security' ), wpError.getErrorCode() ) }</p>
20
+
21
+ { showErrorDetails && wpError.getErrorData() && (
22
+ <Fragment>
23
+ <p>{ __( 'If you contact support about this error, please provide the following debug details:', 'better-wp-security' ) }</p>
24
+ <pre>
25
+ { JSON.stringify( { code: wpError.getErrorCode(), data: wpError.getErrorData() }, null, 2 ) }
26
+ </pre>
27
+ </Fragment>
28
+ ) }
29
+ </WrappedSection>
30
+ );
31
+ }
32
+
33
+ export default WPErrorDetails;
core/packages/components/src/site-scan-results/wrapped-section.js ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import classnames from 'classnames';
5
+ import { isEmpty } from 'lodash';
6
+
7
+ /**
8
+ * WordPress dependencies
9
+ */
10
+ import { __ } from '@wordpress/i18n';
11
+ import { Fragment } from '@wordpress/element';
12
+ import { Button } from '@wordpress/components';
13
+ import { compose, withState, withInstanceId } from '@wordpress/compose';
14
+
15
+ /**
16
+ * Internal dependencies
17
+ */
18
+ import Details from './details';
19
+
20
+ function WrappedSection( { type, status, description, isShowing, setState, instanceId, children } ) {
21
+ let statusText;
22
+
23
+ switch ( status ) {
24
+ case 'clean':
25
+ statusText = __( 'Clean', 'better-wp-security' );
26
+ break;
27
+ case 'warn':
28
+ statusText = __( 'Warn', 'better-wp-security' );
29
+ break;
30
+ case 'error':
31
+ statusText = __( 'Error', 'better-wp-security' );
32
+ break;
33
+ default:
34
+ statusText = status;
35
+ break;
36
+ }
37
+
38
+ const statusEl = ( <span className={ `itsec-site-scan__status itsec-site-scan__status--${ status }` }>{ statusText }</span> );
39
+
40
+ return (
41
+ <div className={ classnames( 'itsec-site-scan-results-section', `itsec-site-scan-results-${ type }-section` ) }>
42
+ { isEmpty( children ) ? ( <p>{ statusEl } { description }</p> ) : (
43
+ <Fragment>
44
+ <p>
45
+ { statusEl }
46
+ { description }
47
+ <Button isLink className="itsec-site-scan-toggle-details" onClick={ () => setState( { isShowing: ! isShowing } ) }
48
+ aria-expanded={ isShowing } aria-controls={ `itsec-site-scan__details--${ instanceId }` }>
49
+ { isShowing ?
50
+ __( 'Hide Details', 'better-wp-security' ) :
51
+ __( 'Show Details', 'better-wp-security' )
52
+ }
53
+ </Button>
54
+ </p>
55
+ <Details id={ `itsec-site-scan__details--${ instanceId }` } isVisible={ isShowing }>
56
+ { children }
57
+ </Details>
58
+ </Fragment>
59
+ ) }
60
+ </div>
61
+ );
62
+ }
63
+
64
+ export default compose( [
65
+ withState( { isShowing: false } ),
66
+ withInstanceId,
67
+ ] )( WrappedSection );
core/packages/hocs/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/hocs/src/index.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ export { default as withProps } from './with-props';
2
+ export { default as withDebounceHandler } from './with-debounce-handler';
3
+ export { default as withPropChangeCallback } from './with-prop-change-callback';
4
+ export { default as withInterval } from './with-interval';
5
+ export { default as withWidth } from './with-width';
core/packages/hocs/src/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/hocs/src/with-debounce-handler.js ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { debounce } from 'lodash';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { Component } from '@wordpress/element';
10
+ import { createHigherOrderComponent } from '@wordpress/compose';
11
+
12
+ /**
13
+ * Higher-order component that debounces an action.
14
+ *
15
+ * @Link https://github.com/deepsweet/hocs/tree/master/packages/debounce-handler (MIT)
16
+ *
17
+ * @param {string} handlerName
18
+ * @param {number|Function} wait
19
+ * @param {Object} [options]
20
+ * @return {WPComponent} Debounced component.
21
+ */
22
+ export default function withDebounceHandler( handlerName, wait, options = {} ) {
23
+ return createHigherOrderComponent( ( WrappedComponent ) => {
24
+ return class Wrapper extends Component {
25
+ constructor() {
26
+ super( ...arguments );
27
+
28
+ this.debouncedPropInvoke = debounce(
29
+ ( ...args ) => this.props[ handlerName ]( ...args ),
30
+ typeof wait === 'function' ? wait( this.props ) : wait,
31
+ options
32
+ );
33
+
34
+ this.handler = ( e, ...rest ) => {
35
+ if ( e && typeof e.persist === 'function' ) {
36
+ e.persist();
37
+ }
38
+
39
+ return this.debouncedPropInvoke( e, ...rest );
40
+ };
41
+ }
42
+
43
+ componentWillUnmount() {
44
+ this.debouncedPropInvoke.cancel();
45
+ }
46
+
47
+ render() {
48
+ const props = {
49
+ ...this.props,
50
+ [ handlerName ]: this.handler,
51
+ };
52
+
53
+ return <WrappedComponent { ...props } />;
54
+ }
55
+ };
56
+ }, 'withDebounceHandler' );
57
+ }
core/packages/hocs/src/with-interval.js ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { isFunction } from 'lodash';
5
+
6
+ /**
7
+ * WordPress Dependencies
8
+ */
9
+ import { Component } from '@wordpress/element';
10
+ import { createHigherOrderComponent } from '@wordpress/compose';
11
+
12
+ export default function withInterval( delay, cb ) {
13
+ let intervals;
14
+
15
+ if ( isFunction( cb ) ) {
16
+ intervals = [ { delay, cb } ];
17
+ } else {
18
+ intervals = delay;
19
+ }
20
+
21
+ return createHigherOrderComponent( ( WrappedComponent ) => {
22
+ return class Wrapper extends Component {
23
+ constructor() {
24
+ super( ...arguments );
25
+
26
+ this.intervalIds = [];
27
+ }
28
+
29
+ componentDidMount() {
30
+ for ( const interval of intervals ) {
31
+ ( ( callback ) => {
32
+ this.intervalIds.push( setInterval( () => callback( this.props ), interval.delay ) );
33
+ } )( interval.cb );
34
+ }
35
+ }
36
+
37
+ componentWillUnmount() {
38
+ this.intervalIds.forEach( clearInterval );
39
+ }
40
+
41
+ render() {
42
+ return <WrappedComponent { ...this.props } />;
43
+ }
44
+ };
45
+ }, 'withInterval' );
46
+ }
core/packages/hocs/src/with-prop-change-callback.js ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { isFunction } from 'lodash';
5
+ /**
6
+ * WordPress dependencies
7
+ */
8
+ import { Component } from '@wordpress/element';
9
+ import { createHigherOrderComponent } from '@wordpress/compose';
10
+
11
+ /**
12
+ * Higher-order component that allows for firing an action after certain props have changed.
13
+ *
14
+ * @param {...string|{prop: string, cb: Function}} prop Prop to listen to, or object with prop to listener to and callback to execute.
15
+ * @param {Function} [cb] Function to call when prop changes.
16
+ *
17
+ * @return {WPComponent} Component with prop change listeners.
18
+ */
19
+ export default function withPropChangeCallback( prop, cb ) {
20
+ let listeners;
21
+
22
+ if ( isFunction( cb ) ) {
23
+ listeners = [ { prop, cb } ];
24
+ } else {
25
+ listeners = arguments;
26
+ }
27
+
28
+ return createHigherOrderComponent( ( WrappedComponent ) => {
29
+ return class Wrapper extends Component {
30
+ componentDidUpdate( prevProps ) {
31
+ for ( const listener of listeners ) {
32
+ if ( this.props[ listener.prop ] !== prevProps[ listener.prop ] ) {
33
+ listener.cb( prevProps[ listener.prop ], this.props );
34
+ }
35
+ }
36
+ }
37
+
38
+ render() {
39
+ return <WrappedComponent { ...this.props } />;
40
+ }
41
+ };
42
+ }, 'withProps' );
43
+ }
core/packages/hocs/src/with-props.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { Component } from '@wordpress/element';
5
+ import { createHigherOrderComponent } from '@wordpress/compose';
6
+
7
+ /**
8
+ * Higher-order component that applies props to the inner component.
9
+ *
10
+ * @param {Object} props
11
+ *
12
+ * @return {WPComponent} Debounced component.
13
+ */
14
+ export default function withProps( props ) {
15
+ return createHigherOrderComponent( ( WrappedComponent ) => {
16
+ return class Wrapper extends Component {
17
+ render() {
18
+ return <WrappedComponent { ...this.props } { ...props } />;
19
+ }
20
+ };
21
+ }, 'withProps' );
22
+ }
core/packages/hocs/src/with-width.js ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { Component, findDOMNode } from '@wordpress/element';
5
+ import { createHigherOrderComponent } from '@wordpress/compose';
6
+
7
+ /*
8
+ * A simple HOC that provides facility for listening to container resizes.
9
+ */
10
+ const withWidth = createHigherOrderComponent( ( WrappedComponent ) => {
11
+ return class WithWidth extends Component {
12
+ static defaultProps = {
13
+ measureBeforeMount: false,
14
+ };
15
+
16
+ state = {
17
+ width: 1280,
18
+ };
19
+
20
+ mounted = false;
21
+ ref = null;
22
+
23
+ componentDidMount() {
24
+ this.mounted = true;
25
+
26
+ window.addEventListener( 'resize', this.onWindowResize );
27
+ document.getElementById( 'collapse-button' ).addEventListener( 'click', this.onWindowResize );
28
+ this.onWindowResize();
29
+ }
30
+
31
+ componentWillUnmount() {
32
+ this.mounted = false;
33
+ window.removeEventListener( 'resize', this.onWindowResize );
34
+ document.getElementById( 'collapse-button' ).removeEventListener( 'click', this.onWindowResize );
35
+ }
36
+
37
+ onWindowResize = () => {
38
+ if ( ! this.mounted ) {
39
+ return;
40
+ }
41
+
42
+ // eslint-disable-next-line react/no-find-dom-node
43
+ const node = findDOMNode( this );
44
+
45
+ if ( node instanceof window.HTMLElement ) {
46
+ const width = node.offsetWidth;
47
+ this.setState( { width } );
48
+ }
49
+ };
50
+
51
+ render() {
52
+ const { measureBeforeMount, ...rest } = this.props;
53
+ if ( measureBeforeMount && ! this.mounted ) {
54
+ return (
55
+ <div className={ this.props.className } style={ this.props.style } />
56
+ );
57
+ }
58
+
59
+ return <WrappedComponent { ...rest } width={ this.state.width + 20 } />;
60
+ }
61
+ };
62
+ }, 'withWidth' );
63
+
64
+ export default withWidth;
core/packages/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/style-guide/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/style-guide/src/animations.scss ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ @keyframes itsec-animation-fade-in-constant {
2
+ from {
3
+ opacity: 0;
4
+ }
5
+
6
+ to {
7
+ opacity: 1;
8
+ }
9
+ }
core/packages/style-guide/src/breakpoints.scss ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ $huge: 1440px;
2
+ $wide: 1280px;
3
+ $large: 960px;
4
+ $medium: 782px;
5
+ $small: 600px;
6
+ $mobile: 480px;
core/packages/style-guide/src/colors.js ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const PRIMARYS = Object.freeze( [
2
+ /*'#0083E3',
3
+ '#FFCE00',
4
+ '#52c551',
5
+ '#7ABEED',*/
6
+ '#e67e22', // carrot
7
+ '#2ecc71', // emerald
8
+ '#3498db', // peter river
9
+ '#e74c3c', // alizarin
10
+ '#8e44ad', // wisteria
11
+ '#1abc9c', // turquoise
12
+ '#2c3e50', // midnight blue
13
+ ] );
core/packages/style-guide/src/colors.scss ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $main-blue: #0081E3;
2
+ $light-blue: #7ABEED;
3
+
4
+ // Hugo's new WordPress shades of gray, from http://codepen.io/hugobaeta/pen/grJjVp.
5
+ $black: #000;
6
+ $dark-gray-900: #191e23;
7
+ $dark-gray-800: #23282d;
8
+ $dark-gray-700: #32373c;
9
+ $dark-gray-600: #40464d;
10
+ $dark-gray-500: #555d66; // Use this most of the time for dark items.
11
+ $dark-gray-400: #606a73;
12
+ $dark-gray-300: #6c7781; // Lightest gray that can be used for AA text contrast.
13
+ $dark-gray-200: #7e8993;
14
+ $dark-gray-150: #8d96a0; // Lightest gray that can be used for AA non-text contrast.
15
+ $dark-gray-100: #8f98a1;
16
+ $light-gray-900: #a2aab2;
17
+ $light-gray-800: #b5bcc2;
18
+ $light-gray-700: #ccd0d4;
19
+ $light-gray-600: #d7dade;
20
+ $light-gray-500: #e2e4e7; // Good for "grayed" items and borders.
21
+ $light-gray-400: #e8eaeb; // Good for "readonly" input fields and special text selection.
22
+ $light-gray-300: #edeff0;
23
+ $light-gray-200: #f3f4f5;
24
+ $light-gray-100: #f8f9f9;
25
+ $white: #fff;
26
+
27
+ // Dark opacities, for use with light themes.
28
+ $dark-opacity-900: rgba(#000510, 0.9);
29
+ $dark-opacity-800: rgba(#00000a, 0.85);
30
+ $dark-opacity-700: rgba(#06060b, 0.8);
31
+ $dark-opacity-600: rgba(#000913, 0.75);
32
+ $dark-opacity-500: rgba(#0a1829, 0.7);
33
+ $dark-opacity-400: rgba(#0a1829, 0.65);
34
+ $dark-opacity-300: rgba(#0e1c2e, 0.62);
35
+ $dark-opacity-200: rgba(#162435, 0.55);
36
+ $dark-opacity-100: rgba(#223443, 0.5);
37
+ $dark-opacity-light-900: rgba(#304455, 0.45);
38
+ $dark-opacity-light-800: rgba(#425863, 0.4);
39
+ $dark-opacity-light-700: rgba(#667886, 0.35);
40
+ $dark-opacity-light-600: rgba(#7b86a2, 0.3);
41
+ $dark-opacity-light-500: rgba(#9197a2, 0.25);
42
+ $dark-opacity-light-400: rgba(#95959c, 0.2);
43
+ $dark-opacity-light-300: rgba(#829493, 0.15);
44
+ $dark-opacity-light-200: rgba(#8b8b96, 0.1);
45
+ $dark-opacity-light-100: rgba(#747474, 0.05);
46
+
47
+ // Light opacities, for use with dark themes.
48
+ $light-opacity-900: rgba($white, 1);
49
+ $light-opacity-800: rgba($white, 0.9);
50
+ $light-opacity-700: rgba($white, 0.85);
51
+ $light-opacity-600: rgba($white, 0.8);
52
+ $light-opacity-500: rgba($white, 0.75);
53
+ $light-opacity-400: rgba($white, 0.7);
54
+ $light-opacity-300: rgba($white, 0.65);
55
+ $light-opacity-200: rgba($white, 0.6);
56
+ $light-opacity-100: rgba($white, 0.55);
57
+ $light-opacity-light-900: rgba($white, 0.5);
58
+ $light-opacity-light-800: rgba($white, 0.45);
59
+ $light-opacity-light-700: rgba($white, 0.4);
60
+ $light-opacity-light-600: rgba($white, 0.35);
61
+ $light-opacity-light-500: rgba($white, 0.3);
62
+ $light-opacity-light-400: rgba($white, 0.25);
63
+ $light-opacity-light-300: rgba($white, 0.2);
64
+ $light-opacity-light-200: rgba($white, 0.15);
65
+ $light-opacity-light-100: rgba($white, 0.1);
66
+
67
+ // Additional colors.
68
+ // Some are from https://make.wordpress.org/design/handbook/foundations/colors/.
69
+ $blue-wordpress-700: #00669b;
70
+ $blue-dark-900: #0071a1;
71
+
72
+ $blue-medium-900: #006589;
73
+ $blue-medium-800: #00739c;
74
+ $blue-medium-700: #007fac;
75
+ $blue-medium-600: #008dbe;
76
+ $blue-medium-500: #00a0d2;
77
+ $blue-medium-400: #33b3db;
78
+ $blue-medium-300: #66c6e4;
79
+ $blue-medium-200: #bfe7f3;
80
+ $blue-medium-100: #e5f5fa;
81
+ $blue-medium-highlight: #b3e7fe;
82
+ $blue-medium-focus: #007cba;
83
+
84
+ // Alert colors.
85
+ $alert-yellow: #f0b849;
86
+ $alert-orange: #f78b53;
87
+ $alert-red: #d94f4f;
88
+ $alert-green: #4ab866;
89
+ $alert-blue: $blue-medium-500;
core/packages/style-guide/src/index.js ADDED
@@ -0,0 +1 @@
 
1
+ export * from './colors';
core/packages/style-guide/src/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/style-guide/src/mixins.scss ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "colors.scss";
2
+
3
+ @mixin sticky-table($spacing: 1em) {
4
+ & thead th {
5
+ position: sticky;
6
+ top: 0;
7
+ padding-top: $spacing;
8
+ padding-bottom: $spacing;
9
+ background: white;
10
+ color: $dark-gray-500;
11
+ text-transform: uppercase;
12
+ font-weight: normal;
13
+ border-bottom: 1px solid $light-gray-500;
14
+ border-top: 1px solid $light-gray-500;
15
+ }
16
+
17
+ & th,
18
+ & td {
19
+ text-align: left;
20
+ padding: $spacing;
21
+
22
+ &:first-child {
23
+ }
24
+
25
+ &:last-child {
26
+ text-align: right;
27
+ }
28
+ }
29
+
30
+ & tbody th {
31
+ font-weight: normal;
32
+ }
33
+ }
34
+
35
+ @mixin bordered-button($style: 'blue') {
36
+ $primary: $main-blue;
37
+ $alternate: #E1f2fc;
38
+
39
+ @if ($style == 'green') {
40
+ $primary: $alert-green;
41
+ $alternate: #e4f4e8;
42
+ }
43
+
44
+ @if ($style == 'red') {
45
+ $primary: $alert-red;
46
+ $alternate: #fcefef;
47
+ }
48
+
49
+ @if ($style == 'orange') {
50
+ $primary: $alert-orange;
51
+ $alternate: #fef1ea;
52
+ }
53
+
54
+
55
+ padding: .5em 1em;
56
+ background: $alternate;
57
+ color: $primary;
58
+ border: 1px solid $primary;
59
+ transition: background-color 300ms, color 150ms;
60
+
61
+ &:hover {
62
+ background: $primary;
63
+ color: white;
64
+ }
65
+
66
+ &.is-busy {
67
+ animation: components-button__busy-animation 2500ms infinite linear;
68
+ background-size: 100px 100%;
69
+ background-image: repeating-linear-gradient(-45deg, $alternate, #fff 11px, #fff 10px, $alternate 20px);
70
+ opacity: 1;
71
+
72
+ &:hover {
73
+ color: $primary;
74
+ }
75
+ }
76
+ }
core/packages/utils/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/utils/src/error-response.js ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ export default class ErrorResponse extends Error {
7
+ constructor( response, ...args ) {
8
+ super( response.message || __( 'An unknown error occurred.', 'better-wp-security' ), ...args );
9
+
10
+ if ( Error.captureStackTrace ) {
11
+ Error.captureStackTrace( this, ErrorResponse );
12
+ }
13
+
14
+ this.__response = response;
15
+
16
+ for ( const prop in response ) {
17
+ if ( response.hasOwnProperty( prop ) ) {
18
+ Object.defineProperty( this, prop, {
19
+ value: response[ prop ],
20
+ configurable: true,
21
+ enumerable: true,
22
+ writable: true,
23
+ } );
24
+ }
25
+ }
26
+ }
27
+
28
+ toString() {
29
+ return this.__response.toString();
30
+ }
31
+
32
+ getResponse() {
33
+ return this.__response;
34
+ }
35
+ }
core/packages/utils/src/index.js ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { isPlainObject } from 'lodash';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import WPError from './wp-error';
10
+ import ErrorResponse from './error-response';
11
+
12
+ export function makeUrlRelative( baseUrl, target ) {
13
+ let rel = target.replace( baseUrl, '' );
14
+
15
+ if ( rel.charAt( 0 ) !== '/' ) {
16
+ rel = '/' + rel;
17
+ }
18
+
19
+ return rel;
20
+ }
21
+
22
+ export function shortenNumber( number ) {
23
+ if ( number <= 999 ) {
24
+ return number.toString();
25
+ }
26
+
27
+ if ( number <= 9999 ) {
28
+ const dec = ( number / 1000 ),
29
+ fixed = dec.toFixed( 1 );
30
+
31
+ if ( fixed.charAt( fixed.length - 1 ) === '0' ) {
32
+ return fixed.replace( '.0', 'k' );
33
+ }
34
+
35
+ return `${ fixed }k`;
36
+ }
37
+
38
+ if ( number <= 99999 ) {
39
+ return number.toString().substring( 0, 2 ) + 'k';
40
+ }
41
+
42
+ if ( number <= 999999 ) {
43
+ return number.toString().substring( 0, 3 ) + 'k';
44
+ }
45
+
46
+ if ( number <= 9999999 ) {
47
+ const dec = ( number / 1000000 ),
48
+ fixed = dec.toFixed( 1 );
49
+
50
+ if ( fixed.charAt( fixed.length - 1 ) === '0' ) {
51
+ return fixed.replace( '.0', 'm' );
52
+ }
53
+
54
+ return `${ fixed }m`;
55
+ }
56
+
57
+ if ( number <= 99999999 ) {
58
+ return number.toString().substring( 0, 2 ) + 'm';
59
+ }
60
+
61
+ if ( number <= 999999999 ) {
62
+ return number.toString().substring( 0, 3 ) + 'm';
63
+ }
64
+
65
+ if ( number <= 9999999999 ) {
66
+ const dec = ( number / 1000000000 ),
67
+ fixed = dec.toFixed( 1 );
68
+
69
+ if ( fixed.charAt( fixed.length - 1 ) === '0' ) {
70
+ return fixed.replace( '.0', 'b' );
71
+ }
72
+
73
+ return `${ fixed }b`;
74
+ }
75
+
76
+ return number;
77
+ }
78
+
79
+ /**
80
+ * Is the given value likely a WP Error object.
81
+ *
82
+ * @param {*} object
83
+ * @return {boolean} Whether it was an error.
84
+ */
85
+ export function isWPError( object ) {
86
+ if ( ! isPlainObject( object ) ) {
87
+ return false;
88
+ }
89
+
90
+ const keys = Object.keys( object );
91
+
92
+ if ( keys.length !== 2 ) {
93
+ return false;
94
+ }
95
+
96
+ return keys.includes( 'errors' ) && keys.includes( 'error_data' );
97
+ }
98
+
99
+ export function isApiError( object ) {
100
+ if ( ! isPlainObject( object ) ) {
101
+ return false;
102
+ }
103
+
104
+ const keys = Object.keys( object );
105
+
106
+ if ( keys.length !== 3 && keys.length !== 4 ) {
107
+ return false;
108
+ }
109
+
110
+ if ( keys.length === 4 && ! keys.includes( 'additional_errors' ) ) {
111
+ return false;
112
+ }
113
+
114
+ return keys.includes( 'code' ) && keys.includes( 'message' ) && keys.includes( 'data' );
115
+ }
116
+
117
+ /**
118
+ * Cast to a WPError instance.
119
+ *
120
+ * @param {*} object
121
+ * @return {WPError} WPError instance.
122
+ */
123
+ export function castWPError( object ) {
124
+ if ( isWPError( object ) ) {
125
+ return WPError.fromPHPObject( object );
126
+ }
127
+
128
+ if ( isApiError( object ) ) {
129
+ return WPError.fromApiError( object );
130
+ }
131
+
132
+ return new WPError();
133
+ }
134
+
135
+ /**
136
+ * Convert an entries iterator to an object.
137
+ *
138
+ * @param {iterator} entries
139
+ *
140
+ * @return {Object} Object with entry[0] as the key and entry[1] as the value.
141
+ */
142
+ export function entriesToObject( entries ) {
143
+ const obj = {};
144
+
145
+ for ( const [ key, val ] of entries ) {
146
+ obj[ key ] = val;
147
+ }
148
+
149
+ return obj;
150
+ }
151
+
152
+ /**
153
+ * Convert a response object from @wordpress/apiFetch to an Error object.
154
+ *
155
+ * @param {Object} response
156
+ */
157
+ export default function responseToError( response ) {
158
+ if ( response instanceof Error ) {
159
+ throw response;
160
+ }
161
+
162
+ throw new ErrorResponse( response );
163
+ }
164
+
165
+ export const MYSTERY_MAN_AVATAR = 'https://secure.gravatar.com/avatar/d7a973c7dab26985da5f961be7b74480?s=96&d=mm&f=y&r=g';
core/packages/utils/src/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/utils/src/test/index.js ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { makeUrlRelative, shortenNumber } from '@ithemes/security-utils';
2
+
3
+ describe( 'makeUrlRelative', () => {
4
+ it( 'should return the relative url', () => {
5
+ const relative = makeUrlRelative( 'https://security.test/', 'https://security.test/wp-json/' );
6
+
7
+ expect( relative ).toEqual( '/wp-json/' );
8
+ } );
9
+
10
+ it( 'should return the relative url when base has a path', () => {
11
+ const relative = makeUrlRelative( 'https://security.test/wp-json/', 'https://security.test/wp-json/wp/v2/' );
12
+ expect( relative ).toEqual( '/wp/v2/' );
13
+ } );
14
+ } );
15
+
16
+ describe( 'shortenNumber', () => {
17
+ const cases = [
18
+ [ 5, '5' ],
19
+ [ 50, '50' ],
20
+ [ 53, '53' ],
21
+ [ 100, '100' ],
22
+ [ 152, '152' ],
23
+ [ 1000, '1k' ],
24
+ [ 1005, '1k' ],
25
+ [ 1025, '1k' ],
26
+ [ 1125, '1.1k' ],
27
+ [ 5232, '5.2k' ],
28
+ [ 12000, '12k' ],
29
+ [ 12345, '12k' ],
30
+ [ 123456, '123k' ],
31
+ [ 1000000, '1m' ],
32
+ [ 1234567, '1.2m' ],
33
+ [ 12345678, '12m' ],
34
+ [ 123456789, '123m' ],
35
+ [ 1234567890, '1.2b' ],
36
+ ];
37
+
38
+ it.each( cases )( 'should convert %d to %s', ( number, shortened ) => {
39
+ expect( shortenNumber( number ) ).toBe( shortened );
40
+ } );
41
+ } );
core/packages/utils/src/test/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/utils/src/wp-error.js ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default class WPError {
2
+ #errors = {};
3
+ #errorData = {};
4
+
5
+ /**
6
+ * WP Error object.
7
+ *
8
+ * Close to the WordPress PHP WP Error object. Really only meant to be used for interfacing
9
+ * with server generated PHP errors, not for JS programming.
10
+ *
11
+ * @param {string} [code]
12
+ * @param {string} [message]
13
+ * @param {*} [data]
14
+ */
15
+ constructor( code = undefined, message = undefined, data = undefined ) {
16
+ if ( ! code ) {
17
+ return;
18
+ }
19
+
20
+ if ( message ) {
21
+ this.#errors[ code ] = [ message ];
22
+ }
23
+
24
+ if ( data ) {
25
+ this.#errorData[ code ] = data;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Create a WPError from a PHP object.
31
+ *
32
+ * @param {Object} object WPError like object. {@see isWPError}
33
+ * @return {WPError} WPError instance.
34
+ */
35
+ static fromPHPObject( object ) {
36
+ const error = new WPError();
37
+ error.#errors = object.errors;
38
+ error.#errorData = object.error_data;
39
+
40
+ return error;
41
+ }
42
+
43
+ /**
44
+ * Create a WPError from a REST API error.
45
+ *
46
+ * @param {Object} object Api WP Error like object. {@see isApiError}
47
+ * @return {WPError} WPError instance.
48
+ */
49
+ static fromApiError( object ) {
50
+ const error = new WPError();
51
+ error.#errors[ object.code ] = [ object.message ];
52
+ error.#errorData[ object.code ] = object.data;
53
+
54
+ if ( object.additional_errors ) {
55
+ for ( const additional of object.additional_errors ) {
56
+ error.#errors[ additional.code ] = [ additional.message ];
57
+ error.#errorData[ additional.code ] = additional.data;
58
+ }
59
+ }
60
+
61
+ return error;
62
+ }
63
+
64
+ /**
65
+ * Get all the codes.
66
+ *
67
+ * @return {string[]} Array of error codes.
68
+ */
69
+ getErrorCodes = () => {
70
+ return Object.keys( this.#errors );
71
+ };
72
+
73
+ /**
74
+ *Get the main error code.
75
+ *
76
+ * @return {string|undefined} Primary error code or undefined if no errors.
77
+ */
78
+ getErrorCode = () => {
79
+ return this.getErrorCodes()[ 0 ];
80
+ };
81
+
82
+ /**
83
+ * Get all the error messages.
84
+ *
85
+ * @param {string} [code] Optionally limit to errors from a specific code.
86
+ * @return {Array<string>} Array of error messages.
87
+ */
88
+ getErrorMessages = ( code = undefined ) => {
89
+ if ( code ) {
90
+ return this.#errors[ code ];
91
+ }
92
+
93
+ const messages = [];
94
+
95
+ for ( const errorCode in this.#errors ) {
96
+ if ( this.#errors.hasOwnProperty( errorCode ) ) {
97
+ messages.concat( this.#errors[ errorCode ] );
98
+ }
99
+ }
100
+
101
+ return messages;
102
+ };
103
+
104
+ /**
105
+ * Get the error message.
106
+ *
107
+ * @param {string} [code] Optionally specify the code.
108
+ * @return {*|undefined} Primary error message.
109
+ */
110
+ getErrorMessage = ( code = undefined ) => {
111
+ code = code || this.getErrorCode();
112
+
113
+ return this.getErrorMessages( code )[ 0 ];
114
+ };
115
+
116
+ /**
117
+ * Get error data.
118
+ *
119
+ * @param {string} [code]
120
+ * @return {*|undefined} Error data for this code, or undefined.
121
+ */
122
+ getErrorData = ( code = undefined ) => {
123
+ code = code || this.getErrorCode();
124
+
125
+ return this.#errorData[ code ];
126
+ };
127
+ }
core/packages/webpack/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/webpack/src/babel.js ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ presets: [ '@wordpress/default' ],
3
+ plugins: [
4
+ [ '@wordpress/babel-plugin-import-jsx-pragma', {
5
+ scopeVariable: 'createElement',
6
+ source: '@wordpress/element',
7
+ isDefault: false,
8
+ } ],
9
+ [ '@babel/plugin-transform-react-jsx', {
10
+ pragma: 'createElement',
11
+ } ],
12
+ '@babel/plugin-proposal-class-properties',
13
+ '@babel/plugin-syntax-dynamic-import',
14
+ ],
15
+ };
core/packages/webpack/src/config/index.js ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const MiniCssExtractPlugin = require( 'mini-css-extract-plugin' );
2
+ const ManifestPlugin = require( 'webpack-manifest-plugin' );
3
+ const FilterWarningsPlugin = require( 'webpack-filter-warnings-plugin' );
4
+ const CustomTemplatedPathPlugin = require( '../custom-templated-path-webpack-plugin' );
5
+ const DynamicPublicPath = require( '../dynamic-public-path' );
6
+ const StyleOnlyEntryPlugin = require( '../style-only-entry-plugin' );
7
+ const SplitChunkName = require( '../split-chunk-name' );
8
+ const { generate: generateManifest, serialize: serializeManifest } = require( '../manifest' );
9
+ const wpExternals = require( '../wp-externals' );
10
+ const debug = process.env.NODE_ENV !== 'production';
11
+ const glob = require( 'glob' );
12
+ const path = require( 'path' );
13
+ const autoprefixer = require( 'autoprefixer' );
14
+ const webpack = require( 'webpack' );
15
+
16
+ module.exports = function makeConfig( directory, pro ) {
17
+ /*
18
+ Convert the wildcard entry points into an entry object suitable for Webpack consumption.
19
+
20
+ This requires an object where the key is the path to the destination file without a file extension
21
+ and the value is the path to the source file.
22
+
23
+ For Example:
24
+ [ 'pro/dashboard/entries/dashboard.js' ]
25
+ { 'dashboard/dashboard': './pro/dashboard/entries/dashboard.js' }
26
+ */
27
+ const entries = glob.sync( 'core/modules/**/entries/*.js' ).reduce( function( acc, entry ) {
28
+ const baseName = path.basename( entry, '.js' );
29
+ let out = path.join( entry, '..', '..', baseName );
30
+ out = out.replace( /^core\/modules\//, '' );
31
+
32
+ // The entry needs to be marked as relative to the current directory.
33
+ acc[ out ] = './' + entry;
34
+
35
+ return acc;
36
+ }, {} );
37
+
38
+ if ( pro ) {
39
+ Object.assign( entries, glob.sync( 'pro/**/entries/*.js' ).reduce( function( acc, entry ) {
40
+ const baseName = path.basename( entry, '.js' );
41
+ let out = path.join( entry, '..', '..', baseName );
42
+ out = out.replace( /^pro\//, '' );
43
+
44
+ acc[ out ] = './' + entry;
45
+
46
+ return acc;
47
+ }, {} ) );
48
+ }
49
+
50
+ const config = {
51
+ context: directory,
52
+ devtool: debug ? 'inline-sourcemap' : false,
53
+ mode: debug ? 'development' : 'production',
54
+ entry: {
55
+ ...entries,
56
+ 'core/packages/components/site-scan-results/style': './core/packages/components/src/site-scan-results/style.scss',
57
+ },
58
+ output: {
59
+ path: path.resolve( directory, 'dist' ),
60
+ filename: debug ? '[name].js' : '[name].min.js',
61
+ jsonpFunction: 'itsecWebpackJsonP',
62
+ library: [ 'itsec', '[module]', '[entry]' ],
63
+ libraryTarget: 'this',
64
+ },
65
+ externals: [
66
+ ...wpExternals,
67
+ function( context, request, callback ) {
68
+ if ( /^@ithemes\/security\./.test( request ) ) {
69
+ const parts = request.split( '.' );
70
+ const external = {
71
+ this: [ 'itsec', parts[ 1 ], parts[ 2 ] ],
72
+ };
73
+
74
+ callback( null, external, 'this' );
75
+ } else {
76
+ callback();
77
+ }
78
+ },
79
+ ],
80
+ module: {
81
+ rules: [
82
+ { parser: { amd: false } },
83
+ {
84
+ test: /\.js$/,
85
+ exclude: /node_modules/,
86
+ use: [
87
+ DynamicPublicPath.loader,
88
+ {
89
+ loader: 'babel-loader',
90
+ options: {
91
+ configFile: path.resolve( directory, './core/packages/webpack/src/babel.js' ),
92
+ },
93
+ },
94
+ ],
95
+ },
96
+ {
97
+ test: /\.s?css$/,
98
+ use: [
99
+ MiniCssExtractPlugin.loader,
100
+ {
101
+ loader: 'css-loader',
102
+ options: {
103
+ url: false,
104
+ },
105
+ },
106
+ {
107
+ loader: 'postcss-loader',
108
+ options: {
109
+ plugins: [
110
+ autoprefixer,
111
+ ],
112
+ },
113
+ },
114
+ {
115
+ loader: 'sass-loader',
116
+ options: {
117
+ outputStyle: debug ? 'nested' : 'compressed',
118
+ sourceMap: debug ? 'inline' : false,
119
+ includePaths: [
120
+ path.resolve( directory, './core/packages/style-guide/src' ),
121
+ ],
122
+ },
123
+ },
124
+ ],
125
+ },
126
+ {
127
+ test: /\.svg$/,
128
+ exclude: /node_modules/,
129
+ use: [
130
+ {
131
+ loader: 'svg-react-loader',
132
+ query: {
133
+ classIdPrefix: 'itsec-icon-[name]-[hash:5]__',
134
+ },
135
+ },
136
+ ],
137
+ },
138
+ ],
139
+ },
140
+ plugins: [
141
+ new MiniCssExtractPlugin( {
142
+ filename: debug ? '[name].css' : '[name].min.css',
143
+ } ),
144
+ new FilterWarningsPlugin( {
145
+ exclude: /mini-css-extract-plugin[^]*Conflicting order between:/,
146
+ } ),
147
+ new StyleOnlyEntryPlugin(),
148
+ new DynamicPublicPath( 'itsecWebpackPublicPath' ),
149
+ new CustomTemplatedPathPlugin( {
150
+ entry( p, data ) {
151
+ const parts = data.chunk.name.split( '/' );
152
+
153
+ return parts[ 1 ];
154
+ },
155
+ module( p, data ) {
156
+ const parts = data.chunk.name.split( '/' );
157
+
158
+ return parts[ 0 ];
159
+ },
160
+ } ),
161
+ new ManifestPlugin( {
162
+ fileName: debug ? 'manifest-dev.php' : 'manifest.php',
163
+ generate: generateManifest,
164
+ serialize: serializeManifest,
165
+ } ),
166
+ ],
167
+ resolve: {
168
+ modules: [
169
+ path.resolve( directory, './' ),
170
+ path.resolve( directory, './node_modules' ),
171
+ ],
172
+ alias: {
173
+ '@ithemes/security-utils': path.resolve( directory, './core/packages/utils/src/index.js' ),
174
+ '@ithemes/security-style-guide': path.resolve( directory, './core/packages/style-guide/src/index.js' ),
175
+ '@ithemes/security-hocs': path.resolve( directory, './core/packages/hocs/src/index.js' ),
176
+ '@ithemes/security-components': path.resolve( directory, './core/packages/components/src/index.js' ),
177
+ ...Object.keys( entries ).reduce( function( acc, entry ) {
178
+ const parts = entry.split( '/' );
179
+ const alias = `@ithemes/security.${ parts[ 0 ] }.${ parts[ 1 ] }`;
180
+
181
+ acc[ alias ] = path.resolve( directory, entries[ entry ] );
182
+
183
+ return acc;
184
+ }, {} ),
185
+ },
186
+ },
187
+ optimization: {},
188
+ };
189
+
190
+ if ( ! debug ) {
191
+ const splitChunkName = new SplitChunkName();
192
+
193
+ config.optimization.splitChunks = {
194
+ chunks: 'all',
195
+ maxInitialRequests: 10,
196
+ hidePathInfo: true,
197
+ cacheGroups: {
198
+ recharts: {
199
+ test: /[\\/]node_modules[\\/](recharts)[\\/]/,
200
+ name: 'vendors/recharts',
201
+ enforce: true,
202
+ },
203
+ },
204
+ name: splitChunkName.name.bind( splitChunkName ),
205
+ };
206
+
207
+ config.plugins.push( new webpack.HashedModuleIdsPlugin() );
208
+ }
209
+
210
+ return config;
211
+ };
core/packages/webpack/src/config/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/webpack/src/custom-templated-path-webpack-plugin/index.js ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * External dependencies
3
+ */
4
+ const escapeStringRegexp = require( 'escape-string-regexp' );
5
+
6
+ /**
7
+ * Webpack plugin for handling specific template tags in Webpack configuration
8
+ * values like those supported in the base Webpack functionality (e.g. `name`).
9
+ *
10
+ * @see webpack.TemplatedPathPlugin
11
+ */
12
+ class CustomTemplatedPathPlugin {
13
+ /**
14
+ * CustomTemplatedPathPlugin constructor. Initializes handlers as a tuple
15
+ * set of RegExp, handler, where the regular expression is used in matching
16
+ * a Webpack asset path.
17
+ *
18
+ * @param {Object.<string,Function>} handlers Object keyed by tag to match,
19
+ * with function value returning
20
+ * replacement string.
21
+ */
22
+ constructor( handlers ) {
23
+ this.handlers = [];
24
+
25
+ for ( const [ key, handler ] of Object.entries( handlers ) ) {
26
+ const regexp = new RegExp( `\\[${ escapeStringRegexp( key ) }\\]`, 'gi' );
27
+ this.handlers.push( [ regexp, handler ] );
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Webpack plugin application logic.
33
+ *
34
+ * @param {Object} compiler Webpack compiler
35
+ */
36
+ apply( compiler ) {
37
+ compiler.hooks.compilation.tap( 'CustomTemplatedPathPlugin', ( compilation ) => {
38
+ compilation.mainTemplate.hooks.assetPath.tap( 'CustomTemplatedPathPlugin', ( path, data ) => {
39
+ for ( let i = 0; i < this.handlers.length; i++ ) {
40
+ const [ regexp, handler ] = this.handlers[ i ];
41
+ if ( regexp.test( path ) ) {
42
+ path = path.replace( regexp, handler( path, data ) );
43
+ }
44
+ }
45
+
46
+ return path;
47
+ } );
48
+ } );
49
+ }
50
+ }
51
+
52
+ module.exports = CustomTemplatedPathPlugin;
core/packages/webpack/src/custom-templated-path-webpack-plugin/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/webpack/src/dynamic-public-path/index.js ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const path = require( 'path' );
2
+
3
+ function DynamicPublicPathPlugin( propertyName ) {
4
+ this.propertyName = propertyName;
5
+ }
6
+
7
+ DynamicPublicPathPlugin.prototype.apply = function( compiler ) {
8
+ compiler.hooks.thisCompilation.tap( 'dynamic-public-path', ( compilation ) => {
9
+ compilation.hooks.normalModuleLoader.tap( 'dynamic-public-path', ( loaderContext ) => {
10
+ const entryFiles = [];
11
+ Object.values( compiler.options.entry ).forEach( function( entry ) {
12
+ entryFiles.push( path.join( compiler.options.context, entry ) );
13
+ } );
14
+
15
+ loaderContext[ 'dynamic-public-path' ] = {
16
+ propertyName: this.propertyName,
17
+ entryFiles,
18
+ };
19
+ } );
20
+ } );
21
+ };
22
+
23
+ DynamicPublicPathPlugin.loader = require.resolve( './loader.js' );
24
+
25
+ module.exports = DynamicPublicPathPlugin;
core/packages/webpack/src/dynamic-public-path/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/webpack/src/dynamic-public-path/loader.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = function( source ) {
2
+ const config = this[ 'dynamic-public-path' ];
3
+ const resource = this.resource;
4
+
5
+ if ( config.entryFiles.includes( resource ) && resource.match( /\.js$/ ) ) {
6
+ source = `__webpack_public_path__ = window.${ config.propertyName };\n${ source }`;
7
+ }
8
+
9
+ return source;
10
+ };
core/packages/webpack/src/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/webpack/src/manifest/index.js ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const crypto = require( 'crypto' );
2
+ const path = require( 'path' );
3
+ const spawn = require( 'child_process' ).spawnSync;
4
+
5
+ function generate( seed, files ) {
6
+ const manifest = {};
7
+ const splitChunks = [];
8
+
9
+ for ( const file of files ) {
10
+ if ( ! file.chunk || ! file.chunk.name ) {
11
+ continue;
12
+ }
13
+
14
+ if ( ! manifest[ file.chunk.name ] ) {
15
+ manifest[ file.chunk.name ] = generateChunk( file.chunk );
16
+ }
17
+
18
+ manifest[ file.chunk.name ].files.push( file.name );
19
+
20
+ if ( ! file.chunk.hasRuntime() ) {
21
+ splitChunks.push( file );
22
+ }
23
+ }
24
+
25
+ for ( const file of splitChunks ) {
26
+ file.chunk.groupsIterable.forEach( ( group ) => {
27
+ if ( manifest[ group.name ] && ! manifest[ group.name ].vendors.includes( file.chunk.name ) ) {
28
+ manifest[ group.name ].vendors.push( file.chunk.name );
29
+ }
30
+ } );
31
+ }
32
+
33
+ return manifest;
34
+ }
35
+
36
+ /**
37
+ * Generate a chunk manifest entry.
38
+ *
39
+ * @param {Chunk} chunk
40
+ * @return {{runtime: boolean, vendors: Array, hash: string, dependencies: Array}} Manifest object.
41
+ */
42
+ function generateChunk( chunk ) {
43
+ const chunkManifest = {
44
+ runtime: chunk.hasRuntime(),
45
+ files: [],
46
+ hash: crypto.createHash( 'md4' ).update( JSON.stringify( chunk.contentHash ) ).digest( 'hex' ),
47
+ contentHash: chunk.contentHash,
48
+ vendors: [],
49
+ dependencies: [],
50
+ };
51
+
52
+ chunk.getModules().forEach( ( module ) => {
53
+ if ( module.external && module.userRequest ) {
54
+ if ( module.userRequest.includes( '@wordpress/' ) ) {
55
+ chunkManifest.dependencies.push( `wp-${ module.userRequest.replace( '@wordpress/', '' ) }` );
56
+ } else {
57
+ chunkManifest.dependencies.push( module.userRequest );
58
+ }
59
+ }
60
+ } );
61
+
62
+ chunkManifest.dependencies.sort();
63
+
64
+ return chunkManifest;
65
+ }
66
+
67
+ function serialize( data ) {
68
+ const out = spawn( 'php', [
69
+ path.resolve( __dirname, 'json-to-php.php' ),
70
+ JSON.stringify( data ),
71
+ ] );
72
+
73
+ if ( out.status !== 0 ) {
74
+ throw Error( 'Failed to generate PHP manifest.' );
75
+ }
76
+
77
+ return `<?php return ${ out.stdout };`;
78
+ }
79
+
80
+ module.exports = { generate, serialize };
core/packages/webpack/src/manifest/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/webpack/src/manifest/json-to-php.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( 'cli' !== php_sapi_name() ) {
4
+ die( 1 );
5
+ }
6
+
7
+ if ( ! isset( $argv[1] ) ) {
8
+ die( 1 );
9
+ }
10
+
11
+ $json = $argv[1];
12
+
13
+ if ( ! $decoded = json_decode( $json, true ) ) {
14
+ die( 1 );
15
+ }
16
+
17
+ ksort( $decoded );
18
+
19
+ fwrite( STDOUT, var_export( $decoded, true ) );
20
+ die( 0 );
core/packages/webpack/src/split-chunk-name/index.js ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const crypto = require( 'crypto' );
2
+
3
+ class SplitChunksName {
4
+ constructor() {
5
+ this.cache = new WeakMap();
6
+ }
7
+
8
+ name( module, chunks, cacheGroup ) {
9
+ const automaticNamePrefix = cacheGroup === 'vendors' ? 'vendors' : '',
10
+ automaticNameDelimiter = '~';
11
+
12
+ let cacheEntry = this.cache.get( chunks );
13
+ if ( cacheEntry === undefined ) {
14
+ cacheEntry = {};
15
+ this.cache.set( chunks, cacheEntry );
16
+ } else if ( cacheGroup in cacheEntry ) {
17
+ return cacheEntry[ cacheGroup ];
18
+ }
19
+ const names = chunks.filter( ( c ) => !! c.name ).map( ( c ) => c.name.split( '/' ) );
20
+ if ( ! names.length || ! names.every( Boolean ) ) {
21
+ cacheEntry[ cacheGroup ] = undefined;
22
+ return;
23
+ }
24
+ names.sort( ( a, b ) => b.length - a.length );
25
+ const prefix = typeof automaticNamePrefix === 'string' ? automaticNamePrefix : cacheGroup;
26
+ const namePrefix = prefix ? prefix + '/' : '';
27
+
28
+ /*
29
+ [
30
+ [ 'dashboard', 'dashboard' ]
31
+ [ 'dashboard', 'widget' ],
32
+ ]
33
+ -> 'dashboard/dashboard~widget'
34
+
35
+ [
36
+ [ 'dashboard', 'dashboard' ]
37
+ [ 'fingerprinting', 'manager' ],
38
+ ]
39
+ -> 'dist/dashboard-dist-dashboard~fingerprinting-dist-manager'
40
+ */
41
+
42
+ let name = '';
43
+ const max = names[ 0 ].length;
44
+
45
+ for ( let i = 0; i < max; i++ ) {
46
+ if ( this.allAtPosSame( names, i ) ) {
47
+ name += names[ 0 ][ i ] + '/';
48
+ } else {
49
+ name = '';
50
+ name += names[ 0 ].slice( 0, i ).join( '/' ) + '/';
51
+ name += names.map( ( parts ) => parts[ i ] ).filter( ( part ) => typeof part === 'string' ).join( automaticNameDelimiter ) + '/';
52
+ }
53
+ }
54
+
55
+ name = namePrefix + name;
56
+
57
+ if ( '/' === name.charAt( name.length - 1 ) ) {
58
+ name = name.substring( 0, name.length - 1 );
59
+ }
60
+
61
+ if ( name.length > 100 ) {
62
+ name = name.slice( 0, 100 ) + automaticNameDelimiter + this.hashFilename( name );
63
+ }
64
+
65
+ cacheEntry[ cacheGroup ] = name;
66
+ return name;
67
+ }
68
+
69
+ allAtPosSame( list, pos ) {
70
+ let prev = null;
71
+
72
+ for ( const item of list ) {
73
+ if ( prev === null ) {
74
+ prev = item[ pos ];
75
+ } else if ( prev !== item[ pos ] ) {
76
+ return false;
77
+ }
78
+ }
79
+
80
+ return true;
81
+ }
82
+
83
+ hashFilename( name ) {
84
+ return crypto
85
+ .createHash( 'md4' )
86
+ .update( name )
87
+ .digest( 'hex' )
88
+ .slice( 0, 8 );
89
+ }
90
+ }
91
+
92
+ module.exports = SplitChunksName;
core/packages/webpack/src/split-chunk-name/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/webpack/src/style-only-entry-plugin/index.js ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function StyleOnlyEntryPlugin( styleTests ) {
2
+ let list = [];
3
+
4
+ if ( styleTests instanceof RegExp ) {
5
+ list.push( styleTests );
6
+ } else if ( Array.isArray( styleTests ) && styleTests.length ) {
7
+ list = styleTests;
8
+ } else {
9
+ list = [ /\.s?css$/ ];
10
+ }
11
+
12
+ Object.assign( this, {
13
+ styleTests: list,
14
+ } );
15
+ }
16
+
17
+ StyleOnlyEntryPlugin.prototype.isFileStyle = function( file ) {
18
+ for ( const test of this.styleTests ) {
19
+ if ( test.test( file ) ) {
20
+ return true;
21
+ }
22
+ }
23
+
24
+ return false;
25
+ };
26
+
27
+ StyleOnlyEntryPlugin.prototype.apply = function( compiler ) {
28
+ compiler.hooks.emit.tap( 'style-only-entry-plugin', ( compilation ) => {
29
+ for ( const chunk of compilation.chunks ) {
30
+ if ( chunk.entryModule && this.isFileStyle( chunk.entryModule.userRequest ) ) {
31
+ for ( const file of chunk.files ) {
32
+ if ( ! this.isFileStyle( file ) ) {
33
+ delete compilation.assets[ file ];
34
+ }
35
+ }
36
+ }
37
+ }
38
+ } );
39
+ };
40
+
41
+ module.exports = StyleOnlyEntryPlugin;
core/packages/webpack/src/style-only-entry-plugin/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/packages/webpack/src/wp-externals/index.js ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function camelCaseDash( string ) {
2
+ return string.replace(
3
+ /-([a-z])/g,
4
+ ( match, letter ) => letter.toUpperCase(),
5
+ );
6
+ }
7
+
8
+ const formatRequest = ( request ) => {
9
+ // '@wordpress/api-fetch' -> [ '@wordpress', 'api-fetch' ]
10
+ const [ , name ] = request.split( '/' );
11
+
12
+ // { this: [ 'wp', 'apiFetch' ] }
13
+ return {
14
+ this: [ 'wp', camelCaseDash( name ) ],
15
+ };
16
+ };
17
+
18
+ const wpPackages = ( context, request, callback ) => {
19
+ if ( /^@wordpress\//.test( request ) ) {
20
+ callback( null, formatRequest( request ), 'this' );
21
+ } else {
22
+ callback();
23
+ }
24
+ };
25
+
26
+ const externals = Object.freeze( [
27
+ {
28
+ react: 'React',
29
+ 'react-dom': 'ReactDOM',
30
+ tinymce: 'tinymce',
31
+ moment: 'moment',
32
+ jquery: 'jQuery',
33
+ lodash: 'lodash',
34
+ 'lodash-es': 'lodash',
35
+ },
36
+ wpPackages,
37
+ ] );
38
+
39
+ module.exports = externals;
core/packages/webpack/src/wp-externals/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
core/setup.php CHANGED
@@ -107,8 +107,21 @@ final class ITSEC_Setup {
107
  self::upgrade_data_to_4031();
108
  }
109
 
 
 
 
 
110
  if ( $build < 4069 ) {
111
  self::upgrade_data_to_4069();
 
 
 
 
 
 
 
 
 
112
  }
113
 
114
  // Run upgrade routines for modules to ensure that they are up-to-date.
107
  self::upgrade_data_to_4031();
108
  }
109
 
110
+ if ( $build < 4067 ) {
111
+ delete_site_option( 'itsec_pro_just_activated' );
112
+ }
113
+
114
  if ( $build < 4069 ) {
115
  self::upgrade_data_to_4069();
116
+ delete_site_option( 'itsec_free_just_activated' );
117
+ }
118
+
119
+ if ( $build < 4076 ) {
120
+ $digest = wp_next_scheduled( 'itsec_digest_email' );
121
+
122
+ if ( $digest ) {
123
+ wp_unschedule_event( $digest, 'itsec_digest_email' );
124
+ }
125
  }
126
 
127
  // Run upgrade routines for modules to ensure that they are up-to-date.
dist/core/admin-notices-api.min.js ADDED
@@ -0,0 +1 @@
 
1
+ this.itsec=this.itsec||{},this.itsec.core=this.itsec.core||{},this.itsec.core["admin-notices-api"]=function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s="M/oR")}({"1ZqX":function(t,e){!function(){t.exports=this.wp.data}()},"7W2i":function(t,e,n){var r=n("SksO");t.exports=function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),e&&r(t,e)}},"92Nh":function(t,e){t.exports=function(t,e,n){if(!e.has(t))throw new TypeError("attempted to set private field on non-instance");var r=e.get(t);if(!r.writable)throw new TypeError("attempted to set read only private field");return r.value=n,n}},Bnag:function(t,e){t.exports=function(){throw new TypeError("Invalid attempt to spread non-iterable instance")}},EbDI:function(t,e){t.exports=function(t){if(Symbol.iterator in Object(t)||"[object Arguments]"===Object.prototype.toString.call(t))return Array.from(t)}},Ijbi:function(t,e){t.exports=function(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e<t.length;e++)n[e]=t[e];return n}}},J4zp:function(t,e,n){var r=n("wTVA"),o=n("m0LI"),i=n("wkBT");t.exports=function(t,e){return r(t)||o(t,e)||i()}},"M/oR":function(t,e,n){"use strict";n.r(e);var r={};n.r(r),n.d(r,"receiveNotices",function(){return U}),n.d(r,"startNoticeAction",function(){return C}),n.d(r,"finishNoticeAction",function(){return F}),n.d(r,"failedNoticeAction",function(){return L}),n.d(r,"receiveMutedHighlights",function(){return G}),n.d(r,"startUpdateMutedHighlight",function(){return J}),n.d(r,"finishUpdateMutedHighlight",function(){return V}),n.d(r,"failedUpdateMutedHighlight",function(){return q}),n.d(r,"doNoticeAction",function(){return W}),n.d(r,"updateMutedHighlight",function(){return Z}),n.d(r,"RECEIVE_NOTICES",function(){return B}),n.d(r,"START_NOTICE_ACTION",function(){return Y}),n.d(r,"FINISH_NOTICE_ACTION",function(){return X}),n.d(r,"FAILED_NOTICE_ACTION",function(){return z}),n.d(r,"RECEIVE_MUTED_HIGHLIGHTS",function(){return K}),n.d(r,"START_UPDATE_MUTED_HIGHLIGHT",function(){return Q}),n.d(r,"FINISH_UPDATE_MUTED_HIGHLIGHT",function(){return $}),n.d(r,"FAILED_UPDATE_MUTED_HIGHLIGHT",function(){return tt});var o={};n.r(o),n.d(o,"isResolving",function(){return et}),n.d(o,"isResolved",function(){return nt}),n.d(o,"getNotices",function(){return rt}),n.d(o,"areNoticesLoaded",function(){return ot}),n.d(o,"isDoingAction",function(){return it}),n.d(o,"getInProgressActions",function(){return ct}),n.d(o,"getMutedHighlights",function(){return st}),n.d(o,"getMutedHighlightUpdatesInFlight",function(){return at});var i={};n.r(i),n.d(i,"getNotices",function(){return pt}),n.d(i,"getMutedHighlights",function(){return dt});var u=n("1ZqX"),c=n("RIqP"),s=n.n(c),a=n("MVZn"),f=n.n(a),l=n("YLtl"),p=n("ywyh"),d=n.n(p),h=(n("J4zp"),n("lwsE")),g=n.n(h),y=n("W8MJ"),m=n.n(y),b=n("lSNA"),v=n.n(b),I=(n("92Nh"),n("tmk3"),new WeakMap,new WeakMap,n("a1gu")),T=n.n(I),x=n("Nsbk"),w=n.n(x),E=n("7W2i"),_=n.n(E),O=n("PJYZ"),A=n.n(O),S=n("oShl"),H=n.n(S),N=n("l3Sj"),j=function(t){function e(t){var n,r;g()(this,e);for(var o=arguments.length,i=new Array(o>1?o-1:0),u=1;u<o;u++)i[u-1]=arguments[u];for(var c in r=T()(this,(n=w()(e)).call.apply(n,[this,t.message||Object(N.__)("An unknown error occurred.","better-wp-security")].concat(i))),Error.captureStackTrace&&Error.captureStackTrace(A()(A()(r)),e),r.__response=t,t)t.hasOwnProperty(c)&&Object.defineProperty(A()(A()(r)),c,{value:t[c],configurable:!0,enumerable:!0,writable:!0});return r}return _()(e,t),m()(e,[{key:"toString",value:function(){return this.__response.toString()}},{key:"getResponse",value:function(){return this.__response}}]),e}(H()(Error));function P(t){if(t instanceof Error)throw t;throw new j(t)}function R(t){return{type:"API_FETCH",request:t}}var M={API_FETCH:function(t){var e=t.request;return d()(e).catch(P)},SELECT:function(t){var e,n=t.selectorName,r=t.args;return(e=Object(u.select)("ithemes-security/admin-notices"))[n].apply(e,s()(r))},CREATE_NOTICE:function(t){var e=t.status,n=t.content,r=t.options;r.autoDismiss&&(r.id=r.id||Object(l.uniqueId)("itsec-auto-dismiss-"),setTimeout(function(){return Object(u.dispatch)("core/notices").removeNotice(r.id,r.context)},r.autoDismiss)),Object(u.dispatch)("core/notices").createNotice(e,n,r)}},D=regeneratorRuntime.mark(W),k=regeneratorRuntime.mark(Z);function U(t){return{type:B,notices:t}}function C(t,e){return{type:Y,noticeId:t,actionId:e}}function F(t,e,n){return{type:X,noticeId:t,actionId:e,response:n}}function L(t,e,n){return{type:z,noticeId:t,actionId:e,error:n}}function G(t){return{type:K,mutedHighlights:t}}function J(t,e){return{type:Q,slug:t,mute:e}}function V(t,e){return{type:$,slug:t,mute:e}}function q(t,e,n){return{type:tt,slug:t,mute:e,error:n}}function W(t,e){var n,r,o=arguments;return regeneratorRuntime.wrap(function(i){for(;;)switch(i.prev=i.next){case 0:return n=o.length>2&&void 0!==o[2]?o[2]:{},i.next=3,C(t,e);case 3:return i.prev=3,i.next=6,R({path:"/ithemes-security/v1/admin-notices/".concat(t,"/").concat(e),method:"POST",data:n});case 6:r=i.sent,i.next=14;break;case 9:return i.prev=9,i.t0=i.catch(3),i.next=13,L(t,e,i.t0);case 13:return i.abrupt("return",i.t0);case 14:return i.next=16,F(t,e,r);case 16:return i.abrupt("return",r);case 17:case"end":return i.stop()}},D,this,[[3,9]])}function Z(t,e){var n;return regeneratorRuntime.wrap(function(r){for(;;)switch(r.prev=r.next){case 0:return r.next=2,J(t,e);case 2:return r.prev=2,r.next=5,R({path:"/ithemes-security/v1/admin-notices/settings",method:"PUT",data:{muted_highlights:v()({},t,e)}});case 5:n=r.sent,r.next=13;break;case 8:return r.prev=8,r.t0=r.catch(2),r.next=12,q(t,e,r.t0);case 12:return r.abrupt("return",r.t0);case 13:return r.next=15,V(t,e);case 15:return r.abrupt("return",n);case 16:case"end":return r.stop()}},k,this,[[2,8]])}var B="RECEIVE_NOTICES",Y="START_NOTICE_ACTION",X="FINISH_NOTICE_ACTION",z="FAILED_NOTICE_ACTION",K="RECEIVE_MUTED_HIGHLIGHTS",Q="START_UPDATE_MUTED_HIGHLIGHT",$="FINISH_UPDATE_MUTED_HIGHLIGHT",tt="FAILED_UPDATE_MUTED_HIGHLIGHT";function et(t){for(var e=arguments.length,n=new Array(e>1?e-1:0),r=1;r<e;r++)n[r-1]=arguments[r];return Object(u.select)("core/data").isResolving("ithemes-security/admin-notices",t,n)}function nt(t){for(var e=arguments.length,n=new Array(e>1?e-1:0),r=1;r<e;r++)n[r-1]=arguments[r];return Object(u.select)("core/data").hasFinishedResolution("ithemes-security/admin-notices",t,n)}function rt(t){return t.notices}function ot(){return nt("getNotices")}function it(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";return!!t.doingActions[e]&&(""===n||t.doingActions[e].includes(n))}var ut=[];function ct(t,e){return t.doingActions[e]||ut}function st(t){return t.mutedHighlights}function at(t){return t.mutedHighlightUpdatesInFlight}var ft={notices:[],doingActions:{},mutedHighlights:{},mutedHighlightUpdatesInFlight:{}};var lt=regeneratorRuntime.mark(dt),pt={fulfill:regeneratorRuntime.mark(function t(){var e;return regeneratorRuntime.wrap(function(t){for(;;)switch(t.prev=t.next){case 0:return t.next=2,R({path:"/ithemes-security/v1/admin-notices"});case 2:return e=t.sent,t.next=5,U(e);case 5:case"end":return t.stop()}},t,this)}),shouldInvalidate:function(t){return t.type===X||t.type===$}};function dt(){var t;return regeneratorRuntime.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,R({path:"/ithemes-security/v1/admin-notices/settings"});case 2:return t=e.sent,e.next=5,G(Object(l.isEmpty)(t.muted_highlights)?{}:t.muted_highlights);case 5:case"end":return e.stop()}},lt,this)}Object(u.registerStore)("ithemes-security/admin-notices",{controls:M,actions:r,selectors:o,resolvers:i,reducer:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:ft,e=arguments.length>1?arguments[1]:void 0;switch(e.type){case B:return f()({},t,{notices:s()(e.notices)});case Y:return f()({},t,{doingActions:f()({},t.doingActions,v()({},e.noticeId,s()(t.doingActions[e.noticeId]||[]).concat([e.actionId])))});case X:case z:return f()({},t,{doingActions:f()({},t.doingActions,v()({},e.noticeId,(t.doingActions[e.noticeId]||[]).filter(function(t){return t!==e.actionId})))});case K:return f()({},t,{mutedHighlights:e.mutedHighlights});case Q:return f()({},t,{mutedHighlightUpdatesInFlight:f()({},t.mutedHighlightUpdatesInFlight,v()({},e.slug,{mute:e.mute}))});case $:return f()({},t,{mutedHighlightUpdatesInFlight:Object(l.omit)(t.mutedHighlightUpdatesInFlight,e.slug),mutedHighlights:f()({},t.mutedHighlights,v()({},e.slug,e.mute))});case tt:return f()({},t,{mutedHighlightUpdatesInFlight:Object(l.omit)(t.mutedHighlightUpdatesInFlight,e.slug)});default:return t}}});n.p=window.itsecWebpackPublicPath},MVZn:function(t,e,n){var r=n("lSNA");t.exports=function(t){for(var e=1;e<arguments.length;e++){var n=null!=arguments[e]?arguments[e]:{},o=Object.keys(n);"function"==typeof Object.getOwnPropertySymbols&&(o=o.concat(Object.getOwnPropertySymbols(n).filter(function(t){return Object.getOwnPropertyDescriptor(n,t).enumerable}))),o.forEach(function(e){r(t,e,n[e])})}return t}},Nsbk:function(t,e){function n(e){return t.exports=n=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)},n(e)}t.exports=n},PJYZ:function(t,e){t.exports=function(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}},RIqP:function(t,e,n){var r=n("Ijbi"),o=n("EbDI"),i=n("Bnag");t.exports=function(t){return r(t)||o(t)||i()}},SksO:function(t,e){function n(e,r){return t.exports=n=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t},n(e,r)}t.exports=n},W8MJ:function(t,e){function n(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}t.exports=function(t,e,r){return e&&n(t.prototype,e),r&&n(t,r),t}},YLtl:function(t,e){!function(){t.exports=this.lodash}()},a1gu:function(t,e,n){var r=n("cDf5"),o=n("PJYZ");t.exports=function(t,e){return!e||"object"!==r(e)&&"function"!=typeof e?o(t):e}},cDf5:function(t,e){function n(t){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function r(e){return"function"==typeof Symbol&&"symbol"===n(Symbol.iterator)?t.exports=r=function(t){return n(t)}:t.exports=r=function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":n(t)},r(e)}t.exports=r},l3Sj:function(t,e){!function(){t.exports=this.wp.i18n}()},lSNA:function(t,e){t.exports=function(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}},lwsE:function(t,e){t.exports=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}},m0LI:function(t,e){t.exports=function(t,e){var n=[],r=!0,o=!1,i=void 0;try{for(var u,c=t[Symbol.iterator]();!(r=(u=c.next()).done)&&(n.push(u.value),!e||n.length!==e);r=!0);}catch(t){o=!0,i=t}finally{try{r||null==c.return||c.return()}finally{if(o)throw i}}return n}},oShl:function(t,e,n){var r=n("Nsbk"),o=n("SksO"),i=n("xfeJ"),u=n("sXyB");function c(e){var n="function"==typeof Map?new Map:void 0;return t.exports=c=function(t){if(null===t||!i(t))return t;if("function"!=typeof t)throw new TypeError("Super expression must either be null or a function");if(void 0!==n){if(n.has(t))return n.get(t);n.set(t,e)}function e(){return u(t,arguments,r(this).constructor)}return e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),o(e,t)},c(e)}t.exports=c},sXyB:function(t,e,n){var r=n("SksO");function o(e,n,i){return!function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(t){return!1}}()?t.exports=o=function(t,e,n){var o=[null];o.push.apply(o,e);var i=new(Function.bind.apply(t,o));return n&&r(i,n.prototype),i}:t.exports=o=Reflect.construct,o.apply(null,arguments)}t.exports=o},tmk3:function(t,e){t.exports=function(t,e){if(!e.has(t))throw new TypeError("attempted to get private field on non-instance");return e.get(t).value}},wTVA:function(t,e){t.exports=function(t){if(Array.isArray(t))return t}},wkBT:function(t,e){t.exports=function(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}},xfeJ:function(t,e){t.exports=function(t){return-1!==Function.toString.call(t).indexOf("[native code]")}},ywyh:function(t,e){!function(){t.exports=this.wp.apiFetch}()}});
dist/core/admin-notices-dashboard-admin-bar.min.css ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ .itsec-admin-notice{background:white;border-radius:3px;box-shadow:0 0 5px rgba(211,211,211,0.35)}.itsec-admin-notice__header{background:#eeecec;padding:10px;border-top-left-radius:3px;border-top-right-radius:3px}.itsec-admin-notice__header .components-button{margin-top:1em;padding:.5em 1em;background:#E1f2fc;color:#0081E3;border:1px solid #0081E3;transition:background-color 300ms, color 150ms}.itsec-admin-notice__header .components-button:hover{background:#0081E3;color:white}.itsec-admin-notice__header .components-button.is-busy{animation:components-button__busy-animation 2500ms infinite linear;background-size:100px 100%;background-image:repeating-linear-gradient(-45deg, #E1f2fc, #fff 11px, #fff 10px, #E1f2fc 20px);opacity:1}.itsec-admin-notice__header .components-button.is-busy:hover{color:#0081E3}.itsec-admin-notice--severity-error .itsec-admin-notice__header{background:#d94f4f}.itsec-admin-notice--severity-error .itsec-admin-notice__header .components-button{padding:.5em 1em;background:#fcefef;color:#d94f4f;border:1px solid #d94f4f;transition:background-color 300ms, color 150ms}.itsec-admin-notice--severity-error .itsec-admin-notice__header .components-button:hover{background:#d94f4f;color:white}.itsec-admin-notice--severity-error .itsec-admin-notice__header .components-button.is-busy{animation:components-button__busy-animation 2500ms infinite linear;background-size:100px 100%;background-image:repeating-linear-gradient(-45deg, #fcefef, #fff 11px, #fff 10px, #fcefef 20px);opacity:1}.itsec-admin-notice--severity-error .itsec-admin-notice__header .components-button.is-busy:hover{color:#d94f4f}.itsec-admin-notice--severity-warning .itsec-admin-notice__header{background:#f78b53}.itsec-admin-notice--severity-warning .itsec-admin-notice__header .components-button{padding:.5em 1em;background:#fef1ea;color:#f78b53;border:1px solid #f78b53;transition:background-color 300ms, color 150ms}.itsec-admin-notice--severity-warning .itsec-admin-notice__header .components-button:hover{background:#f78b53;color:white}.itsec-admin-notice--severity-warning .itsec-admin-notice__header .components-button.is-busy{animation:components-button__busy-animation 2500ms infinite linear;background-size:100px 100%;background-image:repeating-linear-gradient(-45deg, #fef1ea, #fff 11px, #fff 10px, #fef1ea 20px);opacity:1}.itsec-admin-notice--severity-warning .itsec-admin-notice__header .components-button.is-busy:hover{color:#f78b53}.itsec-admin-notice--severity-info .itsec-admin-notice__header{background:#00a0d2}.itsec-admin-notice--severity-success .itsec-admin-notice__header{background:#4ab866}.itsec-admin-notice--severity-success .itsec-admin-notice__header .components-button{padding:.5em 1em;background:#e4f4e8;color:#4ab866;border:1px solid #4ab866;transition:background-color 300ms, color 150ms}.itsec-admin-notice--severity-success .itsec-admin-notice__header .components-button:hover{background:#4ab866;color:white}.itsec-admin-notice--severity-success .itsec-admin-notice__header .components-button.is-busy{animation:components-button__busy-animation 2500ms infinite linear;background-size:100px 100%;background-image:repeating-linear-gradient(-45deg, #e4f4e8, #fff 11px, #fff 10px, #e4f4e8 20px);opacity:1}.itsec-admin-notice--severity-success .itsec-admin-notice__header .components-button.is-busy:hover{color:#4ab866}.itsec-admin-notice .itsec-admin-notice__header:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.itsec-admin-notice__header .itsec-admin-notice__header-inset{background:white;border-radius:5px;padding:10px}.itsec-admin-notice__header h4{font-size:14px;margin:0}.itsec-admin-notice__message{padding:10px 20px}.itsec-admin-notice__message p:first-child{margin-top:0}.itsec-admin-notice__message p:last-child{margin-bottom:0}.itsec-admin-notice__meta{background:#eeecec;padding:5px 20px;margin:0;display:grid;grid-template:auto / -webkit-min-content 1fr;grid-template:auto / min-content 1fr;grid-gap:5px 20px}.itsec-admin-notice__meta dt,.itsec-admin-notice__meta dd{margin:0}.itsec-admin-notice__meta dt{text-transform:uppercase;color:#040404}.itsec-admin-notice__footer{padding:5px 20px;font-size:11px}
2
+
3
+ .itsec-admin-notice-list-actions .components-icon-button{padding:2px !important}.itsec-admin-notice-list-actions .components-icon-button:hover{background-color:#c6d1da !important;box-shadow:none !important}.itsec-admin-notice-list-actions__more-menu-items .components-popover__content>div{overflow:hidden;padding:7px 0}.itsec-admin-notice-list-actions__more-menu-items .components-button{width:100%;min-height:38px;box-sizing:border-box;padding:10px;color:#0073aa;justify-content:space-between;align-items:center}.itsec-admin-notice-list-actions__more-menu-items .components-button:focus{outline-offset:-2px;outline:1px dotted #555d66;box-shadow:none}.itsec-admin-notice-list-actions__more-menu-items .components-button:hover{color:#191e23;background:#f3f4f5}.itsec-admin-notice-list-actions__more-menu-items .components-button .components-spinner{margin:0}
4
+
5
+ .itsec-admin-notice-list{margin:0}.itsec-admin-notice-list-item-container{display:grid;grid-template:auto / -webkit-min-content 1fr;grid-template:auto / min-content 1fr;grid-gap:1em;margin-bottom:2em}.itsec-admin-notice-list-item-container:last-child{margin-bottom:0}
6
+
7
+ @keyframes itsec-animation-fade-in-constant{from{opacity:0}to{opacity:1}}.itsec-admin-bar-admin-notices__content.components-popover::after{border-color:#E5EAEE !important}.itsec-admin-notice-panel{padding:1em;background:#E5EAEE;width:420px;box-sizing:border-box}.itsec-admin-notice-panel .itsec-admin-notice-panel__header{border-bottom:1px solid #7ABEED;margin-bottom:1em}.itsec-admin-notice-panel .itsec-admin-notice-panel__header h3{color:#0081E3;text-align:center}.itsec-admin-notice-panel .itsec-admin-notice-panel__header p{text-align:center;font-style:oblique}.is-mobile .itsec-admin-notice-panel{width:100%;overflow-x:auto;height:100%}.is-mobile .itsec-admin-notice-panel header h3{display:none}.is-mobile .itsec-admin-notice-panel header p{margin-top:0}.itsec-admin-notice-panel .itsec-admin-notice-panel__configure-trigger.components-icon-button{padding:5px;height:30px;position:absolute;right:1em;top:1em;transition:opacity 400ms}.itsec-admin-notice-panel .itsec-admin-notice-panel__configure-trigger.components-icon-button:hover{opacity:.5;box-shadow:none !important}.itsec-admin-notice-panel.itsec-admin-notice-panel--is-configuring .itsec-admin-notice-panel__configure-trigger.components-icon-button{color:#0081E3}.itsec-admin-notice-panel.itsec-admin-notice-panel--is-configuring .itsec-admin-notice-panel__configure-trigger.components-icon-button:hover{color:#0081E3}.itsec-admin-notice-panel .itsec-admin-notice-panel__configure-highlighted-logs{border-bottom:1px solid #7ABEED;margin-bottom:1em;padding-bottom:1em}.itsec-admin-notice-panel .itsec-admin-notice-panel__configure-highlighted-logs li{display:flex;justify-content:space-between;border-bottom:1px solid #ccc;margin-bottom:1em;padding-bottom:1em;padding-top:0;margin-top:0}.itsec-admin-notice-panel .itsec-admin-notice-panel__configure-highlighted-logs li:last-child{border-bottom:none;margin-bottom:0;padding-bottom:0}.itsec-admin-notice-panel .itsec-admin-notice-panel__configure-highlighted-logs li label{margin-left:calc(24px + 1em);font-size:14px;font-weight:bold}.itsec-admin-notice-panel .itsec-admin-notice-panel__configure-highlighted-logs li .components-form-toggle.is-checked .components-form-toggle__track{background-color:#0081E3;border-color:#0081E3}.itsec-admin-notice-panel .itsec-admin-notice-panel__configure-highlighted-logs li .components-form-toggle__input:disabled+.components-form-toggle__track{opacity:.5}
8
+
9
+ .itsec-admin-bar__admin-notices{margin-left:auto}.itsec-admin-bar__admin-notices .itsec-admin-bar-admin-notices__trigger>.components-button{color:#0081E3}.itsec-admin-bar__admin-notices .itsec-admin-bar-admin-notices__trigger>.components-button:hover{color:#4ab1ff}.itsec-admin-bar__admin-notices .itsec-admin-bar-admin-notices__trigger>.components-button svg{margin-right:.5em}.itsec-admin-bar__admin-notices .itsec-admin-bar-admin-notices__trigger .components-button{position:relative}.itsec-admin-bar__admin-notices .itsec-admin-bar-admin-notices__trigger .components-button::before{content:'';background:#d8514f;height:5px;width:5px;border-radius:5px;vertical-align:middle;border:1px solid #f1f1f1;z-index:1;position:absolute;left:4px;top:3px;opacity:0;transition:opacity 1000ms ease-in-out}.itsec-admin-bar__admin-notices .itsec-admin-bar-admin-notices__trigger--has-notices .components-button::before{opacity:1}.itsec-admin-bar-admin-notices__content .components-popover__content{box-shadow:rgba(0,0,0,0.19) 0 4px 5px}@media screen and (max-width: 600px){.itsec-admin-bar .itsec-admin-bar__admin-notices{margin-left:0}}
10
+
dist/core/admin-notices-dashboard-admin-bar.min.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ this.itsec=this.itsec||{},this.itsec.core=this.itsec.core||{},this.itsec.core["admin-notices-dashboard-admin-bar"]=function(e){var t={};function n(i){if(t[i])return t[i].exports;var c=t[i]={i:i,l:!1,exports:{}};return e[i].call(c.exports,c,c.exports,n),c.l=!0,c.exports}return n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var c in e)n.d(i,c,function(t){return e[t]}.bind(null,c));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s="Pesb")}({"1ZqX":function(e,t){!function(){e.exports=this.wp.data}()},CDPQ:function(e,t,n){},Ckse:function(e,t,n){},GRId:function(e,t){!function(){e.exports=this.wp.element}()},Hz6T:function(e,t){!function(){e.exports=this.itsec.core["admin-notices-api"]}()},K9lf:function(e,t){!function(){e.exports=this.wp.compose}()},KTI5:function(e,t,n){},Pesb:function(e,t,n){"use strict";n.r(t);var i=n("TvNi"),c=(n("Hz6T"),n("GRId")),r=n("TSYQ"),a=n.n(r),o=n("tI+e"),s=n("l3Sj"),l=n("K9lf"),u=n("1ZqX"),m=n("cruf"),d=n("vYuV"),f=n("rE5z");n("CDPQ");var b=Object(l.compose)([Object(u.withSelect)(function(e){return{notices:e("ithemes-security/admin-notices").getNotices(),noticesLoaded:e("ithemes-security/admin-notices").areNoticesLoaded()}}),Object(l.withState)({isToggled:!1})])(function(e){var t=e.notices,n=e.noticesLoaded,i=e.isToggled,r=e.setState;return Object(c.createElement)(m.AdminBarFill,null,Object(c.createElement)("div",{className:"itsec-admin-bar__admin-notices"},Object(c.createElement)("div",{className:a()("itsec-admin-bar-admin-notices__trigger",{"itsec-admin-bar-admin-notices__trigger--has-notices":t.length>0})},Object(c.createElement)(o.Button,{"aria-expanded":i,onClick:function(){return r({isToggled:!i})}},Object(c.createElement)(o.Dashicon,{icon:"megaphone",size:15}),Object(s.__)("Notifications","better-wp-security")),i&&Object(c.createElement)(o.Popover,{className:"itsec-admin-bar-admin-notices__content",expandOnMobile:!0,focusOnMount:"container",position:"bottom left",headerTitle:Object(s.__)("Notifications","better-wp-security"),onClose:function(){return r({isToggled:!1})},onClickOutside:function(e){"itsec-admin-notices-toolbar-trigger"===e.target.id||"itsec-admin-notices-toolbar-trigger"===e.target.parentNode.id||Object(d.a)(e.target)||r({isToggled:!1})}},Object(c.createElement)(f.a,{notices:t,loaded:n,close:function(){return r({isToggled:!1})}})))))});n.p=window.itsecWebpackPublicPath,Object(i.registerPlugin)("itsec-admin-notices-dashboard-admin-bar",{render:b})},TSYQ:function(e,t,n){
2
+ /*!
3
+ Copyright (c) 2017 Jed Watson.
4
+ Licensed under the MIT License (MIT), see
5
+ http://jedwatson.github.io/classnames
6
+ */
7
+ !function(){"use strict";var t={}.hasOwnProperty;function n(){for(var e=[],i=0;i<arguments.length;i++){var c=arguments[i];if(c){var r=typeof c;if("string"===r||"number"===r)e.push(c);else if(Array.isArray(c)&&c.length){var a=n.apply(null,c);a&&e.push(a)}else if("object"===r)for(var o in c)t.call(c,o)&&c[o]&&e.push(o)}}return e.join(" ")}e.exports?(n.default=n,e.exports=n):"function"==typeof define&&"object"==typeof define.amd&&define.amd?define("classnames",[],function(){return n}):window.classNames=n}()},TvNi:function(e,t){!function(){e.exports=this.wp.plugins}()},UuzZ:function(e,t){!function(){e.exports=this.wp.autop}()},VwaH:function(e,t,n){},YLtl:function(e,t){!function(){e.exports=this.lodash}()},cruf:function(e,t){!function(){e.exports=this.itsec.dashboard.api}()},l3Sj:function(e,t){!function(){e.exports=this.wp.i18n}()},"o/Pk":function(e,t,n){},rE5z:function(e,t,n){"use strict";var i=n("GRId"),c=n("TSYQ"),r=n.n(c),a=n("YLtl"),o=n("l3Sj"),s=n("tI+e"),l=n("K9lf"),u=n("1ZqX"),m=n("UuzZ");n("VwaH");function d(e){var t=e.notice;return Object(i.createElement)("article",{className:"itsec-admin-notice itsec-admin-notice--severity-".concat(t.severity)},Object(i.createElement)("header",{className:"itsec-admin-notice__header"},Object(i.createElement)("div",{className:"itsec-admin-notice__header-inset"},Object(i.createElement)("h4",{dangerouslySetInnerHTML:{__html:t.title||f(t.message,t)}}),Object(a.map)(t.actions,function(e,t){return"primary"===e.style&&Object(i.createElement)(s.Button,{key:t,href:e.uri},e.title)}))),t.title&&t.message&&Object(i.createElement)("section",{className:"itsec-admin-notice__message",dangerouslySetInnerHTML:{__html:Object(m.autop)(f(t.message,t))}}),function(e){if(Object(a.isEmpty)(e.meta))return!1;if(1===Object(a.size)(e.meta)&&e.meta.hasOwnProperty("created_at"))return!1;return!0}(t)&&Object(i.createElement)("dl",{className:"itsec-admin-notice__meta"},Object(a.map)(t.meta,function(e,t){return"created_at"!==t&&Object(i.createElement)(i.Fragment,{key:t},Object(i.createElement)("dt",null,e.label),Object(i.createElement)("dd",null,e.formatted))})),t.meta.created_at&&Object(i.createElement)("footer",{className:"itsec-admin-notice__footer"},Object(i.createElement)("time",{dateTime:t.meta.created_at.value},t.meta.created_at.formatted)))}function f(e,t){for(var n in t.actions)t.actions.hasOwnProperty(n)&&""!==t.actions[n].uri&&(e=e.replace("{{ $"+n+" }}",t.actions[n].uri));return e}n("o/Pk");var b=Object(l.compose)([Object(u.withDispatch)(function(e){return{doAction:e("ithemes-security/admin-notices").doNoticeAction}}),Object(u.withSelect)(function(e,t){return{inProgress:e("ithemes-security/admin-notices").getInProgressActions(t.notice.id)}})])(function(e){var t=e.notice,n=e.doAction,c=e.inProgress,r=[],l=function(e){if(!t.actions.hasOwnProperty(e))return"continue";var a=t.actions[e];"close"===a.style&&r.push(Object(i.createElement)("li",{key:e},Object(i.createElement)(s.IconButton,{icon:"dismiss",label:a.title,onClick:function(){return n(t.id,e)},isBusy:c.includes(e)})))};for(var u in t.actions)l(u);var m=function(e){var t={};for(var n in e.actions)if(e.actions.hasOwnProperty(n)){var i=e.actions[n];"close"!==i.style&&"primary"!==i.style&&(t[n]=i)}return t}(t);return Object(a.isEmpty)(m)||r.push(Object(i.createElement)("li",{key:"more"},Object(i.createElement)(s.Dropdown,{position:"bottom right",className:"itsec-admin-notice-list-actions__more-menu",contentClassName:"itsec-admin-notice-list-actions__more-menu-items",renderToggle:function(e){var t=e.isOpen,n=e.onToggle;return Object(i.createElement)(s.IconButton,{icon:"ellipsis",label:Object(o.__)("More Actions","better-wp-security"),onClick:n,"aria-haspopup":!0,"aria-expanded":t})},renderContent:function(){return Object(i.createElement)(s.NavigableMenu,{role:"menu"},Object(a.map)(m,function(e,r){return e.uri?Object(i.createElement)(s.Button,{key:r,href:e.uri},e.title):Object(i.createElement)(s.Button,{key:r,onClick:function(){return n(t.id,r)},disabled:c.includes(r)},e.title,c.includes(r)&&Object(i.createElement)(s.Spinner,null))}))}}))),Object(i.createElement)("ul",{className:"itsec-admin-notice-list-actions"},r)});n("Ckse");var g=function(e){var t=e.notices;return Object(i.createElement)("ul",{className:"itsec-admin-notice-list"},t.map(function(e){return Object(i.createElement)("li",{className:"itsec-admin-notice-list-item-container",key:e.id},Object(i.createElement)(b,{notice:e}),Object(i.createElement)(d,{notice:e,noticeId:e.id}))}))};n("KTI5");t.a=Object(l.compose)([Object(l.withState)({isConfiguring:!1,checked:{}}),Object(u.withSelect)(function(e){return{mutedHighlights:e("ithemes-security/admin-notices").getMutedHighlights(),mutedHighlightUpdatesInFlight:e("ithemes-security/admin-notices").getMutedHighlightUpdatesInFlight()}}),Object(u.withDispatch)(function(e){return{updateMutedHighlight:e("ithemes-security/admin-notices").updateMutedHighlight}})])(function(e){var t=e.notices,n=e.loaded,c=e.mutedHighlights,l=e.mutedHighlightUpdatesInFlight,u=e.updateMutedHighlight,m=e.isConfiguring,d=e.setState;return Object(i.createElement)("div",{className:r()("itsec-admin-notice-panel",{"itsec-admin-notice-panel--is-configuring":m})},Object(i.createElement)(s.IconButton,{icon:"admin-generic",label:Object(o.__)("Configure","better-wp-security"),className:"itsec-admin-notice-panel__configure-trigger",style:{opacity:Object(a.size)(c)>0?1:0},onClick:function(){return d({isConfiguring:!m})}}),Object(i.createElement)("header",{className:"itsec-admin-notice-panel__header"},Object(i.createElement)("h3",null,Object(o.__)("Security Admin Messages","better-wp-security")),Object(i.createElement)("p",null,Object(o.__)("Important notices from iThemes Security","better-wp-security"))),m&&Object(i.createElement)("ul",{className:"itsec-admin-notice-panel__configure-highlighted-logs"},[{slug:"file-change-report",label:Object(o.__)("File Change Report","better-wp-security")},{slug:"notification-center-send-failed",label:Object(o.__)("Notification Center Errors","better-wp-security")},{slug:"malware-scan-report",label:Object(o.__)("Malware Scan Report","better-wp-security")},{slug:"malware-scan-failed",label:Object(o.__)("Malware Scan Failed","better-wp-security")}].map(function(e){var t=e.slug,r=e.label;return void 0!==c[t]&&Object(i.createElement)("li",null,Object(i.createElement)("label",{htmlFor:"itsec-mute-highlight-".concat(t)},r),Object(i.createElement)(s.FormToggle,{id:"itsec-mute-highlight-".concat(t),disabled:!n||l[t],checked:!Object(a.get)(l,[t,"mute"],c[t]),onChange:function(){return u(t,!c[t])}}))})),t.length>0?Object(i.createElement)(g,{notices:t}):n&&Object(i.createElement)("span",null,Object(o.__)("No notices at the moment.","better-wp-security")))})},"tI+e":function(e,t){!function(){e.exports=this.wp.components}()},vYuV:function(e,t,n){"use strict";function i(e){for(var t=e.parentNode;null!==t;){if(t.classList&&t.classList.contains("itsec-admin-notice-list-actions__more-menu-items"))return!0;t=t.parentNode}return!1}n.d(t,"a",function(){return i})}});
dist/core/admin-notices.min.css ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ .itsec-admin-notice{background:white;border-radius:3px;box-shadow:0 0 5px rgba(211,211,211,0.35)}.itsec-admin-notice__header{background:#eeecec;padding:10px;border-top-left-radius:3px;border-top-right-radius:3px}.itsec-admin-notice__header .components-button{margin-top:1em;padding:.5em 1em;background:#E1f2fc;color:#0081E3;border:1px solid #0081E3;transition:background-color 300ms, color 150ms}.itsec-admin-notice__header .components-button:hover{background:#0081E3;color:white}.itsec-admin-notice__header .components-button.is-busy{animation:components-button__busy-animation 2500ms infinite linear;background-size:100px 100%;background-image:repeating-linear-gradient(-45deg, #E1f2fc, #fff 11px, #fff 10px, #E1f2fc 20px);opacity:1}.itsec-admin-notice__header .components-button.is-busy:hover{color:#0081E3}.itsec-admin-notice--severity-error .itsec-admin-notice__header{background:#d94f4f}.itsec-admin-notice--severity-error .itsec-admin-notice__header .components-button{padding:.5em 1em;background:#fcefef;color:#d94f4f;border:1px solid #d94f4f;transition:background-color 300ms, color 150ms}.itsec-admin-notice--severity-error .itsec-admin-notice__header .components-button:hover{background:#d94f4f;color:white}.itsec-admin-notice--severity-error .itsec-admin-notice__header .components-button.is-busy{animation:components-button__busy-animation 2500ms infinite linear;background-size:100px 100%;background-image:repeating-linear-gradient(-45deg, #fcefef, #fff 11px, #fff 10px, #fcefef 20px);opacity:1}.itsec-admin-notice--severity-error .itsec-admin-notice__header .components-button.is-busy:hover{color:#d94f4f}.itsec-admin-notice--severity-warning .itsec-admin-notice__header{background:#f78b53}.itsec-admin-notice--severity-warning .itsec-admin-notice__header .components-button{padding:.5em 1em;background:#fef1ea;color:#f78b53;border:1px solid #f78b53;transition:background-color 300ms, color 150ms}.itsec-admin-notice--severity-warning .itsec-admin-notice__header .components-button:hover{background:#f78b53;color:white}.itsec-admin-notice--severity-warning .itsec-admin-notice__header .components-button.is-busy{animation:components-button__busy-animation 2500ms infinite linear;background-size:100px 100%;background-image:repeating-linear-gradient(-45deg, #fef1ea, #fff 11px, #fff 10px, #fef1ea 20px);opacity:1}.itsec-admin-notice--severity-warning .itsec-admin-notice__header .components-button.is-busy:hover{color:#f78b53}.itsec-admin-notice--severity-info .itsec-admin-notice__header{background:#00a0d2}.itsec-admin-notice--severity-success .itsec-admin-notice__header{background:#4ab866}.itsec-admin-notice--severity-success .itsec-admin-notice__header .components-button{padding:.5em 1em;background:#e4f4e8;color:#4ab866;border:1px solid #4ab866;transition:background-color 300ms, color 150ms}.itsec-admin-notice--severity-success .itsec-admin-notice__header .components-button:hover{background:#4ab866;color:white}.itsec-admin-notice--severity-success .itsec-admin-notice__header .components-button.is-busy{animation:components-button__busy-animation 2500ms infinite linear;background-size:100px 100%;background-image:repeating-linear-gradient(-45deg, #e4f4e8, #fff 11px, #fff 10px, #e4f4e8 20px);opacity:1}.itsec-admin-notice--severity-success .itsec-admin-notice__header .components-button.is-busy:hover{color:#4ab866}.itsec-admin-notice .itsec-admin-notice__header:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.itsec-admin-notice__header .itsec-admin-notice__header-inset{background:white;border-radius:5px;padding:10px}.itsec-admin-notice__header h4{font-size:14px;margin:0}.itsec-admin-notice__message{padding:10px 20px}.itsec-admin-notice__message p:first-child{margin-top:0}.itsec-admin-notice__message p:last-child{margin-bottom:0}.itsec-admin-notice__meta{background:#eeecec;padding:5px 20px;margin:0;display:grid;grid-template:auto / -webkit-min-content 1fr;grid-template:auto / min-content 1fr;grid-gap:5px 20px}.itsec-admin-notice__meta dt,.itsec-admin-notice__meta dd{margin:0}.itsec-admin-notice__meta dt{text-transform:uppercase;color:#040404}.itsec-admin-notice__footer{padding:5px 20px;font-size:11px}
2
+
3
+ .itsec-admin-notice-list-actions .components-icon-button{padding:2px !important}.itsec-admin-notice-list-actions .components-icon-button:hover{background-color:#c6d1da !important;box-shadow:none !important}.itsec-admin-notice-list-actions__more-menu-items .components-popover__content>div{overflow:hidden;padding:7px 0}.itsec-admin-notice-list-actions__more-menu-items .components-button{width:100%;min-height:38px;box-sizing:border-box;padding:10px;color:#0073aa;justify-content:space-between;align-items:center}.itsec-admin-notice-list-actions__more-menu-items .components-button:focus{outline-offset:-2px;outline:1px dotted #555d66;box-shadow:none}.itsec-admin-notice-list-actions__more-menu-items .components-button:hover{color:#191e23;background:#f3f4f5}.itsec-admin-notice-list-actions__more-menu-items .components-button .components-spinner{margin:0}
4
+
5
+ .itsec-admin-notice-list{margin:0}.itsec-admin-notice-list-item-container{display:grid;grid-template:auto / -webkit-min-content 1fr;grid-template:auto / min-content 1fr;grid-gap:1em;margin-bottom:2em}.itsec-admin-notice-list-item-container:last-child{margin-bottom:0}
6
+
7
+ @keyframes itsec-animation-fade-in-constant{from{opacity:0}to{opacity:1}}.itsec-admin-bar-admin-notices__content.components-popover::after{border-color:#E5EAEE !important}.itsec-admin-notice-panel{padding:1em;background:#E5EAEE;width:420px;box-sizing:border-box}.itsec-admin-notice-panel .itsec-admin-notice-panel__header{border-bottom:1px solid #7ABEED;margin-bottom:1em}.itsec-admin-notice-panel .itsec-admin-notice-panel__header h3{color:#0081E3;text-align:center}.itsec-admin-notice-panel .itsec-admin-notice-panel__header p{text-align:center;font-style:oblique}.is-mobile .itsec-admin-notice-panel{width:100%;overflow-x:auto;height:100%}.is-mobile .itsec-admin-notice-panel header h3{display:none}.is-mobile .itsec-admin-notice-panel header p{margin-top:0}.itsec-admin-notice-panel .itsec-admin-notice-panel__configure-trigger.components-icon-button{padding:5px;height:30px;position:absolute;right:1em;top:1em;transition:opacity 400ms}.itsec-admin-notice-panel .itsec-admin-notice-panel__configure-trigger.components-icon-button:hover{opacity:.5;box-shadow:none !important}.itsec-admin-notice-panel.itsec-admin-notice-panel--is-configuring .itsec-admin-notice-panel__configure-trigger.components-icon-button{color:#0081E3}.itsec-admin-notice-panel.itsec-admin-notice-panel--is-configuring .itsec-admin-notice-panel__configure-trigger.components-icon-button:hover{color:#0081E3}.itsec-admin-notice-panel .itsec-admin-notice-panel__configure-highlighted-logs{border-bottom:1px solid #7ABEED;margin-bottom:1em;padding-bottom:1em}.itsec-admin-notice-panel .itsec-admin-notice-panel__configure-highlighted-logs li{display:flex;justify-content:space-between;border-bottom:1px solid #ccc;margin-bottom:1em;padding-bottom:1em;padding-top:0;margin-top:0}.itsec-admin-notice-panel .itsec-admin-notice-panel__configure-highlighted-logs li:last-child{border-bottom:none;margin-bottom:0;padding-bottom:0}.itsec-admin-notice-panel .itsec-admin-notice-panel__configure-highlighted-logs li label{margin-left:calc(24px + 1em);font-size:14px;font-weight:bold}.itsec-admin-notice-panel .itsec-admin-notice-panel__configure-highlighted-logs li .components-form-toggle.is-checked .components-form-toggle__track{background-color:#0081E3;border-color:#0081E3}.itsec-admin-notice-panel .itsec-admin-notice-panel__configure-highlighted-logs li .components-form-toggle__input:disabled+.components-form-toggle__track{opacity:.5}
8
+
9
+ #wpadminbar #wp-admin-bar-itsec_admin_bar_menu .ab-sub-wrapper{background:transparent;box-shadow:none}#wpadminbar .itsec-admin-notices-toolbar-bubble{display:inline-block;vertical-align:top;margin:8px 0 0 5px;padding:0 5px;min-width:7px;height:17px;border-radius:11px;font-size:9px;line-height:17px;text-align:center;z-index:26;background-color:#ca4a1f;color:#fff;animation:itsec-admin-notices-toolbar-bubble-fade-in 400ms}#wpadminbar .itsec-admin-notices-toolbar-bubble .itsec-admin-notices-toolbar-bubble__count{line-height:17px;font-size:9px}@keyframes itsec-admin-notices-toolbar-bubble-fade-in{from{opacity:0}to{opacity:1}}#itsec-admin-notices-root .itsec-admin-notice-panel{border:1px solid #e2e4e7;box-shadow:0 3px 30px rgba(25,30,35,0.1)}#itsec-admin-notices-root .components-popover:not(.is-mobile).is-bottom{z-index:99999}#itsec-admin-notices-root .itsec-admin-notices-toolbar__popover .components-popover__content{box-shadow:rgba(0,0,0,0.19) 0 4px 5px}#wp-admin-bar-itsec_admin_bar_menu .components-button{cursor:pointer}#wp-admin-bar-itsec_admin_bar_menu .it-icon-itsec{display:none;margin-top:8px;color:rgba(240,245,250,0.6);height:20px;width:52px;line-height:32px;font-size:32px;text-align:center}@media screen and (max-width: 782px){#wp-toolbar>ul>li#wp-admin-bar-itsec_admin_bar_menu{display:list-item}#wp-toolbar>ul>li#wp-admin-bar-itsec_admin_bar_menu .it-icon-itsec{display:inline-block}#wp-toolbar>ul>li#wp-admin-bar-itsec_admin_bar_menu .itsec-toolbar-text{display:none}#wp-toolbar>ul>li#wp-admin-bar-itsec_admin_bar_menu .itsec-admin-notices-toolbar-bubble{display:none}#wp-toolbar>ul>li#wp-admin-bar-itsec_admin_bar_menu .itsec-admin-notices-toolbar--has-notices{position:relative}#wp-toolbar>ul>li#wp-admin-bar-itsec_admin_bar_menu .itsec-admin-notices-toolbar--has-notices::before{content:'';background:#d8514f;height:5px;width:5px;border-radius:5px;vertical-align:middle;border:1px solid #23282d;z-index:1;position:absolute;left:4px;top:3px;animation:itsec-admin-notices-toolbar-bubble-fade-in 400ms}}
10
+
11
+
dist/core/admin-notices.min.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ this.itsec=this.itsec||{},this.itsec.core=this.itsec.core||{},this.itsec.core["admin-notices"]=function(e){var t={};function n(i){if(t[i])return t[i].exports;var c=t[i]={i:i,l:!1,exports:{}};return e[i].call(c.exports,c,c.exports,n),c.l=!0,c.exports}return n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var c in e)n.d(i,c,function(t){return e[t]}.bind(null,c));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s="dwGD")}({"1Zi1":function(e,t,n){},"1ZqX":function(e,t){!function(){e.exports=this.wp.data}()},"6IbE":function(e,t,n){},Ckse:function(e,t,n){},GRId:function(e,t){!function(){e.exports=this.wp.element}()},Hz6T:function(e,t){!function(){e.exports=this.itsec.core["admin-notices-api"]}()},K9lf:function(e,t){!function(){e.exports=this.wp.compose}()},KTI5:function(e,t,n){},TSYQ:function(e,t,n){
2
+ /*!
3
+ Copyright (c) 2017 Jed Watson.
4
+ Licensed under the MIT License (MIT), see
5
+ http://jedwatson.github.io/classnames
6
+ */
7
+ !function(){"use strict";var t={}.hasOwnProperty;function n(){for(var e=[],i=0;i<arguments.length;i++){var c=arguments[i];if(c){var r=typeof c;if("string"===r||"number"===r)e.push(c);else if(Array.isArray(c)&&c.length){var a=n.apply(null,c);a&&e.push(a)}else if("object"===r)for(var o in c)t.call(c,o)&&c[o]&&e.push(o)}}return e.join(" ")}e.exports?(n.default=n,e.exports=n):"function"==typeof define&&"object"==typeof define.amd&&define.amd?define("classnames",[],function(){return n}):window.classNames=n}()},UuzZ:function(e,t){!function(){e.exports=this.wp.autop}()},VwaH:function(e,t,n){},Y8OO:function(e,t){!function(){e.exports=this.wp.domReady}()},YLtl:function(e,t){!function(){e.exports=this.lodash}()},dwGD:function(e,t,n){"use strict";n.r(t);var i=n("GRId"),c=n("l3Sj"),r=n("Y8OO"),a=n.n(r),o=n("tI+e"),s=(n("Hz6T"),n("TSYQ")),l=n.n(s),u=n("K9lf"),m=n("vYuV"),d=n("rE5z"),b=(n("6IbE"),n("1ZqX"));var f=Object(u.compose)([Object(b.withSelect)(function(e){return{notices:e("ithemes-security/admin-notices").getNotices(),noticesLoaded:e("ithemes-security/admin-notices").areNoticesLoaded()}}),Object(u.withState)({isToggled:!1})])(function(e){var t=e.notices,n=e.noticesLoaded,r=e.isToggled,a=e.setState;return Object(i.createElement)(i.Fragment,null,Object(i.createElement)(o.Button,{id:"itsec-admin-notices-toolbar-trigger",className:l()("ab-item ab-empty-item",{"itsec-admin-notices-toolbar--has-notices":t.length>0}),onClick:function(){return a({isToggled:!r})},"aria-expanded":r},Object(i.createElement)("span",{className:"it-icon-itsec"}),Object(i.createElement)("span",{className:"itsec-toolbar-text"},Object(c.__)("Security","better-wp-security")),t.length>0&&Object(i.createElement)("span",{className:"itsec-admin-notices-toolbar-bubble"},Object(i.createElement)("span",{className:"itsec-admin-notices-toolbar-bubble__count"},t.length))),r&&Object(i.createElement)(o.Popover,{className:"itsec-admin-notices-toolbar__popover",noArrow:!0,expandOnMobile:!0,focusOnMount:"container",position:"bottom center",headerTitle:Object(c.__)("Security","better-wp-security"),onClose:function(){return a({isToggled:!1})},onClickOutside:function(e){"itsec-admin-notices-toolbar-trigger"===e.target.id||"itsec-admin-notices-toolbar-trigger"===e.target.parentNode.id||Object(m.a)(e.target)||a({isToggled:!1})}},Object(i.createElement)(d.a,{notices:t,loaded:n,close:function(){return a({isToggled:!1})}})))});n("1Zi1");var p=function(e){var t=e.portalEl;return Object(i.createElement)(o.SlotFillProvider,null,Object(i.createPortal)(Object(i.createElement)(o.Popover.Slot,null),t),Object(i.createElement)(f,null))};n.p=window.itsecWebpackPublicPath,Object(c.setLocaleData)({"":{}},"better-wp-security"),a()(function(){var e=document.getElementById("wp-admin-bar-itsec_admin_bar_menu"),t=document.getElementById("itsec-admin-notices-root");return Object(i.render)(Object(i.createElement)(p,{portalEl:t}),e)})},l3Sj:function(e,t){!function(){e.exports=this.wp.i18n}()},"o/Pk":function(e,t,n){},rE5z:function(e,t,n){"use strict";var i=n("GRId"),c=n("TSYQ"),r=n.n(c),a=n("YLtl"),o=n("l3Sj"),s=n("tI+e"),l=n("K9lf"),u=n("1ZqX"),m=n("UuzZ");n("VwaH");function d(e){var t=e.notice;return Object(i.createElement)("article",{className:"itsec-admin-notice itsec-admin-notice--severity-".concat(t.severity)},Object(i.createElement)("header",{className:"itsec-admin-notice__header"},Object(i.createElement)("div",{className:"itsec-admin-notice__header-inset"},Object(i.createElement)("h4",{dangerouslySetInnerHTML:{__html:t.title||b(t.message,t)}}),Object(a.map)(t.actions,function(e,t){return"primary"===e.style&&Object(i.createElement)(s.Button,{key:t,href:e.uri},e.title)}))),t.title&&t.message&&Object(i.createElement)("section",{className:"itsec-admin-notice__message",dangerouslySetInnerHTML:{__html:Object(m.autop)(b(t.message,t))}}),function(e){if(Object(a.isEmpty)(e.meta))return!1;if(1===Object(a.size)(e.meta)&&e.meta.hasOwnProperty("created_at"))return!1;return!0}(t)&&Object(i.createElement)("dl",{className:"itsec-admin-notice__meta"},Object(a.map)(t.meta,function(e,t){return"created_at"!==t&&Object(i.createElement)(i.Fragment,{key:t},Object(i.createElement)("dt",null,e.label),Object(i.createElement)("dd",null,e.formatted))})),t.meta.created_at&&Object(i.createElement)("footer",{className:"itsec-admin-notice__footer"},Object(i.createElement)("time",{dateTime:t.meta.created_at.value},t.meta.created_at.formatted)))}function b(e,t){for(var n in t.actions)t.actions.hasOwnProperty(n)&&""!==t.actions[n].uri&&(e=e.replace("{{ $"+n+" }}",t.actions[n].uri));return e}n("o/Pk");var f=Object(l.compose)([Object(u.withDispatch)(function(e){return{doAction:e("ithemes-security/admin-notices").doNoticeAction}}),Object(u.withSelect)(function(e,t){return{inProgress:e("ithemes-security/admin-notices").getInProgressActions(t.notice.id)}})])(function(e){var t=e.notice,n=e.doAction,c=e.inProgress,r=[],l=function(e){if(!t.actions.hasOwnProperty(e))return"continue";var a=t.actions[e];"close"===a.style&&r.push(Object(i.createElement)("li",{key:e},Object(i.createElement)(s.IconButton,{icon:"dismiss",label:a.title,onClick:function(){return n(t.id,e)},isBusy:c.includes(e)})))};for(var u in t.actions)l(u);var m=function(e){var t={};for(var n in e.actions)if(e.actions.hasOwnProperty(n)){var i=e.actions[n];"close"!==i.style&&"primary"!==i.style&&(t[n]=i)}return t}(t);return Object(a.isEmpty)(m)||r.push(Object(i.createElement)("li",{key:"more"},Object(i.createElement)(s.Dropdown,{position:"bottom right",className:"itsec-admin-notice-list-actions__more-menu",contentClassName:"itsec-admin-notice-list-actions__more-menu-items",renderToggle:function(e){var t=e.isOpen,n=e.onToggle;return Object(i.createElement)(s.IconButton,{icon:"ellipsis",label:Object(o.__)("More Actions","better-wp-security"),onClick:n,"aria-haspopup":!0,"aria-expanded":t})},renderContent:function(){return Object(i.createElement)(s.NavigableMenu,{role:"menu"},Object(a.map)(m,function(e,r){return e.uri?Object(i.createElement)(s.Button,{key:r,href:e.uri},e.title):Object(i.createElement)(s.Button,{key:r,onClick:function(){return n(t.id,r)},disabled:c.includes(r)},e.title,c.includes(r)&&Object(i.createElement)(s.Spinner,null))}))}}))),Object(i.createElement)("ul",{className:"itsec-admin-notice-list-actions"},r)});n("Ckse");var p=function(e){var t=e.notices;return Object(i.createElement)("ul",{className:"itsec-admin-notice-list"},t.map(function(e){return Object(i.createElement)("li",{className:"itsec-admin-notice-list-item-container",key:e.id},Object(i.createElement)(f,{notice:e}),Object(i.createElement)(d,{notice:e,noticeId:e.id}))}))};n("KTI5");t.a=Object(l.compose)([Object(l.withState)({isConfiguring:!1,checked:{}}),Object(u.withSelect)(function(e){return{mutedHighlights:e("ithemes-security/admin-notices").getMutedHighlights(),mutedHighlightUpdatesInFlight:e("ithemes-security/admin-notices").getMutedHighlightUpdatesInFlight()}}),Object(u.withDispatch)(function(e){return{updateMutedHighlight:e("ithemes-security/admin-notices").updateMutedHighlight}})])(function(e){var t=e.notices,n=e.loaded,c=e.mutedHighlights,l=e.mutedHighlightUpdatesInFlight,u=e.updateMutedHighlight,m=e.isConfiguring,d=e.setState;return Object(i.createElement)("div",{className:r()("itsec-admin-notice-panel",{"itsec-admin-notice-panel--is-configuring":m})},Object(i.createElement)(s.IconButton,{icon:"admin-generic",label:Object(o.__)("Configure","better-wp-security"),className:"itsec-admin-notice-panel__configure-trigger",style:{opacity:Object(a.size)(c)>0?1:0},onClick:function(){return d({isConfiguring:!m})}}),Object(i.createElement)("header",{className:"itsec-admin-notice-panel__header"},Object(i.createElement)("h3",null,Object(o.__)("Security Admin Messages","better-wp-security")),Object(i.createElement)("p",null,Object(o.__)("Important notices from iThemes Security","better-wp-security"))),m&&Object(i.createElement)("ul",{className:"itsec-admin-notice-panel__configure-highlighted-logs"},[{slug:"file-change-report",label:Object(o.__)("File Change Report","better-wp-security")},{slug:"notification-center-send-failed",label:Object(o.__)("Notification Center Errors","better-wp-security")},{slug:"malware-scan-report",label:Object(o.__)("Malware Scan Report","better-wp-security")},{slug:"malware-scan-failed",label:Object(o.__)("Malware Scan Failed","better-wp-security")}].map(function(e){var t=e.slug,r=e.label;return void 0!==c[t]&&Object(i.createElement)("li",null,Object(i.createElement)("label",{htmlFor:"itsec-mute-highlight-".concat(t)},r),Object(i.createElement)(s.FormToggle,{id:"itsec-mute-highlight-".concat(t),disabled:!n||l[t],checked:!Object(a.get)(l,[t,"mute"],c[t]),onChange:function(){return u(t,!c[t])}}))})),t.length>0?Object(i.createElement)(p,{notices:t}):n&&Object(i.createElement)("span",null,Object(o.__)("No notices at the moment.","better-wp-security")))})},"tI+e":function(e,t){!function(){e.exports=this.wp.components}()},vYuV:function(e,t,n){"use strict";function i(e){for(var t=e.parentNode;null!==t;){if(t.classList&&t.classList.contains("itsec-admin-notice-list-actions__more-menu-items"))return!0;t=t.parentNode}return!1}n.d(t,"a",function(){return i})}});
dist/core/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
dist/core/packages/components/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
dist/core/packages/components/site-scan-results/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
dist/core/packages/components/site-scan-results/style.min.css ADDED
@@ -0,0 +1,2 @@
 
 
1
+ .itsec-site-scan-results h4{margin-top:5px}.itsec-site-scan-results .itsec-site-scan-details pre{background-color:#eaeaea;padding:1em;white-space:pre-wrap}.itsec-site-scan-results .itsec-site-scan-results-section{border:1px solid #ddd;border-bottom-color:transparent;padding:1em}.itsec-site-scan-results .itsec-site-scan-results-section>p{margin:0}.itsec-site-scan-results .itsec-site-scan-results-section:last-child{border-bottom-color:#ddd}.itsec-site-scan-results .itsec-site-scan__status{display:inline-block;padding:2px 6px;color:#fff;margin-right:1em;width:60px;text-align:center}.itsec-site-scan-results .itsec-site-scan__status.itsec-site-scan__status--clean{background:#7ad03a}.itsec-site-scan-results .itsec-site-scan__status.itsec-site-scan__status--warn{background:#dd3d36}.itsec-site-scan-results .itsec-site-scan__status.itsec-site-scan__status--error{background:#dd3d36}.itsec-site-scan-results .itsec-site-scan__details ul{margin-left:2.5em;list-style:disc outside}.itsec-site-scan-results .itsec-site-scan__details .itsec-site-scan__detail{width:100%;margin-bottom:.25em;padding-bottom:.5em;color:inherit;border-bottom:1px solid #ddd}.itsec-site-scan-results .itsec-site-scan__details .itsec-site-scan__detail:last-child{border:none}.itsec-site-scan-results .itsec-site-scan__details .itsec-site-scan__detail.itsec-site-scan__detail--warn{color:#dd3d36}.itsec-site-scan-results .itsec-site-scan__details .itsec-site-scan__detail.itsec-site-scan__detail--error{color:#dd3d36}.itsec-site-scan-results .itsec-site-scan__details .itsec-site-scan__detail.itsec-site-scan__detail--clean{color:#7ad03a}.itsec-site-scan-results .itsec-site-scan__details .itsec-site-scan__detail span{color:#444}.itsec-site-scan-results .itsec-site-scan-toggle-details{margin-left:1em}
2
+
dist/core/packages/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
dist/index.php ADDED
@@ -0,0 +1 @@
 
1
+ <?php // Silence is golden.
dist/manifest.php ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php return array (
2
+ 'core/admin-notices' =>
3
+ array (
4
+ 'runtime' => true,
5
+ 'files' =>
6
+ array (
7
+ 0 => 'core/admin-notices.css',
8
+ 1 => 'core/admin-notices.js',
9
+ ),
10
+ 'hash' => '9d0cac63105d6f0bcc30d06f969d8be7',
11
+ 'contentHash' =>
12
+ array (
13
+ 'css/mini-extract' => 'd5d74d576a086d437a59',
14
+ 'javascript' => '78a2c380e8a93d8e10f7',
15
+ ),
16
+ 'vendors' =>
17
+ array (
18
+ ),
19
+ 'dependencies' =>
20
+ array (
21
+ 0 => '@ithemes/security.core.admin-notices-api',
22
+ 1 => 'lodash',
23
+ 2 => 'wp-autop',
24
+ 3 => 'wp-components',
25
+ 4 => 'wp-compose',
26
+ 5 => 'wp-data',
27
+ 6 => 'wp-dom-ready',
28
+ 7 => 'wp-element',
29
+ 8 => 'wp-i18n',
30
+ ),
31
+ ),
32
+ 'core/admin-notices-api' =>
33
+ array (
34
+ 'runtime' => true,
35
+ 'files' =>
36
+ array (
37
+ 0 => 'core/admin-notices-api.js',
38
+ ),
39
+ 'hash' => '4830a04fca439f907049c335efb143f9',
40
+ 'contentHash' =>
41
+ array (
42
+ 'css/mini-extract' => '31d6cfe0d16ae931b73c',
43
+ 'javascript' => 'f64242c0f2acc73e4f26',
44
+ ),
45
+ 'vendors' =>
46
+ array (
47
+ ),
48
+ 'dependencies' =>
49
+ array (
50
+ 0 => 'lodash',
51
+ 1 => 'wp-api-fetch',
52
+ 2 => 'wp-data',
53
+ 3 => 'wp-i18n',
54
+ ),
55
+ ),
56
+ 'core/admin-notices-dashboard-admin-bar' =>
57
+ array (
58
+ 'runtime' => true,
59
+ 'files' =>
60
+ array (
61
+ 0 => 'core/admin-notices-dashboard-admin-bar.css',
62
+ 1 => 'core/admin-notices-dashboard-admin-bar.js',
63
+ ),
64
+ 'hash' => 'a95fc2fb1e30fed9c7938f08c12b6d7b',
65
+ 'contentHash' =>
66
+ array (
67
+ 'css/mini-extract' => '732388055deb8942488c',
68
+ 'javascript' => 'b6d1b12859cd32d64740',
69
+ ),
70
+ 'vendors' =>
71
+ array (
72
+ ),
73
+ 'dependencies' =>
74
+ array (
75
+ 0 => '@ithemes/security.core.admin-notices-api',
76
+ 1 => '@ithemes/security.dashboard.api',
77
+ 2 => 'lodash',
78
+ 3 => 'wp-autop',
79
+ 4 => 'wp-components',
80
+ 5 => 'wp-compose',
81
+ 6 => 'wp-data',
82
+ 7 => 'wp-element',
83
+ 8 => 'wp-i18n',
84
+ 9 => 'wp-plugins',
85
+ ),
86
+ ),
87
+ 'core/packages/components/site-scan-results/style' =>
88
+ array (
89
+ 'runtime' => true,
90
+ 'files' =>
91
+ array (
92
+ 0 => 'core/packages/components/site-scan-results/style.css',
93
+ 1 => 'core/packages/components/site-scan-results/style.js',
94
+ ),
95
+ 'hash' => '17241399d68eeb900f5f2c289b1748f7',
96
+ 'contentHash' =>
97
+ array (
98
+ 'css/mini-extract' => '4938d7aee319aaa3968c',
99
+ 'javascript' => '0ac75a114cf101452605',
100
+ ),
101
+ 'vendors' =>
102
+ array (
103
+ ),
104
+ 'dependencies' =>
105
+ array (
106
+ ),
107
+ ),
108
+ );
history.txt CHANGED
@@ -836,3 +836,8 @@
836
  Bug Fix: Resolve warning when a user is set to "No Role".
837
  7.3.3 - 2019-03-25 - Chris Jean & Timothy Jacobs
838
  Bug Fix: Hide backend bypass.
 
 
 
 
 
836
  Bug Fix: Resolve warning when a user is set to "No Role".
837
  7.3.3 - 2019-03-25 - Chris Jean & Timothy Jacobs
838
  Bug Fix: Hide backend bypass.
839
+ 7.4.0 - 2019-06-10 - Chris Jean & Timothy Jacobs
840
+ New: iThemes Security Admin Notices are now conveniently located in the new Security Messages Menu. Check your notices in the Security menu on the WordPress Admin Bar.
841
+ Enhancement: Add Security Message when a Notification Center email fails to send.
842
+ Enhancement: Replace Trace IP with IP Tracker Online.
843
+ Tweak: Remove 'DELETE' method from "System Tweaks -> Filter Request Methods"
package.json ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ithemes-security",
3
+ "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.",
4
+ "version": "5.6.0",
5
+ "author": "iThemes",
6
+ "browserslist": [
7
+ "extends @wordpress/browserslist-config"
8
+ ],
9
+ "dependencies": {
10
+ "@wordpress/a11y": "*",
11
+ "@wordpress/api-fetch": "*",
12
+ "@wordpress/autop": "^2.2.0",
13
+ "@wordpress/components": "^7.0.8",
14
+ "@wordpress/compose": "^3.0.1",
15
+ "@wordpress/data": "^4.2.1",
16
+ "@wordpress/date": "^3.0.1",
17
+ "@wordpress/dom-ready": "*",
18
+ "@wordpress/element": "*",
19
+ "@wordpress/hooks": "*",
20
+ "@wordpress/html-entities": "*",
21
+ "@wordpress/i18n": "*",
22
+ "@wordpress/is-shallow-equal": "*",
23
+ "@wordpress/keycodes": "^2.0.6",
24
+ "@wordpress/notices": "*",
25
+ "@wordpress/plugins": "^2.2.0",
26
+ "@wordpress/redux-routine": "*",
27
+ "@wordpress/rich-text": "^3.0.7",
28
+ "@wordpress/url": "*",
29
+ "@wordpress/viewport": "*",
30
+ "classnames": "^2.2.6",
31
+ "contrast": "^1.0.1",
32
+ "li": "^1.3.0",
33
+ "lodash": "^4.17.11",
34
+ "memize": "^1.0.5",
35
+ "react": "^16.6.3",
36
+ "react-error-boundary": "^1.2.3",
37
+ "react-grid-layout": "^0.16.6",
38
+ "react-select": "^2.4.1",
39
+ "react-transition-group": "^2.0.0",
40
+ "recharts": "^1.5.0",
41
+ "rememo": "^3.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@babel/core": "^7.3.3",
45
+ "@babel/plugin-proposal-class-properties": "^7.3.3",
46
+ "@babel/plugin-syntax-dynamic-import": "^7.2.0",
47
+ "@babel/plugin-transform-react-jsx": "^7.3.0",
48
+ "@babel/runtime-corejs2": "^7.3.1",
49
+ "@wordpress/babel-plugin-import-jsx-pragma": "^1.1.3",
50
+ "@wordpress/babel-preset-default": "^3.0.2",
51
+ "@wordpress/browserslist-config": "^2.2.3",
52
+ "@wordpress/custom-templated-path-webpack-plugin": "^1.2.0",
53
+ "@wordpress/jest-preset-default": "^3.0.0",
54
+ "@wordpress/scripts": "^2.4.4",
55
+ "acorn": "^6.0.5",
56
+ "autoprefixer": "^9.4.7",
57
+ "babel-loader": "^8.0.5",
58
+ "clean-webpack-plugin": "^2.0.2",
59
+ "concurrently": "^4.1.0",
60
+ "css-loader": "^2.1.0",
61
+ "escape-string-regexp": "^2.0.0",
62
+ "eslint-config-wordpress": "2.0.0",
63
+ "eslint-plugin-jest": "^22.3.0",
64
+ "eslint-plugin-jsx-a11y": "^6.2.1",
65
+ "eslint-plugin-react": "^7.12.4",
66
+ "eslint-plugin-wordpress": "git://github.com/WordPress-Coding-Standards/eslint-plugin-wordpress.git#1774343f6226052a46b081e01db3fca8793cc9f1",
67
+ "glob": "^7.1.3",
68
+ "husky": "^1.3.1",
69
+ "jest": "^24.1.0",
70
+ "loader-utils": "^1.2.3",
71
+ "mini-css-extract-plugin": "^0.5.0",
72
+ "node-sass": "^4.12.0",
73
+ "postcss-loader": "^3.0.0",
74
+ "raw-loader": "^1.0.0",
75
+ "readdirp": "^2.2.1",
76
+ "rimraf": "^2.6.3",
77
+ "sass-loader": "^7.1.0",
78
+ "style-loader": "^0.23.1",
79
+ "svg-react-loader": "github:woutervanvliet/svg-react-loader",
80
+ "webpack": "^4.29.5",
81
+ "webpack-cli": "^3.2.3",
82
+ "webpack-filter-warnings-plugin": "^1.2.1",
83
+ "webpack-manifest-plugin": "^2.0.4",
84
+ "webpack-sources": "latest",
85
+ "webpack-webstorm-debugger-script": "^1.0.1"
86
+ },
87
+ "directories": {},
88
+ "homepage": "https://ithemes.com/security",
89
+ "husky": {
90
+ "hooks": {
91
+ "pre-commit": "node bin/pre-commit"
92
+ }
93
+ },
94
+ "license": "GPL-2.0-or-later",
95
+ "private": true,
96
+ "repository": {
97
+ "type": "git",
98
+ "url": "git+ssh://git@bitbucket.org/ithemes/ithemes-security-pro.git"
99
+ },
100
+ "scripts": {
101
+ "build": "NODE_ENV=production ./node_modules/.bin/webpack",
102
+ "clean": "node ./bin/clean.js ",
103
+ "dev": "./node_modules/.bin/webpack",
104
+ "lint": "concurrently \"npm run lint-js\"",
105
+ "lint-js": "wp-scripts lint-js .",
106
+ "lint-js:fix": "wp-scripts lint-js . --fix",
107
+ "test": "concurrently \"npm run lint-js && npm run test-unit\"",
108
+ "test-unit": "wp-scripts test-unit-js --config tests/js/unit/jest.config.json",
109
+ "test-unit:coverage": "npm run test-unit -- --coverage",
110
+ "test-unit:update": "npm run test-unit -- --updateSnapshot",
111
+ "test-unit:watch": "npm run test-unit -- --watch",
112
+ "watch": "./node_modules/.bin/webpack --watch"
113
+ }
114
+ }
readme.txt CHANGED
@@ -2,8 +2,8 @@
2
  Contributors: ithemes, chrisjean, 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: 5.2.0
6
- Stable tag: 7.3.3
7
  Requires PHP: 5.2
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
@@ -189,6 +189,12 @@ Free support may be available with the help of the community in the <a href="htt
189
 
190
  == Changelog ==
191
 
 
 
 
 
 
 
192
  = 7.3.3 =
193
  * Bug Fix: Hide backend bypass.
194
 
@@ -534,5 +540,5 @@ Free support may be available with the help of the community in the <a href="htt
534
 
535
  == Upgrade Notice ==
536
 
537
- = 7.3.2 =
538
- Version 7.3.2 contains important bug fixes and security improvements. It is recommended for all users.
2
  Contributors: ithemes, chrisjean, 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: 5.2.2
6
+ Stable tag: 7.4.0
7
  Requires PHP: 5.2
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
189
 
190
  == Changelog ==
191
 
192
+ = 7.4.0 =
193
+ * New: iThemes Security Admin Notices are now conveniently located in the new Security Messages Menu. Check your notices in the Security menu on the WordPress Admin Bar.
194
+ * Enhancement: Add Security Message when a Notification Center email fails to send.
195
+ * Enhancement: Replace Trace IP with IP Tracker Online.
196
+ * Tweak: Remove 'DELETE' method from "System Tweaks -> Filter Request Methods"
197
+
198
  = 7.3.3 =
199
  * Bug Fix: Hide backend bypass.
200
 
540
 
541
  == Upgrade Notice ==
542
 
543
+ = 7.4.0 =
544
+ Version 7.4.0 contains important improvements. It is recommended for all users.
webpack.config.js ADDED
@@ -0,0 +1,2 @@
 
 
1
+ const makeConfig = require( './core/packages/webpack/src/config' );
2
+ module.exports = makeConfig( __dirname, false );