All In One WP Security & Firewall - Version 5.0.0

Version Description

  • 24/August/2022 =

  • FEATURE: Two-Factor Authentication (2FA) functionality & related settings.

  • FEATURE: Set up a mechanism to load the firewall PHP file early.

  • FEATURE: PHP firewall rule engine.

  • FEATURE: Add WHOIS lookup functionality.

  • FEATURE: Implement 6G firewall rules in the new PHP-based firewall.

  • FEATURE: Disable WordPress application passwords.

  • FEATURE: Remove the plugin's tables and options when uninstalling the plugin according to configuration settings.

  • FEATURE: Trash spam comments after n number of days as per configuration set in Admin Dashboard > WP Security > SPAM Prevention > the "Comment SPAM" tab > the "Comment Processing" section > the "Trash Comments After" settings.

  • FEATURE: Brute force Cookie-based Firewall Protection based on the PHP code instead of htaccess rules so that it also works with Nginx, IIS etc servers.

  • FEATURE: Allow multiple email addresses for the User Login > Notify By Email setting.

  • FEATURE: IPv6 range support in CIDR Format enabled.

  • FIX: The WooCommerce customer was redirected to the wp-login page after payment with an external payment gateway if forced logout configured after a specific number of minutes.

  • FIX: If the WordPress language was set to something other than English, then auto-update core, plugin, and theme emails sent in English instead of the configured language.

  • FIX: Database error for multisite when creating a new site solved.

  • FIX: Captcha options should not be autoloaded.

  • FIX: Database error for multisite cronjob column name.

  • FIX: The plugin clogs up the database with lots of rows. Delete old data after 90 days.

  • FIX: Rename Login issue with wp plugin list command solved.

  • FIX: Rename Login breaks logout functionality if WP_HOME is set to a different URL than the WordPress core files URL.

  • FIX: PHP Fatal error: Uncaught Error: Class 'AIOWPSecurity_Admin_Init' not found in html/wp-content/plugins/all-in-one-wp-security-and-firewall/wp-security-core.php:366.

  • FIX: The Spam comment blocked IP address remains blocked even after spammed comments are approved.

  • FIX: Admin Dashboard > WP Security > Security Points Breakdown Section piechart tooltips flickering.

  • FIX: The "Time Length of 404 Lockout" option doesn't do anything.

  • FIX: Search did not work for the 404 Event Logs list table.

  • FIX: Search did not work for Failed Logins list table.

  • FIX: Search did not work for the Account Activity list table.

  • FIX: Bulk deletions did not work for the Account Activity list table.

  • FIX: Warning when bots make malformed requests.

  • FIX: When the user had pressed the bottom bulk action button of the list table, the bulk action was confirmed by two confirm alerts.

  • FIX: Unblock link in 404 Event Logs list table redirected to wrong tab.

  • FIX: Temp Block, Blacklist IP and Delete links in 404 Event Logs list table didn't work.

  • FIX: Rename login page and Cookie based brute force login prevention configurations didn't work simultaneously.

  • FIX: Fatal error when activating using older PHP versions

  • FIX: If auto_prepend_file is already pointed to the firewall bootstrap file from php.ini manually, the bootstrap file try to include itself.

  • FIX: The custom logo wasn't displayed on the login lockdown unlock request form.

  • TWEAK: Allow taking database backups via the UpdraftPlus backup plugin.

  • TWEAK: Make lockout reasons more specific.

  • TWEAK: Update notice class.

  • TWEAK: If the user has not performed the cookie test, the brute force attack prevention configuration fields remain disabled in the Admin Dashboard > WP Security > Brute Force > Cookie Based Brute Force Prevention.

  • TWEAK: Display locked IP addresses lockout date and release date in WordPress settings format.

  • TWEAK: Improve success or messages when performing bulk actions on the table list.

  • TWEAK: 404 events date is displayed in WordPress settings format.

  • TWEAK: Account activity login date and logout date are displayed in WordPress settings format.

  • TWEAK: Add a label for each setting field.

  • TWEAK: JQMIGRATE: jQuery.fn.click() event shorthand is deprecated.

  • TWEAK: Fix typos at Admin Dashboard > WP Security > Firewall > Basic Firewall Rules > Block Access to Debug Log File.

Download this release

Release Info

Developer DavidAnderson
Plugin Icon 128x128 All In One WP Security & Firewall
Version 5.0.0
Comparing to
See all releases

Code changes from version 4.4.12 to 5.0.0

Files changed (114) hide show
  1. admin/general/wp-security-list-table.php +22 -1
  2. admin/wp-security-admin-init.php +46 -28
  3. admin/wp-security-admin-menu.php +20 -19
  4. admin/wp-security-blacklist-menu.php +7 -17
  5. admin/wp-security-brute-force-menu.php +153 -205
  6. admin/wp-security-dashboard-menu.php +25 -20
  7. admin/wp-security-database-menu.php +123 -189
  8. admin/wp-security-filescan-menu.php +16 -12
  9. admin/wp-security-filesystem-menu.php +6 -6
  10. admin/wp-security-firewall-menu.php +218 -78
  11. admin/wp-security-firewall-setup-notice.php +590 -0
  12. admin/wp-security-list-404.php +111 -79
  13. admin/wp-security-list-acct-activity.php +99 -86
  14. admin/wp-security-list-comment-spammer-ip.php +2 -2
  15. admin/wp-security-list-locked-ip.php +117 -94
  16. admin/wp-security-list-logged-in-users.php +1 -1
  17. admin/wp-security-list-login-fails.php +82 -85
  18. admin/wp-security-list-permanent-blocked-ip.php +22 -21
  19. admin/wp-security-maintenance-menu.php +3 -3
  20. admin/wp-security-misc-options-menu.php +15 -15
  21. admin/wp-security-settings-menu.php +136 -58
  22. admin/wp-security-spam-menu.php +79 -24
  23. admin/wp-security-tools-menu.php +239 -0
  24. admin/wp-security-user-accounts-menu.php +5 -5
  25. admin/wp-security-user-login-menu.php +232 -104
  26. admin/wp-security-user-registration-menu.php +8 -8
  27. classes/firewall/family/wp-security-firewall-families.php +9 -0
  28. classes/firewall/family/wp-security-firewall-family-builder.php +33 -0
  29. classes/firewall/family/wp-security-firewall-family-collection.php +49 -0
  30. classes/firewall/family/wp-security-firewall-family.php +86 -0
  31. classes/firewall/libs/wp-security-firewall-config.php +121 -0
  32. classes/firewall/rule/actions/action-exit-trait.php +17 -0
  33. classes/firewall/rule/actions/action-forbid-and-exit-trait.php +23 -0
  34. classes/firewall/rule/actions/action-forbid-trait.php +17 -0
  35. classes/firewall/rule/rules/rule-block-query-strings-6g.php +61 -0
  36. classes/firewall/rule/rules/rule-block-refs-6g.php +52 -0
  37. classes/firewall/rule/rules/rule-block-request-strings-6g.php +61 -0
  38. classes/firewall/rule/rules/rule-block-user-agents-6g.php +52 -0
  39. classes/firewall/rule/rules/rule-request-method-6g.php +53 -0
  40. classes/firewall/rule/wp-security-firewall-rule-builder.php +48 -0
  41. classes/firewall/rule/wp-security-firewall-rule-utils.php +29 -0
  42. classes/firewall/rule/wp-security-firewall-rule.php +90 -0
  43. classes/firewall/wp-security-firewall-loader.php +157 -0
  44. classes/firewall/wp-security-firewall.php +20 -0
  45. classes/grade-system/wp-security-feature-item-manager.php +27 -4
  46. classes/wp-security-abstract-ids.php +20 -0
  47. classes/wp-security-backup.php +0 -339
  48. classes/wp-security-base-tasks.php +34 -0
  49. classes/wp-security-block-bootstrap.php +169 -0
  50. classes/wp-security-block-file.php +193 -0
  51. classes/wp-security-block-htaccess.php +89 -0
  52. classes/wp-security-block-litespeed.php +29 -0
  53. classes/wp-security-block-muplugin.php +93 -0
  54. classes/wp-security-block-userini.php +96 -0
  55. classes/wp-security-block-wpconfig.php +94 -0
  56. classes/wp-security-blocking.php +5 -0
  57. classes/wp-security-captcha.php +4 -4
  58. classes/wp-security-cleanup.php +51 -0
  59. classes/wp-security-comment.php +45 -0
  60. classes/wp-security-configure-settings.php +31 -7
  61. classes/wp-security-cronjob-handler.php +64 -4
  62. classes/wp-security-deactivation-tasks.php +16 -43
  63. classes/wp-security-debug-logger.php +0 -41
  64. classes/wp-security-general-init-tasks.php +97 -11
  65. classes/wp-security-installer.php +36 -9
  66. classes/wp-security-notices.php +98 -2
  67. classes/wp-security-process-renamed-login-page.php +22 -11
  68. classes/wp-security-two-factor-login.php +104 -0
  69. classes/wp-security-uninstallation-tasks.php +83 -0
  70. classes/wp-security-user-login.php +184 -55
  71. classes/wp-security-utility-file.php +53 -4
  72. classes/wp-security-utility-firewall.php +186 -0
  73. classes/wp-security-utility-htaccess.php +12 -162
  74. classes/wp-security-utility-ip-address.php +47 -27
  75. classes/wp-security-utility.php +177 -54
  76. classes/wp-security-wp-footer-content.php +1 -1
  77. css/wp-security-admin-styles.css +8 -0
  78. includes/simba-tfa/includes/frontend-settings.js +126 -0
  79. includes/simba-tfa/includes/jquery-qrcode/README.md +40 -0
  80. includes/simba-tfa/includes/jquery-qrcode/jquery-qrcode.js +2332 -0
  81. includes/simba-tfa/includes/jquery-qrcode/jquery-qrcode.min.js +2 -0
  82. includes/simba-tfa/includes/jquery.blockUI.js +619 -0
  83. includes/simba-tfa/includes/jquery.blockUI.min.js +14 -0
  84. includes/simba-tfa/includes/login-form-integrations.php +129 -0
  85. includes/simba-tfa/includes/select2.css +482 -0
  86. includes/simba-tfa/includes/select2.js +5580 -0
  87. includes/simba-tfa/includes/select2.min.js +2 -0
  88. includes/simba-tfa/includes/tfa.js +218 -0
  89. includes/simba-tfa/includes/tfa_admin_icon_16x16.png +0 -0
  90. includes/simba-tfa/includes/tfa_admin_icon_32x32.png +0 -0
  91. includes/simba-tfa/includes/tfa_frontend.php +204 -0
  92. includes/simba-tfa/includes/users.css +13 -0
  93. includes/simba-tfa/providers/totp-hotp/Base32/Base32.php +225 -0
  94. includes/simba-tfa/providers/totp-hotp/hotp-php-master/LICENSE +24 -0
  95. includes/simba-tfa/providers/totp-hotp/hotp-php-master/README.markdown +27 -0
  96. includes/simba-tfa/providers/totp-hotp/hotp-php-master/example.php +160 -0
  97. includes/simba-tfa/providers/totp-hotp/hotp-php-master/hotp.php +174 -0
  98. includes/simba-tfa/providers/totp-hotp/loader.php +1015 -0
  99. includes/simba-tfa/simba-tfa.php +1250 -0
  100. includes/simba-tfa/templates/admin-settings.php +185 -0
  101. includes/simba-tfa/templates/settings-intro-notices.php +28 -0
  102. includes/simba-tfa/templates/shortcode-tfa-user-settings.php +26 -0
  103. includes/simba-tfa/templates/trusted-devices-inner-box.php +29 -0
  104. includes/simba-tfa/templates/user-settings.php +61 -0
  105. js/wp-security-admin-script.js +43 -3
  106. languages/all-in-one-wp-security-and-firewall-de_DE.po +11 -11
  107. languages/all-in-one-wp-security-and-firewall-fr_FR.po +9 -9
  108. languages/all-in-one-wp-security-and-firewall-hu_HU.po +9 -9
  109. languages/all-in-one-wp-security-and-firewall-ko_KR.po +9 -9
  110. languages/all-in-one-wp-security-and-firewall-nl_NL.po +11 -11
  111. languages/all-in-one-wp-security-and-firewall-pl_PL.po +11 -11
  112. languages/all-in-one-wp-security-and-firewall-pt_BR.po +11 -11
  113. languages/all-in-one-wp-security-and-firewall-ru_RU.po +9 -9
  114. languages/all-in-one-wp-security-and-firewall-sv_SE.po +5 -5
admin/general/wp-security-list-table.php CHANGED
@@ -477,7 +477,13 @@ class AIOWPSecurity_List_Table {
477
 
478
  echo "</select>\n";
479
 
480
- submit_button( __( 'Apply' ), 'action', '', false, array( 'id' => "doaction$two", 'onclick' => "return confirm('Are you sure you want to perform this bulk action?')" ) );
 
 
 
 
 
 
481
  echo "\n";
482
  }
483
 
@@ -1401,4 +1407,19 @@ class AIOWPSecurity_List_Table {
1401
 
1402
  printf( "<script>list_args = %s;</script>\n", wp_json_encode( $args ) );
1403
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1404
  }
477
 
478
  echo "</select>\n";
479
 
480
+ $submit_attributes = array('id' => "doaction$two");
481
+
482
+ if ('top' == $which) {
483
+ $submit_attributes['onclick'] = "return confirm('".esc_js(__('Are you sure you want to perform this bulk action?', 'all-in-one-wp-security-and-firewall'))."')";
484
+ }
485
+
486
+ submit_button(__('Apply'), 'action', '', false, $submit_attributes);
487
  echo "\n";
488
  }
489
 
1407
 
1408
  printf( "<script>list_args = %s;</script>\n", wp_json_encode( $args ) );
1409
  }
1410
+
1411
+ /**
1412
+ * Retrieves and returns current WP general settings date time format.
1413
+ *
1414
+ * @return String
1415
+ */
1416
+ protected function get_wp_date_time_format() {
1417
+ static $wp_date_time_format;
1418
+
1419
+ if (!isset($wp_date_time_format)) {
1420
+ $wp_date_time_format = get_option('date_format').' '.get_option('time_format');
1421
+ }
1422
+
1423
+ return $wp_date_time_format;
1424
+ }
1425
  }
admin/wp-security-admin-init.php CHANGED
@@ -44,10 +44,13 @@ class AIOWPSecurity_Admin_Init {
44
  */
45
  public function __construct() {
46
  //This class is only initialized if is_admin() is true
47
- $this->admin_includes();
48
- add_action('admin_menu', array($this, 'create_admin_menus'));
49
  //handle CSV download
50
- add_action('admin_init', array($this, 'aiowps_csv_download'));
 
 
 
 
51
 
52
  add_action('admin_init', array($this, 'hook_admin_notices'));
53
 
@@ -83,7 +86,7 @@ class AIOWPSecurity_Admin_Init {
83
  }
84
  }
85
 
86
- function aiowps_csv_download() {
87
  global $aio_wp_security;
88
  if (isset($_POST['aiowpsec_export_acct_activity_logs_to_csv'])) { //Export account activity logs
89
  $nonce = $_REQUEST['_wpnonce'];
@@ -156,7 +159,7 @@ class AIOWPSecurity_Admin_Init {
156
  return $this->is_aiowps_admin_page;
157
  }
158
  global $pagenow;
159
- $this->is_aiowps_admin_page = ('admin.php' == $pagenow && isset($_GET['page']) && false !== strpos($_GET['page'], AIOWPSEC_MENU_SLUG_PREFIX));
160
  return $this->is_aiowps_admin_page;
161
  }
162
 
@@ -200,6 +203,8 @@ class AIOWPSecurity_Admin_Init {
200
  public function render_admin_notices() {
201
  global $aio_wp_security;
202
 
 
 
203
  $installed_at = $aio_wp_security->notices->get_aiowps_plugin_installed_timestamp();
204
  $time_now = $aio_wp_security->notices->get_time_now();
205
  $installed_for = $time_now - $installed_at;
@@ -218,17 +223,25 @@ class AIOWPSecurity_Admin_Init {
218
  include_once('wp-security-admin-menu.php');
219
  }
220
 
221
- function admin_menu_page_scripts()
222
- {
 
 
 
 
 
 
 
 
223
  wp_enqueue_script('jquery');
224
  wp_enqueue_script('postbox');
225
  wp_enqueue_script('dashboard');
226
  wp_enqueue_script('thickbox');
227
  wp_enqueue_script('media-upload');
228
- wp_register_script('aiowpsec-admin-js', AIO_WP_SECURITY_URL. '/js/wp-security-admin-script.js', array('jquery'));
229
  wp_enqueue_script('aiowpsec-admin-js');
230
  wp_register_script('aiowpsec-pw-tool-js', AIO_WP_SECURITY_URL. '/js/password-strength-tool.js', array('jquery')); // We will enqueue this in the user acct menu class
231
- }
232
 
233
  function admin_menu_page_styles()
234
  {
@@ -236,7 +249,8 @@ class AIOWPSecurity_Admin_Init {
236
  wp_enqueue_style('thickbox');
237
  wp_enqueue_style('global');
238
  wp_enqueue_style('wp-admin');
239
- wp_enqueue_style('aiowpsec-admin-css', AIO_WP_SECURITY_URL. '/css/wp-security-admin-styles.css');
 
240
  }
241
 
242
  function init_hook_handler_for_admin_side()
@@ -302,22 +316,15 @@ class AIOWPSecurity_Admin_Init {
302
  //The old "test cookie" used to be too easy to guess because someone could just read the code and get the value.
303
  //So now we will drop a more secure test cookie using a 10 digit random string
304
 
305
- if($aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention')=='1'){
306
- // This code is for users who had this feature saved using an older release. This will drop the new more secure test cookie to the browser and will write it to the .htaccess file too
307
- $test_cookie = $aio_wp_security->configs->get_value('aiowps_cookie_brute_test');
308
- if(empty($test_cookie)){
309
  $random_suffix = AIOWPSecurity_Utility::generate_alpha_numeric_random_string(10);
310
  $test_cookie_name = 'aiowps_cookie_test_'.$random_suffix;
311
  $aio_wp_security->configs->set_value('aiowps_cookie_brute_test',$test_cookie_name);
312
  $aio_wp_security->configs->save_config();//save the value
313
- AIOWPSecurity_Utility::set_cookie_value($test_cookie_name, "1");
314
-
315
- //Write this new cookie to the .htaccess file
316
- $res = AIOWPSecurity_Utility_Htaccess::write_to_htaccess();
317
- if( !$res ){
318
- $aio_wp_security->debug_logger->log_debug("Error writing new test cookie with random suffix to .htaccess file!",4);
319
- }
320
-
321
  }
322
  }
323
  //For cookie test form submission case
@@ -329,7 +336,7 @@ class AIOWPSecurity_Admin_Init {
329
  $test_cookie_name = 'aiowps_cookie_test_'.$random_suffix;
330
  $aio_wp_security->configs->set_value('aiowps_cookie_brute_test',$test_cookie_name);
331
  $aio_wp_security->configs->save_config();//save the value
332
- AIOWPSecurity_Utility::set_cookie_value($test_cookie_name, "1");
333
  $cur_url = "admin.php?page=".AIOWPSEC_BRUTE_FORCE_MENU_SLUG."&tab=tab2";
334
  $redirect_url = AIOWPSecurity_Utility::add_query_data_to_url($cur_url, 'aiowps_cookie_test', "1");
335
  AIOWPSecurity_Utility::redirect_to_url($redirect_url);
@@ -399,30 +406,31 @@ class AIOWPSecurity_Admin_Init {
399
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('User Login', 'all-in-one-wp-security-and-firewall'), __('User Login', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_USER_LOGIN_MENU_SLUG, array($this, 'handle_user_login_menu_rendering'));
400
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('User Registration', 'all-in-one-wp-security-and-firewall'), __('User Registration', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_USER_REGISTRATION_MENU_SLUG, array($this, 'handle_user_registration_menu_rendering'));
401
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('Database Security', 'all-in-one-wp-security-and-firewall'), __('Database Security', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_DB_SEC_MENU_SLUG, array($this, 'handle_database_menu_rendering'));
402
- if (AIOWPSecurity_Utility::is_multisite_install() && get_current_blog_id() != 1){
403
  //Suppress the Filesystem Security menu if site is a multi site AND not the main site
404
  }else{
405
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('Filesystem Security', 'all-in-one-wp-security-and-firewall'), __('Filesystem Security', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_FILESYSTEM_MENU_SLUG, array($this, 'handle_filesystem_menu_rendering'));
406
  }
407
- if (AIOWPSecurity_Utility::is_multisite_install() && get_current_blog_id() != 1){
408
  //Suppress the Blacklist Manager menu if site is a multi site AND not the main site
409
  }else{
410
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('Blacklist Manager', 'all-in-one-wp-security-and-firewall'), __('Blacklist Manager', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_BLACKLIST_MENU_SLUG, array($this, 'handle_blacklist_menu_rendering'));
411
  }
412
- if (AIOWPSecurity_Utility::is_multisite_install() && get_current_blog_id() != 1){
413
  //Suppress the firewall menu if site is a multi site AND not the main site
414
  }else{
415
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('Firewall', 'all-in-one-wp-security-and-firewall'), __('Firewall', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_FIREWALL_MENU_SLUG, array($this, 'handle_firewall_menu_rendering'));
416
  }
417
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('Brute Force', 'all-in-one-wp-security-and-firewall'), __('Brute Force', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_BRUTE_FORCE_MENU_SLUG, array($this, 'handle_brute_force_menu_rendering'));
418
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('SPAM Prevention', 'all-in-one-wp-security-and-firewall'), __('SPAM Prevention', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_SPAM_MENU_SLUG, array($this, 'handle_spam_menu_rendering'));
419
- if (AIOWPSecurity_Utility::is_multisite_install() && get_current_blog_id() != 1){
420
  //Suppress the filescan menu if site is a multi site AND not the main site
421
  }else{
422
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('Scanner', 'all-in-one-wp-security-and-firewall'), __('Scanner', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_FILESCAN_MENU_SLUG, array($this, 'handle_filescan_menu_rendering'));
423
  }
424
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('Maintenance', 'all-in-one-wp-security-and-firewall'), __('Maintenance', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_MAINTENANCE_MENU_SLUG, array($this, 'handle_maintenance_menu_rendering'));
425
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('Miscellaneous', 'all-in-one-wp-security-and-firewall'), __('Miscellaneous', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_MISC_MENU_SLUG, array($this, 'handle_misc_menu_rendering'));
 
426
  do_action('aiowpsecurity_admin_menu_created');
427
  }
428
 
@@ -510,6 +518,16 @@ class AIOWPSecurity_Admin_Init {
510
  include_once('wp-security-misc-options-menu.php');
511
  $this->misc_menu = new AIOWPSecurity_Misc_Options_Menu();
512
  }
513
-
 
 
 
 
 
 
 
 
 
 
514
  }//End of class
515
 
44
  */
45
  public function __construct() {
46
  //This class is only initialized if is_admin() is true
47
+
 
48
  //handle CSV download
49
+ if (current_user_can(AIOWPSEC_MANAGEMENT_PERMISSION)) {
50
+ $this->admin_includes();
51
+ add_action('admin_menu', array($this, 'create_admin_menus'));
52
+ add_action('admin_init', array($this, 'aiowps_csv_download'));
53
+ }
54
 
55
  add_action('admin_init', array($this, 'hook_admin_notices'));
56
 
86
  }
87
  }
88
 
89
+ public function aiowps_csv_download() {
90
  global $aio_wp_security;
91
  if (isset($_POST['aiowpsec_export_acct_activity_logs_to_csv'])) { //Export account activity logs
92
  $nonce = $_REQUEST['_wpnonce'];
159
  return $this->is_aiowps_admin_page;
160
  }
161
  global $pagenow;
162
+ $this->is_aiowps_admin_page = (current_user_can(AIOWPSEC_MANAGEMENT_PERMISSION) && 'admin.php' == $pagenow && isset($_GET['page']) && false !== strpos($_GET['page'], AIOWPSEC_MENU_SLUG_PREFIX));
163
  return $this->is_aiowps_admin_page;
164
  }
165
 
203
  public function render_admin_notices() {
204
  global $aio_wp_security;
205
 
206
+ $aio_wp_security->notices->do_notice('automated-database-backup', 'automated-database-backup');
207
+
208
  $installed_at = $aio_wp_security->notices->get_aiowps_plugin_installed_timestamp();
209
  $time_now = $aio_wp_security->notices->get_time_now();
210
  $installed_for = $time_now - $installed_at;
223
  include_once('wp-security-admin-menu.php');
224
  }
225
 
226
+ /**
227
+ * Enqueue admin JavaScripts.
228
+ *
229
+ * @return Void
230
+ */
231
+ public function admin_menu_page_scripts() {
232
+ if (!AIOWPSecurity_Utility::has_manage_cap()) {
233
+ return;
234
+ }
235
+
236
  wp_enqueue_script('jquery');
237
  wp_enqueue_script('postbox');
238
  wp_enqueue_script('dashboard');
239
  wp_enqueue_script('thickbox');
240
  wp_enqueue_script('media-upload');
241
+ wp_register_script('aiowpsec-admin-js', AIO_WP_SECURITY_URL. '/js/wp-security-admin-script.js', array('jquery'), AIO_WP_SECURITY_VERSION, true);
242
  wp_enqueue_script('aiowpsec-admin-js');
243
  wp_register_script('aiowpsec-pw-tool-js', AIO_WP_SECURITY_URL. '/js/password-strength-tool.js', array('jquery')); // We will enqueue this in the user acct menu class
244
+ }
245
 
246
  function admin_menu_page_styles()
247
  {
249
  wp_enqueue_style('thickbox');
250
  wp_enqueue_style('global');
251
  wp_enqueue_style('wp-admin');
252
+ $admin_css_version = (defined('WP_DEBUG') && WP_DEBUG) ? time() : filemtime(AIO_WP_SECURITY_PATH. '/css/wp-security-admin-styles.css');
253
+ wp_enqueue_style('aiowpsec-admin-css', AIO_WP_SECURITY_URL. '/css/wp-security-admin-styles.css', array(), $admin_css_version);
254
  }
255
 
256
  function init_hook_handler_for_admin_side()
316
  //The old "test cookie" used to be too easy to guess because someone could just read the code and get the value.
317
  //So now we will drop a more secure test cookie using a 10 digit random string
318
 
319
+ if ('1' == $aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention')) {
320
+ // This code is for users who had this feature saved using an older release. This will drop the new more secure test cookie to the browser
321
+ $test_cookie_name_saved = $aio_wp_security->configs->get_value('aiowps_cookie_brute_test');
322
+ if (empty($test_cookie_name_saved)) {
323
  $random_suffix = AIOWPSecurity_Utility::generate_alpha_numeric_random_string(10);
324
  $test_cookie_name = 'aiowps_cookie_test_'.$random_suffix;
325
  $aio_wp_security->configs->set_value('aiowps_cookie_brute_test',$test_cookie_name);
326
  $aio_wp_security->configs->save_config();//save the value
327
+ AIOWPSecurity_Utility::set_cookie_value($test_cookie_name, '1');
 
 
 
 
 
 
 
328
  }
329
  }
330
  //For cookie test form submission case
336
  $test_cookie_name = 'aiowps_cookie_test_'.$random_suffix;
337
  $aio_wp_security->configs->set_value('aiowps_cookie_brute_test',$test_cookie_name);
338
  $aio_wp_security->configs->save_config();//save the value
339
+ AIOWPSecurity_Utility::set_cookie_value($test_cookie_name, '1');
340
  $cur_url = "admin.php?page=".AIOWPSEC_BRUTE_FORCE_MENU_SLUG."&tab=tab2";
341
  $redirect_url = AIOWPSecurity_Utility::add_query_data_to_url($cur_url, 'aiowps_cookie_test', "1");
342
  AIOWPSecurity_Utility::redirect_to_url($redirect_url);
406
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('User Login', 'all-in-one-wp-security-and-firewall'), __('User Login', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_USER_LOGIN_MENU_SLUG, array($this, 'handle_user_login_menu_rendering'));
407
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('User Registration', 'all-in-one-wp-security-and-firewall'), __('User Registration', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_USER_REGISTRATION_MENU_SLUG, array($this, 'handle_user_registration_menu_rendering'));
408
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('Database Security', 'all-in-one-wp-security-and-firewall'), __('Database Security', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_DB_SEC_MENU_SLUG, array($this, 'handle_database_menu_rendering'));
409
+ if (is_multisite() && get_current_blog_id() != 1){
410
  //Suppress the Filesystem Security menu if site is a multi site AND not the main site
411
  }else{
412
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('Filesystem Security', 'all-in-one-wp-security-and-firewall'), __('Filesystem Security', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_FILESYSTEM_MENU_SLUG, array($this, 'handle_filesystem_menu_rendering'));
413
  }
414
+ if (is_multisite() && get_current_blog_id() != 1){
415
  //Suppress the Blacklist Manager menu if site is a multi site AND not the main site
416
  }else{
417
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('Blacklist Manager', 'all-in-one-wp-security-and-firewall'), __('Blacklist Manager', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_BLACKLIST_MENU_SLUG, array($this, 'handle_blacklist_menu_rendering'));
418
  }
419
+ if (is_multisite() && get_current_blog_id() != 1){
420
  //Suppress the firewall menu if site is a multi site AND not the main site
421
  }else{
422
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('Firewall', 'all-in-one-wp-security-and-firewall'), __('Firewall', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_FIREWALL_MENU_SLUG, array($this, 'handle_firewall_menu_rendering'));
423
  }
424
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('Brute Force', 'all-in-one-wp-security-and-firewall'), __('Brute Force', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_BRUTE_FORCE_MENU_SLUG, array($this, 'handle_brute_force_menu_rendering'));
425
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('SPAM Prevention', 'all-in-one-wp-security-and-firewall'), __('SPAM Prevention', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_SPAM_MENU_SLUG, array($this, 'handle_spam_menu_rendering'));
426
+ if (is_multisite() && get_current_blog_id() != 1){
427
  //Suppress the filescan menu if site is a multi site AND not the main site
428
  }else{
429
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('Scanner', 'all-in-one-wp-security-and-firewall'), __('Scanner', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_FILESCAN_MENU_SLUG, array($this, 'handle_filescan_menu_rendering'));
430
  }
431
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('Maintenance', 'all-in-one-wp-security-and-firewall'), __('Maintenance', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_MAINTENANCE_MENU_SLUG, array($this, 'handle_maintenance_menu_rendering'));
432
  add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('Miscellaneous', 'all-in-one-wp-security-and-firewall'), __('Miscellaneous', 'all-in-one-wp-security-and-firewall') , AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_MISC_MENU_SLUG, array($this, 'handle_misc_menu_rendering'));
433
+ add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('Tools', 'all-in-one-wp-security-and-firewall'), __('Tools', 'all-in-one-wp-security-and-firewall'), AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_TOOLS_MENU_SLUG, array($this, 'handle_tools_menu_rendering'));
434
  do_action('aiowpsecurity_admin_menu_created');
435
  }
436
 
518
  include_once('wp-security-misc-options-menu.php');
519
  $this->misc_menu = new AIOWPSecurity_Misc_Options_Menu();
520
  }
521
+
522
+ /**
523
+ * Renders 'Tools' submenu first tab page.
524
+ *
525
+ * @return Void
526
+ */
527
+ public function handle_tools_menu_rendering() {
528
+ include_once(AIO_WP_SECURITY_PATH.'/admin/wp-security-tools-menu.php');
529
+ new AIOWPSecurity_Tools_Menu();
530
+ }
531
+
532
  }//End of class
533
 
admin/wp-security-admin-menu.php CHANGED
@@ -67,14 +67,25 @@ abstract class AIOWPSecurity_Admin_Menu
67
  _e('Settings successfully updated.','all-in-one-wp-security-and-firewall');
68
  echo '</strong></p></div>';
69
  }
70
-
71
- static function show_msg_record_deleted_st()
72
- {
73
- echo '<div id="message" class="updated fade"><p><strong>';
74
- _e('The selected record(s) deleted successfully!','all-in-one-wp-security-and-firewall');
75
- echo '</strong></p></div>';
76
- }
77
-
 
 
 
 
 
 
 
 
 
 
 
78
  function show_msg_updated($msg)
79
  {
80
  echo '<div id="message" class="updated fade"><p><strong>';
@@ -114,15 +125,5 @@ abstract class AIOWPSecurity_Admin_Menu
114
  ob_end_clean();
115
  return $output;
116
  }
117
-
118
- static function display_bulk_result_message()
119
- {
120
- if(isset($_GET['bulk_count'])) {
121
- AIOWPSecurity_Admin_Menu::show_msg_updated_st(__('The bulk action was successful', 'all-in-one-wp-security-and-firewall'));
122
- }
123
-
124
- if(isset($_GET['bulk_error'])) {
125
- AIOWPSecurity_Admin_Menu::show_msg_error_st(__('The bulk action failed', 'all-in-one-wp-security-and-firewall'));
126
- }
127
- }
128
  }
67
  _e('Settings successfully updated.','all-in-one-wp-security-and-firewall');
68
  echo '</strong></p></div>';
69
  }
70
+
71
+ /**
72
+ * Renders record(s) successfully deleted message at top of page.
73
+ *
74
+ * @return Void
75
+ */
76
+ public static function show_msg_record_deleted_st() {
77
+ AIOWPSecurity_Admin_Menu::show_msg_updated_st(__('Successfully deleted the selected record(s).', 'all-in-one-wp-security-and-firewall'));
78
+ }
79
+
80
+ /**
81
+ * Renders record(s) unsuccessfully deleted message at top of page.
82
+ *
83
+ * @return Void
84
+ */
85
+ public static function show_msg_record_not_deleted_st() {
86
+ AIOWPSecurity_Admin_Menu::show_msg_error_st(__('Failed to delete the selected record(s).', 'all-in-one-wp-security-and-firewall'));
87
+ }
88
+
89
  function show_msg_updated($msg)
90
  {
91
  echo '<div id="message" class="updated fade"><p><strong>';
125
  ob_end_clean();
126
  return $output;
127
  }
128
+
 
 
 
 
 
 
 
 
 
 
129
  }
admin/wp-security-blacklist-menu.php CHANGED
@@ -185,33 +185,23 @@ class AIOWPSecurity_Blacklist_Menu extends AIOWPSecurity_Admin_Menu
185
  <tr valign="top">
186
  <th scope="row"><?php _e('Enable IP or User Agent Blacklisting', 'all-in-one-wp-security-and-firewall')?>:</th>
187
  <td>
188
- <input name="aiowps_enable_blacklisting" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_blacklisting')=='1') echo ' checked="checked"'; ?> value="1"/>
189
- <span class="description"><?php _e('Check this if you want to enable the banning (or blacklisting) of selected IP addresses and/or user agents specified in the settings below', 'all-in-one-wp-security-and-firewall'); ?></span>
190
  </td>
191
  </tr>
192
  <tr valign="top">
193
- <th scope="row"><?php _e('Enter IP Addresses:', 'all-in-one-wp-security-and-firewall')?></th>
194
  <td>
195
- <textarea name="aiowps_banned_ip_addresses" rows="5" cols="50"><?php echo ($result == -1)?htmlspecialchars($_POST['aiowps_banned_ip_addresses']):htmlspecialchars($aio_wp_security->configs->get_value('aiowps_banned_ip_addresses')); ?></textarea>
196
  <br />
197
  <span class="description"><?php _e('Enter one or more IP addresses or IP ranges.','all-in-one-wp-security-and-firewall');?></span>
198
- <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
199
- <div class="aiowps_more_info_body">
200
- <?php
201
- echo '<p class="description">'.__('Each IP address must be on a new line.', 'all-in-one-wp-security-and-firewall').'</p>';
202
- echo '<p class="description">'.__('To specify an IP range use a wildcard "*" character. Acceptable ways to use wildcards is shown in the examples below:', 'all-in-one-wp-security-and-firewall').'</p>';
203
- echo '<p class="description">'.__('Example 1: 195.47.89.*', 'all-in-one-wp-security-and-firewall').'</p>';
204
- echo '<p class="description">'.__('Example 2: 195.47.*.*', 'all-in-one-wp-security-and-firewall').'</p>';
205
- echo '<p class="description">'.__('Example 3: 195.*.*.*', 'all-in-one-wp-security-and-firewall').'</p>';
206
- ?>
207
- </div>
208
-
209
  </td>
210
  </tr>
211
  <tr valign="top">
212
- <th scope="row"><?php _e('Enter User Agents:', 'all-in-one-wp-security-and-firewall')?></th>
213
  <td>
214
- <textarea name="aiowps_banned_user_agents" rows="5" cols="50"><?php echo ($result == -1)?htmlspecialchars($_POST['aiowps_banned_user_agents']):htmlspecialchars($aio_wp_security->configs->get_value('aiowps_banned_user_agents')); ?></textarea>
215
  <br />
216
  <span class="description">
217
  <?php _e('Enter one or more user agent strings.','all-in-one-wp-security-and-firewall');?></span>
185
  <tr valign="top">
186
  <th scope="row"><?php _e('Enable IP or User Agent Blacklisting', 'all-in-one-wp-security-and-firewall')?>:</th>
187
  <td>
188
+ <input id="aiowps_enable_blacklisting" name="aiowps_enable_blacklisting" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_blacklisting')=='1') echo ' checked="checked"'; ?> value="1"/>
189
+ <label for="aiowps_enable_blacklisting" class="description"><?php _e('Check this if you want to enable the banning (or blacklisting) of selected IP addresses and/or user agents specified in the settings below', 'all-in-one-wp-security-and-firewall'); ?></label>
190
  </td>
191
  </tr>
192
  <tr valign="top">
193
+ <th scope="row"><label for="aiowps_banned_ip_addresses"><?php _e('Enter IP Addresses:', 'all-in-one-wp-security-and-firewall')?></label></th>
194
  <td>
195
+ <textarea id="aiowps_banned_ip_addresses" name="aiowps_banned_ip_addresses" rows="5" cols="50"><?php echo ($result == -1)?htmlspecialchars($_POST['aiowps_banned_ip_addresses']):htmlspecialchars($aio_wp_security->configs->get_value('aiowps_banned_ip_addresses')); ?></textarea>
196
  <br />
197
  <span class="description"><?php _e('Enter one or more IP addresses or IP ranges.','all-in-one-wp-security-and-firewall');?></span>
198
+ <?php $aio_wp_security->include_template('info/ip-address-ip-range-info.php');?>
 
 
 
 
 
 
 
 
 
 
199
  </td>
200
  </tr>
201
  <tr valign="top">
202
+ <th scope="row"><label for="aiowps_banned_user_agents"><?php _e('Enter User Agents:', 'all-in-one-wp-security-and-firewall')?></label></th>
203
  <td>
204
+ <textarea id="aiowps_banned_user_agents" name="aiowps_banned_user_agents" rows="5" cols="50"><?php echo ($result == -1)?htmlspecialchars($_POST['aiowps_banned_user_agents']):htmlspecialchars($aio_wp_security->configs->get_value('aiowps_banned_user_agents')); ?></textarea>
205
  <br />
206
  <span class="description">
207
  <?php _e('Enter one or more user agent strings.','all-in-one-wp-security-and-firewall');?></span>
admin/wp-security-brute-force-menu.php CHANGED
@@ -10,16 +10,20 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
10
  /* Specify all the tabs of this menu in the following array */
11
  var $menu_tabs;
12
 
13
- var $menu_tabs_handler = array(
 
 
 
 
 
14
  'tab1' => 'render_tab1',
15
  'tab2' => 'render_tab2',
16
  'tab3' => 'render_tab3',
17
  'tab4' => 'render_tab4',
18
  'tab5' => 'render_tab5',
19
- );
20
 
21
- function __construct()
22
- {
23
  $this->render_menu_page();
24
  }
25
 
@@ -31,7 +35,6 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
31
  'tab3' => __('Login Captcha', 'all-in-one-wp-security-and-firewall'),
32
  'tab4' => __('Login Whitelist', 'all-in-one-wp-security-and-firewall'),
33
  'tab5' => __('Honeypot', 'all-in-one-wp-security-and-firewall'),
34
-
35
  );
36
  }
37
 
@@ -45,7 +48,7 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
45
  echo '<h2 class="nav-tab-wrapper">';
46
  foreach ( $this->menu_tabs as $tab_key => $tab_caption )
47
  {
48
- if (AIOWPSecurity_Utility::is_multisite_install() && get_current_blog_id() != 1
49
  && stristr($tab_caption, "Rename Login Page") === false && stristr($tab_caption, "Login Captcha") === false){
50
  //Suppress the all Brute Force menu tabs if site is a multi site AND not the main site except "rename login" and "captcha"
51
  }else{
@@ -84,9 +87,9 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
84
  $aiowps_login_page_slug = '';
85
 
86
  if (get_option('permalink_structure')){
87
- $home_url = trailingslashit(home_url());
88
  }else{
89
- $home_url = trailingslashit(home_url()) . '?';
90
  }
91
 
92
  if(isset($_POST['aiowps_save_rename_login_page_settings']))//Do form submission tasks
@@ -95,8 +98,8 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
95
  $nonce=$_REQUEST['_wpnonce'];
96
  if (!wp_verify_nonce($nonce, 'aiowpsec-rename-login-page-nonce'))
97
  {
98
- $aio_wp_security->debug_logger->log_debug("Nonce check failed for rename login page save!",4);
99
- die("Nonce check failed for rename login page save!");
100
  }
101
 
102
  if (empty($_POST['aiowps_login_page_slug']) && isset($_POST["aiowps_enable_rename_login_page"])){
@@ -118,21 +121,12 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
118
  //Save all the form values to the options
119
  if (isset($_POST["aiowps_enable_rename_login_page"])){
120
  $aio_wp_security->configs->set_value('aiowps_enable_rename_login_page', '1');
121
- // check if the cookie based feature was active and deactivate it and delete the directives in .htaccess
122
- if($aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention')){
123
- $cookie_feature_active = true;
124
- $aio_wp_security->configs->set_value('aiowps_enable_brute_force_attack_prevention', '');//deactivate cookie based feature
125
- }
126
  }else{
127
  $aio_wp_security->configs->set_value('aiowps_enable_rename_login_page', '');
128
  }
129
  $aio_wp_security->configs->set_value('aiowps_login_page_slug',$aiowps_login_page_slug);
130
  $aio_wp_security->configs->save_config();
131
 
132
- // if cookie based feature was active previously need to clear those rules out of .htaccess
133
- if($cookie_feature_active){
134
- $htaccess_res = AIOWPSecurity_Utility_Htaccess::write_to_htaccess(); //Delete the cookie based directives
135
- }
136
 
137
  //Recalculate points after the feature status/options have been altered
138
  $aiowps_feature_mgr->check_feature_status_and_recalculate_points();
@@ -178,8 +172,7 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
178
  <div class="aio_yellow_box">
179
  <p><?php _e('Your WordPress login page URL has been renamed.', 'all-in-one-wp-security-and-firewall'); ?></p>
180
  <p><?php _e('Your current login URL is:', 'all-in-one-wp-security-and-firewall'); ?></p>
181
- <p><strong><?php echo $home_url.$aio_wp_security->configs->get_value('aiowps_login_page_slug'); ?></strong></p>
182
- <p><strong><?php _e('NOTE: If you already had the Cookie-Based Brute Force Prevention feature active, the plugin has automatically deactivated it because only one of these features can be active at any one time.', 'all-in-one-wp-security-and-firewall'); ?></strong></p>
183
  </div>
184
 
185
  <?php
@@ -207,13 +200,13 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
207
  <tr valign="top">
208
  <th scope="row"><?php _e('Enable Rename Login Page Feature', 'all-in-one-wp-security-and-firewall')?>:</th>
209
  <td>
210
- <input name="aiowps_enable_rename_login_page" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_rename_login_page')=='1') echo ' checked="checked"'; ?> value="1"/>
211
- <span class="description"><?php _e('Check this if you want to enable the rename login page feature', 'all-in-one-wp-security-and-firewall'); ?></span>
212
  </td>
213
  </tr>
214
  <tr valign="top">
215
- <th scope="row"><?php _e('Login Page URL', 'all-in-one-wp-security-and-firewall')?>:</th>
216
- <td><code><?php echo $home_url; ?></code><input type="text" size="15" name="aiowps_login_page_slug" value="<?php echo $aio_wp_security->configs->get_value('aiowps_login_page_slug'); ?>" />
217
  <span class="description"><?php _e('Enter a string which will represent your secure login page slug. You are encouraged to choose something which is hard to guess and only you will remember.', 'all-in-one-wp-security-and-firewall'); ?></span>
218
  </td>
219
  </tr>
@@ -225,101 +218,79 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
225
  <?php
226
  }
227
 
228
- function render_tab2()
229
- {
 
 
 
 
230
  global $aio_wp_security;
231
  global $aiowps_feature_mgr;
232
  $error = false;
 
233
 
234
  //Save settings for brute force cookie method
235
- if(isset($_POST['aiowps_apply_cookie_based_bruteforce_firewall']))
236
- {
237
- $nonce=$_REQUEST['_wpnonce'];
238
- if (!wp_verify_nonce($nonce, 'aiowpsec-enable-cookie-based-brute-force-prevention'))
239
- {
240
- $aio_wp_security->debug_logger->log_debug("Nonce check failed on enable cookie based brute force prevention feature!",4);
241
- die("Nonce check failed on enable cookie based brute force prevention feature!");
242
  }
243
 
244
- if(isset($_POST['aiowps_enable_brute_force_attack_prevention']))
245
- {
246
  $brute_force_feature_secret_word = sanitize_text_field($_POST['aiowps_brute_force_secret_word']);
247
- if(empty($brute_force_feature_secret_word)){
248
- $brute_force_feature_secret_word = "aiowpssecret";
249
- }else if(!ctype_alnum($brute_force_feature_secret_word)){
250
  $msg = '<p>'.__('Settings have not been saved - your secret word must consist only of alphanumeric characters, ie, letters and/or numbers only!', 'all-in-one-wp-security-and-firewall').'</p>';
251
  $error = true;
252
  }
253
 
254
- if(filter_var($_POST['aiowps_cookie_based_brute_force_redirect_url'], FILTER_VALIDATE_URL))
255
- {
256
- $aio_wp_security->configs->set_value('aiowps_cookie_based_brute_force_redirect_url',esc_url_raw($_POST['aiowps_cookie_based_brute_force_redirect_url']));
257
- }
258
- else
259
- {
260
- $aio_wp_security->configs->set_value('aiowps_cookie_based_brute_force_redirect_url','http://127.0.0.1');
261
  }
 
262
 
263
- $aio_wp_security->configs->set_value('aiowps_enable_brute_force_attack_prevention','1');
264
- $aio_wp_security->configs->set_value('aiowps_enable_rename_login_page',''); //Disable the Rename Login Page feature
265
-
266
- if (!$error)
267
- {
268
- $aio_wp_security->configs->set_value('aiowps_brute_force_secret_word',$brute_force_feature_secret_word);
269
  $msg = '<p>'.__('You have successfully enabled the cookie based brute force prevention feature', 'all-in-one-wp-security-and-firewall').'</p>';
270
  $msg .= '<p>'.__('From now on you will need to log into your WP Admin using the following URL:', 'all-in-one-wp-security-and-firewall').'</p>';
271
  $msg .= '<p><strong>'.AIOWPSEC_WP_URL.'/?'.$brute_force_feature_secret_word.'=1</strong></p>';
272
  $msg .= '<p>'.__('It is important that you save this URL value somewhere in case you forget it, OR,', 'all-in-one-wp-security-and-firewall').'</p>';
273
  $msg .= '<p>'.sprintf( __('simply remember to add a "?%s=1" to your current site URL address.', 'all-in-one-wp-security-and-firewall'), $brute_force_feature_secret_word).'</p>';
274
  }
275
- }
276
- else
277
- {
278
- $aio_wp_security->configs->set_value('aiowps_enable_brute_force_attack_prevention','');
279
  $msg = __('You have successfully saved cookie based brute force prevention feature settings.', 'all-in-one-wp-security-and-firewall');
280
  }
281
 
282
- if(isset($_POST['aiowps_brute_force_attack_prevention_pw_protected_exception']))
283
- {
284
- $aio_wp_security->configs->set_value('aiowps_brute_force_attack_prevention_pw_protected_exception','1');
285
- }
286
- else
287
- {
288
- $aio_wp_security->configs->set_value('aiowps_brute_force_attack_prevention_pw_protected_exception','');
289
  }
290
 
291
- if(isset($_POST['aiowps_brute_force_attack_prevention_ajax_exception']))
292
- {
293
- $aio_wp_security->configs->set_value('aiowps_brute_force_attack_prevention_ajax_exception','1');
294
- }
295
- else
296
- {
297
- $aio_wp_security->configs->set_value('aiowps_brute_force_attack_prevention_ajax_exception','');
298
  }
299
 
300
- if (!$error)
301
- {
302
  $aio_wp_security->configs->save_config();//save the value
303
 
304
  //Recalculate points after the feature status/options have been altered
305
  $aiowps_feature_mgr->check_feature_status_and_recalculate_points();
306
-
307
- $res = AIOWPSecurity_Utility_Htaccess::write_to_htaccess();
308
- if ($res) {
309
- echo '<div id="message" class="updated fade"><p>';
310
- echo $msg;
311
- echo '</p></div>';
312
- }
313
- else {
314
- $this->show_msg_error(__('Could not write to the .htaccess file. Please check the file permissions.', 'all-in-one-wp-security-and-firewall'));
315
  }
316
- }
317
- else
318
- {
319
  $this->show_msg_error($msg);
320
  }
321
  }
322
-
323
  ?>
324
  <h2><?php _e('Brute Force Prevention Firewall Settings', 'all-in-one-wp-security-and-firewall')?></h2>
325
 
@@ -328,35 +299,18 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
328
  //TODO - need to fix the following message
329
  echo '<p>'.__('A Brute Force Attack is when a hacker tries many combinations of usernames and passwords until they succeed in guessing the right combination.', 'all-in-one-wp-security-and-firewall').
330
  '<br />'.__('Due to the fact that at any one time there may be many concurrent login attempts occurring on your site via malicious automated robots, this also has a negative impact on your server\'s memory and performance.', 'all-in-one-wp-security-and-firewall').
331
- '<br />'.__('The features in this tab will stop the majority of Brute Force Login Attacks at the .htaccess level thus providing even better protection for your WP login page and also reducing the load on your server because the system does not have to run PHP code to process the login attempts.', 'all-in-one-wp-security-and-firewall').'</p>';
332
  ?>
333
  </div>
334
  <div class="aio_yellow_box">
335
  <?php
336
  $backup_tab_link = '<a href="admin.php?page='.AIOWPSEC_SETTINGS_MENU_SLUG.'&tab=tab2" target="_blank">'.__('backup', 'all-in-one-wp-security-and-firewall').'</a>';
337
  $video_link = '<a href="https://www.tipsandtricks-hq.com/all-in-one-wp-security-plugin-cookie-based-brute-force-login-attack-prevention-feature-5994" target="_blank">'.__('video tutorial', 'all-in-one-wp-security-and-firewall').'</a>';
338
- $info_msg = sprintf( __('Even though this feature should not have any impact on your site\'s general functionality <strong>you are strongly encouraged to take a %s of your .htaccess file before proceeding</strong>.', 'all-in-one-wp-security-and-firewall'), $backup_tab_link);
339
- $info_msg1 = __('If this feature is not used correctly, you can get locked out of your site. A backed up .htaccess file will come in handy if that happens.', 'all-in-one-wp-security-and-firewall');
340
- $info_msg2 = sprintf( __('To learn more about how to use this feature please watch the following %s.', 'all-in-one-wp-security-and-firewall'), $video_link);
341
  $brute_force_login_feature_link = '<a href="admin.php?page='.AIOWPSEC_FIREWALL_MENU_SLUG.'&tab=tab4" target="_blank">'.__('Cookie-Based Brute Force Login Prevention', 'all-in-one-wp-security-and-firewall').'</a>';
342
- echo '<p>'.$info_msg.
343
- '<br />'.$info_msg1.
344
- '<br />'.$info_msg2.'</p>';
345
  ?>
346
  </div>
347
- <?php
348
- //Show the user the new login URL if this feature is active
349
- if ($aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention')=='1')
350
- {
351
- ?>
352
- <div class="aio_yellow_box">
353
- <p><strong><?php _e('NOTE: If you already had the Rename Login Page feature active, the plugin has automatically deactivated it because only one of these features can be active at any one time.', 'all-in-one-wp-security-and-firewall'); ?></strong></p>
354
- </div>
355
-
356
- <?php
357
- }
358
- ?>
359
-
360
  <div class="postbox">
361
  <h3 class="hndle"><label for="title"><?php _e('Cookie Based Brute Force Login Prevention', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
362
  <div class="inside">
@@ -372,12 +326,46 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
372
  <?php _e('This feature can lock you out of admin if it doesn\'t work correctly on your site. You <a href="https://www.tipsandtricks-hq.com/wordpress-security-and-firewall-plugin#advanced_features_note" target="_blank">'.__('must read this message', 'all-in-one-wp-security-and-firewall').'</a> before activating this feature.', 'all-in-one-wp-security-and-firewall'); ?>
373
  </p>
374
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
375
  <table class="form-table">
376
  <tr valign="top">
377
- <th scope="row"><?php _e('Enable Brute Force Attack Prevention', 'all-in-one-wp-security-and-firewall')?>:</th>
378
  <td>
379
- <input name="aiowps_enable_brute_force_attack_prevention" type="checkbox"<?php checked($aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention')); ?> value="1"/>
380
- <span class="description"><?php _e('Check this if you want to protect your login page from Brute Force Attack.', 'all-in-one-wp-security-and-firewall'); ?></span>
381
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
382
  <div class="aiowps_more_info_body">
383
  <p class="description">
@@ -399,14 +387,14 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
399
  </td>
400
  </tr>
401
  <tr valign="top">
402
- <th scope="row"><?php _e('Secret Word', 'all-in-one-wp-security-and-firewall')?>:</th>
403
- <td><input type="text" size="40" name="aiowps_brute_force_secret_word" value="<?php echo $aio_wp_security->configs->get_value('aiowps_brute_force_secret_word'); ?>"<?php if(!$aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention')) echo ' disabled'; ?>/>
404
  <span class="description"><?php _e('Choose a secret word consisting of alphanumeric characters which you can use to access your special URL. Your are highly encouraged to choose a word which will be difficult to guess.', 'all-in-one-wp-security-and-firewall'); ?></span>
405
  </td>
406
  </tr>
407
  <tr valign="top">
408
- <th scope="row"><?php _e('Re-direct URL', 'all-in-one-wp-security-and-firewall')?>:</th>
409
- <td><input type="text" size="40" name="aiowps_cookie_based_brute_force_redirect_url" value="<?php echo $aio_wp_security->configs->get_value('aiowps_cookie_based_brute_force_redirect_url'); ?>"<?php if(!$aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention')) echo ' disabled'; ?>/>
410
  <span class="description">
411
  <?php
412
  _e('Specify a URL to redirect a hacker to when they try to access your WordPress login page.', 'all-in-one-wp-security-and-firewall');
@@ -431,17 +419,17 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
431
  </td>
432
  </tr>
433
  <tr valign="top">
434
- <th scope="row"><?php _e('My Site Has Posts Or Pages Which Are Password Protected', 'all-in-one-wp-security-and-firewall')?>:</th>
435
  <td>
436
- <input name="aiowps_brute_force_attack_prevention_pw_protected_exception" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_brute_force_attack_prevention_pw_protected_exception')=='1') echo ' checked="checked"'; ?> value="1"/>
437
- <span class="description"><?php _e('Check this if you are using the native WordPress password protection feature for some or all of your blog posts or pages.', 'all-in-one-wp-security-and-firewall'); ?></span>
438
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
439
  <div class="aiowps_more_info_body">
440
  <p class="description">
441
  <?php
442
- _e('In the cases where you are protecting some of your posts or pages using the in-built WordPress password protection feature, a few extra lines of directives and exceptions need to be added to your .htacces file so that people trying to access pages are not automatically blocked.', 'all-in-one-wp-security-and-firewall');
443
  echo '<br />';
444
- _e('By enabling this checkbox the plugin will add the necessary rules and exceptions to your .htacces file so that people trying to access these pages are not automatically blocked.', 'all-in-one-wp-security-and-firewall');
445
  echo '<br />';
446
  echo "<strong>".__('Helpful Tip:', 'all-in-one-wp-security-and-firewall')."</strong>";
447
  echo '<br />';
@@ -452,17 +440,17 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
452
  </td>
453
  </tr>
454
  <tr valign="top">
455
- <th scope="row"><?php _e('My Site Has a Theme or Plugins Which Use AJAX', 'all-in-one-wp-security-and-firewall')?>:</th>
456
  <td>
457
- <input name="aiowps_brute_force_attack_prevention_ajax_exception" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_brute_force_attack_prevention_ajax_exception')=='1') echo ' checked="checked"'; ?> value="1"/>
458
- <span class="description"><?php _e('Check this if your site uses AJAX functionality.', 'all-in-one-wp-security-and-firewall'); ?></span>
459
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
460
  <div class="aiowps_more_info_body">
461
  <p class="description">
462
  <?php
463
- _e('In the cases where your WordPress installation has a theme or plugins which use AJAX, a few extra lines of directives and exceptions need to be added to your .htacces file to prevent AJAX requests from being automatically blocked by the brute force prevention feature.', 'all-in-one-wp-security-and-firewall');
464
  echo '<br />';
465
- _e('By enabling this checkbox the plugin will add the necessary rules and exceptions to your .htacces file so that AJAX operations will work as expected.', 'all-in-one-wp-security-and-firewall');
466
  ?>
467
  </p>
468
  </div>
@@ -470,33 +458,9 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
470
  </tr>
471
  </table>
472
  <?php
473
- $cookie_test_value = $aio_wp_security->configs->get_value('aiowps_cookie_test_success');
474
- $bfla_feature_enabled = $aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention');
475
- if($cookie_test_value == '1' || $bfla_feature_enabled == '1')//If the cookie test is successful or if the feature is already enabled then go ahead as normal
476
- {
477
- if (isset($_REQUEST['aiowps_cookie_test']))
478
- {//Cookie test was just performed and the test succeded
479
- echo '<div class="aio_green_box"><p>';
480
- _e('The cookie test was successful. You can now enable this feature.', 'all-in-one-wp-security-and-firewall');
481
- echo '</p></div>';
482
- }
483
- echo '<input type="submit" name="aiowps_apply_cookie_based_bruteforce_firewall" value="'.__('Save Feature Settings', 'all-in-one-wp-security-and-firewall').'" class="button-primary" />';
484
- }
485
- else
486
- {
487
- //Cookie test needs to be performed
488
- if(isset($_REQUEST['aiowps_cookie_test']) && $cookie_test_value != '1'){//Test failed
489
- echo '<div class="aio_red_box"><p>';
490
- _e('The cookie test failed on this server. So this feature cannot be used on this site.', 'all-in-one-wp-security-and-firewall');
491
- echo '</p></div>';
492
- }
493
-
494
- echo '<div class="aio_yellow_box"><p>';
495
- _e("Before using this feature you are required to perform a cookie test first. This is to make sure that your browser cookie is working correctly and that you won't lock yourself out.", 'all-in-one-wp-security-and-firewall');
496
- echo '</p></div>';
497
- echo '<input type="submit" name="aiowps_do_cookie_test_for_bfla" value="'.__('Perform Cookie Test', 'all-in-one-wp-security-and-firewall').'" class="button-primary" />';
498
- }
499
- ?>
500
  </form>
501
  </div></div>
502
  <?php
@@ -507,14 +471,11 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
507
  global $aio_wp_security;
508
  global $aiowps_feature_mgr;
509
 
510
- if(isset($_POST['aiowpsec_save_captcha_settings']))//Do form submission tasks
511
- {
512
- $error = '';
513
- $nonce=$_REQUEST['_wpnonce'];
514
- if (!wp_verify_nonce($nonce, 'aiowpsec-captcha-settings-nonce'))
515
- {
516
- $aio_wp_security->debug_logger->log_debug("Nonce check failed on captcha settings save!",4);
517
- die("Nonce check failed on captcha settings save!");
518
  }
519
 
520
 
@@ -539,7 +500,7 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
539
  }
540
  }
541
 
542
- $aio_wp_security->configs->set_value('aiowps_default_recaptcha',isset($_POST["aiowps_default_recaptcha"])?'1':'');//Checkbox
543
  $aio_wp_security->configs->save_config();
544
 
545
  //Recalculate points after the feature status/options have been altered
@@ -592,18 +553,18 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
592
  <tr valign="top">
593
  <th scope="row"><?php _e('Use Google reCAPTCHA as default', 'all-in-one-wp-security-and-firewall')?>:</th>
594
  <td>
595
- <input name="aiowps_default_recaptcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_default_recaptcha')=='1') echo ' checked="checked"'; ?> value="1"/>
596
- <span class="description"><?php _e('Check this if you want to default to Google reCAPTCHA for all settings below. (If this is left unchecked, all captcha forms will revert to the plain maths captcha)', 'all-in-one-wp-security-and-firewall'); ?></span>
597
  </td>
598
  </tr>
599
  <tr valign="top">
600
- <th scope="row"><?php _e('Site Key', 'all-in-one-wp-security-and-firewall')?>:</th>
601
- <td><input type="text" size="50" name="aiowps_recaptcha_site_key" value="<?php echo esc_html( $aio_wp_security->configs->get_value('aiowps_recaptcha_site_key') ); ?>" />
602
  </td>
603
  </tr>
604
  <tr valign="top">
605
- <th scope="row"><?php _e('Secret Key', 'all-in-one-wp-security-and-firewall')?>:</th>
606
- <td><input type="text" size="50" name="aiowps_recaptcha_secret_key" value="<?php echo esc_html( $secret_key_masked ); ?>" />
607
  </td>
608
  </tr>
609
  </table>
@@ -620,8 +581,8 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
620
  <tr valign="top">
621
  <th scope="row"><?php _e('Enable Captcha On Login Page', 'all-in-one-wp-security-and-firewall')?>:</th>
622
  <td>
623
- <input name="aiowps_enable_login_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_login_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
624
- <span class="description"><?php _e('Check this if you want to insert a captcha form on the login page', 'all-in-one-wp-security-and-firewall'); ?></span>
625
  </td>
626
  </tr>
627
  </table>
@@ -639,8 +600,8 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
639
  <tr valign="top">
640
  <th scope="row"><?php _e('Enable Captcha On Lost Password Page', 'all-in-one-wp-security-and-firewall')?>:</th>
641
  <td>
642
- <input name="aiowps_enable_lost_password_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_lost_password_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
643
- <span class="description"><?php _e('Check this if you want to insert a captcha form on the lost password page', 'all-in-one-wp-security-and-firewall'); ?></span>
644
  </td>
645
  </tr>
646
  </table>
@@ -657,15 +618,15 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
657
  <tr valign="top">
658
  <th scope="row"><?php _e('Enable Captcha On Custom Login Form', 'all-in-one-wp-security-and-firewall')?>:</th>
659
  <td>
660
- <input name="aiowps_enable_custom_login_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_custom_login_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
661
- <span class="description"><?php _e('Check this if you want to insert captcha on a custom login form generated by the following WP function: wp_login_form()', 'all-in-one-wp-security-and-firewall'); ?></span>
662
  </td>
663
  </tr>
664
  </table>
665
  </div></div>
666
  <?php
667
  // Only display woocommerce captcha settings if woo is active
668
- if ( in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {
669
  ?>
670
  <div class="postbox">
671
  <h3 class="hndle"><label for="title"><?php _e('Woocommerce Forms Captcha Settings', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
@@ -679,8 +640,8 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
679
  <tr valign="top">
680
  <th scope="row"><?php _e('Enable Captcha On Woocommerce Login Form', 'all-in-one-wp-security-and-firewall')?>:</th>
681
  <td>
682
- <input name="aiowps_enable_woo_login_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_woo_login_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
683
- <span class="description"><?php _e('Check this if you want to insert captcha on a Woocommerce login form', 'all-in-one-wp-security-and-firewall'); ?></span>
684
  </td>
685
  </tr>
686
  </table>
@@ -692,8 +653,8 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
692
  <tr valign="top">
693
  <th scope="row"><?php _e('Enable Captcha On Woocommerce Lost Password Form', 'all-in-one-wp-security-and-firewall')?>:</th>
694
  <td>
695
- <input name="aiowps_enable_woo_lostpassword_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_woo_lostpassword_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
696
- <span class="description"><?php _e('Check this if you want to insert captcha on a Woocommerce lost password form', 'all-in-one-wp-security-and-firewall'); ?></span>
697
  </td>
698
  </tr>
699
  </table>
@@ -705,8 +666,8 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
705
  <tr valign="top">
706
  <th scope="row"><?php _e('Enable Captcha On Woocommerce Registration Form', 'all-in-one-wp-security-and-firewall')?>:</th>
707
  <td>
708
- <input name="aiowps_enable_woo_register_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_woo_register_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
709
- <span class="description"><?php _e('Check this if you want to insert captcha on a Woocommerce registration form', 'all-in-one-wp-security-and-firewall'); ?></span>
710
  </td>
711
  </tr>
712
  </table>
@@ -731,8 +692,8 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
731
  $nonce=$_REQUEST['_wpnonce'];
732
  if (!wp_verify_nonce($nonce, 'aiowpsec-whitelist-settings-nonce'))
733
  {
734
- $aio_wp_security->debug_logger->log_debug("Nonce check failed for save whitelist settings!",4);
735
- die(__('Nonce check failed for save whitelist settings!','all-in-one-wp-security-and-firewall'));
736
  }
737
 
738
  if (isset($_POST["aiowps_enable_whitelisting"]) && empty($_POST['aiowps_allowed_ip_addresses']))
@@ -819,36 +780,24 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
819
  <tr valign="top">
820
  <th scope="row"><?php _e('Enable IP Whitelisting', 'all-in-one-wp-security-and-firewall')?>:</th>
821
  <td>
822
- <input name="aiowps_enable_whitelisting" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_whitelisting')=='1') echo ' checked="checked"'; ?> value="1"/>
823
- <span class="description"><?php _e('Check this if you want to enable the whitelisting of selected IP addresses specified in the settings below', 'all-in-one-wp-security-and-firewall'); ?></span>
824
  </td>
825
  </tr>
826
  <tr valign="top">
827
- <th scope="row"><?php _e('Your Current IP Address', 'all-in-one-wp-security-and-firewall')?>:</th>
828
  <td>
829
- <input size="20" name="aiowps_user_ip" type="text" value="<?php echo $your_ip_address; ?>" readonly="readonly"/>
830
  <span class="description"><?php _e('You can copy and paste this address in the text box below if you want to include it in your login whitelist.', 'all-in-one-wp-security-and-firewall'); ?></span>
831
  </td>
832
  </tr>
833
  <tr valign="top">
834
- <th scope="row"><?php _e('Enter Whitelisted IP Addresses:', 'all-in-one-wp-security-and-firewall')?></th>
835
  <td>
836
- <textarea name="aiowps_allowed_ip_addresses" rows="5" cols="50"><?php echo ($result == -1)?htmlspecialchars($_POST['aiowps_allowed_ip_addresses']):htmlspecialchars($aio_wp_security->configs->get_value('aiowps_allowed_ip_addresses')); ?></textarea>
837
  <br />
838
- <span class="description"><?php _e('Enter one or more IP addresses or IP ranges you wish to include in your whitelist. Only the addresses specified here will have access to the WordPress login page.','all-in-one-wp-security-and-firewall');?></span>
839
- <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
840
- <div class="aiowps_more_info_body">
841
- <?php
842
- echo '<p class="description"><strong>'.__('Each IP address must be on a new line.', 'all-in-one-wp-security-and-firewall').'</strong></p>';
843
- echo '<p class="description">'.__('To specify an IPv4 range use a wildcard "*" character. Acceptable ways to use wildcards is shown in the examples below:', 'all-in-one-wp-security-and-firewall').'</p>';
844
- echo '<p class="description">'.__('Example 1: 195.47.89.*', 'all-in-one-wp-security-and-firewall').'</p>';
845
- echo '<p class="description">'.__('Example 2: 195.47.*.*', 'all-in-one-wp-security-and-firewall').'</p>';
846
- echo '<p class="description">'.__('Example 3: 195.*.*.*', 'all-in-one-wp-security-and-firewall').'</p>';
847
- echo '<p class="description">'.__('Or you can enter an IPv6 address (NOTE: ranges/wildcards are currently not supported for ipv6)', 'all-in-one-wp-security-and-firewall').'</p>';
848
- echo '<p class="description">'.__('Example 4: 4102:0:3ea6:79fd:b:46f8:230f:bb05', 'all-in-one-wp-security-and-firewall').'</p>';
849
- echo '<p class="description">'.__('Example 5: 2205:0:1ca2:810d::', 'all-in-one-wp-security-and-firewall').'</p>';
850
- ?>
851
- </div>
852
 
853
  </td>
854
  </tr>
@@ -870,8 +819,8 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
870
  $nonce=$_REQUEST['_wpnonce'];
871
  if (!wp_verify_nonce($nonce, 'aiowpsec-honeypot-settings-nonce'))
872
  {
873
- $aio_wp_security->debug_logger->log_debug("Nonce check failed on honeypot settings save!",4);
874
- die("Nonce check failed on honeypot settings save!");
875
  }
876
 
877
  //Save all the form values to the options
@@ -908,8 +857,8 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
908
  <tr valign="top">
909
  <th scope="row"><?php _e('Enable Honeypot On Login Page', 'all-in-one-wp-security-and-firewall')?>:</th>
910
  <td>
911
- <input name="aiowps_enable_login_honeypot" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_login_honeypot')=='1') echo ' checked="checked"'; ?> value="1"/>
912
- <span class="description"><?php _e('Check this if you want to enable the honeypot feature for the login page', 'all-in-one-wp-security-and-firewall'); ?></span>
913
  </td>
914
  </tr>
915
  </table>
@@ -919,6 +868,5 @@ class AIOWPSecurity_Brute_Force_Menu extends AIOWPSecurity_Admin_Menu
919
  </form>
920
  <?php
921
  }
922
-
923
-
924
  } //end class
10
  /* Specify all the tabs of this menu in the following array */
11
  var $menu_tabs;
12
 
13
+ /**
14
+ * Tab slugs and respective render functions.
15
+ *
16
+ * @var string[]
17
+ */
18
+ private $menu_tabs_handler = array(
19
  'tab1' => 'render_tab1',
20
  'tab2' => 'render_tab2',
21
  'tab3' => 'render_tab3',
22
  'tab4' => 'render_tab4',
23
  'tab5' => 'render_tab5',
24
+ );
25
 
26
+ public function __construct() {
 
27
  $this->render_menu_page();
28
  }
29
 
35
  'tab3' => __('Login Captcha', 'all-in-one-wp-security-and-firewall'),
36
  'tab4' => __('Login Whitelist', 'all-in-one-wp-security-and-firewall'),
37
  'tab5' => __('Honeypot', 'all-in-one-wp-security-and-firewall'),
 
38
  );
39
  }
40
 
48
  echo '<h2 class="nav-tab-wrapper">';
49
  foreach ( $this->menu_tabs as $tab_key => $tab_caption )
50
  {
51
+ if (is_multisite() && get_current_blog_id() != 1
52
  && stristr($tab_caption, "Rename Login Page") === false && stristr($tab_caption, "Login Captcha") === false){
53
  //Suppress the all Brute Force menu tabs if site is a multi site AND not the main site except "rename login" and "captcha"
54
  }else{
87
  $aiowps_login_page_slug = '';
88
 
89
  if (get_option('permalink_structure')){
90
+ $site_url = trailingslashit(site_url());
91
  }else{
92
+ $site_url = trailingslashit(site_url()) . '?';
93
  }
94
 
95
  if(isset($_POST['aiowps_save_rename_login_page_settings']))//Do form submission tasks
98
  $nonce=$_REQUEST['_wpnonce'];
99
  if (!wp_verify_nonce($nonce, 'aiowpsec-rename-login-page-nonce'))
100
  {
101
+ $aio_wp_security->debug_logger->log_debug("Nonce check failed for rename login page save.",4);
102
+ die("Nonce check failed for rename login page save.");
103
  }
104
 
105
  if (empty($_POST['aiowps_login_page_slug']) && isset($_POST["aiowps_enable_rename_login_page"])){
121
  //Save all the form values to the options
122
  if (isset($_POST["aiowps_enable_rename_login_page"])){
123
  $aio_wp_security->configs->set_value('aiowps_enable_rename_login_page', '1');
 
 
 
 
 
124
  }else{
125
  $aio_wp_security->configs->set_value('aiowps_enable_rename_login_page', '');
126
  }
127
  $aio_wp_security->configs->set_value('aiowps_login_page_slug',$aiowps_login_page_slug);
128
  $aio_wp_security->configs->save_config();
129
 
 
 
 
 
130
 
131
  //Recalculate points after the feature status/options have been altered
132
  $aiowps_feature_mgr->check_feature_status_and_recalculate_points();
172
  <div class="aio_yellow_box">
173
  <p><?php _e('Your WordPress login page URL has been renamed.', 'all-in-one-wp-security-and-firewall'); ?></p>
174
  <p><?php _e('Your current login URL is:', 'all-in-one-wp-security-and-firewall'); ?></p>
175
+ <p><strong><?php echo $site_url.$aio_wp_security->configs->get_value('aiowps_login_page_slug'); ?></strong></p>
 
176
  </div>
177
 
178
  <?php
200
  <tr valign="top">
201
  <th scope="row"><?php _e('Enable Rename Login Page Feature', 'all-in-one-wp-security-and-firewall')?>:</th>
202
  <td>
203
+ <input id="aiowps_enable_rename_login_page" name="aiowps_enable_rename_login_page" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_rename_login_page')=='1') echo ' checked="checked"'; ?> value="1"/>
204
+ <label for="aiowps_enable_rename_login_page" class="description"><?php _e('Check this if you want to enable the rename login page feature', 'all-in-one-wp-security-and-firewall'); ?></label>
205
  </td>
206
  </tr>
207
  <tr valign="top">
208
+ <th scope="row"><label for="aiowps_login_page_slug"><?php _e('Login Page URL', 'all-in-one-wp-security-and-firewall')?>:</label></th>
209
+ <td><code><?php echo $site_url; ?></code><input id="aiowps_login_page_slug" type="text" size="15" name="aiowps_login_page_slug" value="<?php echo $aio_wp_security->configs->get_value('aiowps_login_page_slug'); ?>" />
210
  <span class="description"><?php _e('Enter a string which will represent your secure login page slug. You are encouraged to choose something which is hard to guess and only you will remember.', 'all-in-one-wp-security-and-firewall'); ?></span>
211
  </td>
212
  </tr>
218
  <?php
219
  }
220
 
221
+ /**
222
+ * Render content of the cookie based brute force prevention tab.
223
+ *
224
+ * @return Void
225
+ */
226
+ private function render_tab2() {
227
  global $aio_wp_security;
228
  global $aiowps_feature_mgr;
229
  $error = false;
230
+ $msg = '';
231
 
232
  //Save settings for brute force cookie method
233
+ if (isset($_POST['aiowps_apply_cookie_based_bruteforce_firewall'])) {
234
+ if (!wp_verify_nonce($_POST['_wpnonce'], 'aiowpsec-enable-cookie-based-brute-force-prevention')) {
235
+ $aio_wp_security->debug_logger->log_debug('Nonce check failed on enable cookie based brute force prevention feature.',4);
236
+ die('Nonce check failed on enable cookie based brute force prevention feature.');
 
 
 
237
  }
238
 
239
+ if (isset($_POST['aiowps_enable_brute_force_attack_prevention'])) {
 
240
  $brute_force_feature_secret_word = sanitize_text_field($_POST['aiowps_brute_force_secret_word']);
241
+ if (empty($brute_force_feature_secret_word)) {
242
+ $brute_force_feature_secret_word = "aiossecret";
243
+ } elseif (!ctype_alnum($brute_force_feature_secret_word)) {
244
  $msg = '<p>'.__('Settings have not been saved - your secret word must consist only of alphanumeric characters, ie, letters and/or numbers only!', 'all-in-one-wp-security-and-firewall').'</p>';
245
  $error = true;
246
  }
247
 
248
+ if (filter_var($_POST['aiowps_cookie_based_brute_force_redirect_url'], FILTER_VALIDATE_URL)) {
249
+ $aio_wp_security->configs->set_value('aiowps_cookie_based_brute_force_redirect_url', esc_url_raw($_POST['aiowps_cookie_based_brute_force_redirect_url']));
250
+ } else {
251
+ $aio_wp_security->configs->set_value('aiowps_cookie_based_brute_force_redirect_url', 'http://127.0.0.1');
 
 
 
252
  }
253
+ $aio_wp_security->configs->set_value('aiowps_enable_brute_force_attack_prevention', '1');
254
 
255
+ if (!$error) {
256
+ $aio_wp_security->configs->set_value('aiowps_brute_force_secret_word', $brute_force_feature_secret_word);
 
 
 
 
257
  $msg = '<p>'.__('You have successfully enabled the cookie based brute force prevention feature', 'all-in-one-wp-security-and-firewall').'</p>';
258
  $msg .= '<p>'.__('From now on you will need to log into your WP Admin using the following URL:', 'all-in-one-wp-security-and-firewall').'</p>';
259
  $msg .= '<p><strong>'.AIOWPSEC_WP_URL.'/?'.$brute_force_feature_secret_word.'=1</strong></p>';
260
  $msg .= '<p>'.__('It is important that you save this URL value somewhere in case you forget it, OR,', 'all-in-one-wp-security-and-firewall').'</p>';
261
  $msg .= '<p>'.sprintf( __('simply remember to add a "?%s=1" to your current site URL address.', 'all-in-one-wp-security-and-firewall'), $brute_force_feature_secret_word).'</p>';
262
  }
263
+ } else {
264
+ $aio_wp_security->configs->set_value('aiowps_enable_brute_force_attack_prevention', '');
 
 
265
  $msg = __('You have successfully saved cookie based brute force prevention feature settings.', 'all-in-one-wp-security-and-firewall');
266
  }
267
 
268
+ if (isset($_POST['aiowps_brute_force_attack_prevention_pw_protected_exception'])) {
269
+ $aio_wp_security->configs->set_value('aiowps_brute_force_attack_prevention_pw_protected_exception', '1');
270
+ } else {
271
+ $aio_wp_security->configs->set_value('aiowps_brute_force_attack_prevention_pw_protected_exception', '');
 
 
 
272
  }
273
 
274
+ if (isset($_POST['aiowps_brute_force_attack_prevention_ajax_exception'])) {
275
+ $aio_wp_security->configs->set_value('aiowps_brute_force_attack_prevention_ajax_exception', '1');
276
+ } else {
277
+ $aio_wp_security->configs->set_value('aiowps_brute_force_attack_prevention_ajax_exception', '');
 
 
 
278
  }
279
 
280
+ if (!$error) {
 
281
  $aio_wp_security->configs->save_config();//save the value
282
 
283
  //Recalculate points after the feature status/options have been altered
284
  $aiowps_feature_mgr->check_feature_status_and_recalculate_points();
285
+ if ('' != $msg) {
286
+ echo '<div id="message" class="updated fade"><p>';
287
+ echo $msg;
288
+ echo '</p></div>';
 
 
 
 
 
289
  }
290
+ } else {
 
 
291
  $this->show_msg_error($msg);
292
  }
293
  }
 
294
  ?>
295
  <h2><?php _e('Brute Force Prevention Firewall Settings', 'all-in-one-wp-security-and-firewall')?></h2>
296
 
299
  //TODO - need to fix the following message
300
  echo '<p>'.__('A Brute Force Attack is when a hacker tries many combinations of usernames and passwords until they succeed in guessing the right combination.', 'all-in-one-wp-security-and-firewall').
301
  '<br />'.__('Due to the fact that at any one time there may be many concurrent login attempts occurring on your site via malicious automated robots, this also has a negative impact on your server\'s memory and performance.', 'all-in-one-wp-security-and-firewall').
302
+ '<br />'.__('The features in this tab will stop the majority of brute force login attacks thus providing even better protection for your WP login page.', 'all-in-one-wp-security-and-firewall').'</p>';
303
  ?>
304
  </div>
305
  <div class="aio_yellow_box">
306
  <?php
307
  $backup_tab_link = '<a href="admin.php?page='.AIOWPSEC_SETTINGS_MENU_SLUG.'&tab=tab2" target="_blank">'.__('backup', 'all-in-one-wp-security-and-firewall').'</a>';
308
  $video_link = '<a href="https://www.tipsandtricks-hq.com/all-in-one-wp-security-plugin-cookie-based-brute-force-login-attack-prevention-feature-5994" target="_blank">'.__('video tutorial', 'all-in-one-wp-security-and-firewall').'</a>';
309
+ $info_msg = sprintf( __('To learn more about how to use this feature, please watch the following %s.', 'all-in-one-wp-security-and-firewall'), $video_link);
 
 
310
  $brute_force_login_feature_link = '<a href="admin.php?page='.AIOWPSEC_FIREWALL_MENU_SLUG.'&tab=tab4" target="_blank">'.__('Cookie-Based Brute Force Login Prevention', 'all-in-one-wp-security-and-firewall').'</a>';
311
+ echo '<p>' . $info_msg . '</p>';
 
 
312
  ?>
313
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  <div class="postbox">
315
  <h3 class="hndle"><label for="title"><?php _e('Cookie Based Brute Force Login Prevention', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
316
  <div class="inside">
326
  <?php _e('This feature can lock you out of admin if it doesn\'t work correctly on your site. You <a href="https://www.tipsandtricks-hq.com/wordpress-security-and-firewall-plugin#advanced_features_note" target="_blank">'.__('must read this message', 'all-in-one-wp-security-and-firewall').'</a> before activating this feature.', 'all-in-one-wp-security-and-firewall'); ?>
327
  </p>
328
  </div>
329
+ <?php
330
+ $cookie_test_value = $aio_wp_security->configs->get_value('aiowps_cookie_test_success');
331
+
332
+ $disable_brute_force_fetaure_input = true;
333
+ // If the cookie test is successful or if the feature is already enabled then go ahead as normal
334
+ if ('1' == $cookie_test_value || '1' == $aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention')) {
335
+ if (isset($_POST['aiowps_cookie_test'])) {//Cookie test was just performed and the test succeded
336
+ echo '<div class="aio_green_box"><p>';
337
+ _e('The cookie test was successful. You can now enable this feature.', 'all-in-one-wp-security-and-firewall');
338
+ echo '</p></div>';
339
+ }
340
+ $disable_brute_force_fetaure_input = false;
341
+ } else {
342
+ //Cookie test needs to be performed
343
+ if (isset($_POST['aiowps_cookie_test']) && '1' != $cookie_test_value) {//Test failed
344
+ echo '<div class="aio_red_box"><p>';
345
+ _e('The cookie test failed on this server. Consequently, this feature cannot be used on this site.', 'all-in-one-wp-security-and-firewall');
346
+ echo '</p></div>';
347
+ }
348
+ ?>
349
+ <div class="aio_yellow_box">
350
+ <p>
351
+ <?php
352
+ _e('Before using this feature, you must perform a cookie test first.', 'all-in-one-wp-security-and-firewall');
353
+ echo ' ';
354
+ echo htmlspecialchars(__("This ensures that your browser cookie is working correctly and that you won't lock yourself out.", 'all-in-one-wp-security-and-firewall'));
355
+ ?>
356
+ </p>
357
+ </div>
358
+ <?php
359
+ submit_button(__('Perform cookie test', 'all-in-one-wp-security-and-firewall'), 'primary' , 'aiowps_do_cookie_test_for_bfla');
360
+ }
361
+ $disable_brute_force_sub_fields = !$aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention');
362
+ ?>
363
  <table class="form-table">
364
  <tr valign="top">
365
+ <th scope="row"><?php _e('Enable brute force attack prevention', 'all-in-one-wp-security-and-firewall')?>:</th>
366
  <td>
367
+ <input id="aiowps_enable_brute_force_attack_prevention" name="aiowps_enable_brute_force_attack_prevention" type="checkbox"<?php checked($aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention'));?> value="1"<?php disabled($disable_brute_force_fetaure_input); ?>/>
368
+ <label for="aiowps_enable_brute_force_attack_prevention" class="description"><?php _e('Check this if you want to protect your login page from Brute Force Attack.', 'all-in-one-wp-security-and-firewall'); ?></label>
369
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
370
  <div class="aiowps_more_info_body">
371
  <p class="description">
387
  </td>
388
  </tr>
389
  <tr valign="top">
390
+ <th scope="row"><label for="aiowps_brute_force_secret_word"><?php _e('Secret Word', 'all-in-one-wp-security-and-firewall')?>:</label></th>
391
+ <td><input id="aiowps_brute_force_secret_word" type="text" size="40" name="aiowps_brute_force_secret_word" value="<?php echo $aio_wp_security->configs->get_value('aiowps_brute_force_secret_word'); ?>"<?php disabled($disable_brute_force_sub_fields); ?>/>
392
  <span class="description"><?php _e('Choose a secret word consisting of alphanumeric characters which you can use to access your special URL. Your are highly encouraged to choose a word which will be difficult to guess.', 'all-in-one-wp-security-and-firewall'); ?></span>
393
  </td>
394
  </tr>
395
  <tr valign="top">
396
+ <th scope="row"><label for="aiowps_cookie_based_brute_force_redirect_url"><?php _e('Re-direct URL', 'all-in-one-wp-security-and-firewall')?>:</label></th>
397
+ <td><input id="aiowps_cookie_based_brute_force_redirect_url" type="text" size="40" name="aiowps_cookie_based_brute_force_redirect_url" value="<?php echo $aio_wp_security->configs->get_value('aiowps_cookie_based_brute_force_redirect_url'); ?>" <?php disabled($disable_brute_force_sub_fields); ?> />
398
  <span class="description">
399
  <?php
400
  _e('Specify a URL to redirect a hacker to when they try to access your WordPress login page.', 'all-in-one-wp-security-and-firewall');
419
  </td>
420
  </tr>
421
  <tr valign="top">
422
+ <th scope="row"><?php _e('My site has posts or pages which are password protected', 'all-in-one-wp-security-and-firewall')?>:</th>
423
  <td>
424
+ <input id="aiowps_brute_force_attack_prevention_pw_protected_exception" name="aiowps_brute_force_attack_prevention_pw_protected_exception" type="checkbox"<?php checked($aio_wp_security->configs->get_value('aiowps_brute_force_attack_prevention_pw_protected_exception')); ?> value="1"<?php disabled($disable_brute_force_sub_fields); ?> />
425
+ <label for="aiowps_brute_force_attack_prevention_pw_protected_exception" class="description"><?php _e('Check this if you are using the native WordPress password protection feature for some or all of your blog posts or pages.', 'all-in-one-wp-security-and-firewall'); ?></label>
426
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
427
  <div class="aiowps_more_info_body">
428
  <p class="description">
429
  <?php
430
+ _e('In the cases where you are protecting some of your posts or pages using the in-built WordPress password protection feature, a few extra lines of directives and exceptions need to be added so that people trying to access pages are not automatically blocked.', 'all-in-one-wp-security-and-firewall');
431
  echo '<br />';
432
+ _e('By enabling this checkbox, the plugin will add the necessary rules and exceptions so that people trying to access these pages are not automatically blocked.', 'all-in-one-wp-security-and-firewall');
433
  echo '<br />';
434
  echo "<strong>".__('Helpful Tip:', 'all-in-one-wp-security-and-firewall')."</strong>";
435
  echo '<br />';
440
  </td>
441
  </tr>
442
  <tr valign="top">
443
+ <th scope="row"><?php _e('My site has a theme or plugins which use AJAX', 'all-in-one-wp-security-and-firewall')?>:</th>
444
  <td>
445
+ <input id="aiowps_brute_force_attack_prevention_ajax_exception" name="aiowps_brute_force_attack_prevention_ajax_exception" type="checkbox"<?php checked($aio_wp_security->configs->get_value('aiowps_brute_force_attack_prevention_ajax_exception')); ?> value="1"<?php disabled($disable_brute_force_sub_fields); ?>/>
446
+ <label for="aiowps_brute_force_attack_prevention_ajax_exception" class="description"><?php _e('Check this if your site uses AJAX functionality.', 'all-in-one-wp-security-and-firewall'); ?></label>
447
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
448
  <div class="aiowps_more_info_body">
449
  <p class="description">
450
  <?php
451
+ _e('In the cases where your WordPress installation has a theme or plugin that uses AJAX, a few extra lines of directives and exceptions need to be added to prevent AJAX requests from being automatically blocked by the brute force prevention feature.', 'all-in-one-wp-security-and-firewall');
452
  echo '<br />';
453
+ _e('By enabling this checkbox, the plugin will add the necessary rules and exceptions so that AJAX operations will work as expected.', 'all-in-one-wp-security-and-firewall');
454
  ?>
455
  </p>
456
  </div>
458
  </tr>
459
  </table>
460
  <?php
461
+ $other_attributes = $disable_brute_force_fetaure_input ? array('disabled' => 'disabled') : array();
462
+ submit_button(__('Save feature settings', 'all-in-one-wp-security-and-firewall'), 'primary', 'aiowps_apply_cookie_based_bruteforce_firewall', false, $other_attributes);
463
+ ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
  </form>
465
  </div></div>
466
  <?php
471
  global $aio_wp_security;
472
  global $aiowps_feature_mgr;
473
 
474
+ if (isset($_POST['aiowpsec_save_captcha_settings'])) { //Do form submission tasks
475
+ $error = '';
476
+ if (!wp_verify_nonce($_POST['_wpnonce'], 'aiowpsec-captcha-settings-nonce')) {
477
+ $aio_wp_security->debug_logger->log_debug('Nonce check failed on captcha settings save.', 4);
478
+ die('Nonce check failed on captcha settings save.');
 
 
 
479
  }
480
 
481
 
500
  }
501
  }
502
 
503
+ $aio_wp_security->configs->set_value('aiowps_default_recaptcha', isset($_POST["aiowps_default_recaptcha"])? '1' : '');//Checkbox
504
  $aio_wp_security->configs->save_config();
505
 
506
  //Recalculate points after the feature status/options have been altered
553
  <tr valign="top">
554
  <th scope="row"><?php _e('Use Google reCAPTCHA as default', 'all-in-one-wp-security-and-firewall')?>:</th>
555
  <td>
556
+ <input id="aiowps_default_recaptcha" name="aiowps_default_recaptcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_default_recaptcha')=='1') echo ' checked="checked"'; ?> value="1"/>
557
+ <label for="aiowps_default_recaptcha" class="description"><?php _e('Check this if you want to default to Google reCAPTCHA for all settings below. (If this is left unchecked, all captcha forms will revert to the plain maths captcha)', 'all-in-one-wp-security-and-firewall'); ?></label>
558
  </td>
559
  </tr>
560
  <tr valign="top">
561
+ <th scope="row"><label for="aiowps_recaptcha_site_key"><?php _e('Site Key', 'all-in-one-wp-security-and-firewall')?>:</label></th>
562
+ <td><input id="aiowps_recaptcha_site_key" type="text" size="50" name="aiowps_recaptcha_site_key" value="<?php echo esc_html( $aio_wp_security->configs->get_value('aiowps_recaptcha_site_key') ); ?>" />
563
  </td>
564
  </tr>
565
  <tr valign="top">
566
+ <th scope="row"><label for="aiowps_recaptcha_secret_key"><?php _e('Secret Key', 'all-in-one-wp-security-and-firewall')?>:</label></th>
567
+ <td><input id="aiowps_recaptcha_secret_key" type="text" size="50" name="aiowps_recaptcha_secret_key" value="<?php echo esc_html($secret_key_masked); ?>" />
568
  </td>
569
  </tr>
570
  </table>
581
  <tr valign="top">
582
  <th scope="row"><?php _e('Enable Captcha On Login Page', 'all-in-one-wp-security-and-firewall')?>:</th>
583
  <td>
584
+ <input id="aiowps_enable_login_captcha" name="aiowps_enable_login_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_login_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
585
+ <label for="aiowps_enable_login_captcha" class="description"><?php _e('Check this if you want to insert a captcha form on the login page', 'all-in-one-wp-security-and-firewall'); ?></label>
586
  </td>
587
  </tr>
588
  </table>
600
  <tr valign="top">
601
  <th scope="row"><?php _e('Enable Captcha On Lost Password Page', 'all-in-one-wp-security-and-firewall')?>:</th>
602
  <td>
603
+ <input id="aiowps_enable_lost_password_captcha" name="aiowps_enable_lost_password_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_lost_password_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
604
+ <label for="aiowps_enable_lost_password_captcha" class="description"><?php _e('Check this if you want to insert a captcha form on the lost password page', 'all-in-one-wp-security-and-firewall'); ?></label>
605
  </td>
606
  </tr>
607
  </table>
618
  <tr valign="top">
619
  <th scope="row"><?php _e('Enable Captcha On Custom Login Form', 'all-in-one-wp-security-and-firewall')?>:</th>
620
  <td>
621
+ <input id="aiowps_enable_custom_login_captcha" name="aiowps_enable_custom_login_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_custom_login_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
622
+ <label for="aiowps_enable_custom_login_captcha" class="description"><?php _e('Check this if you want to insert captcha on a custom login form generated by the following WP function: wp_login_form()', 'all-in-one-wp-security-and-firewall'); ?></label>
623
  </td>
624
  </tr>
625
  </table>
626
  </div></div>
627
  <?php
628
  // Only display woocommerce captcha settings if woo is active
629
+ if (AIOWPSecurity_Utility::is_woocommerce_plugin_active()) {
630
  ?>
631
  <div class="postbox">
632
  <h3 class="hndle"><label for="title"><?php _e('Woocommerce Forms Captcha Settings', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
640
  <tr valign="top">
641
  <th scope="row"><?php _e('Enable Captcha On Woocommerce Login Form', 'all-in-one-wp-security-and-firewall')?>:</th>
642
  <td>
643
+ <input id="aiowps_enable_woo_login_captcha" name="aiowps_enable_woo_login_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_woo_login_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
644
+ <label for="aiowps_enable_woo_login_captcha" class="description"><?php _e('Check this if you want to insert captcha on a Woocommerce login form', 'all-in-one-wp-security-and-firewall'); ?></label>
645
  </td>
646
  </tr>
647
  </table>
653
  <tr valign="top">
654
  <th scope="row"><?php _e('Enable Captcha On Woocommerce Lost Password Form', 'all-in-one-wp-security-and-firewall')?>:</th>
655
  <td>
656
+ <input id="aiowps_enable_woo_lostpassword_captcha" name="aiowps_enable_woo_lostpassword_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_woo_lostpassword_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
657
+ <label for="aiowps_enable_woo_lostpassword_captcha" class="description"><?php _e('Check this if you want to insert captcha on a Woocommerce lost password form', 'all-in-one-wp-security-and-firewall'); ?></label>
658
  </td>
659
  </tr>
660
  </table>
666
  <tr valign="top">
667
  <th scope="row"><?php _e('Enable Captcha On Woocommerce Registration Form', 'all-in-one-wp-security-and-firewall')?>:</th>
668
  <td>
669
+ <input id="aiowps_enable_woo_register_captcha" name="aiowps_enable_woo_register_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_woo_register_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
670
+ <label for="aiowps_enable_woo_register_captcha" class="description"><?php _e('Check this if you want to insert captcha on a Woocommerce registration form', 'all-in-one-wp-security-and-firewall'); ?></label>
671
  </td>
672
  </tr>
673
  </table>
692
  $nonce=$_REQUEST['_wpnonce'];
693
  if (!wp_verify_nonce($nonce, 'aiowpsec-whitelist-settings-nonce'))
694
  {
695
+ $aio_wp_security->debug_logger->log_debug("Nonce check failed for save whitelist settings.",4);
696
+ die(__('Nonce check failed for save whitelist settings.','all-in-one-wp-security-and-firewall'));
697
  }
698
 
699
  if (isset($_POST["aiowps_enable_whitelisting"]) && empty($_POST['aiowps_allowed_ip_addresses']))
780
  <tr valign="top">
781
  <th scope="row"><?php _e('Enable IP Whitelisting', 'all-in-one-wp-security-and-firewall')?>:</th>
782
  <td>
783
+ <input id="aiowps_enable_whitelisting" name="aiowps_enable_whitelisting" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_whitelisting')=='1') echo ' checked="checked"'; ?> value="1"/>
784
+ <label for="aiowps_enable_whitelisting" class="description"><?php _e('Check this if you want to enable the whitelisting of selected IP addresses specified in the settings below', 'all-in-one-wp-security-and-firewall'); ?></label>
785
  </td>
786
  </tr>
787
  <tr valign="top">
788
+ <th scope="row"><label for="aiowps_user_ip"><?php _e('Your Current IP Address', 'all-in-one-wp-security-and-firewall')?>:</label></th>
789
  <td>
790
+ <input id="aiowps_user_ip" size="20" name="aiowps_user_ip" type="text" value="<?php echo $your_ip_address; ?>" readonly="readonly"/>
791
  <span class="description"><?php _e('You can copy and paste this address in the text box below if you want to include it in your login whitelist.', 'all-in-one-wp-security-and-firewall'); ?></span>
792
  </td>
793
  </tr>
794
  <tr valign="top">
795
+ <th scope="row"><label for="aiowps_allowed_ip_addresses"><?php _e('Enter Whitelisted IP Addresses:', 'all-in-one-wp-security-and-firewall')?></label></th>
796
  <td>
797
+ <textarea id="aiowps_allowed_ip_addresses" name="aiowps_allowed_ip_addresses" rows="5" cols="50"><?php echo esc_textarea(wp_unslash(-1 == $result ? $_POST['aiowps_allowed_ip_addresses'] : $aio_wp_security->configs->get_value('aiowps_allowed_ip_addresses'))); ?></textarea>
798
  <br />
799
+ <span class="description"><?php _e('Enter one or more IP addresses or IP ranges you wish to include in your whitelist.', 'all-in-one-wp-security-and-firewall') . ' ' . _e('Only the addresses specified here will have access to the WordPress login page.', 'all-in-one-wp-security-and-firewall');?></span>
800
+ <?php $aio_wp_security->include_template('info/ip-address-ip-range-info.php');?>
 
 
 
 
 
 
 
 
 
 
 
 
801
 
802
  </td>
803
  </tr>
819
  $nonce=$_REQUEST['_wpnonce'];
820
  if (!wp_verify_nonce($nonce, 'aiowpsec-honeypot-settings-nonce'))
821
  {
822
+ $aio_wp_security->debug_logger->log_debug("Nonce check failed on honeypot settings save.",4);
823
+ die("Nonce check failed on honeypot settings save.");
824
  }
825
 
826
  //Save all the form values to the options
857
  <tr valign="top">
858
  <th scope="row"><?php _e('Enable Honeypot On Login Page', 'all-in-one-wp-security-and-firewall')?>:</th>
859
  <td>
860
+ <input id="aiowps_enable_login_honeypot" name="aiowps_enable_login_honeypot" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_login_honeypot')=='1') echo ' checked="checked"'; ?> value="1"/>
861
+ <label for="aiowps_enable_login_honeypot" class="description"><?php _e('Check this if you want to enable the honeypot feature for the login page', 'all-in-one-wp-security-and-firewall'); ?></label>
862
  </td>
863
  </tr>
864
  </table>
868
  </form>
869
  <?php
870
  }
871
+
 
872
  } //end class
admin/wp-security-dashboard-menu.php CHANGED
@@ -83,8 +83,12 @@ class AIOWPSecurity_Dashboard_Menu extends AIOWPSecurity_Admin_Menu
83
  <?php
84
  }
85
 
86
- public function render_tab2()
87
- {
 
 
 
 
88
  global $wpdb;
89
  include_once 'wp-security-list-locked-ip.php'; //For rendering the AIOWPSecurity_List_Table in tab1
90
  $locked_ip_list = new AIOWPSecurity_List_Locked_IP(); //For rendering the AIOWPSecurity_List_Table in tab1
@@ -120,8 +124,7 @@ class AIOWPSecurity_Dashboard_Menu extends AIOWPSecurity_Admin_Menu
120
  $locked_ip_list->prepare_items();
121
  //echo "put table of locked entries here";
122
  ?>
123
- <form id="tables-filter" method="get"
124
- onSubmit="return confirm('Are you sure you want to perform this bulk operation on the selected entries?');">
125
  <!-- For plugins, we also need to ensure that the form posts back to our current page -->
126
  <input type="hidden" name="page" value="<?php echo esc_attr($_REQUEST['page']); ?>"/>
127
  <?php
@@ -138,8 +141,12 @@ class AIOWPSecurity_Dashboard_Menu extends AIOWPSecurity_Admin_Menu
138
  <?php
139
  }
140
 
141
- public function render_tab3()
142
- {
 
 
 
 
143
  global $wpdb;
144
  include_once 'wp-security-list-permanent-blocked-ip.php'; //For rendering the AIOWPSecurity_List_Table
145
  $blocked_ip_list = new AIOWPSecurity_List_Blocked_IP(); //For rendering the AIOWPSecurity_List_Table
@@ -150,7 +157,6 @@ class AIOWPSecurity_Dashboard_Menu extends AIOWPSecurity_Admin_Menu
150
  $blocked_ip_list->unblock_ip_address(strip_tags($_REQUEST['blocked_id']));
151
  }
152
  }
153
- AIOWPSecurity_Admin_Menu::display_bulk_result_message();
154
 
155
  ?>
156
  <div class="aio_blue_box">
@@ -170,7 +176,7 @@ class AIOWPSecurity_Dashboard_Menu extends AIOWPSecurity_Admin_Menu
170
  //Fetch, prepare, sort, and filter our data...
171
  $blocked_ip_list->prepare_items();
172
  ?>
173
- <form id="tables-filter" method="get">
174
  <!-- For plugins, we also need to ensure that the form posts back to our current page -->
175
  <input type="hidden" name="page" value="<?php echo esc_attr($_REQUEST['page']); ?>"/>
176
  <?php
@@ -188,13 +194,12 @@ class AIOWPSecurity_Dashboard_Menu extends AIOWPSecurity_Admin_Menu
188
  <?php
189
  }
190
 
191
- /**
192
- * Renders tab 5 which is the AIOWPS Logs tab. Responsible for displaying the logs
193
- *
194
- * @return void
195
- */
196
- public function render_tab4()
197
- {
198
  //Needed for rendering the debug log table
199
  include_once 'wp-security-list-debug.php';
200
  $debug_log_list = new AIOWPSecurity_List_Debug_Log();
@@ -551,7 +556,7 @@ class AIOWPSecurity_Dashboard_Menu extends AIOWPSecurity_Admin_Menu
551
  foreach ($data as $entry) {
552
  $login_summary_table .= '<tr>';
553
  $login_summary_table .= '<td>' . $entry['user_login'] . '</td>';
554
- $login_summary_table .= '<td>' . $entry['login_date'] . '</td>';
555
  $login_summary_table .= '<td>' . $entry['login_ip'] . '</td>';
556
  $login_summary_table .= '</tr>';
557
  }
@@ -604,9 +609,9 @@ class AIOWPSecurity_Dashboard_Menu extends AIOWPSecurity_Admin_Menu
604
  //Insert Rename Login Page feature box if this feature is active
605
  if ($aio_wp_security->configs->get_value('aiowps_enable_rename_login_page') == '1') {
606
  if (get_option('permalink_structure')) {
607
- $home_url = trailingslashit(home_url());
608
  } else {
609
- $home_url = trailingslashit(home_url()) . '?';
610
  }
611
 
612
  $rename_login_feature_link = '<a href="admin.php?page=' . AIOWPSEC_BRUTE_FORCE_MENU_SLUG . '&tab=tab1" target="_blank">' . __('Rename Login Page', 'all-in-one-wp-security-and-firewall') . '</a>';
@@ -614,7 +619,7 @@ class AIOWPSecurity_Dashboard_Menu extends AIOWPSecurity_Admin_Menu
614
 
615
  echo '<p>' . sprintf(__('The %s feature is currently active.', 'all-in-one-wp-security-and-firewall'), $rename_login_feature_link) . '</p>';
616
  echo '<p>' . __('Your new WordPress login URL is now:', 'all-in-one-wp-security-and-firewall') . '</p>';
617
- echo '<p><strong>' . $home_url . $aio_wp_security->configs->get_value('aiowps_login_page_slug') . '</strong></p>';
618
  echo '</div>'; //yellow box div
619
  echo '<div class="aio_clear_float"></div>';
620
  }//End if statement for Rename Login box
@@ -626,7 +631,7 @@ class AIOWPSecurity_Dashboard_Menu extends AIOWPSecurity_Admin_Menu
626
  // default display messages
627
  $multiple_users_info_msg = __('Number of users currently logged into your site (including you) is:', 'all-in-one-wp-security-and-firewall');
628
  $single_user_info_msg = __('There are no other users currently logged in.', 'all-in-one-wp-security-and-firewall');
629
- if (AIOWPSecurity_Utility::is_multisite_install()) {
630
  $current_blog_id = get_current_blog_id();
631
  $is_main = is_main_site($current_blog_id);
632
 
83
  <?php
84
  }
85
 
86
+ /**
87
+ * Renders the submenu's tab2 tab body.
88
+ *
89
+ * @return Void
90
+ */
91
+ public function render_tab2() {
92
  global $wpdb;
93
  include_once 'wp-security-list-locked-ip.php'; //For rendering the AIOWPSecurity_List_Table in tab1
94
  $locked_ip_list = new AIOWPSecurity_List_Locked_IP(); //For rendering the AIOWPSecurity_List_Table in tab1
124
  $locked_ip_list->prepare_items();
125
  //echo "put table of locked entries here";
126
  ?>
127
+ <form id="tables-filter" method="post">
 
128
  <!-- For plugins, we also need to ensure that the form posts back to our current page -->
129
  <input type="hidden" name="page" value="<?php echo esc_attr($_REQUEST['page']); ?>"/>
130
  <?php
141
  <?php
142
  }
143
 
144
+ /**
145
+ * Renders the submenu's tab3 tab body.
146
+ *
147
+ * @return Void
148
+ */
149
+ public function render_tab3() {
150
  global $wpdb;
151
  include_once 'wp-security-list-permanent-blocked-ip.php'; //For rendering the AIOWPSecurity_List_Table
152
  $blocked_ip_list = new AIOWPSecurity_List_Blocked_IP(); //For rendering the AIOWPSecurity_List_Table
157
  $blocked_ip_list->unblock_ip_address(strip_tags($_REQUEST['blocked_id']));
158
  }
159
  }
 
160
 
161
  ?>
162
  <div class="aio_blue_box">
176
  //Fetch, prepare, sort, and filter our data...
177
  $blocked_ip_list->prepare_items();
178
  ?>
179
+ <form id="tables-filter" method="post">
180
  <!-- For plugins, we also need to ensure that the form posts back to our current page -->
181
  <input type="hidden" name="page" value="<?php echo esc_attr($_REQUEST['page']); ?>"/>
182
  <?php
194
  <?php
195
  }
196
 
197
+ /**
198
+ * Renders tab 4 which is the AIOWPS Logs tab. Responsible for displaying the logs
199
+ *
200
+ * @return void
201
+ */
202
+ public function render_tab4() {
 
203
  //Needed for rendering the debug log table
204
  include_once 'wp-security-list-debug.php';
205
  $debug_log_list = new AIOWPSecurity_List_Debug_Log();
556
  foreach ($data as $entry) {
557
  $login_summary_table .= '<tr>';
558
  $login_summary_table .= '<td>' . $entry['user_login'] . '</td>';
559
+ $login_summary_table .= '<td>' . get_date_from_gmt(mysql2date('Y-m-d H:i:s', $entry['login_date']), get_option('date_format').' '.get_option('time_format')) . '</td>';
560
  $login_summary_table .= '<td>' . $entry['login_ip'] . '</td>';
561
  $login_summary_table .= '</tr>';
562
  }
609
  //Insert Rename Login Page feature box if this feature is active
610
  if ($aio_wp_security->configs->get_value('aiowps_enable_rename_login_page') == '1') {
611
  if (get_option('permalink_structure')) {
612
+ $site_url = trailingslashit(site_url());
613
  } else {
614
+ $site_url = trailingslashit(site_url()) . '?';
615
  }
616
 
617
  $rename_login_feature_link = '<a href="admin.php?page=' . AIOWPSEC_BRUTE_FORCE_MENU_SLUG . '&tab=tab1" target="_blank">' . __('Rename Login Page', 'all-in-one-wp-security-and-firewall') . '</a>';
619
 
620
  echo '<p>' . sprintf(__('The %s feature is currently active.', 'all-in-one-wp-security-and-firewall'), $rename_login_feature_link) . '</p>';
621
  echo '<p>' . __('Your new WordPress login URL is now:', 'all-in-one-wp-security-and-firewall') . '</p>';
622
+ echo '<p><strong>' . $site_url . $aio_wp_security->configs->get_value('aiowps_login_page_slug') . '</strong></p>';
623
  echo '</div>'; //yellow box div
624
  echo '<div class="aio_clear_float"></div>';
625
  }//End if statement for Rename Login box
631
  // default display messages
632
  $multiple_users_info_msg = __('Number of users currently logged into your site (including you) is:', 'all-in-one-wp-security-and-firewall');
633
  $single_user_info_msg = __('There are no other users currently logged in.', 'all-in-one-wp-security-and-firewall');
634
+ if (is_multisite()) {
635
  $current_blog_id = get_current_blog_id();
636
  $is_main = is_main_site($current_blog_id);
637
 
admin/wp-security-database-menu.php CHANGED
@@ -6,7 +6,7 @@ if(!defined('ABSPATH')){
6
  class AIOWPSecurity_Database_Menu extends AIOWPSecurity_Admin_Menu
7
  {
8
  var $menu_page_slug = AIOWPSEC_DB_SEC_MENU_SLUG;
9
-
10
  /* Specify all the tabs of this menu in the following array */
11
  var $menu_tabs;
12
 
@@ -15,26 +15,83 @@ class AIOWPSecurity_Database_Menu extends AIOWPSecurity_Admin_Menu
15
  'tab2' => 'render_tab2',
16
  );
17
 
18
- function __construct()
19
- {
 
 
20
  $this->render_menu_page();
21
  }
22
-
23
- function set_menu_tabs()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  {
25
- if (AIOWPSecurity_Utility::is_multisite_install() && get_current_blog_id() != 1){
26
  //Suppress the DB prefix change tab if site is a multi site AND not the main site
27
  $this->menu_tabs = array(
28
- //'tab1' => __('DB Prefix', 'all-in-one-wp-security-and-firewall'),
29
- 'tab2' => __('DB Backup', 'all-in-one-wp-security-and-firewall'),
30
  );
31
- }else{
32
  $this->menu_tabs = array(
33
- 'tab1' => __('DB Prefix', 'all-in-one-wp-security-and-firewall'),
34
- 'tab2' => __('DB Backup', 'all-in-one-wp-security-and-firewall'),
35
  );
36
  }
37
-
38
  }
39
 
40
  /*
@@ -126,11 +183,11 @@ class AIOWPSecurity_Database_Menu extends AIOWPSecurity_Admin_Menu
126
  }
127
  }
128
  ?>
129
- <h2><?php _e('Change Database Prefix', 'all-in-one-wp-security-and-firewall')?></h2>
130
  <div class="aio_blue_box">
131
  <?php
132
- echo '<p>'.__('Your WordPress DB is the most important asset of your website because it contains a lot of your site\'s precious information.', 'all-in-one-wp-security-and-firewall').'
133
- <br />'.__('The DB is also a target for hackers via methods such as SQL injections and malicious and automated code which targets certain tables.', 'all-in-one-wp-security-and-firewall').'
134
  <br />'.__('One way to add a layer of protection for your DB is to change the default WordPress table prefix from "wp_" to something else which will be difficult for hackers to guess.', 'all-in-one-wp-security-and-firewall').'
135
  <br />'.__('This feature allows you to easily change the prefix to a value of your choice or to a random value set by this plugin.', 'all-in-one-wp-security-and-firewall').'
136
  </p>';
@@ -138,7 +195,7 @@ class AIOWPSecurity_Database_Menu extends AIOWPSecurity_Admin_Menu
138
  </div>
139
 
140
  <div class="postbox">
141
- <h3 class="hndle"><label for="title"><?php _e('DB Prefix Options', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
142
  <div class="inside">
143
  <?php
144
  //Display security info badge
@@ -147,18 +204,21 @@ class AIOWPSecurity_Database_Menu extends AIOWPSecurity_Admin_Menu
147
  ?>
148
 
149
  <div class="aio_red_box">
150
- <?php
151
- $backup_tab_link = '<a href="admin.php?page='.AIOWPSEC_DB_SEC_MENU_SLUG.'&tab=tab2">DB Backup</a>';
152
- $info_msg = '<p><strong>'.sprintf( __('It is recommended that you perform a %s before using this feature', 'all-in-one-wp-security-and-firewall'), $backup_tab_link).'</strong></p>';
153
- echo $info_msg;
154
- ?>
 
 
 
155
  </div>
156
 
157
  <form action="" method="POST">
158
  <?php wp_nonce_field('aiowpsec-db-prefix-change-nonce'); ?>
159
  <table class="form-table">
160
  <tr valign="top">
161
- <th scope="row"><?php _e('Current DB Table Prefix', 'all-in-one-wp-security-and-firewall')?>:</th>
162
  <td>
163
  <span class="aiowpsec_field_value"><strong><?php echo $wpdb->prefix; ?></strong></span>
164
  <?php
@@ -171,17 +231,22 @@ class AIOWPSecurity_Database_Menu extends AIOWPSecurity_Admin_Menu
171
  </td>
172
  </tr>
173
  <tr valign="top">
174
- <th scope="row"><?php _e('Generate New DB Table Prefix', 'all-in-one-wp-security-and-firewall')?>:</th>
 
 
 
 
 
175
  <td>
176
- <input name="aiowps_enable_random_prefix" type="checkbox" <?php if($aio_wp_security->configs->get_value('aiowps_enable_random_prefix')=='1') echo ' checked="checked"'; ?> value="1"/>
177
- <span class="description"><?php _e('Check this if you want the plugin to generate a random 6 character string for the table prefix', 'all-in-one-wp-security-and-firewall'); ?></span>
178
  <br /><?php _e('OR', 'all-in-one-wp-security-and-firewall'); ?>
179
- <br /><input type="text" size="10" name="aiowps_new_manual_db_prefix" value="<?php //echo $aio_wp_security->configs->get_value('aiowps_new_manual_db_prefix'); ?>" />
180
- <span class="description"><?php _e('Choose your own DB prefix by specifying a string which contains letters and/or numbers and/or underscores. Example: xyz_', 'all-in-one-wp-security-and-firewall'); ?></span>
181
  </td>
182
  </tr>
183
  </table>
184
- <input type="submit" name="aiowps_db_prefix_change" value="<?php _e('Change DB Prefix', 'all-in-one-wp-security-and-firewall')?>" class="button-primary" />
185
  </form>
186
  </div></div>
187
  <?php
@@ -191,168 +256,37 @@ class AIOWPSecurity_Database_Menu extends AIOWPSecurity_Admin_Menu
191
  $this->change_db_prefix($old_db_prefix,$new_db_prefix);
192
  }
193
  }
194
-
195
- function render_tab2()
196
- {
197
- global $aio_wp_security;
198
- global $aiowps_feature_mgr;
199
- if (isset($_POST['aiowps_manual_db_backup']))
200
- {
201
- $nonce=$_REQUEST['_wpnonce'];
202
- if (!wp_verify_nonce($nonce, 'aiowpsec-db-manual-change-nonce'))
203
- {
204
- $aio_wp_security->debug_logger->log_debug("Nonce check failed for manual DB backup operation!",4);
205
- die(__('Nonce check failed for manual DB backup operation!','all-in-one-wp-security-and-firewall'));
206
- }
207
 
208
- $result = $aio_wp_security->backup_obj->execute_backup();
209
- if ($result)
210
- {
211
- $backup_file_name = $aio_wp_security->backup_obj->last_backup_file_name;
212
- if (function_exists('is_multisite') && is_multisite())
213
- {
214
- $aiowps_backup_file_path = $aio_wp_security->backup_obj->last_backup_file_dir_multisite . '/' . $backup_file_name;
215
- }
216
- else
217
- {
218
- $aiowps_backup_dir = WP_CONTENT_DIR.'/'.AIO_WP_SECURITY_BACKUPS_DIR_NAME;
219
- $aiowps_backup_file_path = $aiowps_backup_dir. '/' . $backup_file_name;
220
- }
221
- echo '<div id="message" class="updated fade"><p>';
222
- _e('DB Backup was successfully completed! You will receive the backup file via email if you have enabled "Send Backup File Via Email", otherwise you can retrieve it via FTP from the following directory:','all-in-one-wp-security-and-firewall');
223
- echo '</p><p>';
224
- _e('Your DB Backup File location: ');
225
- echo '<strong>'.$aiowps_backup_file_path.'</strong>';
226
- echo '</p></div>';
227
- }
228
- else
229
- {
230
- $aio_wp_security->debug_logger->log_debug("DB Backup - Backup operation failed!",4);
231
- $this->show_msg_error(__('DB Backup failed. Please check the permissions of the backup directory.','all-in-one-wp-security-and-firewall'));
232
- }
233
  }
234
-
235
- if(isset($_POST['aiowps_schedule_backups']))//Do form submission tasks
236
- {
237
- $error = '';
238
- $nonce=$_REQUEST['_wpnonce'];
239
- if (!wp_verify_nonce($nonce, 'aiowpsec-scheduled-backup-nonce'))
240
- {
241
- $aio_wp_security->debug_logger->log_debug("Nonce check failed on scheduled DB backup options save!",4);
242
- die("Nonce check failed on scheduled DB backup options save!");
243
- }
244
-
245
- $backup_frequency = sanitize_text_field($_POST['aiowps_db_backup_frequency']);
246
- if(!is_numeric($backup_frequency))
247
- {
248
- $error .= '<br />'.__('You entered a non numeric value for the "backup time interval" field. It has been set to the default value.','all-in-one-wp-security-and-firewall');
249
- $backup_frequency = '4';//Set it to the default value for this field
250
- }
251
-
252
- $files_to_keep = sanitize_text_field($_POST['aiowps_backup_files_stored']);
253
- if(!is_numeric($files_to_keep))
254
- {
255
- $error .= '<br />'.__('You entered a non numeric value for the "number of backup files to keep" field. It has been set to the default value.','all-in-one-wp-security-and-firewall');
256
- $files_to_keep = '2';//Set it to the default value for this field
257
- }
258
-
259
- $email_address = sanitize_email($_POST['aiowps_backup_email_address']);
260
- if(!is_email($email_address))
261
- {
262
- $error .= '<br />'.__('You have entered an incorrect email address format. It has been set to your WordPress admin email as default.','all-in-one-wp-security-and-firewall');
263
- $email_address = get_bloginfo('admin_email'); //Set the default value to the blog admin email
264
- }
265
-
266
- if($error)
267
- {
268
- $this->show_msg_error(__('Attention!','all-in-one-wp-security-and-firewall').$error);
269
- }
270
-
271
- //Save all the form values to the options
272
- $aio_wp_security->configs->set_value('aiowps_enable_automated_backups',isset($_POST["aiowps_enable_automated_backups"])?'1':'');
273
- $aio_wp_security->configs->set_value('aiowps_db_backup_frequency',absint($backup_frequency));
274
- $aio_wp_security->configs->set_value('aiowps_db_backup_interval',$_POST["aiowps_db_backup_interval"]);
275
- $aio_wp_security->configs->set_value('aiowps_backup_files_stored',absint($files_to_keep));
276
- $aio_wp_security->configs->set_value('aiowps_send_backup_email_address',isset($_POST["aiowps_send_backup_email_address"])?'1':'');
277
- $aio_wp_security->configs->set_value('aiowps_backup_email_address',$email_address);
278
- $aio_wp_security->configs->save_config();
279
-
280
- //Recalculate points after the feature status/options have been altered
281
- $aiowps_feature_mgr->check_feature_status_and_recalculate_points();
282
- $this->show_msg_settings_updated();
283
-
284
- //Let's check if backup interval was set to less than 24 hours
285
- if (isset($_POST["aiowps_enable_automated_backups"]) && ($backup_frequency < 24) && $_POST["aiowps_db_backup_interval"]==0)
286
- {
287
- $alert_user_msg = 'ATTENTION: You have configured your backups to occur at least once daily. For most websites we recommended that you choose a less frequent backup
288
- schedule such as once every few days, once a week or once a month. Choosing a less frequent schedule will also help reduce your server load.';
289
- $this->show_msg_updated_st(__($alert_user_msg, 'all-in-one-wp-security-and-firewall'));
290
- }
291
- }
292
-
293
  ?>
294
  <div class="postbox">
295
- <h3 class="hndle"><label for="title"><?php _e('Manual Backup', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
296
- <div class="inside">
297
- <form action="" method="POST">
298
- <?php wp_nonce_field('aiowpsec-db-manual-change-nonce'); ?>
299
- <p>
300
- <span class="description"><?php _e('To create a new DB backup just click on the button below.', 'all-in-one-wp-security-and-firewall'); ?></span>
301
- </p>
302
- <input type="submit" name="aiowps_manual_db_backup" value="<?php _e('Create DB Backup Now', 'all-in-one-wp-security-and-firewall')?>" class="button-primary" />
303
- </form>
304
- </div></div>
305
- <div class="postbox">
306
- <h3 class="hndle"><label for="title"><?php _e('Automated Scheduled Backups', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
307
- <div class="inside">
308
- <?php
309
- //Display security info badge
310
- global $aiowps_feature_mgr;
311
- $aiowps_feature_mgr->output_feature_details_badge("db-security-db-backup");
312
- ?>
313
-
314
- <form action="" method="POST">
315
- <?php wp_nonce_field('aiowpsec-scheduled-backup-nonce'); ?>
316
- <table class="form-table">
317
- <tr valign="top">
318
- <th scope="row"><?php _e('Enable Automated Scheduled Backups', 'all-in-one-wp-security-and-firewall')?>:</th>
319
- <td>
320
- <input name="aiowps_enable_automated_backups" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_automated_backups')=='1') echo ' checked="checked"'; ?> value="1"/>
321
- <span class="description"><?php _e('Check this if you want the system to automatically generate backups periodically based on the settings below', 'all-in-one-wp-security-and-firewall'); ?></span>
322
- </td>
323
- </tr>
324
- <tr valign="top">
325
- <th scope="row"><?php _e('Backup Time Interval', 'all-in-one-wp-security-and-firewall')?>:</th>
326
- <td><input type="text" size="5" name="aiowps_db_backup_frequency" value="<?php echo $aio_wp_security->configs->get_value('aiowps_db_backup_frequency'); ?>" />
327
- <select id="backup_interval" name="aiowps_db_backup_interval">
328
- <option value="0" <?php selected( $aio_wp_security->configs->get_value('aiowps_db_backup_interval'), '0' ); ?>><?php _e( 'Hours', 'all-in-one-wp-security-and-firewall' ); ?></option>
329
- <option value="1" <?php selected( $aio_wp_security->configs->get_value('aiowps_db_backup_interval'), '1' ); ?>><?php _e( 'Days', 'all-in-one-wp-security-and-firewall' ); ?></option>
330
- <option value="2" <?php selected( $aio_wp_security->configs->get_value('aiowps_db_backup_interval'), '2' ); ?>><?php _e( 'Weeks', 'all-in-one-wp-security-and-firewall' ); ?></option>
331
- </select>
332
- <span class="description"><?php _e('Set the value for how often you would like an automated backup to occur', 'all-in-one-wp-security-and-firewall'); ?></span>
333
- </td>
334
- </tr>
335
- <tr valign="top">
336
- <th scope="row"><?php _e('Number of Backup Files To Keep', 'all-in-one-wp-security-and-firewall')?>:</th>
337
- <td><input type="text" size="5" name="aiowps_backup_files_stored" value="<?php echo $aio_wp_security->configs->get_value('aiowps_backup_files_stored'); ?>" />
338
- <span class="description"><?php _e('Thie field allows you to choose the number of backup files you would like to keep in the backup directory', 'all-in-one-wp-security-and-firewall'); ?></span>
339
- </td>
340
- </tr>
341
- <tr valign="top">
342
- <th scope="row"><?php _e('Send Backup File Via Email', 'all-in-one-wp-security-and-firewall')?>:</th>
343
- <td>
344
- <input name="aiowps_send_backup_email_address" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_send_backup_email_address')=='1') echo ' checked="checked"'; ?> value="1"/>
345
- <span class="description"><?php _e('Check this if you want the system to email you the backup file after a DB backup has been performed', 'all-in-one-wp-security-and-firewall'); ?></span>
346
- <br /><input type="text" size="30" name="aiowps_backup_email_address" value="<?php echo $aio_wp_security->configs->get_value('aiowps_backup_email_address'); ?>" />
347
- <span class="description"><?php _e('Enter an email address', 'all-in-one-wp-security-and-firewall'); ?></span>
348
- </td>
349
- </tr>
350
- </table>
351
- <input type="submit" name="aiowps_schedule_backups" value="<?php _e('Save Settings', 'all-in-one-wp-security-and-firewall')?>" class="button-primary" />
352
- </form>
353
- </div></div>
354
-
355
  <?php
 
356
  }
357
 
358
  /*
@@ -395,7 +329,7 @@ class AIOWPSecurity_Database_Menu extends AIOWPSecurity_Admin_Menu
395
  }
396
 
397
  //Get multisite blog_ids if applicable
398
- if (AIOWPSecurity_Utility::is_multisite_install()) {
399
  $blog_ids = AIOWPSecurity_Utility::get_blog_ids();
400
  }
401
 
@@ -477,7 +411,7 @@ class AIOWPSecurity_Database_Menu extends AIOWPSecurity_Admin_Menu
477
  }
478
 
479
  //Now let's update the options tables for the multisite subsites if applicable
480
- if (AIOWPSecurity_Utility::is_multisite_install()) {
481
  if(!empty($blog_ids)){
482
  foreach ($blog_ids as $blog_id) {
483
  if ($blog_id == 1){continue;} //skip main site
@@ -527,7 +461,7 @@ class AIOWPSecurity_Database_Menu extends AIOWPSecurity_Admin_Menu
527
  }
528
  echo '<p class="aio_success_with_icon">'.__('The usermeta table records which had references to the old DB prefix were updated successfully!', 'all-in-one-wp-security-and-firewall').'</p>';
529
  //Display tasks finished message
530
- $tasks_finished_msg_string = '<p class="aio_info_with_icon">'. __('DB prefix change tasks have been completed.', 'all-in-one-wp-security-and-firewall').'</p>';
531
  echo ($tasks_finished_msg_string);
532
  }
533
 
@@ -601,4 +535,4 @@ class AIOWPSecurity_Database_Menu extends AIOWPSecurity_Admin_Menu
601
  return;
602
  }
603
 
604
- } //end class
6
  class AIOWPSecurity_Database_Menu extends AIOWPSecurity_Admin_Menu
7
  {
8
  var $menu_page_slug = AIOWPSEC_DB_SEC_MENU_SLUG;
9
+
10
  /* Specify all the tabs of this menu in the following array */
11
  var $menu_tabs;
12
 
15
  'tab2' => 'render_tab2',
16
  );
17
 
18
+ /**
19
+ * Class constructor
20
+ */
21
+ public function __construct() {
22
  $this->render_menu_page();
23
  }
24
+
25
+ /**
26
+ * Return installation or activation link of UpdraftPlus plugin
27
+ *
28
+ * @return String
29
+ */
30
+ private function get_install_activate_link_of_updraft_plugin() {
31
+ // If UpdraftPlus is activated, then return empty.
32
+ if (class_exists('UpdraftPlus')) return '';
33
+
34
+ // Generally it is 'updraftplus/updraftplus.php',
35
+ // but we can't assume that the user hasn't renamed the plugin folder - with 3 million UDP users and 1 million AIOWPS, there will be some who have.
36
+ $updraftplus_plugin_file_rel_to_plugins_dir = $this->get_updraftplus_plugin_file_rel_to_plugins_dir();
37
+
38
+ // If UpdraftPlus is installed but not activated, then return activate link.
39
+ if ($updraftplus_plugin_file_rel_to_plugins_dir) {
40
+ $activate_url = add_query_arg(array(
41
+ '_wpnonce' => wp_create_nonce('activate-plugin_'.$updraftplus_plugin_file_rel_to_plugins_dir),
42
+ 'action' => 'activate',
43
+ 'plugin' => $updraftplus_plugin_file_rel_to_plugins_dir,
44
+ ), network_admin_url('plugins.php'));
45
+
46
+ // If is network admin then add to link newtwork activation.
47
+ if (is_network_admin()) {
48
+ $activate_url = add_query_arg(array('networkwide' => 1), $activate_url);
49
+ }
50
+ return sprintf('<a href="%s">%s</a>',
51
+ $activate_url,
52
+ __('UpdraftPlus is installed but currently not active.', 'all-in-one-wp-security-and-firewall') .' '. __('Follow this link to activate UpdraftPlus, to take a backup.', 'all-in-one-wp-security-and-firewall')
53
+ );
54
+ }
55
+
56
+ // If UpdraftPlus is not activated or installed, then return the installation link
57
+ return '<a href="'.wp_nonce_url(self_admin_url('update.php?action=install-plugin&plugin=updraftplus'), 'install-plugin_updraftplus').'">'.__('Follow this link to install UpdraftPlus, to take a database backup.', 'all-in-one-wp-security-and-firewall').'</a>';
58
+ }
59
+
60
+ /**
61
+ * Get path to the UpdraftPlus plugin file relative to the plugins directory.
62
+ *
63
+ * @return String|false path to the UpdraftPlus plugin file relative to the plugins directory
64
+ */
65
+ private function get_updraftplus_plugin_file_rel_to_plugins_dir() {
66
+ if (!function_exists('get_plugins')) {
67
+ include_once ABSPATH . '/wp-admin/includes/plugin.php';
68
+ }
69
+
70
+ $installed_plugins = get_plugins();
71
+ $installed_plugins_keys = array_keys($installed_plugins);
72
+ foreach ($installed_plugins_keys as $plugin_file_rel_to_plugins_dir) {
73
+ $temp_plugin_file_name = substr($plugin_file_rel_to_plugins_dir, strpos($plugin_file_rel_to_plugins_dir, '/') + 1);
74
+ if ('updraftplus.php' == $temp_plugin_file_name) {
75
+ return $plugin_file_rel_to_plugins_dir;
76
+ }
77
+ }
78
+ return false;
79
+ }
80
+
81
+ public function set_menu_tabs()
82
  {
83
+ if (is_multisite() && get_current_blog_id() != 1){
84
  //Suppress the DB prefix change tab if site is a multi site AND not the main site
85
  $this->menu_tabs = array(
86
+ //'tab1' => __('Database prefix', 'all-in-one-wp-security-and-firewall'),
87
+ 'tab2' => __('Database backup', 'all-in-one-wp-security-and-firewall'),
88
  );
89
+ } else {
90
  $this->menu_tabs = array(
91
+ 'tab1' => __('Database prefix', 'all-in-one-wp-security-and-firewall'),
92
+ 'tab2' => __('Database backup', 'all-in-one-wp-security-and-firewall'),
93
  );
94
  }
 
95
  }
96
 
97
  /*
183
  }
184
  }
185
  ?>
186
+ <h2><?php _e('Change database prefix', 'all-in-one-wp-security-and-firewall')?></h2>
187
  <div class="aio_blue_box">
188
  <?php
189
+ echo '<p>'.__('Your WordPress database is the most important asset of your website because it contains a lot of your site\'s precious information.', 'all-in-one-wp-security-and-firewall').'
190
+ <br />'.__('The database is also a target for hackers via methods such as SQL injections and malicious and automated code which targets certain tables.', 'all-in-one-wp-security-and-firewall').'
191
  <br />'.__('One way to add a layer of protection for your DB is to change the default WordPress table prefix from "wp_" to something else which will be difficult for hackers to guess.', 'all-in-one-wp-security-and-firewall').'
192
  <br />'.__('This feature allows you to easily change the prefix to a value of your choice or to a random value set by this plugin.', 'all-in-one-wp-security-and-firewall').'
193
  </p>';
195
  </div>
196
 
197
  <div class="postbox">
198
+ <h3 class="hndle"><label for="title"><?php _e('Database prefix options', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
199
  <div class="inside">
200
  <?php
201
  //Display security info badge
204
  ?>
205
 
206
  <div class="aio_red_box">
207
+ <p>
208
+ <strong>
209
+ <?php
210
+ $backup_tab_link = '<a href="admin.php?page='.AIOWPSEC_DB_SEC_MENU_SLUG.'&tab=tab2">'.__('database backup', 'all-in-one-wp-security-and-firewall').'</a>';
211
+ printf(__('It is recommended that you perform a %s before using this feature', 'all-in-one-wp-security-and-firewall'), $backup_tab_link);
212
+ ?>
213
+ </strong>
214
+ </p>
215
  </div>
216
 
217
  <form action="" method="POST">
218
  <?php wp_nonce_field('aiowpsec-db-prefix-change-nonce'); ?>
219
  <table class="form-table">
220
  <tr valign="top">
221
+ <th scope="row"><?php _e('Current database table prefix', 'all-in-one-wp-security-and-firewall')?>:</th>
222
  <td>
223
  <span class="aiowpsec_field_value"><strong><?php echo $wpdb->prefix; ?></strong></span>
224
  <?php
231
  </td>
232
  </tr>
233
  <tr valign="top">
234
+ <th scope="row">
235
+ <label>
236
+ <label for="aiowps_new_manual_db_prefix">
237
+ <?php _e('Generate new database table prefix', 'all-in-one-wp-security-and-firewall'); ?>:
238
+ </label>
239
+ </th>
240
  <td>
241
+ <input id="aiowps_enable_random_prefix" name="aiowps_enable_random_prefix" type="checkbox" <?php if($aio_wp_security->configs->get_value('aiowps_enable_random_prefix')=='1') echo ' checked="checked"'; ?> value="1"/>
242
+ <label for="aiowps_enable_random_prefix" class="description"><?php _e('Check this if you want the plugin to generate a random 6 character string for the table prefix', 'all-in-one-wp-security-and-firewall'); ?></label>
243
  <br /><?php _e('OR', 'all-in-one-wp-security-and-firewall'); ?>
244
+ <br /><input type="text" size="10" id="aiowps_new_manual_db_prefix" name="aiowps_new_manual_db_prefix" value="<?php //echo $aio_wp_security->configs->get_value('aiowps_new_manual_db_prefix'); ?>" />
245
+ <label for="aiowps_new_manual_db_prefix" class="description"><?php _e('Choose your own DB prefix by specifying a string which contains letters and/or numbers and/or underscores. Example: xyz_', 'all-in-one-wp-security-and-firewall'); ?></label>
246
  </td>
247
  </tr>
248
  </table>
249
+ <input type="submit" name="aiowps_db_prefix_change" value="<?php _e('Change database prefix', 'all-in-one-wp-security-and-firewall')?>" class="button-primary" />
250
  </form>
251
  </div></div>
252
  <?php
256
  $this->change_db_prefix($old_db_prefix,$new_db_prefix);
257
  }
258
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
259
 
260
+ /**
261
+ * Render tab2 contents.
262
+ *
263
+ * @return void
264
+ */
265
+ private function render_tab2() {
266
+ global $aio_wp_security;
267
+ $updraftplus_admin = !empty($GLOBALS['updraftplus_admin']) ? $GLOBALS['updraftplus_admin'] : null;
268
+ if ($updraftplus_admin) {
269
+ $updraftplus_admin->add_backup_scaffolding(__('Take a database backup using UpdraftPlus', 'all-in-one-wp-security-and-firewall'), array($updraftplus_admin, 'backupnow_modal_contents'));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  }
271
+ $install_activate_link = $this->get_install_activate_link_of_updraft_plugin();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  ?>
273
  <div class="postbox">
274
+ <h3 class="hndle"><label for="title"><?php _e('Manual Backup', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
275
+ <div class="inside">
276
+ <?php if (empty($install_activate_link)) { ?>
277
+ <p>
278
+ <a href="<?php echo admin_url('options-general.php?page=updraftplus#updraft-existing-backups-heading'); ?>" title="<?php _e('UpdraftPlus Backup/Restore', 'all-in-one-wp-security-and-firewall'); ?>" alt="<?php _e('UpdraftPlus Backup/Restore', 'all-in-one-wp-security-and-firewall'); ?>"><?php echo __('Your backups are on the UpdraftPlus Backup/Restore admin page.', 'all-in-one-wp-security-and-firewall'); ?></a>
279
+ </p>
280
+ <button type="button" id="aios-manual-db-backup-now" class="button-primary"><?php _e('Create database backup now', 'all-in-one-wp-security-and-firewall'); ?></button>
281
+ <?php } else { ?>
282
+ <p>
283
+ <?php echo wp_kses($install_activate_link, array('a' => array('title' => array(), 'href' => array()))); ?>
284
+ </p>
285
+ <?php } ?>
286
+ </div>
287
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  <?php
289
+ $aio_wp_security->include_template('automated-database-backup.php', false, array('install_activate_link' => $install_activate_link));
290
  }
291
 
292
  /*
329
  }
330
 
331
  //Get multisite blog_ids if applicable
332
+ if (is_multisite()) {
333
  $blog_ids = AIOWPSecurity_Utility::get_blog_ids();
334
  }
335
 
411
  }
412
 
413
  //Now let's update the options tables for the multisite subsites if applicable
414
+ if (is_multisite()) {
415
  if(!empty($blog_ids)){
416
  foreach ($blog_ids as $blog_id) {
417
  if ($blog_id == 1){continue;} //skip main site
461
  }
462
  echo '<p class="aio_success_with_icon">'.__('The usermeta table records which had references to the old DB prefix were updated successfully!', 'all-in-one-wp-security-and-firewall').'</p>';
463
  //Display tasks finished message
464
+ $tasks_finished_msg_string = '<p class="aio_info_with_icon">'. __('The database prefix change tasks have been completed.', 'all-in-one-wp-security-and-firewall').'</p>';
465
  echo ($tasks_finished_msg_string);
466
  }
467
 
535
  return;
536
  }
537
 
538
+ } //end class
admin/wp-security-filescan-menu.php CHANGED
@@ -288,13 +288,13 @@ class AIOWPSecurity_Filescan_Menu extends AIOWPSecurity_Admin_Menu
288
  <tr valign="top">
289
  <th scope="row"><?php _e('Enable Automated File Change Detection Scan', 'all-in-one-wp-security-and-firewall')?>:</th>
290
  <td>
291
- <input name="aiowps_enable_automated_fcd_scan" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_automated_fcd_scan')=='1') echo ' checked="checked"'; ?> value="1"/>
292
- <span class="description"><?php _e('Check this if you want the system to automatically/periodically scan your files to check for file changes based on the settings below', 'all-in-one-wp-security-and-firewall'); ?></span>
293
  </td>
294
  </tr>
295
  <tr valign="top">
296
- <th scope="row"><?php _e('Scan Time Interval', 'all-in-one-wp-security-and-firewall')?>:</th>
297
- <td><input type="text" size="5" name="aiowps_fcd_scan_frequency" value="<?php echo $aio_wp_security->configs->get_value('aiowps_fcd_scan_frequency'); ?>" />
298
  <select id="backup_interval" name="aiowps_fcd_scan_interval">
299
  <option value="0" <?php selected( $aio_wp_security->configs->get_value('aiowps_fcd_scan_interval'), '0' ); ?>><?php _e( 'Hours', 'all-in-one-wp-security-and-firewall' ); ?></option>
300
  <option value="1" <?php selected( $aio_wp_security->configs->get_value('aiowps_fcd_scan_interval'), '1' ); ?>><?php _e( 'Days', 'all-in-one-wp-security-and-firewall' ); ?></option>
@@ -304,8 +304,8 @@ class AIOWPSecurity_Filescan_Menu extends AIOWPSecurity_Admin_Menu
304
  </td>
305
  </tr>
306
  <tr valign="top">
307
- <th scope="row"><?php _e('File Types To Ignore', 'all-in-one-wp-security-and-firewall')?>:</th>
308
- <td><textarea name="aiowps_fcd_exclude_filetypes" rows="5" cols="50"><?php echo htmlspecialchars($aio_wp_security->configs->get_value('aiowps_fcd_exclude_filetypes')); ?></textarea>
309
  <br />
310
  <span class="description"><?php _e('Enter each file type or extension on a new line which you wish to exclude from the file change detection scan.', 'all-in-one-wp-security-and-firewall'); ?></span>
311
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
@@ -321,8 +321,8 @@ class AIOWPSecurity_Filescan_Menu extends AIOWPSecurity_Admin_Menu
321
  </td>
322
  </tr>
323
  <tr valign="top">
324
- <th scope="row"><?php _e('Files/Directories To Ignore', 'all-in-one-wp-security-and-firewall')?>:</th>
325
- <td><textarea name="aiowps_fcd_exclude_files" rows="5" cols="50"><?php echo htmlspecialchars($aio_wp_security->configs->get_value('aiowps_fcd_exclude_files')); ?></textarea>
326
  <br />
327
  <span class="description"><?php _e('Enter each file or directory on a new line which you wish to exclude from the file change detection scan.', 'all-in-one-wp-security-and-firewall'); ?></span>
328
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
@@ -337,12 +337,16 @@ class AIOWPSecurity_Filescan_Menu extends AIOWPSecurity_Admin_Menu
337
  </td>
338
  </tr>
339
  <tr valign="top">
340
- <th scope="row"><?php _e('Send Email When Change Detected', 'all-in-one-wp-security-and-firewall')?>:</th>
 
 
 
 
341
  <td>
342
- <input name="aiowps_send_fcd_scan_email" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_send_fcd_scan_email')=='1') echo ' checked="checked"'; ?> value="1"/>
343
- <span class="description"><?php _e('Check this if you want the system to email you if a file change was detected', 'all-in-one-wp-security-and-firewall'); ?></span>
344
  <br />
345
- <textarea name="aiowps_fcd_scan_email_address" rows="5" cols="50"><?php echo htmlspecialchars($aio_wp_security->configs->get_value('aiowps_fcd_scan_email_address')); ?></textarea>
346
  <br />
347
  <span class="description"><?php _e('Enter one or more email addresses on a new line.', 'all-in-one-wp-security-and-firewall'); ?></span>
348
  </td>
288
  <tr valign="top">
289
  <th scope="row"><?php _e('Enable Automated File Change Detection Scan', 'all-in-one-wp-security-and-firewall')?>:</th>
290
  <td>
291
+ <input id="aiowps_enable_automated_fcd_scan" name="aiowps_enable_automated_fcd_scan" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_automated_fcd_scan')=='1') echo ' checked="checked"'; ?> value="1"/>
292
+ <label for="aiowps_enable_automated_fcd_scan" class="description"><?php _e('Check this if you want the system to automatically/periodically scan your files to check for file changes based on the settings below', 'all-in-one-wp-security-and-firewall'); ?></label>
293
  </td>
294
  </tr>
295
  <tr valign="top">
296
+ <th scope="row"><label for="aiowps_fcd_scan_frequency"><?php _e('Scan Time Interval', 'all-in-one-wp-security-and-firewall')?>:</label></th>
297
+ <td><input id="aiowps_fcd_scan_frequency" type="text" size="5" name="aiowps_fcd_scan_frequency" value="<?php echo $aio_wp_security->configs->get_value('aiowps_fcd_scan_frequency'); ?>" />
298
  <select id="backup_interval" name="aiowps_fcd_scan_interval">
299
  <option value="0" <?php selected( $aio_wp_security->configs->get_value('aiowps_fcd_scan_interval'), '0' ); ?>><?php _e( 'Hours', 'all-in-one-wp-security-and-firewall' ); ?></option>
300
  <option value="1" <?php selected( $aio_wp_security->configs->get_value('aiowps_fcd_scan_interval'), '1' ); ?>><?php _e( 'Days', 'all-in-one-wp-security-and-firewall' ); ?></option>
304
  </td>
305
  </tr>
306
  <tr valign="top">
307
+ <th scope="row"><label for="aiowps_fcd_exclude_filetypes"><?php _e('File Types To Ignore', 'all-in-one-wp-security-and-firewall')?>:</label></th>
308
+ <td><textarea id="aiowps_fcd_exclude_filetypes" name="aiowps_fcd_exclude_filetypes" rows="5" cols="50"><?php echo htmlspecialchars($aio_wp_security->configs->get_value('aiowps_fcd_exclude_filetypes')); ?></textarea>
309
  <br />
310
  <span class="description"><?php _e('Enter each file type or extension on a new line which you wish to exclude from the file change detection scan.', 'all-in-one-wp-security-and-firewall'); ?></span>
311
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
321
  </td>
322
  </tr>
323
  <tr valign="top">
324
+ <th scope="row"><label for="aiowps_fcd_exclude_files"><?php _e('Files/Directories To Ignore', 'all-in-one-wp-security-and-firewall')?>:</label></th>
325
+ <td><textarea id="aiowps_fcd_exclude_files" name="aiowps_fcd_exclude_files" rows="5" cols="50"><?php echo htmlspecialchars($aio_wp_security->configs->get_value('aiowps_fcd_exclude_files')); ?></textarea>
326
  <br />
327
  <span class="description"><?php _e('Enter each file or directory on a new line which you wish to exclude from the file change detection scan.', 'all-in-one-wp-security-and-firewall'); ?></span>
328
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
337
  </td>
338
  </tr>
339
  <tr valign="top">
340
+ <th scope="row">
341
+ <label for="aiowps_fcd_scan_email_address">
342
+ <?php _e('Send Email When Change Detected', 'all-in-one-wp-security-and-firewall'); ?>:
343
+ </label>
344
+ </th>
345
  <td>
346
+ <input id="aiowps_send_fcd_scan_email" name="aiowps_send_fcd_scan_email" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_send_fcd_scan_email')=='1') echo ' checked="checked"'; ?> value="1"/>
347
+ <label for="aiowps_send_fcd_scan_email" class="description"><?php _e('Check this if you want the system to email you if a file change was detected', 'all-in-one-wp-security-and-firewall'); ?></label>
348
  <br />
349
+ <textarea name="aiowps_fcd_scan_email_address" id="aiowps_fcd_scan_email_address" rows="5" cols="50"><?php echo htmlspecialchars($aio_wp_security->configs->get_value('aiowps_fcd_scan_email_address')); ?></textarea>
350
  <br />
351
  <span class="description"><?php _e('Enter one or more email addresses on a new line.', 'all-in-one-wp-security-and-firewall'); ?></span>
352
  </td>
admin/wp-security-filesystem-menu.php CHANGED
@@ -234,8 +234,8 @@ class AIOWPSecurity_Filesystem_Menu extends AIOWPSecurity_Admin_Menu
234
  <tr valign="top">
235
  <th scope="row"><?php _e('Disable Ability To Edit PHP Files', 'all-in-one-wp-security-and-firewall')?>:</th>
236
  <td>
237
- <input name="aiowps_disable_file_editing" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_disable_file_editing')=='1') echo ' checked="checked"'; ?> value="1"/>
238
- <span class="description"><?php _e('Check this if you want to remove the ability for people to edit PHP files via the WP dashboard', 'all-in-one-wp-security-and-firewall'); ?></span>
239
  </td>
240
  </tr>
241
  </table>
@@ -310,8 +310,8 @@ class AIOWPSecurity_Filesystem_Menu extends AIOWPSecurity_Admin_Menu
310
  <tr valign="top">
311
  <th scope="row"><?php _e('Prevent Access to WP Default Install Files', 'all-in-one-wp-security-and-firewall')?>:</th>
312
  <td>
313
- <input name="aiowps_prevent_default_wp_file_access" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_prevent_default_wp_file_access')=='1') echo ' checked="checked"'; ?> value="1"/>
314
- <span class="description"><?php _e('Check this if you want to prevent access to readme.html, license.txt and wp-config-sample.php.', 'all-in-one-wp-security-and-firewall'); ?></span>
315
  </td>
316
  </tr>
317
  </table>
@@ -355,8 +355,8 @@ class AIOWPSecurity_Filesystem_Menu extends AIOWPSecurity_Admin_Menu
355
  <p><?php _e('Please click the button below to view the latest system logs', 'all-in-one-wp-security-and-firewall'); ?>:</p>
356
  <form action="" method="POST">
357
  <?php wp_nonce_field('aiowpsec-view-system-logs-nonce'); ?>
358
- <div><?php _e('Enter System Log File Name', 'all-in-one-wp-security-and-firewall')?>:
359
- <input type="text" size="25" name="aiowps_system_log_file" value="<?php echo esc_html($sys_log_file); ?>" />
360
  <span class="description"><?php _e('Enter your system log file name. (Defaults to error_log)', 'all-in-one-wp-security-and-firewall'); ?></span>
361
  </div>
362
  <div class="aio_spacer_15"></div>
234
  <tr valign="top">
235
  <th scope="row"><?php _e('Disable Ability To Edit PHP Files', 'all-in-one-wp-security-and-firewall')?>:</th>
236
  <td>
237
+ <input id="aiowps_disable_file_editing" name="aiowps_disable_file_editing" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_disable_file_editing')=='1') echo ' checked="checked"'; ?> value="1"/>
238
+ <label for="aiowps_disable_file_editing" class="description"><?php _e('Check this if you want to remove the ability for people to edit PHP files via the WP dashboard', 'all-in-one-wp-security-and-firewall'); ?></label>
239
  </td>
240
  </tr>
241
  </table>
310
  <tr valign="top">
311
  <th scope="row"><?php _e('Prevent Access to WP Default Install Files', 'all-in-one-wp-security-and-firewall')?>:</th>
312
  <td>
313
+ <input id="aiowps_prevent_default_wp_file_access" name="aiowps_prevent_default_wp_file_access" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_prevent_default_wp_file_access')=='1') echo ' checked="checked"'; ?> value="1"/>
314
+ <label for="aiowps_prevent_default_wp_file_access" class="description"><?php _e('Check this if you want to prevent access to readme.html, license.txt and wp-config-sample.php.', 'all-in-one-wp-security-and-firewall'); ?></label>
315
  </td>
316
  </tr>
317
  </table>
355
  <p><?php _e('Please click the button below to view the latest system logs', 'all-in-one-wp-security-and-firewall'); ?>:</p>
356
  <form action="" method="POST">
357
  <?php wp_nonce_field('aiowpsec-view-system-logs-nonce'); ?>
358
+ <div><label for="aiowps_system_log_file"><?php _e('Enter System Log File Name', 'all-in-one-wp-security-and-firewall')?>:</label>
359
+ <input id="aiowps_system_log_file" type="text" size="25" name="aiowps_system_log_file" value="<?php echo esc_html($sys_log_file); ?>" />
360
  <span class="description"><?php _e('Enter your system log file name. (Defaults to error_log)', 'all-in-one-wp-security-and-firewall'); ?></span>
361
  </div>
362
  <div class="aio_spacer_15"></div>
admin/wp-security-firewall-menu.php CHANGED
@@ -72,8 +72,12 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
72
  <?php
73
  }
74
 
75
- function render_tab1()
76
- {
 
 
 
 
77
  global $aiowps_feature_mgr;
78
  global $aio_wp_security;
79
  if(isset($_POST['aiowps_apply_basic_firewall_settings']))//Do form submission tasks
@@ -166,8 +170,8 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
166
  <tr valign="top">
167
  <th scope="row"><?php _e('Enable Basic Firewall Protection', 'all-in-one-wp-security-and-firewall')?>:</th>
168
  <td>
169
- <input name="aiowps_enable_basic_firewall" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_basic_firewall')=='1') echo ' checked="checked"'; ?> value="1"/>
170
- <span class="description"><?php _e('Check this if you want to apply basic firewall protection to your site.', 'all-in-one-wp-security-and-firewall'); ?></span>
171
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
172
  <div class="aiowps_more_info_body">
173
  <?php
@@ -183,8 +187,8 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
183
  </td>
184
  </tr>
185
  <tr valign="top">
186
- <th scope="row"><?php _e('Max File Upload Size (MB)', 'all-in-one-wp-security-and-firewall')?>:</th>
187
- <td><input type="number" min="0" step="1" name="aiowps_max_file_upload_size" value="<?php echo esc_html($aio_wp_security->configs->get_value('aiowps_max_file_upload_size')); ?>" />
188
  <span class="description"><?php _e('The value for the maximum file upload size used in the .htaccess file. (Defaults to 10MB if left blank)', 'all-in-one-wp-security-and-firewall'); ?></span>
189
  </td>
190
  </tr>
@@ -203,8 +207,8 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
203
  <tr valign="top">
204
  <th scope="row"><?php _e('Completely Block Access To XMLRPC', 'all-in-one-wp-security-and-firewall')?>:</th>
205
  <td>
206
- <input name="aiowps_enable_pingback_firewall" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_pingback_firewall')=='1') echo ' checked="checked"'; ?> value="1"/>
207
- <span class="description"><?php _e('Check this if you are not using the WP XML-RPC functionality and you want to completely block external access to XMLRPC.', 'all-in-one-wp-security-and-firewall'); ?></span>
208
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
209
  <div class="aiowps_more_info_body">
210
  <?php
@@ -223,8 +227,8 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
223
  <tr valign="top">
224
  <th scope="row"><?php _e('Disable Pingback Functionality From XMLRPC', 'all-in-one-wp-security-and-firewall')?>:</th>
225
  <td>
226
- <input name="aiowps_disable_xmlrpc_pingback_methods" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_disable_xmlrpc_pingback_methods')=='1') echo ' checked="checked"'; ?> value="1"/>
227
- <span class="description"><?php _e('If you use Jetpack or WP iOS or other apps which need WP XML-RPC functionality then check this. This will enable protection against WordPress pingback vulnerabilities.', 'all-in-one-wp-security-and-firewall'); ?></span>
228
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
229
  <div class="aiowps_more_info_body">
230
  <?php
@@ -249,13 +253,13 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
249
  <tr valign="top">
250
  <th scope="row"><?php _e('Block Access to debug.log File', 'all-in-one-wp-security-and-firewall')?>:</th>
251
  <td>
252
- <input name="aiowps_block_debug_log_file_access" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_block_debug_log_file_access')=='1') echo ' checked="checked"'; ?> value="1"/>
253
- <span class="description"><?php _e('Check this if you want to block access to the debug.log file that WordPress creates when debug logging is enabled.', 'all-in-one-wp-security-and-firewall'); ?></span>
254
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
255
  <div class="aiowps_more_info_body">
256
  <?php
257
  echo '<p class="description">'.__('WordPress has an option to turn on the debug logging to a file located in wp-content/debug.log. This file may contain sensitive information.', 'all-in-one-wp-security-and-firewall').'</p>';
258
- echo '<p class="description">'.__('Using this optoin will block external access to this file. You can still access this file by logging into your site via FTP', 'all-in-one-wp-security-and-firewall').'</p>';
259
  ?>
260
  </div>
261
  </td>
@@ -376,8 +380,8 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
376
  <tr valign="top">
377
  <th scope="row"><?php _e('Disable Index Views', 'all-in-one-wp-security-and-firewall')?>:</th>
378
  <td>
379
- <input name="aiowps_disable_index_views" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_disable_index_views')=='1') echo ' checked="checked"'; ?> value="1"/>
380
- <span class="description"><?php _e('Check this if you want to disable directory and file listing.', 'all-in-one-wp-security-and-firewall'); ?></span>
381
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
382
  <div class="aiowps_more_info_body">
383
  <p class="description">
@@ -406,8 +410,8 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
406
  <tr valign="top">
407
  <th scope="row"><?php _e('Disable Trace and Track', 'all-in-one-wp-security-and-firewall')?>:</th>
408
  <td>
409
- <input name="aiowps_disable_trace_and_track" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_disable_trace_and_track')=='1') echo ' checked="checked"'; ?> value="1"/>
410
- <span class="description"><?php _e('Check this if you want to disable trace and track.', 'all-in-one-wp-security-and-firewall'); ?></span>
411
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
412
  <div class="aiowps_more_info_body">
413
  <p class="description">
@@ -437,8 +441,8 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
437
  <tr valign="top">
438
  <th scope="row"><?php _e('Forbid Proxy Comment Posting', 'all-in-one-wp-security-and-firewall')?>:</th>
439
  <td>
440
- <input name="aiowps_forbid_proxy_comments" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_forbid_proxy_comments')=='1') echo ' checked="checked"'; ?> value="1"/>
441
- <span class="description"><?php _e('Check this if you want to forbid proxy comment posting.', 'all-in-one-wp-security-and-firewall'); ?></span>
442
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
443
  <div class="aiowps_more_info_body">
444
  <p class="description">
@@ -465,8 +469,8 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
465
  <tr valign="top">
466
  <th scope="row"><?php _e('Deny Bad Query Strings', 'all-in-one-wp-security-and-firewall')?>:</th>
467
  <td>
468
- <input name="aiowps_deny_bad_query_strings" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_deny_bad_query_strings')=='1') echo ' checked="checked"'; ?> value="1"/>
469
- <span class="description"><?php _e('This will help protect you against malicious queries via XSS.', 'all-in-one-wp-security-and-firewall'); ?></span>
470
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
471
  <div class="aiowps_more_info_body">
472
  <p class="description">
@@ -494,8 +498,8 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
494
  <tr valign="top">
495
  <th scope="row"><?php _e('Enable Advanced Character String Filter', 'all-in-one-wp-security-and-firewall')?>:</th>
496
  <td>
497
- <input name="aiowps_advanced_char_string_filter" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_advanced_char_string_filter')=='1') echo ' checked="checked"'; ?> value="1"/>
498
- <span class="description"><?php _e('This will block bad character matches from XSS.', 'all-in-one-wp-security-and-firewall'); ?></span>
499
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
500
  <div class="aiowps_more_info_body">
501
  <p class="description">
@@ -516,60 +520,133 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
516
  <?php
517
  }
518
 
519
- function render_tab3()
520
- {
521
- global $aio_wp_security, $aiowps_feature_mgr;
522
- if(isset($_POST['aiowps_apply_5g_6g_firewall_settings']))//Do form submission tasks
523
- {
524
- $nonce=$_REQUEST['_wpnonce'];
525
- if (!wp_verify_nonce($nonce, 'aiowpsec-enable-5g-6g-firewall-nonce'))
526
- {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
527
  $aio_wp_security->debug_logger->log_debug("Nonce check failed on enable 5G/6G firewall settings!",4);
528
  die("Nonce check failed on enable 5G/6G firewall settings!");
529
  }
530
 
531
  //Save settings
532
- if(isset($_POST['aiowps_enable_5g_firewall']))
533
- {
534
- $aio_wp_security->configs->set_value('aiowps_enable_5g_firewall','1');
535
- }
536
- else
537
- {
538
- $aio_wp_security->configs->set_value('aiowps_enable_5g_firewall','');
539
- }
540
- if(isset($_POST['aiowps_enable_6g_firewall']))
541
- {
542
- $aio_wp_security->configs->set_value('aiowps_enable_6g_firewall','1');
543
  }
544
- else
545
- {
546
- $aio_wp_security->configs->set_value('aiowps_enable_6g_firewall','');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
547
  }
548
 
549
  //Commit the config settings
550
  $aio_wp_security->configs->save_config();
551
 
552
- //Now let's write the applicable rules to the .htaccess file
553
- $res = AIOWPSecurity_Utility_Htaccess::write_to_htaccess();
554
-
555
- if ($res)
556
- {
557
  $this->show_msg_updated(__('You have successfully saved the 5G/6G Firewall Protection configuration', 'all-in-one-wp-security-and-firewall'));
558
  // Recalculate points after the feature status/options have been altered
559
  $aiowps_feature_mgr->check_feature_status_and_recalculate_points();
560
- }
561
- else
562
- {
563
  $this->show_msg_error(__('Could not write to the .htaccess file. Please check the file permissions.', 'all-in-one-wp-security-and-firewall'));
564
  }
565
  }
566
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
567
  ?>
568
  <h2><?php _e('Firewall Settings', 'all-in-one-wp-security-and-firewall')?></h2>
569
  <div class="aio_blue_box">
570
  <?php
571
- $backup_tab_link = '<a href="admin.php?page='.AIOWPSEC_SETTINGS_MENU_SLUG.'&tab=tab2" target="_blank">backup</a>';
572
- $info_msg = '<p>'.sprintf( __('This feature allows you to activate the %s (or legacy %s) firewall security protection rules designed and produced by %s.', 'all-in-one-wp-security-and-firewall'), '<a href="http://perishablepress.com/6g/" target="_blank">6G</a>', '<a href="http://perishablepress.com/5g-blacklist-2013/" target="_blank">5G</a>', '<a href="http://perishablepress.com/" target="_blank">Perishable Press</a>').'</p>';
573
  $info_msg .= '<p>'.__('The 6G Blacklist is updated and improved version of 5G Blacklist. If you have 5G Blacklist active, you might consider activating 6G Blacklist instead.', 'all-in-one-wp-security-and-firewall').'</p>';
574
  $info_msg .= '<p>'.__('The 6G Blacklist is a simple, flexible blacklist that helps reduce the number of malicious URL requests that hit your website.', 'all-in-one-wp-security-and-firewall').'</p>';
575
  $info_msg .= '<p>'.__('The added advantage of applying the 6G firewall to your site is that it has been tested and confirmed by the people at PerishablePress.com to be an optimal and least disruptive set of .htaccess security rules for general WP sites running on an Apache server or similar.', 'all-in-one-wp-security-and-firewall').'</p>';
@@ -593,8 +670,8 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
593
  <tr valign="top">
594
  <th scope="row"><?php _e('Enable 6G Firewall Protection', 'all-in-one-wp-security-and-firewall')?>:</th>
595
  <td>
596
- <input name="aiowps_enable_6g_firewall" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_6g_firewall')=='1') echo ' checked="checked"'; ?> value="1"/>
597
- <span class="description"><?php _e('Check this if you want to apply the 6G Blacklist firewall protection from perishablepress.com to your site.', 'all-in-one-wp-security-and-firewall'); ?></span>
598
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
599
  <div class="aiowps_more_info_body">
600
  <?php
@@ -611,8 +688,8 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
611
  <tr valign="top">
612
  <th scope="row"><?php _e('Enable legacy 5G Firewall Protection', 'all-in-one-wp-security-and-firewall')?>:</th>
613
  <td>
614
- <input name="aiowps_enable_5g_firewall" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_5g_firewall')=='1') echo ' checked="checked"'; ?> value="1"/>
615
- <span class="description"><?php _e('Check this if you want to apply the 5G Blacklist firewall protection from perishablepress.com to your site.', 'all-in-one-wp-security-and-firewall'); ?></span>
616
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
617
  <div class="aiowps_more_info_body">
618
  <?php
@@ -630,6 +707,70 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
630
  <input type="submit" name="aiowps_apply_5g_6g_firewall_settings" value="<?php _e('Save 5G/6G Firewall Settings', 'all-in-one-wp-security-and-firewall')?>" class="button-primary" />
631
  </form>
632
  </div></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
633
  <?php
634
  }
635
 
@@ -701,8 +842,8 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
701
  <tr valign="top">
702
  <th scope="row"><?php _e('Block Fake Googlebots', 'all-in-one-wp-security-and-firewall')?>:</th>
703
  <td>
704
- <input name="aiowps_block_fake_googlebots" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_block_fake_googlebots')=='1') echo ' checked="checked"'; ?> value="1"/>
705
- <span class="description"><?php _e('Check this if you want to block all fake Googlebots.', 'all-in-one-wp-security-and-firewall'); ?></span>
706
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
707
  <div class="aiowps_more_info_body">
708
  <?php
@@ -720,8 +861,7 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
720
  <?php
721
  }
722
 
723
- function render_tab5()
724
- {
725
  global $aio_wp_security;
726
  global $aiowps_feature_mgr;
727
 
@@ -776,8 +916,8 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
776
  <tr valign="top">
777
  <th scope="row"><?php _e('Prevent Image Hotlinking', 'all-in-one-wp-security-and-firewall')?>:</th>
778
  <td>
779
- <input name="aiowps_prevent_hotlinking" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_prevent_hotlinking')=='1') echo ' checked="checked"'; ?> value="1"/>
780
- <span class="description"><?php _e('Check this if you want to prevent hotlinking to images on your site.', 'all-in-one-wp-security-and-firewall'); ?></span>
781
  </td>
782
  </tr>
783
  </table>
@@ -920,8 +1060,8 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
920
  <tr valign="top">
921
  <th scope="row"><?php _e('Enable 404 IP Detection and Lockout', 'all-in-one-wp-security-and-firewall')?>:</th>
922
  <td>
923
- <input name="aiowps_enable_404_IP_lockout" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_404_IP_lockout')=='1') echo ' checked="checked"'; ?> value="1"/>
924
- <span class="description"><?php _e('Check this if you want to enable the lockout of selected IP addresses.', 'all-in-one-wp-security-and-firewall'); ?></span>
925
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
926
  <div class="aiowps_more_info_body">
927
  <p class="description">
@@ -942,8 +1082,8 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
942
  </tr>
943
  -->
944
  <tr valign="top">
945
- <th scope="row"><?php _e('Time Length of 404 Lockout (min)', 'all-in-one-wp-security-and-firewall')?>:</th>
946
- <td><input type="text" size="5" name="aiowps_404_lockout_time_length" value="<?php echo $aio_wp_security->configs->get_value('aiowps_404_lockout_time_length'); ?>" />
947
  <span class="description"><?php _e('Set the length of time for which a blocked IP address will be prevented from visiting your site', 'all-in-one-wp-security-and-firewall'); ?></span>
948
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
949
  <div class="aiowps_more_info_body">
@@ -958,8 +1098,8 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
958
  </td>
959
  </tr>
960
  <tr valign="top">
961
- <th scope="row"><?php _e('404 Lockout Redirect URL', 'all-in-one-wp-security-and-firewall')?>:</th>
962
- <td><input type="text" size="50" name="aiowps_404_lock_redirect_url" value="<?php echo esc_url_raw( $aio_wp_security->configs->get_value('aiowps_404_lock_redirect_url'), array( 'http', 'https' ) ); ?>" />
963
  <span class="description"><?php _e('A blocked visitor will be automatically redirected to this URL.', 'all-in-one-wp-security-and-firewall'); ?></span>
964
  </td>
965
  </tr>
@@ -1099,21 +1239,21 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
1099
  <tr valign="top">
1100
  <th scope="row"><?php _e('Enable Custom .htaccess Rules', 'all-in-one-wp-security-and-firewall')?>:</th>
1101
  <td>
1102
- <input name="aiowps_enable_custom_rules" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_custom_rules')=='1') echo ' checked="checked"'; ?> value="1"/>
1103
- <span class="description"><?php _e('Check this if you want to enable custom rules entered in the text box below', 'all-in-one-wp-security-and-firewall'); ?></span>
1104
  </td>
1105
  </tr>
1106
  <tr valign="top">
1107
  <th scope="row"><?php _e('Place custom rules at the top', 'all-in-one-wp-security-and-firewall')?>:</th>
1108
  <td>
1109
- <input name="aiowps_place_custom_rules_at_top" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_place_custom_rules_at_top')=='1') echo ' checked="checked"'; ?> value="1"/>
1110
- <span class="description"><?php _e('Check this if you want to place your custom rules at the beginning of all the rules applied by this plugin', 'all-in-one-wp-security-and-firewall'); ?></span>
1111
  </td>
1112
  </tr>
1113
  <tr valign="top">
1114
- <th scope="row"><?php _e('Enter Custom .htaccess Rules:', 'all-in-one-wp-security-and-firewall')?></th>
1115
  <td>
1116
- <textarea name="aiowps_custom_rules" rows="35" cols="50"><?php echo htmlspecialchars($aio_wp_security->configs->get_value('aiowps_custom_rules')); ?></textarea>
1117
  <br />
1118
  <span class="description"><?php _e('Enter your custom .htaccess rules/directives.','all-in-one-wp-security-and-firewall');?></span>
1119
  </td>
@@ -1125,4 +1265,4 @@ class AIOWPSecurity_Firewall_Menu extends AIOWPSecurity_Admin_Menu
1125
  <?php
1126
  }
1127
 
1128
- } //end class
72
  <?php
73
  }
74
 
75
+ /**
76
+ * Renders the submenu's tab1 tab body.
77
+ *
78
+ * @return Void
79
+ */
80
+ public function render_tab1() {
81
  global $aiowps_feature_mgr;
82
  global $aio_wp_security;
83
  if(isset($_POST['aiowps_apply_basic_firewall_settings']))//Do form submission tasks
170
  <tr valign="top">
171
  <th scope="row"><?php _e('Enable Basic Firewall Protection', 'all-in-one-wp-security-and-firewall')?>:</th>
172
  <td>
173
+ <input id="aiowps_enable_basic_firewall" name="aiowps_enable_basic_firewall" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_basic_firewall')=='1') echo ' checked="checked"'; ?> value="1"/>
174
+ <label for="aiowps_enable_basic_firewall" class="description"><?php _e('Check this if you want to apply basic firewall protection to your site.', 'all-in-one-wp-security-and-firewall'); ?></label>
175
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
176
  <div class="aiowps_more_info_body">
177
  <?php
187
  </td>
188
  </tr>
189
  <tr valign="top">
190
+ <th scope="row"><label for="aiowps_max_file_upload_size"><?php _e('Max File Upload Size (MB)', 'all-in-one-wp-security-and-firewall')?>:</label></th>
191
+ <td><input id="aiowps_max_file_upload_size" type="number" min="0" step="1" name="aiowps_max_file_upload_size" value="<?php echo esc_html($aio_wp_security->configs->get_value('aiowps_max_file_upload_size')); ?>" />
192
  <span class="description"><?php _e('The value for the maximum file upload size used in the .htaccess file. (Defaults to 10MB if left blank)', 'all-in-one-wp-security-and-firewall'); ?></span>
193
  </td>
194
  </tr>
207
  <tr valign="top">
208
  <th scope="row"><?php _e('Completely Block Access To XMLRPC', 'all-in-one-wp-security-and-firewall')?>:</th>
209
  <td>
210
+ <input id="aiowps_enable_pingback_firewall" name="aiowps_enable_pingback_firewall" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_pingback_firewall')=='1') echo ' checked="checked"'; ?> value="1"/>
211
+ <label for="aiowps_enable_pingback_firewall" class="description"><?php _e('Check this if you are not using the WP XML-RPC functionality and you want to completely block external access to XMLRPC.', 'all-in-one-wp-security-and-firewall'); ?></label>
212
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
213
  <div class="aiowps_more_info_body">
214
  <?php
227
  <tr valign="top">
228
  <th scope="row"><?php _e('Disable Pingback Functionality From XMLRPC', 'all-in-one-wp-security-and-firewall')?>:</th>
229
  <td>
230
+ <input id="aiowps_disable_xmlrpc_pingback_methods" name="aiowps_disable_xmlrpc_pingback_methods" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_disable_xmlrpc_pingback_methods')=='1') echo ' checked="checked"'; ?> value="1"/>
231
+ <label for="aiowps_disable_xmlrpc_pingback_methods" class="description"><?php _e('If you use Jetpack or WP iOS or other apps which need WP XML-RPC functionality then check this. This will enable protection against WordPress pingback vulnerabilities.', 'all-in-one-wp-security-and-firewall'); ?></label>
232
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
233
  <div class="aiowps_more_info_body">
234
  <?php
253
  <tr valign="top">
254
  <th scope="row"><?php _e('Block Access to debug.log File', 'all-in-one-wp-security-and-firewall')?>:</th>
255
  <td>
256
+ <input id="aiowps_block_debug_log_file_access" name="aiowps_block_debug_log_file_access" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_block_debug_log_file_access')=='1') echo ' checked="checked"'; ?> value="1"/>
257
+ <label for="aiowps_block_debug_log_file_access" class="description"><?php _e('Check this if you want to block access to the debug.log file that WordPress creates when debug logging is enabled.', 'all-in-one-wp-security-and-firewall'); ?></label>
258
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
259
  <div class="aiowps_more_info_body">
260
  <?php
261
  echo '<p class="description">'.__('WordPress has an option to turn on the debug logging to a file located in wp-content/debug.log. This file may contain sensitive information.', 'all-in-one-wp-security-and-firewall').'</p>';
262
+ echo '<p class="description">'.__('Using this option will block external access to this file.', 'all-in-one-wp-security-and-firewall').' '.__('You can still access this file by logging into your site via FTP.', 'all-in-one-wp-security-and-firewall').'</p>';
263
  ?>
264
  </div>
265
  </td>
380
  <tr valign="top">
381
  <th scope="row"><?php _e('Disable Index Views', 'all-in-one-wp-security-and-firewall')?>:</th>
382
  <td>
383
+ <input id="aiowps_disable_index_views" name="aiowps_disable_index_views" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_disable_index_views')=='1') echo ' checked="checked"'; ?> value="1"/>
384
+ <label for="aiowps_disable_index_views" class="description"><?php _e('Check this if you want to disable directory and file listing.', 'all-in-one-wp-security-and-firewall'); ?></label>
385
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
386
  <div class="aiowps_more_info_body">
387
  <p class="description">
410
  <tr valign="top">
411
  <th scope="row"><?php _e('Disable Trace and Track', 'all-in-one-wp-security-and-firewall')?>:</th>
412
  <td>
413
+ <input id="aiowps_disable_trace_and_track" name="aiowps_disable_trace_and_track" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_disable_trace_and_track')=='1') echo ' checked="checked"'; ?> value="1"/>
414
+ <label for="aiowps_disable_trace_and_track" class="description"><?php _e('Check this if you want to disable trace and track.', 'all-in-one-wp-security-and-firewall'); ?></label>
415
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
416
  <div class="aiowps_more_info_body">
417
  <p class="description">
441
  <tr valign="top">
442
  <th scope="row"><?php _e('Forbid Proxy Comment Posting', 'all-in-one-wp-security-and-firewall')?>:</th>
443
  <td>
444
+ <input id="aiowps_forbid_proxy_comments" name="aiowps_forbid_proxy_comments" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_forbid_proxy_comments')=='1') echo ' checked="checked"'; ?> value="1"/>
445
+ <label for="aiowps_forbid_proxy_comments" class="description"><?php _e('Check this if you want to forbid proxy comment posting.', 'all-in-one-wp-security-and-firewall'); ?></label>
446
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
447
  <div class="aiowps_more_info_body">
448
  <p class="description">
469
  <tr valign="top">
470
  <th scope="row"><?php _e('Deny Bad Query Strings', 'all-in-one-wp-security-and-firewall')?>:</th>
471
  <td>
472
+ <input id="aiowps_deny_bad_query_strings" name="aiowps_deny_bad_query_strings" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_deny_bad_query_strings')=='1') echo ' checked="checked"'; ?> value="1"/>
473
+ <label for="aiowps_deny_bad_query_strings" class="description"><?php _e('This will help protect you against malicious queries via XSS.', 'all-in-one-wp-security-and-firewall'); ?></label>
474
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
475
  <div class="aiowps_more_info_body">
476
  <p class="description">
498
  <tr valign="top">
499
  <th scope="row"><?php _e('Enable Advanced Character String Filter', 'all-in-one-wp-security-and-firewall')?>:</th>
500
  <td>
501
+ <input id="aiowps_advanced_char_string_filter" name="aiowps_advanced_char_string_filter" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_advanced_char_string_filter')=='1') echo ' checked="checked"'; ?> value="1"/>
502
+ <label for="aiowps_advanced_char_string_filter" class="description"><?php _e('This will block bad character matches from XSS.', 'all-in-one-wp-security-and-firewall'); ?></label>
503
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
504
  <div class="aiowps_more_info_body">
505
  <p class="description">
520
  <?php
521
  }
522
 
523
+ /**
524
+ * Renders the 6G Blacklist Firewall Rules tab
525
+ *
526
+ * @return void
527
+ */
528
+ private function render_tab3() {
529
+ global $aio_wp_security, $aiowps_feature_mgr, $aiowps_firewall_config;
530
+
531
+ $block_request_methods = array_map('strtolower', AIOS_Abstracted_Ids::get_firewall_block_request_methods());
532
+
533
+ //Other 6G settings form submission
534
+ if (isset($_POST['aiowps_apply_6g_other_settings'])) {
535
+
536
+ if (!wp_verify_nonce($_POST['_wpnonce'], 'aiowpsec-other-6g-settings-nonce')) {
537
+ $aio_wp_security->debug_logger->log_debug('Nonce check failed for other 6G settings.');
538
+ die("Nonce check failed");
539
+ }
540
+
541
+ $aiowps_firewall_config->set_value('aiowps_6g_block_query', (bool) isset($_POST['aiowps_block_query']));
542
+ $aiowps_firewall_config->set_value('aiowps_6g_block_request', (bool) isset($_POST['aiowps_block_request']));
543
+ $aiowps_firewall_config->set_value('aiowps_6g_block_referrers', (bool) isset($_POST['aiowps_block_refs']));
544
+ $aiowps_firewall_config->set_value('aiowps_6g_block_agents', (bool) isset($_POST['aiowps_block_agents']));
545
+ }
546
+
547
+ //Block request methods form
548
+ if (isset($_POST['aiowps_apply_6g_block_request_methods_settings'])) {
549
+
550
+ if (!wp_verify_nonce($_POST['_wpnonce'], 'aiowpsec-6g-block-request-methods-nonce')) {
551
+ $aio_wp_security->debug_logger->log_debug('Nonce check failed for blocking HTTP request methods');
552
+ die("Nonce check failed");
553
+ }
554
+
555
+ $methods = array();
556
+
557
+ foreach ($block_request_methods as $block_request_method) {
558
+ if (isset($_POST['aiowps_block_request_method_'.$block_request_method])) {
559
+ $methods[] = strtoupper($block_request_method);
560
+ }
561
+ }
562
+
563
+ $aiowps_firewall_config->set_value('aiowps_6g_block_request_methods', $methods);
564
+ }
565
+
566
+ //Save 6G/5G
567
+ if (isset($_POST['aiowps_apply_5g_6g_firewall_settings'])) {
568
+ if (!wp_verify_nonce($_POST['_wpnonce'], 'aiowpsec-enable-5g-6g-firewall-nonce')) {
569
  $aio_wp_security->debug_logger->log_debug("Nonce check failed on enable 5G/6G firewall settings!",4);
570
  die("Nonce check failed on enable 5G/6G firewall settings!");
571
  }
572
 
573
  //Save settings
574
+ if (isset($_POST['aiowps_enable_5g_firewall'])) {
575
+ $aio_wp_security->configs->set_value('aiowps_enable_5g_firewall', '1');
576
+ $res = AIOWPSecurity_Utility_Htaccess::write_to_htaccess(); //Now let's write the applicable rules to the .htaccess file
577
+ } else {
578
+ $aio_wp_security->configs->set_value('aiowps_enable_5g_firewall', '');
 
 
 
 
 
 
579
  }
580
+
581
+ if (isset($_POST['aiowps_enable_6g_firewall'])) {
582
+ $aiowps_firewall_config->set_value('aiowps_6g_block_request_methods', AIOS_Abstracted_Ids::get_firewall_block_request_methods());
583
+ $aiowps_firewall_config->set_value('aiowps_6g_block_query', true);
584
+ $aiowps_firewall_config->set_value('aiowps_6g_block_request', true);
585
+ $aiowps_firewall_config->set_value('aiowps_6g_block_referrers', true);
586
+ $aiowps_firewall_config->set_value('aiowps_6g_block_agents', true);
587
+ $aio_wp_security->configs->set_value('aiowps_enable_6g_firewall', '1');
588
+ $res = true; //shows the success notice
589
+ } else {
590
+ $aiowps_firewall_config->set_value('aiowps_6g_block_request_methods', array());
591
+ $aiowps_firewall_config->set_value('aiowps_6g_block_query', false);
592
+ $aiowps_firewall_config->set_value('aiowps_6g_block_request', false);
593
+ $aiowps_firewall_config->set_value('aiowps_6g_block_referrers', false);
594
+ $aiowps_firewall_config->set_value('aiowps_6g_block_agents', false);
595
+ $aio_wp_security->configs->set_value('aiowps_enable_6g_firewall', '');
596
+ $res = true;
597
  }
598
 
599
  //Commit the config settings
600
  $aio_wp_security->configs->save_config();
601
 
602
+ if ($res) {
 
 
 
 
603
  $this->show_msg_updated(__('You have successfully saved the 5G/6G Firewall Protection configuration', 'all-in-one-wp-security-and-firewall'));
604
  // Recalculate points after the feature status/options have been altered
605
  $aiowps_feature_mgr->check_feature_status_and_recalculate_points();
606
+ } else {
 
 
607
  $this->show_msg_error(__('Could not write to the .htaccess file. Please check the file permissions.', 'all-in-one-wp-security-and-firewall'));
608
  }
609
  }
610
 
611
+ //Load required data from config
612
+ if (!empty($aiowps_firewall_config)) {
613
+ // firewall config is available
614
+ $methods = $aiowps_firewall_config->get_value('aiowps_6g_block_request_methods');
615
+ if (empty($methods)) {
616
+ $methods = array();
617
+ }
618
+
619
+ $blocked_query = (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_query');
620
+ $blocked_request = (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_request');
621
+ $blocked_referrers = (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_referrers');
622
+ $blocked_agents = (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_agents');
623
+ } else {
624
+ // firewall config is unavailable
625
+ ?>
626
+ <div class="notice notice-error">
627
+ <p><strong><?php _e('All in One WP Security and Firewall', 'all-in-one-wp-security-and-firewall'); ?></strong></p>
628
+ <p><?php _e('We were unable to access the firewall\'s configuration file:', 'all-in-one-wp-security-and-firewall');?></p>
629
+ <pre style='max-width: 100%;background-color: #f0f0f0;border:#ccc solid 1px;padding: 10px;white-space:pre-wrap;'><?php echo esc_html(AIOWPSecurity_Utility_Firewall::get_firewall_rules_path() . 'settings');?></pre>
630
+ <p><?php _e('As a result, the firewall will be unavailable.', 'all-in-one-wp-security-and-firewall');?></p>
631
+ <p><?php _e('Please check your PHP error log for further information.', 'all-in-one-wp-security-and-firewall');?></p>
632
+ <p><?php _e('If you\'re unable to locate your PHP log file, please contact your web hosting company to ask them where it can be found on their setup.', 'all-in-one-wp-security-and-firewall');?></p>
633
+ </div>
634
+ <?php
635
+
636
+ //set default variables
637
+ $methods = array();
638
+ $blocked_query = false;
639
+ $blocked_request = false;
640
+ $blocked_referrers = false;
641
+ $blocked_agents = false;
642
+ }
643
+
644
  ?>
645
  <h2><?php _e('Firewall Settings', 'all-in-one-wp-security-and-firewall')?></h2>
646
  <div class="aio_blue_box">
647
  <?php
648
+ $backup_tab_link = '<a href="admin.php?page='.AIOWPSEC_SETTINGS_MENU_SLUG.'&tab=tab2" target="_blank">'.__('backup', 'all-in-one-wp-security-and-firewall').'</a>';
649
+ $info_msg = '<p>'.sprintf(__('This feature allows you to activate the %s (or legacy %s) firewall security protection rules designed and produced by %s.', 'all-in-one-wp-security-and-firewall'), '<a href="http://perishablepress.com/6g/" target="_blank">6G</a>', '<a href="http://perishablepress.com/5g-blacklist-2013/" target="_blank">5G</a>', '<a href="http://perishablepress.com/" target="_blank">Perishable Press</a>').'</p>';
650
  $info_msg .= '<p>'.__('The 6G Blacklist is updated and improved version of 5G Blacklist. If you have 5G Blacklist active, you might consider activating 6G Blacklist instead.', 'all-in-one-wp-security-and-firewall').'</p>';
651
  $info_msg .= '<p>'.__('The 6G Blacklist is a simple, flexible blacklist that helps reduce the number of malicious URL requests that hit your website.', 'all-in-one-wp-security-and-firewall').'</p>';
652
  $info_msg .= '<p>'.__('The added advantage of applying the 6G firewall to your site is that it has been tested and confirmed by the people at PerishablePress.com to be an optimal and least disruptive set of .htaccess security rules for general WP sites running on an Apache server or similar.', 'all-in-one-wp-security-and-firewall').'</p>';
670
  <tr valign="top">
671
  <th scope="row"><?php _e('Enable 6G Firewall Protection', 'all-in-one-wp-security-and-firewall')?>:</th>
672
  <td>
673
+ <input id="aiowps_enable_6g_firewall" name="aiowps_enable_6g_firewall" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_6g_firewall')=='1') echo ' checked="checked"'; ?> value="1"/>
674
+ <label for="aiowps_enable_6g_firewall" class="description"><?php _e('Check this if you want to apply the 6G Blacklist firewall protection from perishablepress.com to your site.', 'all-in-one-wp-security-and-firewall'); ?></label>
675
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
676
  <div class="aiowps_more_info_body">
677
  <?php
688
  <tr valign="top">
689
  <th scope="row"><?php _e('Enable legacy 5G Firewall Protection', 'all-in-one-wp-security-and-firewall')?>:</th>
690
  <td>
691
+ <input id="aiowps_enable_5g_firewall" name="aiowps_enable_5g_firewall" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_5g_firewall')=='1') echo ' checked="checked"'; ?> value="1"/>
692
+ <label for="aiowps_enable_5g_firewall" class="description"><?php _e('Check this if you want to apply the 5G Blacklist firewall protection from perishablepress.com to your site.', 'all-in-one-wp-security-and-firewall'); ?></label>
693
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
694
  <div class="aiowps_more_info_body">
695
  <?php
707
  <input type="submit" name="aiowps_apply_5g_6g_firewall_settings" value="<?php _e('Save 5G/6G Firewall Settings', 'all-in-one-wp-security-and-firewall')?>" class="button-primary" />
708
  </form>
709
  </div></div>
710
+
711
+ <?php /** Block 6G request methods form */?>
712
+ <form action="" method="POST">
713
+ <?php wp_nonce_field('aiowpsec-6g-block-request-methods-nonce'); ?>
714
+ <div class="postbox">
715
+ <h3 class="hndle"><label for="title"><?php _e('6G block request methods', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
716
+ <div class="inside">
717
+ <table class="form-table">
718
+ <?php foreach ($block_request_methods as $block_request_method) {?>
719
+ <tr>
720
+ <th><?php printf(__('Block %s method', 'all-in-one-wp-security-and-firewall'), strtoupper($block_request_method));?>:</th>
721
+ <td>
722
+ <input id="<?php echo esc_attr("aiowps_block_request_method_{$block_request_method}");?>" name="<?php echo esc_attr("aiowps_block_request_method_{$block_request_method}");?>" type="checkbox"<?php checked(in_array(strtoupper($block_request_method), $methods));?>>
723
+ <label for="<?php echo esc_attr("aiowps_block_request_method_{$block_request_method}");?>" class="description"><?php printf(__('Check this to block the %s request method', 'all-in-one-wp-security-and-firewall'), strtoupper($block_request_method));?></label>
724
+ </td>
725
+ </tr>
726
+ <?php } ?>
727
+ </table>
728
+ <input type="submit" name="aiowps_apply_6g_block_request_methods_settings" value="<?php esc_attr_e('Save request methods settings', 'all-in-one-wp-security-and-firewall');?>" class="button-primary"<?php disabled(empty($aiowps_firewall_config)); ?>/>
729
+ </div></div>
730
+ </form>
731
+
732
+ <?php /** Other 6G settings form */?>
733
+ <form action="" method="POST">
734
+ <?php wp_nonce_field('aiowpsec-other-6g-settings-nonce'); ?>
735
+ <div class="postbox">
736
+ <h3 class="hndle"><label for="title"><?php _e('6G other settings', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
737
+ <div class="inside">
738
+ <table class="form-table">
739
+ <tr>
740
+ <th><?php _e('Block query strings', 'all-in-one-wp-security-and-firewall');?>:</th>
741
+ <td>
742
+ <input id="aiowps_block_query" name="aiowps_block_query" type="checkbox"<?php checked($blocked_query);?>>
743
+ <label for="aiowps_block_query" class="description"><?php _e('Check this to block all query strings recommended by 6G', 'all-in-one-wp-security-and-firewall');?></label>
744
+ </td>
745
+ </tr>
746
+ <tr>
747
+ <th><?php _e('Block request strings', 'all-in-one-wp-security-and-firewall');?>:</th>
748
+ <td>
749
+ <input id="aiowps_block_request" name="aiowps_block_request" type="checkbox"<?php checked($blocked_request);?>>
750
+ <label for="aiowps_block_request" class="description"><?php _e('Check this to block all request strings recommended by 6G', 'all-in-one-wp-security-and-firewall');?></label>
751
+ </td>
752
+ </tr>
753
+ <tr>
754
+ <th><?php _e('Block referrers', 'all-in-one-wp-security-and-firewall');?>:</th>
755
+ <td>
756
+ <input id="aiowps_block_refs" name="aiowps_block_refs" type="checkbox"<?php checked($blocked_referrers);?>>
757
+ <label for="aiowps_block_refs" class="description"><?php _e('Check this to block all referrers recommended by 6G', 'all-in-one-wp-security-and-firewall');?></label>
758
+ </td>
759
+ </tr>
760
+ <tr>
761
+ <th><?php _e('Block user-agents', 'all-in-one-wp-security-and-firewall');?>:</th>
762
+ <td>
763
+ <input id="aiowps_block_agents" name="aiowps_block_agents" type="checkbox"<?php checked($blocked_agents);?>>
764
+ <label for="aiowps_block_agents" class="description"><?php _e('Check this to block all user-agents recommended by 6G', 'all-in-one-wp-security-and-firewall');?></label>
765
+ </td>
766
+ </tr>
767
+ </table>
768
+ <input type="submit" name="aiowps_apply_6g_other_settings"<?php disabled(empty($aiowps_firewall_config));?> value="<?php _e('Save other settings', 'all-in-one-wp-security-and-firewall')?>" class="button-primary" />
769
+ </div>
770
+ </div>
771
+ </form>
772
+
773
+
774
  <?php
775
  }
776
 
842
  <tr valign="top">
843
  <th scope="row"><?php _e('Block Fake Googlebots', 'all-in-one-wp-security-and-firewall')?>:</th>
844
  <td>
845
+ <input id="aiowps_block_fake_googlebots" name="aiowps_block_fake_googlebots" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_block_fake_googlebots')=='1') echo ' checked="checked"'; ?> value="1"/>
846
+ <label for="aiowps_block_fake_googlebots" class="description"><?php _e('Check this if you want to block all fake Googlebots.', 'all-in-one-wp-security-and-firewall'); ?></label>
847
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
848
  <div class="aiowps_more_info_body">
849
  <?php
861
  <?php
862
  }
863
 
864
+ public function render_tab5() {
 
865
  global $aio_wp_security;
866
  global $aiowps_feature_mgr;
867
 
916
  <tr valign="top">
917
  <th scope="row"><?php _e('Prevent Image Hotlinking', 'all-in-one-wp-security-and-firewall')?>:</th>
918
  <td>
919
+ <input id="aiowps_prevent_hotlinking" name="aiowps_prevent_hotlinking" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_prevent_hotlinking')=='1') echo ' checked="checked"'; ?> value="1"/>
920
+ <label for="aiowps_prevent_hotlinking" class="description"><?php _e('Check this if you want to prevent hotlinking to images on your site.', 'all-in-one-wp-security-and-firewall'); ?></label>
921
  </td>
922
  </tr>
923
  </table>
1060
  <tr valign="top">
1061
  <th scope="row"><?php _e('Enable 404 IP Detection and Lockout', 'all-in-one-wp-security-and-firewall')?>:</th>
1062
  <td>
1063
+ <input id="aiowps_enable_404_IP_lockout" name="aiowps_enable_404_IP_lockout" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_404_IP_lockout')=='1') echo ' checked="checked"'; ?> value="1"/>
1064
+ <label for="aiowps_enable_404_IP_lockout" class="description"><?php _e('Check this if you want to enable the lockout of selected IP addresses.', 'all-in-one-wp-security-and-firewall'); ?></label>
1065
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
1066
  <div class="aiowps_more_info_body">
1067
  <p class="description">
1082
  </tr>
1083
  -->
1084
  <tr valign="top">
1085
+ <th scope="row"><label for="aiowps_404_lockout_time_length"><?php _e('Time Length of 404 Lockout (min)', 'all-in-one-wp-security-and-firewall')?>:</label></th>
1086
+ <td><input id="aiowps_404_lockout_time_length" type="text" size="5" name="aiowps_404_lockout_time_length" value="<?php echo $aio_wp_security->configs->get_value('aiowps_404_lockout_time_length'); ?>" />
1087
  <span class="description"><?php _e('Set the length of time for which a blocked IP address will be prevented from visiting your site', 'all-in-one-wp-security-and-firewall'); ?></span>
1088
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
1089
  <div class="aiowps_more_info_body">
1098
  </td>
1099
  </tr>
1100
  <tr valign="top">
1101
+ <th scope="row"><label for="aiowps_404_lock_redirect_url"><?php _e('404 Lockout Redirect URL', 'all-in-one-wp-security-and-firewall')?>:</label></th>
1102
+ <td><input id="aiowps_404_lock_redirect_url" type="text" size="50" name="aiowps_404_lock_redirect_url" value="<?php echo esc_url_raw( $aio_wp_security->configs->get_value('aiowps_404_lock_redirect_url'), array( 'http', 'https' ) ); ?>" />
1103
  <span class="description"><?php _e('A blocked visitor will be automatically redirected to this URL.', 'all-in-one-wp-security-and-firewall'); ?></span>
1104
  </td>
1105
  </tr>
1239
  <tr valign="top">
1240
  <th scope="row"><?php _e('Enable Custom .htaccess Rules', 'all-in-one-wp-security-and-firewall')?>:</th>
1241
  <td>
1242
+ <input id="aiowps_enable_custom_rules" name="aiowps_enable_custom_rules" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_custom_rules')=='1') echo ' checked="checked"'; ?> value="1"/>
1243
+ <label for="aiowps_enable_custom_rules" class="description"><?php _e('Check this if you want to enable custom rules entered in the text box below', 'all-in-one-wp-security-and-firewall'); ?></label>
1244
  </td>
1245
  </tr>
1246
  <tr valign="top">
1247
  <th scope="row"><?php _e('Place custom rules at the top', 'all-in-one-wp-security-and-firewall')?>:</th>
1248
  <td>
1249
+ <input id="aiowps_place_custom_rules_at_top" name="aiowps_place_custom_rules_at_top" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_place_custom_rules_at_top')=='1') echo ' checked="checked"'; ?> value="1"/>
1250
+ <label for="aiowps_place_custom_rules_at_top" class="description"><?php _e('Check this if you want to place your custom rules at the beginning of all the rules applied by this plugin', 'all-in-one-wp-security-and-firewall'); ?></label>
1251
  </td>
1252
  </tr>
1253
  <tr valign="top">
1254
+ <th scope="row"><label for="aiowps_custom_rules"><?php _e('Enter Custom .htaccess Rules:', 'all-in-one-wp-security-and-firewall')?></label></th>
1255
  <td>
1256
+ <textarea id="aiowps_custom_rules" name="aiowps_custom_rules" rows="35" cols="50"><?php echo htmlspecialchars($aio_wp_security->configs->get_value('aiowps_custom_rules')); ?></textarea>
1257
  <br />
1258
  <span class="description"><?php _e('Enter your custom .htaccess rules/directives.','all-in-one-wp-security-and-firewall');?></span>
1259
  </td>
1265
  <?php
1266
  }
1267
 
1268
+ } //end class
admin/wp-security-firewall-setup-notice.php ADDED
@@ -0,0 +1,590 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if (!defined('ABSPATH')) {
3
+ exit; //Exit if accessed directly
4
+ }
5
+
6
+ class AIOWPSecurity_Firewall_Setup_Notice {
7
+
8
+ /**
9
+ * Holds reference to an instance of itself
10
+ *
11
+ * @var AIOWPSecurity_Firewall_Setup_Notice
12
+ */
13
+ private static $instance = null;
14
+
15
+ /**
16
+ * Holds our wp-config file wrapped in our manager class
17
+ *
18
+ * @var AIOWPSecurity_Block_WpConfig
19
+ */
20
+ private $wpconfig;
21
+
22
+ /**
23
+ * Holds our mu-plugin file wrapped in our manager class
24
+ *
25
+ * @var AIOWPSecurity_Block_Muplugin
26
+ */
27
+ private $muplugin;
28
+
29
+ /**
30
+ * Holds our bootstrap file wrapped in our manager class
31
+ *
32
+ * @var AIOWPSecurity_Block_Bootstrap
33
+ */
34
+ private $bootstrap;
35
+
36
+ /**
37
+ * Constants for the different notice types
38
+ *
39
+ * @var string
40
+ */
41
+ const NOTICE_BOOTSTRAP = 'manual_bootstrap';
42
+ const NOTICE_MANUAL = 'manual';
43
+ const NOTICE_INSTALLED = 'success';
44
+ const NOTICE_DIRECTIVE_SET = 'userini_directive';
45
+
46
+ /**
47
+ * Constructs our object by setting up our essential files
48
+ */
49
+ private function __construct() {
50
+ $this->bootstrap = AIOWPSecurity_Utility_Firewall::get_bootstrap_file();
51
+ $this->wpconfig = AIOWPSecurity_Utility_Firewall::get_wpconfig_file();
52
+ $this->muplugin = AIOWPSecurity_Utility_Firewall::get_muplugin_file();
53
+ AIOWPSecurity_Utility_Firewall::get_firewall_rules_path(); //creates the needed directories for the first time
54
+ }
55
+
56
+ /**
57
+ * Entry point for the dashboard notice
58
+ *
59
+ * @return void
60
+ */
61
+ public function start_firewall_setup() {
62
+
63
+ global $aio_wp_security;
64
+
65
+ $firewall_files = array(
66
+ 'server' => AIOWPSecurity_Utility_Firewall::get_server_file(),
67
+ 'bootstrap' => $this->bootstrap,
68
+ 'wpconfig' => $this->wpconfig,
69
+ 'muplugin' => $this->muplugin,
70
+ );
71
+
72
+ //Check each file and update the contents if necessary
73
+ foreach ($firewall_files as $name => $file) {
74
+ ${'is_firewall_in_'.$name} = false;
75
+
76
+ if (AIOWPSecurity_Utility_Firewall::MANUAL_SETUP === $file) {
77
+ continue;
78
+ }
79
+
80
+ ${'is_firewall_in_'.$name} = $file->contains_contents();
81
+
82
+ if (true === ${'is_firewall_in_'.$name}) {
83
+ $file->update_contents();
84
+ }
85
+ }
86
+
87
+ if (!$aio_wp_security->is_aiowps_admin_page()) {
88
+ return;
89
+ }
90
+
91
+ if (true !== $is_firewall_in_server || true !== $is_firewall_in_bootstrap) {
92
+
93
+ if (true !== $is_firewall_in_wpconfig) {
94
+ $this->render_automatic_setup_notice(); //Show notice to setup firewall, if the firewall is not present in our required files
95
+ }elseif (true === $is_firewall_in_wpconfig) {
96
+ $this->render_upgrade_protection_notice();
97
+ }
98
+ }
99
+
100
+ $this->render_notices();
101
+ }
102
+
103
+ /**
104
+ * Will execute when the user presses 'Set up now' button
105
+ *
106
+ * @return void
107
+ */
108
+ private function do_setup() {
109
+
110
+ $is_inserted_firewall_file = false;
111
+
112
+ $is_inserted_bootstrap_file = $this->bootstrap->contains_contents();
113
+
114
+ if (true !== $is_inserted_bootstrap_file) {
115
+
116
+ $is_inserted_bootstrap_file = $this->bootstrap->insert_contents();
117
+
118
+ if (true !== $is_inserted_bootstrap_file) {
119
+ $this->log_wp_error($is_inserted_bootstrap_file);
120
+ $this->show_notice(self::NOTICE_BOOTSTRAP);
121
+ return;
122
+ }
123
+
124
+ }
125
+
126
+ $firewall_file = AIOWPSecurity_Utility_Firewall::get_server_file();
127
+
128
+ if ($firewall_file instanceof AIOWPSecurity_Block_Userini) {
129
+
130
+ $directive = AIOWPSecurity_Utility_Firewall::get_already_set_directive($firewall_file);
131
+
132
+ if (!empty($directive)) {
133
+
134
+ if (AIOWPSecurity_Utility_Firewall::get_bootstrap_path() === $directive) {
135
+ $is_inserted_firewall_file = true;
136
+ } else {
137
+ $this->show_notice(self::NOTICE_DIRECTIVE_SET, array('directive'=>$directive));
138
+ }
139
+
140
+ } else {
141
+ $is_inserted_firewall_file = $firewall_file->insert_contents();
142
+ }
143
+
144
+ } else {
145
+
146
+ if (AIOWPSecurity_Utility_Firewall::MANUAL_SETUP !== $firewall_file) {
147
+ $is_inserted_firewall_file = $firewall_file->insert_contents(); // attempts to insert firewall into required file
148
+ }
149
+ }
150
+
151
+ $is_inserted_wpconfig = $this->wpconfig->contains_contents();
152
+ if (true !== $is_inserted_wpconfig) {
153
+ $is_inserted_wpconfig = $this->wpconfig->insert_contents();
154
+ }
155
+
156
+ if (true === $is_inserted_firewall_file) {
157
+ $this->show_notice(self::NOTICE_INSTALLED);
158
+ }
159
+
160
+ if (true !== $is_inserted_firewall_file) {
161
+
162
+ if (true !== $is_inserted_wpconfig) {
163
+ $is_inserted_muplugin = $this->muplugin->insert_contents();
164
+ $this->log_wp_error($is_inserted_muplugin);
165
+ $this->log_wp_error($is_inserted_wpconfig);
166
+ }
167
+
168
+ $this->log_wp_error($is_inserted_firewall_file);
169
+ $this->show_notice(self::NOTICE_MANUAL);
170
+
171
+ }
172
+
173
+ }
174
+
175
+ /**
176
+ * Handles the form submission for the 'Set up now' notice
177
+ *
178
+ * @return void
179
+ */
180
+ public function handle_setup_form() {
181
+ if (isset($_POST['_wpnonce']) && wp_verify_nonce($_POST['_wpnonce'], 'aiowpsec-firewall-setup')) {
182
+ $this->do_setup();
183
+ AIOWPSecurity_Utility::redirect_to_url(admin_url('admin.php?page=aiowpsec'));
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Wrapper function to log WP_Errors to debug log
189
+ *
190
+ * @param WP_Error $wp_error - Our error which gets logged
191
+ * @return void
192
+ */
193
+ private function log_wp_error($wp_error) {
194
+
195
+ if (is_wp_error($wp_error)) {
196
+ global $aio_wp_security;
197
+
198
+ $error_message = $wp_error->get_error_message();
199
+ $error_message .= ' - ';
200
+ $error_message .= $wp_error->get_error_data();
201
+ $aio_wp_security->debug_logger->log_debug($error_message, 4);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Sets the flags to show notices
207
+ *
208
+ * @param string $type - the type of notice we want to set
209
+ * @param array $values - any values that need to be passed
210
+ * @return void
211
+ */
212
+ private function show_notice($type, $values = array()) {
213
+ global $aio_wp_security;
214
+
215
+ $aio_wp_security->configs->set_value('firewall_notice_'.$type, true);
216
+
217
+ if (!empty($values)) {
218
+ $aio_wp_security->configs->set_value('firewall_notice_values', $values);
219
+ }
220
+
221
+ $aio_wp_security->configs->save_config();
222
+ }
223
+
224
+ /**
225
+ * Renders any necessary notices
226
+ *
227
+ * @return void
228
+ */
229
+ private function render_notices() {
230
+ global $aio_wp_security;
231
+
232
+ $notices = array(
233
+ self::NOTICE_BOOTSTRAP,
234
+ self::NOTICE_MANUAL,
235
+ self::NOTICE_INSTALLED,
236
+ self::NOTICE_DIRECTIVE_SET,
237
+ );
238
+
239
+ foreach($notices as $notice) {
240
+ if ($aio_wp_security->configs->get_value('firewall_notice_'.$notice)) {
241
+
242
+ switch($notice) {
243
+ case self::NOTICE_BOOTSTRAP:
244
+ $this->render_bootstrap_notice(); break;
245
+
246
+ case self::NOTICE_MANUAL:
247
+ if (!$this->any_pending_notices(self::NOTICE_MANUAL)) {
248
+ $this->render_manual_setup_notice();
249
+ }
250
+ break;
251
+ case self::NOTICE_INSTALLED:
252
+ $this->render_firewall_installed_notice(); break;
253
+
254
+ case self::NOTICE_DIRECTIVE_SET:
255
+ $values = $aio_wp_security->configs->get_value('firewall_notice_values');
256
+ $this->render_userini_directive_set_notice($values['directive']);
257
+ $aio_wp_security->configs->delete_value('firewall_notice_values');
258
+ break;
259
+ }
260
+
261
+ $aio_wp_security->configs->delete_value('firewall_notice_'.$notice);
262
+ }
263
+ }
264
+
265
+ $aio_wp_security->configs->save_config();
266
+ }
267
+
268
+ /**
269
+ * Detects if we have any notices pending to display
270
+ *
271
+ * @param string $exclude - do not check the status of these notices
272
+ * @return boolean
273
+ */
274
+ private function any_pending_notices(...$exclude) {
275
+ global $aio_wp_security;
276
+
277
+ $notices = array(
278
+ self::NOTICE_BOOTSTRAP,
279
+ self::NOTICE_MANUAL,
280
+ self::NOTICE_INSTALLED,
281
+ self::NOTICE_DIRECTIVE_SET,
282
+ );
283
+ $notices = array_diff($notices, $exclude);
284
+
285
+ foreach($notices as $notice) {
286
+ if (true === $aio_wp_security->configs->get_value('firewall_notice_'.$notice)) {
287
+ return true;
288
+ }
289
+ }
290
+
291
+ return false;
292
+ }
293
+
294
+ /**
295
+ * Notice is shown if we are unable to write to the bootstrap file
296
+ *
297
+ * @return void
298
+ */
299
+ private function render_bootstrap_notice() {
300
+ ?>
301
+ <div class="notice notice-error is-dismissible">
302
+ <p>
303
+ <strong><?php _e('All In One WP Security and Firewall', 'all-in-one-wp-security-and-firewall'); ?></strong>
304
+ </p>
305
+ <p><?php _e('We were unable to create the file necessary to give you the highest level of protection.', 'all-in-one-wp-security-and-firewall');?></p>
306
+ <p><?php _e('Your firewall will have reduced protection which means some of your firewall\'s functionality will be unavailable.', 'all-in-one-wp-security-and-firewall');?></p>
307
+ <p><?php _e('If you would like to manually set up the necessary file, please follow these steps:', 'all-in-one-wp-security-and-firewall');?></p>
308
+ <p>
309
+ <?php
310
+ /* translators: %s Boostrap file name. */
311
+ printf(__('1. Create a file with the name %s in the same directory as your WordPress install is in, i.e.:', 'all-in-one-wp-security-and-firewall'), pathinfo($this->bootstrap, PATHINFO_BASENAME));
312
+ ?>
313
+ </p>
314
+ <pre style='max-width: 100%;background-color: #f0f0f0;border:#ccc solid 1px;padding: 10px;white-space:pre-wrap;'><?php echo esc_html($this->bootstrap); ?></pre>
315
+ <p><?php _e('2. Paste in the following code:', 'all-in-one-wp-security-and-firewall');?></p>
316
+ <pre style='max-width: 100%;background-color: #f0f0f0;border:#ccc solid 1px;padding: 10px;white-space:pre-wrap;'><?php echo htmlentities($this->bootstrap->get_contents()); ?></pre>
317
+ <p><?php _e('3. Save the file and press the \'Try again\' button below:', 'all-in-one-wp-security-and-firewall');?></p>
318
+ <?php
319
+ $this->render_try_again_button();
320
+ $this->render_manual_notice_footer();
321
+ }
322
+
323
+ /**
324
+ * Notice is shown if auto_prepend_file directive is already set in user.ini
325
+ *
326
+ * @param string $directive_value
327
+ * @return void
328
+ */
329
+ private function render_userini_directive_set_notice($directive_value) {
330
+
331
+ $bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();
332
+ $firewall_file = AIOWPSecurity_Utility_Firewall::get_server_file();
333
+
334
+ $this->render_manual_notice_header();
335
+ ?>
336
+ <p>
337
+ <?php _e('1. Open the following file:', 'all-in-one-wp-security-and-firewall'); ?>
338
+ </p>
339
+ <p><code><?php echo esc_html($firewall_file); ?></code></p>
340
+
341
+ <?php if (empty($directive_value)) {?>
342
+ <p>
343
+ <?php _e('2. Look for the auto_prepend_file directive.', 'all-in-one-wp-security-and-firewall'); ?>
344
+ </p>
345
+ <?php } else {?>
346
+ <p>
347
+ <?php _e('2. Look for the following:', 'all-in-one-wp-security-and-firewall'); ?>
348
+ </p>
349
+ <pre style='max-width: 100%;background-color: #f0f0f0;border:#ccc solid 1px;padding: 10px;white-space:pre-wrap;'><?php echo "auto_prepend_file='".esc_html($directive_value)."'";?></pre>
350
+ <?php } ?>
351
+
352
+ <p>
353
+ <?php _e('3. Change it to the following:', 'all-in-one-wp-security-and-firewall'); ?>
354
+ </p>
355
+ <pre style='max-width: 100%;background-color: #f0f0f0;border:#ccc solid 1px;padding: 10px;white-space:pre-wrap;'><?php echo "auto_prepend_file='".esc_html($bootstrap_path)."'";?></pre>
356
+ <p>
357
+ <?php echo __('4. Save the file and press the \'Try again\' button below:', 'all-in-one-wp-security-and-firewall').' '.__('You may have to wait up to 5 minutes before the settings take effect.', 'all-in-one-wp-security-and-firewall'); ?>
358
+ </p>
359
+ <?php
360
+ $this->render_try_again_button();
361
+ $this->render_manual_notice_footer();
362
+ }
363
+
364
+ /**
365
+ * Shows when the firewall has successfully installed
366
+ *
367
+ * @return void
368
+ */
369
+ private function render_firewall_installed_notice() {
370
+ ?>
371
+ <div class='notice notice-success is-dismissible'>
372
+ <p><strong><?php _e('All In One WP Security and Firewall', 'all-in-one-wp-security-and-firewall'); ?></strong></p>
373
+ <p>
374
+ <?php
375
+ echo __('Your firewall has been installed with the highest level of protection.', 'all-in-one-wp-security-and-firewall').' '.
376
+ __('You may have to wait 5 minutes for the changes to take effect.', 'all-in-one-wp-security-and-firewall');
377
+ ?>
378
+ </p>
379
+ </div>
380
+ <?php
381
+ }
382
+
383
+ /**
384
+ * Renders the 'manual setup' dashboard notice
385
+ *
386
+ * @return void
387
+ */
388
+ private function render_manual_setup_notice() {
389
+
390
+ $firewall_file = AIOWPSecurity_Utility_Firewall::get_server_file();
391
+
392
+ if (AIOWPSecurity_Utility_Firewall::MANUAL_SETUP === $firewall_file) {
393
+ //Show users how to manually add the firewall via php.ini if we can't detect their server
394
+ $bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();
395
+
396
+ $this->render_manual_notice_header();
397
+ ?>
398
+ <p>
399
+ <?php _e('1. Open your php.ini file.', 'all-in-one-wp-security-and-firewall'); ?>
400
+ </p>
401
+ <p>
402
+ <?php _e('2. Set the auto_prepend_file directive like below:', 'all-in-one-wp-security-and-firewall'); ?>
403
+ </p>
404
+ <pre style='max-width: 100%;background-color: #f0f0f0;border:#ccc solid 1px;padding: 10px;white-space:pre-wrap;'><?php echo "auto_prepend_file='".esc_html($bootstrap_path)."'";?></pre>
405
+ <p>
406
+ <?php echo __('3. Restart the webserver and refresh the page', 'all-in-one-wp-security-and-firewall').' '.__('You may have to wait up to 5 minutes before the settings take effect.', 'all-in-one-wp-security-and-firewall'); ?>
407
+ </p>
408
+ <?php
409
+ $this->render_manual_notice_footer();
410
+ } else {
411
+ //Show users how to manually add the firewall via their own server file
412
+ $this->render_manual_notice_header();
413
+ $firewall_file_name = pathinfo($firewall_file, PATHINFO_BASENAME);
414
+ ?>
415
+ <p>
416
+ <?php
417
+ /* translators: %s Firewall file name. */
418
+ printf(__('1. Create a file with the name %s in the same directory as your WordPress install is in, i.e.:', 'all-in-one-wp-security-and-firewall'), $firewall_file_name);
419
+ ?>
420
+ <p><code><?php echo esc_html($firewall_file); ?></code></p>
421
+ </p>
422
+ <p>
423
+ <?php _e('2. Paste in the following directives:', 'all-in-one-wp-security-and-firewall'); ?>
424
+ </p>
425
+ <pre style='max-width: 100%;background-color: #f0f0f0;border:#ccc solid 1px;padding: 10px;white-space:pre-wrap;'><?php echo htmlentities($firewall_file->get_contents()); ?></pre>
426
+ <p>
427
+ <?php echo __('3. Save the file and press the \'Try again\' button below:', 'all-in-one-wp-security-and-firewall'); ?>
428
+ </p>
429
+ <?php
430
+ $this->render_try_again_button();
431
+ $this->render_manual_notice_footer();
432
+ }
433
+ }
434
+
435
+ /**
436
+ * The header for notices that require manual intervention
437
+ *
438
+ * @return void
439
+ */
440
+ private function render_manual_notice_header() {
441
+ ?>
442
+ <div class="notice notice-warning is-dismissible">
443
+ <p>
444
+ <strong><?php _e('All In One WP Security and Firewall', 'all-in-one-wp-security-and-firewall'); ?></strong>
445
+ </p>
446
+ <p>
447
+ <?php echo __('We were unable to set up your firewall with the highest level of protection.', 'all-in-one-wp-security-and-firewall').' '.
448
+ __('Your firewall will have reduced functionality.', 'all-in-one-wp-security-and-firewall');
449
+ ?>
450
+ </p>
451
+ <p>
452
+ <?php _e('To give your site the highest level of protection, please follow these steps:', 'all-in-one-wp-security-and-firewall'); ?>
453
+ </p>
454
+ <?php
455
+ }
456
+
457
+ /**
458
+ * The footer for notices that require manual intervention
459
+ *
460
+ * @return void
461
+ */
462
+ private function render_manual_notice_footer() {
463
+ ?>
464
+ <p>
465
+ <strong><?php _e('Note: if you\'re unable to perform any of the aforementioned steps, please ask your web hosting provider for further assistance.', 'all-in-one-wp-security-and-firewall'); ?></strong>
466
+ </p>
467
+ </div>
468
+ <?php
469
+ }
470
+
471
+ /**
472
+ * Render Try again button.
473
+ *
474
+ * @return void
475
+ */
476
+ private function render_try_again_button() {
477
+ ?>
478
+ <form action="<?php echo esc_url(admin_url('admin-post.php')); ?>" method="POST">
479
+ <?php wp_nonce_field('aiowpsec-firewall-setup'); ?>
480
+ <input type="hidden" name="action" value="aiowps_firewall_setup">
481
+ <div style="padding-top: 10px; padding-bottom: 10px;">
482
+ <input class="button button-primary" type="submit" name="btn_try_again" value="<?php _e('Try again', 'all-in-one-wp-security-and-firewall'); ?>">
483
+ </div>
484
+ </form>
485
+ <?php
486
+ }
487
+
488
+ /**
489
+ * Renders the warning that users do not have the highest level of protection
490
+ *
491
+ * @return void
492
+ */
493
+ private function render_upgrade_protection_notice() {
494
+
495
+ if ($this->should_not_show_notice()) {
496
+ return;
497
+ }
498
+ ?>
499
+ <div class="notice notice-warning">
500
+ <form action="<?php echo esc_url(admin_url('admin-post.php')); ?>" method="POST">
501
+ <?php wp_nonce_field('aiowpsec-firewall-setup'); ?>
502
+ <input type="hidden" name="action" value="aiowps_firewall_setup">
503
+ <p>
504
+ <?php _e('We have detected that your AIOWPS firewall is not fully installed, and therefore does not have the highest level of protection. ', 'all-in-one-wp-security-and-firewall');?>
505
+ <?php _e('Your firewall will have reduced functionality until it has been upgraded. ', 'all-in-one-wp-security-and-firewall');?>
506
+ <div style="padding-top: 10px;">
507
+ <input class="button button-primary" type="submit" name="btn_upgrade_now" value="<?php _e('Upgrade your protection now', 'all-in-one-wp-security-and-firewall'); ?>">
508
+ </div>
509
+ </p>
510
+ </form>
511
+
512
+ </div>
513
+
514
+ <?php
515
+ }
516
+
517
+ /**
518
+ * Whether the firewall notice should not be shown.
519
+ *
520
+ * return boolean True if the firewall notice should not be shown otherwise false.
521
+ */
522
+ private function should_not_show_notice() {
523
+ if (!is_main_site()) {
524
+ return true;
525
+ }
526
+
527
+ if (!current_user_can(AIOWPSEC_MANAGEMENT_PERMISSION)) {
528
+ return true;
529
+ }
530
+
531
+ if ($this->any_pending_notices()) {
532
+ return true; //only display if there are no other notices waiting to be displayed
533
+ }
534
+
535
+ return false;
536
+ }
537
+
538
+ /**
539
+ * Renders the 'Set up now' dashboard notice
540
+ *
541
+ * @return void
542
+ */
543
+ private function render_automatic_setup_notice() {
544
+
545
+ if ($this->should_not_show_notice()) {
546
+ return;
547
+ }
548
+ ?>
549
+ <div class="notice notice-information">
550
+
551
+ <form action="<?php echo esc_url(admin_url('admin-post.php')); ?>" method="POST">
552
+ <?php wp_nonce_field('aiowpsec-firewall-setup'); ?>
553
+ <input type="hidden" name="action" value="aiowps_firewall_setup">
554
+ <p>
555
+ <strong><?php _e('All In One WP Security and Firewall', 'all-in-one-wp-security-and-firewall'); ?></strong>
556
+ </p>
557
+ <p>
558
+ <?php echo __('Our PHP-based firewall has been created to give you even greater protection.', 'all-in-one-wp-security-and-firewall').' '.
559
+ __('To ensure the PHP-based firewall runs before any potentially vulnerable code in your WordPress site can be reached, it will need to be set up.');?>
560
+ </p>
561
+ <p>
562
+ <?php _e('If you already have our .htaccess-based firewall enabled, you will still need to set up the PHP-based firewall to benefit from its protection.', 'all-in-one-wp-security-and-firewall'); ?>
563
+ </p>
564
+ <p>
565
+ <?php _e('To set up the PHP-based firewall, press the \'Set up now\' button below:', 'all-in-one-wp-security-and-firewall'); ?>
566
+ </p>
567
+ <div style="padding-top: 10px; padding-bottom: 10px;">
568
+ <input class="button button-primary" type="submit" name="btn_setup_now" value="<?php _e('Set up now', 'all-in-one-wp-security-and-firewall'); ?>">
569
+ </div>
570
+ </form>
571
+
572
+ </div>
573
+
574
+ <?php
575
+ }
576
+
577
+ /**
578
+ * Ensures only one instance of the class can be created (singleton)
579
+ *
580
+ * @return void
581
+ */
582
+ public static function get_instance() {
583
+
584
+ if (null === self::$instance) {
585
+ self::$instance = new self();
586
+ }
587
+
588
+ return self::$instance;
589
+ }
590
+ }
admin/wp-security-list-404.php CHANGED
@@ -5,7 +5,7 @@ if(!defined('ABSPATH')){
5
 
6
  class AIOWPSecurity_List_404 extends AIOWPSecurity_List_Table {
7
 
8
- function __construct() {
9
  global $status, $page;
10
 
11
  //Set parent defaults
@@ -16,15 +16,22 @@ class AIOWPSecurity_List_404 extends AIOWPSecurity_List_Table {
16
  ));
17
  }
18
 
19
- function column_default($item, $column_name) {
20
  return $item[$column_name];
21
  }
22
 
23
- function column_id($item) {
 
 
 
 
 
 
 
24
  $tab = strip_tags($_REQUEST['tab']);
25
  $ip = $item['ip_or_host'];
26
 
27
- $blocked_ips_tab = 'tab3';
28
  //Check if this IP address is locked
29
  $is_locked = AIOWPSecurity_Utility::check_locked_ip($ip);
30
  $delete_url = sprintf('admin.php?page=%s&tab=%s&action=%s&id=%s', AIOWPSEC_FIREWALL_MENU_SLUG, $tab, 'delete_event_log', $item['id']);
@@ -52,7 +59,7 @@ class AIOWPSecurity_List_404 extends AIOWPSecurity_List_Table {
52
  );
53
  }
54
 
55
- function column_status($item) {
56
  global $aio_wp_security;
57
  $ip = $item['ip_or_host'];
58
  //Check if this IP address is locked
@@ -69,7 +76,7 @@ class AIOWPSecurity_List_404 extends AIOWPSecurity_List_Table {
69
  }
70
  }
71
 
72
- function column_cb($item) {
73
  return sprintf(
74
  '<input type="checkbox" name="%1$s[]" value="%2$s" />',
75
  /* $1%s */ $this->_args['singular'], //Let's simply repurpose the table's singular label
@@ -77,7 +84,7 @@ class AIOWPSecurity_List_404 extends AIOWPSecurity_List_Table {
77
  );
78
  }
79
 
80
- function get_columns() {
81
  $columns = array(
82
  'cb' => '<input type="checkbox" />', //Render a checkbox
83
  'id' => 'ID',
@@ -92,7 +99,7 @@ class AIOWPSecurity_List_404 extends AIOWPSecurity_List_Table {
92
  return $columns;
93
  }
94
 
95
- function get_sortable_columns() {
96
  $sortable_columns = array(
97
  'id' => array('id', false),
98
  'event_type' => array('event_type', false),
@@ -105,7 +112,7 @@ class AIOWPSecurity_List_404 extends AIOWPSecurity_List_Table {
105
  return $sortable_columns;
106
  }
107
 
108
- function get_bulk_actions() {
109
  $actions = array(
110
  //'unlock' => 'Unlock',
111
  'bulk_block_ip' => 'Temp Block IP',
@@ -115,7 +122,7 @@ class AIOWPSecurity_List_404 extends AIOWPSecurity_List_Table {
115
  return $actions;
116
  }
117
 
118
- function process_bulk_action() {
119
  if ('bulk_block_ip' === $this->current_action()) {//Process delete bulk actions
120
  if (!isset($_REQUEST['item'])) {
121
  AIOWPSecurity_Admin_Menu::show_msg_error_st(__('Please select some records using the checkboxes', 'all-in-one-wp-security-and-firewall'));
@@ -140,13 +147,16 @@ class AIOWPSecurity_List_404 extends AIOWPSecurity_List_Table {
140
  }
141
  }
142
 
143
- /*
144
- * This function will lock an IP address by adding it to the "login_lockdown" table
145
- */
146
-
147
- function block_ip($entries, $username = '') {
 
 
 
 
148
  global $wpdb;
149
- $events_table = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
150
  if (is_array($entries)) {
151
  //lock multiple records
152
  $entries = array_filter($entries, 'is_numeric'); //discard non-numeric ID values
@@ -176,11 +186,14 @@ class AIOWPSecurity_List_404 extends AIOWPSecurity_List_Table {
176
  }
177
  }
178
 
179
- /*
180
- * This function will lock an IP address by adding it to the "login_lockdown" table
181
- */
182
-
183
- function blacklist_ip_address($entries) {
 
 
 
184
  global $wpdb, $aio_wp_security;
185
  $bl_ip_addresses = $aio_wp_security->configs->get_value('aiowps_banned_ip_addresses'); //get the currently saved blacklisted IPs
186
  $ip_list_array = AIOWPSecurity_Utility_IP::create_ip_list_array_from_string_with_newline($bl_ip_addresses);
@@ -229,12 +242,14 @@ class AIOWPSecurity_List_404 extends AIOWPSecurity_List_Table {
229
  }
230
  }
231
 
232
- /*
233
- * This function will delete selected 404 records from the "events" table.
234
- * The function accepts either an array of IDs or a single ID
235
- */
236
-
237
- function delete_404_event_records($entries) {
 
 
238
  global $wpdb, $aio_wp_security;
239
  $events_table = AIOWPSEC_TBL_EVENTS;
240
  if (is_array($entries)) {
@@ -246,9 +261,13 @@ class AIOWPSecurity_List_404 extends AIOWPSecurity_List_Table {
246
  $id_list = "(" . implode(",", $entries) . ")"; //Create comma separate list for DB operation
247
  $delete_command = "DELETE FROM " . $events_table . " WHERE id IN " . $id_list;
248
  $result = $wpdb->query($delete_command);
249
- if ($result != NULL) {
250
- AIOWPSecurity_Admin_Menu::show_msg_record_deleted_st();
251
- }
 
 
 
 
252
  }
253
 
254
  } elseif ($entries != NULL) {
@@ -263,62 +282,75 @@ class AIOWPSecurity_List_404 extends AIOWPSecurity_List_Table {
263
  $delete_command = "DELETE FROM " . $events_table . " WHERE id = '" . absint($entries) . "'";
264
  //$delete_command = $wpdb->prepare("DELETE FROM $events_table WHERE id = %s", absint($entries));
265
  $result = $wpdb->query($delete_command);
266
- if ($result != NULL) {
267
- AIOWPSecurity_Admin_Menu::show_msg_record_deleted_st();
268
- }
 
 
 
 
269
  }
270
  }
271
 
272
- function prepare_items($ignore_pagination=false) {
273
- /**
274
- * First, lets decide how many records per page to show
275
- */
276
- $per_page = 100;
277
- $columns = $this->get_columns();
278
- $hidden = array();
279
- $sortable = $this->get_sortable_columns();
 
 
 
 
 
 
 
 
280
 
281
- $this->_column_headers = array($columns, $hidden, $sortable);
282
 
283
- $this->process_bulk_action();
284
 
285
- global $wpdb;
286
- $events_table_name = AIOWPSEC_TBL_EVENTS;
287
 
288
- /* -- Ordering parameters -- */
289
- //Parameters that are going to be used to order the result
290
- isset($_GET["orderby"]) ? $orderby = strip_tags($_GET["orderby"]): $orderby = '';
291
- isset($_GET["order"]) ? $order = strip_tags($_GET["order"]): $order = '';
292
 
293
- $orderby = !empty($orderby) ? esc_sql($orderby) : 'id';
294
- $order = !empty($order) ? esc_sql($order) : 'DESC';
295
-
296
- $orderby = AIOWPSecurity_Utility::sanitize_value_by_array($orderby, $sortable);
297
- $order = AIOWPSecurity_Utility::sanitize_value_by_array($order, array('DESC' => '1', 'ASC' => '1'));
298
 
299
- if (isset($_POST['s'])) {
300
- $search_term = trim($_POST['s']);
301
- $data = $wpdb->get_results($wpdb->prepare("SELECT * FROM " . $events_table_name . " WHERE `ip_or_host` LIKE '%%%s%%' OR `url` LIKE '%%%s%%' OR `referer_info` LIKE '%%%s%%'", $search_term, $search_term, $search_term), ARRAY_A);
302
- } else {
303
- $data = $wpdb->get_results($wpdb->prepare("SELECT * FROM $events_table_name WHERE event_type=%s ORDER BY $orderby $order",'404'), ARRAY_A);
304
- }
305
- $new_data = array();
306
- foreach ($data as $row) {
307
- //lets insert an empty "status" column - we will use later
308
- $row['status'] = '';
309
- $new_data[] = $row;
310
- }
311
- if (!$ignore_pagination) {
312
- $current_page = $this->get_pagenum();
313
- $total_items = count($new_data);
314
- $new_data = array_slice($new_data, (($current_page - 1) * $per_page), $per_page);
315
- $this->set_pagination_args(array(
316
- 'total_items' => $total_items, //WE have to calculate the total number of items
317
- 'per_page' => $per_page, //WE have to determine how many items to show on a page
318
- 'total_pages' => ceil($total_items / $per_page) //WE have to calculate the total number of pages
319
- ));
320
- }
321
- $this->items = $new_data;
322
- }
323
 
324
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  class AIOWPSecurity_List_404 extends AIOWPSecurity_List_Table {
7
 
8
+ public function __construct() {
9
  global $status, $page;
10
 
11
  //Set parent defaults
16
  ));
17
  }
18
 
19
+ public function column_default($item, $column_name) {
20
  return $item[$column_name];
21
  }
22
 
23
+ /**
24
+ * Returns id column html to be rendered.
25
+ *
26
+ * @param Array - data for the columns on the current row
27
+ *
28
+ * @return String
29
+ */
30
+ public function column_id($item) {
31
  $tab = strip_tags($_REQUEST['tab']);
32
  $ip = $item['ip_or_host'];
33
 
34
+ $blocked_ips_tab = 'tab2';
35
  //Check if this IP address is locked
36
  $is_locked = AIOWPSecurity_Utility::check_locked_ip($ip);
37
  $delete_url = sprintf('admin.php?page=%s&tab=%s&action=%s&id=%s', AIOWPSEC_FIREWALL_MENU_SLUG, $tab, 'delete_event_log', $item['id']);
59
  );
60
  }
61
 
62
+ public function column_status($item) {
63
  global $aio_wp_security;
64
  $ip = $item['ip_or_host'];
65
  //Check if this IP address is locked
76
  }
77
  }
78
 
79
+ public function column_cb($item) {
80
  return sprintf(
81
  '<input type="checkbox" name="%1$s[]" value="%2$s" />',
82
  /* $1%s */ $this->_args['singular'], //Let's simply repurpose the table's singular label
84
  );
85
  }
86
 
87
+ public function get_columns() {
88
  $columns = array(
89
  'cb' => '<input type="checkbox" />', //Render a checkbox
90
  'id' => 'ID',
99
  return $columns;
100
  }
101
 
102
+ public function get_sortable_columns() {
103
  $sortable_columns = array(
104
  'id' => array('id', false),
105
  'event_type' => array('event_type', false),
112
  return $sortable_columns;
113
  }
114
 
115
+ public function get_bulk_actions() {
116
  $actions = array(
117
  //'unlock' => 'Unlock',
118
  'bulk_block_ip' => 'Temp Block IP',
122
  return $actions;
123
  }
124
 
125
+ public function process_bulk_action() {
126
  if ('bulk_block_ip' === $this->current_action()) {//Process delete bulk actions
127
  if (!isset($_REQUEST['item'])) {
128
  AIOWPSecurity_Admin_Menu::show_msg_error_st(__('Please select some records using the checkboxes', 'all-in-one-wp-security-and-firewall'));
147
  }
148
  }
149
 
150
+ /**
151
+ * Locks an IP address by adding it to the AIOWPSEC_TBL_LOGIN_LOCKDOWN table.
152
+ *
153
+ * @param Array|String - ids that correspond to ip addresses in the AIOWPSEC_TBL_EVENTS table or a single ip address
154
+ * @param String - (optional)username of user being locked
155
+ *
156
+ * @return Boolean|Void
157
+ */
158
+ public function block_ip($entries, $username = '') {
159
  global $wpdb;
 
160
  if (is_array($entries)) {
161
  //lock multiple records
162
  $entries = array_filter($entries, 'is_numeric'); //discard non-numeric ID values
186
  }
187
  }
188
 
189
+ /**
190
+ * Permanently blocks an IP address by adding it to the blacklist and writing rules to the htaccess file.
191
+ *
192
+ * @param Array|String - ids that correspond to ip addresses in the AIOWPSEC_TBL_EVENTS table or a single ip address
193
+ *
194
+ * @return Boolean|Void
195
+ */
196
+ public function blacklist_ip_address($entries) {
197
  global $wpdb, $aio_wp_security;
198
  $bl_ip_addresses = $aio_wp_security->configs->get_value('aiowps_banned_ip_addresses'); //get the currently saved blacklisted IPs
199
  $ip_list_array = AIOWPSecurity_Utility_IP::create_ip_list_array_from_string_with_newline($bl_ip_addresses);
242
  }
243
  }
244
 
245
+ /**
246
+ * Deletes one or more records from the AIOWPSEC_TBL_EVENTS table.
247
+ *
248
+ * @param Array|String|Integer - ids or a single id
249
+ *
250
+ * @return Void
251
+ */
252
+ public function delete_404_event_records($entries) {
253
  global $wpdb, $aio_wp_security;
254
  $events_table = AIOWPSEC_TBL_EVENTS;
255
  if (is_array($entries)) {
261
  $id_list = "(" . implode(",", $entries) . ")"; //Create comma separate list for DB operation
262
  $delete_command = "DELETE FROM " . $events_table . " WHERE id IN " . $id_list;
263
  $result = $wpdb->query($delete_command);
264
+ if ($result) {
265
+ AIOWPSecurity_Admin_Menu::show_msg_record_deleted_st();
266
+ } else {
267
+ // Error on bulk delete
268
+ $aio_wp_security->debug_logger->log_debug('Database error occurred when deleting rows from Events table. Database error: '.$wpdb->last_error, 4);
269
+ AIOWPSecurity_Admin_Menu::show_msg_record_not_deleted_st();
270
+ }
271
  }
272
 
273
  } elseif ($entries != NULL) {
282
  $delete_command = "DELETE FROM " . $events_table . " WHERE id = '" . absint($entries) . "'";
283
  //$delete_command = $wpdb->prepare("DELETE FROM $events_table WHERE id = %s", absint($entries));
284
  $result = $wpdb->query($delete_command);
285
+ if ($result) {
286
+ AIOWPSecurity_Admin_Menu::show_msg_record_deleted_st();
287
+ } elseif ($result === false) {
288
+ // Error on single delete
289
+ $aio_wp_security->debug_logger->log_debug('Database error occurred when deleting rows from Events table. Database error: '.$wpdb->last_error, 4);
290
+ AIOWPSecurity_Admin_Menu::show_msg_record_not_deleted_st();
291
+ }
292
  }
293
  }
294
 
295
+ /**
296
+ * Retrieves all items from AIOWPSEC_TBL_EVENTS according to a search term inside $_REQUEST['s'] and only '404' events if there is no search term. It then assigns to $this->items.
297
+ *
298
+ * @param Boolean $ignore_pagination - whether to not paginate
299
+ *
300
+ * @return Void
301
+ */
302
+ public function prepare_items($ignore_pagination = false) {
303
+ /**
304
+ * First, lets decide how many records per page to show
305
+ */
306
+ $per_page = 100;
307
+ $columns = $this->get_columns();
308
+ $hidden = array();
309
+ $sortable = $this->get_sortable_columns();
310
+ $search_term = isset($_REQUEST['s']) ? sanitize_text_field(stripslashes($_REQUEST['s'])) : '';
311
 
312
+ $this->_column_headers = array($columns, $hidden, $sortable);
313
 
314
+ $this->process_bulk_action();
315
 
316
+ global $wpdb;
317
+ $events_table_name = AIOWPSEC_TBL_EVENTS;
318
 
319
+ /* -- Ordering parameters -- */
320
+ //Parameters that are going to be used to order the result
321
+ isset($_GET['orderby']) ? $orderby = strip_tags($_GET['orderby']): $orderby = '';
322
+ isset($_GET['order']) ? $order = strip_tags($_GET['order']): $order = '';
323
 
324
+ $orderby = !empty($orderby) ? esc_sql($orderby) : 'id';
325
+ $order = !empty($order) ? esc_sql($order) : 'DESC';
 
 
 
326
 
327
+ $orderby = AIOWPSecurity_Utility::sanitize_value_by_array($orderby, $sortable);
328
+ $order = AIOWPSecurity_Utility::sanitize_value_by_array($order, array('DESC' => '1', 'ASC' => '1'));
329
+
330
+ if (empty($search_term)) {
331
+ $data = $wpdb->get_results("SELECT * FROM $events_table_name WHERE `event_type` = '404' ORDER BY $orderby $order", ARRAY_A);
332
+ } else {
333
+ $data = $wpdb->get_results($wpdb->prepare("SELECT * FROM $events_table_name WHERE `ip_or_host` LIKE '%%%s%%' OR `url` LIKE '%%%s%%' OR `referer_info` LIKE '%%%s%%' ORDER BY $orderby $order", $search_term, $search_term, $search_term), ARRAY_A);
334
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
 
336
+ if (!$ignore_pagination) {
337
+ $current_page = $this->get_pagenum();
338
+ $total_items = count($data);
339
+ $data = array_slice($data, (($current_page - 1) * $per_page), $per_page);
340
+ $this->set_pagination_args(array(
341
+ 'total_items' => $total_items, //WE have to calculate the total number of items
342
+ 'per_page' => $per_page, //WE have to determine how many items to show on a page
343
+ 'total_pages' => ceil($total_items / $per_page) //WE have to calculate the total number of pages
344
+ ));
345
+ }
346
+
347
+ foreach ($data as $index => $row) {
348
+ // Insert an empty status column - we will use later
349
+ $data[$index]['status'] = '';
350
+ $data[$index]['event_date'] = get_date_from_gmt(mysql2date('Y-m-d H:i:s', $row['event_date']), $this->get_wp_date_time_format());
351
+ }
352
+
353
+ $this->items = $data;
354
+ }
355
+
356
+ }
admin/wp-security-list-acct-activity.php CHANGED
@@ -5,7 +5,10 @@ if(!defined('ABSPATH')){
5
 
6
  class AIOWPSecurity_List_Account_Activity extends AIOWPSecurity_List_Table {
7
 
8
- function __construct(){
 
 
 
9
  global $status, $page;
10
 
11
  //Set parent defaults
@@ -17,12 +20,12 @@ class AIOWPSecurity_List_Account_Activity extends AIOWPSecurity_List_Table {
17
 
18
  }
19
 
20
- function column_default($item, $column_name){
21
  return $item[$column_name];
22
  }
23
 
24
- function column_user_id($item){
25
- $tab = strip_tags($_REQUEST['tab']);
26
  $delete_url = sprintf('admin.php?page=%s&tab=%s&action=%s&activity_login_rec=%s', AIOWPSEC_USER_LOGIN_MENU_SLUG, $tab, 'delete_acct_activity_rec', $item['id']);
27
  //Add nonce to delete URL
28
  $delete_url_nonce = wp_nonce_url($delete_url, "delete_acct_activity_log", "aiowps_nonce");
@@ -43,7 +46,7 @@ class AIOWPSecurity_List_Account_Activity extends AIOWPSecurity_List_Table {
43
  return '1000-10-10 10:00:00' == $item['logout_date'] ? __('Login session still active', 'all-in-one-wp-security-and-firewall') : $item['logout_date'];
44
  }
45
 
46
- function column_cb($item){
47
  return sprintf(
48
  '<input type="checkbox" name="%1$s[]" value="%2$s" />',
49
  /*$1%s*/ $this->_args['singular'], //Let's simply repurpose the table's singular label
@@ -51,7 +54,7 @@ class AIOWPSecurity_List_Account_Activity extends AIOWPSecurity_List_Table {
51
  );
52
  }
53
 
54
- function get_columns(){
55
  $columns = array(
56
  'cb' => '<input type="checkbox" />', //Render a checkbox
57
  'user_id' => __('User ID', 'all-in-one-wp-security-and-firewall'),
@@ -63,7 +66,7 @@ class AIOWPSecurity_List_Account_Activity extends AIOWPSecurity_List_Table {
63
  return $columns;
64
  }
65
 
66
- function get_sortable_columns() {
67
  $sortable_columns = array(
68
  'user_id' => array('user_id',false),
69
  'user_login' => array('user_login',false),
@@ -74,36 +77,36 @@ class AIOWPSecurity_List_Account_Activity extends AIOWPSecurity_List_Table {
74
  return $sortable_columns;
75
  }
76
 
77
- function get_bulk_actions() {
78
  $actions = array(
79
  'delete' => 'Delete'
80
  );
81
  return $actions;
82
  }
83
 
84
- function process_bulk_action() {
85
- if('delete'===$this->current_action())
86
  {//Process delete bulk actions
87
- if(!isset($_REQUEST['item']))
88
- {
89
  $error_msg = '<div id="message" class="error"><p><strong>';
90
  $error_msg .= __('Please select some records using the checkboxes','all-in-one-wp-security-and-firewall');
91
  $error_msg .= '</strong></p></div>';
92
- _e($error_msg);
93
  } else{
94
- $this->delete_login_activity_records(($_REQUEST['item']));
 
95
  }
96
  }
97
  }
98
-
99
-
100
-
101
- /*
102
- * This function will delete selected records from the "user_login_activity" table.
103
- * The function accepts either an array of IDs or a single ID
104
- */
105
- function delete_login_activity_records($entries)
106
- {
107
  global $wpdb, $aio_wp_security;
108
  $login_activity_table = AIOWPSEC_TBL_USER_LOGIN_ACTIVITY;
109
  if (is_array($entries))
@@ -111,28 +114,22 @@ class AIOWPSecurity_List_Account_Activity extends AIOWPSecurity_List_Table {
111
  if (isset($_REQUEST['_wp_http_referer']))
112
  {
113
  //Delete multiple records
114
- $tab = strip_tags($_REQUEST['tab']);
115
 
116
  $entries = array_filter($entries, 'is_numeric'); //discard non-numeric ID values
117
  $id_list = "(" .implode(",",$entries) .")"; //Create comma separate list for DB operation
118
  $delete_command = "DELETE FROM ".$login_activity_table." WHERE id IN ".$id_list;
119
  $result = $wpdb->query($delete_command);
120
- if($result !== false)
121
- {
122
- $redir_url = sprintf('admin.php?page=%s&tab=%s&bulk_count=%s', AIOWPSEC_USER_LOGIN_MENU_SLUG, $tab, count($entries));
123
- AIOWPSecurity_Utility::redirect_to_url($redir_url);
124
- } else {
125
- // error on bulk delete
126
- $aio_wp_security->debug_logger->log_debug("DB error: ".$wpdb->last_error,4);
127
- $redir_url = sprintf('admin.php?page=%s&tab=%s&bulk_error=%s', AIOWPSEC_USER_LOGIN_MENU_SLUG, $tab, 1);
128
- AIOWPSecurity_Utility::redirect_to_url($redir_url);
129
-
130
- }
131
  }
132
- }
133
- elseif ($entries != NULL)
134
- {
135
- $nonce=isset($_GET['aiowps_nonce'])?$_GET['aiowps_nonce']:'';
136
  if (!isset($nonce) ||!wp_verify_nonce($nonce, 'delete_acct_activity_log'))
137
  {
138
  $aio_wp_security->debug_logger->log_debug("Nonce check failed for delete selected account activity logs operation!",4);
@@ -141,61 +138,77 @@ class AIOWPSecurity_List_Account_Activity extends AIOWPSecurity_List_Table {
141
  //Delete single record
142
  $delete_command = "DELETE FROM ".$login_activity_table." WHERE id = '".absint($entries)."'";
143
  $result = $wpdb->query($delete_command);
144
- if($result !== false)
145
- {
146
- $success_msg = '<div id="message" class="updated fade"><p><strong>';
147
- $success_msg .= __('The selected entry was deleted successfully!','all-in-one-wp-security-and-firewall');
148
- $success_msg .= '</strong></p></div>';
149
- echo $success_msg;
150
- }
151
  }
152
  }
153
-
154
- function prepare_items($ignore_pagination = false) {
155
- /**
156
- * First, lets decide how many records per page to show
157
- */
158
- $per_page = 100;
159
- $columns = $this->get_columns();
160
- $hidden = array();
161
- $sortable = $this->get_sortable_columns();
162
- $search = isset( $_REQUEST['s'] ) ? sanitize_text_field( $_REQUEST['s'] ) : '';
163
 
164
- $this->_column_headers = array($columns, $hidden, $sortable);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
 
166
- $this->process_bulk_action();
167
 
168
- global $wpdb;
169
- $login_activity_table = AIOWPSEC_TBL_USER_LOGIN_ACTIVITY;
170
 
171
- /* -- Ordering parameters -- */
172
- //Parameters that are going to be used to order the result
173
 
174
- isset($_GET["orderby"]) ? $orderby = strip_tags($_GET["orderby"]) : $orderby = '';
175
- isset($_GET["order"]) ? $order = strip_tags($_GET["order"]) : $order = '';
176
 
177
- $orderby = !empty($orderby) ? esc_sql($orderby) : 'login_date';
178
- $order = !empty($order) ? esc_sql($order) : 'DESC';
179
 
180
- $orderby = AIOWPSecurity_Utility::sanitize_value_by_array($orderby, $sortable);
181
- $order = AIOWPSecurity_Utility::sanitize_value_by_array($order, array('DESC' => '1', 'ASC' => '1'));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
 
183
- if(empty($search)) {
184
- $data = $wpdb->get_results("SELECT * FROM $login_activity_table ORDER BY $orderby $order", ARRAY_A);
185
- } else {
186
- $data = $wpdb->get_results($wpdb->prepare("SELECT * FROM $login_activity_table WHERE `user_login` LIKE '%%%s%%' OR `login_ip` LIKE '%%%s%%' ORDER BY $orderby $order LIMIT %d", $search, $search, 100), ARRAY_A);
187
- }
188
-
189
- if (!$ignore_pagination) {
190
- $current_page = $this->get_pagenum();
191
- $total_items = count($data);
192
- $data = array_slice($data, (($current_page - 1) * $per_page), $per_page);
193
- $this->set_pagination_args(array(
194
- 'total_items' => $total_items, //WE have to calculate the total number of items
195
- 'per_page' => $per_page, //WE have to determine how many items to show on a page
196
- 'total_pages' => ceil($total_items / $per_page) //WE have to calculate the total number of pages
197
- ));
198
- }
199
- $this->items = $data;
200
- }
201
  }
5
 
6
  class AIOWPSecurity_List_Account_Activity extends AIOWPSecurity_List_Table {
7
 
8
+ /**
9
+ * Class constructor
10
+ */
11
+ public function __construct(){
12
  global $status, $page;
13
 
14
  //Set parent defaults
20
 
21
  }
22
 
23
+ public function column_default($item, $column_name){
24
  return $item[$column_name];
25
  }
26
 
27
+ public function column_user_id($item){
28
+ $tab = strip_tags(stripslashes($_REQUEST['tab']));
29
  $delete_url = sprintf('admin.php?page=%s&tab=%s&action=%s&activity_login_rec=%s', AIOWPSEC_USER_LOGIN_MENU_SLUG, $tab, 'delete_acct_activity_rec', $item['id']);
30
  //Add nonce to delete URL
31
  $delete_url_nonce = wp_nonce_url($delete_url, "delete_acct_activity_log", "aiowps_nonce");
46
  return '1000-10-10 10:00:00' == $item['logout_date'] ? __('Login session still active', 'all-in-one-wp-security-and-firewall') : $item['logout_date'];
47
  }
48
 
49
+ public function column_cb($item){
50
  return sprintf(
51
  '<input type="checkbox" name="%1$s[]" value="%2$s" />',
52
  /*$1%s*/ $this->_args['singular'], //Let's simply repurpose the table's singular label
54
  );
55
  }
56
 
57
+ public function get_columns(){
58
  $columns = array(
59
  'cb' => '<input type="checkbox" />', //Render a checkbox
60
  'user_id' => __('User ID', 'all-in-one-wp-security-and-firewall'),
66
  return $columns;
67
  }
68
 
69
+ public function get_sortable_columns() {
70
  $sortable_columns = array(
71
  'user_id' => array('user_id',false),
72
  'user_login' => array('user_login',false),
77
  return $sortable_columns;
78
  }
79
 
80
+ public function get_bulk_actions() {
81
  $actions = array(
82
  'delete' => 'Delete'
83
  );
84
  return $actions;
85
  }
86
 
87
+ public function process_bulk_action() {
88
+ if ('delete'===$this->current_action())
89
  {//Process delete bulk actions
90
+ if (!isset($_REQUEST['item'])) {
 
91
  $error_msg = '<div id="message" class="error"><p><strong>';
92
  $error_msg .= __('Please select some records using the checkboxes','all-in-one-wp-security-and-firewall');
93
  $error_msg .= '</strong></p></div>';
94
+ echo $error_msg;
95
  } else{
96
+ $delete_login_activity_ids = array_filter(array_map('intval', $_REQUEST['item']));
97
+ $this->delete_login_activity_records($delete_login_activity_ids);
98
  }
99
  }
100
  }
101
+
102
+ /**
103
+ * Deletes one or more records from the AIOWPSEC_TBL_USER_LOGIN_ACTIVITY table.
104
+ *
105
+ * @param Array|String|Integer - ids or a single id
106
+ *
107
+ * @return Void
108
+ */
109
+ public function delete_login_activity_records($entries) {
110
  global $wpdb, $aio_wp_security;
111
  $login_activity_table = AIOWPSEC_TBL_USER_LOGIN_ACTIVITY;
112
  if (is_array($entries))
114
  if (isset($_REQUEST['_wp_http_referer']))
115
  {
116
  //Delete multiple records
117
+ $tab = strip_tags(stripslashes($_REQUEST['tab']));
118
 
119
  $entries = array_filter($entries, 'is_numeric'); //discard non-numeric ID values
120
  $id_list = "(" .implode(",",$entries) .")"; //Create comma separate list for DB operation
121
  $delete_command = "DELETE FROM ".$login_activity_table." WHERE id IN ".$id_list;
122
  $result = $wpdb->query($delete_command);
123
+ if ($result) {
124
+ AIOWPSecurity_Admin_Menu::show_msg_record_deleted_st();
125
+ } else {
126
+ // Error on bulk delete
127
+ $aio_wp_security->debug_logger->log_debug('Database error occurred when deleting rows from User Login Activity table. Database error: '.$wpdb->last_error, 4);
128
+ AIOWPSecurity_Admin_Menu::show_msg_record_not_deleted_st();
129
+ }
 
 
 
 
130
  }
131
+ } elseif ($entries != NULL) {
132
+ $nonce=isset($_GET['aiowps_nonce']) ? stripslashes($_GET['aiowps_nonce']) : '';
 
 
133
  if (!isset($nonce) ||!wp_verify_nonce($nonce, 'delete_acct_activity_log'))
134
  {
135
  $aio_wp_security->debug_logger->log_debug("Nonce check failed for delete selected account activity logs operation!",4);
138
  //Delete single record
139
  $delete_command = "DELETE FROM ".$login_activity_table." WHERE id = '".absint($entries)."'";
140
  $result = $wpdb->query($delete_command);
141
+ if ($result) {
142
+ AIOWPSecurity_Admin_Menu::show_msg_record_deleted_st();
143
+ } elseif ($result === false) {
144
+ // Error on single delete
145
+ $aio_wp_security->debug_logger->log_debug('Database error occurred when deleting rows from User Login Activity table. Database error: '.$wpdb->last_error, 4);
146
+ AIOWPSecurity_Admin_Menu::show_msg_record_not_deleted_st();
147
+ }
148
  }
149
  }
 
 
 
 
 
 
 
 
 
 
150
 
151
+ /**
152
+ * Retrieves all items from AIOWPSEC_TBL_USER_LOGIN_ACTIVITY according to a search term inside $_REQUEST['s']. It then assigns to $this->items.
153
+ *
154
+ * @param Boolean $ignore_pagination - whether to not paginate
155
+ *
156
+ * @return Void
157
+ */
158
+ public function prepare_items($ignore_pagination = false) {
159
+ /**
160
+ * First, lets decide how many records per page to show
161
+ */
162
+ $per_page = 100;
163
+ $columns = $this->get_columns();
164
+ $hidden = array();
165
+ $sortable = $this->get_sortable_columns();
166
+ $search_term = isset($_REQUEST['s']) ? sanitize_text_field(stripslashes($_REQUEST['s'])) : '';
167
 
168
+ $this->_column_headers = array($columns, $hidden, $sortable);
169
 
170
+ $this->process_bulk_action();
 
171
 
172
+ global $wpdb;
173
+ $login_activity_table = AIOWPSEC_TBL_USER_LOGIN_ACTIVITY;
174
 
175
+ /* -- Ordering parameters -- */
176
+ //Parameters that are going to be used to order the result
177
 
178
+ $orderby = isset($_GET['orderby']) ? strip_tags(stripslashes($_GET['orderby'])) : $orderby = '';
179
+ $order = isset($_GET['order']) ? strip_tags(stripslashes($_GET['order'])) : $order = '';
180
 
181
+ $orderby = !empty($orderby) ? esc_sql($orderby) : 'login_date';
182
+ $order = !empty($order) ? esc_sql($order) : 'DESC';
183
+
184
+ $orderby = AIOWPSecurity_Utility::sanitize_value_by_array($orderby, $sortable);
185
+ $order = AIOWPSecurity_Utility::sanitize_value_by_array($order, array('DESC' => '1', 'ASC' => '1'));
186
+
187
+ if (empty($search_term)) {
188
+ $data = $wpdb->get_results("SELECT * FROM $login_activity_table ORDER BY $orderby $order", ARRAY_A);
189
+ } else {
190
+ $data = $wpdb->get_results($wpdb->prepare("SELECT * FROM $login_activity_table WHERE `user_login` LIKE '%%%s%%' OR `login_ip` LIKE '%%%s%%' ORDER BY $orderby $order LIMIT 100", $search_term, $search_term), ARRAY_A);
191
+ }
192
+
193
+ if (!$ignore_pagination) {
194
+ $current_page = $this->get_pagenum();
195
+ $total_items = count($data);
196
+ $data = array_slice($data, (($current_page - 1) * $per_page), $per_page);
197
+ $this->set_pagination_args(array(
198
+ 'total_items' => $total_items, //WE have to calculate the total number of items
199
+ 'per_page' => $per_page, //WE have to determine how many items to show on a page
200
+ 'total_pages' => ceil($total_items / $per_page) //WE have to calculate the total number of pages
201
+ ));
202
+ }
203
+
204
+ foreach ($data as $index => $row) {
205
+ $data[$index]['login_date'] = get_date_from_gmt(mysql2date('Y-m-d H:i:s', $row['login_date']), $this->get_wp_date_time_format());
206
+ if ('1000-10-10 10:00:00' != $row['logout_date']) {
207
+ $data[$index]['logout_date'] = get_date_from_gmt(mysql2date('Y-m-d H:i:s', $row['logout_date']), $this->get_wp_date_time_format());
208
+ }
209
+ }
210
+
211
+ $this->items = $data;
212
+ }
213
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  }
admin/wp-security-list-comment-spammer-ip.php CHANGED
@@ -24,7 +24,7 @@ class AIOWPSecurity_List_Comment_Spammer_IP extends AIOWPSecurity_List_Table {
24
  function column_comment_author_IP($item){
25
  $tab = strip_tags($_REQUEST['tab']);
26
  //Build row actions
27
- if (AIOWPSecurity_Utility::is_multisite_install() && get_current_blog_id() != 1){
28
  //Suppress the block link if site is a multi site AND not the main site
29
  $actions = array(); //blank array
30
  }else{
@@ -73,7 +73,7 @@ class AIOWPSecurity_List_Comment_Spammer_IP extends AIOWPSecurity_List_Table {
73
  }
74
 
75
  function get_bulk_actions() {
76
- if (AIOWPSecurity_Utility::is_multisite_install() && get_current_blog_id() != 1){
77
  //Suppress the block link if site is a multi site AND not the main site
78
  $actions = array(); //blank array
79
  }else{
24
  function column_comment_author_IP($item){
25
  $tab = strip_tags($_REQUEST['tab']);
26
  //Build row actions
27
+ if (is_multisite() && get_current_blog_id() != 1){
28
  //Suppress the block link if site is a multi site AND not the main site
29
  $actions = array(); //blank array
30
  }else{
73
  }
74
 
75
  function get_bulk_actions() {
76
+ if (is_multisite() && get_current_blog_id() != 1){
77
  //Suppress the block link if site is a multi site AND not the main site
78
  $actions = array(); //blank array
79
  }else{
admin/wp-security-list-locked-ip.php CHANGED
@@ -109,54 +109,55 @@ class AIOWPSecurity_List_Locked_IP extends AIOWPSecurity_List_Table {
109
  }
110
  }
111
  }
112
-
113
-
114
- /*
115
- * This function will unlock an IP range by modifying the "release_date" column of a record in the "login_lockdown" table
116
- */
117
- function unlock_ip_range($entries)
118
- {
119
- global $wpdb,$aio_wp_security;
120
- $lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
121
- if (is_array($entries))
122
- {
123
- if (isset($_REQUEST['_wp_http_referer']))
124
- {
125
- //Unlock multiple records
126
- $entries = array_filter($entries, 'is_numeric'); //discard non-numeric ID values
127
- $id_list = "(" .implode(",",$entries) .")"; //Create comma separate list for DB operation
128
- $unlock_command = "UPDATE ".$lockdown_table." SET release_date = now() WHERE id IN ".$id_list;
129
- $result = $wpdb->query($unlock_command);
130
- if($result != NULL)
131
- {
132
- AIOWPSecurity_Admin_Menu::show_msg_updated_st(__('The selected IP entries were unlocked successfully!','all-in-one-wp-security-and-firewall'));
133
- }
134
- }
135
- } elseif ($entries != NULL)
136
- {
137
- $nonce=isset($_GET['aiowps_nonce'])?$_GET['aiowps_nonce']:'';
138
- if (!isset($nonce) ||!wp_verify_nonce($nonce, 'unlock_ip'))
139
- {
140
- $aio_wp_security->debug_logger->log_debug("Nonce check failed for unlock IP operation!",4);
141
- die(__('Nonce check failed for unlock IP operation!','all-in-one-wp-security-and-firewall'));
142
- }
143
-
144
- //Unlock single record
145
- $unlock_command = $wpdb->prepare( "UPDATE ".$lockdown_table." SET release_date = now() WHERE id = %d", absint($entries) );
146
- $result = $wpdb->query($unlock_command);
147
- if($result != NULL)
148
- {
149
- AIOWPSecurity_Admin_Menu::show_msg_updated_st(__('The selected IP entry was unlocked successfully!','all-in-one-wp-security-and-firewall'));
150
- }
151
- }
152
- }
153
-
154
- /*
155
- * This function will delete selected records from the "login_lockdown" table.
156
- * The function accepts either an array of IDs or a single ID
157
- */
158
- function delete_lockdown_records($entries)
159
- {
 
160
  global $wpdb, $aio_wp_security;
161
  $lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
162
  if (is_array($entries))
@@ -168,10 +169,13 @@ class AIOWPSecurity_List_Locked_IP extends AIOWPSecurity_List_Table {
168
  $id_list = "(" .implode(",",$entries) .")"; //Create comma separate list for DB operation
169
  $delete_command = "DELETE FROM ".$lockdown_table." WHERE id IN ".$id_list;
170
  $result = $wpdb->query($delete_command);
171
- if($result != NULL)
172
- {
173
- AIOWPSecurity_Admin_Menu::show_msg_record_deleted_st();
174
- }
 
 
 
175
  }
176
  }
177
  elseif ($entries != NULL)
@@ -185,50 +189,69 @@ class AIOWPSecurity_List_Locked_IP extends AIOWPSecurity_List_Table {
185
  //Delete single record
186
  $delete_command = "DELETE FROM ".$lockdown_table." WHERE id = '".absint($entries)."'";
187
  $result = $wpdb->query($delete_command);
188
- if($result != NULL)
189
- {
190
- AIOWPSecurity_Admin_Menu::show_msg_record_deleted_st();
191
- }
 
 
 
192
  }
193
  }
194
-
195
- function prepare_items() {
196
- /**
197
- * First, lets decide how many records per page to show
198
- */
199
- $per_page = 100;
200
- $columns = $this->get_columns();
201
- $hidden = array();
202
- $sortable = $this->get_sortable_columns();
203
-
204
- $this->_column_headers = array($columns, $hidden, $sortable);
205
-
206
- $this->process_bulk_action();
207
-
208
- global $wpdb;
209
- $lockdown_table_name = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
210
 
211
- /* -- Ordering parameters -- */
212
- //Parameters that are going to be used to order the result
213
- isset($_GET["orderby"]) ? $orderby = strip_tags($_GET["orderby"]): $orderby = '';
214
- isset($_GET["order"]) ? $order = strip_tags($_GET["order"]): $order = '';
 
 
 
 
 
215
 
216
- $orderby = !empty($orderby) ? esc_sql($orderby) : 'lockdown_date';
217
- $order = !empty($order) ? esc_sql($order) : 'DESC';
218
 
219
- $orderby = AIOWPSecurity_Utility::sanitize_value_by_array($orderby, $sortable);
220
- $order = AIOWPSecurity_Utility::sanitize_value_by_array($order, array('DESC' => '1', 'ASC' => '1'));
221
-
222
- $now = current_time( 'mysql' );
223
- $data = $wpdb->get_results($wpdb->prepare("SELECT * FROM $lockdown_table_name WHERE (lock_reason=%s OR lock_reason=%s) AND release_date > %s ORDER BY $orderby $order", 'login_fail', '404', $now), ARRAY_A);
224
- $current_page = $this->get_pagenum();
225
- $total_items = count($data);
226
- $data = array_slice($data,(($current_page-1)*$per_page),$per_page);
227
- $this->items = $data;
228
- $this->set_pagination_args( array(
229
- 'total_items' => $total_items, //WE have to calculate the total number of items
230
- 'per_page' => $per_page, //WE have to determine how many items to show on a page
231
- 'total_pages' => ceil($total_items/$per_page) //WE have to calculate the total number of pages
232
- ) );
233
- }
234
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  }
110
  }
111
  }
112
+
113
+ /**
114
+ * Unlocks an IP range by modifying the release_date column of a record in the AIOWPSEC_TBL_LOGIN_LOCKDOWN table.
115
+ *
116
+ * @param Array|Integer - ids or a single id
117
+ *
118
+ * @return Void
119
+ */
120
+ public function unlock_ip_range($entries) {
121
+ global $wpdb, $aio_wp_security;
122
+
123
+ $lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
124
+
125
+ $now = current_time('mysql', true);
126
+
127
+ if (is_array($entries)) {
128
+ if (isset($_REQUEST['_wp_http_referer'])) {
129
+ // Unlock multiple records
130
+ $entries = array_filter($entries, 'is_numeric'); // Discard non-numeric ID values
131
+ $id_list = '(' .implode(',', $entries) .')'; // Create comma separate list for DB operation
132
+ $result = $wpdb->query($wpdb->prepare("UPDATE $lockdown_table SET `release_date` = %s WHERE `id` IN $id_list", $now));
133
+
134
+ if (NULL != $result) {
135
+ AIOWPSecurity_Admin_Menu::show_msg_updated_st(__('The selected IP entries were unlocked successfully!', 'all-in-one-wp-security-and-firewall'));
136
+ }
137
+ }
138
+ } elseif (NULL != $entries) {
139
+ if (!isset($_GET['aiowps_nonce']) || !wp_verify_nonce($_GET['aiowps_nonce'], 'unlock_ip')) {
140
+ $aio_wp_security->debug_logger->log_debug('Nonce check failed for unlock IP operation.', 4);
141
+ die(__('Nonce check failed for unlock IP operation!', 'all-in-one-wp-security-and-firewall'));
142
+ }
143
+
144
+ // Unlock single record
145
+ $result = $wpdb->query($wpdb->prepare("UPDATE $lockdown_table SET `release_date` = %s WHERE `id` = %d", $now, absint($entries)));
146
+
147
+ if (NULL != $result) {
148
+ AIOWPSecurity_Admin_Menu::show_msg_updated_st(__('The selected IP entry was unlocked successfully.', 'all-in-one-wp-security-and-firewall'));
149
+ }
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Deletes one or more records from the AIOWPSEC_TBL_LOGIN_LOCKDOWN table.
155
+ *
156
+ * @param Array|String|Integer - ids or a single id
157
+ *
158
+ * @return Void
159
+ */
160
+ public function delete_lockdown_records($entries) {
161
  global $wpdb, $aio_wp_security;
162
  $lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
163
  if (is_array($entries))
169
  $id_list = "(" .implode(",",$entries) .")"; //Create comma separate list for DB operation
170
  $delete_command = "DELETE FROM ".$lockdown_table." WHERE id IN ".$id_list;
171
  $result = $wpdb->query($delete_command);
172
+ if ($result) {
173
+ AIOWPSecurity_Admin_Menu::show_msg_record_deleted_st();
174
+ } else {
175
+ // Error on bulk delete
176
+ $aio_wp_security->debug_logger->log_debug('Database error occurred when deleting rows from Login Lockdown table. Database error: '.$wpdb->last_error, 4);
177
+ AIOWPSecurity_Admin_Menu::show_msg_record_not_deleted_st();
178
+ }
179
  }
180
  }
181
  elseif ($entries != NULL)
189
  //Delete single record
190
  $delete_command = "DELETE FROM ".$lockdown_table." WHERE id = '".absint($entries)."'";
191
  $result = $wpdb->query($delete_command);
192
+ if ($result) {
193
+ AIOWPSecurity_Admin_Menu::show_msg_record_deleted_st();
194
+ } elseif ($result === false) {
195
+ // Error on single delete
196
+ $aio_wp_security->debug_logger->log_debug('Database error occurred when deleting rows from Login Lockdown table. Database error: '.$wpdb->last_error, 4);
197
+ AIOWPSecurity_Admin_Menu::show_msg_record_not_deleted_st();
198
+ }
199
  }
200
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
+ /**
203
+ * Retrieves all items from AIOWPSEC_TBL_LOGIN_LOCKDOWN. It may paginate and then assigns to $this->items.
204
+ *
205
+ * @param Boolean $ignore_pagination - whether to not paginate
206
+ *
207
+ * @return Void
208
+ */
209
+ public function prepare_items($ignore_pagination = false) {
210
+ global $wpdb;
211
 
212
+ $lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
 
213
 
214
+ $this->process_bulk_action();
215
+
216
+ // How many records per page to show
217
+ $per_page = 100;
218
+ $columns = $this->get_columns();
219
+ $hidden = array();
220
+ $sortable = $this->get_sortable_columns();
221
+
222
+ $this->_column_headers = array($columns, $hidden, $sortable);
223
+
224
+ // Parameters that are going to be used to order the result
225
+ $orderby = isset($_GET['orderby']) ? sanitize_text_field(wp_unslash($_GET['orderby'])) : '';
226
+ $order = isset($_GET['order']) ? sanitize_text_field(wp_unslash($_GET['order'])) : '';
227
+
228
+ $orderby = !empty($orderby) ? esc_sql($orderby) : 'lockdown_date';
229
+ $order = !empty($order) ? esc_sql($order) : 'DESC';
230
+
231
+ $orderby = AIOWPSecurity_Utility::sanitize_value_by_array($orderby, $sortable);
232
+ $order = AIOWPSecurity_Utility::sanitize_value_by_array($order, array('DESC' => '1', 'ASC' => '1'));
233
+
234
+ $now = current_time('mysql', true);
235
+
236
+ $data = $wpdb->get_results($wpdb->prepare("SELECT * FROM $lockdown_table WHERE `release_date` > %s ORDER BY $orderby $order", $now), ARRAY_A);
237
+
238
+ if (!$ignore_pagination) {
239
+ $current_page = $this->get_pagenum();
240
+ $total_items = count($data);
241
+ $data = array_slice($data, (($current_page - 1) * $per_page), $per_page);
242
+ $this->set_pagination_args(array(
243
+ 'total_items' => $total_items, // WE have to calculate the total number of items
244
+ 'per_page' => $per_page, // WE have to determine how many items to show on a page
245
+ 'total_pages' => ceil($total_items / $per_page) // WE have to calculate the total number of pages
246
+ ));
247
+ }
248
+
249
+ foreach ($data as $index => $row) {
250
+ $data[$index]['lockdown_date'] = get_date_from_gmt(mysql2date('Y-m-d H:i:s', $row['lockdown_date']), $this->get_wp_date_time_format());
251
+ $data[$index]['release_date'] = get_date_from_gmt(mysql2date('Y-m-d H:i:s', $row['release_date']), $this->get_wp_date_time_format());
252
+ }
253
+
254
+ $this->items = $data;
255
+ }
256
+
257
+ }
admin/wp-security-list-logged-in-users.php CHANGED
@@ -112,7 +112,7 @@ class AIOWPSecurity_List_Logged_In_Users extends AIOWPSecurity_List_Table {
112
  global $wpdb;
113
  global $aio_wp_security;
114
 
115
- if (AIOWPSecurity_Utility::is_multisite_install()) {
116
  $current_blog_id = get_current_blog_id();
117
  $logged_in_users = AIOWPSecurity_User_Login::get_subsite_logged_in_users($current_blog_id);
118
  } else {
112
  global $wpdb;
113
  global $aio_wp_security;
114
 
115
+ if (is_multisite()) {
116
  $current_blog_id = get_current_blog_id();
117
  $logged_in_users = AIOWPSecurity_User_Login::get_subsite_logged_in_users($current_blog_id);
118
  } else {
admin/wp-security-list-login-fails.php CHANGED
@@ -21,17 +21,6 @@ class AIOWPSecurity_List_Login_Failed_Attempts extends AIOWPSecurity_List_Table
21
  return $item[$column_name];
22
  }
23
 
24
- /**
25
- * This method returns failed login date after being formatted according to the date_format and time_format options.
26
- *
27
- * @param Array $item - an array containing data for a single row of the failed logins table
28
- *
29
- * @return String returns formatted date-time string
30
- */
31
- protected function column_failed_login_date($item) {
32
- return get_date_from_gmt(mysql2date('Y-m-d H:i:s', $item['failed_login_date']), get_option('date_format').' '.get_option('time_format'));
33
- }
34
-
35
  function column_login_attempt_ip($item){
36
  $tab = strip_tags($_REQUEST['tab']);
37
  $delete_url = sprintf('admin.php?page=%s&tab=%s&action=%s&failed_login_id=%s', AIOWPSEC_USER_LOGIN_MENU_SLUG, $tab, 'delete_failed_login_rec', $item['id']);
@@ -50,7 +39,6 @@ class AIOWPSecurity_List_Login_Failed_Attempts extends AIOWPSecurity_List_Table
50
  );
51
  }
52
 
53
-
54
  function column_cb($item){
55
  return sprintf(
56
  '<input type="checkbox" name="%1$s[]" value="%2$s" />',
@@ -58,7 +46,7 @@ class AIOWPSecurity_List_Login_Failed_Attempts extends AIOWPSecurity_List_Table
58
  /*$2%s*/ $item['id'] //The value of the checkbox should be the record's id
59
  );
60
  }
61
-
62
  function get_columns(){
63
  $columns = array(
64
  'cb' => '<input type="checkbox" />', //Render a checkbox
@@ -103,15 +91,15 @@ class AIOWPSecurity_List_Login_Failed_Attempts extends AIOWPSecurity_List_Table
103
  }
104
  }
105
  }
106
-
107
-
108
-
109
- /*
110
- * This function will delete selected records from the "failed_logins" table.
111
- * The function accepts either an array of IDs or a single ID
112
- */
113
- function delete_login_failed_records($entries)
114
- {
115
  global $wpdb, $aio_wp_security;
116
  $failed_login_table = AIOWPSEC_TBL_FAILED_LOGINS;
117
  if (is_array($entries))
@@ -124,17 +112,13 @@ class AIOWPSecurity_List_Login_Failed_Attempts extends AIOWPSecurity_List_Table
124
  $id_list = "(" .implode(",",$entries) .")"; //Create comma separate list for DB operation
125
  $delete_command = "DELETE FROM ".$failed_login_table." WHERE ID IN ".$id_list;
126
  $result = $wpdb->query($delete_command);
127
- if($result !== false)
128
- {
129
- $redir_url = sprintf('admin.php?page=%s&tab=%s&bulk_count=%s', AIOWPSEC_USER_LOGIN_MENU_SLUG, $tab, count($entries));
130
- AIOWPSecurity_Utility::redirect_to_url($redir_url);
131
- } else {
132
- // error on bulk delete
133
- $aio_wp_security->debug_logger->log_debug("DB error: ".$wpdb->last_error,4);
134
- $redir_url = sprintf('admin.php?page=%s&tab=%s&bulk_error=%s', AIOWPSEC_USER_LOGIN_MENU_SLUG, $tab, 1);
135
- AIOWPSecurity_Utility::redirect_to_url($redir_url);
136
-
137
- }
138
  }
139
 
140
  } elseif ($entries != NULL)
@@ -148,59 +132,72 @@ class AIOWPSecurity_List_Login_Failed_Attempts extends AIOWPSecurity_List_Table
148
  //Delete single record
149
  $delete_command = "DELETE FROM ".$failed_login_table." WHERE ID = '".absint($entries)."'";
150
  $result = $wpdb->query($delete_command);
151
- if($result !== false)
152
- {
153
- $success_msg = '<div id="message" class="updated fade"><p><strong>';
154
- $success_msg .= __('The selected entry was deleted successfully!','all-in-one-wp-security-and-firewall');
155
- $success_msg .= '</strong></p></div>';
156
- _e($success_msg);
157
- }
158
  }
159
  }
160
-
161
- function prepare_items($ignore_pagination = false) {
162
- /**
163
- * First, lets decide how many records per page to show
164
- */
165
- $per_page = 100;
166
- $columns = $this->get_columns();
167
- $hidden = array();
168
- $sortable = $this->get_sortable_columns();
169
- $search = isset( $_REQUEST['s'] ) ? sanitize_text_field( $_REQUEST['s'] ) : '';
170
-
171
- $this->_column_headers = array($columns, $hidden, $sortable);
172
-
173
- $this->process_bulk_action();
174
-
175
- global $wpdb;
176
- $failed_logins_table_name = AIOWPSEC_TBL_FAILED_LOGINS;
177
-
178
- /* -- Ordering parameters -- */
179
- //Parameters that are going to be used to order the result
180
- isset($_GET["orderby"]) ? $orderby = strip_tags($_GET["orderby"]) : $orderby = '';
181
- isset($_GET["order"]) ? $order = strip_tags($_GET["order"]) : $order = '';
182
-
183
- $orderby = !empty($orderby) ? esc_sql($orderby) : 'failed_login_date';
184
- $order = !empty($order) ? esc_sql($order) : 'DESC';
185
-
186
- $orderby = AIOWPSecurity_Utility::sanitize_value_by_array($orderby, $sortable);
187
- $order = AIOWPSecurity_Utility::sanitize_value_by_array($order, array('DESC' => '1', 'ASC' => '1'));
188
- if(empty($search)) {
189
- $data = $wpdb->get_results("SELECT * FROM " . $failed_logins_table_name . " ORDER BY $orderby $order", ARRAY_A);
190
- } else {
191
- $data = $wpdb->get_results($wpdb->prepare("SELECT * FROM $failed_logins_table_name WHERE `user_login` LIKE '%%%s%%' OR `login_attempt_ip` LIKE '%%%s%%' ORDER BY $orderby $order", $search, $search), ARRAY_A);
192
- }
193
 
194
- if (!$ignore_pagination) {
195
- $current_page = $this->get_pagenum();
196
- $total_items = count($data);
197
- $data = array_slice($data, (($current_page - 1) * $per_page), $per_page);
198
- $this->set_pagination_args(array(
199
- 'total_items' => $total_items, //WE have to calculate the total number of items
200
- 'per_page' => $per_page, //WE have to determine how many items to show on a page
201
- 'total_pages' => ceil($total_items / $per_page) //WE have to calculate the total number of pages
202
- ));
203
- }
204
- $this->items = $data;
205
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  }
21
  return $item[$column_name];
22
  }
23
 
 
 
 
 
 
 
 
 
 
 
 
24
  function column_login_attempt_ip($item){
25
  $tab = strip_tags($_REQUEST['tab']);
26
  $delete_url = sprintf('admin.php?page=%s&tab=%s&action=%s&failed_login_id=%s', AIOWPSEC_USER_LOGIN_MENU_SLUG, $tab, 'delete_failed_login_rec', $item['id']);
39
  );
40
  }
41
 
 
42
  function column_cb($item){
43
  return sprintf(
44
  '<input type="checkbox" name="%1$s[]" value="%2$s" />',
46
  /*$2%s*/ $item['id'] //The value of the checkbox should be the record's id
47
  );
48
  }
49
+
50
  function get_columns(){
51
  $columns = array(
52
  'cb' => '<input type="checkbox" />', //Render a checkbox
91
  }
92
  }
93
  }
94
+
95
+ /**
96
+ * Deletes one or more records from the AIOWPSEC_TBL_FAILED_LOGINS table.
97
+ *
98
+ * @param Array|String|Integer - ids or a single id
99
+ *
100
+ * @return Void
101
+ */
102
+ public function delete_login_failed_records($entries) {
103
  global $wpdb, $aio_wp_security;
104
  $failed_login_table = AIOWPSEC_TBL_FAILED_LOGINS;
105
  if (is_array($entries))
112
  $id_list = "(" .implode(",",$entries) .")"; //Create comma separate list for DB operation
113
  $delete_command = "DELETE FROM ".$failed_login_table." WHERE ID IN ".$id_list;
114
  $result = $wpdb->query($delete_command);
115
+ if ($result) {
116
+ AIOWPSecurity_Admin_Menu::show_msg_record_deleted_st();
117
+ } else {
118
+ // Error on bulk delete
119
+ $aio_wp_security->debug_logger->log_debug('Database error occurred when deleting rows from Failed Logins table. Database error: '.$wpdb->last_error, 4);
120
+ AIOWPSecurity_Admin_Menu::show_msg_record_not_deleted_st();
121
+ }
 
 
 
 
122
  }
123
 
124
  } elseif ($entries != NULL)
132
  //Delete single record
133
  $delete_command = "DELETE FROM ".$failed_login_table." WHERE ID = '".absint($entries)."'";
134
  $result = $wpdb->query($delete_command);
135
+ if ($result) {
136
+ AIOWPSecurity_Admin_Menu::show_msg_record_deleted_st();
137
+ } elseif ($result === false) {
138
+ // Error on single delete
139
+ $aio_wp_security->debug_logger->log_debug('Database error occurred when deleting rows from Failed Logins table. Database error: '.$wpdb->last_error, 4);
140
+ AIOWPSecurity_Admin_Menu::show_msg_record_not_deleted_st();
141
+ }
142
  }
143
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
+ /**
146
+ * Retrieves all items from AIOWPSEC_TBL_FAILED_LOGINS according to a search term inside $_REQUEST['s']. It then assigns to $this->items.
147
+ *
148
+ * @param Boolean $ignore_pagination - whether to not paginate
149
+ *
150
+ * @return Void
151
+ */
152
+ public function prepare_items($ignore_pagination = false) {
153
+ /**
154
+ * First, lets decide how many records per page to show
155
+ */
156
+ $per_page = 100;
157
+ $columns = $this->get_columns();
158
+ $hidden = array();
159
+ $sortable = $this->get_sortable_columns();
160
+ $search_term = isset($_REQUEST['s']) ? sanitize_text_field(stripslashes($_REQUEST['s'])) : '';
161
+
162
+ $this->_column_headers = array($columns, $hidden, $sortable);
163
+
164
+ $this->process_bulk_action();
165
+
166
+ global $wpdb;
167
+ $failed_logins_table_name = AIOWPSEC_TBL_FAILED_LOGINS;
168
+
169
+ /* -- Ordering parameters -- */
170
+ //Parameters that are going to be used to order the result
171
+ isset($_GET['orderby']) ? $orderby = strip_tags($_GET['orderby']) : $orderby = '';
172
+ isset($_GET['order']) ? $order = strip_tags($_GET['order']) : $order = '';
173
+
174
+ $orderby = !empty($orderby) ? esc_sql($orderby) : 'failed_login_date';
175
+ $order = !empty($order) ? esc_sql($order) : 'DESC';
176
+
177
+ $orderby = AIOWPSecurity_Utility::sanitize_value_by_array($orderby, $sortable);
178
+ $order = AIOWPSecurity_Utility::sanitize_value_by_array($order, array('DESC' => '1', 'ASC' => '1'));
179
+ if(empty($search_term)) {
180
+ $data = $wpdb->get_results("SELECT * FROM $failed_logins_table_name ORDER BY $orderby $order", ARRAY_A);
181
+ } else {
182
+ $data = $wpdb->get_results($wpdb->prepare("SELECT * FROM $failed_logins_table_name WHERE `user_login` LIKE '%%%s%%' OR `login_attempt_ip` LIKE '%%%s%%' ORDER BY $orderby $order", $search_term, $search_term), ARRAY_A);
183
+ }
184
+
185
+ if (!$ignore_pagination) {
186
+ $current_page = $this->get_pagenum();
187
+ $total_items = count($data);
188
+ $data = array_slice($data, (($current_page - 1) * $per_page), $per_page);
189
+ $this->set_pagination_args(array(
190
+ 'total_items' => $total_items, //WE have to calculate the total number of items
191
+ 'per_page' => $per_page, //WE have to determine how many items to show on a page
192
+ 'total_pages' => ceil($total_items / $per_page) //WE have to calculate the total number of pages
193
+ ));
194
+ }
195
+
196
+ foreach ($data as $index => $row) {
197
+ $data[$index]['failed_login_date'] = get_date_from_gmt(mysql2date('Y-m-d H:i:s', $row['failed_login_date']), $this->get_wp_date_time_format());
198
+ }
199
+
200
+ $this->items = $data;
201
+ }
202
+
203
  }
admin/wp-security-list-permanent-blocked-ip.php CHANGED
@@ -100,13 +100,14 @@ class AIOWPSecurity_List_Blocked_IP extends AIOWPSecurity_List_Table
100
  }
101
  }
102
 
103
-
104
- /*
105
- * This function will delete selected records from the "AIOWPSEC_TBL_PERM_BLOCK" table.
106
- * The function accepts either an array of IDs or a single ID
107
- */
108
- function unblock_ip_address($entries)
109
- {
 
110
  global $wpdb, $aio_wp_security;
111
  if (is_array($entries)) {
112
  if (isset($_REQUEST['_wp_http_referer'])) {
@@ -117,17 +118,13 @@ class AIOWPSecurity_List_Blocked_IP extends AIOWPSecurity_List_Table
117
  $id_list = "(" . implode(",", $entries) . ")"; //Create comma separate list for DB operation
118
  $delete_command = "DELETE FROM " . AIOWPSEC_TBL_PERM_BLOCK . " WHERE id IN " . $id_list;
119
  $result = $wpdb->query($delete_command);
120
- if($result !== false)
121
- {
122
- $redir_url = sprintf('admin.php?page=%s&tab=%s&bulk_count=%s', AIOWPSEC_MAIN_MENU_SLUG, $tab, count($entries));
123
- AIOWPSecurity_Utility::redirect_to_url($redir_url);
124
- } else {
125
- // error on bulk delete
126
- $aio_wp_security->debug_logger->log_debug("DB error: ".$wpdb->last_error,4);
127
- $redir_url = sprintf('admin.php?page=%s&tab=%s&bulk_error=%s', AIOWPSEC_MAIN_MENU_SLUG, $tab, 1);
128
- AIOWPSecurity_Utility::redirect_to_url($redir_url);
129
-
130
- }
131
  }
132
  } elseif ($entries != NULL) {
133
  $nonce = isset($_GET['aiowps_nonce']) ? $_GET['aiowps_nonce'] : '';
@@ -138,9 +135,13 @@ class AIOWPSecurity_List_Blocked_IP extends AIOWPSecurity_List_Table
138
  //Delete single record
139
  $delete_command = "DELETE FROM " . AIOWPSEC_TBL_PERM_BLOCK . " WHERE id = '" . absint($entries) . "'";
140
  $result = $wpdb->query($delete_command);
141
- if ($result !== false) {
142
- AIOWPSecurity_Admin_Menu::show_msg_record_deleted_st();
143
- }
 
 
 
 
144
  }
145
  }
146
 
100
  }
101
  }
102
 
103
+ /**
104
+ * Deletes one or more records from the AIOWPSEC_TBL_PERM_BLOCK table.
105
+ *
106
+ * @param Array|String|Integer - ids or a single id
107
+ *
108
+ * @return Void
109
+ */
110
+ public function unblock_ip_address($entries) {
111
  global $wpdb, $aio_wp_security;
112
  if (is_array($entries)) {
113
  if (isset($_REQUEST['_wp_http_referer'])) {
118
  $id_list = "(" . implode(",", $entries) . ")"; //Create comma separate list for DB operation
119
  $delete_command = "DELETE FROM " . AIOWPSEC_TBL_PERM_BLOCK . " WHERE id IN " . $id_list;
120
  $result = $wpdb->query($delete_command);
121
+ if ($result) {
122
+ AIOWPSecurity_Admin_Menu::show_msg_updated_st(__('Successfully unblocked and deleted the selected record(s).', 'all-in-one-wp-security-and-firewall'));
123
+ } else {
124
+ // Error on bulk delete
125
+ $aio_wp_security->debug_logger->log_debug('Database error occurred when deleting rows from Perm Block table. Database error: '.$wpdb->last_error, 4);
126
+ AIOWPSecurity_Admin_Menu::show_msg_error_st(__('Failed to unblock and delete the selected record(s).', 'all-in-one-wp-security-and-firewall'));
127
+ }
 
 
 
 
128
  }
129
  } elseif ($entries != NULL) {
130
  $nonce = isset($_GET['aiowps_nonce']) ? $_GET['aiowps_nonce'] : '';
135
  //Delete single record
136
  $delete_command = "DELETE FROM " . AIOWPSEC_TBL_PERM_BLOCK . " WHERE id = '" . absint($entries) . "'";
137
  $result = $wpdb->query($delete_command);
138
+ if ($result) {
139
+ AIOWPSecurity_Admin_Menu::show_msg_updated_st(__('Successfully unblocked and deleted the selected record(s).', 'all-in-one-wp-security-and-firewall'));
140
+ } elseif ($result === false) {
141
+ // Error on single delete
142
+ $aio_wp_security->debug_logger->log_debug('Database error occurred when deleting rows from Perm Block table. Database error: '.$wpdb->last_error, 4);
143
+ AIOWPSecurity_Admin_Menu::show_msg_error_st(__('Failed to unblock and delete the selected record(s).', 'all-in-one-wp-security-and-firewall'));
144
+ }
145
  }
146
  }
147
 
admin/wp-security-maintenance-menu.php CHANGED
@@ -103,12 +103,12 @@ class AIOWPSecurity_Maintenance_Menu extends AIOWPSecurity_Admin_Menu
103
  <tr valign="top">
104
  <th scope="row"><?php _e('Enable Front-end Lockout', 'all-in-one-wp-security-and-firewall')?>:</th>
105
  <td>
106
- <input name="aiowps_site_lockout" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_site_lockout')=='1') echo ' checked="checked"'; ?> value="1"/>
107
- <span class="description"><?php _e('Check this if you want all visitors except those who are logged in as administrator to be locked out of the front-end of your site.', 'all-in-one-wp-security-and-firewall'); ?></span>
108
  </td>
109
  </tr>
110
  <tr valign="top">
111
- <th scope="row"><?php _e('Enter a Message:', 'all-in-one-wp-security-and-firewall')?></th>
112
  <td>
113
  <?php
114
  $aiowps_site_lockout_msg_raw = $aio_wp_security->configs->get_value('aiowps_site_lockout_msg');
103
  <tr valign="top">
104
  <th scope="row"><?php _e('Enable Front-end Lockout', 'all-in-one-wp-security-and-firewall')?>:</th>
105
  <td>
106
+ <input id="aiowps_site_lockout" name="aiowps_site_lockout" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_site_lockout')=='1') echo ' checked="checked"'; ?> value="1"/>
107
+ <label for="aiowps_site_lockout" class="description"><?php _e('Check this if you want all visitors except those who are logged in as administrator to be locked out of the front-end of your site.', 'all-in-one-wp-security-and-firewall'); ?></label>
108
  </td>
109
  </tr>
110
  <tr valign="top">
111
+ <th scope="row"><label for="aiowps_site_lockout_msg_editor_content"><?php _e('Enter a Message:', 'all-in-one-wp-security-and-firewall')?></label></th>
112
  <td>
113
  <?php
114
  $aiowps_site_lockout_msg_raw = $aio_wp_security->configs->get_value('aiowps_site_lockout_msg');
admin/wp-security-misc-options-menu.php CHANGED
@@ -24,12 +24,12 @@ class AIOWPSecurity_Misc_Options_Menu extends AIOWPSecurity_Admin_Menu
24
 
25
  function set_menu_tabs()
26
  {
27
- $this->menu_tabs = array(
28
- 'tab1' => __('Copy Protection', 'all-in-one-wp-security-and-firewall'),
29
- 'tab2' => __('Frames', 'all-in-one-wp-security-and-firewall'),
30
- 'tab3' => __('Users Enumeration', 'all-in-one-wp-security-and-firewall'),
31
- 'tab4' => __('WP REST API', 'all-in-one-wp-security-and-firewall'),
32
- );
33
  }
34
 
35
  /*
@@ -105,8 +105,8 @@ class AIOWPSecurity_Misc_Options_Menu extends AIOWPSecurity_Admin_Menu
105
  <tr valign="top">
106
  <th scope="row"><?php _e('Enable Copy Protection', 'all-in-one-wp-security-and-firewall')?>:</th>
107
  <td>
108
- <input name="aiowps_copy_protection" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_copy_protection')=='1') echo ' checked="checked"'; ?> value="1"/>
109
- <span class="description"><?php _e('Check this if you want to disable the "Right Click", "Text Selection" and "Copy" option on the front end of your site.', 'all-in-one-wp-security-and-firewall'); ?></span>
110
  </td>
111
  </tr>
112
 
@@ -156,8 +156,8 @@ class AIOWPSecurity_Misc_Options_Menu extends AIOWPSecurity_Admin_Menu
156
  <tr valign="top">
157
  <th scope="row"><?php _e('Enable iFrame Protection', 'all-in-one-wp-security-and-firewall')?>:</th>
158
  <td>
159
- <input name="aiowps_prevent_site_display_inside_frame" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_prevent_site_display_inside_frame')=='1') echo ' checked="checked"'; ?> value="1"/>
160
- <span class="description"><?php _e('Check this if you want to stop other sites from displaying your content in a frame or iframe.', 'all-in-one-wp-security-and-firewall'); ?></span>
161
  </td>
162
  </tr>
163
 
@@ -207,8 +207,8 @@ class AIOWPSecurity_Misc_Options_Menu extends AIOWPSecurity_Admin_Menu
207
  <tr valign="top">
208
  <th scope="row"><?php _e('Disable Users Enumeration', 'all-in-one-wp-security-and-firewall')?>:</th>
209
  <td>
210
- <input name="aiowps_prevent_users_enumeration" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_prevent_users_enumeration')=='1') echo ' checked="checked"'; ?> value="1"/>
211
- <span class="description"><?php _e('Check this if you want to stop users enumeration.', 'all-in-one-wp-security-and-firewall'); ?></span>
212
  </td>
213
  </tr>
214
 
@@ -267,8 +267,8 @@ class AIOWPSecurity_Misc_Options_Menu extends AIOWPSecurity_Admin_Menu
267
  <tr valign="top">
268
  <th scope="row"><?php _e('Disallow Unauthorized REST Requests', 'all-in-one-wp-security-and-firewall')?>:</th>
269
  <td>
270
- <input name="aiowps_disallow_unauthorized_rest_requests" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_disallow_unauthorized_rest_requests')=='1') echo ' checked="checked"'; ?> value="1"/>
271
- <span class="description"><?php _e('Check this if you want to stop REST API access for non-logged in requests.', 'all-in-one-wp-security-and-firewall'); ?></span>
272
  </td>
273
  </tr>
274
 
@@ -281,5 +281,5 @@ class AIOWPSecurity_Misc_Options_Menu extends AIOWPSecurity_Admin_Menu
281
  </div></div>
282
  <?php
283
  }
284
-
285
  } //end class
24
 
25
  function set_menu_tabs()
26
  {
27
+ $this->menu_tabs = array(
28
+ 'tab1' => __('Copy Protection', 'all-in-one-wp-security-and-firewall'),
29
+ 'tab2' => __('Frames', 'all-in-one-wp-security-and-firewall'),
30
+ 'tab3' => __('Users Enumeration', 'all-in-one-wp-security-and-firewall'),
31
+ 'tab4' => __('WP REST API', 'all-in-one-wp-security-and-firewall'),
32
+ );
33
  }
34
 
35
  /*
105
  <tr valign="top">
106
  <th scope="row"><?php _e('Enable Copy Protection', 'all-in-one-wp-security-and-firewall')?>:</th>
107
  <td>
108
+ <input id="aiowps_copy_protection" name="aiowps_copy_protection" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_copy_protection')=='1') echo ' checked="checked"'; ?> value="1"/>
109
+ <label for="aiowps_copy_protection" class="description"><?php _e('Check this if you want to disable the "Right Click", "Text Selection" and "Copy" option on the front end of your site.', 'all-in-one-wp-security-and-firewall'); ?></label>
110
  </td>
111
  </tr>
112
 
156
  <tr valign="top">
157
  <th scope="row"><?php _e('Enable iFrame Protection', 'all-in-one-wp-security-and-firewall')?>:</th>
158
  <td>
159
+ <input id="aiowps_prevent_site_display_inside_frame" name="aiowps_prevent_site_display_inside_frame" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_prevent_site_display_inside_frame')=='1') echo ' checked="checked"'; ?> value="1"/>
160
+ <label for="aiowps_prevent_site_display_inside_frame" class="description"><?php _e('Check this if you want to stop other sites from displaying your content in a frame or iframe.', 'all-in-one-wp-security-and-firewall'); ?></label>
161
  </td>
162
  </tr>
163
 
207
  <tr valign="top">
208
  <th scope="row"><?php _e('Disable Users Enumeration', 'all-in-one-wp-security-and-firewall')?>:</th>
209
  <td>
210
+ <input id="aiowps_prevent_users_enumeration" name="aiowps_prevent_users_enumeration" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_prevent_users_enumeration')=='1') echo ' checked="checked"'; ?> value="1"/>
211
+ <label for="aiowps_prevent_users_enumeration" class="description"><?php _e('Check this if you want to stop users enumeration.', 'all-in-one-wp-security-and-firewall'); ?></label>
212
  </td>
213
  </tr>
214
 
267
  <tr valign="top">
268
  <th scope="row"><?php _e('Disallow Unauthorized REST Requests', 'all-in-one-wp-security-and-firewall')?>:</th>
269
  <td>
270
+ <input id="aiowps_disallow_unauthorized_rest_requests" name="aiowps_disallow_unauthorized_rest_requests" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_disallow_unauthorized_rest_requests')=='1') echo ' checked="checked"'; ?> value="1"/>
271
+ <label for="aiowps_disallow_unauthorized_rest_requests" class="description"><?php _e('Check this if you want to stop REST API access for non-logged in requests.', 'all-in-one-wp-security-and-firewall'); ?></label>
272
  </td>
273
  </tr>
274
 
281
  </div></div>
282
  <?php
283
  }
284
+
285
  } //end class
admin/wp-security-settings-menu.php CHANGED
@@ -15,7 +15,7 @@ class AIOWPSecurity_Settings_Menu extends AIOWPSecurity_Admin_Menu
15
  }
16
 
17
  public function set_menu_tabs() {
18
- $this->menu_tabs = apply_filters('aiowpsecurity_setting_tabs',
19
  array(
20
  'tab1' => array(
21
  'title' => __('General Settings', 'all-in-one-wp-security-and-firewall'),
@@ -28,6 +28,10 @@ class AIOWPSecurity_Settings_Menu extends AIOWPSecurity_Admin_Menu
28
  'tab3' => array(
29
  'title' => 'wp-config.php '.__('File', 'all-in-one-wp-security-and-firewall'),
30
  'render_callback' => array($this, 'render_tab3'),
 
 
 
 
31
  ),
32
  'tab4' => array(
33
  'title' => __('WP Version Info', 'all-in-one-wp-security-and-firewall'),
@@ -39,6 +43,7 @@ class AIOWPSecurity_Settings_Menu extends AIOWPSecurity_Admin_Menu
39
  ),
40
  )
41
  );
 
42
  }
43
 
44
  /*
@@ -55,6 +60,20 @@ class AIOWPSecurity_Settings_Menu extends AIOWPSecurity_Admin_Menu
55
  echo '</h2>';
56
  }
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  /*
59
  * The menu rendering goes here
60
  */
@@ -276,8 +295,8 @@ class AIOWPSecurity_Settings_Menu extends AIOWPSecurity_Admin_Menu
276
  <tr valign="top">
277
  <th scope="row"><?php _e('Enable Debug', 'all-in-one-wp-security-and-firewall')?>:</th>
278
  <td>
279
- <input name="aiowps_enable_debug" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_debug')=='1') echo ' checked="checked"'; ?> value="1"/>
280
- <span class="description"><?php _e('Check this if you want to enable debug. You should keep this option disabled after you have finished debugging the issue.', 'all-in-one-wp-security-and-firewall'); ?></span>
281
  </td>
282
  </tr>
283
  </table>
@@ -288,12 +307,15 @@ class AIOWPSecurity_Settings_Menu extends AIOWPSecurity_Admin_Menu
288
  <?php
289
  }
290
 
291
- function render_tab2()
292
- {
 
 
 
 
293
  global $aio_wp_security;
294
 
295
- if ( !function_exists( 'get_home_path' ) ) require_once( ABSPATH. '/wp-admin/includes/file.php' );
296
- $home_path = get_home_path();
297
  $htaccess_path = $home_path . '.htaccess';
298
 
299
  if(isset($_POST['aiowps_save_htaccess']))//Do form submission tasks
@@ -383,7 +405,7 @@ class AIOWPSecurity_Settings_Menu extends AIOWPSecurity_Admin_Menu
383
  </div>
384
  <?php
385
  $blog_id = get_current_blog_id();
386
- if (AIOWPSecurity_Utility::is_multisite_install() && !is_main_site( $blog_id ))
387
  {
388
  //Hide config settings if MS and not main site
389
  AIOWPSecurity_Utility::display_multisite_message();
@@ -407,7 +429,7 @@ class AIOWPSecurity_Settings_Menu extends AIOWPSecurity_Admin_Menu
407
  <?php wp_nonce_field('aiowpsec-restore-htaccess-nonce'); ?>
408
  <table class="form-table">
409
  <tr valign="top">
410
- <th scope="row"><?php _e('.htaccess file to restore from', 'all-in-one-wp-security-and-firewall')?>:</th>
411
  <td>
412
  <input type="button" id="aiowps_htaccess_file_button" name="aiowps_htaccess_file_button" class="button rbutton" value="<?php _e('Select Your htaccess File', 'all-in-one-wp-security-and-firewall'); ?>" />
413
  <input name="aiowps_htaccess_file" type="text" id="aiowps_htaccess_file" value="" size="80" />
@@ -484,7 +506,7 @@ class AIOWPSecurity_Settings_Menu extends AIOWPSecurity_Admin_Menu
484
  </div>
485
  <?php
486
  $blog_id = get_current_blog_id();
487
- if (AIOWPSecurity_Utility::is_multisite_install() && !is_main_site( $blog_id ))
488
  {
489
  //Hide config settings if MS and not main site
490
  AIOWPSecurity_Utility::display_multisite_message();
@@ -509,7 +531,7 @@ class AIOWPSecurity_Settings_Menu extends AIOWPSecurity_Admin_Menu
509
  <?php wp_nonce_field('aiowpsec-restore-wp-config-nonce'); ?>
510
  <table class="form-table">
511
  <tr valign="top">
512
- <th scope="row"><?php _e('wp-config file to restore from', 'all-in-one-wp-security-and-firewall')?>:</th>
513
  <td>
514
  <input type="button" id="aiowps_wp_config_file_button" name="aiowps_wp_config_file_button" class="button rbutton" value="<?php _e('Select Your wp-config File', 'all-in-one-wp-security-and-firewall'); ?>" />
515
  <input name="aiowps_wp_config_file" type="text" id="aiowps_wp_config_file" value="" size="80" />
@@ -538,8 +560,59 @@ class AIOWPSecurity_Settings_Menu extends AIOWPSecurity_Admin_Menu
538
  } //End if statement
539
  }
540
 
541
- function render_tab4()
542
- {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
543
  global $aio_wp_security;
544
  global $aiowps_feature_mgr;
545
 
@@ -558,7 +631,7 @@ class AIOWPSecurity_Settings_Menu extends AIOWPSecurity_Admin_Menu
558
  $aiowps_feature_mgr->check_feature_status_and_recalculate_points();
559
 
560
  $this->show_msg_settings_updated();
561
- }
562
  ?>
563
  <h2><?php _e('WP Generator Meta Tag & Version Info', 'all-in-one-wp-security-and-firewall')?></h2>
564
  <div class="aio_blue_box">
@@ -588,8 +661,8 @@ class AIOWPSecurity_Settings_Menu extends AIOWPSecurity_Admin_Menu
588
  <tr valign="top">
589
  <th scope="row"><?php _e('Remove WP Generator Meta Info', 'all-in-one-wp-security-and-firewall')?>:</th>
590
  <td>
591
- <input name="aiowps_remove_wp_generator_meta_info" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_remove_wp_generator_meta_info')=='1') echo ' checked="checked"'; ?> value="1"/>
592
- <span class="description"><?php _e('Check this if you want to remove the version and meta info produced by WP from all pages', 'all-in-one-wp-security-and-firewall'); ?></span>
593
  </td>
594
  </tr>
595
  </table>
@@ -599,7 +672,6 @@ class AIOWPSecurity_Settings_Menu extends AIOWPSecurity_Admin_Menu
599
  <?php
600
  }
601
 
602
-
603
  public function render_tab5() {
604
  global $aio_wp_security;
605
 
@@ -723,48 +795,54 @@ class AIOWPSecurity_Settings_Menu extends AIOWPSecurity_Admin_Menu
723
  </div>
724
 
725
  <div class="postbox">
726
- <h3 class="hndle"><label for="title"><?php _e('Export AIOWPS Settings', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
727
- <div class="inside">
728
- <form action="" method="POST">
729
- <?php wp_nonce_field('aiowpsec-export-settings-nonce'); ?>
730
- <table class="form-table">
731
- <tr valign="top">
732
- <span class="description"><?php _e('To export your All In One WP Security & Firewall settings click the button below.', 'all-in-one-wp-security-and-firewall'); ?></span>
733
- </tr>
734
- </table>
735
- <input type="submit" name="aiowps_export_settings" value="<?php _e('Export AIOWPS Settings', 'all-in-one-wp-security-and-firewall')?>" class="button-primary" />
736
- </form>
737
- </div></div>
738
  <div class="postbox">
739
- <h3 class="hndle"><label for="title"><?php _e('Import AIOWPS Settings', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
740
- <div class="inside">
741
- <form action="" method="POST">
742
- <?php wp_nonce_field('aiowpsec-import-settings-nonce'); ?>
743
- <table class="form-table">
744
- <tr valign="top">
745
- <span class="description"><?php _e('Use this section to import your All In One WP Security & Firewall settings from a file. Alternatively, copy/paste the contents of your import file into the textarea below.', 'all-in-one-wp-security-and-firewall'); ?></span>
746
- <th scope="row"><?php _e('Import File', 'all-in-one-wp-security-and-firewall')?>:</th>
747
- <td>
748
- <input type="button" id="aiowps_import_settings_file_button" name="aiowps_import_settings_file_button" class="button rbutton" value="<?php _e('Select Your Import Settings File', 'all-in-one-wp-security-and-firewall'); ?>" />
749
- <input name="aiowps_import_settings_file" type="text" id="aiowps_import_settings_file" value="" size="80" />
750
- <p class="description">
751
- <?php
752
- _e('After selecting your file, click the button below to apply the settings to your site.', 'all-in-one-wp-security-and-firewall');
753
- ?>
754
- </p>
755
- </td>
756
- </tr>
757
- <tr valign="top">
758
- <th scope="row"><?php _e('Copy/Paste Import Data', 'all-in-one-wp-security-and-firewall')?>:</th>
759
- <td>
760
- <textarea name="aiowps_import_settings_text" id="aiowps_import_settings_text" style="width:80%;height:140px;"></textarea>
761
- </td>
762
- </tr>
763
- </table>
764
- <input type="submit" name="aiowps_import_settings" value="<?php _e('Import AIOWPS Settings', 'all-in-one-wp-security-and-firewall')?>" class="button-primary" />
765
- </form>
766
- </div></div>
767
- <?php
 
 
 
 
 
 
768
  }
769
 
770
  function check_if_wp_config_contents($wp_file)
15
  }
16
 
17
  public function set_menu_tabs() {
18
+ $menu_tabs = apply_filters('aiowpsecurity_setting_tabs',
19
  array(
20
  'tab1' => array(
21
  'title' => __('General Settings', 'all-in-one-wp-security-and-firewall'),
28
  'tab3' => array(
29
  'title' => 'wp-config.php '.__('File', 'all-in-one-wp-security-and-firewall'),
30
  'render_callback' => array($this, 'render_tab3'),
31
+ ),
32
+ 'delete-plugin-settings' => array(
33
+ 'title' => __('Delete Plugin Settings', 'all-in-one-wp-security-and-firewall'),
34
+ 'render_callback' => array($this, 'render_delete_plugin_settings_tab'),
35
  ),
36
  'tab4' => array(
37
  'title' => __('WP Version Info', 'all-in-one-wp-security-and-firewall'),
43
  ),
44
  )
45
  );
46
+ $this->menu_tabs = array_filter($menu_tabs, array($this, 'should_display_tab'));
47
  }
48
 
49
  /*
60
  echo '</h2>';
61
  }
62
 
63
+ /**
64
+ * Decide whether to display the tab for the given tab information.
65
+ *
66
+ * @param array $tab_info tab information array cotaining element keys like title, render_callback and display_condition_callback etc..
67
+ * @return boolean The tab information array contains element keys such as title, render_callback, and display_condition_callback, among others.
68
+ */
69
+ private function should_display_tab($tab_info) {
70
+ if (!empty($tab_info['display_condition_callback']) && is_callable($tab_info['display_condition_callback'])) {
71
+ return call_user_func($tab_info['display_condition_callback']);
72
+ } else {
73
+ return true;
74
+ }
75
+ }
76
+
77
  /*
78
  * The menu rendering goes here
79
  */
295
  <tr valign="top">
296
  <th scope="row"><?php _e('Enable Debug', 'all-in-one-wp-security-and-firewall')?>:</th>
297
  <td>
298
+ <input id="aiowps_enable_debug" name="aiowps_enable_debug" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_debug')=='1') echo ' checked="checked"'; ?> value="1"/>
299
+ <label for="aiowps_enable_debug" class="description"><?php _e('Check this if you want to enable debug. You should keep this option disabled after you have finished debugging the issue.', 'all-in-one-wp-security-and-firewall'); ?></label>
300
  </td>
301
  </tr>
302
  </table>
307
  <?php
308
  }
309
 
310
+ /**
311
+ * Render tab 2 content.
312
+ *
313
+ * @return void
314
+ */
315
+ private function render_tab2() {
316
  global $aio_wp_security;
317
 
318
+ $home_path = AIOWPSecurity_Utility_File::get_home_path();
 
319
  $htaccess_path = $home_path . '.htaccess';
320
 
321
  if(isset($_POST['aiowps_save_htaccess']))//Do form submission tasks
405
  </div>
406
  <?php
407
  $blog_id = get_current_blog_id();
408
+ if (is_multisite() && !is_main_site( $blog_id ))
409
  {
410
  //Hide config settings if MS and not main site
411
  AIOWPSecurity_Utility::display_multisite_message();
429
  <?php wp_nonce_field('aiowpsec-restore-htaccess-nonce'); ?>
430
  <table class="form-table">
431
  <tr valign="top">
432
+ <th scope="row"><label for="aiowps_htaccess_file_button"><?php _e('.htaccess file to restore from', 'all-in-one-wp-security-and-firewall')?></label>:</th>
433
  <td>
434
  <input type="button" id="aiowps_htaccess_file_button" name="aiowps_htaccess_file_button" class="button rbutton" value="<?php _e('Select Your htaccess File', 'all-in-one-wp-security-and-firewall'); ?>" />
435
  <input name="aiowps_htaccess_file" type="text" id="aiowps_htaccess_file" value="" size="80" />
506
  </div>
507
  <?php
508
  $blog_id = get_current_blog_id();
509
+ if (is_multisite() && !is_main_site( $blog_id ))
510
  {
511
  //Hide config settings if MS and not main site
512
  AIOWPSecurity_Utility::display_multisite_message();
531
  <?php wp_nonce_field('aiowpsec-restore-wp-config-nonce'); ?>
532
  <table class="form-table">
533
  <tr valign="top">
534
+ <th scope="row"><label for="aiowps_wp_config_file_button"><?php _e('wp-config file to restore from', 'all-in-one-wp-security-and-firewall')?></label>:</th>
535
  <td>
536
  <input type="button" id="aiowps_wp_config_file_button" name="aiowps_wp_config_file_button" class="button rbutton" value="<?php _e('Select Your wp-config File', 'all-in-one-wp-security-and-firewall'); ?>" />
537
  <input name="aiowps_wp_config_file" type="text" id="aiowps_wp_config_file" value="" size="80" />
560
  } //End if statement
561
  }
562
 
563
+ public function render_delete_plugin_settings_tab() {
564
+ global $aio_wp_security;
565
+
566
+ if (isset($_POST['aiowpsec_save_delete_plugin_settings']))
567
+ {
568
+ $nonce=$_REQUEST['_wpnonce'];
569
+ if (!wp_verify_nonce($nonce, 'aiowpsec-delete-plugin-settings'))
570
+ {
571
+ $aio_wp_security->debug_logger->log_debug("Nonce check failed on manage delete plugin settings save.",4);
572
+ die("Nonce check failed on manage delete plugin settings save.");
573
+ }
574
+
575
+ //Save settings
576
+ $aio_wp_security->configs->set_value('aiowps_on_uninstall_delete_db_tables', isset($_POST['aiowps_on_uninstall_delete_db_tables']) ? '1' : '');
577
+ $aio_wp_security->configs->set_value('aiowps_on_uninstall_delete_configs', isset($_POST['aiowps_on_uninstall_delete_configs']) ? '1' : '');
578
+ $aio_wp_security->configs->save_config();
579
+
580
+ $this->show_msg_updated(__('Manage delete plugin settings saved.', 'all-in-one-wp-security-and-firewall'));
581
+
582
+ }
583
+ ?>
584
+ <div class="postbox">
585
+ <h3 class="hndle"><label for="title"><?php _e('Manage delete plugin tasks', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
586
+ <div class="inside">
587
+ <form action="" method="POST">
588
+ <?php wp_nonce_field('aiowpsec-delete-plugin-settings'); ?>
589
+
590
+ <table class="form-table">
591
+ <tr valign="top">
592
+ <th scope="row"><?php _e('Delete database tables', 'all-in-one-wp-security-and-firewall')?>:</th>
593
+ <td>
594
+ <input id="aiowps_on_uninstall_delete_db_tables" name="aiowps_on_uninstall_delete_db_tables" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_on_uninstall_delete_db_tables')=='1') echo ' checked="checked"'; ?> value="1"/>
595
+ <label for="aiowps_on_uninstall_delete_db_tables" class="description"><?php _e('Check this if you want to remove database tables when the plugin is deactivated.', 'all-in-one-wp-security-and-firewall'); ?></label>
596
+ </td>
597
+ </tr>
598
+ <tr valign="top">
599
+ <th scope="row"><?php _e('Delete settings', 'all-in-one-wp-security-and-firewall')?>:</th>
600
+ <td>
601
+ <input id="aiowps_on_uninstall_delete_configs" name="aiowps_on_uninstall_delete_configs" type="checkbox"<?php checked($aio_wp_security->configs->get_value('aiowps_on_uninstall_delete_configs'), '1'); ?> value="1"/>
602
+ <label for="aiowps_on_uninstall_delete_configs" class="description"><?php echo __('Check this if you want to remove all plugin settings when uninstalling the plugin.', 'all-in-one-wp-security-and-firewall').' '.__('It will also remove all custom htaccess rules that were added by this plugin.', 'all-in-one-wp-security-and-firewall'); ?></label>
603
+ </td>
604
+ </tr>
605
+ </table>
606
+
607
+ <div class="submit">
608
+ <input type="submit" class="button-primary" name="aiowpsec_save_delete_plugin_settings" value="<?php _e('Save Settings', 'all-in-one-wp-security-and-firewall'); ?>" />
609
+ </div>
610
+ </form>
611
+ </div></div>
612
+ <?php
613
+ }
614
+
615
+ public function render_tab4() {
616
  global $aio_wp_security;
617
  global $aiowps_feature_mgr;
618
 
631
  $aiowps_feature_mgr->check_feature_status_and_recalculate_points();
632
 
633
  $this->show_msg_settings_updated();
634
+ }
635
  ?>
636
  <h2><?php _e('WP Generator Meta Tag & Version Info', 'all-in-one-wp-security-and-firewall')?></h2>
637
  <div class="aio_blue_box">
661
  <tr valign="top">
662
  <th scope="row"><?php _e('Remove WP Generator Meta Info', 'all-in-one-wp-security-and-firewall')?>:</th>
663
  <td>
664
+ <input id="aiowps_remove_wp_generator_meta_info" name="aiowps_remove_wp_generator_meta_info" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_remove_wp_generator_meta_info')=='1') echo ' checked="checked"'; ?> value="1"/>
665
+ <label for="aiowps_remove_wp_generator_meta_info" class="description"><?php _e('Check this if you want to remove the version and meta info produced by WP from all pages', 'all-in-one-wp-security-and-firewall'); ?></label>
666
  </td>
667
  </tr>
668
  </table>
672
  <?php
673
  }
674
 
 
675
  public function render_tab5() {
676
  global $aio_wp_security;
677
 
795
  </div>
796
 
797
  <div class="postbox">
798
+ <h3 class="hndle"><label for="title"><?php _e('Export AIOWPS Settings', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
799
+ <div class="inside">
800
+ <form action="" method="POST">
801
+ <?php wp_nonce_field('aiowpsec-export-settings-nonce'); ?>
802
+ <table class="form-table">
803
+ <tr valign="top">
804
+ <span class="description"><?php _e('To export your All In One WP Security & Firewall settings click the button below.', 'all-in-one-wp-security-and-firewall'); ?></span>
805
+ </tr>
806
+ </table>
807
+ <input type="submit" name="aiowps_export_settings" value="<?php _e('Export AIOWPS Settings', 'all-in-one-wp-security-and-firewall')?>" class="button-primary" />
808
+ </form>
809
+ </div></div>
810
  <div class="postbox">
811
+ <h3 class="hndle"><label for="title"><?php _e('Import AIOWPS Settings', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
812
+ <div class="inside">
813
+ <form action="" method="POST">
814
+ <?php wp_nonce_field('aiowpsec-import-settings-nonce'); ?>
815
+ <table class="form-table">
816
+ <tr valign="top">
817
+ <span class="description"><?php _e('Use this section to import your All In One WP Security & Firewall settings from a file. Alternatively, copy/paste the contents of your import file into the textarea below.', 'all-in-one-wp-security-and-firewall'); ?></span>
818
+ <th scope="row">
819
+ <label for="aiowps_import_settings_file_button">
820
+ <?php _e('Import File', 'all-in-one-wp-security-and-firewall'); ?>:
821
+ </label>
822
+ </th>
823
+ <td>
824
+ <input type="button" id="aiowps_import_settings_file_button" name="aiowps_import_settings_file_button" class="button rbutton" value="<?php _e('Select Your Import Settings File', 'all-in-one-wp-security-and-firewall'); ?>" />
825
+ <input name="aiowps_import_settings_file" type="text" id="aiowps_import_settings_file" value="" size="80" />
826
+ <p class="description">
827
+ <?php
828
+ _e('After selecting your file, click the button below to apply the settings to your site.', 'all-in-one-wp-security-and-firewall');
829
+ ?>
830
+ </p>
831
+ </td>
832
+ </tr>
833
+ <tr valign="top">
834
+ <th scope="row">
835
+ <label for="aiowps_import_settings_text"><?php _e('Copy/Paste Import Data', 'all-in-one-wp-security-and-firewall'); ?>:</label>
836
+ </th>
837
+ <td>
838
+ <textarea name="aiowps_import_settings_text" id="aiowps_import_settings_text" style="width:80%;height:140px;"></textarea>
839
+ </td>
840
+ </tr>
841
+ </table>
842
+ <input type="submit" name="aiowps_import_settings" value="<?php _e('Import AIOWPS Settings', 'all-in-one-wp-security-and-firewall')?>" class="button-primary" />
843
+ </form>
844
+ </div></div>
845
+ <?php
846
  }
847
 
848
  function check_if_wp_config_contents($wp_file)
admin/wp-security-spam-menu.php CHANGED
@@ -89,9 +89,26 @@ class AIOWPSecurity_Spam_Menu extends AIOWPSecurity_Admin_Menu
89
  $aio_wp_security->configs->set_value('aiowps_enable_comment_captcha',isset($_POST["aiowps_enable_comment_captcha"])?'1':'');
90
  $aio_wp_security->configs->set_value('aiowps_enable_spambot_blocking',isset($_POST["aiowps_enable_spambot_blocking"])?'1':'');
91
 
92
- //Commit the config settings
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  $aio_wp_security->configs->save_config();
94
 
 
 
95
  //Recalculate points after the feature status/options have been altered
96
  $aiowps_feature_mgr->check_feature_status_and_recalculate_points();
97
 
@@ -130,8 +147,8 @@ class AIOWPSecurity_Spam_Menu extends AIOWPSecurity_Admin_Menu
130
  <tr valign="top">
131
  <th scope="row"><?php _e('Enable Captcha On Comment Forms', 'all-in-one-wp-security-and-firewall')?>:</th>
132
  <td>
133
- <input name="aiowps_enable_comment_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_comment_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
134
- <span class="description"><?php _e('Check this if you want to insert a captcha field on the comment forms', 'all-in-one-wp-security-and-firewall'); ?></span>
135
  </td>
136
  </tr>
137
  </table>
@@ -151,7 +168,7 @@ class AIOWPSecurity_Spam_Menu extends AIOWPSecurity_Admin_Menu
151
  //Display security info badge
152
  $aiowps_feature_mgr->output_feature_details_badge("block-spambots");
153
  $blog_id = get_current_blog_id();
154
- if (AIOWPSecurity_Utility::is_multisite_install() && !is_main_site( $blog_id ))
155
  {
156
  //Hide config settings if MS and not main site
157
  AIOWPSecurity_Utility::display_multisite_message();
@@ -163,8 +180,8 @@ class AIOWPSecurity_Spam_Menu extends AIOWPSecurity_Admin_Menu
163
  <tr valign="top">
164
  <th scope="row"><?php _e('Block Spambots From Posting Comments', 'all-in-one-wp-security-and-firewall')?>:</th>
165
  <td>
166
- <input name="aiowps_enable_spambot_blocking" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_spambot_blocking')=='1') echo ' checked="checked"'; ?> value="1"/>
167
- <span class="description"><?php _e('Check this if you want to apply a firewall rule which will block comments originating from spambots.', 'all-in-one-wp-security-and-firewall'); ?></span>
168
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
169
  <div class="aiowps_more_info_body">
170
  <?php
@@ -180,13 +197,51 @@ class AIOWPSecurity_Spam_Menu extends AIOWPSecurity_Admin_Menu
180
  <?php } //End if statement ?>
181
  </div></div>
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  <input type="submit" name="aiowps_apply_comment_spam_prevention_settings" value="<?php _e('Save Settings', 'all-in-one-wp-security-and-firewall')?>" class="button-primary" />
184
  </form>
185
  <?php
186
  }
187
-
188
- function render_tab2()
189
- {
 
 
 
 
190
  global $aio_wp_security;
191
  global $aiowps_feature_mgr;
192
  include_once 'wp-security-list-comment-spammer-ip.php'; //For rendering the AIOWPSecurity_List_Table in tab2
@@ -330,13 +385,13 @@ class AIOWPSecurity_Spam_Menu extends AIOWPSecurity_Admin_Menu
330
  <tr valign="top">
331
  <th scope="row"><?php _e('Enable Auto Block of SPAM Comment IPs', 'all-in-one-wp-security-and-firewall')?>:</th>
332
  <td>
333
- <input name="aiowps_enable_autoblock_spam_ip" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_autoblock_spam_ip')=='1') echo ' checked="checked"'; ?> value="1"/>
334
- <span class="description"><?php _e('Check this box if you want this plugin to automatically block IP addresses which submit SPAM comments.', 'all-in-one-wp-security-and-firewall'); ?></span>
335
  </td>
336
  </tr>
337
  <tr valign="top">
338
- <th scope="row"><?php _e('Minimum number of SPAM comments', 'all-in-one-wp-security-and-firewall')?>:</th>
339
- <td><input type="text" size="5" name="aiowps_spam_ip_min_comments_block" value="<?php echo $aio_wp_security->configs->get_value('aiowps_spam_ip_min_comments_block'); ?>" />
340
  <span class="description"><?php _e('Specify the minimum number of SPAM comments for an IP address before it is permanently blocked.', 'all-in-one-wp-security-and-firewall');?></span>
341
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
342
  <div class="aiowps_more_info_body">
@@ -377,8 +432,8 @@ class AIOWPSecurity_Spam_Menu extends AIOWPSecurity_Admin_Menu
377
  <?php wp_nonce_field('aiowpsec-spammer-ip-list-nonce'); ?>
378
  <table class="form-table">
379
  <tr valign="top">
380
- <th scope="row"><?php _e('Minimum number of SPAM comments per IP', 'all-in-one-wp-security-and-firewall')?>:</th>
381
- <td><input type="text" size="5" name="aiowps_spam_ip_min_comments" value="<?php echo $aio_wp_security->configs->get_value('aiowps_spam_ip_min_comments'); ?>" />
382
  <span class="description"><?php _e('This field allows you to list only those IP addresses which have been used to post X or more SPAM comments.', 'all-in-one-wp-security-and-firewall');?></span>
383
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
384
  <div class="aiowps_more_info_body">
@@ -398,7 +453,7 @@ class AIOWPSecurity_Spam_Menu extends AIOWPSecurity_Admin_Menu
398
  <h3 class="hndle"><label for="title"><?php _e('SPAMMER IP Address Results', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
399
  <div class="inside">
400
  <?php
401
- if (AIOWPSecurity_Utility::is_multisite_install() && get_current_blog_id() != 1)
402
  {
403
  echo '<div class="aio_yellow_box">';
404
  echo '<p>'.__('The plugin has detected that you are using a Multi-Site WordPress installation.', 'all-in-one-wp-security-and-firewall').'</p>
@@ -410,7 +465,7 @@ class AIOWPSecurity_Spam_Menu extends AIOWPSecurity_Admin_Menu
410
  $spammer_ip_list->prepare_items();
411
  //echo "put table of locked entries here";
412
  ?>
413
- <form id="tables-filter" method="get" onSubmit="return confirm('Are you sure you want to perform this bulk operation on the selected entries?');">
414
  <!-- For plugins, we also need to ensure that the form posts back to our current page -->
415
  <input type="hidden" name="page" value="<?php echo esc_attr($_REQUEST['page']); ?>" />
416
  <input type="hidden" name="tab" value="<?php echo esc_attr($_REQUEST['tab']); ?>" />
@@ -468,10 +523,10 @@ class AIOWPSecurity_Spam_Menu extends AIOWPSecurity_Admin_Menu
468
  ?>
469
  <table class="form-table">
470
  <tr valign="top">
471
- <th scope="row"><?php _e('Enable Captcha On BuddyPress Registration Form', 'all-in-one-wp-security-and-firewall')?>:</th>
472
  <td>
473
- <input name="aiowps_enable_bp_register_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_bp_register_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
474
- <span class="description"><?php _e('Check this if you want to insert a captcha field on the BuddyPress registration forms', 'all-in-one-wp-security-and-firewall'); ?></span>
475
  </td>
476
  </tr>
477
  </table>
@@ -530,10 +585,10 @@ class AIOWPSecurity_Spam_Menu extends AIOWPSecurity_Admin_Menu
530
  ?>
531
  <table class="form-table">
532
  <tr valign="top">
533
- <th scope="row"><?php _e('Enable Captcha On BBPress New Topic Form', 'all-in-one-wp-security-and-firewall')?>:</th>
534
  <td>
535
- <input name="aiowps_enable_bbp_new_topic_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_bbp_new_topic_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
536
- <span class="description"><?php _e('Check this if you want to insert a captcha field on the BBPress new topic forms', 'all-in-one-wp-security-and-firewall'); ?></span>
537
  </td>
538
  </tr>
539
  </table>
@@ -546,4 +601,4 @@ class AIOWPSecurity_Spam_Menu extends AIOWPSecurity_Admin_Menu
546
  }
547
  }
548
 
549
- } //end class
89
  $aio_wp_security->configs->set_value('aiowps_enable_comment_captcha',isset($_POST["aiowps_enable_comment_captcha"])?'1':'');
90
  $aio_wp_security->configs->set_value('aiowps_enable_spambot_blocking',isset($_POST["aiowps_enable_spambot_blocking"])?'1':'');
91
 
92
+ $aio_wp_security->configs->set_value('aiowps_enable_trash_spam_comments', isset($_POST['aiowps_enable_trash_spam_comments']) ? '1' : '');
93
+ $aiowps_trash_spam_comments_after_days = '';
94
+ if (isset($_POST['aiowps_trash_spam_comments_after_days'])) {
95
+ if (!empty($_POST['aiowps_trash_spam_comments_after_days'])) {
96
+ $aiowps_trash_spam_comments_after_days = sanitize_text_field($_POST['aiowps_trash_spam_comments_after_days']);
97
+ }
98
+ if (isset($_POST['aiowps_enable_trash_spam_comments']) && !is_numeric($aiowps_trash_spam_comments_after_days)) {
99
+ $error = __('You entered a non numeric value for the "move spam comments to trash after number of days" field.','all-in-one-wp-security-and-firewall').' '.__('It has been set to the default value.','all-in-one-wp-security-and-firewall');
100
+ $aiowps_trash_spam_comments_after_days = '14';//Set it to the default value for this field
101
+ $this->show_msg_error(__('Attention!','all-in-one-wp-security-and-firewall').'&nbsp;'.htmlspecialchars($error));
102
+ }
103
+ $aiowps_trash_spam_comments_after_days = absint($aiowps_trash_spam_comments_after_days);
104
+ $aio_wp_security->configs->set_value('aiowps_trash_spam_comments_after_days', $aiowps_trash_spam_comments_after_days);
105
+ }
106
+
107
+ //Commit the config settings
108
  $aio_wp_security->configs->save_config();
109
 
110
+ AIOWPSecurity_Comment::trash_spam_comments();
111
+
112
  //Recalculate points after the feature status/options have been altered
113
  $aiowps_feature_mgr->check_feature_status_and_recalculate_points();
114
 
147
  <tr valign="top">
148
  <th scope="row"><?php _e('Enable Captcha On Comment Forms', 'all-in-one-wp-security-and-firewall')?>:</th>
149
  <td>
150
+ <input id="aiowps_enable_comment_captcha" name="aiowps_enable_comment_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_comment_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
151
+ <label for="aiowps_enable_comment_captcha" class="description"><?php _e('Check this if you want to insert a captcha field on the comment forms', 'all-in-one-wp-security-and-firewall'); ?></label>
152
  </td>
153
  </tr>
154
  </table>
168
  //Display security info badge
169
  $aiowps_feature_mgr->output_feature_details_badge("block-spambots");
170
  $blog_id = get_current_blog_id();
171
+ if (is_multisite() && !is_main_site( $blog_id ))
172
  {
173
  //Hide config settings if MS and not main site
174
  AIOWPSecurity_Utility::display_multisite_message();
180
  <tr valign="top">
181
  <th scope="row"><?php _e('Block Spambots From Posting Comments', 'all-in-one-wp-security-and-firewall')?>:</th>
182
  <td>
183
+ <input id="aiowps_enable_spambot_blocking" name="aiowps_enable_spambot_blocking" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_spambot_blocking')=='1') echo ' checked="checked"'; ?> value="1"/>
184
+ <label for="aiowps_enable_spambot_blocking" class="description"><?php _e('Check this if you want to apply a firewall rule which will block comments originating from spambots.', 'all-in-one-wp-security-and-firewall'); ?></label>
185
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
186
  <div class="aiowps_more_info_body">
187
  <?php
197
  <?php } //End if statement ?>
198
  </div></div>
199
 
200
+ <div class="postbox">
201
+ <h3 class="hndle"><label for="title"><?php _e('Comment Processing', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
202
+ <div class="inside">
203
+ <table class="form-table">
204
+ <tr valign="top">
205
+ <th scope="row">
206
+ <label for="aiowps_trash_spam_comments_after_days">
207
+ <?php _e('Trash spam comments', 'all-in-one-wp-security-and-firewall'); ?>:
208
+ </label>
209
+ </th>
210
+ <td>
211
+ <input name="aiowps_enable_trash_spam_comments" id="aiowps_enable_trash_spam_comments" type="checkbox" <?php checked($aio_wp_security->configs->get_value('aiowps_enable_trash_spam_comments'), 1); ?> value="1"/>
212
+ <?php
213
+ $disbled = '';
214
+ if(!$aio_wp_security->configs->get_value('aiowps_enable_trash_spam_comments')) $disbled = "disabled";
215
+ echo '<label for="aiowps_enable_trash_spam_comments" class="description">';
216
+ printf(
217
+ __('Move spam comments to trash after %s days.', 'all-in-one-wp-security-and-firewall'),
218
+ '</label><input type="number" min="1" max="99" name="aiowps_trash_spam_comments_after_days" value="'.$aio_wp_security->configs->get_value('aiowps_trash_spam_comments_after_days').'" '.$disbled.'><label for="aiowps_enable_trash_spam_comments">'
219
+ );
220
+ echo '</label>';
221
+ ?>
222
+ <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
223
+ <div class="aiowps_more_info_body">
224
+ <?php
225
+ echo '<p class="description">'.__('Enble this feature in order to move the spam comments to trash after given number of days.', 'all-in-one-wp-security-and-firewall').'</p>';
226
+ ?>
227
+ </div>
228
+ </td>
229
+ </tr>
230
+ </table>
231
+ </div>
232
+ </div>
233
+
234
  <input type="submit" name="aiowps_apply_comment_spam_prevention_settings" value="<?php _e('Save Settings', 'all-in-one-wp-security-and-firewall')?>" class="button-primary" />
235
  </form>
236
  <?php
237
  }
238
+
239
+ /**
240
+ * Renders the submenu's tab2 tab body.
241
+ *
242
+ * @return Void
243
+ */
244
+ public function render_tab2() {
245
  global $aio_wp_security;
246
  global $aiowps_feature_mgr;
247
  include_once 'wp-security-list-comment-spammer-ip.php'; //For rendering the AIOWPSecurity_List_Table in tab2
385
  <tr valign="top">
386
  <th scope="row"><?php _e('Enable Auto Block of SPAM Comment IPs', 'all-in-one-wp-security-and-firewall')?>:</th>
387
  <td>
388
+ <input id="aiowps_enable_autoblock_spam_ip" name="aiowps_enable_autoblock_spam_ip" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_autoblock_spam_ip')=='1') echo ' checked="checked"'; ?> value="1"/>
389
+ <label for="aiowps_enable_autoblock_spam_ip" class="description"><?php _e('Check this box if you want this plugin to automatically block IP addresses which submit SPAM comments.', 'all-in-one-wp-security-and-firewall'); ?></label>
390
  </td>
391
  </tr>
392
  <tr valign="top">
393
+ <th scope="row"><label for="aiowps_spam_ip_min_comments_block"><?php _e('Minimum number of SPAM comments', 'all-in-one-wp-security-and-firewall')?>:</label></th>
394
+ <td><input id="aiowps_spam_ip_min_comments_block" type="text" size="5" name="aiowps_spam_ip_min_comments_block" value="<?php echo $aio_wp_security->configs->get_value('aiowps_spam_ip_min_comments_block'); ?>" />
395
  <span class="description"><?php _e('Specify the minimum number of SPAM comments for an IP address before it is permanently blocked.', 'all-in-one-wp-security-and-firewall');?></span>
396
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
397
  <div class="aiowps_more_info_body">
432
  <?php wp_nonce_field('aiowpsec-spammer-ip-list-nonce'); ?>
433
  <table class="form-table">
434
  <tr valign="top">
435
+ <th scope="row"><label for="aiowps_spam_ip_min_comments"><?php _e('Minimum number of SPAM comments per IP', 'all-in-one-wp-security-and-firewall')?>:</label></th>
436
+ <td><input id="aiowps_spam_ip_min_comments" type="text" size="5" name="aiowps_spam_ip_min_comments" value="<?php echo $aio_wp_security->configs->get_value('aiowps_spam_ip_min_comments'); ?>" />
437
  <span class="description"><?php _e('This field allows you to list only those IP addresses which have been used to post X or more SPAM comments.', 'all-in-one-wp-security-and-firewall');?></span>
438
  <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
439
  <div class="aiowps_more_info_body">
453
  <h3 class="hndle"><label for="title"><?php _e('SPAMMER IP Address Results', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
454
  <div class="inside">
455
  <?php
456
+ if (is_multisite() && get_current_blog_id() != 1)
457
  {
458
  echo '<div class="aio_yellow_box">';
459
  echo '<p>'.__('The plugin has detected that you are using a Multi-Site WordPress installation.', 'all-in-one-wp-security-and-firewall').'</p>
465
  $spammer_ip_list->prepare_items();
466
  //echo "put table of locked entries here";
467
  ?>
468
+ <form id="tables-filter" method="get">
469
  <!-- For plugins, we also need to ensure that the form posts back to our current page -->
470
  <input type="hidden" name="page" value="<?php echo esc_attr($_REQUEST['page']); ?>" />
471
  <input type="hidden" name="tab" value="<?php echo esc_attr($_REQUEST['tab']); ?>" />
523
  ?>
524
  <table class="form-table">
525
  <tr valign="top">
526
+ <th scope="row"><label for="aiowps_enable_bp_register_captcha"><?php _e('Enable Captcha On BuddyPress Registration Form', 'all-in-one-wp-security-and-firewall')?>:</label></th>
527
  <td>
528
+ <input id="aiowps_enable_bp_register_captcha" name="aiowps_enable_bp_register_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_bp_register_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
529
+ <label for="aiowps_enable_bp_register_captcha"><?php _e('Check this if you want to insert a captcha field on the BuddyPress registration forms', 'all-in-one-wp-security-and-firewall'); ?></label>
530
  </td>
531
  </tr>
532
  </table>
585
  ?>
586
  <table class="form-table">
587
  <tr valign="top">
588
+ <th scope="row"><label for="aiowps_enable_bbp_new_topic_captcha"><?php _e('Enable Captcha On BBPress New Topic Form', 'all-in-one-wp-security-and-firewall')?>:</label></th>
589
  <td>
590
+ <input id="aiowps_enable_bbp_new_topic_captcha" name="aiowps_enable_bbp_new_topic_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_bbp_new_topic_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
591
+ <label for="aiowps_enable_bbp_new_topic_captcha"><?php _e('Check this if you want to insert a captcha field on the BBPress new topic forms', 'all-in-one-wp-security-and-firewall'); ?></label>
592
  </td>
593
  </tr>
594
  </table>
601
  }
602
  }
603
 
604
+ } //end class
admin/wp-security-tools-menu.php ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if (!defined('ABSPATH')) {
4
+ exit; // Exit if accessed directly
5
+ }
6
+
7
+ class AIOWPSecurity_Tools_Menu extends AIOWPSecurity_Admin_Menu {
8
+
9
+ /**
10
+ * All tab keys, titles and render callbacks.
11
+ *
12
+ * @var Array
13
+ */
14
+ protected $menu_tabs;
15
+
16
+ /**
17
+ * Renders the submenu's current tab page.
18
+ *
19
+ * @return Void
20
+ */
21
+ public function __construct() {
22
+ $this->render_menu_page();
23
+ }
24
+
25
+ /**
26
+ * Populates $menu_tabs array.
27
+ *
28
+ * @return Void
29
+ */
30
+ private function set_menu_tabs() {
31
+ $this->menu_tabs = apply_filters('aiowpsecurity_tools_tabs',
32
+ array(
33
+ 'whois-lookup' => array(
34
+ 'title' => __('WHOIS Lookup', 'all-in-one-wp-security-and-firewall'),
35
+ 'render_callback' => array($this, 'render_whois_lookup_tab'),
36
+ )
37
+ )
38
+ );
39
+ }
40
+
41
+ /**
42
+ * Renders the submenu's tabs as nav items.
43
+ *
44
+ * @return Void
45
+ */
46
+ private function render_menu_tabs() {
47
+ $current_tab = $this->get_current_tab();
48
+
49
+ echo '<h2 class="nav-tab-wrapper">';
50
+ foreach ($this->menu_tabs as $tab_key => $tab_info) {
51
+ $active = $current_tab == $tab_key ? 'nav-tab-active' : '';
52
+ echo '<a class="nav-tab '.$active.'" href="?page='.AIOWPSEC_TOOLS_MENU_SLUG.'&tab='.$tab_key.'">'.esc_html($tab_info['title']).'</a>';
53
+ }
54
+ echo '</h2>';
55
+ }
56
+
57
+ /**
58
+ * Renders the submenu's current tab page.
59
+ *
60
+ * @return Void
61
+ */
62
+ private function render_menu_page() {
63
+ echo '<div class="wrap">'; // Start of wrap
64
+ echo '<h2>'.__('Tools', 'all-in-one-wp-security-and-firewall').'</h2>'; // Interface title
65
+ $this->set_menu_tabs();
66
+ $tab = $this->get_current_tab();
67
+ $this->render_menu_tabs();
68
+
69
+ ?>
70
+ <div id="poststuff">
71
+ <div id="post-body">
72
+ <?php call_user_func($this->menu_tabs[$tab]['render_callback']); ?>
73
+ </div>
74
+ </div>
75
+ <?php
76
+
77
+ echo '</div>'; // End of wrap
78
+ }
79
+
80
+ /**
81
+ * Does a WHOIS lookup on an IP address or domain name and then returns the result.
82
+ *
83
+ * @param String $search - IP address or domain name to do a WHOIS lookup on
84
+ * @param Integer $timeout - connection timeout for fsockopen
85
+ *
86
+ * @return String|WP_Error - returns preformatted WHOIS lookup result or WP_Error
87
+ */
88
+ private function whois_lookup($search, $timeout = 10) {
89
+ $fp = @fsockopen('whois.iana.org', 43, $errno, $errstr, $timeout);
90
+
91
+ if (!$fp) {
92
+ return new WP_Error('whois_lookup_failed', 'whois.iana.org: Socket Error '.$errno.' - '.$errstr);
93
+ }
94
+
95
+ $queries = sprintf(__('Querying %s: %s', 'all-in-one-wp-security-and-firewall'), 'whois.iana.org', $search)."\n";
96
+
97
+ fputs($fp, $search."\r\n");
98
+ $out = '';
99
+ while (!feof($fp)) {
100
+ $line = fgets($fp);
101
+ if (preg_match('/refer: +(\S+)/', $line, $matches)) {
102
+ $referral_server = $matches[1];
103
+ $queries .= sprintf(__('Redirected to %s', 'all-in-one-wp-security-and-firewall'), $referral_server)."\n";
104
+ break;
105
+ }
106
+ $out .= $line;
107
+ }
108
+ fclose($fp);
109
+
110
+ if (!isset($referral_server) && filter_var($search, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && preg_match('/whois: +(\S+)/', $out, $matches)) {
111
+ $referral_server = $matches[1];
112
+ $queries .= sprintf(__('Redirected to %s', 'all-in-one-wp-security-and-firewall'), $referral_server)."\n";
113
+ }
114
+
115
+ while (isset($referral_server)) {
116
+ $referrals[] = $referral_server;
117
+
118
+ $fp = @fsockopen($referral_server, 43, $errno, $errstr, $timeout);
119
+
120
+ if (!$fp) {
121
+ return new WP_Error('whois_lookup_failed', $referral_server.': Socket Error '.$errno.' - '.$errstr);
122
+ }
123
+
124
+ if ('whois.arin.net' == $referral_server) {
125
+ $formatted_search = 'n + '.$search;
126
+ } elseif ('whois.denic.de' == $referral_server) {
127
+ $formatted_search = '-T dn,ace '.$search;
128
+ } elseif ('whois.dk-hostmaster.dk' == $referral_server) {
129
+ $formatted_search = '--charset=utf-8 --show-handles '.$search;
130
+ } elseif ('whois.nic.ad.jp' == $referral_server || 'whois.jprs.jp' == $referral_server) {
131
+ $formatted_search = $search.'/e';
132
+ } else {
133
+ $formatted_search = $search;
134
+ }
135
+
136
+ $queries .= sprintf(__('Querying %s: %s', 'all-in-one-wp-security-and-firewall'), $referral_server, $formatted_search)."\n";
137
+
138
+ $referral_server = null;
139
+
140
+ fputs($fp, $formatted_search."\r\n");
141
+ $out = '';
142
+ while (!feof($fp)) {
143
+ $line = fgets($fp);
144
+ if (preg_match('/Registrar WHOIS Server: +(\S+)/', $line, $matches) ||
145
+ preg_match('/% referto: +whois -h (\S+)/', $line, $matches) ||
146
+ preg_match('/% referto: +(\S+)/', $line, $matches) ||
147
+ preg_match('/ReferralServer: +rwhois:\/\/(\S+)/', $line, $matches) ||
148
+ preg_match('/ReferralServer: +whois:\/\/(\S+)/', $line, $matches)) {
149
+ if (!in_array($matches[1], $referrals)) {
150
+ $referral_server = $matches[1];
151
+ $queries .= sprintf(__('Redirected to %s', 'all-in-one-wp-security-and-firewall'), $referral_server)."\n";
152
+ break;
153
+ }
154
+ }
155
+ $out .= $line;
156
+ }
157
+ fclose($fp);
158
+ }
159
+
160
+ return $queries."\n".$out;
161
+ }
162
+
163
+ /**
164
+ * Renders the submenu's whois-lookup tab body.
165
+ *
166
+ * @return Void
167
+ */
168
+ private function render_whois_lookup_tab() {
169
+ global $aio_wp_security;
170
+
171
+ ?>
172
+ <div class="aio_blue_box">
173
+ <p><?php echo __('The WHOIS lookup feature gives you a way to look up who owns an IP address or domain name.', 'all-in-one-wp-security-and-firewall').' '.__('You can use this to investigate users engaging in malicious activity on your site.', 'all-in-one-wp-security-and-firewall'); ?></p>
174
+ </div>
175
+ <div class="postbox">
176
+ <h3 class="hndle"><?php _e('WHOIS Lookup On IP Or Domain', 'all-in-one-wp-security-and-firewall'); ?></h3>
177
+ <div class="inside">
178
+ <form method="post" action="">
179
+ <?php wp_nonce_field('aiowpsec-whois-lookup'); ?>
180
+ <table class="form-table">
181
+ <tr valign="top">
182
+ <th scope="row">
183
+ <label for="aiowps_whois_ip_or_domain"><?php _e('IP address or domain name:', 'all-in-one-wp-security-and-firewall'); ?></label>
184
+ </th>
185
+ <td>
186
+ <input id="aiowps_whois_ip_or_domain" type="text" name="aiowps_whois_ip_or_domain" value="" size="80"/>
187
+ </td>
188
+ </tr>
189
+ </table>
190
+ <input class="button-primary" type="submit" value="<?php _e('Look up IP or domain', 'all-in-one-wp-security-and-firewall'); ?>"/>
191
+ </form>
192
+ </div>
193
+ </div>
194
+ <?php
195
+
196
+ if (isset($_POST['aiowps_whois_ip_or_domain'])) {
197
+ $nonce = $_POST['_wpnonce'];
198
+
199
+ if (!wp_verify_nonce($nonce, 'aiowpsec-whois-lookup')) {
200
+ $aio_wp_security->debug_logger->log_debug('Nonce check failed on WHOIS lookup.', 4);
201
+ die('Nonce check failed on WHOIS lookup.');
202
+ }
203
+
204
+ $ip_or_domain = stripslashes($_POST['aiowps_whois_ip_or_domain']);
205
+
206
+ ?>
207
+ <div class="postbox">
208
+ <h3 class="hndle">
209
+ <table>
210
+ <tr valign="top">
211
+ <th scope="row">WHOIS: </th>
212
+ <td><?php echo htmlspecialchars($ip_or_domain); ?></td>
213
+ </tr>
214
+ </table>
215
+ </h3>
216
+ <div class="inside">
217
+ <pre><?php
218
+ if (empty($ip_or_domain) || !(filter_var($ip_or_domain, FILTER_VALIDATE_IP) || filter_var($ip_or_domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME))) {
219
+ $this->show_msg_error(__('Please enter a valid IP address or domain name to look up.', 'all-in-one-wp-security-and-firewall'));
220
+ _e('Nothing to show.', 'all-in-one-wp-security-and-firewall');
221
+ } else {
222
+ $result = $this->whois_lookup($ip_or_domain);
223
+
224
+ if (is_wp_error($result)) {
225
+ $this->show_msg_error(htmlspecialchars($result->get_error_message()));
226
+ _e('Nothing to show.', 'all-in-one-wp-security-and-firewall');
227
+ } else {
228
+ echo htmlspecialchars($result);
229
+ }
230
+ }
231
+ ?></pre>
232
+ </div>
233
+ </div>
234
+ <?php
235
+
236
+ }
237
+ }
238
+
239
+ } // End of class
admin/wp-security-user-accounts-menu.php CHANGED
@@ -93,7 +93,7 @@ class AIOWPSecurity_User_Accounts_Menu extends AIOWPSecurity_Admin_Menu
93
  <?php
94
  //display a list of all administrator accounts for this site
95
  $postbox_title = __('List of Administrator Accounts', 'all-in-one-wp-security-and-firewall');
96
- if (AIOWPSecurity_Utility::is_multisite_install()) { //Multi-site: get admin accounts for current site
97
  $blog_id = get_current_blog_id();
98
  $this->postbox($postbox_title, $this->get_all_admin_accounts($blog_id));
99
  } else {
@@ -115,8 +115,8 @@ class AIOWPSecurity_User_Accounts_Menu extends AIOWPSecurity_Admin_Menu
115
  <?php wp_nonce_field('aiowpsec-change-admin-nonce'); ?>
116
  <table class="form-table">
117
  <tr valign="top">
118
- <th scope="row"><label for="NewUserName"> <?php _e('New Admin Username', 'all-in-one-wp-security-and-firewall')?>:</label></th>
119
- <td><input type="text" size="16" name="aiowps_new_user_name" />
120
  <p class="description"><?php _e('Choose a new username for admin.', 'all-in-one-wp-security-and-firewall'); ?></p>
121
  </td>
122
  </tr>
@@ -266,7 +266,7 @@ class AIOWPSecurity_User_Accounts_Menu extends AIOWPSecurity_Admin_Menu
266
  }
267
 
268
  //multisite considerations
269
- if ( AIOWPSecurity_Utility::is_multisite_install() ) { //process sitemeta if we're in a multi-site situation
270
  $oldAdmins = $wpdb->get_var( "SELECT meta_value FROM `" . $wpdb->sitemeta . "` WHERE meta_key = 'site_admins'" );
271
  $newAdmins = str_replace( '5:"admin"', strlen( $new_username ) . ':"' . esc_sql( $new_username ) . '"', $oldAdmins );
272
  $wpdb->query( "UPDATE `" . $wpdb->sitemeta . "` SET meta_value = '" . esc_sql( $newAdmins ) . "' WHERE meta_key = 'site_admins'" );
@@ -279,7 +279,7 @@ class AIOWPSecurity_User_Accounts_Menu extends AIOWPSecurity_Admin_Menu
279
  $after_logout_url = AIOWPSecurity_Utility::get_current_page_url();
280
  $after_logout_payload = array('redirect_to'=>$after_logout_url, 'msg'=>$aio_wp_security->user_login_obj->key_login_msg.'=admin_user_changed', );
281
  //Save some of the logout redirect data to a transient
282
- AIOWPSecurity_Utility::is_multisite_install() ? set_site_transient('aiowps_logout_payload', $after_logout_payload, 30 * 60) : set_transient('aiowps_logout_payload', $after_logout_payload, 30 * 60);
283
 
284
  $logout_url = AIOWPSEC_WP_URL.'?aiowpsec_do_log_out=1';
285
  $logout_url = AIOWPSecurity_Utility::add_query_data_to_url($logout_url, 'al_additional_data', '1');
93
  <?php
94
  //display a list of all administrator accounts for this site
95
  $postbox_title = __('List of Administrator Accounts', 'all-in-one-wp-security-and-firewall');
96
+ if (is_multisite()) { //Multi-site: get admin accounts for current site
97
  $blog_id = get_current_blog_id();
98
  $this->postbox($postbox_title, $this->get_all_admin_accounts($blog_id));
99
  } else {
115
  <?php wp_nonce_field('aiowpsec-change-admin-nonce'); ?>
116
  <table class="form-table">
117
  <tr valign="top">
118
+ <th scope="row"><label for="aiowps_new_user_name"><?php _e('New Admin Username', 'all-in-one-wp-security-and-firewall')?>:</label></th>
119
+ <td><input type="text" size="16" id="aiowps_new_user_name" name="aiowps_new_user_name" />
120
  <p class="description"><?php _e('Choose a new username for admin.', 'all-in-one-wp-security-and-firewall'); ?></p>
121
  </td>
122
  </tr>
266
  }
267
 
268
  //multisite considerations
269
+ if ( is_multisite() ) { //process sitemeta if we're in a multi-site situation
270
  $oldAdmins = $wpdb->get_var( "SELECT meta_value FROM `" . $wpdb->sitemeta . "` WHERE meta_key = 'site_admins'" );
271
  $newAdmins = str_replace( '5:"admin"', strlen( $new_username ) . ':"' . esc_sql( $new_username ) . '"', $oldAdmins );
272
  $wpdb->query( "UPDATE `" . $wpdb->sitemeta . "` SET meta_value = '" . esc_sql( $newAdmins ) . "' WHERE meta_key = 'site_admins'" );
279
  $after_logout_url = AIOWPSecurity_Utility::get_current_page_url();
280
  $after_logout_payload = array('redirect_to'=>$after_logout_url, 'msg'=>$aio_wp_security->user_login_obj->key_login_msg.'=admin_user_changed', );
281
  //Save some of the logout redirect data to a transient
282
+ is_multisite() ? set_site_transient('aiowps_logout_payload', $after_logout_payload, 30 * 60) : set_transient('aiowps_logout_payload', $after_logout_payload, 30 * 60);
283
 
284
  $logout_url = AIOWPSEC_WP_URL.'?aiowpsec_do_log_out=1';
285
  $logout_url = AIOWPSecurity_Utility::add_query_data_to_url($logout_url, 'al_additional_data', '1');
admin/wp-security-user-login-menu.php CHANGED
@@ -3,8 +3,7 @@ if(!defined('ABSPATH')){
3
  exit;//Exit if accessed directly
4
  }
5
 
6
- class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
7
- {
8
  var $menu_page_slug = AIOWPSEC_USER_LOGIN_MENU_SLUG;
9
 
10
  /* Specify all the tabs of this menu in the following array */
@@ -15,29 +14,32 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
15
  'tab3' => 'render_tab3',
16
  'tab4' => 'render_tab4',
17
  'tab5' => 'render_tab5',
 
18
  );
19
 
20
- function __construct()
 
 
 
21
  {
22
  $this->render_menu_page();
23
  }
24
 
25
- function set_menu_tabs()
26
- {
27
  $this->menu_tabs = array(
28
  'tab1' => __('Login Lockdown', 'all-in-one-wp-security-and-firewall'),
29
  'tab2' => __('Failed Login Records', 'all-in-one-wp-security-and-firewall'),
30
  'tab3' => __('Force Logout', 'all-in-one-wp-security-and-firewall'),
31
  'tab4' => __('Account Activity Logs', 'all-in-one-wp-security-and-firewall'),
32
  'tab5' => __('Logged In Users', 'all-in-one-wp-security-and-firewall'),
 
33
  );
34
  }
35
 
36
  /*
37
  * Renders our tabs of this menu as nav items
38
  */
39
- function render_menu_tabs()
40
- {
41
  $current_tab = $this->get_current_tab();
42
 
43
  echo '<h2 class="nav-tab-wrapper">';
@@ -52,8 +54,7 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
52
  /*
53
  * The menu rendering goes here
54
  */
55
- function render_menu_page()
56
- {
57
  echo '<div class="wrap">';
58
  echo '<h2>'.__('User Login','all-in-one-wp-security-and-firewall').'</h2>';//Interface title
59
  $this->set_menu_tabs();
@@ -69,9 +70,13 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
69
  </div><!-- end of wrap -->
70
  <?php
71
  }
72
-
73
- function render_tab1()
74
- {
 
 
 
 
75
  global $aio_wp_security;
76
  global $aiowps_feature_mgr;
77
  include_once 'wp-security-list-locked-ip.php'; //For rendering the AIOWPSecurity_List_Table in tab1
@@ -88,9 +93,8 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
88
  }
89
 
90
  $max_login_attempt_val = sanitize_text_field($_POST['aiowps_max_login_attempts']);
91
- if(!is_numeric($max_login_attempt_val))
92
- {
93
- $error .= '<br />'.__('You entered a non numeric value for the max login attempts field. It has been set to the default value.','all-in-one-wp-security-and-firewall');
94
  $max_login_attempt_val = '3';//Set it to the default value for this field
95
  }
96
 
@@ -105,25 +109,47 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
105
  if(!is_numeric($lockout_time_length))
106
  {
107
  $error .= '<br />'.__('You entered a non numeric value for the lockout time length field. It has been set to the default value.','all-in-one-wp-security-and-firewall');
108
- $lockout_time_length = '60';//Set it to the default value for this field
 
 
 
 
 
 
109
  }
110
 
111
- $email_address = sanitize_email($_POST['aiowps_email_address']);
112
- if(!is_email($email_address))
113
- {
114
- $error .= '<br />'.__('You have entered an incorrect email address format. It has been set to your WordPress admin email as default.','all-in-one-wp-security-and-firewall');
115
- $email_address = get_bloginfo('admin_email'); //Set the default value to the blog admin email
 
 
 
 
 
 
 
 
 
 
 
 
116
  }
 
 
 
 
117
 
118
  // Instantly lockout specific usernames
119
- $_ilsu = isset($_POST['aiowps_instantly_lockout_specific_usernames']) ? $_POST['aiowps_instantly_lockout_specific_usernames'] : '';
120
  // Read into array, sanitize, filter empty and keep only unique usernames.
121
  $instantly_lockout_specific_usernames
122
  = array_unique(
123
  array_filter(
124
  array_map(
125
  'sanitize_user',
126
- AIOWPSecurity_Utility::explode_trim_filter_empty($_ilsu)
127
  ),
128
  'strlen'
129
  )
@@ -144,11 +170,13 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
144
  $aio_wp_security->configs->set_value('aiowps_max_login_attempts',absint($max_login_attempt_val));
145
  $aio_wp_security->configs->set_value('aiowps_retry_time_period',absint($login_retry_time_period));
146
  $aio_wp_security->configs->set_value('aiowps_lockout_time_length',absint($lockout_time_length));
 
147
  $aio_wp_security->configs->set_value('aiowps_set_generic_login_msg',isset($_POST["aiowps_set_generic_login_msg"])?'1':'');
148
  $aio_wp_security->configs->set_value('aiowps_enable_invalid_username_lockdown',isset($_POST["aiowps_enable_invalid_username_lockdown"])?'1':'');
149
  $aio_wp_security->configs->set_value('aiowps_instantly_lockout_specific_usernames', $instantly_lockout_specific_usernames);
150
  $aio_wp_security->configs->set_value('aiowps_enable_email_notify',isset($_POST["aiowps_enable_email_notify"])?'1':'');
151
- $aio_wp_security->configs->set_value('aiowps_email_address',$email_address);
 
152
  $aio_wp_security->configs->save_config();
153
 
154
  //Recalculate points after the feature status/options have been altered
@@ -156,19 +184,7 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
156
 
157
  $this->show_msg_settings_updated();
158
  }
159
-
160
-
161
- if(isset($_REQUEST['action'])) //Do list table form row action tasks
162
- {
163
- if($_REQUEST['action'] == 'delete_blocked_ip'){ //Delete link was clicked for a row in list table
164
- $locked_ip_list->delete_lockdown_records(strip_tags($_REQUEST['lockdown_id']));
165
- }
166
-
167
- if($_REQUEST['action'] == 'unlock_ip'){ //Unlock link was clicked for a row in list table
168
- $locked_ip_list->unlock_ip_range(strip_tags($_REQUEST['lockdown_id']));
169
- }
170
- }
171
-
172
  //login lockdown whitelist settings
173
  $result = 1;
174
  if (isset($_POST['aiowps_save_lockdown_whitelist_settings']))
@@ -188,7 +204,7 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
188
  {
189
  if (!empty($_POST['aiowps_lockdown_allowed_ip_addresses']))
190
  {
191
- $ip_addresses = $_POST['aiowps_lockdown_allowed_ip_addresses'];
192
  $ip_list_array = AIOWPSecurity_Utility_IP::create_ip_list_array_from_string_with_newline($ip_addresses);
193
  $payload = AIOWPSecurity_Utility_IP::validate_ip_list($ip_list_array, 'whitelist');
194
  if($payload[0] == 1){
@@ -244,70 +260,123 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
244
  <tr valign="top">
245
  <th scope="row"><?php _e('Enable Login Lockdown Feature', 'all-in-one-wp-security-and-firewall')?>:</th>
246
  <td>
247
- <input name="aiowps_enable_login_lockdown" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_login_lockdown')=='1') echo ' checked="checked"'; ?> value="1"/>
248
- <span class="description"><?php _e('Check this if you want to enable the login lockdown feature and apply the settings below', 'all-in-one-wp-security-and-firewall'); ?></span>
249
  </td>
250
  </tr>
251
  <tr valign="top">
252
  <th scope="row"><?php _e('Allow Unlock Requests', 'all-in-one-wp-security-and-firewall')?>:</th>
253
  <td>
254
- <input name="aiowps_allow_unlock_requests" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_allow_unlock_requests')=='1') echo ' checked="checked"'; ?> value="1"/>
255
- <span class="description"><?php _e('Check this if you want to allow users to generate an automated unlock request link which will unlock their account', 'all-in-one-wp-security-and-firewall'); ?></span>
256
  </td>
257
  </tr>
258
  <tr valign="top">
259
- <th scope="row"><?php _e('Max Login Attempts', 'all-in-one-wp-security-and-firewall')?>:</th>
260
- <td><input type="text" size="5" name="aiowps_max_login_attempts" value="<?php echo esc_html($aio_wp_security->configs->get_value('aiowps_max_login_attempts')); ?>" />
261
  <span class="description"><?php _e('Set the value for the maximum login retries before IP address is locked out', 'all-in-one-wp-security-and-firewall'); ?></span>
262
  </td>
263
  </tr>
264
  <tr valign="top">
265
- <th scope="row"><?php _e('Login Retry Time Period (min)', 'all-in-one-wp-security-and-firewall')?>:</th>
266
- <td><input type="text" size="5" name="aiowps_retry_time_period" value="<?php echo esc_html($aio_wp_security->configs->get_value('aiowps_retry_time_period')); ?>" />
267
  <span class="description"><?php _e('If the maximum number of failed login attempts for a particular IP address occur within this time period the plugin will lock out that address', 'all-in-one-wp-security-and-firewall'); ?></span>
268
  </td>
269
  </tr>
270
  <tr valign="top">
271
- <th scope="row"><?php _e('Time Length of Lockout (min)', 'all-in-one-wp-security-and-firewall')?>:</th>
272
- <td><input type="text" size="5" name="aiowps_lockout_time_length" value="<?php echo esc_html($aio_wp_security->configs->get_value('aiowps_lockout_time_length')); ?>" />
273
- <span class="description"><?php _e('Set the length of time for which a particular IP address will be prevented from logging in', 'all-in-one-wp-security-and-firewall'); ?></span>
 
 
 
 
 
 
 
 
 
274
  </td>
275
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  <tr valign="top">
277
  <th scope="row"><?php _e('Display Generic Error Message', 'all-in-one-wp-security-and-firewall')?>:</th>
278
  <td>
279
- <input name="aiowps_set_generic_login_msg" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_set_generic_login_msg')=='1') echo ' checked="checked"'; ?> value="1"/>
280
- <span class="description"><?php _e('Check this if you want to show a generic error message when a login attempt fails', 'all-in-one-wp-security-and-firewall'); ?></span>
281
  </td>
282
  </tr>
283
  <tr valign="top">
284
  <th scope="row"><?php _e('Instantly Lockout Invalid Usernames', 'all-in-one-wp-security-and-firewall')?>:</th>
285
  <td>
286
- <input name="aiowps_enable_invalid_username_lockdown" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_invalid_username_lockdown')=='1') echo ' checked="checked"'; ?> value="1"/>
287
- <span class="description"><?php _e('Check this if you want to instantly lockout login attempts with usernames which do not exist on your system', 'all-in-one-wp-security-and-firewall'); ?></span>
288
  </td>
289
  </tr>
290
  <tr valign="top">
291
- <th scope="row"><?php _e('Instantly Lockout Specific Usernames', 'all-in-one-wp-security-and-firewall')?>:</th>
 
 
 
 
292
  <td>
293
- <?php
294
  $instant_lockout_users_list = $aio_wp_security->configs->get_value('aiowps_instantly_lockout_specific_usernames');
295
  if(empty($instant_lockout_users_list)){
296
  $instant_lockout_users_list = array();
297
  }
298
  ?>
299
- <textarea name="aiowps_instantly_lockout_specific_usernames" cols="50" rows="5"><?php echo esc_textarea(implode(PHP_EOL, $instant_lockout_users_list)); ?></textarea><br>
300
  <span class="description"><?php _e('Insert one username per line. Existing usernames are not blocked even if present in the list.', 'all-in-one-wp-security-and-firewall'); ?></span>
301
  </td>
302
  </tr>
303
  <tr valign="top">
304
- <th scope="row"><?php _e('Notify By Email', 'all-in-one-wp-security-and-firewall')?>:</th>
 
 
 
 
305
  <td>
306
- <input name="aiowps_enable_email_notify" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_email_notify')=='1') echo ' checked="checked"'; ?> value="1"/>
307
- <span class="description"><?php _e('Check this if you want to receive an email when someone has been locked out due to maximum failed login attempts', 'all-in-one-wp-security-and-firewall'); ?></span>
308
- <br /><input type="text" size="30" name="aiowps_email_address" value="<?php echo esc_html($aio_wp_security->configs->get_value('aiowps_email_address')); ?>" />
309
- <span class="description"><?php _e('Enter an email address', 'all-in-one-wp-security-and-firewall'); ?></span>
310
- </td>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311
  </tr>
312
  </table>
313
  <input type="submit" name="aiowps_login_lockdown" value="<?php _e('Save Settings', 'all-in-one-wp-security-and-firewall')?>" class="button-primary" />
@@ -330,29 +399,19 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
330
  <?php wp_nonce_field('aiowpsec-lockdown-whitelist-settings-nonce'); ?>
331
  <table class="form-table">
332
  <tr valign="top">
333
- <th scope="row"><?php _e('Enable Login Lockdown IP Whitelist', 'all-in-one-wp-security-and-firewall')?>:</th>
334
  <td>
335
- <input name="aiowps_lockdown_enable_whitelisting" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_lockdown_enable_whitelisting')=='1') echo ' checked="checked"'; ?> value="1"/>
336
  <span class="description"><?php _e('Check this if you want to enable the whitelisting of selected IP addresses specified in the settings below', 'all-in-one-wp-security-and-firewall'); ?></span>
337
  </td>
338
  </tr>
339
  <tr valign="top">
340
- <th scope="row"><?php _e('Enter Whitelisted IP Addresses:', 'all-in-one-wp-security-and-firewall')?></th>
341
  <td>
342
- <textarea name="aiowps_lockdown_allowed_ip_addresses" rows="5" cols="50"><?php echo ($result == -1)?htmlspecialchars($_POST['aiowps_lockdown_allowed_ip_addresses']):htmlspecialchars($aio_wp_security->configs->get_value('aiowps_lockdown_allowed_ip_addresses')); ?></textarea>
343
  <br />
344
- <span class="description"><?php _e('Enter one or more IP addresses or IP ranges you wish to include in your whitelist. The addresses specified here will never be blocked by the login lockdown feature.','all-in-one-wp-security-and-firewall');?></span>
345
- <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
346
- <div class="aiowps_more_info_body">
347
- <?php
348
- echo '<p class="description">'.__('Each IP address must be on a new line.', 'all-in-one-wp-security-and-firewall').'</p>';
349
- echo '<p class="description">'.__('To specify an IP range use a wildcard "*" character. Acceptable ways to use wildcards is shown in the examples below:', 'all-in-one-wp-security-and-firewall').'</p>';
350
- echo '<p class="description">'.__('Example 1: 195.47.89.*', 'all-in-one-wp-security-and-firewall').'</p>';
351
- echo '<p class="description">'.__('Example 2: 195.47.*.*', 'all-in-one-wp-security-and-firewall').'</p>';
352
- echo '<p class="description">'.__('Example 3: 195.*.*.*', 'all-in-one-wp-security-and-firewall').'</p>';
353
- ?>
354
- </div>
355
-
356
  </td>
357
  </tr>
358
  </table>
@@ -363,8 +422,12 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
363
  <?php
364
  }
365
 
366
- function render_tab2()
367
- {
 
 
 
 
368
  global $aio_wp_security, $wpdb;
369
  if (isset($_POST['aiowps_delete_failed_login_records']))
370
  {
@@ -385,7 +448,7 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
385
  }
386
  else
387
  {
388
- $this->show_msg_updated(__('All records from the Failed Logins table were deleted successfully!','all-in-one-wp-security-and-firewall'));
389
  }
390
  }
391
 
@@ -397,8 +460,7 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
397
  $failed_login_list->delete_login_failed_records(strip_tags($_REQUEST['failed_login_id']));
398
  }
399
  }
400
-
401
- AIOWPSecurity_Admin_Menu::display_bulk_result_message();
402
  ?>
403
  <div class="aio_blue_box">
404
  <?php
@@ -416,7 +478,7 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
416
  $failed_login_list->prepare_items();
417
  //echo "put table of locked entries here";
418
  ?>
419
- <form id="tables-filter" method="get">
420
  <!-- For plugins, we also need to ensure that the form posts back to our current page -->
421
  <input type="hidden" name="page" value="<?php echo esc_attr($_REQUEST['page']); ?>" />
422
  <?php
@@ -525,13 +587,13 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
525
  <tr valign="top">
526
  <th scope="row"><?php _e('Enable Force WP User Logout', 'all-in-one-wp-security-and-firewall')?>:</th>
527
  <td>
528
- <input name="aiowps_enable_forced_logout" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_forced_logout')=='1') echo ' checked="checked"'; ?> value="1"/>
529
- <span class="description"><?php _e('Check this if you want to force a wp user to be logged out after a configured amount of time', 'all-in-one-wp-security-and-firewall'); ?></span>
530
  </td>
531
  </tr>
532
  <tr valign="top">
533
- <th scope="row"><?php _e('Logout the WP User After XX Minutes', 'all-in-one-wp-security-and-firewall')?>:</th>
534
- <td><input type="text" size="5" name="aiowps_logout_time_period" value="<?php echo $aio_wp_security->configs->get_value('aiowps_logout_time_period'); ?>" />
535
  <span class="description"><?php _e('(Minutes) The user will be forced to log back in after this time period has elapased.', 'all-in-one-wp-security-and-firewall'); ?></span>
536
  </td>
537
  </tr>
@@ -541,9 +603,13 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
541
  </div></div>
542
  <?php
543
  }
544
-
545
- function render_tab4()
546
- {
 
 
 
 
547
  include_once 'wp-security-list-acct-activity.php'; //For rendering the AIOWPSecurity_List_Table in tab4
548
  $acct_activity_list = new AIOWPSecurity_List_Account_Activity(); //For rendering the AIOWPSecurity_List_Table in tab2
549
  if(isset($_REQUEST['action'])) //Do row action tasks for list table form for login activity display
@@ -552,8 +618,7 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
552
  $acct_activity_list->delete_login_activity_records(strip_tags($_REQUEST['activity_login_rec']));
553
  }
554
  }
555
-
556
- AIOWPSecurity_Admin_Menu::display_bulk_result_message();
557
  ?>
558
  <div class="aio_blue_box">
559
  <?php
@@ -570,7 +635,7 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
570
  $acct_activity_list->prepare_items();
571
  //echo "put table of locked entries here";
572
  ?>
573
- <form id="tables-filter" method="get">
574
  <!-- For plugins, we also need to ensure that the form posts back to our current page -->
575
  <input type="hidden" name="page" value="<?php echo esc_attr($_REQUEST['page']); ?>" />
576
  <?php
@@ -598,11 +663,15 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
598
  </div></div>
599
  <?php
600
  }
601
-
602
- function render_tab5()
603
- {
 
 
 
 
604
  global $aio_wp_security;
605
- $logged_in_users = (AIOWPSecurity_Utility::is_multisite_install() ? get_site_transient('users_online') : get_transient('users_online'));
606
 
607
  include_once 'wp-security-list-logged-in-users.php'; //For rendering the AIOWPSecurity_List_Table
608
  $user_list = new AIOWPSecurity_List_Logged_In_Users();
@@ -613,8 +682,7 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
613
  }
614
  }
615
 
616
- if (isset($_POST['aiowps_refresh_logged_in_user_list']))
617
- {
618
  $nonce=$_REQUEST['_wpnonce'];
619
  if (!wp_verify_nonce($nonce, 'aiowpsec-logged-in-users-nonce'))
620
  {
@@ -634,8 +702,7 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
634
  <input type="submit" name="aiowps_refresh_logged_in_user_list" value="<?php _e('Refresh Data', 'all-in-one-wp-security-and-firewall')?>" class="button-primary" />
635
  </form>
636
  </div></div>
637
-
638
- <div class="aio_blue_box">
639
  <?php
640
  echo '<p>'.__('This tab displays all users who are currently logged into your site.', 'all-in-one-wp-security-and-firewall').'
641
  <br />'.__('If you suspect there is a user or users who are logged in which should not be, you can block them by inspecting the IP addresses from the data below and adding them to your blacklist.', 'all-in-one-wp-security-and-firewall').'
@@ -651,7 +718,7 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
651
  $user_list->prepare_items();
652
  //echo "put table of locked entries here";
653
  ?>
654
- <form id="tables-filter" method="get" onSubmit="return confirm('Are you sure you want to perform this bulk operation on the selected entries?');">
655
  <!-- For plugins, we also need to ensure that the form posts back to our current page -->
656
  <input type="hidden" name="page" value="<?php echo esc_attr($_REQUEST['page']); ?>" />
657
  <input type="hidden" name="tab" value="<?php echo esc_attr($_REQUEST['tab']); ?>" />
@@ -662,5 +729,66 @@ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu
662
  <?php
663
 
664
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
665
 
666
- } //end class
3
  exit;//Exit if accessed directly
4
  }
5
 
6
+ class AIOWPSecurity_User_Login_Menu extends AIOWPSecurity_Admin_Menu {
 
7
  var $menu_page_slug = AIOWPSEC_USER_LOGIN_MENU_SLUG;
8
 
9
  /* Specify all the tabs of this menu in the following array */
14
  'tab3' => 'render_tab3',
15
  'tab4' => 'render_tab4',
16
  'tab5' => 'render_tab5',
17
+ 'additional' => 'render_additional_tab',
18
  );
19
 
20
+ /**
21
+ * Class constructor
22
+ */
23
+ public function __construct()
24
  {
25
  $this->render_menu_page();
26
  }
27
 
28
+ function set_menu_tabs() {
 
29
  $this->menu_tabs = array(
30
  'tab1' => __('Login Lockdown', 'all-in-one-wp-security-and-firewall'),
31
  'tab2' => __('Failed Login Records', 'all-in-one-wp-security-and-firewall'),
32
  'tab3' => __('Force Logout', 'all-in-one-wp-security-and-firewall'),
33
  'tab4' => __('Account Activity Logs', 'all-in-one-wp-security-and-firewall'),
34
  'tab5' => __('Logged In Users', 'all-in-one-wp-security-and-firewall'),
35
+ 'additional' => __('Additional Settings', 'all-in-one-wp-security-and-firewall'),
36
  );
37
  }
38
 
39
  /*
40
  * Renders our tabs of this menu as nav items
41
  */
42
+ function render_menu_tabs() {
 
43
  $current_tab = $this->get_current_tab();
44
 
45
  echo '<h2 class="nav-tab-wrapper">';
54
  /*
55
  * The menu rendering goes here
56
  */
57
+ function render_menu_page() {
 
58
  echo '<div class="wrap">';
59
  echo '<h2>'.__('User Login','all-in-one-wp-security-and-firewall').'</h2>';//Interface title
60
  $this->set_menu_tabs();
70
  </div><!-- end of wrap -->
71
  <?php
72
  }
73
+
74
+ /**
75
+ * Displays the Login Lockdown tab.
76
+ *
77
+ * @return Void
78
+ */
79
+ private function render_tab1() {
80
  global $aio_wp_security;
81
  global $aiowps_feature_mgr;
82
  include_once 'wp-security-list-locked-ip.php'; //For rendering the AIOWPSecurity_List_Table in tab1
93
  }
94
 
95
  $max_login_attempt_val = sanitize_text_field($_POST['aiowps_max_login_attempts']);
96
+ if (!is_numeric($max_login_attempt_val)) {
97
+ $error .= '<br />'.__('You entered a non-numeric value for the max login attempts field. It has been set to the default value.','all-in-one-wp-security-and-firewall');
 
98
  $max_login_attempt_val = '3';//Set it to the default value for this field
99
  }
100
 
109
  if(!is_numeric($lockout_time_length))
110
  {
111
  $error .= '<br />'.__('You entered a non numeric value for the lockout time length field. It has been set to the default value.','all-in-one-wp-security-and-firewall');
112
+ $lockout_time_length = '5'; //Set it to the default value for this field
113
+ }
114
+
115
+ $max_lockout_time_length = sanitize_text_field($_POST['aiowps_max_lockout_time_length']);
116
+ if (!is_numeric($max_lockout_time_length)) {
117
+ $error .= '<br />'.__('You entered a non numeric value for the maximim lockout time length field. It has been set to the default value.','all-in-one-wp-security-and-firewall');
118
+ $max_lockout_time_length = '60'; //Set it to the default value for this field
119
  }
120
 
121
+ $email_addresses = isset($_POST['aiowps_email_address']) ? stripslashes($_POST['aiowps_email_address']) : get_bloginfo('admin_email');
122
+ $email_addresses_trimmed = AIOWPSecurity_Utility::explode_trim_filter_empty($email_addresses, "\n");
123
+ // Read into array, sanitize, filter empty and keep only unique usernames.
124
+ $email_address_list
125
+ = array_unique(
126
+ array_filter(
127
+ array_map(
128
+ 'sanitize_email',
129
+ $email_addresses_trimmed
130
+ ),
131
+ 'is_email'
132
+ )
133
+ );
134
+ if (isset($_POST['aiowps_enable_email_notify']) && 1 == $_POST['aiowps_enable_email_notify'] && 0 == count($email_addresses_trimmed)) {
135
+ $error .= '<br />' . __('Please fill in one or more email addresses to notify.', 'all-in-one-wp-security-and-firewall');
136
+ } else if (isset($_POST['aiowps_enable_email_notify']) && 1 == $_POST['aiowps_enable_email_notify'] && (0 == count($email_address_list) || count($email_address_list) != count($email_addresses_trimmed))) {
137
+ $error .= '<br />' . __('You have entered one or more invalid email addresses.', 'all-in-one-wp-security-and-firewall');
138
  }
139
+ if (0 == count($email_address_list)) {
140
+ $error .= ' ' . __('It has been set to your WordPress admin email as default.', 'all-in-one-wp-security-and-firewall');
141
+ $email_address_list[] = get_bloginfo('admin_email');
142
+ }
143
 
144
  // Instantly lockout specific usernames
145
+ $instantly_lockout_specific_usernames = isset($_POST['aiowps_instantly_lockout_specific_usernames']) ? $_POST['aiowps_instantly_lockout_specific_usernames'] : '';
146
  // Read into array, sanitize, filter empty and keep only unique usernames.
147
  $instantly_lockout_specific_usernames
148
  = array_unique(
149
  array_filter(
150
  array_map(
151
  'sanitize_user',
152
+ AIOWPSecurity_Utility::explode_trim_filter_empty($instantly_lockout_specific_usernames)
153
  ),
154
  'strlen'
155
  )
170
  $aio_wp_security->configs->set_value('aiowps_max_login_attempts',absint($max_login_attempt_val));
171
  $aio_wp_security->configs->set_value('aiowps_retry_time_period',absint($login_retry_time_period));
172
  $aio_wp_security->configs->set_value('aiowps_lockout_time_length',absint($lockout_time_length));
173
+ $aio_wp_security->configs->set_value('aiowps_max_lockout_time_length', absint($max_lockout_time_length));
174
  $aio_wp_security->configs->set_value('aiowps_set_generic_login_msg',isset($_POST["aiowps_set_generic_login_msg"])?'1':'');
175
  $aio_wp_security->configs->set_value('aiowps_enable_invalid_username_lockdown',isset($_POST["aiowps_enable_invalid_username_lockdown"])?'1':'');
176
  $aio_wp_security->configs->set_value('aiowps_instantly_lockout_specific_usernames', $instantly_lockout_specific_usernames);
177
  $aio_wp_security->configs->set_value('aiowps_enable_email_notify',isset($_POST["aiowps_enable_email_notify"])?'1':'');
178
+ $aio_wp_security->configs->set_value('aiowps_enable_php_backtrace_in_email', isset($_POST['aiowps_enable_php_backtrace_in_email']) ? '1' : '');
179
+ $aio_wp_security->configs->set_value('aiowps_email_address', $email_address_list);
180
  $aio_wp_security->configs->save_config();
181
 
182
  //Recalculate points after the feature status/options have been altered
184
 
185
  $this->show_msg_settings_updated();
186
  }
187
+
 
 
 
 
 
 
 
 
 
 
 
 
188
  //login lockdown whitelist settings
189
  $result = 1;
190
  if (isset($_POST['aiowps_save_lockdown_whitelist_settings']))
204
  {
205
  if (!empty($_POST['aiowps_lockdown_allowed_ip_addresses']))
206
  {
207
+ $ip_addresses = stripslashes($_POST['aiowps_lockdown_allowed_ip_addresses']);
208
  $ip_list_array = AIOWPSecurity_Utility_IP::create_ip_list_array_from_string_with_newline($ip_addresses);
209
  $payload = AIOWPSecurity_Utility_IP::validate_ip_list($ip_list_array, 'whitelist');
210
  if($payload[0] == 1){
260
  <tr valign="top">
261
  <th scope="row"><?php _e('Enable Login Lockdown Feature', 'all-in-one-wp-security-and-firewall')?>:</th>
262
  <td>
263
+ <input id="aiowps_enable_login_lockdown" name="aiowps_enable_login_lockdown" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_login_lockdown')=='1') echo ' checked="checked"'; ?> value="1"/>
264
+ <label for="aiowps_enable_login_lockdown" class="description"><?php _e('Check this if you want to enable the login lockdown feature and apply the settings below', 'all-in-one-wp-security-and-firewall'); ?></label>
265
  </td>
266
  </tr>
267
  <tr valign="top">
268
  <th scope="row"><?php _e('Allow Unlock Requests', 'all-in-one-wp-security-and-firewall')?>:</th>
269
  <td>
270
+ <input id="aiowps_allow_unlock_requests" name="aiowps_allow_unlock_requests" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_allow_unlock_requests')=='1') echo ' checked="checked"'; ?> value="1"/>
271
+ <label for="aiowps_allow_unlock_requests" class="description"><?php _e('Check this if you want to allow users to generate an automated unlock request link which will unlock their account', 'all-in-one-wp-security-and-firewall'); ?></label>
272
  </td>
273
  </tr>
274
  <tr valign="top">
275
+ <th scope="row"><label for="aiowps_max_login_attempts"><?php _e('Max Login Attempts', 'all-in-one-wp-security-and-firewall'); ?>:</label></th>
276
+ <td><input id="aiowps_max_login_attempts" type="text" size="5" name="aiowps_max_login_attempts" value="<?php echo esc_html($aio_wp_security->configs->get_value('aiowps_max_login_attempts')); ?>" />
277
  <span class="description"><?php _e('Set the value for the maximum login retries before IP address is locked out', 'all-in-one-wp-security-and-firewall'); ?></span>
278
  </td>
279
  </tr>
280
  <tr valign="top">
281
+ <th scope="row"><label for="aiowps_retry_time_period"><?php _e('Login Retry Time Period (min)', 'all-in-one-wp-security-and-firewall'); ?>:</label></th>
282
+ <td><input id="aiowps_retry_time_period" type="text" size="5" name="aiowps_retry_time_period" value="<?php echo esc_html($aio_wp_security->configs->get_value('aiowps_retry_time_period')); ?>" />
283
  <span class="description"><?php _e('If the maximum number of failed login attempts for a particular IP address occur within this time period the plugin will lock out that address', 'all-in-one-wp-security-and-firewall'); ?></span>
284
  </td>
285
  </tr>
286
  <tr valign="top">
287
+ <th scope="row">
288
+ <label for="aiowps_lockout_time_length">
289
+ <?php _e('Minimum lockout time length', 'all-in-one-wp-security-and-firewall'); ?>:
290
+ </label>
291
+ </th>
292
+ <td><input type="text" size="5" name="aiowps_lockout_time_length" id="aiowps_lockout_time_length" value="<?php echo esc_attr($aio_wp_security->configs->get_value('aiowps_lockout_time_length')); ?>" />
293
+ <span class="description">
294
+ <?php
295
+ echo __('Set the minimum time period in minutes of lockout.', 'all-in-one-wp-security-and-firewall').' '.
296
+ __('This failed login lockout time will be tripled on each failed login.', 'all-in-one-wp-security-and-firewall');
297
+ ?>
298
+ </span>
299
  </td>
300
  </tr>
301
+ <tr valign="top">
302
+ <th scope="row">
303
+ <label for="aiowps_max_lockout_time_length">
304
+ <?php _e('Maximum lockout time length', 'all-in-one-wp-security-and-firewall')?>:
305
+ </label>
306
+ </th>
307
+ <td><input type="text" size="5" name="aiowps_max_lockout_time_length" id="aiowps_max_lockout_time_length" value="<?php echo esc_attr($aio_wp_security->configs->get_value('aiowps_max_lockout_time_length')); ?>" />
308
+ <span class="description">
309
+ <?php
310
+ echo __('Set the maximum time period in minutes of lockout.', 'all-in-one-wp-security-and-firewall').' '.
311
+ __('No IP address will be blocked for more than this time period after making a failed login attempt.', 'all-in-one-wp-security-and-firewall')
312
+ ?>
313
+ </span>
314
+ </tr>
315
  <tr valign="top">
316
  <th scope="row"><?php _e('Display Generic Error Message', 'all-in-one-wp-security-and-firewall')?>:</th>
317
  <td>
318
+ <input id="aiowps_set_generic_login_msg" name="aiowps_set_generic_login_msg" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_set_generic_login_msg')=='1') echo ' checked="checked"'; ?> value="1"/>
319
+ <label for="aiowps_set_generic_login_msg" class="description"><?php _e('Check this if you want to show a generic error message when a login attempt fails', 'all-in-one-wp-security-and-firewall'); ?></label>
320
  </td>
321
  </tr>
322
  <tr valign="top">
323
  <th scope="row"><?php _e('Instantly Lockout Invalid Usernames', 'all-in-one-wp-security-and-firewall')?>:</th>
324
  <td>
325
+ <input id="aiowps_enable_invalid_username_lockdown" name="aiowps_enable_invalid_username_lockdown" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_invalid_username_lockdown')=='1') echo ' checked="checked"'; ?> value="1"/>
326
+ <label for="aiowps_enable_invalid_username_lockdown" class="description"><?php _e('Check this if you want to instantly lockout login attempts with usernames which do not exist on your system', 'all-in-one-wp-security-and-firewall'); ?></label>
327
  </td>
328
  </tr>
329
  <tr valign="top">
330
+ <th scope="row">
331
+ <label for="aiowps_instantly_lockout_specific_usernames">
332
+ <?php _e('Instantly Lockout Specific Usernames', 'all-in-one-wp-security-and-firewall')?>:
333
+ </label>
334
+ </th>
335
  <td>
336
+ <?php
337
  $instant_lockout_users_list = $aio_wp_security->configs->get_value('aiowps_instantly_lockout_specific_usernames');
338
  if(empty($instant_lockout_users_list)){
339
  $instant_lockout_users_list = array();
340
  }
341
  ?>
342
+ <textarea id="aiowps_instantly_lockout_specific_usernames" name="aiowps_instantly_lockout_specific_usernames" cols="50" rows="5"><?php echo esc_textarea(implode(PHP_EOL, $instant_lockout_users_list)); ?></textarea><br>
343
  <span class="description"><?php _e('Insert one username per line. Existing usernames are not blocked even if present in the list.', 'all-in-one-wp-security-and-firewall'); ?></span>
344
  </td>
345
  </tr>
346
  <tr valign="top">
347
+ <th scope="row">
348
+ <label for="aiowps_email_address">
349
+ <?php _e('Notify By Email', 'all-in-one-wp-security-and-firewall')?>:
350
+ </label>
351
+ </th>
352
  <td>
353
+ <input id="aiowps_enable_email_notify" name="aiowps_enable_email_notify" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_email_notify')=='1') echo ' checked="checked"'; ?> value="1"/>
354
+ <label for="aiowps_enable_email_notify" class="description"><?php _e('Check this if you want to receive an email when someone has been locked out due to maximum failed login attempts', 'all-in-one-wp-security-and-firewall'); ?></span></label>
355
+ <br />
356
+ <?php
357
+ $aiowps_email_address_list = (array)$aio_wp_security->configs->get_value('aiowps_email_address');
358
+ ?>
359
+ <textarea id="aiowps_email_address" name="aiowps_email_address" cols="50" rows="5"><?php echo esc_textarea(implode("\n", $aiowps_email_address_list)); ?></textarea><br>
360
+ <span class="description"><?php _e('Fill in one email address per line.', 'all-in-one-wp-security-and-firewall'); ?></span>
361
+ <span class="aiowps_more_info_anchor"><span class="aiowps_more_info_toggle_char">+</span><span class="aiowps_more_info_toggle_text"><?php _e('More Info', 'all-in-one-wp-security-and-firewall'); ?></span></span>
362
+ <div class="aiowps_more_info_body">
363
+ <?php
364
+ echo '<p class="description">'.__('Each email address must be on a new line.', 'all-in-one-wp-security-and-firewall').'</p>';
365
+ echo '<p class="description">'.__('If a valid email address has not been filled in, it will not be saved.', 'all-in-one-wp-security-and-firewall').'</p>';
366
+ echo '<p class="description">'.__('The valid email address format is userid@example.com', 'all-in-one-wp-security-and-firewall').'</p>';
367
+ echo '<p class="description">'.sprintf(__('Example: %s', 'all-in-one-wp-security-and-firewall'), 'rick@wordpress.org').'</p>';
368
+ ?>
369
+ </div>
370
+ </td>
371
+ </tr>
372
+ <tr valign="top">
373
+ <th scope="row">
374
+ <?php _e('Enable PHP Backtrace In Email', 'all-in-one-wp-security-and-firewall')?>:
375
+ </th>
376
+ <td>
377
+ <input name="aiowps_enable_php_backtrace_in_email" id="aiowps_enable_php_backtrace_in_email" type="checkbox"<?php checked($aio_wp_security->configs->get_value('aiowps_enable_php_backtrace_in_email'), '1'); ?> value="1"/>
378
+ <label for="aiowps_enable_php_backtrace_in_email"><?php _e('Check this if you want to include the PHP backtrace in notification emails.', 'all-in-one-wp-security-and-firewall'); ?> <?php _e('This is internal coding information which makes it easier to investigate where an issued occurred.', 'all-in-one-wp-security-and-firewall'); ?></label>
379
+ </td>
380
  </tr>
381
  </table>
382
  <input type="submit" name="aiowps_login_lockdown" value="<?php _e('Save Settings', 'all-in-one-wp-security-and-firewall')?>" class="button-primary" />
399
  <?php wp_nonce_field('aiowpsec-lockdown-whitelist-settings-nonce'); ?>
400
  <table class="form-table">
401
  <tr valign="top">
402
+ <th scope="row"><label for="aiowps_lockdown_enable_whitelisting"><?php _e('Enable Login Lockdown IP Whitelist', 'all-in-one-wp-security-and-firewall')?></label>:</th>
403
  <td>
404
+ <input id="aiowps_lockdown_enable_whitelisting" name="aiowps_lockdown_enable_whitelisting" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_lockdown_enable_whitelisting')=='1') echo ' checked="checked"'; ?> value="1"/>
405
  <span class="description"><?php _e('Check this if you want to enable the whitelisting of selected IP addresses specified in the settings below', 'all-in-one-wp-security-and-firewall'); ?></span>
406
  </td>
407
  </tr>
408
  <tr valign="top">
409
+ <th scope="row"><label for="aiowps_lockdown_allowed_ip_addresses"><?php _e('Enter Whitelisted IP Addresses:', 'all-in-one-wp-security-and-firewall')?></label></th>
410
  <td>
411
+ <textarea id="aiowps_lockdown_allowed_ip_addresses" name="aiowps_lockdown_allowed_ip_addresses" rows="5" cols="50"><?php echo esc_textarea(wp_unslash(-1 == $result ? $_POST['aiowps_lockdown_allowed_ip_addresses'] : $aio_wp_security->configs->get_value('aiowps_lockdown_allowed_ip_addresses'))); ?></textarea>
412
  <br />
413
+ <span class="description"><?php _e('Enter one or more IP addresses or IP ranges you wish to include in your whitelist.', 'all-in-one-wp-security-and-firewall') . ' ' . _e('The addresses specified here will never be blocked by the login lockdown feature.', 'all-in-one-wp-security-and-firewall');?></span>
414
+ <?php $aio_wp_security->include_template('info/ip-address-ip-range-info.php');?>
 
 
 
 
 
 
 
 
 
 
415
  </td>
416
  </tr>
417
  </table>
422
  <?php
423
  }
424
 
425
+ /**
426
+ * Renders the submenu's tab2 tab body.
427
+ *
428
+ * @return Void
429
+ */
430
+ public function render_tab2() {
431
  global $aio_wp_security, $wpdb;
432
  if (isset($_POST['aiowps_delete_failed_login_records']))
433
  {
448
  }
449
  else
450
  {
451
+ $this->show_msg_updated(__('All records from the Failed Logins table were deleted successfully.','all-in-one-wp-security-and-firewall'));
452
  }
453
  }
454
 
460
  $failed_login_list->delete_login_failed_records(strip_tags($_REQUEST['failed_login_id']));
461
  }
462
  }
463
+
 
464
  ?>
465
  <div class="aio_blue_box">
466
  <?php
478
  $failed_login_list->prepare_items();
479
  //echo "put table of locked entries here";
480
  ?>
481
+ <form id="tables-filter" method="post">
482
  <!-- For plugins, we also need to ensure that the form posts back to our current page -->
483
  <input type="hidden" name="page" value="<?php echo esc_attr($_REQUEST['page']); ?>" />
484
  <?php
587
  <tr valign="top">
588
  <th scope="row"><?php _e('Enable Force WP User Logout', 'all-in-one-wp-security-and-firewall')?>:</th>
589
  <td>
590
+ <input id="aiowps_enable_forced_logout" name="aiowps_enable_forced_logout" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_forced_logout')=='1') echo ' checked="checked"'; ?> value="1"/>
591
+ <label for="aiowps_enable_forced_logout" class="description"><?php _e('Check this if you want to force a wp user to be logged out after a configured amount of time', 'all-in-one-wp-security-and-firewall'); ?></label>
592
  </td>
593
  </tr>
594
  <tr valign="top">
595
+ <th scope="row"><label for="aiowps_logout_time_period"><?php _e('Logout the WP User After XX Minutes', 'all-in-one-wp-security-and-firewall')?></label>:</th>
596
+ <td><input id="aiowps_logout_time_period" type="text" size="5" name="aiowps_logout_time_period" value="<?php echo $aio_wp_security->configs->get_value('aiowps_logout_time_period'); ?>" />
597
  <span class="description"><?php _e('(Minutes) The user will be forced to log back in after this time period has elapased.', 'all-in-one-wp-security-and-firewall'); ?></span>
598
  </td>
599
  </tr>
603
  </div></div>
604
  <?php
605
  }
606
+
607
+ /**
608
+ * Renders the submenu's tab4 tab body.
609
+ *
610
+ * @return Void
611
+ */
612
+ public function render_tab4() {
613
  include_once 'wp-security-list-acct-activity.php'; //For rendering the AIOWPSecurity_List_Table in tab4
614
  $acct_activity_list = new AIOWPSecurity_List_Account_Activity(); //For rendering the AIOWPSecurity_List_Table in tab2
615
  if(isset($_REQUEST['action'])) //Do row action tasks for list table form for login activity display
618
  $acct_activity_list->delete_login_activity_records(strip_tags($_REQUEST['activity_login_rec']));
619
  }
620
  }
621
+
 
622
  ?>
623
  <div class="aio_blue_box">
624
  <?php
635
  $acct_activity_list->prepare_items();
636
  //echo "put table of locked entries here";
637
  ?>
638
+ <form id="tables-filter" method="post">
639
  <!-- For plugins, we also need to ensure that the form posts back to our current page -->
640
  <input type="hidden" name="page" value="<?php echo esc_attr($_REQUEST['page']); ?>" />
641
  <?php
663
  </div></div>
664
  <?php
665
  }
666
+
667
+ /**
668
+ * Renders the submenu's tab5 tab body.
669
+ *
670
+ * @return Void
671
+ */
672
+ public function render_tab5() {
673
  global $aio_wp_security;
674
+ $logged_in_users = (is_multisite() ? get_site_transient('users_online') : get_transient('users_online'));
675
 
676
  include_once 'wp-security-list-logged-in-users.php'; //For rendering the AIOWPSecurity_List_Table
677
  $user_list = new AIOWPSecurity_List_Logged_In_Users();
682
  }
683
  }
684
 
685
+ if (isset($_POST['aiowps_refresh_logged_in_user_list'])) {
 
686
  $nonce=$_REQUEST['_wpnonce'];
687
  if (!wp_verify_nonce($nonce, 'aiowpsec-logged-in-users-nonce'))
688
  {
702
  <input type="submit" name="aiowps_refresh_logged_in_user_list" value="<?php _e('Refresh Data', 'all-in-one-wp-security-and-firewall')?>" class="button-primary" />
703
  </form>
704
  </div></div>
705
+ <div class="aio_blue_box">
 
706
  <?php
707
  echo '<p>'.__('This tab displays all users who are currently logged into your site.', 'all-in-one-wp-security-and-firewall').'
708
  <br />'.__('If you suspect there is a user or users who are logged in which should not be, you can block them by inspecting the IP addresses from the data below and adding them to your blacklist.', 'all-in-one-wp-security-and-firewall').'
718
  $user_list->prepare_items();
719
  //echo "put table of locked entries here";
720
  ?>
721
+ <form id="tables-filter" method="get">
722
  <!-- For plugins, we also need to ensure that the form posts back to our current page -->
723
  <input type="hidden" name="page" value="<?php echo esc_attr($_REQUEST['page']); ?>" />
724
  <input type="hidden" name="tab" value="<?php echo esc_attr($_REQUEST['tab']); ?>" />
729
  <?php
730
 
731
  }
732
+
733
+ /**
734
+ * Shows additional tab and field for the disable application password and saves on submit.
735
+ *
736
+ * @global AIO_WP_Security $aio_wp_security
737
+ * @global AIOWPSecurity_Feature_Item_Manager $aiowps_feature_mgr
738
+ * @return void
739
+ */
740
+ public function render_additional_tab() {
741
+ global $aio_wp_security;
742
+ global $aiowps_feature_mgr;
743
+
744
+ if(isset($_POST['aiowpsec_save_additonal_settings'])) {
745
+ if (!wp_verify_nonce($_POST['_wpnonce'], 'aiowpsec-additonal-settings-nonce')) {
746
+ $aio_wp_security->debug_logger->log_debug("Nonce check failed on additonal settings save.", 4);
747
+ die("Nonce check failed on additonal settings save.");
748
+ }
749
+
750
+ //Save all the form values to the options
751
+ $aio_wp_security->configs->set_value('aiowps_disable_application_password', isset($_POST['aiowps_disable_application_password']) ? '1' : '');
752
+ $aio_wp_security->configs->save_config();
753
+
754
+ //Recalculate points after the feature status/options have been altered
755
+ $aiowps_feature_mgr->check_feature_status_and_recalculate_points();
756
+
757
+ $this->show_msg_settings_updated();
758
+ }
759
+ ?>
760
+ <div class="aio_blue_box">
761
+ <?php
762
+ echo '<p>'.__('WordPress 5.6 introduced a new feature called "Application Passwords".', 'all-in-one-wp-security-and-firewall').'
763
+ <br />'.__('This allows you to create a token from the WordPress dashboard which then can be used in the authorization header.', 'all-in-one-wp-security-and-firewall').'
764
+ <br /><br />'.__('This feature allows you to disable Application Passwords as they can leave your site vulnerable to social engineering and phishing scams.', 'all-in-one-wp-security-and-firewall').'
765
+ </p>';
766
+ ?>
767
+ </div>
768
+ <form action="" method="POST">
769
+ <div class="postbox">
770
+ <h3 class="hndle"><label for="title"><?php _e('Additonal Settings', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
771
+ <div class="inside">
772
+ <?php
773
+ //Display security info badge
774
+ global $aiowps_feature_mgr;
775
+ $aiowps_feature_mgr->output_feature_details_badge("disable-application-password");
776
+ ?>
777
+
778
+ <?php wp_nonce_field('aiowpsec-additonal-settings-nonce'); ?>
779
+ <table class="form-table">
780
+ <tr valign="top">
781
+ <th scope="row"><?php _e('Disable Application Password', 'all-in-one-wp-security-and-firewall')?>:</th>
782
+ <td>
783
+ <input name="aiowps_disable_application_password" id="aiowps_disable_application_password" type="checkbox" <?php checked($aio_wp_security->configs->get_value('aiowps_disable_application_password'), '1'); ?> value="1"/>
784
+ <label for="aiowps_disable_application_password"><?php _e('Check this if you want to disable the application password.', 'all-in-one-wp-security-and-firewall'); ?></label>
785
+ </td>
786
+ </tr>
787
+ </table>
788
+ </div></div>
789
+ <input type="submit" name="aiowpsec_save_additonal_settings" value="<?php _e('Save Settings', 'all-in-one-wp-security-and-firewall')?>" class="button-primary" />
790
+ </form>
791
+ <?php
792
+ }
793
 
794
+ } //end class
admin/wp-security-user-registration-menu.php CHANGED
@@ -129,7 +129,7 @@ class AIOWPSecurity_User_Registration_Menu extends AIOWPSecurity_Admin_Menu
129
  //Display security info badge
130
  $aiowps_feature_mgr->output_feature_details_badge("manually-approve-registrations");
131
  $blog_id = get_current_blog_id();
132
- if (AIOWPSecurity_Utility::is_multisite_install() && !is_main_site( $blog_id ))
133
  {
134
  //Hide config settings if MS and not main site
135
  AIOWPSecurity_Utility::display_multisite_message();
@@ -141,8 +141,8 @@ class AIOWPSecurity_User_Registration_Menu extends AIOWPSecurity_Admin_Menu
141
  <tr valign="top">
142
  <th scope="row"><?php _e('Enable manual approval of new registrations', 'all-in-one-wp-security-and-firewall')?>:</th>
143
  <td>
144
- <input name="aiowps_enable_manual_registration_approval" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_manual_registration_approval')=='1') echo ' checked="checked"'; ?> value="1"/>
145
- <span class="description"><?php _e('Check this if you want to automatically disable all newly registered accounts so that you can approve them manually.', 'all-in-one-wp-security-and-firewall'); ?></span>
146
  </td>
147
  </tr>
148
  </table>
@@ -213,7 +213,7 @@ class AIOWPSecurity_User_Registration_Menu extends AIOWPSecurity_Admin_Menu
213
  <h3 class="hndle"><label for="title"><?php _e('Registration Page Captcha Settings', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
214
  <div class="inside">
215
  <?php
216
- if (AIOWPSecurity_Utility::is_multisite_install() && get_current_blog_id() != 1)
217
  {
218
  //Hide config settings if MS and not main site
219
  $special_msg = '<div class="aio_yellow_box">';
@@ -235,8 +235,8 @@ class AIOWPSecurity_User_Registration_Menu extends AIOWPSecurity_Admin_Menu
235
  <tr valign="top">
236
  <th scope="row"><?php _e('Enable Captcha On Registration Page', 'all-in-one-wp-security-and-firewall')?>:</th>
237
  <td>
238
- <input name="aiowps_enable_registration_page_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_registration_page_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
239
- <span class="description"><?php _e('Check this if you want to insert a captcha form on the WordPress user registration page (if you allow user registration).', 'all-in-one-wp-security-and-firewall'); ?></span>
240
  </td>
241
  </tr>
242
  </table>
@@ -296,8 +296,8 @@ class AIOWPSecurity_User_Registration_Menu extends AIOWPSecurity_Admin_Menu
296
  <tr valign="top">
297
  <th scope="row"><?php _e('Enable Honeypot On Registration Page', 'all-in-one-wp-security-and-firewall')?>:</th>
298
  <td>
299
- <input name="aiowps_enable_registration_honeypot" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_registration_honeypot')=='1') echo ' checked="checked"'; ?> value="1"/>
300
- <span class="description"><?php _e('Check this if you want to enable the honeypot feature for the registration page', 'all-in-one-wp-security-and-firewall'); ?></span>
301
  </td>
302
  </tr>
303
  </table>
129
  //Display security info badge
130
  $aiowps_feature_mgr->output_feature_details_badge("manually-approve-registrations");
131
  $blog_id = get_current_blog_id();
132
+ if (is_multisite() && !is_main_site( $blog_id ))
133
  {
134
  //Hide config settings if MS and not main site
135
  AIOWPSecurity_Utility::display_multisite_message();
141
  <tr valign="top">
142
  <th scope="row"><?php _e('Enable manual approval of new registrations', 'all-in-one-wp-security-and-firewall')?>:</th>
143
  <td>
144
+ <input id="aiowps_enable_manual_registration_approval" name="aiowps_enable_manual_registration_approval" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_manual_registration_approval')=='1') echo ' checked="checked"'; ?> value="1"/>
145
+ <label for="aiowps_enable_manual_registration_approval" class="description"><?php _e('Check this if you want to automatically disable all newly registered accounts so that you can approve them manually.', 'all-in-one-wp-security-and-firewall'); ?></label>
146
  </td>
147
  </tr>
148
  </table>
213
  <h3 class="hndle"><label for="title"><?php _e('Registration Page Captcha Settings', 'all-in-one-wp-security-and-firewall'); ?></label></h3>
214
  <div class="inside">
215
  <?php
216
+ if (is_multisite() && get_current_blog_id() != 1)
217
  {
218
  //Hide config settings if MS and not main site
219
  $special_msg = '<div class="aio_yellow_box">';
235
  <tr valign="top">
236
  <th scope="row"><?php _e('Enable Captcha On Registration Page', 'all-in-one-wp-security-and-firewall')?>:</th>
237
  <td>
238
+ <input id="aiowps_enable_registration_page_captcha" name="aiowps_enable_registration_page_captcha" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_registration_page_captcha')=='1') echo ' checked="checked"'; ?> value="1"/>
239
+ <label for="aiowps_enable_registration_page_captcha" class="description"><?php _e('Check this if you want to insert a captcha form on the WordPress user registration page (if you allow user registration).', 'all-in-one-wp-security-and-firewall'); ?></label>
240
  </td>
241
  </tr>
242
  </table>
296
  <tr valign="top">
297
  <th scope="row"><?php _e('Enable Honeypot On Registration Page', 'all-in-one-wp-security-and-firewall')?>:</th>
298
  <td>
299
+ <input id="aiowps_enable_registration_honeypot" name="aiowps_enable_registration_honeypot" type="checkbox"<?php if($aio_wp_security->configs->get_value('aiowps_enable_registration_honeypot')=='1') echo ' checked="checked"'; ?> value="1"/>
300
+ <label for="aiowps_enable_registration_honeypot" class="description"><?php _e('Check this if you want to enable the honeypot feature for the registration page', 'all-in-one-wp-security-and-firewall'); ?></label>
301
  </td>
302
  </tr>
303
  </table>
classes/firewall/family/wp-security-firewall-families.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace AIOWPS\Firewall;
3
+
4
+ /**
5
+ * Our list of families
6
+ */
7
+ return array(
8
+ array('name' => '6G', 'priority' => 0),
9
+ );
classes/firewall/family/wp-security-firewall-family-builder.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace AIOWPS\Firewall;
3
+
4
+ /**
5
+ * Builds all our families
6
+ */
7
+ class Family_Builder {
8
+
9
+ /**
10
+ * Get our families sorted by priority
11
+ *
12
+ * @return array
13
+ */
14
+ public static function get_families() {
15
+
16
+ $family_list = include(AIOWPS_FIREWALL_DIR.'/family/wp-security-firewall-families.php');
17
+
18
+ //Prioritise the families
19
+ usort($family_list, function($member, $member2) {
20
+ if ($member['priority'] == $member2['priority']) {
21
+ return 0;
22
+ }
23
+ return ($member['priority'] > $member2['priority']) ? 1 : -1;
24
+ });
25
+
26
+ $families = array();
27
+ foreach ($family_list as $member) {
28
+ $families[strtolower($member['name'])] = new Family($member['name'], $member['priority']);
29
+ }
30
+
31
+ return $families;
32
+ }
33
+ }
classes/firewall/family/wp-security-firewall-family-collection.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace AIOWPS\Firewall;
3
+
4
+ /**
5
+ * Holds all our families
6
+ */
7
+ class Family_Collection {
8
+
9
+ /**
10
+ * Holds our families
11
+ *
12
+ * @var array
13
+ */
14
+ protected $families;
15
+
16
+
17
+ /**
18
+ * Constructs our family collection object
19
+ *
20
+ * @param array $families - The sorted families to contain
21
+ */
22
+ public function __construct($families = array()) {
23
+ $this->families = $families;
24
+ }
25
+
26
+ /**
27
+ * Generator method to iterate over the familes
28
+ *
29
+ * @return iterable
30
+ */
31
+ public function get_family() {
32
+ foreach ($this->families as $family) {
33
+ yield $family;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Adds a new rule to a family member
39
+ *
40
+ * @param Rule $rule - an active rule to add to its family
41
+ * @return void
42
+ */
43
+ public function add_rule_to_member(Rule $rule) {
44
+ $key = strtolower($rule->family);
45
+ if (array_key_exists($key, $this->families)) {
46
+ $this->families[$key]->add_rule($rule);
47
+ }
48
+ }
49
+ }
classes/firewall/family/wp-security-firewall-family.php ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace AIOWPS\Firewall;
3
+
4
+ /**
5
+ * Represents a family (a grouping of rules)
6
+ */
7
+ class Family {
8
+
9
+ /**
10
+ * Name of the family
11
+ *
12
+ * @var string
13
+ */
14
+ public $name;
15
+
16
+ /**
17
+ * Priority of the family (0 is the highest)
18
+ *
19
+ * @var int
20
+ */
21
+ public $priority;
22
+
23
+ /**
24
+ * List of rules to apply
25
+ *
26
+ * @var array
27
+ */
28
+ protected $rules;
29
+
30
+ /**
31
+ * Builds our family object
32
+ *
33
+ * @param string $name
34
+ * @param integer $priority
35
+ */
36
+ public function __construct($name, $priority = 999999) {
37
+ $this->name = $name;
38
+ $this->priority = $priority;
39
+ $this->rules = array();
40
+ }
41
+
42
+ /**
43
+ * Adds a rule to the family
44
+ *
45
+ * @param Rule $rule
46
+ * @return void
47
+ */
48
+ public function add_rule(Rule $rule) {
49
+ $this->rules[] = $rule;
50
+ }
51
+
52
+ /**
53
+ * Applies all the rules in the family
54
+ *
55
+ * @return void
56
+ */
57
+ public function apply_all() {
58
+
59
+ if (empty($this->rules)) {
60
+ return;
61
+ }
62
+
63
+ //ensure the rules are ordered by priority
64
+ usort($this->rules, function(Rule $rule, Rule $rule2) {
65
+ if ($rule->priority == $rule2->priority) {
66
+ return 0;
67
+ }
68
+ return ($rule->priority > $rule2->priority) ? 1 : -1;
69
+ });
70
+
71
+ foreach ($this->rules as $rule) {
72
+ $rule->apply();
73
+ }
74
+
75
+ }
76
+
77
+ /**
78
+ * Returns the family name if used as a string
79
+ *
80
+ * @return string
81
+ */
82
+ public function __toString() {
83
+ return $this->name;
84
+ }
85
+
86
+ }
classes/firewall/libs/wp-security-firewall-config.php ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace AIOWPS\Firewall;
3
+
4
+ /**
5
+ * Gives us access to our firewall's config
6
+ */
7
+ class Config {
8
+
9
+ /**
10
+ * The path to our config file
11
+ *
12
+ * @var string
13
+ */
14
+ protected $path;
15
+
16
+ /**
17
+ * Constructs object
18
+ *
19
+ * @param string $path
20
+ */
21
+ public function __construct($path) {
22
+ $this->path = $path;
23
+ $this->init_file();
24
+ }
25
+
26
+ /**
27
+ * Initialise the file if it doesn't exist
28
+ *
29
+ * @return void
30
+ */
31
+ private function init_file() {
32
+ clearstatcache();
33
+ if (!file_exists($this->path)) {
34
+ @file_put_contents($this->path, $this->get_file_content_prefix() . json_encode(array())); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Get the config file's prefix content. N.B. Some code assumes that this doesn't change, so review all consumers of this method before changing its output.
40
+ *
41
+ * @return string
42
+ */
43
+ private function get_file_content_prefix() {
44
+ return "<?php __halt_compiler();\n";
45
+ }
46
+
47
+ /**
48
+ * Gets the value from the config array
49
+ *
50
+ * @param string $key
51
+ * @return mixed|null
52
+ */
53
+ public function get_value($key) {
54
+
55
+ $contents = $this->get_contents();
56
+
57
+ if (null === $contents) {
58
+ return null;
59
+ }
60
+
61
+ if (!isset($contents[$key])) {
62
+ return null;
63
+ }
64
+
65
+ return $contents[$key];
66
+
67
+ }
68
+
69
+ /**
70
+ * Sets a value in our config array
71
+ *
72
+ * @param string $key
73
+ * @param mixed $value
74
+ * @return boolean
75
+ */
76
+ public function set_value($key, $value) {
77
+
78
+ $contents = $this->get_contents();
79
+
80
+ if (null === $contents) {
81
+ return false;
82
+ }
83
+
84
+ $contents[$key] = $value;
85
+
86
+ return (false !== @file_put_contents($this->path, $this->get_file_content_prefix() . json_encode($contents), LOCK_EX)); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
87
+
88
+
89
+ }
90
+
91
+ /**
92
+ * Loads the config array from file
93
+ *
94
+ * @return string
95
+ */
96
+ private function get_contents() {
97
+ // __COMPILER_HALT_OFFSET__ doesn't define in a few PHP versions. It's a PHP bug.
98
+ // https://bugs.php.net/bug.php?id=70164
99
+ $contents = @file_get_contents($this->path, false, null, strlen($this->get_file_content_prefix())); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
100
+
101
+ if (false === $contents) {
102
+ return null;
103
+ }
104
+
105
+ if (empty($contents)) {
106
+ return array();
107
+ }
108
+
109
+ return json_decode($contents, true);
110
+ }
111
+
112
+ /**
113
+ * Returns the path
114
+ *
115
+ * @return string
116
+ */
117
+ public function __toString() {
118
+ return $this->path;
119
+ }
120
+
121
+ }
classes/firewall/rule/actions/action-exit-trait.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace AIOWPS\Firewall;
3
+
4
+ /**
5
+ * Trait which exits the current request
6
+ */
7
+ trait Action_Exit_Trait {
8
+
9
+ /**
10
+ * Exit when the rule condition is satisfied.
11
+ *
12
+ * @return void
13
+ */
14
+ public function do_action() {
15
+ exit();
16
+ }
17
+ }
classes/firewall/rule/actions/action-forbid-and-exit-trait.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace AIOWPS\Firewall;
3
+
4
+ /**
5
+ * Combines the forbid and exit trait
6
+ */
7
+ trait Action_Forbid_and_Exit_Trait {
8
+
9
+ use Action_Forbid_Trait, Action_Exit_Trait {
10
+ Action_Forbid_Trait::do_action as protected do_action_forbid;
11
+ Action_Exit_Trait::do_action as protected do_action_exit;
12
+ }
13
+
14
+ /**
15
+ * Forbid 403 and Exit when the rule condition is satisfied.
16
+ *
17
+ * @return void
18
+ */
19
+ public function do_action() {
20
+ $this->do_action_forbid();
21
+ $this->do_action_exit();
22
+ }
23
+ }
classes/firewall/rule/actions/action-forbid-trait.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace AIOWPS\Firewall;
3
+
4
+ /**
5
+ * Trait to set the header to forbidden
6
+ */
7
+ trait Action_Forbid_Trait {
8
+
9
+ /**
10
+ * Forbid 403 when the rule condition is satisfied.
11
+ *
12
+ * @return void
13
+ */
14
+ public function do_action() {
15
+ header('HTTP/1.1 403 Forbidden');
16
+ }
17
+ }
classes/firewall/rule/rules/rule-block-query-strings-6g.php ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace AIOWPS\Firewall;
3
+
4
+ /**
5
+ * Rule that blocks certain kinds of data from the query string
6
+ */
7
+ class Rule_Block_Query_Strings_6g extends Rule {
8
+
9
+ /**
10
+ * Implements the action to be taken
11
+ */
12
+ use Action_Forbid_and_Exit_Trait;
13
+
14
+ /**
15
+ * Construct our rule
16
+ */
17
+ public function __construct() {
18
+ // Set the rule's metadata
19
+ $this->name = 'Block query strings';
20
+ $this->family = '6G';
21
+ $this->priority = 0;
22
+ }
23
+
24
+ /**
25
+ * Determines whether the rule is active
26
+ *
27
+ * @return boolean
28
+ */
29
+ public function is_active() {
30
+ global $aiowps_firewall_config;
31
+ return (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_query');
32
+ }
33
+
34
+ /**
35
+ * The condition to be satisfied for the rule to apply
36
+ *
37
+ * @return boolean
38
+ */
39
+ public function is_satisfied() {
40
+
41
+ if (empty($_SERVER['QUERY_STRING'])) return !Rule::SATISFIED;
42
+
43
+ //Patterns to match against
44
+ $patterns = array(
45
+ '/[a-z0-9]{2000,}/i',
46
+ '/(eval\()/i',
47
+ '/(127\.0\.0\.1)/i',
48
+ '/(javascript:)(.*)(;)/i',
49
+ '/(base64_encode)(.*)(\()/i',
50
+ '/(GLOBALS|REQUEST)(=|\[|%)/i',
51
+ '/(<|%3C)(.*)script(.*)(>|%3)/i',
52
+ '#(\|\.\.\.|\.\./|~|`|<|>|\|)#i',
53
+ '#(boot\.ini|etc/passwd|self/environ)#i',
54
+ '/(thumbs?(_editor|open)?|tim(thumb)?)\.php/i',
55
+ '/(\'|\")(.*)(drop|insert|md5|select|union)/i',
56
+ );
57
+
58
+ return Rule_Utils::contains_pattern(rawurldecode($_SERVER['QUERY_STRING']), $patterns);
59
+ }
60
+
61
+ }
classes/firewall/rule/rules/rule-block-refs-6g.php ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace AIOWPS\Firewall;
3
+
4
+ /**
5
+ * Rule that blocks certain referrers recommended by 6G
6
+ */
7
+ class Rule_Block_Refs_6g extends Rule {
8
+
9
+ /**
10
+ * Implements the action to be taken
11
+ */
12
+ use Action_Forbid_and_Exit_Trait;
13
+
14
+ /**
15
+ * Construct our rule
16
+ */
17
+ public function __construct() {
18
+ // Set the rule's metadata
19
+ $this->name = 'Block referrer strings';
20
+ $this->family = '6G';
21
+ $this->priority = 0;
22
+ }
23
+
24
+ /**
25
+ * Determines whether the rule is active
26
+ *
27
+ * @return boolean
28
+ */
29
+ public function is_active() {
30
+ global $aiowps_firewall_config;
31
+ return (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_referrers');
32
+ }
33
+
34
+ /**
35
+ * The condition to be satisfied for the rule to apply
36
+ *
37
+ * @return boolean
38
+ */
39
+ public function is_satisfied() {
40
+
41
+ if (empty($_SERVER['HTTP_REFERER'])) return !Rule::SATISFIED;
42
+
43
+ //Patterns to match against
44
+ $patterns = array(
45
+ '/[a-z0-9]{2000,}/i',
46
+ '/(semalt.com|todaperfeita)/i',
47
+ );
48
+
49
+ return Rule_Utils::contains_pattern($_SERVER['HTTP_REFERER'], $patterns);
50
+ }
51
+
52
+ }
classes/firewall/rule/rules/rule-block-request-strings-6g.php ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace AIOWPS\Firewall;
3
+
4
+ /**
5
+ * Rule that blocks certain kinds of data from the request string
6
+ */
7
+ class Rule_Block_Request_Strings_6g extends Rule {
8
+
9
+ /**
10
+ * Implements the action to be taken
11
+ */
12
+ use Action_Forbid_and_Exit_Trait;
13
+
14
+ /**
15
+ * Construct our rule
16
+ */
17
+ public function __construct() {
18
+ // Set the rule's metadata
19
+ $this->name = 'Block request strings';
20
+ $this->family = '6G';
21
+ $this->priority = 0;
22
+ }
23
+
24
+ /**
25
+ * Determines whether the rule is active
26
+ *
27
+ * @return boolean
28
+ */
29
+ public function is_active() {
30
+ global $aiowps_firewall_config;
31
+ return (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_request');
32
+ }
33
+
34
+ /**
35
+ * The condition to be satisfied for the rule to apply
36
+ *
37
+ * @return boolean
38
+ */
39
+ public function is_satisfied() {
40
+
41
+ if (empty($_SERVER['PHP_SELF'])) return !Rule::SATISFIED;
42
+
43
+ //Patterns to match against
44
+ $patterns = array(
45
+ '/[a-z0-9]{2000,}/i',
46
+ '#(https?|ftp|php):/#i',
47
+ '#(base64_encode)(.*)(\()#i',
48
+ '#(=\'|=\%27|/\'/?)\.#i',
49
+ '#/(\$(\&)?|\*|\"|\.|,|&|&amp;?)/?$#i',
50
+ '#(\{0\}|\(/\(|\.\.\.|\+\+\+|\\"\\")#i',
51
+ '#(~|`|<|>|:|;|,|%|\|\s|\{|\}|\[|\]|\|)#i',
52
+ '#/(=|\$&|_mm|cgi-|etc/passwd|muieblack)#i',
53
+ '#(&pws=0|_vti_|\(null\)|\{\$itemURL\}|echo(.*)kae|etc/passwd|eval\(|self/environ)#i',
54
+ '#\.(aspx?|bash|bak?|cfg|cgi|dll|exe|git|hg|ini|jsp|log|mdb|out|sql|svn|swp|tar|rar|rdf)$#i',
55
+ '#/(^$|(wp-)?config|mobiquo|phpinfo|shell|sqlpatch|thumb|thumb_editor|thumbopen|timthumb|webshell)\.php#i',
56
+ );
57
+
58
+ return Rule_Utils::contains_pattern(rawurldecode($_SERVER['PHP_SELF']), $patterns);
59
+ }
60
+
61
+ }
classes/firewall/rule/rules/rule-block-user-agents-6g.php ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace AIOWPS\Firewall;
3
+
4
+ /**
5
+ * Rule that blocks certain user-agents recommended by 6G
6
+ */
7
+ class Rule_Block_User_Agents_6g extends Rule {
8
+
9
+ /**
10
+ * Implements the action to be taken
11
+ */
12
+ use Action_Forbid_and_Exit_Trait;
13
+
14
+ /**
15
+ * Construct our rule
16
+ */
17
+ public function __construct() {
18
+ // Set the rule's metadata
19
+ $this->name = 'Block user-agents';
20
+ $this->family = '6G';
21
+ $this->priority = 0;
22
+ }
23
+
24
+ /**
25
+ * Determines whether the rule is active
26
+ *
27
+ * @return boolean
28
+ */
29
+ public function is_active() {
30
+ global $aiowps_firewall_config;
31
+ return (bool) $aiowps_firewall_config->get_value('aiowps_6g_block_agents');
32
+ }
33
+
34
+ /**
35
+ * The condition to be satisfied for the rule to apply
36
+ *
37
+ * @return boolean
38
+ */
39
+ public function is_satisfied() {
40
+
41
+ if (empty($_SERVER['HTTP_USER_AGENT'])) return !Rule::SATISFIED;
42
+
43
+ //Patterns to match against
44
+ $patterns = array(
45
+ '/[a-z0-9]{2000,}/i',
46
+ '/(archive.org|binlar|casper|checkpriv|choppy|clshttp|cmsworld|diavol|dotbot|extract|feedfinder|flicky|g00g1e|harvest|heritrix|httrack|kmccrew|loader|miner|nikto|nutch|planetwork|postrank|purebot|pycurl|python|seekerspider|siclab|skygrid|sqlmap|sucker|turnit|vikspider|winhttp|xxxyy|youda|zmeu|zune)/i',
47
+ );
48
+
49
+ return Rule_Utils::contains_pattern($_SERVER['HTTP_USER_AGENT'], $patterns);
50
+ }
51
+
52
+ }
classes/firewall/rule/rules/rule-request-method-6g.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace AIOWPS\Firewall;
3
+
4
+ /**
5
+ * Rule that blocks certain kinds of HTTP request methods (e.g DEBUG or PUT)
6
+ */
7
+ class Rule_Request_Method_6g extends Rule {
8
+
9
+ /**
10
+ * Implements the action to be taken
11
+ */
12
+ use Action_Forbid_and_Exit_Trait;
13
+
14
+ /**
15
+ * List of request methods to block
16
+ *
17
+ * @var array
18
+ */
19
+ private $blocked_methods;
20
+
21
+ /**
22
+ * Construct our rule
23
+ */
24
+ public function __construct() {
25
+ global $aiowps_firewall_config;
26
+
27
+ // Set the rule's metadata
28
+ $this->name = 'Block request methods';
29
+ $this->family = '6G';
30
+ $this->priority = 0;
31
+
32
+ $this->blocked_methods = $aiowps_firewall_config->get_value('aiowps_6g_block_request_methods');
33
+ }
34
+
35
+ /**
36
+ * Determines whether the rule is active
37
+ *
38
+ * @return boolean
39
+ */
40
+ public function is_active() {
41
+ return !empty($this->blocked_methods);
42
+ }
43
+
44
+ /**
45
+ * The condition to be satisfied for the rule to apply
46
+ *
47
+ * @return boolean
48
+ */
49
+ public function is_satisfied() {
50
+ return in_array(strtoupper($_SERVER['REQUEST_METHOD']), $this->blocked_methods);
51
+ }
52
+
53
+ }
classes/firewall/rule/wp-security-firewall-rule-builder.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace AIOWPS\Firewall;
3
+
4
+ /**
5
+ * Builds our rules
6
+ */
7
+ class Rule_Builder {
8
+
9
+ /**
10
+ * Gets our rule if it's active
11
+ *
12
+ * @return iterable
13
+ */
14
+ public static function get_active_rule() {
15
+
16
+ foreach (self::get_rule_classname() as $classname) {
17
+
18
+ $rule = new $classname();
19
+
20
+ if (!$rule->is_active()) {
21
+ continue;
22
+ }
23
+
24
+ yield $rule;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Generates the classname for each rule
30
+ *
31
+ * @return iterable
32
+ */
33
+ private static function get_rule_classname() {
34
+
35
+ $handle = opendir(AIOWPS_FIREWALL_DIR.'/rule/rules/');
36
+ if ($handle) {
37
+ while (false !== ($entry = readdir($handle))) {
38
+ $matches = array();
39
+ if (preg_match('/^rule-(.*)\.php$/', $entry, $matches)) {
40
+ yield "AIOWPS\Firewall\Rule_".ucwords(str_replace('-', '_', $matches[1]), '_');
41
+ }
42
+ }
43
+ @closedir($handle); //phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
44
+ }
45
+
46
+ }
47
+
48
+ }
classes/firewall/rule/wp-security-firewall-rule-utils.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace AIOWPS\Firewall;
3
+
4
+ /**
5
+ * Utility methods to help with the rules
6
+ */
7
+ class Rule_Utils {
8
+
9
+ /**
10
+ * Check if the subject contains the given pattern or patterns
11
+ *
12
+ * @param string $subject - The subject we wish to check the pattern or patterns against.
13
+ * @param string|array $pattern - Regex pattern. An array for multiple patterns; a string otherwise.
14
+ * @return boolean
15
+ */
16
+ public static function contains_pattern($subject, $pattern) {
17
+
18
+ if (is_string($pattern)) return (1 === preg_match($pattern, $subject));
19
+
20
+ if (!is_array($pattern)) return false;
21
+
22
+ foreach ($pattern as $patt) {
23
+ if (preg_match($patt, $subject)) return true;
24
+ }
25
+
26
+ return false;
27
+ }
28
+
29
+ }
classes/firewall/rule/wp-security-firewall-rule.php ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace AIOWPS\Firewall;
3
+
4
+ /**
5
+ * Base class for our firewall rules
6
+ */
7
+ abstract class Rule {
8
+
9
+ /**
10
+ * Name of the rule
11
+ *
12
+ * @var string
13
+ */
14
+ public $name;
15
+
16
+ /**
17
+ * Name of the family the rule belongs to
18
+ *
19
+ * @var string
20
+ */
21
+ public $family;
22
+
23
+ /**
24
+ * Rule's priority (0 is the highest)
25
+ *
26
+ * @var int
27
+ */
28
+ public $priority;
29
+
30
+ /**
31
+ * An abstraction for when the rule is satisfied
32
+ *
33
+ * @var boolean
34
+ */
35
+ const SATISFIED = true;
36
+
37
+ /**
38
+ * Executes the rule's action
39
+ *
40
+ * @return void
41
+ */
42
+ abstract public function do_action();
43
+
44
+ /**
45
+ * Check if the rule is active
46
+ *
47
+ * @return boolean
48
+ */
49
+ abstract public function is_active();
50
+
51
+ /**
52
+ * Check if the rule has been satisfied
53
+ *
54
+ * @return boolean
55
+ */
56
+ abstract public function is_satisfied();
57
+
58
+ /**
59
+ * Apply the rule and execute the action if satisfied
60
+ *
61
+ * @return void
62
+ */
63
+ public function apply() {
64
+
65
+ if ($this->is_satisfied()) {
66
+
67
+ if (defined('AIOS_FIREWALL_DEBUG') && AIOS_FIREWALL_DEBUG) {
68
+ error_log("AIOS firewall rule triggered: {$this->name}");
69
+
70
+ if (defined('AIOS_FIREWALL_SERVER_DUMP') && AIOS_FIREWALL_SERVER_DUMP) {
71
+ error_log(print_r($_SERVER, true));
72
+ }
73
+ }
74
+
75
+
76
+
77
+ $this->do_action();
78
+ }
79
+
80
+ }
81
+
82
+ /**
83
+ * Show the rule's name
84
+ *
85
+ * @return string
86
+ */
87
+ public function __toString() {
88
+ return $this->name;
89
+ }
90
+ }
classes/firewall/wp-security-firewall-loader.php ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace AIOWPS\Firewall;
3
+
4
+ if (!defined('AIOWPS_FIREWALL_DIR')) {
5
+ header('HTTP/1.1 403 Forbidden');
6
+ exit();
7
+ }
8
+
9
+ /**
10
+ * Loads and executes our firewall
11
+ */
12
+ class Loader {
13
+
14
+ /**
15
+ * Reference to itself
16
+ *
17
+ * @var Loader
18
+ */
19
+ protected static $instance = null;
20
+
21
+ /**
22
+ * Loads and builds all the necessary files
23
+ *
24
+ * @return void
25
+ */
26
+ public function load_firewall() {
27
+
28
+ try {
29
+
30
+ $this->init_includes();
31
+ $this->init_services();
32
+
33
+ $families = new Family_Collection(Family_Builder::get_families());
34
+
35
+ foreach (Rule_Builder::get_active_rule() as $rule) {
36
+ $families->add_rule_to_member($rule);
37
+ }
38
+
39
+ foreach ($families->get_family() as $member) {
40
+ $member->apply_all();
41
+ }
42
+
43
+ } catch (\Exception $e) {
44
+ $this->log_message($e->getMessage());
45
+ } catch (\Error $e) {
46
+ $this->log_message($e->getMessage());
47
+ }
48
+
49
+ }
50
+
51
+ /**
52
+ * Log our error messages
53
+ *
54
+ * @param string $message
55
+ * @return void
56
+ */
57
+ private function log_message($message) {
58
+ if (function_exists('do_action')) {
59
+ do_action('aios_firewall_loader_log_error', $message, $this);
60
+ }
61
+
62
+ error_log('AIOS firewall error: ' . $message);
63
+ }
64
+
65
+ /**
66
+ * Initialises our services
67
+ *
68
+ * @return void
69
+ */
70
+ private function init_services() {
71
+
72
+ $workspace = $this->get_firewall_workspace();
73
+
74
+ if (empty($workspace)) {
75
+ throw new \Exception('unable to locate workspace.');
76
+ }
77
+
78
+ $GLOBALS['aiowps_firewall_config'] = new Config($workspace . 'settings.php');
79
+
80
+ }
81
+
82
+ /**
83
+ * Get our workspace directory (i.e: where we save and load data to and from)
84
+ *
85
+ * @return string
86
+ */
87
+ private function get_firewall_workspace() {
88
+ global $aiowps_firewall_rules_path;
89
+ $workspace = '';
90
+
91
+ if (!empty($aiowps_firewall_rules_path)) {
92
+ $workspace = $aiowps_firewall_rules_path;
93
+ } else {
94
+ //Are we in a WordPress context?
95
+ if (function_exists('wp_get_upload_dir')) {
96
+ $upload_dir_info = wp_get_upload_dir();
97
+ $firewall_rules_path = trailingslashit($upload_dir_info['basedir'].'/aios/firewall-rules');
98
+ $workspace = wp_normalize_path($firewall_rules_path);
99
+ }
100
+ }
101
+
102
+ return $workspace;
103
+ }
104
+
105
+ /**
106
+ * Registers the autoloader
107
+ *
108
+ * @return void
109
+ */
110
+ private function init_includes() {
111
+
112
+ spl_autoload_register(function($class) {
113
+ if (0 === strpos($class, "AIOWPS\\Firewall\\")) { //only autoload the firewall's files
114
+
115
+ $relative_classname = substr($class, strlen("AIOWPS\\Firewall\\"), strlen($class)-1);
116
+
117
+ $classname = str_replace('_', '-', strtolower($relative_classname));
118
+
119
+ $file = "wp-security-firewall-{$classname}.php";
120
+ $rule = "{$classname}.php";
121
+
122
+ $paths = array(
123
+ AIOWPS_FIREWALL_DIR."/{$file}",
124
+ AIOWPS_FIREWALL_DIR."/family/{$file}",
125
+ AIOWPS_FIREWALL_DIR."/rule/{$file}",
126
+ AIOWPS_FIREWALL_DIR."/rule/actions/{$classname}.php",
127
+ AIOWPS_FIREWALL_DIR."/rule/rules/{$rule}",
128
+ AIOWPS_FIREWALL_DIR."/libs/{$file}",
129
+ );
130
+
131
+ clearstatcache();
132
+ foreach ($paths as $path) {
133
+ if (file_exists($path)) {
134
+ include_once($path);
135
+ break;
136
+ }
137
+ }
138
+ }
139
+ });
140
+
141
+ }
142
+
143
+ /**
144
+ * Gets or creates an instance of this object
145
+ *
146
+ * @return Loader
147
+ */
148
+ public static function get_instance() {
149
+
150
+ if (null === self::$instance) {
151
+ return new self();
152
+ }
153
+
154
+ return self::$instance;
155
+ }
156
+
157
+ }
classes/firewall/wp-security-firewall.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This is the file that will be loaded using auto_prepend_file directive
4
+ */
5
+
6
+ if (!defined('AIOWPS_FIREWALL_DIR')) {
7
+ define('AIOWPS_FIREWALL_DIR', dirname(__FILE__));
8
+ }
9
+
10
+ if (!defined('AIOWPSEC_FIREWALL_DONE')) {
11
+
12
+ //Gracefully handle if the file is unable to be included. (i.e: ensure the user's site does not crash)
13
+ if (!(@include_once AIOWPS_FIREWALL_DIR . '/wp-security-firewall-loader.php')) {
14
+ error_log('AIOS firewall error: unable to load the firewall.');
15
+ return;
16
+ }
17
+
18
+ \AIOWPS\Firewall\Loader::get_instance()->load_firewall();
19
+ define('AIOWPSEC_FIREWALL_DONE', true);
20
+ }
classes/grade-system/wp-security-feature-item-manager.php CHANGED
@@ -72,10 +72,10 @@ class AIOWPSecurity_Feature_Item_Manager {
72
  $this->feature_items[] = new AIOWPSecurity_Feature_Item("registration-honeypot", __("Enable Registration Honeypot", "all-in-one-wp-security-and-firewall"), $this->feature_point_2, $this->sec_level_inter);
73
 
74
  //Database Security Menu Features
75
- //DB Prefix
76
- $this->feature_items[] = new AIOWPSecurity_Feature_Item("db-security-db-prefix", __("DB Prefix", "all-in-one-wp-security-and-firewall"), $this->feature_point_2, $this->sec_level_inter);
77
- //DB Backup
78
- $this->feature_items[] = new AIOWPSecurity_Feature_Item("db-security-db-backup", __("DB Backup", "all-in-one-wp-security-and-firewall"), $this->feature_point_4, $this->sec_level_basic);
79
 
80
  //File System Security Menu Features
81
  //File Permissions
@@ -102,6 +102,9 @@ class AIOWPSecurity_Feature_Item_Manager {
102
  //Login Honeypot
103
  $this->feature_items[] = new AIOWPSecurity_Feature_Item("login-honeypot", __("Enable Login Honeypot", "all-in-one-wp-security-and-firewall"), $this->feature_point_2, $this->sec_level_inter);
104
 
 
 
 
105
  //Additional and Advanced firewall
106
  $this->feature_items[] = new AIOWPSecurity_Feature_Item("firewall-enable-brute-force-attack-prevention", __("Enable Brute Force Attack Prevention", "all-in-one-wp-security-and-firewall"), $this->feature_point_4, $this->sec_level_advanced);
107
  $this->feature_items[] = new AIOWPSecurity_Feature_Item("firewall-disable-index-views", __("Disable Index Views", "all-in-one-wp-security-and-firewall"), $this->feature_point_1, $this->sec_level_inter);
@@ -311,6 +314,10 @@ class AIOWPSecurity_Feature_Item_Manager {
311
  if ("login-honeypot" == $item->feature_id) {
312
  $this->check_enable_login_honeypot_feature($item);
313
  }
 
 
 
 
314
 
315
  if ("block-spambots" == $item->feature_id) {
316
  $this->check_enable_block_spambots_feature($item);
@@ -702,6 +709,22 @@ class AIOWPSecurity_Feature_Item_Manager {
702
  $item->set_feature_status($this->feature_inactive);
703
  }
704
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
705
 
706
  public function check_enable_block_spambots_feature($item) {
707
  global $aio_wp_security;
72
  $this->feature_items[] = new AIOWPSecurity_Feature_Item("registration-honeypot", __("Enable Registration Honeypot", "all-in-one-wp-security-and-firewall"), $this->feature_point_2, $this->sec_level_inter);
73
 
74
  //Database Security Menu Features
75
+ //Database prefix
76
+ $this->feature_items[] = new AIOWPSecurity_Feature_Item("db-security-db-prefix", __("Database prefix", "all-in-one-wp-security-and-firewall"), $this->feature_point_2, $this->sec_level_inter);
77
+ //Database backup
78
+ $this->feature_items[] = new AIOWPSecurity_Feature_Item("db-security-db-backup", __("Database backup", "all-in-one-wp-security-and-firewall"), $this->feature_point_4, $this->sec_level_basic);
79
 
80
  //File System Security Menu Features
81
  //File Permissions
102
  //Login Honeypot
103
  $this->feature_items[] = new AIOWPSecurity_Feature_Item("login-honeypot", __("Enable Login Honeypot", "all-in-one-wp-security-and-firewall"), $this->feature_point_2, $this->sec_level_inter);
104
 
105
+ //Disabled application password feture set points and level
106
+ $this->feature_items[] = new AIOWPSecurity_Feature_Item('disable-application-password', __('Disable Application Password', 'all-in-one-wp-security-and-firewall'), $this->feature_point_2, $this->sec_level_inter);
107
+
108
  //Additional and Advanced firewall
109
  $this->feature_items[] = new AIOWPSecurity_Feature_Item("firewall-enable-brute-force-attack-prevention", __("Enable Brute Force Attack Prevention", "all-in-one-wp-security-and-firewall"), $this->feature_point_4, $this->sec_level_advanced);
110
  $this->feature_items[] = new AIOWPSecurity_Feature_Item("firewall-disable-index-views", __("Disable Index Views", "all-in-one-wp-security-and-firewall"), $this->feature_point_1, $this->sec_level_inter);
314
  if ("login-honeypot" == $item->feature_id) {
315
  $this->check_enable_login_honeypot_feature($item);
316
  }
317
+
318
+ if ("disable-application-password" == $item->feature_id) {
319
+ $this->check_disable_application_password_feature($item);
320
+ }
321
 
322
  if ("block-spambots" == $item->feature_id) {
323
  $this->check_enable_block_spambots_feature($item);
709
  $item->set_feature_status($this->feature_inactive);
710
  }
711
  }
712
+
713
+ /**
714
+ * Featurs list updated based on the disabled appliction password on or off
715
+ *
716
+ * @param object $item
717
+ * @global AIO_WP_Security $aio_wp_security
718
+ * @return void
719
+ */
720
+ public function check_disable_application_password_feature($item) {
721
+ global $aio_wp_security;
722
+ if ('1' == $aio_wp_security->configs->get_value('aiowps_disable_application_password')) {
723
+ $item->set_feature_status($this->feature_active);
724
+ } else {
725
+ $item->set_feature_status($this->feature_inactive);
726
+ }
727
+ }
728
 
729
  public function check_enable_block_spambots_feature($item) {
730
  global $aio_wp_security;
classes/wp-security-abstract-ids.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if (!defined('ABSPATH')) {
3
+ exit; //Exit if accessed directly
4
+ }
5
+
6
+ /**
7
+ * All ids and static names, array.
8
+ */
9
+ class AIOS_Abstracted_Ids {
10
+
11
+ /**
12
+ * Get firewall block request methods.
13
+ *
14
+ * @return array
15
+ */
16
+ public static function get_firewall_block_request_methods() {
17
+ return array('DEBUG','MOVE', 'PUT', 'TRACK');
18
+ }
19
+
20
+ }
classes/wp-security-backup.php DELETED
@@ -1,339 +0,0 @@
1
- <?php
2
- if (!defined('ABSPATH')) {
3
- exit;//Exit if accessed directly
4
- }
5
-
6
- class AIOWPSecurity_Backup {
7
-
8
- public $last_backup_file_name;//Stores the name of the last backup file when execute_backup function is called
9
-
10
- public $last_backup_file_path;
11
-
12
- public $last_backup_file_dir_multisite;
13
-
14
- public function __construct() {
15
- add_action('aiowps_perform_scheduled_backup_tasks', array($this, 'aiowps_scheduled_backup_handler'));
16
- add_action('aiowps_perform_db_cleanup_tasks', array($this, 'aiowps_scheduled_db_cleanup_handler'));
17
- }
18
-
19
- /**
20
- * Add slashes, sanitize end-of-line characters (?), wrap $value in quotation marks.
21
- *
22
- * @param string $value
23
- * @return string
24
- */
25
- public function sanitize_db_field($value) {
26
- return '"' . preg_replace("/".PHP_EOL."/", "\n", addslashes($value)) . '"';
27
- }
28
-
29
- /**
30
- * Write sql dump of $tables to backup file identified by $handle.
31
- *
32
- * @global wpdb $wpdb WordPress database abstraction object.
33
- * @global AIO_WP_Security $aio_wp_security
34
- * @param resource $handle
35
- * @param array $tables
36
- * @return boolean True, if database tables dump have been successfully written to the backup file, false otherwise.
37
- */
38
- public function write_db_backup_file($handle, $tables) {
39
- global $wpdb, $aio_wp_security;
40
-
41
- // When importing the backup, tell database server that our data is in UTF-8...
42
- // ...and that foreign key checks should be ignored.
43
- $preamble
44
- = "-- All In One WP Security & Firewall {$aio_wp_security->version}" . PHP_EOL . '-- MySQL dump' . PHP_EOL . '-- ' . date('Y-m-d H:i:s') . PHP_EOL . PHP_EOL . "SET NAMES utf8;" . PHP_EOL . "SET foreign_key_checks = 0;" . PHP_EOL . PHP_EOL;
45
- if (!@fwrite($handle, $preamble)) {
46
- return false;
47
- }
48
-
49
- // Loop through each table
50
- foreach ($tables as $table) {
51
- $table_name = $table[0];
52
-
53
- $result_create_table = $wpdb->get_row('SHOW CREATE TABLE `' . $table_name . '`;', ARRAY_N);
54
- if (empty($result_create_table)) {
55
- $aio_wp_security->debug_logger->log_debug(__METHOD__ . " - get_row returned null for table: ".$table_name, 4);
56
- return false; // Avoid incomplete backups
57
- }
58
-
59
- // Drop/create table preamble
60
- $drop_and_create = 'DROP TABLE IF EXISTS `' . $table_name . '`;' . PHP_EOL . PHP_EOL . $result_create_table[1] . ";" . PHP_EOL . PHP_EOL;
61
- if (!@fwrite($handle, $drop_and_create)) {
62
- return false;
63
- }
64
-
65
- // Dump table contents
66
- // Fetch results as row of objects to spare memory.
67
- $result = $wpdb->get_results('SELECT * FROM `' . $table_name . '`;', OBJECT);
68
- foreach ($result as $object_row) {
69
- // Convert object row to array row: this is what $wpdb->get_results()
70
- // internally does when invoked with ARRAY_N param, but in the process
71
- // it creates new copy of entire results array that eats a lot of memory.
72
- $row = array_values(get_object_vars($object_row));
73
- // Start INSERT statement
74
- if (!@fwrite($handle, 'INSERT INTO `' . $table_name . '` VALUES(')) {
75
- return false;
76
- }
77
- // Loop through all fields and echo them out
78
- foreach ($row as $idx => $field) {
79
- // Echo fields separator (except for first loop)
80
- if (($idx > 0) && !@fwrite($handle, ',')) {
81
- return false;
82
- }
83
- // Echo field content (sanitized)
84
- if (!@fwrite($handle, $this->sanitize_db_field($field))) {
85
- return false;
86
- }
87
- }
88
- // Finish INSERT statement
89
- if (!@fwrite($handle, ");" . PHP_EOL)) {
90
- return false;
91
- }
92
- }
93
- // Place two-empty lines after table data
94
- if (!@fwrite($handle, PHP_EOL . PHP_EOL)) {
95
- return false;
96
- }
97
- }
98
-
99
- return true;
100
- }
101
-
102
- /**
103
- * This function will perform a database backup
104
- */
105
- public function execute_backup() {
106
- global $wpdb, $aio_wp_security;
107
- $is_multi_site = function_exists('is_multisite') && is_multisite();
108
-
109
- $execute_backup_memory_limit = apply_filters('aiowps_execute_backup_set_memory_limit', '512M');
110
- @ini_set('memory_limit', $execute_backup_memory_limit);
111
-
112
- if ($is_multi_site) {
113
- //Let's get the current site's table prefix
114
- $site_pref = esc_sql($wpdb->prefix);
115
- $db_query = "SHOW TABLES LIKE '".$site_pref."%'";
116
- $tables = $wpdb->get_results($db_query, ARRAY_N);
117
- } else {
118
- //get all of the tables
119
- $tables = $wpdb->get_results('SHOW TABLES', ARRAY_N);
120
- }
121
-
122
- if (empty($tables)) {
123
- $aio_wp_security->debug_logger->log_debug(__METHOD__ . " - no tables found!", 4);
124
- return false;
125
- }
126
-
127
- //Check to see if the main "backups" directory exists - create it otherwise
128
-
129
- $aiowps_backup_dir = WP_CONTENT_DIR.'/'.AIO_WP_SECURITY_BACKUPS_DIR_NAME;
130
- if (!AIOWPSecurity_Utility_File::create_dir($aiowps_backup_dir)) {
131
- $aio_wp_security->debug_logger->log_debug(__METHOD__ . " - Creation of DB backup directory failed!", 4);
132
- return false;
133
- }
134
-
135
- //Generate a random prefix for more secure filenames
136
- $random_suffix = AIOWPSecurity_Utility::generate_alpha_numeric_random_string(10);
137
-
138
- if ($is_multi_site) {
139
- global $current_blog;
140
- $blog_id = $current_blog->blog_id;
141
- //Get the current site name string for use later
142
- $site_name = get_bloginfo('name');
143
-
144
- $site_name = strtolower($site_name);
145
-
146
- //make alphanumeric
147
- $site_name = preg_replace("/[^a-z0-9_\s-]/", "", $site_name);
148
-
149
- //Cleanup multiple instances of dashes or whitespaces
150
- $site_name = preg_replace("/[\s-]+/", " ", $site_name);
151
-
152
- //Convert whitespaces and underscore to dash
153
- $site_name = preg_replace("/[\s_]/", "-", $site_name);
154
-
155
- $file = 'database-backup-site-name-' . $site_name . '-' . current_time('Ymd-His') . '-' . $random_suffix;
156
-
157
- //We will create a sub dir for the blog using its blog id
158
- $dirpath = $aiowps_backup_dir . '/blogid_' . $blog_id;
159
-
160
- //Create a subdirectory for this blog_id
161
- if (!AIOWPSecurity_Utility_File::create_dir($dirpath)) {
162
- $aio_wp_security->debug_logger->log_debug("Creation failed of DB backup directory for the following multisite blog ID: ".$blog_id, 4);
163
- return false;
164
- }
165
- } else {
166
- $dirpath = $aiowps_backup_dir;
167
- $file = 'database-backup-' . current_time('Ymd-His') . '-' . $random_suffix;
168
- }
169
-
170
- $handle = @fopen($dirpath . '/' . $file . '.sql', 'w+');
171
-
172
- if (false === $handle) {
173
- $aio_wp_security->debug_logger->log_debug("Cannot create DB backup file: {$dirpath}/{$file}.sql", 4);
174
- return false;
175
- }
176
-
177
- // Delete old backup files now to avoid polluting backups directory
178
- // with incomplete backups on websites where max execution time is too
179
- // low for database content to be written to a file:
180
- // https://github.com/Arsenal21/all-in-one-wordpress-security/issues/62
181
- $this->aiowps_delete_backup_files($dirpath);
182
-
183
- $fw_res = $this->write_db_backup_file($handle, $tables);
184
- @fclose($handle);
185
-
186
- if (!$fw_res) {
187
- @unlink($dirpath . '/' . $file . '.sql');
188
- $aio_wp_security->debug_logger->log_debug(__METHOD__ . " - Write to DB backup file failed", 4);
189
- return false;
190
- }
191
-
192
- //zip the file
193
- if (class_exists('ZipArchive')) {
194
- $zip = new ZipArchive();
195
- $archive = $zip->open($dirpath . '/' . $file . '.zip', ZipArchive::CREATE);// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
196
- $zip->addFile($dirpath . '/' . $file . '.sql', $file . '.sql');
197
- $zip->close();
198
-
199
- //delete .sql and keep zip
200
- @unlink($dirpath . '/' . $file . '.sql');
201
- $fileext = '.zip';
202
- } else {
203
- $fileext = '.sql';
204
- }
205
- $this->last_backup_file_name = $file . $fileext;//database-backup-YYYYMMDD-HHIISS-<random-string>.zip or database-backup-YYYYMMDD-HHIISS-<random-string>.sql
206
- $this->last_backup_file_path = $dirpath . '/' . $file . $fileext;
207
- if ($is_multi_site) {
208
- $this->last_backup_file_dir_multisite = $aiowps_backup_dir . '/blogid_' . $blog_id;
209
- }
210
-
211
- $this->aiowps_send_backup_email(); //Send backup file via email if applicable
212
- return true;
213
- }
214
-
215
- public function aiowps_send_backup_email() {
216
- global $aio_wp_security;
217
- if ($aio_wp_security->configs->get_value('aiowps_send_backup_email_address') == '1') {
218
- //Get the right email address.
219
- if (is_email($aio_wp_security->configs->get_value('aiowps_backup_email_address'))) {
220
- $toaddress = $aio_wp_security->configs->get_value('aiowps_backup_email_address');
221
- } else {
222
- $toaddress = get_site_option('admin_email');
223
- }
224
-
225
- $to = $toaddress;
226
- $site_title = get_bloginfo('name');
227
- $from_name = empty($site_title) ? 'WordPress' : $site_title;
228
-
229
- $headers = 'From: ' . $from_name . ' <' . get_option('admin_email') . '>' . PHP_EOL;
230
- $subject = __('All In One WP Security - Site Database Backup', 'all-in-one-wp-security-and-firewall') . ' ' . date('l, F jS, Y \a\\t g:i a', current_time('timestamp'));
231
- $attachment = array($this->last_backup_file_path);
232
- $message = __('Attached is your latest DB backup file for site URL', 'all-in-one-wp-security-and-firewall') . ' ' . get_option('siteurl') . __(' generated on', 'all-in-one-wp-security-and-firewall') . ' ' . date('l, F jS, Y \a\\t g:i a', current_time('timestamp'));
233
-
234
- $sendMail = wp_mail($to, $subject, $message, $headers, $attachment);
235
- if (false === $sendMail) {
236
- $aio_wp_security->debug_logger->log_debug("Backup notification email failed to send to ".$to, 4);
237
- }
238
- }
239
- }
240
-
241
- public function aiowps_delete_backup_files($backups_dir) {
242
- global $aio_wp_security;
243
- $files_to_keep = absint($aio_wp_security->configs->get_value('aiowps_backup_files_stored'));
244
- if ($files_to_keep > 0) {
245
- $aio_wp_security->debug_logger->log_debug(sprintf('DB Backup - Deleting all but %d latest backup file(s) in %s directory.', $files_to_keep, $backups_dir));
246
- $files = AIOWPSecurity_Utility_File::scan_dir_sort_date($backups_dir);
247
- $count = 0;
248
-
249
- foreach ($files as $file) {
250
- if (strpos($file, 'database-backup') !== false) {
251
- if ($count >= $files_to_keep) {
252
- @unlink($backups_dir . '/' . $file);
253
- }
254
- $count++;
255
- }
256
- }
257
- } else {
258
- $aio_wp_security->debug_logger->log_debug('DB Backup - Backup configuration prevents removal of old backup files!', 3);
259
- }
260
- }
261
-
262
- public function aiowps_scheduled_backup_handler() {
263
- global $aio_wp_security;
264
- if ($aio_wp_security->configs->get_value('aiowps_enable_automated_backups')=='1') {
265
- $aio_wp_security->debug_logger->log_debug_cron("DB Backup - Scheduled backup is enabled. Checking if a backup needs to be done now...");
266
- $time_now = current_time('mysql');
267
- $current_time = strtotime($time_now);
268
- $backup_frequency = $aio_wp_security->configs->get_value('aiowps_db_backup_frequency'); //Number of hours or days or months interval per backup
269
- $interval_setting = $aio_wp_security->configs->get_value('aiowps_db_backup_interval'); //Hours/Days/Months
270
- switch ($interval_setting) {
271
- case '0':
272
- $interval = 'hours';
273
- break;
274
- case '1':
275
- $interval = 'days';
276
- break;
277
- case '2':
278
- $interval = 'weeks';
279
- break;
280
- default:
281
- // Fall back to default value, if config is corrupted for some reason.
282
- $interval = 'weeks';
283
- break;
284
- }
285
- $last_backup_time = $aio_wp_security->configs->get_value('aiowps_last_backup_time');
286
- if (null != $last_backup_time) {
287
- $last_backup_time = strtotime($aio_wp_security->configs->get_value('aiowps_last_backup_time'));
288
- $next_backup_time = strtotime("+".abs($backup_frequency).$interval, $last_backup_time);
289
- if ($next_backup_time <= $current_time) {
290
- //It's time to do a backup
291
- $result = $this->execute_backup();
292
- if ($result) {
293
- $aio_wp_security->configs->set_value('aiowps_last_backup_time', $time_now);
294
- $aio_wp_security->configs->save_config();
295
- $aio_wp_security->debug_logger->log_debug_cron("DB Backup - Scheduled backup was successfully completed.");
296
- } else {
297
- $aio_wp_security->debug_logger->log_debug_cron("DB Backup - Scheduled backup operation failed!", 4);
298
- }
299
- }
300
- } else {
301
- //Set the last backup time to now so it can trigger for the next scheduled period
302
- $aio_wp_security->configs->set_value('aiowps_last_backup_time', $time_now);
303
- $aio_wp_security->configs->save_config();
304
- }
305
- }
306
- }
307
-
308
-
309
- public function aiowps_scheduled_db_cleanup_handler() {
310
- //Check the events table because this can grow quite large especially when 404 events are being logged
311
- $events_table_name = AIOWPSEC_TBL_EVENTS;
312
- $max_rows_event_table = '5000'; //Keep a max of 5000 rows in the events table
313
- $max_rows_event_table = apply_filters('aiowps_max_rows_event_table', $max_rows_event_table);
314
- AIOWPSecurity_Utility::cleanup_table($events_table_name, $max_rows_event_table);
315
-
316
- //Check the failed logins table
317
- $failed_logins_table_name = AIOWPSEC_TBL_FAILED_LOGINS;
318
- $max_rows_failed_logins_table = '5000'; //Keep a max of 5000 rows in the events table
319
- $max_rows_failed_logins_table = apply_filters('aiowps_max_rows_failed_logins_table', $max_rows_failed_logins_table);
320
- AIOWPSecurity_Utility::cleanup_table($failed_logins_table_name, $max_rows_failed_logins_table);
321
-
322
- //Check the login activity table
323
- $login_activity_table_name = AIOWPSEC_TBL_USER_LOGIN_ACTIVITY;
324
- $max_rows_login_activity_table = '5000'; //Keep a max of 5000 rows in the events table
325
- $max_rows_login_activity_table = apply_filters('aiowps_max_rows_login_attempts_table', $max_rows_login_activity_table);
326
- AIOWPSecurity_Utility::cleanup_table($login_activity_table_name, $max_rows_login_activity_table);
327
-
328
- //Check the global meta table
329
- $global_meta_table_name = AIOWPSEC_TBL_GLOBAL_META_DATA;
330
- $max_rows_global_meta_table = '5000'; //Keep a max of 5000 rows in this table
331
- $max_rows_global_meta_table = apply_filters('aiowps_max_rows_global_meta_table', $max_rows_global_meta_table);
332
- AIOWPSecurity_Utility::cleanup_table($global_meta_table_name, $max_rows_global_meta_table);
333
-
334
- //Delete any expired _aiowps_captcha_string_info_xxxx option
335
- AIOWPSecurity_Utility::delete_expired_captcha_options();
336
-
337
- //Keep adding other DB cleanup tasks as they arise...
338
- }
339
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/wp-security-base-tasks.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if (!defined('ABSPATH')) {
3
+ exit;//Exit if accessed directly
4
+ }
5
+
6
+ abstract class AIOWPSecurity_Base_Tasks {
7
+ /**
8
+ * Runs intended various tasks
9
+ * Handles single and multi-site (NW activation) cases
10
+ *
11
+ * @global type $wpdb
12
+ */
13
+ public static function run() {
14
+ if (is_multisite()) {
15
+ global $wpdb;
16
+ // check if it is a network activation
17
+ $blog_ids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs");
18
+ foreach ($blog_ids as $blog_id) {
19
+ switch_to_blog($blog_id);
20
+ static::run_for_a_site();
21
+ restore_current_blog();
22
+ }
23
+ } else {
24
+ static::run_for_a_site();
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Run uninstallation task for a single site.
30
+ *
31
+ * @return void
32
+ */
33
+ abstract protected static function run_for_a_site();
34
+ }
classes/wp-security-block-bootstrap.php ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if (!defined('ABSPATH')) {
3
+ exit; //Exit if accessed directly
4
+ }
5
+
6
+ /**
7
+ * Firewall bootstrap file content block.
8
+ */
9
+ class AIOWPSecurity_Block_Bootstrap extends AIOWPSecurity_Block_File {
10
+
11
+ /**
12
+ * Keeps track of our bootstrap file version
13
+ *
14
+ * @var string
15
+ */
16
+ protected $version = '1.0.0';
17
+
18
+ /**
19
+ * Inserts our code into our bootstrap file.
20
+ *
21
+ * @return boolean|WP_Error
22
+ */
23
+ public function insert_contents() {
24
+ $info = pathinfo($this->file_path);
25
+
26
+ if (!isset($info['dirname'])) {
27
+ return new WP_Error(
28
+ 'file_no_directory',
29
+ 'No directory has been set',
30
+ $this->file_path
31
+ );
32
+ }
33
+
34
+ if (!is_writable($info['dirname'])) {
35
+ return new WP_Error(
36
+ 'file_directory_not_writable',
37
+ 'The directory has incorrect write permissions. Please double check its permissions and try again.',
38
+ $info['dirname']
39
+ );
40
+ }
41
+
42
+ return (false !== @file_put_contents($this->file_path, $this->get_contents())); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
43
+ }
44
+
45
+ /**
46
+ * Checks whether the bootstrap contents are valid
47
+ *
48
+ * @param string $contents
49
+ * @return boolean
50
+ */
51
+ protected function is_content_valid($contents) {
52
+
53
+ //The regexes we extract the paths from
54
+ $regexes = array('/file_exists\(\'?(.*)\'?\)/isU', '/include_once\(\'?(.*)\'?\)/isU');
55
+ $firewall_path_str = $this->get_firewall_path_str();
56
+
57
+ foreach ($regexes as $regex) {
58
+ $matches = array();
59
+ $result = preg_match($regex, $contents, $matches);
60
+
61
+ if (empty($matches[1]) || false === $result) {
62
+ continue;
63
+ }
64
+
65
+ if ($firewall_path_str !== $matches[1]) {
66
+ return false;
67
+ }
68
+
69
+ }
70
+
71
+ return true;
72
+ }
73
+
74
+ /**
75
+ * Get the firewall path string that contains "__DIR__" for home dir, if plugin dir isn't a symbolic link..
76
+ *
77
+ * @return string The firewall path string.
78
+ */
79
+ private function get_firewall_path_str() {
80
+ $firewall_path = AIOWPSecurity_Utility_Firewall::get_firewall_path();
81
+ $firewall_path_str = $this->get_path_str_for_given_absolute_path($firewall_path);
82
+ return $firewall_path_str;
83
+ }
84
+
85
+ /**
86
+ * Get path string to write bootstrap file from given path.
87
+ *
88
+ * @param string $path a path that we want to write to the bootstrap file.
89
+ * @return string The path that can be written in the bootstrap file.
90
+ */
91
+ private function get_path_str_for_given_absolute_path($path) {
92
+ $home_path = AIOWPSecurity_Utility_File::get_home_path();
93
+ // If the plugin is symbolic linked, then the plugin's firewall path is not started with home_path.
94
+ $path_str = (0 === strpos($path, $home_path)) ? "__DIR__.'/".substr($path, strlen($home_path))."'" : "'".$path."'";
95
+ return $path_str;
96
+ }
97
+
98
+ /**
99
+ * Get the firewall rules path string that contains "__DIR__" for home dir, if plugin dir isn't a symbolic link.
100
+ *
101
+ * @return string The firewall rule path string.
102
+ */
103
+ private function get_firewall_rules_path_str() {
104
+ $firewall_rules_path = AIOWPSecurity_Utility_Firewall::get_firewall_rules_path();
105
+ $firewall_rules_path_str = $this->get_path_str_for_given_absolute_path($firewall_rules_path);
106
+ return $firewall_rules_path_str;
107
+ }
108
+
109
+ /**
110
+ * The regex pattern that demarcates our contents
111
+ *
112
+ * @return string
113
+ */
114
+ protected function get_regex_pattern() {
115
+ return '#// Begin AIOWPSEC Firewall(.*)// End AIOWPSEC Firewall#isU';
116
+ }
117
+
118
+ /**
119
+ * Bootstrap file contents to insert
120
+ *
121
+ * @return string
122
+ */
123
+ public function get_contents() {
124
+ $firewall_path_str = $this->get_firewall_path_str();
125
+ $firewall_rules_path_str = $this->get_firewall_rules_path_str();
126
+
127
+ $code = "<?php\n";
128
+ $code .= $this->get_warning_message();
129
+
130
+ $directive = AIOWPSecurity_Utility_Firewall::get_already_set_directive();
131
+
132
+ if (!empty($directive) && $directive !== $this->file_path) {
133
+ $code .= "// Previously set auto_prepend_file\n";
134
+ $code .= "if (file_exists('{$directive}')) {\n";
135
+ $code .= "\tinclude_once('{$directive}');\n";
136
+ $code .= "}\n";
137
+ }
138
+
139
+ $code .= "\$aiowps_firewall_rules_path = {$firewall_rules_path_str};\n\n";
140
+ $code .= "// Begin AIOWPSEC Firewall\n";
141
+ $code .= "if (file_exists({$firewall_path_str})) {\n";
142
+ $code .= "\tinclude_once({$firewall_path_str});\n";
143
+ $code .= "}\n";
144
+ $code .= "// End AIOWPSEC Firewall\n";
145
+
146
+ return $code;
147
+ }
148
+
149
+ /**
150
+ * Gets our warning message for users
151
+ *
152
+ * @return string
153
+ */
154
+ protected function get_warning_message() {
155
+
156
+ $warning = "/** \n";
157
+ $warning .= " * @version {$this->version}\n";
158
+ $warning .= " * WARNING: Please do not delete this file.\n";
159
+ $warning .= " * \n";
160
+ $warning .= " * This will cause PHP to throw a fatal error and render your site unusable.\n";
161
+ $warning .= " * \n";
162
+ $warning .= " * To safely delete this file, please check both your .user.ini file and your php.ini file and ensure this file is not set in the auto_prepend_file directive.\n";
163
+ $warning .= " * \n";
164
+ $warning .= " * Please ask your web hosting provider if you need guidance with executing the aforementioned steps.\n";
165
+ $warning .= " */\n";
166
+
167
+ return $warning;
168
+ }
169
+ }
classes/wp-security-block-file.php ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if (!defined('ABSPATH')) {
3
+ exit; //Exit if accessed directly
4
+ }
5
+
6
+ abstract class AIOWPSecurity_Block_File {
7
+
8
+ /**
9
+ * Full path to the file we're managing
10
+ *
11
+ * @var string
12
+ */
13
+ protected $file_path;
14
+
15
+ /**
16
+ * Receives the full file path
17
+ *
18
+ * @param string $file_path
19
+ */
20
+ public function __construct($file_path) {
21
+ $this->file_path = $file_path;
22
+ }
23
+
24
+ /**
25
+ * Insert the contents to the managed file
26
+ *
27
+ * @return boolean|WP_Error true if success; false if failed
28
+ */
29
+ abstract public function insert_contents();
30
+
31
+ /**
32
+ * Returns the contents to be inserted into the managed file
33
+ *
34
+ * @return string
35
+ */
36
+ abstract public function get_contents();
37
+
38
+ /**
39
+ * Returns the regex pattern that separates our contents from others the file may contain
40
+ *
41
+ * @return string
42
+ */
43
+ abstract protected function get_regex_pattern();
44
+
45
+ /**
46
+ * Checks whether the file's contents are valid
47
+ *
48
+ * @param string $contents
49
+ * @return boolean
50
+ */
51
+ abstract protected function is_content_valid($contents);
52
+
53
+ /**
54
+ * Updates the contents of the managed file
55
+ *
56
+ * @return boolean|WP_Error true if updated; false if not updated
57
+ */
58
+ public function update_contents() {
59
+
60
+ if (!is_readable($this->file_path) || !is_writable($this->file_path)) {
61
+ return new WP_Error(
62
+ 'file_wrong_permissions',
63
+ 'The file has incorrect read or write permissions. Please double check its permissions and try again.',
64
+ $this->file_path
65
+ );
66
+ }
67
+
68
+ $contents = @file_get_contents($this->file_path); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
69
+
70
+ if (false === $contents) {
71
+ return new WP_Error(
72
+ 'file_unable_to_read',
73
+ 'Unable to read the file. Please double check its permissions and try again.',
74
+ $this->file_path
75
+ );
76
+ }
77
+
78
+ $matches = array();
79
+ $match_count = preg_match_all($this->get_regex_pattern(), $contents, $matches);
80
+
81
+ if (empty($matches[1]) || false === $match_count) {
82
+ return false;
83
+ }
84
+
85
+ //checks whether an update is required
86
+ $requires_update = false;
87
+ $match = '';
88
+ foreach ($matches[1] as $match) {
89
+
90
+ $requires_update = !$this->is_content_valid($match);
91
+
92
+ if (true === $requires_update) {
93
+ break;
94
+ }
95
+ }
96
+
97
+ //perform the update
98
+ if ($requires_update) {
99
+
100
+ $block_removed = $this->remove_contents();
101
+ $block_inserted = $this->insert_contents();
102
+
103
+ return (true === $block_removed && true === $block_inserted);
104
+ }
105
+
106
+ return false;
107
+ }
108
+
109
+ /**
110
+ * Checks whether the file contains our contents
111
+ *
112
+ * @return boolean|WP_Error true if found; false if not found
113
+ */
114
+ public function contains_contents() {
115
+
116
+ if (!is_readable($this->file_path)) {
117
+ return new WP_Error(
118
+ 'file_wrong_permissions',
119
+ 'The file has incorrect read permissions. Please double check its permissions and try again.',
120
+ $this->file_path
121
+ );
122
+ }
123
+
124
+ $contents = @file_get_contents($this->file_path); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
125
+
126
+ if (false === $contents) {
127
+ return new WP_Error(
128
+ 'file_unable_to_read',
129
+ 'Unable to read the file. Please double check its permissions and try again.',
130
+ $this->file_path
131
+ );
132
+ }
133
+
134
+ return (1 === preg_match($this->get_regex_pattern(), $contents));
135
+ }
136
+
137
+ /**
138
+ * Removes our contents from the file
139
+ *
140
+ * @return boolean|WP_Error
141
+ */
142
+ public function remove_contents() {
143
+
144
+ if (!is_readable($this->file_path) || !is_writable($this->file_path)) {
145
+ return new WP_Error(
146
+ 'file_wrong_permissions',
147
+ 'The file has incorrect read or write permissions. Please double check its permissions and try again.',
148
+ $this->file_path
149
+ );
150
+ }
151
+
152
+ $contents = @file_get_contents($this->file_path); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
153
+
154
+ if (false === $contents) {
155
+ return new WP_Error(
156
+ 'file_unable_to_read',
157
+ 'Unable to read the file. Please double check its permissions and try again.',
158
+ $this->file_path
159
+ );
160
+ }
161
+
162
+ $removed = 0;
163
+ $contents = preg_replace($this->get_regex_pattern(), "", $contents, -1, $removed);
164
+
165
+ if (null === $contents) {
166
+ return new WP_Error(
167
+ 'file_unable_to_alter',
168
+ 'Unable to alter the file.',
169
+ $this->file_path
170
+ );
171
+ }
172
+
173
+ if (false === @file_put_contents($this->file_path, $contents, LOCK_EX)) { // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
174
+ return new WP_Error(
175
+ 'file_unable_to_write',
176
+ 'Unable to write to the file. Please double check its permissions and try again.',
177
+ $this->file_path
178
+ );
179
+ }
180
+
181
+ return $removed > 0;
182
+ }
183
+
184
+ /**
185
+ * By default returns the full path to the file being managed
186
+ *
187
+ * @return string
188
+ */
189
+ public function __toString() {
190
+ return $this->file_path;
191
+ }
192
+
193
+ } //end of class
classes/wp-security-block-htaccess.php ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if (!defined('ABSPATH')) {
3
+ exit; //Exit if accessed directly
4
+ }
5
+
6
+ class AIOWPSecurity_Block_Htaccess extends AIOWPSecurity_Block_File {
7
+
8
+ /**
9
+ * Attempts to insert our apache directives into the htaccess file
10
+ *
11
+ * @return boolean|WP_Error true if success; false if failed
12
+ */
13
+ public function insert_contents() {
14
+ $home_path = AIOWPSecurity_Utility_File::get_home_path();
15
+
16
+ if (!is_writable($home_path)) {
17
+ return new WP_Error(
18
+ 'file_wrong_permissions',
19
+ 'The file has incorrect write permissions. Please double check its permissions and try again.',
20
+ $this->file_path
21
+ );
22
+ }
23
+
24
+ return (false !== @file_put_contents($this->file_path, $this->get_contents(), FILE_APPEND)); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
25
+ }
26
+
27
+ /**
28
+ * Checks whether the file's contents are valid
29
+ *
30
+ * @param string $contents
31
+ * @return boolean
32
+ */
33
+ protected function is_content_valid($contents) {
34
+
35
+ $regex = '/php_value auto_prepend_file \'(.*)\'/isU';
36
+ $bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();
37
+
38
+ $matches = array();
39
+
40
+ if (preg_match_all($regex, $contents, $matches)) {
41
+ $match = '';
42
+ foreach ($matches[1] as $match) {
43
+
44
+ if ($bootstrap_path !== $match) {
45
+ return false;
46
+ }
47
+ }
48
+
49
+ } else {
50
+ return false;
51
+ }
52
+
53
+ return true;
54
+ }
55
+
56
+ /**
57
+ * The regex pattern that demarcates our contents
58
+ *
59
+ * @return string
60
+ */
61
+ protected function get_regex_pattern() {
62
+ return '/\r?\n# Begin AIOWPSEC Firewall(.*?)# End AIOWPSEC Firewall/is';
63
+ }
64
+
65
+ /**
66
+ * Our contents; the required apache directives for auto prepending a file
67
+ *
68
+ * @return string
69
+ */
70
+ public function get_contents() {
71
+ $bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();
72
+
73
+ $directives = "\n# Begin AIOWPSEC Firewall\n";
74
+ $directives .= "\t<IfModule mod_php5.c>\n";
75
+ $directives .= "\t\tphp_value auto_prepend_file '{$bootstrap_path}'\n";
76
+ $directives .= "\t</IfModule>\n";
77
+ $directives .= "\t<IfModule mod_php7.c>\n";
78
+ $directives .= "\t\tphp_value auto_prepend_file '{$bootstrap_path}'\n";
79
+ $directives .= "\t</IfModule>\n";
80
+ $directives .= "\t<IfModule mod_php.c>\n";
81
+ $directives .= "\t\tphp_value auto_prepend_file '{$bootstrap_path}'\n";
82
+ $directives .= "\t</IfModule>\n";
83
+ $directives .= "# End AIOWPSEC Firewall";
84
+
85
+ return $directives;
86
+ }
87
+
88
+
89
+ } //end of class
classes/wp-security-block-litespeed.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if (!defined('ABSPATH')) {
3
+ exit; //Exit if accessed directly
4
+ }
5
+
6
+ class AIOWPSecurity_Block_Litespeed extends AIOWPSecurity_Block_Htaccess {
7
+
8
+ /**
9
+ * Get the directives needed for litespeed server
10
+ *
11
+ * @return string
12
+ */
13
+ public function get_contents() {
14
+ $bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();
15
+
16
+ $directives = "\n# Begin AIOWPSEC Firewall\n";
17
+ $directives .= "\t<IfModule LiteSpeed>\n";
18
+ $directives .= "\t\tphp_value auto_prepend_file '{$bootstrap_path}'\n";
19
+ $directives .= "\t</IfModule>\n";
20
+ $directives .= "\t<IfModule lsapi_module>\n";
21
+ $directives .= "\t\tphp_value auto_prepend_file '{$bootstrap_path}'\n";
22
+ $directives .= "\t</IfModule>\n";
23
+ $directives .= "# End AIOWPSEC Firewall";
24
+
25
+ return $directives;
26
+ }
27
+
28
+
29
+ }
classes/wp-security-block-muplugin.php ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if (!defined('ABSPATH')) {
3
+ exit; //Exit if accessed directly
4
+ }
5
+
6
+ class AIOWPSecurity_Block_Muplugin extends AIOWPSecurity_Block_File {
7
+
8
+ /**
9
+ * Inserts our code into our mu-plugin.
10
+ *
11
+ * The mu-plugin and the mu-plugin directory will be created if they don't already exists
12
+ *
13
+ * @return boolean|WP_Error
14
+ */
15
+ public function insert_contents() {
16
+ $info = pathinfo($this->file_path);
17
+
18
+ if (!isset($info['dirname'])) {
19
+ return new WP_Error(
20
+ 'file_no_directory',
21
+ 'No directory has been set',
22
+ $this->file_path
23
+ );
24
+ }
25
+
26
+ if (false === wp_mkdir_p($info['dirname'])) {
27
+ return new WP_Error(
28
+ 'file_no_directory_created',
29
+ 'Unable to create the directory',
30
+ $info['dirname']
31
+ );
32
+ }
33
+
34
+ return (false !== @file_put_contents($this->file_path, $this->get_contents())); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
35
+ }
36
+
37
+ /**
38
+ * Checks whether the mu-plugin contents are valid
39
+ *
40
+ * @param string $contents
41
+ * @return boolean
42
+ */
43
+ protected function is_content_valid($contents) {
44
+
45
+ //The regexes we extract the paths from
46
+ $regexes = array('/file_exists\(\'(.*)\'\)/isU', '/include_once\(\'(.*)\'\)/isU');
47
+ $regex = '';
48
+ $bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();
49
+
50
+ foreach ($regexes as $regex) {
51
+ $matches = array();
52
+ $result = preg_match($regex, $contents, $matches);
53
+
54
+ if (empty($matches[1]) || false === $result) {
55
+ continue;
56
+ }
57
+
58
+ if ($bootstrap_path !== $matches[1]) {
59
+ return false;
60
+ }
61
+
62
+ }
63
+
64
+ return true;
65
+ }
66
+
67
+ /**
68
+ * The regex pattern that demarcates our contents
69
+ *
70
+ * @return string
71
+ */
72
+ protected function get_regex_pattern() {
73
+ return '#// Begin AIOWPSEC Firewall(.*)// End AIOWPSEC Firewall#isU';
74
+ }
75
+
76
+ /**
77
+ * Our firewall code to insert
78
+ *
79
+ * @return string
80
+ */
81
+ public function get_contents() {
82
+ $bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();
83
+
84
+ $code = "<?php\n";
85
+ $code .= "// Begin AIOWPSEC Firewall\n";
86
+ $code .= "if (file_exists('{$bootstrap_path}')) {\n";
87
+ $code .= "\tinclude_once('{$bootstrap_path}');\n";
88
+ $code .= "}\n";
89
+ $code .= "// End AIOWPSEC Firewall\n";
90
+
91
+ return $code;
92
+ }
93
+ }
classes/wp-security-block-userini.php ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if (!defined('ABSPATH')) {
3
+ exit; //Exit if accessed directly
4
+ }
5
+
6
+ class AIOWPSecurity_Block_Userini extends AIOWPSecurity_Block_File {
7
+
8
+ /**
9
+ * Inserts our directive into the user.ini file
10
+ *
11
+ * @return boolean|WP_Error true if inserted; false if failed
12
+ */
13
+ public function insert_contents() {
14
+ $home_path = AIOWPSecurity_Utility_File::get_home_path();
15
+
16
+ if (!is_writable($home_path)) {
17
+ return new WP_Error(
18
+ 'file_directory_not_writable',
19
+ 'The directory has incorrect write permissions. Please double check its permissions and try again.',
20
+ $home_path
21
+ );
22
+ }
23
+
24
+ return (false !== @file_put_contents($this->file_path, $this->get_contents(), FILE_APPEND)); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
25
+ }
26
+
27
+ /**
28
+ * Checks whether the user.ini file contents are valid
29
+ *
30
+ * @param string $contents
31
+ * @return boolean
32
+ */
33
+ protected function is_content_valid($contents) {
34
+
35
+ $regex = '/auto_prepend_file=\'(.*)\'/isU';
36
+ $bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();
37
+
38
+ $match = array();
39
+ $result = preg_match($regex, $contents, $match);
40
+
41
+ if (empty($match[1]) || false === $result) {
42
+ return false;
43
+ }
44
+
45
+ if ($bootstrap_path !== $match[1]) {
46
+ return false;
47
+ }
48
+
49
+ return true;
50
+
51
+ }
52
+
53
+ /**
54
+ * Our regex pattern that demarcates our contents
55
+ *
56
+ * @return string
57
+ */
58
+ protected function get_regex_pattern() {
59
+ return '/\r?\n# Begin AIOWPSEC Firewall(.*?)# End AIOWPSEC Firewall/is';
60
+ }
61
+
62
+ /**
63
+ * Directives inserted into user.ini
64
+ *
65
+ * @return string
66
+ */
67
+ public function get_contents() {
68
+ $bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();
69
+
70
+ $directive = "\n# Begin AIOWPSEC Firewall\n";
71
+ $directive .= "auto_prepend_file='{$bootstrap_path}'\n";
72
+ $directive .= "# End AIOWPSEC Firewall";
73
+
74
+ return $directive;
75
+ }
76
+
77
+ /**
78
+ * Extends the contains_contents function to check for already set directives
79
+ *
80
+ * @return boolean|WP_Error
81
+ */
82
+ public function contains_contents() {
83
+ $contains = parent::contains_contents();
84
+
85
+ if (false === $contains) {
86
+ $directive_userini = AIOWPSecurity_Utility_Firewall::get_already_set_directive($this->file_path);
87
+ $directive = AIOWPSecurity_Utility_Firewall::get_already_set_directive();
88
+
89
+ if ((AIOWPSecurity_Utility_Firewall::get_bootstrap_path() === $directive) || (AIOWPSecurity_Utility_Firewall::get_bootstrap_path() === $directive_userini)) {
90
+ return true;
91
+ }
92
+ }
93
+
94
+ return $contains;
95
+ }
96
+ }
classes/wp-security-block-wpconfig.php ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if (!defined('ABSPATH')) {
3
+ exit; //Exit if accessed directly
4
+ }
5
+
6
+ class AIOWPSecurity_Block_WpConfig extends AIOWPSecurity_Block_File {
7
+
8
+ /**
9
+ * Attempts to insert our code contents into wp-config file
10
+ *
11
+ * @return boolean|WP_Error true if success; false if unsuccessful
12
+ */
13
+ public function insert_contents() {
14
+
15
+ if (!is_readable($this->file_path) || !is_writable($this->file_path)) {
16
+ return new WP_Error(
17
+ 'file_wrong_permissions',
18
+ 'The file has incorrect read or write permissions. Please double check its permissions and try again.',
19
+ $this->file_path
20
+ );
21
+ }
22
+
23
+ $wp_config = file($this->file_path, FILE_IGNORE_NEW_LINES);
24
+
25
+ if (false === $wp_config) {
26
+ return new WP_Error(
27
+ 'file_no_contents',
28
+ 'Unable to access the file\'s contents',
29
+ $this->file_path
30
+ );
31
+ }
32
+
33
+ array_shift($wp_config);
34
+ array_unshift($wp_config, $this->get_contents());
35
+
36
+ return (false !== @file_put_contents($this->file_path, implode(PHP_EOL, $wp_config), LOCK_EX)); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
37
+ }
38
+
39
+ /**
40
+ * Checks the validity of the content
41
+ *
42
+ * @param string $contents - contents we're checking
43
+ * @return boolean true if content is valid; false if invalid
44
+ */
45
+ protected function is_content_valid($contents) {
46
+ //The regexes we extract the paths from
47
+ $regexes = array('/file_exists\(\'(.*)\'\)/isU', '/include_once\(\'(.*)\'\)/isU');
48
+ $regex = '';
49
+ $bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();
50
+
51
+ foreach ($regexes as $regex) {
52
+ $matches = array();
53
+ $result = preg_match($regex, $contents, $matches);
54
+
55
+ if (empty($matches[1]) || false === $result) {
56
+ continue;
57
+ }
58
+
59
+ if ($bootstrap_path !== $matches[1]) {
60
+ return false;
61
+ }
62
+ }
63
+
64
+ return true;
65
+ }
66
+
67
+ /**
68
+ * The particular regex that demarcates our contents
69
+ *
70
+ * @return string
71
+ */
72
+ protected function get_regex_pattern() {
73
+ return '#\r?\n// Begin AIOWPSEC Firewall(.*?)// End AIOWPSEC Firewall#is';
74
+ }
75
+
76
+ /**
77
+ * Our firewall code to insert
78
+ *
79
+ * @return string
80
+ */
81
+ public function get_contents() {
82
+ $bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();
83
+
84
+ $code = "<?php\n";
85
+ $code .= "// Begin AIOWPSEC Firewall\n";
86
+ $code .= "if (file_exists('{$bootstrap_path}')) {\n";
87
+ $code .= "\tinclude_once('{$bootstrap_path}');\n";
88
+ $code .= "}\n";
89
+ $code .= "// End AIOWPSEC Firewall";
90
+
91
+ return $code;
92
+ }
93
+
94
+ }
classes/wp-security-blocking.php CHANGED
@@ -61,6 +61,11 @@ class AIOWPSecurity_Blocking {
61
  */
62
  public static function add_ip_to_block_list($ip_address, $reason = '') {
63
  global $wpdb, $aio_wp_security;
 
 
 
 
 
64
  //Check if this IP address is already in the block list
65
  $blocked = AIOWPSecurity_Blocking::is_ip_blocked($ip_address);
66
  $time_now = current_time('mysql');
61
  */
62
  public static function add_ip_to_block_list($ip_address, $reason = '') {
63
  global $wpdb, $aio_wp_security;
64
+ $user = wp_get_current_user();
65
+ if (array_intersect(array('administrator', 'editor', 'author'), $user->roles) && AIOWPSecurity_Utility_IP::get_user_ip_address() == $ip_address) {
66
+ return;
67
+ }
68
+
69
  //Check if this IP address is already in the block list
70
  $blocked = AIOWPSecurity_Blocking::is_ip_blocked($ip_address);
71
  $time_now = current_time('mysql');
classes/wp-security-captcha.php CHANGED
@@ -109,12 +109,12 @@ class AIOWPSecurity_Captcha {
109
  $current_time = time();
110
  $enc_result = base64_encode($current_time.$captcha_secret_string.$result);
111
  $random_str = AIOWPSecurity_Utility::generate_alpha_numeric_random_string(10);
112
- if (AIOWPSecurity_Utility::is_multisite_install()) {
113
  update_site_option('aiowps_captcha_string_info_'.$random_str, $enc_result);
114
  update_site_option('aiowps_captcha_string_info_time_'.$random_str, $current_time);
115
  } else {
116
- update_option('aiowps_captcha_string_info_'.$random_str, $enc_result);
117
- update_option('aiowps_captcha_string_info_time_'.$random_str, $current_time);
118
  }
119
  $equation_string .= '<input type="hidden" name="aiowps-captcha-string-info" id="aiowps-captcha-string-info" value="'.$random_str.'" />';
120
  $equation_string .= '<input type="hidden" name="aiowps-captcha-temp-string" id="aiowps-captcha-temp-string" value="'.$current_time.'" />';
@@ -199,7 +199,7 @@ class AIOWPSecurity_Captcha {
199
  $captcha_temp_string = sanitize_text_field($_POST['aiowps-captcha-temp-string']);
200
  $submitted_encoded_string = base64_encode($captcha_temp_string.$captcha_secret_string.$captcha_answer);
201
  $trans_handle = sanitize_text_field($_POST['aiowps-captcha-string-info']);
202
- if (AIOWPSecurity_Utility::is_multisite_install()) {
203
  $captcha_string_info_option = get_site_option('aiowps_captcha_string_info_'.$trans_handle);
204
  delete_site_option('aiowps_captcha_string_info_'.$trans_handle);
205
  delete_site_option('aiowps_captcha_string_info_time_'.$trans_handle);
109
  $current_time = time();
110
  $enc_result = base64_encode($current_time.$captcha_secret_string.$result);
111
  $random_str = AIOWPSecurity_Utility::generate_alpha_numeric_random_string(10);
112
+ if (is_multisite()) {
113
  update_site_option('aiowps_captcha_string_info_'.$random_str, $enc_result);
114
  update_site_option('aiowps_captcha_string_info_time_'.$random_str, $current_time);
115
  } else {
116
+ update_option('aiowps_captcha_string_info_'.$random_str, $enc_result, false);
117
+ update_option('aiowps_captcha_string_info_time_'.$random_str, $current_time, false);
118
  }
119
  $equation_string .= '<input type="hidden" name="aiowps-captcha-string-info" id="aiowps-captcha-string-info" value="'.$random_str.'" />';
120
  $equation_string .= '<input type="hidden" name="aiowps-captcha-temp-string" id="aiowps-captcha-temp-string" value="'.$current_time.'" />';
199
  $captcha_temp_string = sanitize_text_field($_POST['aiowps-captcha-temp-string']);
200
  $submitted_encoded_string = base64_encode($captcha_temp_string.$captcha_secret_string.$captcha_answer);
201
  $trans_handle = sanitize_text_field($_POST['aiowps-captcha-string-info']);
202
+ if (is_multisite()) {
203
  $captcha_string_info_option = get_site_option('aiowps_captcha_string_info_'.$trans_handle);
204
  delete_site_option('aiowps_captcha_string_info_'.$trans_handle);
205
  delete_site_option('aiowps_captcha_string_info_time_'.$trans_handle);
classes/wp-security-cleanup.php ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if (!defined('ABSPATH')) {
3
+ exit;//Exit if accessed directly
4
+ }
5
+
6
+ /**
7
+ * AIOWPSecurity_Cleanup class for clean up database etc.
8
+ *
9
+ * @access public
10
+ */
11
+ class AIOWPSecurity_Cleanup {
12
+
13
+ /**
14
+ * Class constructor added action
15
+ */
16
+ public function __construct() {
17
+ add_action('aiowps_perform_db_cleanup_tasks', array($this, 'aiowps_scheduled_db_cleanup_handler'));
18
+ }
19
+
20
+ /**
21
+ * Clean up unnecessary old data from aiowps tables.
22
+ *
23
+ * @return void
24
+ */
25
+ public function aiowps_scheduled_db_cleanup_handler() {
26
+ //Check the events table because this can grow quite large especially when 404 events are being logged
27
+ $events_table_name = AIOWPSEC_TBL_EVENTS;
28
+ $purge_events_records_after_days = AIOS_PURGE_EVENTS_RECORDS_AFTER_DAYS; //purge older records in the events table
29
+ $purge_events_records_after_days = apply_filters('aios_purge_events_records_after_days', $purge_events_records_after_days);
30
+ AIOWPSecurity_Utility::purge_table_records($events_table_name, $purge_events_records_after_days, 'event_date');
31
+
32
+ //Check the failed logins table
33
+ // aiowps_perform_failed_login_cleanup_task already does it.
34
+
35
+ //Check the login activity table
36
+ $login_activity_table_name = AIOWPSEC_TBL_USER_LOGIN_ACTIVITY;
37
+ $purge_login_activity_records_after_days = AIOS_PURGE_LOGIN_ACTIVITY_RECORDS_AFTER_DAYS; //purge older records in the login activity table
38
+ $purge_login_activity_records_after_days = apply_filters('aios_purge_login_activity_records_after_days', $purge_login_activity_records_after_days);
39
+ AIOWPSecurity_Utility::purge_table_records($login_activity_table_name, $purge_login_activity_records_after_days, 'login_date');
40
+
41
+ //Check the global meta table
42
+ $global_meta_table_name = AIOWPSEC_TBL_GLOBAL_META_DATA;
43
+ $purge_global_meta_records_after_days = AIOS_PURGE_GLOBAL_META_DATA_RECORDS_AFTER_DAYS; //purge older records in global meta table
44
+ $purge_global_meta_records_after_days = apply_filters('aios_purge_global_meta_records_after_days', $purge_global_meta_records_after_days);
45
+ AIOWPSecurity_Utility::purge_table_records($global_meta_table_name, $purge_global_meta_records_after_days, 'date_time');
46
+
47
+ //Delete any expired _aiowps_captcha_string_info_xxxx option
48
+ AIOWPSecurity_Utility::delete_expired_captcha_options();
49
+ //Keep adding other DB cleanup tasks as they arise...
50
+ }
51
+ }
classes/wp-security-comment.php CHANGED
@@ -15,6 +15,8 @@ class AIOWPSecurity_Comment {
15
  */
16
  public function __construct() {
17
  add_filter('pre_comment_user_ip', array($this, 'pre_comment_user_ip'));
 
 
18
  }
19
 
20
  /**
@@ -29,4 +31,47 @@ class AIOWPSecurity_Comment {
29
  }
30
  return $comment_user_ip;
31
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  }
15
  */
16
  public function __construct() {
17
  add_filter('pre_comment_user_ip', array($this, 'pre_comment_user_ip'));
18
+ add_action('comment_spam_to_approved', array($this, 'comment_spam_status_change'));
19
+ add_action('comment_spam_to_unapproved', array($this, 'comment_spam_status_change'));
20
  }
21
 
22
  /**
31
  }
32
  return $comment_user_ip;
33
  }
34
+
35
+ /**
36
+ * Move spam comments to trash.
37
+ */
38
+ public static function trash_spam_comments() {
39
+ global $aio_wp_security;
40
+ if ('1' == $aio_wp_security->configs->get_value('aiowps_enable_trash_spam_comments') && absint($aio_wp_security->configs->get_value('aiowps_trash_spam_comments_after_days'))) {
41
+ $date_before = absint($aio_wp_security->configs->get_value('aiowps_trash_spam_comments_after_days')).' days ago';
42
+ $comment_ids = get_comments(array(
43
+ 'fields' => 'ids',
44
+ 'status' => 'spam',
45
+ 'date_query' => array(
46
+ array(
47
+ 'before' => $date_before,
48
+ 'inclusive' => true,
49
+ ),
50
+ )
51
+ ));
52
+
53
+ if (!empty($comment_ids)) {
54
+ foreach ($comment_ids as $comment_id) {
55
+ wp_trash_comment($comment_id);
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Delete ip from aiowps_permanent_block table once the comment's spam status changed.
63
+ *
64
+ * @param object $comment_data comment object.
65
+ */
66
+ public function comment_spam_status_change($comment_data) {
67
+ global $wpdb, $aio_wp_security;
68
+ $comment_ip = $comment_data->comment_author_IP;
69
+ $sql = $wpdb->prepare("SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_author_IP = %s AND comment_approved = 'spam'", $comment_ip);
70
+ $total_spam_comment = $wpdb->get_var($sql);
71
+ $min_comment_before_block = $aio_wp_security->configs->get_value('aiowps_spam_ip_min_comments_block');
72
+ if ($total_spam_comment < $min_comment_before_block) {
73
+ $where = array('blocked_ip' => $comment_ip, 'block_reason' => 'spam');
74
+ $wpdb->delete(AIOWPSEC_TBL_PERM_BLOCK, $where, array('%s'));
75
+ }
76
+ }
77
  }
classes/wp-security-configure-settings.php CHANGED
@@ -12,11 +12,15 @@ class AIOWPSecurity_Configure_Settings {
12
  */
13
  public static function set_default_settings() {
14
  global $aio_wp_security;
15
- $blog_email_address = get_bloginfo('admin_email'); //Get the blog admin email address - we will use as the default value
16
-
 
17
  //Debug
18
  $aio_wp_security->configs->set_value('aiowps_enable_debug', '');//Checkbox
19
-
 
 
 
20
  //WP Generator Meta Tag feature
21
  $aio_wp_security->configs->set_value('aiowps_remove_wp_generator_meta_info', '');//Checkbox
22
 
@@ -31,7 +35,8 @@ class AIOWPSecurity_Configure_Settings {
31
  $aio_wp_security->configs->set_value('aiowps_allow_unlock_requests', '1'); // Checkbox
32
  $aio_wp_security->configs->set_value('aiowps_max_login_attempts', '3');
33
  $aio_wp_security->configs->set_value('aiowps_retry_time_period', '5');
34
- $aio_wp_security->configs->set_value('aiowps_lockout_time_length', '60');
 
35
  $aio_wp_security->configs->set_value('aiowps_set_generic_login_msg', '');//Checkbox
36
  $aio_wp_security->configs->set_value('aiowps_enable_email_notify', '');//Checkbox
37
  $aio_wp_security->configs->set_value('aiowps_email_address', $blog_email_address);//text field
@@ -109,6 +114,7 @@ class AIOWPSecurity_Configure_Settings {
109
  //Brute Force features
110
  $aio_wp_security->configs->set_value('aiowps_enable_rename_login_page', '');//Checkbox
111
  $aio_wp_security->configs->set_value('aiowps_enable_login_honeypot', '');//Checkbox
 
112
 
113
  $aio_wp_security->configs->set_value('aiowps_enable_brute_force_attack_prevention', '');//Checkbox
114
  $aio_wp_security->configs->set_value('aiowps_brute_force_secret_word', '');
@@ -128,6 +134,8 @@ class AIOWPSecurity_Configure_Settings {
128
  $aio_wp_security->configs->set_value('aiowps_spam_ip_min_comments_block', '');
129
  $aio_wp_security->configs->set_value('aiowps_enable_bp_register_captcha', '');
130
  $aio_wp_security->configs->set_value('aiowps_enable_bbp_new_topic_captcha', '');//Checkbox
 
 
131
 
132
  //Filescan features
133
  //File change detection feature
@@ -156,6 +164,10 @@ class AIOWPSecurity_Configure_Settings {
156
  $aio_wp_security->configs->set_value('aiowps_recaptcha_secret_key', '');
157
  $aio_wp_security->configs->set_value('aiowps_default_recaptcha', '');//Checkbox
158
 
 
 
 
 
159
  //TODO - keep adding default options for any fields that require it
160
 
161
  //Save it
@@ -164,11 +176,15 @@ class AIOWPSecurity_Configure_Settings {
164
 
165
  public static function add_option_values() {
166
  global $aio_wp_security;
167
- $blog_email_address = get_bloginfo('admin_email'); //Get the blog admin email address - we will use as the default value
 
168
 
169
  //Debug
170
  $aio_wp_security->configs->add_value('aiowps_enable_debug', '');//Checkbox
171
-
 
 
 
172
  //WP Generator Meta Tag feature
173
  $aio_wp_security->configs->add_value('aiowps_remove_wp_generator_meta_info', '');//Checkbox
174
 
@@ -184,7 +200,8 @@ class AIOWPSecurity_Configure_Settings {
184
  $aio_wp_security->configs->add_value('aiowps_allow_unlock_requests', '1'); // Checkbox
185
  $aio_wp_security->configs->add_value('aiowps_max_login_attempts', '3');
186
  $aio_wp_security->configs->add_value('aiowps_retry_time_period', '5');
187
- $aio_wp_security->configs->add_value('aiowps_lockout_time_length', '60');
 
188
  $aio_wp_security->configs->add_value('aiowps_set_generic_login_msg', '');//Checkbox
189
  $aio_wp_security->configs->add_value('aiowps_enable_email_notify', '');//Checkbox
190
  $aio_wp_security->configs->add_value('aiowps_email_address', $blog_email_address);//text field
@@ -258,6 +275,7 @@ class AIOWPSecurity_Configure_Settings {
258
  //Brute Force features
259
  $aio_wp_security->configs->add_value('aiowps_enable_rename_login_page', '');//Checkbox
260
  $aio_wp_security->configs->add_value('aiowps_enable_login_honeypot', '');//Checkbox
 
261
 
262
  $aio_wp_security->configs->add_value('aiowps_enable_brute_force_attack_prevention', '');//Checkbox
263
  $aio_wp_security->configs->add_value('aiowps_brute_force_secret_word', '');
@@ -277,6 +295,8 @@ class AIOWPSecurity_Configure_Settings {
277
  $aio_wp_security->configs->add_value('aiowps_spam_ip_min_comments_block', '');
278
  $aio_wp_security->configs->add_value('aiowps_enable_bp_register_captcha', '');
279
  $aio_wp_security->configs->add_value('aiowps_enable_bbp_new_topic_captcha', '');//Checkbox
 
 
280
 
281
 
282
  //Filescan features
@@ -306,6 +326,10 @@ class AIOWPSecurity_Configure_Settings {
306
  $aio_wp_security->configs->add_value('aiowps_recaptcha_secret_key', '');
307
  $aio_wp_security->configs->add_value('aiowps_default_recaptcha', '');//Checkbox
308
 
 
 
 
 
309
  //TODO - keep adding default options for any fields that require it
310
 
311
  //Save it
12
  */
13
  public static function set_default_settings() {
14
  global $aio_wp_security;
15
+ $blog_email_address = array();
16
+ $blog_email_address[] = get_bloginfo('admin_email'); //Get the blog admin email address - we will use as the default value
17
+
18
  //Debug
19
  $aio_wp_security->configs->set_value('aiowps_enable_debug', '');//Checkbox
20
+
21
+ //PHP backtrace
22
+ $aio_wp_security->configs->set_value('aiowps_enable_php_backtrace_in_email', '');//Checkbox
23
+
24
  //WP Generator Meta Tag feature
25
  $aio_wp_security->configs->set_value('aiowps_remove_wp_generator_meta_info', '');//Checkbox
26
 
35
  $aio_wp_security->configs->set_value('aiowps_allow_unlock_requests', '1'); // Checkbox
36
  $aio_wp_security->configs->set_value('aiowps_max_login_attempts', '3');
37
  $aio_wp_security->configs->set_value('aiowps_retry_time_period', '5');
38
+ $aio_wp_security->configs->set_value('aiowps_lockout_time_length', '5');
39
+ $aio_wp_security->configs->set_value('aiowps_max_lockout_time_length', '60');
40
  $aio_wp_security->configs->set_value('aiowps_set_generic_login_msg', '');//Checkbox
41
  $aio_wp_security->configs->set_value('aiowps_enable_email_notify', '');//Checkbox
42
  $aio_wp_security->configs->set_value('aiowps_email_address', $blog_email_address);//text field
114
  //Brute Force features
115
  $aio_wp_security->configs->set_value('aiowps_enable_rename_login_page', '');//Checkbox
116
  $aio_wp_security->configs->set_value('aiowps_enable_login_honeypot', '');//Checkbox
117
+ $aio_wp_security->configs->set_value('aiowps_disable_application_password', '');//Checkbox
118
 
119
  $aio_wp_security->configs->set_value('aiowps_enable_brute_force_attack_prevention', '');//Checkbox
120
  $aio_wp_security->configs->set_value('aiowps_brute_force_secret_word', '');
134
  $aio_wp_security->configs->set_value('aiowps_spam_ip_min_comments_block', '');
135
  $aio_wp_security->configs->set_value('aiowps_enable_bp_register_captcha', '');
136
  $aio_wp_security->configs->set_value('aiowps_enable_bbp_new_topic_captcha', '');//Checkbox
137
+ $aio_wp_security->configs->set_value('aiowps_enable_trash_spam_comments', '');
138
+ $aio_wp_security->configs->set_value('aiowps_trash_spam_comments_after_days', '14');
139
 
140
  //Filescan features
141
  //File change detection feature
164
  $aio_wp_security->configs->set_value('aiowps_recaptcha_secret_key', '');
165
  $aio_wp_security->configs->set_value('aiowps_default_recaptcha', '');//Checkbox
166
 
167
+ // Deactivation Handler
168
+ $aio_wp_security->configs->set_value('aiowps_on_uninstall_delete_db_tables', '1'); //Checkbox
169
+ $aio_wp_security->configs->set_value('aiowps_on_uninstall_delete_configs', '1'); //Checkbox
170
+
171
  //TODO - keep adding default options for any fields that require it
172
 
173
  //Save it
176
 
177
  public static function add_option_values() {
178
  global $aio_wp_security;
179
+ $blog_email_address = array();
180
+ $blog_email_address[] = get_bloginfo('admin_email'); //Get the blog admin email address - we will use as the default value
181
 
182
  //Debug
183
  $aio_wp_security->configs->add_value('aiowps_enable_debug', '');//Checkbox
184
+
185
+ //PHP backtrace
186
+ $aio_wp_security->configs->add_value('aiowps_enable_php_backtrace_in_email', '');//Checkbox
187
+
188
  //WP Generator Meta Tag feature
189
  $aio_wp_security->configs->add_value('aiowps_remove_wp_generator_meta_info', '');//Checkbox
190
 
200
  $aio_wp_security->configs->add_value('aiowps_allow_unlock_requests', '1'); // Checkbox
201
  $aio_wp_security->configs->add_value('aiowps_max_login_attempts', '3');
202
  $aio_wp_security->configs->add_value('aiowps_retry_time_period', '5');
203
+ $aio_wp_security->configs->add_value('aiowps_lockout_time_length', '5');
204
+ $aio_wp_security->configs->add_value('aiowps_max_lockout_time_length', '60');
205
  $aio_wp_security->configs->add_value('aiowps_set_generic_login_msg', '');//Checkbox
206
  $aio_wp_security->configs->add_value('aiowps_enable_email_notify', '');//Checkbox
207
  $aio_wp_security->configs->add_value('aiowps_email_address', $blog_email_address);//text field
275
  //Brute Force features
276
  $aio_wp_security->configs->add_value('aiowps_enable_rename_login_page', '');//Checkbox
277
  $aio_wp_security->configs->add_value('aiowps_enable_login_honeypot', '');//Checkbox
278
+ $aio_wp_security->configs->set_value('aiowps_disable_application_password', '1');//Checkbox
279
 
280
  $aio_wp_security->configs->add_value('aiowps_enable_brute_force_attack_prevention', '');//Checkbox
281
  $aio_wp_security->configs->add_value('aiowps_brute_force_secret_word', '');
295
  $aio_wp_security->configs->add_value('aiowps_spam_ip_min_comments_block', '');
296
  $aio_wp_security->configs->add_value('aiowps_enable_bp_register_captcha', '');
297
  $aio_wp_security->configs->add_value('aiowps_enable_bbp_new_topic_captcha', '');//Checkbox
298
+ $aio_wp_security->configs->set_value('aiowps_enable_trash_spam_comments', '');
299
+ $aio_wp_security->configs->set_value('aiowps_trash_spam_comments_after_days', '14');
300
 
301
 
302
  //Filescan features
326
  $aio_wp_security->configs->add_value('aiowps_recaptcha_secret_key', '');
327
  $aio_wp_security->configs->add_value('aiowps_default_recaptcha', '');//Checkbox
328
 
329
+ // Deactivation Handler
330
+ $aio_wp_security->configs->set_value('aiowps_on_uninstall_delete_db_tables', '1'); //Checkbox
331
+ $aio_wp_security->configs->set_value('aiowps_on_uninstall_delete_configs', '1'); //Checkbox
332
+
333
  //TODO - keep adding default options for any fields that require it
334
 
335
  //Save it
classes/wp-security-cronjob-handler.php CHANGED
@@ -12,17 +12,66 @@ class AIOWPSecurity_Cronjob_Handler {
12
  * Class constructor
13
  */
14
  public function __construct() {
 
 
 
15
  add_action('aiowps_hourly_cron_event', array($this, 'aiowps_hourly_cron_event_handler'));
16
  add_action('aiowps_daily_cron_event', array($this, 'aiowps_daily_cron_event_handler'));
17
  add_action('aiowps_perform_failed_login_cleanup_task', array($this, 'failed_login_cleanup'));
18
  add_action('aiowps_purge_old_debug_logs', array($this, 'purge_old_debug_logs'));
 
19
  }
20
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  public function aiowps_hourly_cron_event_handler() {
22
  //Do stuff that needs checking hourly
23
- do_action('aiowps_perform_scheduled_backup_tasks');
24
  do_action('aiowps_perform_fcd_scan_tasks');
25
- do_action('aiowps_perform_db_cleanup_tasks');
26
  }
27
 
28
  /**
@@ -31,8 +80,10 @@ class AIOWPSecurity_Cronjob_Handler {
31
  * @return void
32
  */
33
  public function aiowps_daily_cron_event_handler() {
 
34
  do_action('aiowps_perform_failed_login_cleanup_task');
35
  do_action('aiowps_purge_old_debug_logs');
 
36
  }
37
 
38
  /**
@@ -44,7 +95,7 @@ class AIOWPSecurity_Cronjob_Handler {
44
  global $wpdb, $aio_wp_security;
45
 
46
  $purge_records_after_days = apply_filters('aiowps_purge_failed_login_records_after_days', AIOWPSEC_PURGE_FAILED_LOGIN_RECORDS_AFTER_DAYS);
47
- $older_than_date_time = date('Y-m-d H:m:s', strtotime('-' . $purge_records_after_days . ' days', strtotime(current_time('mysql', true))));
48
  $sql = $wpdb->prepare('DELETE FROM ' . AIOWPSEC_TBL_FAILED_LOGINS . ' WHERE failed_login_date<%s', $older_than_date_time);
49
  $ret_deleted = $wpdb->query($sql);
50
  if (false === $ret_deleted) {
@@ -83,4 +134,13 @@ class AIOWPSecurity_Cronjob_Handler {
83
  $aio_wp_security->debug_logger->log_debug_cron("Failed to purge older debug logs : {$error_msg}", 4);
84
  }
85
  }
 
 
 
 
 
 
 
 
 
86
  }
12
  * Class constructor
13
  */
14
  public function __construct() {
15
+ add_filter('cron_schedules', array('AIOWPSecurity_Cronjob_Handler', 'cron_schedules'));
16
+
17
+ add_action('aios_15_minutes_cron_event', array($this, 'aios_15_minutes_cron_event'));
18
  add_action('aiowps_hourly_cron_event', array($this, 'aiowps_hourly_cron_event_handler'));
19
  add_action('aiowps_daily_cron_event', array($this, 'aiowps_daily_cron_event_handler'));
20
  add_action('aiowps_perform_failed_login_cleanup_task', array($this, 'failed_login_cleanup'));
21
  add_action('aiowps_purge_old_debug_logs', array($this, 'purge_old_debug_logs'));
22
+ add_action('aiowps_send_lockout_email', array($this, 'send_lockout_email'));
23
  }
24
+
25
+ /**
26
+ * Adds a custom cron schedule for every 5 minutes.
27
+ *
28
+ * @param array $schedules An array of cron schedules.
29
+ * @return array Filtered array of cron schedules.
30
+ */
31
+ public static function cron_schedules($schedules) {
32
+ $schedules['aios-every-15-minutes'] = array(
33
+ 'interval' => 900, // 15 * 60
34
+ 'display' => __('Every 15 minutes', 'all-in-one-wp-security-and-firewall')
35
+ );
36
+ return $schedules;
37
+ }
38
+
39
+ /**
40
+ * Run cron event every 5 minute.
41
+ *
42
+ * @return void
43
+ */
44
+ public function aios_15_minutes_cron_event() {
45
+ global $aio_wp_security;
46
+
47
+ if (!class_exists('Updraft_Semaphore_3_0')) {
48
+ require_once(AIO_WP_SECURITY_PATH.'/vendor/team-updraft/common-libs/src/updraft-semaphore/class-updraft-semaphore.php');
49
+ }
50
+
51
+ $fifteen_minutes_cron_semaphore = new Updraft_Semaphore_3_0('aios_15_minutes_cron_event', 60);
52
+
53
+ if ($fifteen_minutes_cron_semaphore->lock(2)) {
54
+ try {
55
+ $aio_wp_security->user_login_obj->send_login_lockout_emails();
56
+ } catch (Exception $e) {
57
+ $log_message = 'Exception ('.get_class($e).') occurred during the 5 minutes cron event action call: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
58
+ $aio_wp_security->debug_logger->log_debug($log_message, 4);
59
+ // @codingStandardsIgnoreLine
60
+ } catch (Error $e) {
61
+ $log_message = 'PHP Fatal error ('.get_class($e).') occurred during the the 5 minutes cron event action call. Error Message: '.$e->getMessage().' (Code: '.$e->getCode().', line '.$e->getLine().' in '.$e->getFile().')';
62
+ $aio_wp_security->debug_logger->log_debug($log_message, 4);
63
+ }
64
+
65
+ $fifteen_minutes_cron_semaphore->release();
66
+ } else {
67
+ $aio_wp_security->debug_logger->log_debug('The 15 minutes cron event lock could not get the lock.');
68
+ }
69
+ }
70
+
71
  public function aiowps_hourly_cron_event_handler() {
72
  //Do stuff that needs checking hourly
73
+ AIOWPSecurity_Comment::trash_spam_comments();
74
  do_action('aiowps_perform_fcd_scan_tasks');
 
75
  }
76
 
77
  /**
80
  * @return void
81
  */
82
  public function aiowps_daily_cron_event_handler() {
83
+ do_action('aiowps_perform_db_cleanup_tasks');
84
  do_action('aiowps_perform_failed_login_cleanup_task');
85
  do_action('aiowps_purge_old_debug_logs');
86
+ do_action('aiowps_send_lockout_email');
87
  }
88
 
89
  /**
95
  global $wpdb, $aio_wp_security;
96
 
97
  $purge_records_after_days = apply_filters('aiowps_purge_failed_login_records_after_days', AIOWPSEC_PURGE_FAILED_LOGIN_RECORDS_AFTER_DAYS);
98
+ $older_than_date_time = date('Y-m-d H:m:s', strtotime('-' . $purge_records_after_days . ' days', current_time('timestamp', true)));
99
  $sql = $wpdb->prepare('DELETE FROM ' . AIOWPSEC_TBL_FAILED_LOGINS . ' WHERE failed_login_date<%s', $older_than_date_time);
100
  $ret_deleted = $wpdb->query($sql);
101
  if (false === $ret_deleted) {
134
  $aio_wp_security->debug_logger->log_debug_cron("Failed to purge older debug logs : {$error_msg}", 4);
135
  }
136
  }
137
+ /**
138
+ * Send email notification to an user who has flag is_lockout_email_sent is 0.
139
+ *
140
+ * @return Void
141
+ */
142
+ public function send_lockout_email() {
143
+ global $aio_wp_security;
144
+ $aio_wp_security->user_login_obj->send_login_lockout_emails();
145
+ }
146
  }
classes/wp-security-deactivation-tasks.php CHANGED
@@ -3,54 +3,27 @@ if (!defined('ABSPATH')) {
3
  exit;//Exit if accessed directly
4
  }
5
 
6
- //Allows activating via wp-cli
7
- require_once(dirname(__FILE__) . '/wp-security-configure-settings.php');
8
 
9
- class AIOWPSecurity_Deactivation {
10
  /**
11
- * Runs various deactivation tasks
12
- * Handles single and multi-site (NW activation) cases
13
  *
14
- * @global type $wpdb
15
- * @global type $aio_wp_security
16
- * @param type $networkwide
17
  */
18
- public static function run_deactivation_tasks($networkwide) {
19
- global $wpdb;
20
  global $aio_wp_security;
21
-
22
- if (AIOWPSecurity_Utility::is_multisite_install()) {
23
- delete_site_transient('users_online');
24
- } else {
25
- delete_transient('users_online');
26
- }
27
-
28
- if (AIOWPSecurity_Utility::is_multisite_install() && $networkwide) {
29
- // check if it is a network activation
30
- $blogids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs");
31
- foreach ($blogids as $blog_id) {
32
- switch_to_blog($blog_id);
33
- //Let's first save the current aio_wp_security_configs options in a temp option
34
- update_option('aiowps_temp_configs', $aio_wp_security->configs->configs);
35
-
36
- AIOWPSecurity_Deactivation::clear_cron_events();
37
- restore_current_blog();
38
- }
39
- } else {
40
- //Let's first save the current aio_wp_security_configs options in a temp option
41
- update_option('aiowps_temp_configs', $aio_wp_security->configs->configs);
42
-
43
- AIOWPSecurity_Deactivation::clear_cron_events();
44
  }
45
- //Deactivate all firewall and other .htaccess rules
46
- AIOWPSecurity_Configure_Settings::turn_off_all_firewall_rules();
47
- }
48
-
49
- /**
50
- * Helper function which clears aiowps cron events
51
- */
52
- public static function clear_cron_events() {
53
- wp_clear_scheduled_hook('aiowps_hourly_cron_event');
54
- wp_clear_scheduled_hook('aiowps_daily_cron_event');
55
  }
56
  }
3
  exit;//Exit if accessed directly
4
  }
5
 
6
+ require_once(AIO_WP_SECURITY_PATH.'/classes/wp-security-base-tasks.php');
 
7
 
8
+ class AIOWPSecurity_Deactivation_Tasks extends AIOWPSecurity_Base_Tasks {
9
  /**
10
+ * Run deactivation task for a single site.
 
11
  *
12
+ * @return void
 
 
13
  */
14
+ protected static function run_for_a_site() {
 
15
  global $aio_wp_security;
16
+ //Let's first save the current aio_wp_security_configs options in a temp option
17
+ update_option('aiowps_temp_configs', $aio_wp_security->configs->configs);
18
+
19
+ delete_option('aio_wp_security_configs');
20
+
21
+ if (is_main_site()) {
22
+ // Remove all firewall and other .htaccess rules and remove all settings from .htaccess file that were added by this plugin
23
+ AIOWPSecurity_Configure_Settings::turn_off_all_firewall_rules();
24
+
25
+ //Deactivates PHP-based firewall
26
+ AIOWPSecurity_Utility_Firewall::remove_firewall();
 
 
 
 
 
 
 
 
 
 
 
 
27
  }
 
 
 
 
 
 
 
 
 
 
28
  }
29
  }
classes/wp-security-debug-logger.php CHANGED
@@ -16,7 +16,6 @@ class AIOWPSecurity_Logger {
16
 
17
  public function __construct($debug_enabled) {
18
  $this->debug_enabled = $debug_enabled;
19
- $this->maybe_create_debug_log_table();
20
  }
21
 
22
  /**
@@ -29,46 +28,6 @@ class AIOWPSecurity_Logger {
29
  return isset($this->debug_readable_level[$level_code]) ? $this->debug_readable_level[$level_code] : 'UNKNOWN';
30
  }
31
 
32
- /**
33
- * Creates the debug log table if it doesn't already exist
34
- *
35
- * @return void
36
- */
37
- private function maybe_create_debug_log_table() {
38
-
39
- global $wpdb;
40
-
41
- if (!function_exists('maybe_create_table')) {
42
- //needed for the maybe_create_table function
43
- require_once ABSPATH . 'wp-admin/includes/upgrade.php';
44
- }
45
-
46
- $charset_collate = '';
47
- if (!empty($wpdb->charset)) {
48
- $charset_collate = "DEFAULT CHARACTER SET $wpdb->charset";
49
- } else {
50
- $charset_collate = "DEFAULT CHARSET=utf8";
51
- }
52
- if (!empty($wpdb->collate)) {
53
- $charset_collate .= " COLLATE $wpdb->collate";
54
- }
55
-
56
- //This exists as a constant, but multisite will need to refresh $wpdb->prefix
57
- $debug_log_tbl_name = $wpdb->prefix.'aiowps_debug_log';
58
-
59
- $debug_log_tbl_sql = "CREATE TABLE " . $debug_log_tbl_name . " (
60
- id bigint(20) NOT NULL AUTO_INCREMENT,
61
- level varchar(25) NOT NULL DEFAULT '',
62
- message text NOT NULL DEFAULT '',
63
- type varchar(25) NOT NULL DEFAULT '',
64
- created datetime NOT NULL DEFAULT '1000-10-10 10:00:00',
65
- PRIMARY KEY (id)
66
- )" . $charset_collate . ";";
67
-
68
- maybe_create_table($debug_log_tbl_name, $debug_log_tbl_sql);
69
-
70
- }
71
-
72
  /**
73
  * Clears the debug logs
74
  *
16
 
17
  public function __construct($debug_enabled) {
18
  $this->debug_enabled = $debug_enabled;
 
19
  }
20
 
21
  /**
28
  return isset($this->debug_readable_level[$level_code]) ? $this->debug_readable_level[$level_code] : 'UNKNOWN';
29
  }
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  /**
32
  * Clears the debug logs
33
  *
classes/wp-security-general-init-tasks.php CHANGED
@@ -28,16 +28,18 @@ class AIOWPSecurity_General_Init_Tasks {
28
  add_filter('retrieve_password_message', array($this, 'decode_reset_pw_msg'), 10, 4); //Fix for non decoded html entities in password reset link
29
  }
30
 
31
- if (current_user_can(AIOWPSEC_MANAGEMENT_PERMISSION) && $aio_wp_security->configs->get_value('aios_is_google_recaptcha_wrong_site_key')) {
32
- add_action('all_admin_notices', array($this, 'google_recaptcha_notice'));
33
- }
 
 
 
 
34
 
35
- if (current_user_can(AIOWPSEC_MANAGEMENT_PERMISSION)) {
36
  $this->reapply_htaccess_rules();
37
  add_action('admin_notices', array($this,'reapply_htaccess_rules_notice'));
38
  }
39
 
40
-
41
  /**
42
  * Send X-Frame-Options: SAMEORIGIN in HTTP header
43
  */
@@ -54,13 +56,38 @@ class AIOWPSecurity_General_Init_Tasks {
54
  // For the cookie based brute force prevention feature
55
  if ($aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention') == 1) {
56
  $bfcf_secret_word = $aio_wp_security->configs->get_value('aiowps_brute_force_secret_word');
 
57
  if (isset($_GET[$bfcf_secret_word])) {
58
  // If URL contains secret word in query param then set cookie and then redirect to the login page
59
- AIOWPSecurity_Utility::set_cookie_value($bfcf_secret_word, "1");
60
- AIOWPSecurity_Utility::redirect_to_url(AIOWPSEC_WP_URL."/wp-admin");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  }
62
  }
63
-
64
  // Stop users enumeration feature
65
  if ($aio_wp_security->configs->get_value('aiowps_prevent_users_enumeration') == 1) {
66
  include_once(AIO_WP_SECURITY_PATH.'/other-includes/wp-security-stop-users-enumeration.php');
@@ -167,6 +194,25 @@ class AIOWPSecurity_General_Init_Tasks {
167
  }
168
  }
169
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  // For lost password captcha feature
171
  if ($aio_wp_security->configs->get_value('aiowps_enable_lost_password_captcha') == '1') {
172
  if (!is_user_logged_in()) {
@@ -181,7 +227,7 @@ class AIOWPSecurity_General_Init_Tasks {
181
  }
182
 
183
  // For registration page captcha feature
184
- if (AIOWPSecurity_Utility::is_multisite_install()) {
185
  $blog_id = get_current_blog_id();
186
  switch_to_blog($blog_id);
187
  if ($aio_wp_security->configs->get_value('aiowps_enable_registration_page_captcha') == '1') {
@@ -201,7 +247,7 @@ class AIOWPSecurity_General_Init_Tasks {
201
  }
202
 
203
  // For comment captcha feature or custom login form captcha
204
- if (AIOWPSecurity_Utility::is_multisite_install()) {
205
  $blog_id = get_current_blog_id();
206
  switch_to_blog($blog_id);
207
  if ($aio_wp_security->configs->get_value('aiowps_enable_comment_captcha') == '1') {
@@ -248,7 +294,6 @@ class AIOWPSecurity_General_Init_Tasks {
248
  if ($aio_wp_security->configs->get_value('aiowps_enable_404_logging') == '1') {
249
  add_action('wp_head', array($this, 'check_404_event'));
250
  }
251
-
252
  // Add more tasks that need to be executed at init time
253
 
254
  } // end _construct()
@@ -435,6 +480,35 @@ class AIOWPSecurity_General_Init_Tasks {
435
  echo $honey_input;
436
  }
437
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
  public function process_comment_post($comment) {
439
  global $aio_wp_security;
440
  if (is_user_logged_in()) {
@@ -657,4 +731,16 @@ class AIOWPSecurity_General_Init_Tasks {
657
  wp_enqueue_script('google-recaptcha', 'https://www.google.com/recaptcha/api.js', false);
658
  }
659
  }
 
 
 
 
 
 
 
 
 
 
 
 
660
  }
28
  add_filter('retrieve_password_message', array($this, 'decode_reset_pw_msg'), 10, 4); //Fix for non decoded html entities in password reset link
29
  }
30
 
31
+ if (current_user_can(AIOWPSEC_MANAGEMENT_PERMISSION) && is_admin()) {
32
+ if ($aio_wp_security->configs->get_value('aios_is_google_recaptcha_wrong_site_key')) {
33
+ add_action('all_admin_notices', array($this, 'google_recaptcha_notice'));
34
+ }
35
+
36
+ add_action('all_admin_notices', array($this, 'do_firewall_notice'));
37
+ add_action('admin_post_aiowps_firewall_setup', array(AIOWPSecurity_Firewall_Setup_Notice::get_instance(), 'handle_setup_form'));
38
 
 
39
  $this->reapply_htaccess_rules();
40
  add_action('admin_notices', array($this,'reapply_htaccess_rules_notice'));
41
  }
42
 
 
43
  /**
44
  * Send X-Frame-Options: SAMEORIGIN in HTTP header
45
  */
56
  // For the cookie based brute force prevention feature
57
  if ($aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention') == 1) {
58
  $bfcf_secret_word = $aio_wp_security->configs->get_value('aiowps_brute_force_secret_word');
59
+ $login_page_slug = $aio_wp_security->configs->get_value('aiowps_login_page_slug');
60
  if (isset($_GET[$bfcf_secret_word])) {
61
  // If URL contains secret word in query param then set cookie and then redirect to the login page
62
+ AIOWPSecurity_Utility::set_cookie_value($bfcf_secret_word, '1');
63
+ if ('1' == $aio_wp_security->configs->get_value('aiowps_enable_rename_login_page') && !is_user_logged_in()) {
64
+ $login_url = site_url($login_page_slug);
65
+ AIOWPSecurity_Utility::redirect_to_url($login_url);
66
+ } else {
67
+ AIOWPSecurity_Utility::redirect_to_url(AIOWPSEC_WP_URL.'/wp-admin');
68
+ }
69
+ } else {
70
+ $secret_word_cookie_val = AIOWPSecurity_Utility::get_cookie_value($bfcf_secret_word);
71
+ $pw_protected_exception = $aio_wp_security->configs->get_value('aiowps_brute_force_attack_prevention_pw_protected_exception');
72
+ $prevent_ajax_exception = $aio_wp_security->configs->get_value('aiowps_brute_force_attack_prevention_ajax_exception');
73
+
74
+ if ('' != $_SERVER['REQUEST_URI'] && 1 != $secret_word_cookie_val) {
75
+ // admin section or login page or login custom slug called
76
+ $is_admin_or_login = (false != strpos($_SERVER['REQUEST_URI'], 'wp-admin') || false != strpos($_SERVER['REQUEST_URI'], 'wp-login') || ('' != $login_page_slug && false != strpos($_SERVER['REQUEST_URI'], $login_page_slug))) ? 1 : 0;
77
+
78
+ // admin side ajax called
79
+ $is_admin_ajax_request = ('1' == $prevent_ajax_exception && false != strpos($_SERVER['REQUEST_URI'], 'wp-admin/admin-ajax.php')) ? 1 : 0;
80
+
81
+ // password protected page called
82
+ $is_password_protected_access = ('1' == $pw_protected_exception && isset($_GET['action']) && 'postpass' == $_GET['action']) ? 1 : 0;
83
+ // cookie based brute force on and accessing admin without ajax and password protected then redirect
84
+ if ($is_admin_or_login && !$is_admin_ajax_request && !$is_password_protected_access) {
85
+ $redirect_url = $aio_wp_security->configs->get_value('aiowps_cookie_based_brute_force_redirect_url');
86
+ AIOWPSecurity_Utility::redirect_to_url($redirect_url);
87
+ }
88
+ }
89
  }
90
  }
 
91
  // Stop users enumeration feature
92
  if ($aio_wp_security->configs->get_value('aiowps_prevent_users_enumeration') == 1) {
93
  include_once(AIO_WP_SECURITY_PATH.'/other-includes/wp-security-stop-users-enumeration.php');
194
  }
195
  }
196
 
197
+ // For disable application password feature hide generate password
198
+ if ('1' == $aio_wp_security->configs->get_value('aiowps_disable_application_password')) {
199
+ add_filter('wp_is_application_passwords_available', '__return_false');
200
+ add_action('edit_user_profile', array($this, 'show_disabled_application_password_message'));
201
+ add_action('show_user_profile', array($this, 'show_disabled_application_password_message'));
202
+
203
+ // Override the wp_die handler for app passwords were disabled.
204
+ if (!empty($_SERVER['SCRIPT_FILENAME']) && ABSPATH . 'wp-admin/authorize-application.php' == $_SERVER['SCRIPT_FILENAME']) {
205
+ add_filter('wp_die_handler', function () {
206
+ return function ($message, $title, $args) {
207
+ if ('Application passwords are not available.' == $message) {
208
+ $message = htmlspecialchars(__('Application passwords have been disabled by All In One WP Security & Firewall plugin.', 'all-in-one-wp-security-and-firewall'));
209
+ }
210
+ _default_wp_die_handler($message, $title, $args);
211
+ };
212
+ }, 10, 1);
213
+ }
214
+ }
215
+
216
  // For lost password captcha feature
217
  if ($aio_wp_security->configs->get_value('aiowps_enable_lost_password_captcha') == '1') {
218
  if (!is_user_logged_in()) {
227
  }
228
 
229
  // For registration page captcha feature
230
+ if (is_multisite()) {
231
  $blog_id = get_current_blog_id();
232
  switch_to_blog($blog_id);
233
  if ($aio_wp_security->configs->get_value('aiowps_enable_registration_page_captcha') == '1') {
247
  }
248
 
249
  // For comment captcha feature or custom login form captcha
250
+ if (is_multisite()) {
251
  $blog_id = get_current_blog_id();
252
  switch_to_blog($blog_id);
253
  if ($aio_wp_security->configs->get_value('aiowps_enable_comment_captcha') == '1') {
294
  if ($aio_wp_security->configs->get_value('aiowps_enable_404_logging') == '1') {
295
  add_action('wp_head', array($this, 'check_404_event'));
296
  }
 
297
  // Add more tasks that need to be executed at init time
298
 
299
  } // end _construct()
480
  echo $honey_input;
481
  }
482
 
483
+ /**
484
+ * Shows application password disabed message on user edit profile page.
485
+ * If logged user is admin showing the Change Setting option.
486
+ *
487
+ * @return void
488
+ */
489
+ public function show_disabled_application_password_message() {
490
+ if (is_user_logged_in() && is_admin()) {
491
+ $disabled_message = '<h2>'.__('Application Passwords', 'all-in-one-wp-security-and-firewall').'</h2>';
492
+ $disabled_message .= '<table class="form-table" role="presentation">';
493
+ $disabled_message .= '<tbody>';
494
+ $disabled_message .= '<tr id="disable-password">';
495
+ $disabled_message .= '<th>'.__('Disabled').'</th>';
496
+ $disabled_message .= '<td>'.htmlspecialchars(__('Application passwords have been disabled by All In One WP Security & Firewall plugin.', 'all-in-one-wp-security-and-firewall'));
497
+ if (current_user_can(AIOWPSEC_MANAGEMENT_PERMISSION)) {
498
+ $aiowps_addtional_setting_url = 'admin.php?page=aiowpsec_userlogin&tab=additional';
499
+ $change_setting_url = is_multisite() ? network_admin_url($aiowps_addtional_setting_url) : admin_url($aiowps_addtional_setting_url);
500
+ $disabled_message .= '<p><a href="'.$change_setting_url.'" class="button">'.__('Change Setting', 'all-in-one-wp-security-and-firewall').'</a></p>';
501
+ } else {
502
+ $disabled_message .= ' '.__('Site admin can only change this setting.', 'all-in-one-wp-security-and-firewall');
503
+ }
504
+ $disabled_message .= '</td>';
505
+ $disabled_message .= '</tr>';
506
+ $disabled_message .= '<tbody>';
507
+ $disabled_message .= '</table>';
508
+ echo $disabled_message;
509
+ }
510
+ }
511
+
512
  public function process_comment_post($comment) {
513
  global $aio_wp_security;
514
  if (is_user_logged_in()) {
731
  wp_enqueue_script('google-recaptcha', 'https://www.google.com/recaptcha/api.js', false);
732
  }
733
  }
734
+
735
+ /**
736
+ * Shows the firewall notice
737
+ *
738
+ * @return void
739
+ */
740
+ public function do_firewall_notice() {
741
+
742
+ $firewall_setup = AIOWPSecurity_Firewall_Setup_Notice::get_instance();
743
+ $firewall_setup->start_firewall_setup();
744
+
745
+ }
746
  }
classes/wp-security-installer.php CHANGED
@@ -3,10 +3,14 @@ if (!defined('ABSPATH')) {
3
  exit;//Exit if accessed directly
4
  }
5
 
6
- //Allows activating via wp-cli
7
- require_once(dirname(__FILE__) . '/wp-security-configure-settings.php');
8
-
9
  class AIOWPSecurity_Installer {
 
 
 
 
 
 
 
10
  public static function run_installer($networkwide = '') {
11
  global $wpdb;
12
  if (function_exists('is_multisite') && is_multisite() && $networkwide) {
@@ -57,6 +61,8 @@ class AIOWPSecurity_Installer {
57
  $perm_block_tbl_name = AIOWPSEC_TBL_PERM_BLOCK;
58
  }
59
 
 
 
60
  $charset_collate = '';
61
  if (!empty($wpdb->charset)) {
62
  $charset_collate = "DEFAULT CHARACTER SET $wpdb->charset";
@@ -76,6 +82,8 @@ class AIOWPSecurity_Installer {
76
  failed_login_ip varchar(100) NOT NULL DEFAULT '',
77
  lock_reason varchar(128) NOT NULL DEFAULT '',
78
  unlock_key varchar(128) NOT NULL DEFAULT '',
 
 
79
  PRIMARY KEY (id)
80
  )" . $charset_collate . ";";
81
  dbDelta($ld_tbl_sql);
@@ -146,6 +154,16 @@ class AIOWPSecurity_Installer {
146
  )" . $charset_collate . ";";
147
  dbDelta($pb_tbl_sql);
148
 
 
 
 
 
 
 
 
 
 
 
149
  update_option("aiowpsec_db_version", AIO_WP_SECURITY_DB_VERSION);
150
  }
151
 
@@ -153,7 +171,7 @@ class AIOWPSecurity_Installer {
153
  global $aio_wp_security;
154
  //Create our folder in the "wp-content" directory
155
  $aiowps_dir = WP_CONTENT_DIR . '/' . AIO_WP_SECURITY_BACKUPS_DIR_NAME;
156
- if (!is_dir($aiowps_dir)) {
157
  mkdir($aiowps_dir, 0755, true);
158
  //Let's also create an empty index.html file in this folder
159
  $index_file = $aiowps_dir . '/index.html';
@@ -209,11 +227,15 @@ class AIOWPSecurity_Installer {
209
  * Handles both single and multi-site (NW activation) cases
210
  *
211
  * @global type $wpdb
212
- * @param type $networkwide
 
213
  */
214
- public static function set_cron_tasks_upon_activation($networkwide) {
215
- global $wpdb;
216
- if (AIOWPSecurity_Utility::is_multisite_install() && $networkwide) {
 
 
 
217
  // check if it is a network activation
218
  $blogids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs");
219
  foreach ($blogids as $blog_id) {
@@ -229,9 +251,14 @@ class AIOWPSecurity_Installer {
229
  }
230
 
231
  /**
232
- * Helper function for scheduling aiowps cron events
 
 
233
  */
234
  public static function schedule_cron_events() {
 
 
 
235
  if (!wp_next_scheduled('aiowps_hourly_cron_event')) {
236
  wp_schedule_event(time(), 'hourly', 'aiowps_hourly_cron_event'); //schedule an hourly cron event
237
  }
3
  exit;//Exit if accessed directly
4
  }
5
 
 
 
 
6
  class AIOWPSecurity_Installer {
7
+
8
+ /**
9
+ * Run installer function.
10
+ *
11
+ * @param boolean $networkwide
12
+ * @return void
13
+ */
14
  public static function run_installer($networkwide = '') {
15
  global $wpdb;
16
  if (function_exists('is_multisite') && is_multisite() && $networkwide) {
61
  $perm_block_tbl_name = AIOWPSEC_TBL_PERM_BLOCK;
62
  }
63
 
64
+ $debug_log_tbl_name = AIOWPSEC_TBL_DEBUG_LOG;
65
+
66
  $charset_collate = '';
67
  if (!empty($wpdb->charset)) {
68
  $charset_collate = "DEFAULT CHARACTER SET $wpdb->charset";
82
  failed_login_ip varchar(100) NOT NULL DEFAULT '',
83
  lock_reason varchar(128) NOT NULL DEFAULT '',
84
  unlock_key varchar(128) NOT NULL DEFAULT '',
85
+ is_lockout_email_sent tinyint(1) NOT NULL DEFAULT '0',
86
+ backtrace_log text NOT NULL DEFAULT '',
87
  PRIMARY KEY (id)
88
  )" . $charset_collate . ";";
89
  dbDelta($ld_tbl_sql);
154
  )" . $charset_collate . ";";
155
  dbDelta($pb_tbl_sql);
156
 
157
+ $debug_log_tbl_sql = "CREATE TABLE " . $debug_log_tbl_name . " (
158
+ id bigint(20) NOT NULL AUTO_INCREMENT,
159
+ level varchar(25) NOT NULL DEFAULT '',
160
+ message text NOT NULL DEFAULT '',
161
+ type varchar(25) NOT NULL DEFAULT '',
162
+ created datetime NOT NULL DEFAULT '1000-10-10 10:00:00',
163
+ PRIMARY KEY (id)
164
+ )" . $charset_collate . ";";
165
+ dbDelta($debug_log_tbl_sql);
166
+
167
  update_option("aiowpsec_db_version", AIO_WP_SECURITY_DB_VERSION);
168
  }
169
 
171
  global $aio_wp_security;
172
  //Create our folder in the "wp-content" directory
173
  $aiowps_dir = WP_CONTENT_DIR . '/' . AIO_WP_SECURITY_BACKUPS_DIR_NAME;
174
+ if (!is_dir($aiowps_dir) && is_writable(WP_CONTENT_DIR)) {
175
  mkdir($aiowps_dir, 0755, true);
176
  //Let's also create an empty index.html file in this folder
177
  $index_file = $aiowps_dir . '/index.html';
227
  * Handles both single and multi-site (NW activation) cases
228
  *
229
  * @global type $wpdb
230
+ * @param Boolean $networkwide Whether set cronjob networkwide or normal site.
231
+ * @return Void
232
  */
233
+ public static function set_cron_tasks_upon_activation($networkwide = false) {
234
+ require_once(__DIR__.'/wp-security-cronjob-handler.php');
235
+ // It is required because we are going to schedule a 15-minute cron event upon activation.
236
+ add_filter('cron_schedules', array('AIOWPSecurity_Cronjob_Handler', 'cron_schedules'));
237
+ if (is_multisite() && $networkwide) {
238
+ global $wpdb;
239
  // check if it is a network activation
240
  $blogids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs");
241
  foreach ($blogids as $blog_id) {
251
  }
252
 
253
  /**
254
+ * Helper function for scheduling aiowps cron events.
255
+ *
256
+ * @return Void
257
  */
258
  public static function schedule_cron_events() {
259
+ if (!wp_next_scheduled('aios_15_minutes_cron_event')) {
260
+ wp_schedule_event(time(), 'aios-every-15-minutes', 'aios_15_minutes_cron_event'); //schedule a 15 minutes cron event
261
+ }
262
  if (!wp_next_scheduled('aiowps_hourly_cron_event')) {
263
  wp_schedule_event(time(), 'hourly', 'aiowps_hourly_cron_event'); //schedule an hourly cron event
264
  }
classes/wp-security-notices.php CHANGED
@@ -2,9 +2,9 @@
2
 
3
  if (!defined('AIO_WP_SECURITY_PATH')) die('No direct access allowed');
4
 
5
- if (!class_exists('Updraft_Notices')) require_once(AIO_WP_SECURITY_PATH.'/classes/updraft-notices.php');
6
 
7
- class AIOWPSecurity_Notices extends Updraft_Notices {
8
 
9
  private $initialized = false;
10
 
@@ -22,6 +22,26 @@ class AIOWPSecurity_Notices extends Updraft_Notices {
22
  $parent_notice_content = parent::populate_notices_content();
23
 
24
  $child_notice_content = array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  'rate_plugin' => array(
26
  'text' => sprintf(htmlspecialchars(__('Hey - We noticed All In One WP Security & Firewall has kept your site safe for a while. If you like us, please consider leaving a positive review to spread the word. Or if you have any issues or questions please leave us a support message %s.', 'all-in-one-wp-security-and-firewall')), '<a href="https://wordpress.org/support/plugin/all-in-one-wp-security-and-firewall/" target="_blank">'.__('here', 'all-in-one-wp-security-and-firewall').'</a>').'<br>'.__('Thank you so much!', 'all-in-one-wp-security-and-firewall').'<br><br>- <b>'.__('Team All In One WP Security & Firewall', 'all-in-one-wp-security-and-firewall').'</b>',
27
  'image' => 'notices/aiowps-logo.png',
@@ -57,6 +77,78 @@ class AIOWPSecurity_Notices extends Updraft_Notices {
57
 
58
  return array_merge($parent_notice_content, $child_notice_content);
59
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
  /**
62
  * Call this method to setup the notices
@@ -170,6 +262,8 @@ class AIOWPSecurity_Notices extends Updraft_Notices {
170
  * Checks whether a notice is dismissed(returns true) or not(returns false).
171
  *
172
  * @param String $dismiss_time - dismiss time id for the notice
 
 
173
  */
174
  protected function check_notice_dismissed($dismiss_time) {
175
  $time_now = $this->get_time_now();
@@ -197,6 +291,8 @@ class AIOWPSecurity_Notices extends Updraft_Notices {
197
  $template_file = 'report.php';
198
  } elseif ('report-plain' == $position) {
199
  $template_file = 'report-plain.php';
 
 
200
  } else {
201
  $template_file = 'horizontal-notice.php';
202
  }
2
 
3
  if (!defined('AIO_WP_SECURITY_PATH')) die('No direct access allowed');
4
 
5
+ if (!class_exists('Updraft_Notices_1_2')) require_once(AIO_WP_SECURITY_PATH.'/vendor/team-updraft/common-libs/src/updraft-notices/updraft-notices.php');
6
 
7
+ class AIOWPSecurity_Notices extends Updraft_Notices_1_2 {
8
 
9
  private $initialized = false;
10
 
22
  $parent_notice_content = parent::populate_notices_content();
23
 
24
  $child_notice_content = array(
25
+ // Upgrade AIOS backup to UDP backup in the 5.0.0 version
26
+ 'automated-database-backup' => array(
27
+ 'title' => htmlspecialchars(__('Removed database backup feature from the All In One WP Security & Firewall plugin', 'all-in-one-wp-security-and-firewall')),
28
+ 'text' => '<p>' .
29
+ __('Beginning with version 5.0.0, AIOS has replaced the AIOS backup method with the superior UpdraftPlus method.', 'all-in-one-wp-security-and-firewall') . ' '.
30
+ __('It remains free and is fully supported by the UpdraftPlus team.', 'all-in-one-wp-security-and-firewall') .
31
+ '</p>' .
32
+ '<p>' .
33
+ __('You are seeing this notice because you have previously set up automated database backups in AIOS.', 'all-in-one-wp-security-and-firewall') . ' ' .
34
+ __('Would you like to set up scheduled backups with UpdraftPlus?', 'all-in-one-wp-security-and-firewall') .
35
+ '</p>',
36
+ 'button_link' => add_query_arg(array(
37
+ 'page' => 'aiowpsec_database',
38
+ 'tab' => 'tab2',
39
+ ), admin_url('admin.php')) . '#automated-scheduled-backups-heading',
40
+ 'button_meta' => __('Setup UpdraftPlus backup plugin', 'all-in-one-wp-security-and-firewall'),
41
+ 'dismiss_time' => 'dismiss_automated_database_backup_notice',
42
+ 'supported_positions' => array('automated-database-backup'),
43
+ 'validity_function' => 'should_show_automated_database_backup_notice',
44
+ ),
45
  'rate_plugin' => array(
46
  'text' => sprintf(htmlspecialchars(__('Hey - We noticed All In One WP Security & Firewall has kept your site safe for a while. If you like us, please consider leaving a positive review to spread the word. Or if you have any issues or questions please leave us a support message %s.', 'all-in-one-wp-security-and-firewall')), '<a href="https://wordpress.org/support/plugin/all-in-one-wp-security-and-firewall/" target="_blank">'.__('here', 'all-in-one-wp-security-and-firewall').'</a>').'<br>'.__('Thank you so much!', 'all-in-one-wp-security-and-firewall').'<br><br>- <b>'.__('Team All In One WP Security & Firewall', 'all-in-one-wp-security-and-firewall').'</b>',
47
  'image' => 'notices/aiowps-logo.png',
77
 
78
  return array_merge($parent_notice_content, $child_notice_content);
79
  }
80
+
81
+ /**
82
+ * Decides whether to show an automated database backup notice.
83
+ *
84
+ * @return Boolean True if an automated database notice should be shown, otherwise false.
85
+ */
86
+ protected function should_show_automated_database_backup_notice() {
87
+ if ($this->is_database_backup_admin_page_tab()) {
88
+ return false;
89
+ }
90
+
91
+ if ($this->is_updraftplus_plugin_active() && $this->is_schedule_database_backup_set_in_updraftplus()) {
92
+ return false;
93
+ }
94
+
95
+ global $aio_wp_security;
96
+ if ('1' == $aio_wp_security->configs->get_value('aiowps_enable_automated_backups')) {
97
+ return true;
98
+ }
99
+
100
+ return false;
101
+ }
102
+
103
+ /**
104
+ * Whether the current page is the AIOS database backup admin page
105
+ *
106
+ * @return Boolean True if the current page is the AIOS database backup admin page, otherwise false.
107
+ */
108
+ private function is_database_backup_admin_page_tab() {
109
+ return $this->is_database_security_admin_page() && $this->is_database_backup_tab();
110
+ }
111
+
112
+ /**
113
+ * Whether the current page is the database security admin page.
114
+ *
115
+ * @return Boolean True if the current page is the database security admin page, otherwise false.
116
+ */
117
+ private function is_database_security_admin_page() {
118
+ return ('admin.php' == $GLOBALS['pagenow'] && isset($_GET['page']) && 'aiowpsec_database' == $_GET['page']);
119
+ }
120
+
121
+ /**
122
+ * Whether the current tab is the database backup tab.
123
+ *
124
+ * @return Boolean True if the current tab is the database backup tab, otherwise false.
125
+ */
126
+ private function is_database_backup_tab() {
127
+ return (isset($_GET['tab']) && 'tab2' == $_GET['tab']);
128
+ }
129
+
130
+ /**
131
+ * Check whether the UpdraftPlus plugin is active or not.
132
+ *
133
+ * @return bool True if the UpdraftPlus plugin is active, otherwise false.
134
+ */
135
+ private function is_updraftplus_plugin_active() {
136
+ return class_exists('UpdraftPlus');
137
+ }
138
+
139
+ /**
140
+ * Check whether the database backup scheduled in the UpdraftPlus plugin.
141
+ *
142
+ * @return bool
143
+ */
144
+ private function is_schedule_database_backup_set_in_updraftplus() {
145
+ $updraft_interval_database_option_val = get_option('updraft_interval_database', '');
146
+ if (empty($updraft_interval_database_option_val) || 'manual' == $updraft_interval_database_option_val) {
147
+ return false;
148
+ }
149
+
150
+ return true;
151
+ }
152
 
153
  /**
154
  * Call this method to setup the notices
262
  * Checks whether a notice is dismissed(returns true) or not(returns false).
263
  *
264
  * @param String $dismiss_time - dismiss time id for the notice
265
+ *
266
+ * @return boolean
267
  */
268
  protected function check_notice_dismissed($dismiss_time) {
269
  $time_now = $this->get_time_now();
291
  $template_file = 'report.php';
292
  } elseif ('report-plain' == $position) {
293
  $template_file = 'report-plain.php';
294
+ } elseif ('automated-database-backup' == $position) {
295
+ $template_file = 'automated-database-backup-notice.php';
296
  } else {
297
  $template_file = 'horizontal-notice.php';
298
  }
classes/wp-security-process-renamed-login-page.php CHANGED
@@ -106,6 +106,11 @@ class AIOWPSecurity_Process_Renamed_Login_Page {
106
  return $url;
107
  }
108
 
 
 
 
 
 
109
  public static function renamed_login_init_tasks() {
110
  global $aio_wp_security;
111
 
@@ -183,32 +188,38 @@ class AIOWPSecurity_Process_Renamed_Login_Page {
183
  }
184
  }
185
 
186
- $parsed_url = parse_url($_SERVER['REQUEST_URI']);
 
 
 
 
 
 
187
 
188
  $login_slug = $aio_wp_security->configs->get_value('aiowps_login_page_slug');
189
- $home_url_with_slug = home_url($login_slug, 'relative');
190
 
191
  /*
192
  * Compatibility fix for WPML plugin
193
  */
194
- if (function_exists('wpml_object_id') && strpos($home_url_with_slug, $login_slug)) {
195
- $home_url_with_slug = home_url($login_slug);
196
  function qtranxf_init_language() {}// phpcs:ignore Squiz.WhiteSpace.ScopeClosingBrace.ContentBefore,PEAR.WhiteSpace.ScopeClosingBrace.Line,Squiz.PHP.InnerFunctions.NotAllowed
197
  }
198
 
199
  /*
200
  * *** Compatibility fix for qTranslate-X plugin ***
201
  * qTranslate-X plugin modifies the result for the following command by adding the protocol and host to the url path:
202
- * home_url($login_slug, 'relative');
203
  * Therefore we will remove the protocol and host for the following cases:
204
  * qTranslate-X is active AND the URL being accessed contains the secret slug
205
  */
206
- if (function_exists('qtranxf_init_language') && strpos($home_url_with_slug, $login_slug)) {
207
- $parsed_home_url_with_slug = parse_url($home_url_with_slug);
208
- $home_url_with_slug = $parsed_home_url_with_slug['path']; //this will return just the path minus the protocol and host
209
  }
210
 
211
- if (untrailingslashit($parsed_url['path']) === $home_url_with_slug || (!get_option('permalink_structure') && isset($_GET[$login_slug]))) {
212
  if (empty($action) && is_user_logged_in()) {
213
  //if user is already logged in but tries to access the renamed login page, send them to the dashboard
214
  // or to requested redirect-page, filterd in 'login_redirect'.
@@ -243,9 +254,9 @@ class AIOWPSecurity_Process_Renamed_Login_Page {
243
  global $aio_wp_security;
244
  $login_slug = $aio_wp_security->configs->get_value('aiowps_login_page_slug');
245
  if (get_option('permalink_structure')) {
246
- return trailingslashit(trailingslashit(home_url()) . $login_slug);
247
  } else {
248
- return trailingslashit(home_url()) . '?' . $login_slug;
249
  }
250
  }
251
 
106
  return $url;
107
  }
108
 
109
+ /**
110
+ * Login page renamed related tasks, do not allow access if not logged with rename login page.
111
+ *
112
+ * @return void
113
+ */
114
  public static function renamed_login_init_tasks() {
115
  global $aio_wp_security;
116
 
188
  }
189
  }
190
 
191
+ $parsed_url_path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
192
+
193
+ // for `wp plugin list` it will be empty so showing Not available isntead plugin list.
194
+ if (empty($parsed_url_path) && !defined('WP_CLI')) {
195
+ do_action('aiowps_before_wp_die_renamed_login');
196
+ wp_die(__('Not available.', 'all-in-one-wp-security-and-firewall'), 403);
197
+ }
198
 
199
  $login_slug = $aio_wp_security->configs->get_value('aiowps_login_page_slug');
200
+ $site_url_with_slug = site_url($login_slug, 'relative');
201
 
202
  /*
203
  * Compatibility fix for WPML plugin
204
  */
205
+ if (function_exists('wpml_object_id') && strpos($site_url_with_slug, $login_slug)) {
206
+ $site_url_with_slug = site_url($login_slug);
207
  function qtranxf_init_language() {}// phpcs:ignore Squiz.WhiteSpace.ScopeClosingBrace.ContentBefore,PEAR.WhiteSpace.ScopeClosingBrace.Line,Squiz.PHP.InnerFunctions.NotAllowed
208
  }
209
 
210
  /*
211
  * *** Compatibility fix for qTranslate-X plugin ***
212
  * qTranslate-X plugin modifies the result for the following command by adding the protocol and host to the url path:
213
+ * site_url($login_slug, 'relative');
214
  * Therefore we will remove the protocol and host for the following cases:
215
  * qTranslate-X is active AND the URL being accessed contains the secret slug
216
  */
217
+ if (function_exists('qtranxf_init_language') && strpos($site_url_with_slug, $login_slug)) {
218
+ $parsed_site_url_with_slug = parse_url($site_url_with_slug);
219
+ $site_url_with_slug = $parsed_site_url_with_slug['path']; //this will return just the path minus the protocol and host
220
  }
221
 
222
+ if (untrailingslashit($parsed_url_path) === $site_url_with_slug || (!get_option('permalink_structure') && isset($_GET[$login_slug]))) {
223
  if (empty($action) && is_user_logged_in()) {
224
  //if user is already logged in but tries to access the renamed login page, send them to the dashboard
225
  // or to requested redirect-page, filterd in 'login_redirect'.
254
  global $aio_wp_security;
255
  $login_slug = $aio_wp_security->configs->get_value('aiowps_login_page_slug');
256
  if (get_option('permalink_structure')) {
257
+ return trailingslashit(trailingslashit(site_url()) . $login_slug);
258
  } else {
259
+ return trailingslashit(site_url()) . '?' . $login_slug;
260
  }
261
  }
262
 
classes/wp-security-two-factor-login.php ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if (!defined('ABSPATH')) die('No direct access allowed');
3
+
4
+ if (!class_exists('Simba_Two_Factor_Authentication')) require AIO_WP_SECURITY_PATH.'/includes/simba-tfa/simba-tfa.php';
5
+
6
+ /**
7
+ * This parent-child relationship enables the two to be split without affecting backwards compatibility for developers making direct calls
8
+ *
9
+ * This class is for the plugin encapsulation.
10
+ */
11
+ class AIO_WP_Security_Simba_Two_Factor_Authentication_Plugin extends Simba_Two_Factor_Authentication {
12
+
13
+
14
+ /**
15
+ * Simba_Two_Factor_Authentication_Plugin Constructor
16
+ *
17
+ * @uses __FILE__
18
+ * @return Void
19
+ */
20
+ public function __construct() {
21
+
22
+ if (!function_exists('mcrypt_get_iv_size') && !function_exists('openssl_cipher_iv_length')) {
23
+ add_action('all_admin_notices', array($this, 'admin_notice_missing_mcrypt_and_openssl'));
24
+ return;
25
+ }
26
+ add_filter('aiowpsecurity_setting_tabs', array($this, 'add_two_factor_setting_tab'));
27
+ add_action('admin_menu', array($this, 'menu_entry_for_user'), 30);
28
+ $this->version = AIO_WP_SECURITY_VERSION;
29
+ $this->set_user_settings_page_slug(AIOWPSEC_TWO_FACTOR_AUTH_MENU_SLUG);
30
+ $settings_page_heading = __('Two Factor Authentication - Admin Settings', 'all-in-one-wp-security-and-firewall');
31
+ $this->set_settings_page_heading($settings_page_heading);
32
+ $this->set_plugin_translate_url('https://translate.wordpress.org/projects/wp-plugins/all-in-one-wp-security-and-firewall/');
33
+ $this->set_site_wide_administration_url(admin_url('admin.php?page=aiowpsec_settings&tab=two-factor-authentication'));
34
+ $this->set_premium_version_url('https://aiowpsecurity.com');
35
+ $this->set_faq_url('https://wordpress.org/plugins/all-in-one-wp-security-and-firewall/#faq');
36
+ parent::__construct();
37
+ }
38
+
39
+ /**
40
+ * Runs upon the WP actions admin_menu and network_admin_menu
41
+ */
42
+ public function menu_entry_for_user() {
43
+
44
+ $this->get_totp_controller()->potentially_port_private_keys();
45
+
46
+ global $current_user;
47
+ if ($this->is_activated_for_user($current_user->ID)) {
48
+ if (!current_user_can(AIOWPSEC_MANAGEMENT_PERMISSION)) {
49
+ $menu_icon_url = AIO_WP_SECURITY_URL . '/images/plugin-icon.png';
50
+ add_menu_page(__('WP Security', 'all-in-one-wp-security-and-firewall'), __('WP Security', 'all-in-one-wp-security-and-firewall'), AIOWPSEC_MANAGEMENT_PERMISSION, AIOWPSEC_MAIN_MENU_SLUG, '', $menu_icon_url);
51
+ }
52
+ add_submenu_page(AIOWPSEC_MAIN_MENU_SLUG, __('Two Factor Auth', 'all-in-one-wp-security-and-firewall'), __('Two Factor Auth', 'all-in-one-wp-security-and-firewall'), 'read', AIOWPSEC_TWO_FACTOR_AUTH_MENU_SLUG, array($this, 'show_dashboard_user_settings_page'));
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Builds Two Factor Authentication tab
58
+ *
59
+ * @param array $tabs array that contain tab name and call back function
60
+ * @return array Returns all tabs with callback function name
61
+ */
62
+ public function add_two_factor_setting_tab($tabs = array()) {
63
+ if (!current_user_can(AIOWPSEC_MANAGEMENT_PERMISSION)) return;
64
+ $tabs['two-factor-authentication'] = array(
65
+ 'title' => __('Two Factor Authentication', 'all-in-one-wp-security-and-firewall-premium'),
66
+ 'render_callback' => array($this, 'render_two_factor_authentication'),
67
+ 'display_condition_callback' => 'is_main_site',
68
+ );
69
+ return $tabs;
70
+ }
71
+
72
+ /**
73
+ * Display the Two Factor Authentication tab & handle the operations
74
+ */
75
+ public function render_two_factor_authentication() {
76
+ $this->get_totp_controller()->potentially_port_private_keys();
77
+ $this->show_admin_settings_page();
78
+ }
79
+
80
+ /**
81
+ * Include the admin settings page code.
82
+ */
83
+ public function show_admin_settings_page() {
84
+ $totp_controller = $this->get_totp_controller();
85
+ $totp_controller->setUserHMACTypes();
86
+ if (!is_admin() || !current_user_can(AIOWPSEC_MANAGEMENT_PERMISSION)) return;
87
+ $this->include_template('admin-settings.php', array(
88
+ 'totp_controller' => $totp_controller,
89
+ 'settings_page_heading' => $this->get_settings_page_heading(),
90
+ 'admin_settings_links' => array(),
91
+ ));
92
+ }
93
+
94
+ /**
95
+ * Runs conditionally on the WP action all_admin_notices.
96
+ */
97
+ public function admin_notice_missing_mcrypt_and_openssl() {
98
+ $this->show_admin_warning('<strong>'.__('PHP OpenSSL or mcrypt module required', 'all-in-one-wp-security-and-firewall').'</strong><br> '.__('The All In One WP Security plugin\'s Two Factor Authentication module requires either the PHP openssl (preferred) or mcrypt module to be installed. Please ask your web hosting company to install one of them.', 'all-in-one-wp-security-and-firewall'), 'error');
99
+ }
100
+ }
101
+
102
+ if (false === AIOWPSecurity_Utility::is_incopatible_tfa_premium_version_active() && false === AIOWPSecurity_Utility::is_tfa_or_self_plugin_activating()) {
103
+ $GLOBALS['simba_two_factor_authentication'] = new AIO_WP_Security_Simba_Two_Factor_Authentication_Plugin();
104
+ }
classes/wp-security-uninstallation-tasks.php ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if (!defined('ABSPATH')) {
3
+ exit;//Exit if accessed directly
4
+ }
5
+
6
+ require_once(AIO_WP_SECURITY_PATH.'/classes/wp-security-base-tasks.php');
7
+
8
+ class AIOWPSecurity_Uninstallation_Tasks extends AIOWPSecurity_Base_Tasks {
9
+ /**
10
+ * Runs various uninstallation tasks
11
+ * Handles single and multi-site (NW activation) cases
12
+ *
13
+ * @global type $wpdb
14
+ * @global type $aio_wp_security
15
+ */
16
+ public static function run() {
17
+ if (is_multisite()) {
18
+ delete_site_transient('users_online');
19
+ } else {
20
+ delete_transient('users_online');
21
+ }
22
+ parent::run();
23
+ }
24
+
25
+ /**
26
+ * Run uninstallation task for a single site.
27
+ *
28
+ * @return void
29
+ */
30
+ protected static function run_for_a_site() {
31
+ self::clear_cron_events();
32
+ // Drop db tables and configs
33
+ self::drop_database_tables_and_configs();
34
+ }
35
+
36
+ /**
37
+ * Function to drop database tables and remove configuration settings
38
+ *
39
+ * @return void
40
+ */
41
+ public static function drop_database_tables_and_configs() {
42
+
43
+ global $wpdb, $aio_wp_security;
44
+
45
+ $database_tables = array(
46
+ $wpdb->prefix.'aiowps_login_lockdown',
47
+ $wpdb->prefix.'aiowps_failed_logins',
48
+ $wpdb->prefix.'aiowps_login_activity',
49
+ $wpdb->prefix.'aiowps_global_meta',
50
+ $wpdb->prefix.'aiowps_events',
51
+ $wpdb->prefix.'aiowps_permanent_block',
52
+ $wpdb->prefix.'aiowps_debug_log',
53
+ );
54
+
55
+ // check and drop database tables
56
+ if ('1' == $aio_wp_security->configs->get_value('aiowps_on_uninstall_delete_db_tables')) {
57
+ foreach ($database_tables as $table_name) {
58
+ $wpdb->query("DROP TABLE IF EXISTS `$table_name`");
59
+ }
60
+ }
61
+
62
+ // check and delete configurations
63
+ if ('1' == $aio_wp_security->configs->get_value('aiowps_on_uninstall_delete_configs')) {
64
+
65
+ delete_option('aio_wp_security_configs');
66
+ delete_option('aiowps_temp_configs');
67
+ delete_option('aiowpsec_db_version');
68
+
69
+ if (is_main_site()) {
70
+ // Remove all settings from .htaccess file that were added by this plugin
71
+ AIOWPSecurity_Utility_Htaccess::write_to_htaccess();
72
+ }
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Helper function which clears aiowps cron events
78
+ */
79
+ private static function clear_cron_events() {
80
+ wp_clear_scheduled_hook('aiowps_hourly_cron_event');
81
+ wp_clear_scheduled_hook('aiowps_daily_cron_event');
82
+ }
83
+ }
classes/wp-security-user-login.php CHANGED
@@ -4,7 +4,7 @@ if (!defined('ABSPATH')) {
4
  }
5
 
6
  class AIOWPSecurity_User_Login {
7
-
8
  public $key_login_msg;// This will store a URI query string key for passing messages to the login form
9
 
10
  public function __construct() {
@@ -56,16 +56,22 @@ class AIOWPSecurity_User_Login {
56
  }
57
 
58
  /**
59
- * Display admin to disable lockdown message
 
 
60
  */
61
  public function disable_login_lockdown_by_const_notice() {
62
 
 
 
 
 
63
  echo '<div class="notice notice-error">
64
  <p>'.
65
  __('You have disabled login lockdown by defining the AIOWPS_DISABLE_LOGIN_LOCKDOWN constant value as true, and the login lockdown setting has enabled it.', 'all-in-one-wp-security-and-firewall') . '&nbsp;' .
66
  /* translators: 1: Locked IP Addresses admin page link */
67
  sprintf(__('Delete your login lockdown IP from %s and define the AIOWPS_DISABLE_LOGIN_LOCKDOWN constant value as false.', 'all-in-one-wp-security-and-firewall'),
68
- '<a href="'.admin_url('admin.php?page=aiowpsec&tab=tab3').'">' . __('Locked IP Addresses', 'all-in-one-wp-security-and-firewall') . '</a>'
69
  ).
70
  '</p>
71
  </div>';
@@ -135,6 +141,7 @@ class AIOWPSecurity_User_Login {
135
  }
136
  return $user;
137
  }
 
138
  /**
139
  * Check, whether $user needs to be manually approved by site admin yet.
140
  *
@@ -158,6 +165,7 @@ class AIOWPSecurity_User_Login {
158
  }
159
  return $user;
160
  }
 
161
  /**
162
  * Handle post authentication steps (in case of failed login):
163
  * - increment number of failed logins for $username
@@ -199,19 +207,32 @@ class AIOWPSecurity_User_Login {
199
  // Too many failed logins from user's IP?
200
  $login_attempts_permitted = absint($aio_wp_security->configs->get_value('aiowps_max_login_attempts'));
201
  $too_many_failed_logins = $login_attempts_permitted <= $this->get_login_fail_count();
 
202
  // Is an invalid username or email the reason for login error?
203
  $invalid_username = ($user->get_error_code() === 'invalid_username' || $user->get_error_code() == 'invalid_email');
204
  // Should an invalid username be immediately locked?
205
  $invalid_username_lockdown = $aio_wp_security->configs->get_value('aiowps_enable_invalid_username_lockdown') == '1';
206
  $lock_invalid_username = $invalid_username && $invalid_username_lockdown;
 
207
  // Should an invalid username be blocked as per blacklist?
208
  $instant_lockout_users_list = $aio_wp_security->configs->get_value('aiowps_instantly_lockout_specific_usernames');
209
  if (!is_array($instant_lockout_users_list)) {
210
  $instant_lockout_users_list = array();
211
  }
212
  $username_blacklisted = $invalid_username && in_array($username, $instant_lockout_users_list);
213
- if ($too_many_failed_logins || $lock_invalid_username || $username_blacklisted) {
214
- $this->lock_the_user($username, 'login_fail');
 
 
 
 
 
 
 
 
 
 
 
215
  }
216
  }
217
  }
@@ -232,33 +253,61 @@ class AIOWPSecurity_User_Login {
232
  $login_lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
233
  $ip = AIOWPSecurity_Utility_IP::get_user_ip_address(); //Get the IP address of user
234
  if (empty($ip)) return false;
235
- $now = current_time('mysql');
236
- $locked_user = $wpdb->get_row("SELECT * FROM $login_lockdown_table " . "WHERE release_date > '".$now."' AND " . "failed_login_ip = '" . esc_sql($ip) . "'", ARRAY_A);
237
  return $locked_user;
238
  }
239
  /**
240
  * This function queries the aiowps_failed_logins table and returns the number of failures for current IP range within allowed failure period
241
  */
242
  public function get_login_fail_count() {
 
243
  global $wpdb, $aio_wp_security;
 
244
  $failed_logins_table = AIOWPSEC_TBL_FAILED_LOGINS;
245
  $login_retry_interval = $aio_wp_security->configs->get_value('aiowps_retry_time_period');
246
  $now = current_time('mysql', true);
247
  $ip = AIOWPSecurity_Utility_IP::get_user_ip_address(); //Get the IP address of user
 
248
  if (empty($ip)) return false;
 
249
  $login_failures = $wpdb->get_var("SELECT COUNT(ID) FROM $failed_logins_table " . "WHERE failed_login_date + INTERVAL " . esc_sql($login_retry_interval) . " MINUTE > '" . esc_sql($now) . "' AND " . "login_attempt_ip = '" . esc_sql($ip) . "'");
250
  return $login_failures;
251
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  /**
253
  * Adds an entry to the `aiowps_login_lockdown` table.
254
  *
255
- * @param string $username User's username or email
256
  * @param string $lock_reason
 
257
  */
258
- public function lock_the_user($username, $lock_reason = 'login_fail') {
259
  global $wpdb, $aio_wp_security;
260
  $login_lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
261
- $lock_minutes = $aio_wp_security->configs->get_value('aiowps_lockout_time_length');
262
  $ip = AIOWPSecurity_Utility_IP::get_user_ip_address(); //Get the IP address of user
263
  if (empty($ip)) return;
264
  $ip_range = AIOWPSecurity_Utility_IP::get_sanitized_ip_range($ip); //Get the IP range of the current user
@@ -273,21 +322,24 @@ class AIOWPSecurity_User_Login {
273
  }
274
  $ip_range_str = esc_sql($ip_range).'.*';// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
275
 
276
- $lock_time = current_time('mysql');
277
  $date = new DateTime($lock_time);
278
  $add_interval = 'PT'.absint($lock_minutes).'M';
279
  $date->add(new DateInterval($add_interval));
280
  $release_time = $date->format('Y-m-d H:i:s');
281
-
282
- $data = array('user_id' => $user_id, 'user_login' => $username, 'lockdown_date' => $lock_time, 'release_date' => $release_time, 'failed_login_IP' => $ip, 'lock_reason' => $lock_reason);
283
- $format = array('%d', '%s', '%s', '%s', '%s', '%s');
 
 
 
 
284
  $result = $wpdb->insert($login_lockdown_table, $data, $format);
285
 
286
  if (false === $result) {
287
  $aio_wp_security->debug_logger->log_debug("Error inserting record into ".$login_lockdown_table, 4);//Log the highly unlikely event of DB error
288
  } else {
289
  do_action('aiowps_lockdown_event', $ip_range, $username);
290
- $this->send_ip_lock_notification_email($username, $ip_range, $ip);
291
  $aio_wp_security->debug_logger->log_debug("The following IP address range has been locked out for exceeding the maximum login attempts: ".$ip_range, 2);//Log the lockdown event
292
  }
293
  }
@@ -320,32 +372,46 @@ class AIOWPSecurity_User_Login {
320
  }
321
 
322
  /**
323
- * Send IP Lock notification
324
  *
325
- * @param string $username User's username or email
326
- * @param int $ip_range
327
- * @param int $ip
328
- * @return void
329
  */
330
- public function send_ip_lock_notification_email($username, $ip_range, $ip) {
331
  global $aio_wp_security;
332
- $email_notification_enabled = $aio_wp_security->configs->get_value('aiowps_enable_email_notify');
333
- if (1 == $email_notification_enabled) {
334
- $to_email_address = $aio_wp_security->configs->get_value('aiowps_email_address');
335
- $subject = '['.get_option('home').'] '. __('Site Lockout Notification', 'all-in-one-wp-security-and-firewall');
336
- $email_msg = __('A lockdown event has occurred due to too many failed login attempts or invalid username:', 'all-in-one-wp-security-and-firewall')."\n";
337
- $email_msg .= __('Username:', 'all-in-one-wp-security-and-firewall') . ' ' . $username . "\n";
338
- $email_msg .= __('IP Address:', 'all-in-one-wp-security-and-firewall') . ' ' . $ip . "\n\n";
339
- $email_msg .= __('IP Range:', 'all-in-one-wp-security-and-firewall') . ' ' . $ip_range . '.*' . "\n\n";
340
- $email_msg .= __("Log into your site's WordPress administration panel to see the duration of the lockout or to unlock the user.", 'all-in-one-wp-security-and-firewall') . "\n";
341
- $site_title = get_bloginfo('name');
342
- $from_name = empty($site_title) ? 'WordPress' : $site_title;
343
- $email_header = 'From: '.$from_name.' <'.get_bloginfo('admin_email').'>' . "\r\n\\";
344
- $sendMail = wp_mail($to_email_address, $subject, $email_msg, $email_header);
345
- if (false === $sendMail) {
346
- $aio_wp_security->debug_logger->log_debug("Lockout notification email failed to send to ".$to_email_address." for IP ".$ip, 4);
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  }
348
  }
 
349
  }
350
 
351
  /**
@@ -362,13 +428,13 @@ class AIOWPSecurity_User_Login {
362
  $unlock_link = '';
363
  $lockdown_table_name = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
364
  $secret_rand_key = (md5(uniqid(rand(), true)));
365
- $unlock_request_date_time = date("Y-m-d H:i:s");
366
  $res = $wpdb->query($wpdb->prepare("UPDATE $lockdown_table_name SET unlock_key = %s WHERE release_date > %s AND failed_login_ip LIKE %s", $secret_rand_key, $unlock_request_date_time, "%" . esc_sql($ip_range) . "%"));
367
  if (null == $res) {
368
  $aio_wp_security->debug_logger->log_debug("No locked user found with IP range ".$ip_range, 4);
369
  return false;
370
  } else {
371
- //Check if unlock requestor submitted from a woocommerce account login page
372
  if (isset($_POST['aiowps-woo-login'])) {
373
  $date_time = current_time('mysql');
374
  $data = array('date_time' => $date_time, 'meta_key1' => 'woo_unlock_request_key', 'meta_value1' => $secret_rand_key);
@@ -395,7 +461,7 @@ class AIOWPSecurity_User_Login {
395
  public static function process_unlock_request($unlock_key) {
396
  global $wpdb, $aio_wp_security;
397
  $lockdown_table_name = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
398
- $unlock_request_date_time = date("Y-m-d H:i:s");
399
  $unlock_command = $wpdb->prepare("UPDATE ".$lockdown_table_name." SET release_date = %s WHERE unlock_key = %s", $unlock_request_date_time, $unlock_key);
400
  $result = $wpdb->query($unlock_command);
401
  if (false === $result) {
@@ -412,9 +478,9 @@ class AIOWPSecurity_User_Login {
412
  }
413
  if ($aio_wp_security->configs->get_value('aiowps_enable_rename_login_page')=='1') {
414
  if (get_option('permalink_structure')) {
415
- $home_url = trailingslashit(home_url());
416
  } else {
417
- $home_url = trailingslashit(home_url()) . '?';
418
  }
419
  if ($woo_unlock) {
420
  $login_url = wc_get_page_permalink('myaccount'); //redirect to woo login page if applicable
@@ -424,7 +490,7 @@ class AIOWPSecurity_User_Login {
424
  $aio_wp_security->debug_logger->log_debug("process_unlock_request(): Error deleting row from AIOWPSEC_TBL_GLOBAL_META_DATA for meta_key1=woo_unlock_request_key and meta_value1=".$unlock_key, 4);
425
  }
426
  } else {
427
- $login_url = $home_url.$aio_wp_security->configs->get_value('aiowps_login_page_slug');
428
  }
429
 
430
  AIOWPSecurity_Utility::redirect_to_url($login_url);
@@ -467,8 +533,11 @@ class AIOWPSecurity_User_Login {
467
  if (is_user_logged_in()) {
468
  $current_user = wp_get_current_user();
469
  $user_id = $current_user->ID;
470
- $current_time = current_time('mysql');
471
- $login_time = $this->get_wp_user_last_login_time($user_id);
 
 
 
472
  $diff = strtotime($current_time) - strtotime($login_time);
473
  $logout_time_interval_value = $aio_wp_security->configs->get_value('aiowps_logout_time_period');
474
  $logout_time_interval_val_seconds = $logout_time_interval_value * 60;
@@ -479,7 +548,7 @@ class AIOWPSecurity_User_Login {
479
  $curr_page_url = AIOWPSecurity_Utility::get_current_page_url();
480
  $after_logout_payload = array('redirect_to' => $curr_page_url, 'msg' => $this->key_login_msg.'=session_expired');
481
  //Save some of the logout redirect data to a transient
482
- AIOWPSecurity_Utility::is_multisite_install() ? set_site_transient('aiowps_logout_payload', $after_logout_payload, 30 * 60) : set_transient('aiowps_logout_payload', $after_logout_payload, 30 * 60);
483
  $logout_url = AIOWPSEC_WP_URL.'?aiowpsec_do_log_out=1';
484
  $logout_url = AIOWPSecurity_Utility::add_query_data_to_url($logout_url, 'al_additional_data', '1');
485
  $logout_url_with_nonce = html_entity_decode(wp_nonce_url($logout_url, 'aio_logout'));
@@ -489,10 +558,17 @@ class AIOWPSecurity_User_Login {
489
  }
490
  }
491
 
492
- public function get_wp_user_last_login_time($user_id) {
493
- $last_login = get_user_meta($user_id, 'last_login_time', true);
 
 
 
 
 
 
494
  return $last_login;
495
  }
 
496
  public static function wp_login_action_handler($user_login, $user = '') {
497
  global $wpdb, $aio_wp_security;
498
  $login_activity_table = AIOWPSEC_TBL_USER_LOGIN_ACTIVITY;
@@ -505,8 +581,8 @@ class AIOWPSecurity_User_Login {
505
  return;
506
  }
507
  }
508
- $login_date_time = current_time('mysql');
509
- update_user_meta($user->ID, 'last_login_time', $login_date_time); //store last login time in meta table
510
  $curr_ip_address = AIOWPSecurity_Utility_IP::get_user_ip_address();
511
  $data = array('user_id' => $user->ID, 'user_login' => $user_login, 'login_date' => $login_date_time, 'login_ip' => $curr_ip_address);
512
  $format = array('%d', '%s', '%s', '%s');
@@ -531,7 +607,7 @@ class AIOWPSecurity_User_Login {
531
  //Clean up transients table
532
  $this->cleanup_users_online_transient($user_id, $ip_addr);
533
  $login_activity_table = AIOWPSEC_TBL_USER_LOGIN_ACTIVITY;
534
- $logout_date_time = current_time('mysql');
535
  $data = array('logout_date' => $logout_date_time);
536
  $where = array('user_id' => $user_id, 'login_ip' => $ip_addr, 'logout_date' => '1000-10-10 10:00:00');
537
  $result = $wpdb->update($login_activity_table, $data, $where);
@@ -547,7 +623,7 @@ class AIOWPSecurity_User_Login {
547
  */
548
  public function update_users_online_transient() {
549
  if (is_user_logged_in()) {
550
- $is_multi_site = AIOWPSecurity_Utility::is_multisite_install();
551
  $current_user_ip = AIOWPSecurity_Utility_IP::get_user_ip_address();
552
  // get the logged in users list from transients entry
553
  $logged_in_users = ($is_multi_site ? get_site_transient('users_online') : get_transient('users_online'));
@@ -595,10 +671,10 @@ class AIOWPSecurity_User_Login {
595
  if ($update_existing) {
596
  // Update transient if the last activity was over 15 min ago for this user
597
  $logged_in_users[$item_index] = $current_user_info;
598
- AIOWPSecurity_Utility::is_multisite_install() ? set_site_transient('users_online', $logged_in_users, 30 * 60) : set_transient('users_online', $logged_in_users, 30 * 60);
599
  } else {
600
  $logged_in_users[] = $current_user_info;
601
- AIOWPSecurity_Utility::is_multisite_install() ? set_site_transient('users_online', $logged_in_users, 30 * 60) : set_transient('users_online', $logged_in_users, 30 * 60);
602
  }
603
  }
604
  }
@@ -612,7 +688,7 @@ class AIOWPSecurity_User_Login {
612
  * @return void
613
  */
614
  public function cleanup_users_online_transient($user_id, $ip_addr) {
615
- $is_multi_site = AIOWPSecurity_Utility::is_multisite_install();
616
  if ($is_multi_site) {
617
  $current_blog_id = get_current_blog_id();
618
  $logged_in_users = AIOWPSecurity_User_Login::get_subsite_logged_in_users($current_blog_id);
@@ -709,7 +785,7 @@ class AIOWPSecurity_User_Login {
709
  if (empty($blog_id)) return false;
710
 
711
  $subsite_logged_in_users = array();
712
- if (AIOWPSecurity_Utility::is_multisite_install()) {
713
  // this contains all logged in users sitewide across subsites
714
  $users_online = get_site_transient('users_online');
715
  if (empty($users_online)) {
@@ -725,4 +801,57 @@ class AIOWPSecurity_User_Login {
725
  return $subsite_logged_in_users;
726
  }
727
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
728
  }
4
  }
5
 
6
  class AIOWPSecurity_User_Login {
7
+
8
  public $key_login_msg;// This will store a URI query string key for passing messages to the login form
9
 
10
  public function __construct() {
56
  }
57
 
58
  /**
59
+ * Displays admin to disable lockdown message.
60
+ *
61
+ * @return Void
62
  */
63
  public function disable_login_lockdown_by_const_notice() {
64
 
65
+ if (!AIOWPSecurity_Utility::has_manage_cap()) {
66
+ return;
67
+ }
68
+
69
  echo '<div class="notice notice-error">
70
  <p>'.
71
  __('You have disabled login lockdown by defining the AIOWPS_DISABLE_LOGIN_LOCKDOWN constant value as true, and the login lockdown setting has enabled it.', 'all-in-one-wp-security-and-firewall') . '&nbsp;' .
72
  /* translators: 1: Locked IP Addresses admin page link */
73
  sprintf(__('Delete your login lockdown IP from %s and define the AIOWPS_DISABLE_LOGIN_LOCKDOWN constant value as false.', 'all-in-one-wp-security-and-firewall'),
74
+ '<a href="'.admin_url('admin.php?page=aiowpsec&tab=tab2').'">' . __('Locked IP Addresses', 'all-in-one-wp-security-and-firewall') . '</a>'
75
  ).
76
  '</p>
77
  </div>';
141
  }
142
  return $user;
143
  }
144
+
145
  /**
146
  * Check, whether $user needs to be manually approved by site admin yet.
147
  *
165
  }
166
  return $user;
167
  }
168
+
169
  /**
170
  * Handle post authentication steps (in case of failed login):
171
  * - increment number of failed logins for $username
207
  // Too many failed logins from user's IP?
208
  $login_attempts_permitted = absint($aio_wp_security->configs->get_value('aiowps_max_login_attempts'));
209
  $too_many_failed_logins = $login_attempts_permitted <= $this->get_login_fail_count();
210
+
211
  // Is an invalid username or email the reason for login error?
212
  $invalid_username = ($user->get_error_code() === 'invalid_username' || $user->get_error_code() == 'invalid_email');
213
  // Should an invalid username be immediately locked?
214
  $invalid_username_lockdown = $aio_wp_security->configs->get_value('aiowps_enable_invalid_username_lockdown') == '1';
215
  $lock_invalid_username = $invalid_username && $invalid_username_lockdown;
216
+
217
  // Should an invalid username be blocked as per blacklist?
218
  $instant_lockout_users_list = $aio_wp_security->configs->get_value('aiowps_instantly_lockout_specific_usernames');
219
  if (!is_array($instant_lockout_users_list)) {
220
  $instant_lockout_users_list = array();
221
  }
222
  $username_blacklisted = $invalid_username && in_array($username, $instant_lockout_users_list);
223
+
224
+ $lock_reasons = array();
225
+ if ($too_many_failed_logins) {
226
+ $lock_reasons[] = 'too_many_failed_logins';
227
+ }
228
+ if ($lock_invalid_username) {
229
+ $lock_reasons[] = 'invalid_username';
230
+ }
231
+ if ($username_blacklisted) {
232
+ $lock_reasons[] = 'username_blacklisted';
233
+ }
234
+ if ($lock_reasons) {
235
+ $this->lock_the_user($username, implode(',', $lock_reasons));
236
  }
237
  }
238
  }
253
  $login_lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
254
  $ip = AIOWPSecurity_Utility_IP::get_user_ip_address(); //Get the IP address of user
255
  if (empty($ip)) return false;
256
+ $now = current_time('mysql', true);
257
+ $locked_user = $wpdb->get_row($wpdb->prepare("SELECT * FROM $login_lockdown_table WHERE `release_date` > %s AND `failed_login_ip` = %s", $now, $ip), ARRAY_A);
258
  return $locked_user;
259
  }
260
  /**
261
  * This function queries the aiowps_failed_logins table and returns the number of failures for current IP range within allowed failure period
262
  */
263
  public function get_login_fail_count() {
264
+
265
  global $wpdb, $aio_wp_security;
266
+
267
  $failed_logins_table = AIOWPSEC_TBL_FAILED_LOGINS;
268
  $login_retry_interval = $aio_wp_security->configs->get_value('aiowps_retry_time_period');
269
  $now = current_time('mysql', true);
270
  $ip = AIOWPSecurity_Utility_IP::get_user_ip_address(); //Get the IP address of user
271
+
272
  if (empty($ip)) return false;
273
+
274
  $login_failures = $wpdb->get_var("SELECT COUNT(ID) FROM $failed_logins_table " . "WHERE failed_login_date + INTERVAL " . esc_sql($login_retry_interval) . " MINUTE > '" . esc_sql($now) . "' AND " . "login_attempt_ip = '" . esc_sql($ip) . "'");
275
  return $login_failures;
276
  }
277
+
278
+ /**
279
+ * Get lockout time dynamically multiplied with default lockout time
280
+ *
281
+ * @return Integer get lockout time length.
282
+ */
283
+ private function get_dynamic_lockout_time_length() {
284
+ global $aio_wp_security;
285
+
286
+ $login_fail_count = $this->get_login_fail_count();
287
+ $lockout_time_default = $aio_wp_security->configs->get_value('aiowps_lockout_time_length');
288
+ if (!is_numeric($lockout_time_default)) {
289
+ $lockout_time_default = 5;
290
+ }
291
+ $lockout_time_max = $aio_wp_security->configs->get_value('aiowps_max_lockout_time_length');
292
+ if (!is_numeric($lockout_time_max)) {
293
+ $lockout_time_max = 60;
294
+ }
295
+ $lockout_time_length = (int) ($login_fail_count > 0 ? (3 * $lockout_time_default * ($login_fail_count + 1)) : $lockout_time_default);
296
+
297
+ return $lockout_time_length >= $lockout_time_max ? $lockout_time_max : $lockout_time_length;
298
+ }
299
+
300
  /**
301
  * Adds an entry to the `aiowps_login_lockdown` table.
302
  *
303
+ * @param string $username User's username or email
304
  * @param string $lock_reason
305
+ * @param bool $is_lockout_email_sent flag for lockout email send
306
  */
307
+ public function lock_the_user($username, $lock_reason = 'login_fail', $is_lockout_email_sent = 0) {
308
  global $wpdb, $aio_wp_security;
309
  $login_lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
310
+ $lock_minutes = $this->get_dynamic_lockout_time_length();
311
  $ip = AIOWPSecurity_Utility_IP::get_user_ip_address(); //Get the IP address of user
312
  if (empty($ip)) return;
313
  $ip_range = AIOWPSecurity_Utility_IP::get_sanitized_ip_range($ip); //Get the IP range of the current user
322
  }
323
  $ip_range_str = esc_sql($ip_range).'.*';// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
324
 
325
+ $lock_time = current_time('mysql', true);
326
  $date = new DateTime($lock_time);
327
  $add_interval = 'PT'.absint($lock_minutes).'M';
328
  $date->add(new DateInterval($add_interval));
329
  $release_time = $date->format('Y-m-d H:i:s');
330
+ $backtrace_log = '';
331
+ if ('1' == $aio_wp_security->configs->get_value('aiowps_enable_php_backtrace_in_email')) {
332
+ $backtrace_log = print_r(debug_backtrace(), true);
333
+ }
334
+ $is_lockout_email_sent = (1 == $aio_wp_security->configs->get_value('aiowps_enable_email_notify') ? 0 : -1);
335
+ $data = array('user_id' => $user_id, 'user_login' => $username, 'lockdown_date' => $lock_time, 'release_date' => $release_time, 'failed_login_IP' => $ip, 'lock_reason' => $lock_reason, 'is_lockout_email_sent' => $is_lockout_email_sent, 'backtrace_log' => $backtrace_log);
336
+ $format = array('%d', '%s', '%s', '%s', '%s', '%s', '%d', '%s');
337
  $result = $wpdb->insert($login_lockdown_table, $data, $format);
338
 
339
  if (false === $result) {
340
  $aio_wp_security->debug_logger->log_debug("Error inserting record into ".$login_lockdown_table, 4);//Log the highly unlikely event of DB error
341
  } else {
342
  do_action('aiowps_lockdown_event', $ip_range, $username);
 
343
  $aio_wp_security->debug_logger->log_debug("The following IP address range has been locked out for exceeding the maximum login attempts: ".$ip_range, 2);//Log the lockdown event
344
  }
345
  }
372
  }
373
 
374
  /**
375
+ * Send IP Lock notification.
376
  *
377
+ * @param Array $lockout_ips_list have username, ip_range, ip
378
+ * @param String $backtrace_filepath
379
+ *
380
+ * @return Boolean True if mail sent otherwise false.
381
  */
382
+ private function send_ip_lock_notification_email($lockout_ips_list = array(), $backtrace_filepath = '') {
383
  global $aio_wp_security;
384
+ $send_mail = false;
385
+ if (0 != count($lockout_ips_list)) {
386
+ $email_notification_enabled = $aio_wp_security->configs->get_value('aiowps_enable_email_notify');
387
+ if (1 == $email_notification_enabled) {
388
+ $to_email_address = $aio_wp_security->configs->get_value('aiowps_email_address');
389
+ $subject = '['.get_option('home').'] '. __('Site Lockout Notification', 'all-in-one-wp-security-and-firewall');
390
+ $email_msg = __('Lockdown events had occurred due to too many failed login attempts or invalid username:', 'all-in-one-wp-security-and-firewall')."\n\n";
391
+
392
+ foreach ($lockout_ips_list as $lockout_ip) {
393
+ $email_msg .= __('Username:', 'all-in-one-wp-security-and-firewall') . ' ' . $lockout_ip['username'] . "\n";
394
+ $email_msg .= __('IP Address:', 'all-in-one-wp-security-and-firewall') . ' ' . $lockout_ip['ip'] . "\n";
395
+ if ('' != $lockout_ip['ip_range']) {
396
+ $email_msg .= __('IP Range:', 'all-in-one-wp-security-and-firewall') . ' ' . $lockout_ip['ip_range'] . '.*' . "\n";
397
+ }
398
+ $email_msg .= "\n";
399
+ }
400
+
401
+ $email_msg .= __("Log into your site WordPress administration panel to see the duration of the lockout or to unlock the user.", 'all-in-one-wp-security-and-firewall') . "\n";
402
+
403
+ $site_title = get_bloginfo('name');
404
+ $from_name = empty($site_title) ? 'WordPress' : $site_title;
405
+ $email_header = 'From: '.$from_name.' <'.get_bloginfo('admin_email').'>' . "\r\n\\";
406
+ $send_mail = wp_mail($to_email_address, $subject, $email_msg, $email_header, $backtrace_filepath);
407
+
408
+ if (false === $send_mail) {
409
+ $ips_list = implode(', ', wp_list_pluck($lockout_ips_list, 'ip'));
410
+ $aio_wp_security->debug_logger->log_debug("Lockout notification email failed to send to " . implode(', ', $to_email_address) . " for IPs ".$ips_list, 4);
411
+ }
412
  }
413
  }
414
+ return $send_mail;
415
  }
416
 
417
  /**
428
  $unlock_link = '';
429
  $lockdown_table_name = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
430
  $secret_rand_key = (md5(uniqid(rand(), true)));
431
+ $unlock_request_date_time = current_time('mysql', true);
432
  $res = $wpdb->query($wpdb->prepare("UPDATE $lockdown_table_name SET unlock_key = %s WHERE release_date > %s AND failed_login_ip LIKE %s", $secret_rand_key, $unlock_request_date_time, "%" . esc_sql($ip_range) . "%"));
433
  if (null == $res) {
434
  $aio_wp_security->debug_logger->log_debug("No locked user found with IP range ".$ip_range, 4);
435
  return false;
436
  } else {
437
+ //Check if unlock request or submitted from a woocommerce account login page
438
  if (isset($_POST['aiowps-woo-login'])) {
439
  $date_time = current_time('mysql');
440
  $data = array('date_time' => $date_time, 'meta_key1' => 'woo_unlock_request_key', 'meta_value1' => $secret_rand_key);
461
  public static function process_unlock_request($unlock_key) {
462
  global $wpdb, $aio_wp_security;
463
  $lockdown_table_name = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
464
+ $unlock_request_date_time = current_time('mysql', true);
465
  $unlock_command = $wpdb->prepare("UPDATE ".$lockdown_table_name." SET release_date = %s WHERE unlock_key = %s", $unlock_request_date_time, $unlock_key);
466
  $result = $wpdb->query($unlock_command);
467
  if (false === $result) {
478
  }
479
  if ($aio_wp_security->configs->get_value('aiowps_enable_rename_login_page')=='1') {
480
  if (get_option('permalink_structure')) {
481
+ $site_url = trailingslashit(site_url());
482
  } else {
483
+ $site_url = trailingslashit(site_url()) . '?';
484
  }
485
  if ($woo_unlock) {
486
  $login_url = wc_get_page_permalink('myaccount'); //redirect to woo login page if applicable
490
  $aio_wp_security->debug_logger->log_debug("process_unlock_request(): Error deleting row from AIOWPSEC_TBL_GLOBAL_META_DATA for meta_key1=woo_unlock_request_key and meta_value1=".$unlock_key, 4);
491
  }
492
  } else {
493
+ $login_url = $site_url.$aio_wp_security->configs->get_value('aiowps_login_page_slug');
494
  }
495
 
496
  AIOWPSecurity_Utility::redirect_to_url($login_url);
533
  if (is_user_logged_in()) {
534
  $current_user = wp_get_current_user();
535
  $user_id = $current_user->ID;
536
+ $current_time = current_time('mysql', true);
537
+ $login_time = $this->get_wp_user_aiowps_last_login_time($user_id);
538
+ if (empty($login_time)) {
539
+ return;
540
+ }
541
  $diff = strtotime($current_time) - strtotime($login_time);
542
  $logout_time_interval_value = $aio_wp_security->configs->get_value('aiowps_logout_time_period');
543
  $logout_time_interval_val_seconds = $logout_time_interval_value * 60;
548
  $curr_page_url = AIOWPSecurity_Utility::get_current_page_url();
549
  $after_logout_payload = array('redirect_to' => $curr_page_url, 'msg' => $this->key_login_msg.'=session_expired');
550
  //Save some of the logout redirect data to a transient
551
+ is_multisite() ? set_site_transient('aiowps_logout_payload', $after_logout_payload, 30 * 60) : set_transient('aiowps_logout_payload', $after_logout_payload, 30 * 60);
552
  $logout_url = AIOWPSEC_WP_URL.'?aiowpsec_do_log_out=1';
553
  $logout_url = AIOWPSecurity_Utility::add_query_data_to_url($logout_url, 'al_additional_data', '1');
554
  $logout_url_with_nonce = html_entity_decode(wp_nonce_url($logout_url, 'aio_logout'));
558
  }
559
  }
560
 
561
+ /**
562
+ * Get last logged in time of given user id.
563
+ *
564
+ * @param integer $user_id
565
+ * @return mixed Last login time. False for an invalid $user_id (non-numeric, zero, or negative value). An empty string if a valid but non-existing user ID is passed.
566
+ */
567
+ public function get_wp_user_aiowps_last_login_time($user_id) {
568
+ $last_login = apply_filters('aiowps_get_last_login_time', get_user_meta($user_id, 'aiowps_last_login_time', true), $user_id);
569
  return $last_login;
570
  }
571
+
572
  public static function wp_login_action_handler($user_login, $user = '') {
573
  global $wpdb, $aio_wp_security;
574
  $login_activity_table = AIOWPSEC_TBL_USER_LOGIN_ACTIVITY;
581
  return;
582
  }
583
  }
584
+ $login_date_time = current_time('mysql', true);
585
+ update_user_meta($user->ID, 'aiowps_last_login_time', $login_date_time); //store last login time in meta table
586
  $curr_ip_address = AIOWPSecurity_Utility_IP::get_user_ip_address();
587
  $data = array('user_id' => $user->ID, 'user_login' => $user_login, 'login_date' => $login_date_time, 'login_ip' => $curr_ip_address);
588
  $format = array('%d', '%s', '%s', '%s');
607
  //Clean up transients table
608
  $this->cleanup_users_online_transient($user_id, $ip_addr);
609
  $login_activity_table = AIOWPSEC_TBL_USER_LOGIN_ACTIVITY;
610
+ $logout_date_time = current_time('mysql', true);
611
  $data = array('logout_date' => $logout_date_time);
612
  $where = array('user_id' => $user_id, 'login_ip' => $ip_addr, 'logout_date' => '1000-10-10 10:00:00');
613
  $result = $wpdb->update($login_activity_table, $data, $where);
623
  */
624
  public function update_users_online_transient() {
625
  if (is_user_logged_in()) {
626
+ $is_multi_site = is_multisite();
627
  $current_user_ip = AIOWPSecurity_Utility_IP::get_user_ip_address();
628
  // get the logged in users list from transients entry
629
  $logged_in_users = ($is_multi_site ? get_site_transient('users_online') : get_transient('users_online'));
671
  if ($update_existing) {
672
  // Update transient if the last activity was over 15 min ago for this user
673
  $logged_in_users[$item_index] = $current_user_info;
674
+ is_multisite() ? set_site_transient('users_online', $logged_in_users, 30 * 60) : set_transient('users_online', $logged_in_users, 30 * 60);
675
  } else {
676
  $logged_in_users[] = $current_user_info;
677
+ is_multisite() ? set_site_transient('users_online', $logged_in_users, 30 * 60) : set_transient('users_online', $logged_in_users, 30 * 60);
678
  }
679
  }
680
  }
688
  * @return void
689
  */
690
  public function cleanup_users_online_transient($user_id, $ip_addr) {
691
+ $is_multi_site = is_multisite();
692
  if ($is_multi_site) {
693
  $current_blog_id = get_current_blog_id();
694
  $logged_in_users = AIOWPSecurity_User_Login::get_subsite_logged_in_users($current_blog_id);
785
  if (empty($blog_id)) return false;
786
 
787
  $subsite_logged_in_users = array();
788
+ if (is_multisite()) {
789
  // this contains all logged in users sitewide across subsites
790
  $users_online = get_site_transient('users_online');
791
  if (empty($users_online)) {
801
  return $subsite_logged_in_users;
802
  }
803
 
804
+ /**
805
+ * Send email notification to an user that has flag is_lockout_email_sent is 0.
806
+ *
807
+ * @return Void
808
+ */
809
+ public function send_login_lockout_emails() {
810
+ global $wpdb, $aio_wp_security;
811
+ // if user enabled notification email then only have to send
812
+ $email_notification_enabled = $aio_wp_security->configs->get_value('aiowps_enable_email_notify');
813
+ if (0 == $email_notification_enabled) {
814
+ return;
815
+ }
816
+ // get recent lockout records on top to notify
817
+ $sql = $wpdb->prepare('SELECT id, user_login, failed_login_ip, backtrace_log FROM ' .AIOWPSEC_TBL_LOGIN_LOCKDOWN. ' WHERE is_lockout_email_sent = %d ORDER BY id DESC', 0);
818
+ $result = $wpdb->get_results($sql);
819
+ if (empty($result)) {
820
+ return;
821
+ }
822
+ $login_lockout_ids_send_emails = array();
823
+ $lockout_ips_backtrace_log = array();
824
+ $lockout_ips_list = array();
825
+ $backtrace_filepath = '';
826
+ foreach ($result as $row) {
827
+ $ip_range = AIOWPSecurity_Utility_IP::get_sanitized_ip_range($row->failed_login_ip);
828
+ $lockout_ips_list[] = array('username' => $row->user_login, 'ip' => $row->failed_login_ip, 'ip_range' => $ip_range);
829
+ $login_lockout_ids_send_emails[] = $row->id;
830
+ if ('1' == $aio_wp_security->configs->get_value('aiowps_enable_php_backtrace_in_email') && '' != $row->backtrace_log) {
831
+ $lockout_ips_backtrace_log[] = array('backtrace_log' => $row->backtrace_log);
832
+ }
833
+ }
834
+
835
+ if (0 != count($lockout_ips_backtrace_log)) {
836
+ $backtrace_filepath = AIOWPSecurity_Utility::login_lockdown_email_backtrace_log_file($lockout_ips_backtrace_log);
837
+ }
838
+
839
+ $this->send_ip_lock_notification_email($lockout_ips_list, $backtrace_filepath);
840
+
841
+ if ('' != $backtrace_filepath) {
842
+ unlink($backtrace_filepath);
843
+ }
844
+
845
+ if (!empty($login_lockout_ids_send_emails)) {
846
+ $aio_wp_security->debug_logger->log_debug(sprintf('The IP lock notification emails of login lockout ids [%s] are sent.', implode(', ', $login_lockout_ids_send_emails)), 4);
847
+ // update all email to as sent.
848
+ $sql = $wpdb->prepare('UPDATE '.AIOWPSEC_TBL_LOGIN_LOCKDOWN.' SET is_lockout_email_sent = %d WHERE is_lockout_email_sent = %d', 1, 0);
849
+ //$sql = $wpdb->prepare('UPDATE '.AIOWPSEC_TBL_LOGIN_LOCKDOWN.' SET is_lockout_email_sent = %d WHERE id IN (%1s)', 1, implode(', ', $login_lockout_ids_send_emails));
850
+ $update_result = $wpdb->query($sql);
851
+ if (false === $update_result) {
852
+ $error_msg = empty($wpdb->last_error) ? 'Could not receive the reason for the failure' : $wpdb->last_error;
853
+ $aio_wp_security->debug_logger->log_debug_cron("Lockout email flag is not updated in database due to error: {$error_msg}", 4);
854
+ }
855
+ }
856
+ }
857
  }
classes/wp-security-utility-file.php CHANGED
@@ -13,9 +13,8 @@ class AIOWPSecurity_Utility_File {
13
  // NOTE: we can add to this list in future if we wish
14
 
15
  //Get wp-config.php file path
16
- if (!function_exists('get_home_path')) require_once(ABSPATH. '/wp-admin/includes/file.php');
17
  $wp_config_path = AIOWPSecurity_Utility_File::get_wp_config_file_path();
18
- $home_path = get_home_path();
19
 
20
  $this->files_and_dirs_to_check = array(
21
  array('name' => 'root directory', 'path' => ABSPATH, 'permissions' => '0755'),
@@ -33,6 +32,20 @@ class AIOWPSecurity_Utility_File {
33
 
34
  }
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  public static function get_wp_config_file_path() {
37
  $wp_config_file = ABSPATH . 'wp-config.php';
38
  if (file_exists($wp_config_file)) {
@@ -126,7 +139,7 @@ class AIOWPSecurity_Utility_File {
126
 
127
  //First check if a backup entry already exists in the global_meta table
128
  $aiowps_global_meta_tbl_name = AIOWPSEC_TBL_GLOBAL_META_DATA;
129
- $resultset = $wpdb->get_row($wpdb->prepare("SELECT * FROM $aiowps_global_meta_tbl_name WHERE meta_key1 = '%s'", $key_description));
130
  if ($resultset) {
131
  $where = array('meta_key1' => $key_description);
132
  $res = $wpdb->update($aiowps_global_meta_tbl_name, $data, $where);
@@ -396,7 +409,7 @@ class AIOWPSecurity_Utility_File {
396
  // Remove the upload path base directory from the attachment URL
397
  $attachment_url = str_replace($upload_dir_paths['baseurl'] . '/', '', $attachment_url);
398
  // Now run custom database query to get attachment ID from attachment URL
399
- $attachment_id = $wpdb->get_var($wpdb->prepare("SELECT wposts.ID FROM $wpdb->posts wposts, $wpdb->postmeta wpostmeta WHERE wposts.ID = wpostmeta.post_id AND wpostmeta.meta_key = '_wp_attached_file' AND wpostmeta.meta_value = '%s' AND wposts.post_type = 'attachment'", $attachment_url));
400
  }
401
  return $attachment_id;
402
  }
@@ -424,4 +437,40 @@ class AIOWPSecurity_Utility_File {
424
  return array_keys($files);
425
  }
426
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
  }
13
  // NOTE: we can add to this list in future if we wish
14
 
15
  //Get wp-config.php file path
 
16
  $wp_config_path = AIOWPSecurity_Utility_File::get_wp_config_file_path();
17
+ $home_path = self::get_home_path();
18
 
19
  $this->files_and_dirs_to_check = array(
20
  array('name' => 'root directory', 'path' => ABSPATH, 'permissions' => '0755'),
32
 
33
  }
34
 
35
+ /**
36
+ * Returns full path to mu-plugin directory
37
+ *
38
+ * @return string
39
+ */
40
+ public static function get_mu_plugin_dir() {
41
+ return WPMU_PLUGIN_DIR;
42
+ }
43
+
44
+ /**
45
+ * Returns path to wp-config
46
+ *
47
+ * @return string
48
+ */
49
  public static function get_wp_config_file_path() {
50
  $wp_config_file = ABSPATH . 'wp-config.php';
51
  if (file_exists($wp_config_file)) {
139
 
140
  //First check if a backup entry already exists in the global_meta table
141
  $aiowps_global_meta_tbl_name = AIOWPSEC_TBL_GLOBAL_META_DATA;
142
+ $resultset = $wpdb->get_row($wpdb->prepare("SELECT * FROM $aiowps_global_meta_tbl_name WHERE meta_key1 = %s", $key_description));
143
  if ($resultset) {
144
  $where = array('meta_key1' => $key_description);
145
  $res = $wpdb->update($aiowps_global_meta_tbl_name, $data, $where);
409
  // Remove the upload path base directory from the attachment URL
410
  $attachment_url = str_replace($upload_dir_paths['baseurl'] . '/', '', $attachment_url);
411
  // Now run custom database query to get attachment ID from attachment URL
412
+ $attachment_id = $wpdb->get_var($wpdb->prepare("SELECT wposts.ID FROM $wpdb->posts wposts, $wpdb->postmeta wpostmeta WHERE wposts.ID = wpostmeta.post_id AND wpostmeta.meta_key = '_wp_attached_file' AND wpostmeta.meta_value = %s AND wposts.post_type = 'attachment'", $attachment_url));
413
  }
414
  return $attachment_id;
415
  }
437
  return array_keys($files);
438
  }
439
 
440
+ /**
441
+ * Remove a directory from the local filesystem
442
+ *
443
+ * @param string $dir - the directory
444
+ * @param boolean $contents_only - if set to true, then do not remove the directory, but only empty it of contents
445
+ *
446
+ * @return boolean - success/failure
447
+ */
448
+ public static function remove_local_directory($dir, $contents_only = false) {
449
+
450
+ $handle = opendir($dir);
451
+ if ($handle) {
452
+ while (false !== ($entry = readdir($handle))) {
453
+ if ('.' !== $entry && '..' !== $entry) {
454
+ if (is_dir($dir.'/'.$entry)) {
455
+ self::remove_local_directory($dir.'/'.$entry, false);
456
+ } else {
457
+ @unlink($dir.'/'.$entry);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
458
+ }
459
+ }
460
+ }
461
+ @closedir($handle);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
462
+ }
463
+
464
+ return $contents_only ? true : rmdir($dir);
465
+ }
466
+
467
+ /**
468
+ * Get home path.
469
+ *
470
+ * @return string
471
+ */
472
+ public static function get_home_path() {
473
+ if (!function_exists('get_home_path')) require_once(ABSPATH. '/wp-admin/includes/file.php');
474
+ return wp_normalize_path(get_home_path());
475
+ }
476
  }
classes/wp-security-utility-firewall.php ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if (!defined('ABSPATH')) {
3
+ exit; //Exit if accessed directly
4
+ }
5
+
6
+ class AIOWPSecurity_Utility_Firewall {
7
+
8
+ /**
9
+ * Returned if the user is required to setup auto_prepend_file manually
10
+ *
11
+ * @var null
12
+ */
13
+ const MANUAL_SETUP = null;
14
+
15
+ /**
16
+ * Returns the current path to our firewall file
17
+ *
18
+ * @return string
19
+ */
20
+ public static function get_firewall_path() {
21
+ return wp_normalize_path(AIO_WP_SECURITY_PATH.'/classes/firewall/wp-security-firewall.php');
22
+ }
23
+
24
+ /**
25
+ * Returns the firewall rules path.
26
+ *
27
+ * @return string
28
+ */
29
+ public static function get_firewall_rules_path() {
30
+ $upload_dir_info = wp_get_upload_dir();
31
+ $firewall_rules_path = trailingslashit($upload_dir_info['basedir'].'/aios/firewall-rules');
32
+ wp_mkdir_p($firewall_rules_path);
33
+ return wp_normalize_path($firewall_rules_path);
34
+ }
35
+
36
+ /**
37
+ * Returns the current path to our bootstrap file
38
+ *
39
+ * @return string
40
+ */
41
+ public static function get_bootstrap_path() {
42
+ return path_join(AIOWPSecurity_Utility_File::get_home_path(), 'aios-bootstrap.php');
43
+ }
44
+
45
+ /**
46
+ * Returns the full path to our mu-plugin
47
+ *
48
+ * @return string
49
+ */
50
+ public static function get_muplugin_path() {
51
+ return path_join(AIOWPSecurity_Utility_File::get_mu_plugin_dir(), 'aiowpsec-firewall-loader.php');
52
+ }
53
+
54
+ /**
55
+ * Returns our managed mu-plugin file
56
+ *
57
+ * @return AIOWPSecurity_Block_Muplugin
58
+ */
59
+ public static function get_muplugin_file() {
60
+ return new AIOWPSecurity_Block_Muplugin(AIOWPSecurity_Utility_Firewall::get_muplugin_path());
61
+ }
62
+
63
+ /**
64
+ * Returns our managed wp-config file
65
+ *
66
+ * @return AIOWPSecurity_Block_WpConfig
67
+ */
68
+ public static function get_wpconfig_file() {
69
+ return new AIOWPSecurity_Block_WpConfig(AIOWPSecurity_Utility_File::get_wp_config_file_path());
70
+ }
71
+
72
+ /**
73
+ * Returns our managed bootstrap file
74
+ *
75
+ * @return AIOWPSecurity_Block_Bootstrap
76
+ */
77
+ public static function get_bootstrap_file() {
78
+ return new AIOWPSecurity_Block_Bootstrap(AIOWPSecurity_Utility_Firewall::get_bootstrap_path());
79
+ }
80
+
81
+ /**
82
+ * Gets the auto_prepend_file directive, if already set
83
+ *
84
+ * @param string $source - where to check for the directive
85
+ * @return string - returns the directive if set, or empty string if not set
86
+ */
87
+ public static function get_already_set_directive($source = '') {
88
+
89
+ if (!empty($source)) {
90
+ clearstatcache();
91
+ if (file_exists($source) && is_readable($source)) {
92
+
93
+ $vals = @parse_ini_file($source); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
94
+
95
+ if (false !== $vals && isset($vals['auto_prepend_file'])) {
96
+ return $vals['auto_prepend_file'];
97
+ }
98
+ }
99
+ } else {
100
+ $directive = ini_get('auto_prepend_file');
101
+ if (false !== $directive) {
102
+ return $directive;
103
+ }
104
+ }
105
+
106
+ return '';
107
+ }
108
+
109
+ /**
110
+ * Returns the file that's necessary to load our firewall
111
+ *
112
+ * @return AIOWPSecurity_Block_File|null file needed to load the firewall
113
+ */
114
+ public static function get_server_file() {
115
+ $server_type = AIOWPSecurity_Utility::get_server_type();
116
+ $is_cgi = false;
117
+ $sapi = PHP_SAPI;
118
+
119
+ if (false !== stripos($sapi, 'cgi')) {
120
+ $is_cgi = true;
121
+ }
122
+
123
+ if (AIOWPSecurity_Utility::UNSUPPORTED_SERVER_TYPE === $server_type) {
124
+ return self::MANUAL_SETUP;
125
+
126
+ } elseif (false === $is_cgi && 'apache' === $server_type) {
127
+
128
+ $htpath = path_join(get_home_path(), '.htaccess');
129
+ return new AIOWPSecurity_Block_Htaccess($htpath);
130
+
131
+ } elseif ('litespeed' === $server_type || 'litespeed' === $sapi) {
132
+
133
+ $htpath = path_join(get_home_path(), '.htaccess');
134
+ return new AIOWPSecurity_Block_Litespeed($htpath);
135
+
136
+ } else {
137
+ $userini = path_join(get_home_path(), '.user.ini');
138
+ return new AIOWPSecurity_Block_Userini($userini);
139
+ }
140
+
141
+ }
142
+
143
+ /**
144
+ * Attempts to remove our firewall.
145
+ *
146
+ * @return void
147
+ */
148
+ public static function remove_firewall() {
149
+
150
+ $firewall_files = array(
151
+ 'server' => AIOWPSecurity_Utility_Firewall::get_server_file(),
152
+ 'bootstrap' => AIOWPSecurity_Utility_Firewall::get_bootstrap_file(),
153
+ 'wpconfig' => AIOWPSecurity_Utility_Firewall::get_wpconfig_file(),
154
+ 'muplugin' => AIOWPSecurity_Utility_Firewall::get_muplugin_file(),
155
+ );
156
+
157
+ foreach ($firewall_files as $file) {
158
+ if (AIOWPSecurity_Utility_Firewall::MANUAL_SETUP === $file) {
159
+ continue;
160
+ }
161
+
162
+ if (true === $file->contains_contents()) {
163
+
164
+ $removed = $file->remove_contents();
165
+
166
+ if (is_wp_error($removed)) {
167
+ global $aio_wp_security;
168
+
169
+ $error_message = $removed->get_error_message();
170
+ $error_message .= ' - ';
171
+ $error_message .= $removed->get_error_data();
172
+ $aio_wp_security->debug_logger->log_debug($error_message, 4);
173
+ }
174
+ }
175
+
176
+ }
177
+
178
+ //Delete our mu-plugin, if it's created
179
+ clearstatcache();
180
+ $muplugin_path = $firewall_files['muplugin'];
181
+ if (file_exists($muplugin_path)) {
182
+ @unlink($muplugin_path); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore this
183
+ }
184
+
185
+ }
186
+ }
classes/wp-security-utility-htaccess.php CHANGED
@@ -106,8 +106,7 @@ class AIOWPSecurity_Utility_Htaccess {
106
  return false; //unable to write to the file
107
  }
108
 
109
- if (!function_exists('get_home_path')) require_once(ABSPATH. '/wp-admin/includes/file.php');
110
- $home_path = get_home_path();
111
  $htaccess = $home_path . '.htaccess';
112
 
113
  if (!$f = @fopen($htaccess, 'a+')) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged,Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure
@@ -157,8 +156,7 @@ class AIOWPSecurity_Utility_Htaccess {
157
  * @return boolean
158
  */
159
  public static function delete_from_htaccess($section = 'All In One WP Security') {
160
- if (!function_exists('get_home_path')) require_once(ABSPATH. '/wp-admin/includes/file.php');
161
- $home_path = get_home_path();
162
  $htaccess = $home_path . '.htaccess';
163
 
164
  if (!file_exists($htaccess)) {
@@ -223,9 +221,7 @@ class AIOWPSecurity_Utility_Htaccess {
223
  $rules .= AIOWPSecurity_Utility_Htaccess::getrules_forbid_proxy_comment_posting();
224
  $rules .= AIOWPSecurity_Utility_Htaccess::getrules_deny_bad_query_strings();
225
  $rules .= AIOWPSecurity_Utility_Htaccess::getrules_advanced_character_string_filter();
226
- $rules .= AIOWPSecurity_Utility_Htaccess::getrules_6g_blacklist();
227
  $rules .= AIOWPSecurity_Utility_Htaccess::getrules_5g_blacklist();
228
- $rules .= AIOWPSecurity_Utility_Htaccess::getrules_enable_brute_force_prevention();
229
  $rules .= AIOWPSecurity_Utility_Htaccess::getrules_block_spambots();
230
  $rules .= AIOWPSecurity_Utility_Htaccess::getrules_enable_login_whitelist_v2();
231
  $rules .= AIOWPSecurity_Utility_Htaccess::prevent_image_hotlinks();
@@ -278,7 +274,7 @@ class AIOWPSecurity_Utility_Htaccess {
278
  $hosts = AIOWPSecurity_Utility::explode_trim_filter_empty($aio_wp_security->configs->get_value('aiowps_banned_ip_addresses'));
279
  // Filter out duplicate lines, add netmask to IP addresses
280
  $ips_with_netmask = self::add_netmask(array_unique($hosts));
281
- if (!empty($ips_with_netmask) && ($aio_wp_security->configs->get_value('aiowps_enable_6g_firewall') != '1')) {
282
  $rules .= AIOWPSecurity_Utility_Htaccess::$ip_blacklist_marker_start . PHP_EOL; //Add feature marker start
283
 
284
  if ($apache_or_litespeed) {
@@ -410,36 +406,6 @@ class AIOWPSecurity_Utility_Htaccess {
410
  return $rules;
411
  }
412
 
413
- /**
414
- * This function will write some drectives to block all people who do not have a cookie
415
- * when trying to access the WP login page
416
- */
417
- public static function getrules_enable_brute_force_prevention() {
418
- global $aio_wp_security;
419
- $rules = '';
420
- if ($aio_wp_security->configs->get_value('aiowps_enable_brute_force_attack_prevention') == '1') {
421
- $cookie_name = $aio_wp_security->configs->get_value('aiowps_brute_force_secret_word');
422
- $test_cookie_name = $aio_wp_security->configs->get_value('aiowps_cookie_brute_test');
423
- $redirect_url = $aio_wp_security->configs->get_value('aiowps_cookie_based_brute_force_redirect_url');
424
- $rules .= AIOWPSecurity_Utility_Htaccess::$enable_brute_force_attack_prevention_marker_start . PHP_EOL; //Add feature marker start
425
- $rules .= 'RewriteEngine On' . PHP_EOL;
426
- $rules .= 'RewriteCond %{REQUEST_URI} (wp-admin|wp-login)' . PHP_EOL;// If URI contains wp-admin or wp-login
427
- if ($aio_wp_security->configs->get_value('aiowps_brute_force_attack_prevention_ajax_exception') == '1') {
428
- $rules .= 'RewriteCond %{REQUEST_URI} !(wp-admin/admin-ajax.php)' . PHP_EOL; // To allow ajax requests through
429
- }
430
- if ($aio_wp_security->configs->get_value('aiowps_brute_force_attack_prevention_pw_protected_exception') == '1') {
431
- $rules .= 'RewriteCond %{QUERY_STRING} !(action\=postpass)' . PHP_EOL; // Possible workaround for people usign the password protected page/post feature
432
- }
433
- $rules .= 'RewriteCond %{HTTP_COOKIE} !' . $cookie_name . '= [NC]' . PHP_EOL;
434
- $rules .= 'RewriteCond %{HTTP_COOKIE} !' . $test_cookie_name . '= [NC]' . PHP_EOL;
435
- $rules .= 'RewriteRule .* ' . $redirect_url . ' [L]' . PHP_EOL;
436
- $rules .= AIOWPSecurity_Utility_Htaccess::$enable_brute_force_attack_prevention_marker_end . PHP_EOL; //Add feature marker end
437
- }
438
-
439
- return $rules;
440
- }
441
-
442
-
443
  /**
444
  * This function will write some directives to allow IPs in the whitelist to access wp-login.php or wp-admin
445
  * The function also handles the following special cases:
@@ -628,12 +594,11 @@ class AIOWPSecurity_Utility_Htaccess {
628
  if (!empty($ips_with_netmask)) {
629
  foreach ($ips_with_netmask as $xhost) {
630
  $ipv6 = false;
631
- if (strpos($xhost, ':') !== false) {
632
- //possible ipv6 addr
633
- //ipv6 - for now we will support only whole ipv6 addresses, NOT ranges
634
- $ipv6 = WP_Http::is_ip_address($xhost);
635
- if (false === $ipv6) {
636
- continue;
637
  }
638
  }
639
  $ip_range = substr($xhost, 0, strpos($xhost, "/")); //check if address range
@@ -964,119 +929,6 @@ class AIOWPSecurity_Utility_Htaccess {
964
  return $rules;
965
  }
966
 
967
- /**
968
- * This function contains the rules for the 6G blacklist produced by Jeff Starr:
969
- * https://perishablepress.com/6g/
970
- */
971
- public static function getrules_6g_blacklist() {
972
- global $aio_wp_security;
973
- $rules = '';
974
- $ip_blacklist_23 = '';
975
- $ip_blacklist_24 = '';
976
- if ($aio_wp_security->configs->get_value('aiowps_enable_6g_firewall') == '1') {
977
- //if ip blacklist is enabled we will merge that code with the 6G rules to prevent clashes
978
- if ($aio_wp_security->configs->get_value('aiowps_enable_blacklisting') == '1') {
979
- // Let's do the list of blacklisted IPs first
980
- $hosts = AIOWPSecurity_Utility::explode_trim_filter_empty($aio_wp_security->configs->get_value('aiowps_banned_ip_addresses'));
981
- // Filter out duplicate lines, add netmask to IP addresses
982
- $ips_with_netmask = self::add_netmask(array_unique($hosts));
983
-
984
- if (!empty($ips_with_netmask)) {
985
- // Apache or LiteSpeed webserver
986
- // Apache 2.2 and older
987
- $ip_blacklist_23 .= '#AIOWPS_IP_BLACKLIST_2_3_START' . PHP_EOL;
988
- $ip_blacklist_24 .= '#AIOWPS_IP_BLACKLIST_2_4_START' . PHP_EOL;
989
- foreach ($ips_with_netmask as $ip_with_netmask) {
990
- $ip_blacklist_23 .= "Deny from " . $ip_with_netmask . PHP_EOL;
991
- $ip_blacklist_24 .= "Require not ip " . $ip_with_netmask . PHP_EOL;
992
- }
993
- $ip_blacklist_23 .= '#AIOWPS_IP_BLACKLIST_2_3_END' . PHP_EOL;
994
- $ip_blacklist_24 .= '#AIOWPS_IP_BLACKLIST_2_4_END' . PHP_EOL;
995
- }
996
- }
997
-
998
- $rules .= AIOWPSecurity_Utility_Htaccess::$six_g_blacklist_marker_start . PHP_EOL; //Add feature marker start
999
-
1000
- $rules .= '# 6G FIREWALL/BLACKLIST
1001
- # @ https://perishablepress.com/6g/
1002
-
1003
- # 6G:[QUERY STRINGS]
1004
- <IfModule mod_rewrite.c>
1005
- RewriteEngine On
1006
- RewriteCond %{QUERY_STRING} (eval\() [NC,OR]
1007
- RewriteCond %{QUERY_STRING} (127\.0\.0\.1) [NC,OR]
1008
- RewriteCond %{QUERY_STRING} ([a-z0-9]{2000,}) [NC,OR]
1009
- RewriteCond %{QUERY_STRING} (javascript:)(.*)(;) [NC,OR]
1010
- RewriteCond %{QUERY_STRING} (base64_encode)(.*)(\() [NC,OR]
1011
- RewriteCond %{QUERY_STRING} (GLOBALS|REQUEST)(=|\[|%) [NC,OR]
1012
- RewriteCond %{QUERY_STRING} (<|%3C)(.*)script(.*)(>|%3) [NC,OR]
1013
- RewriteCond %{QUERY_STRING} (\\|\.\.\.|\.\./|~|`|<|>|\|) [NC,OR]
1014
- RewriteCond %{QUERY_STRING} (boot\.ini|etc/passwd|self/environ) [NC,OR]
1015
- RewriteCond %{QUERY_STRING} (thumbs?(_editor|open)?|tim(thumb)?)\.php [NC,OR]
1016
- RewriteCond %{QUERY_STRING} (\'|\")(.*)(drop|insert|md5|select|union) [NC]
1017
- RewriteRule .* - [F]
1018
- </IfModule>
1019
-
1020
- # 6G:[REQUEST METHOD]
1021
- <IfModule mod_rewrite.c>
1022
- RewriteCond %{REQUEST_METHOD} ^(connect|debug|move|put|trace|track) [NC]
1023
- RewriteRule .* - [F]
1024
- </IfModule>
1025
-
1026
- # 6G:[REFERRERS]
1027
- <IfModule mod_rewrite.c>
1028
- RewriteCond %{HTTP_REFERER} ([a-z0-9]{2000,}) [NC,OR]
1029
- RewriteCond %{HTTP_REFERER} (semalt.com|todaperfeita) [NC]
1030
- RewriteRule .* - [F]
1031
- </IfModule>
1032
-
1033
- # 6G:[REQUEST STRINGS]
1034
- <IfModule mod_alias.c>
1035
- RedirectMatch 403 (?i)([a-z0-9]{2000,})
1036
- RedirectMatch 403 (?i)(https?|ftp|php):/
1037
- RedirectMatch 403 (?i)(base64_encode)(.*)(\()
1038
- RedirectMatch 403 (?i)(=\\\'|=\\%27|/\\\'/?)\.
1039
- RedirectMatch 403 (?i)/(\$(\&)?|\*|\"|\.|,|&|&amp;?)/?$
1040
- RedirectMatch 403 (?i)(\{0\}|\(/\(|\.\.\.|\+\+\+|\\\"\\\")
1041
- RedirectMatch 403 (?i)(~|`|<|>|:|;|,|%|\\|\s|\{|\}|\[|\]|\|)
1042
- RedirectMatch 403 (?i)/(=|\$&|_mm|cgi-|etc/passwd|muieblack)
1043
- RedirectMatch 403 (?i)(&pws=0|_vti_|\(null\)|\{\$itemURL\}|echo(.*)kae|etc/passwd|eval\(|self/environ)
1044
- RedirectMatch 403 (?i)\.(aspx?|bash|bak?|cfg|cgi|dll|exe|git|hg|ini|jsp|log|mdb|out|sql|svn|swp|tar|rar|rdf)$
1045
- RedirectMatch 403 (?i)/(^$|(wp-)?config|mobiquo|phpinfo|shell|sqlpatch|thumb|thumb_editor|thumbopen|timthumb|webshell)\.php
1046
- </IfModule>
1047
-
1048
- # 6G:[USER AGENTS]
1049
- <IfModule mod_setenvif.c>
1050
- SetEnvIfNoCase User-Agent ([a-z0-9]{2000,}) bad_bot
1051
- SetEnvIfNoCase User-Agent (archive.org|binlar|casper|checkpriv|choppy|clshttp|cmsworld|diavol|dotbot|extract|feedfinder|flicky|g00g1e|harvest|heritrix|httrack|kmccrew|loader|miner|nikto|nutch|planetwork|postrank|purebot|pycurl|python|seekerspider|siclab|skygrid|sqlmap|sucker|turnit|vikspider|winhttp|xxxyy|youda|zmeu|zune) bad_bot
1052
-
1053
- # Apache < 2.3
1054
- <IfModule !mod_authz_core.c>
1055
- Order Allow,Deny
1056
- Allow from all
1057
- Deny from env=bad_bot';
1058
- if (!empty($ip_blacklist_23))
1059
- $rules .= PHP_EOL.$ip_blacklist_23; //add ip blacklist if applicable
1060
- $rules .= '
1061
- </IfModule>
1062
-
1063
- # Apache >= 2.3
1064
- <IfModule mod_authz_core.c>
1065
- <RequireAll>
1066
- Require all Granted
1067
- Require not env bad_bot';
1068
- if (!empty($ip_blacklist_24))
1069
- $rules .= PHP_EOL.$ip_blacklist_24; //add ip blacklist if applicable
1070
- $rules .= '
1071
- </RequireAll>
1072
- </IfModule>
1073
- </IfModule>' . PHP_EOL;
1074
- $rules .= AIOWPSecurity_Utility_Htaccess::$six_g_blacklist_marker_end . PHP_EOL; //Add feature marker end
1075
- }
1076
-
1077
- return $rules;
1078
- }
1079
-
1080
  /**
1081
  * This function will write some directives to block all comments which do not originate from the blog's domain
1082
  * OR if the user agent is empty. All blocked requests will be redirected to 127.0.0.1
@@ -1272,15 +1124,13 @@ END;
1272
  foreach ($ips as $ip) {
1273
  //Check if ipv6
1274
  if (strpos($ip, ':') !== false) {
1275
- //for now we'll only support whole ipv6 (not address ranges)
1276
- $ipv6 = WP_Http::is_ip_address($ip);
1277
- if (false === $ipv6) {
1278
- continue;
1279
  }
1280
- $output[] = $ip;
1281
  }
1282
 
1283
-
1284
  $parts = explode('.', $ip);
1285
 
1286
  // Skip any IP that is empty, has more parts than expected or has
106
  return false; //unable to write to the file
107
  }
108
 
109
+ $home_path = AIOWPSecurity_Utility_File::get_home_path();
 
110
  $htaccess = $home_path . '.htaccess';
111
 
112
  if (!$f = @fopen($htaccess, 'a+')) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged,Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure
156
  * @return boolean
157
  */
158
  public static function delete_from_htaccess($section = 'All In One WP Security') {
159
+ $home_path = AIOWPSecurity_Utility_File::get_home_path();
 
160
  $htaccess = $home_path . '.htaccess';
161
 
162
  if (!file_exists($htaccess)) {
221
  $rules .= AIOWPSecurity_Utility_Htaccess::getrules_forbid_proxy_comment_posting();
222
  $rules .= AIOWPSecurity_Utility_Htaccess::getrules_deny_bad_query_strings();
223
  $rules .= AIOWPSecurity_Utility_Htaccess::getrules_advanced_character_string_filter();
 
224
  $rules .= AIOWPSecurity_Utility_Htaccess::getrules_5g_blacklist();
 
225
  $rules .= AIOWPSecurity_Utility_Htaccess::getrules_block_spambots();
226
  $rules .= AIOWPSecurity_Utility_Htaccess::getrules_enable_login_whitelist_v2();
227
  $rules .= AIOWPSecurity_Utility_Htaccess::prevent_image_hotlinks();
274
  $hosts = AIOWPSecurity_Utility::explode_trim_filter_empty($aio_wp_security->configs->get_value('aiowps_banned_ip_addresses'));
275
  // Filter out duplicate lines, add netmask to IP addresses
276
  $ips_with_netmask = self::add_netmask(array_unique($hosts));
277
+ if (!empty($ips_with_netmask)) {
278
  $rules .= AIOWPSecurity_Utility_Htaccess::$ip_blacklist_marker_start . PHP_EOL; //Add feature marker start
279
 
280
  if ($apache_or_litespeed) {
406
  return $rules;
407
  }
408
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  /**
410
  * This function will write some directives to allow IPs in the whitelist to access wp-login.php or wp-admin
411
  * The function also handles the following special cases:
594
  if (!empty($ips_with_netmask)) {
595
  foreach ($ips_with_netmask as $xhost) {
596
  $ipv6 = false;
597
+ if (false !== strpos($xhost, ':')) {
598
+ //possible ipv6 addr or range
599
+ $checked_ip = AIOWPSecurity_Utility_IP::is_ipv6_address_or_ipv6_range($xhost);
600
+ if (false == $checked_ip) {
601
+ continue;
 
602
  }
603
  }
604
  $ip_range = substr($xhost, 0, strpos($xhost, "/")); //check if address range
929
  return $rules;
930
  }
931
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
932
  /**
933
  * This function will write some directives to block all comments which do not originate from the blog's domain
934
  * OR if the user agent is empty. All blocked requests will be redirected to 127.0.0.1
1124
  foreach ($ips as $ip) {
1125
  //Check if ipv6
1126
  if (strpos($ip, ':') !== false) {
1127
+ //for now support whole ipv6 and CIDR range.
1128
+ $checked_ip = AIOWPSecurity_Utility_IP::is_ipv6_address_or_ipv6_range($ip);
1129
+ if (false != $checked_ip) {
1130
+ $output[] = $ip;
1131
  }
 
1132
  }
1133
 
 
1134
  $parts = explode('.', $ip);
1135
 
1136
  // Skip any IP that is empty, has more parts than expected or has
classes/wp-security-utility-ip-address.php CHANGED
@@ -90,6 +90,36 @@ class AIOWPSecurity_Utility_IP {
90
  return $ip_list_array;
91
  }
92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  public static function validate_ip_list($ip_list_array, $list_type = '') {
94
  $errors = '';
95
 
@@ -101,18 +131,18 @@ class AIOWPSecurity_Utility_IP {
101
  foreach ($submitted_ips as $item) {
102
  $item = sanitize_text_field($item);
103
  if (strlen($item) > 0) {
104
- //ipv6 - for now we will support only whole ipv6 addresses, NOT ranges
105
  if (strpos($item, ':') !== false) {
106
  //possible ipv6 addr
107
- $res = WP_Http::is_ip_address($item);
108
- if (false === $res) {
109
  $errors .= "\n".$item.__(' is not a valid ip address format.', 'all-in-one-wp-security-and-firewall');
110
- } elseif ('6' == $res) {
111
  $list[] = trim($item);
112
  }
113
  continue;
114
  }
115
-
116
  $ipParts = explode('.', $item);
117
  $isIP = 0;
118
  $partcount = 1;
@@ -132,18 +162,18 @@ class AIOWPSecurity_Utility_IP {
132
 
133
  switch ($partcount) {
134
  case 1:
135
- if (trim($part) == '*') {
136
  $goodip = false;
137
  $errors .= "\n".$item.__(' is not a valid ip address format.', 'all-in-one-wp-security-and-firewall');
138
  }
139
  break;
140
  case 2:
141
- if (trim($part) == '*') {
142
  $foundwild = true;
143
  }
144
  break;
145
  default:
146
- if (trim($part) != '*') {
147
  if (true == $foundwild) {
148
  $goodip = false;
149
  $errors .= "\n".$item.__(' is not a valid ip address format.', 'all-in-one-wp-security-and-firewall');
@@ -203,30 +233,20 @@ class AIOWPSecurity_Utility_IP {
203
  if (empty($ip_address) || empty($whitelisted_ips)) return false;
204
 
205
  $ip_list_array = AIOWPSecurity_Utility_IP::create_ip_list_array_from_string_with_newline($whitelisted_ips);
206
-
207
  if (empty($ip_list_array)) return false;
208
 
209
- $visitor_ipParts = explode('.', $ip_address);
 
210
  foreach ($ip_list_array as $white_ip) {
211
  $ipParts = explode('.', $white_ip);
212
  $found = array_search('*', $ipParts);
213
- if (false !== $found) {
214
- //Means we have a whitelisted IP range so do some checks
215
- if (1 == $found) {
216
- //means last 3 octets are wildcards - check if visitor IP falls inside this range
217
- if ($visitor_ipParts[0] == $ipParts[0]) {
218
- return true;
219
- }
220
- } elseif (2 == $found) {
221
- //means last 2 octets are wildcards - check if visitor IP falls inside this range
222
- if ($visitor_ipParts[0] == $ipParts[0] && $visitor_ipParts[1] == $ipParts[1]) {
223
- return true;
224
- }
225
- } elseif (3 == $found) {
226
- //means last octet is wildcard - check if visitor IP falls inside this range
227
- if ($visitor_ipParts[0] == $ipParts[0] && $visitor_ipParts[1] == $ipParts[1] && $visitor_ipParts[2] == $ipParts[2]) {
228
- return true;
229
- }
230
  }
231
  } elseif ($white_ip == $ip_address) {
232
  return true;
90
  return $ip_list_array;
91
  }
92
 
93
+ /**
94
+ * Returns IPv6 ip address or IPv6 range if valid
95
+ *
96
+ * @param string $item possible IPv6 ip address or IPv6 range
97
+ * @return string|boolean $checked_ip trimmed IPv6 ip address or IPv6 range if given input is valid otherwise false.
98
+ */
99
+ public static function is_ipv6_address_or_ipv6_range($item) {
100
+ $checked_ip = false;
101
+ $res = WP_Http::is_ip_address($item);
102
+ if ('6' == $res && Requests_IPv6::check_ipv6($item)) {
103
+ $checked_ip = trim($item);
104
+ } else {
105
+ //ipv6 - range check for valid CIDR range
106
+ $item_ip_range = explode('/', $item);
107
+ $ip_part_valid = filter_var($item_ip_range[0], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
108
+ //1 - 164 range of the IPv6 subnect masking as per CISCO propersed change from 128.
109
+ if (2 == count($item_ip_range) && $ip_part_valid && $item_ip_range[1] >= 1 && $item_ip_range[1] <= 164) {
110
+ $checked_ip = trim($item);
111
+ }
112
+ }
113
+ return $checked_ip;
114
+ }
115
+
116
+ /**
117
+ * Validates IP or IP range
118
+ *
119
+ * @param array $ip_list_array
120
+ * @param string $list_type
121
+ * @return array $return_payload
122
+ */
123
  public static function validate_ip_list($ip_list_array, $list_type = '') {
124
  $errors = '';
125
 
131
  foreach ($submitted_ips as $item) {
132
  $item = sanitize_text_field($item);
133
  if (strlen($item) > 0) {
134
+ //ipv6 - check
135
  if (strpos($item, ':') !== false) {
136
  //possible ipv6 addr
137
+ $checked_ip = AIOWPSecurity_Utility_IP::is_ipv6_address_or_ipv6_range($item);
138
+ if (false === $checked_ip) {
139
  $errors .= "\n".$item.__(' is not a valid ip address format.', 'all-in-one-wp-security-and-firewall');
140
+ } else {
141
  $list[] = trim($item);
142
  }
143
  continue;
144
  }
145
+
146
  $ipParts = explode('.', $item);
147
  $isIP = 0;
148
  $partcount = 1;
162
 
163
  switch ($partcount) {
164
  case 1:
165
+ if ('*' == trim($part)) {
166
  $goodip = false;
167
  $errors .= "\n".$item.__(' is not a valid ip address format.', 'all-in-one-wp-security-and-firewall');
168
  }
169
  break;
170
  case 2:
171
+ if ('*' == trim($part)) {
172
  $foundwild = true;
173
  }
174
  break;
175
  default:
176
+ if ('*' != trim($part)) {
177
  if (true == $foundwild) {
178
  $goodip = false;
179
  $errors .= "\n".$item.__(' is not a valid ip address format.', 'all-in-one-wp-security-and-firewall');
233
  if (empty($ip_address) || empty($whitelisted_ips)) return false;
234
 
235
  $ip_list_array = AIOWPSecurity_Utility_IP::create_ip_list_array_from_string_with_newline($whitelisted_ips);
 
236
  if (empty($ip_list_array)) return false;
237
 
238
+ require_once(AIO_WP_SECURITY_PATH.'/vendor/mlocati/ip-lib/ip-lib.php');
239
+ $ip_address_parsed = \IPLib\Factory::parseAddressString($ip_address);
240
  foreach ($ip_list_array as $white_ip) {
241
  $ipParts = explode('.', $white_ip);
242
  $found = array_search('*', $ipParts);
243
+ $found_white_ipv6 = strpos($white_ip, ':');
244
+ // ipv4 range check starts
245
+ if (false !== $found || false != $found_white_ipv6) {
246
+ $range = \IPLib\Factory::parseRangeString($white_ip);
247
+ $with_in_range = $range->contains($ip_address_parsed);
248
+ if (true == $with_in_range) {
249
+ return true;
 
 
 
 
 
 
 
 
 
 
250
  }
251
  } elseif ($white_ip == $ip_address) {
252
  return true;
classes/wp-security-utility.php CHANGED
@@ -4,10 +4,32 @@ if (!defined('ABSPATH')) {
4
  }
5
 
6
  class AIOWPSecurity_Utility {
 
 
 
 
 
 
 
 
 
 
 
7
  public function __construct() {
8
  //NOP
9
  }
10
 
 
 
 
 
 
 
 
 
 
 
 
11
  /**
12
  * Explode $string with $delimiter, trim all lines and filter out empty ones.
13
  *
@@ -86,7 +108,7 @@ class AIOWPSecurity_Utility {
86
  }
87
 
88
  //If multisite
89
- if (AIOWPSecurity_Utility::is_multisite_install()) {
90
  $blog_id = get_current_blog_id();
91
  $admin_users = get_users('blog_id=' . $blog_id . '&orderby=login&role=administrator');
92
  foreach ($admin_users as $user) {
@@ -205,15 +227,6 @@ class AIOWPSecurity_Utility {
205
  return "";
206
  }
207
 
208
- /**
209
- * Checks if installation is multisite
210
- *
211
- * @return type
212
- */
213
- public static function is_multisite_install() {
214
- return function_exists('is_multisite') && is_multisite();
215
- }
216
-
217
  /**
218
  * This is a general yellow box message for when we want to suppress a feature's config items because site is subsite of multi-site
219
  */
@@ -370,7 +383,7 @@ class AIOWPSecurity_Utility {
370
  $referer_info = isset($_SERVER['HTTP_REFERER']) ? esc_attr($_SERVER['HTTP_REFERER']) : '';
371
  }
372
 
373
- $current_time = current_time('mysql');
374
  $data = array(
375
  'event_type' => $event_type,
376
  'username' => $username,
@@ -401,7 +414,8 @@ class AIOWPSecurity_Utility {
401
  public static function check_locked_ip($ip) {
402
  global $wpdb;
403
  $login_lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
404
- $locked_ip = $wpdb->get_row("SELECT * FROM $login_lockdown_table " . "WHERE release_date > now() AND " . "failed_login_ip = '" . esc_sql($ip) . "'", ARRAY_A);
 
405
  if (null != $locked_ip) {
406
  return true;
407
  } else {
@@ -418,7 +432,7 @@ class AIOWPSecurity_Utility {
418
  public static function get_locked_ips() {
419
  global $wpdb;
420
  $login_lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
421
- $now = current_time('mysql');
422
  $locked_ips = $wpdb->get_results($wpdb->prepare("SELECT * FROM $login_lockdown_table WHERE release_date > %s", $now), ARRAY_A);
423
 
424
  if (empty($locked_ips)) {
@@ -430,18 +444,27 @@ class AIOWPSecurity_Utility {
430
 
431
 
432
  /**
433
- * Locks an IP address - Adds an entry to the aiowps_lockdowns table
434
  *
435
- * @global type $wpdb
436
- * @global type $aio_wp_security
437
- * @param type $ip
438
- * @param type $lock_reason
439
- * @param type $username
 
 
 
440
  */
441
- public static function lock_IP($ip, $lock_reason = '', $username = '') {
442
  global $wpdb, $aio_wp_security;
443
  $login_lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
444
- $lockout_time_length = $aio_wp_security->configs->get_value('aiowps_lockout_time_length'); //TODO add a setting for this feature
 
 
 
 
 
 
445
  $username = sanitize_user($username);
446
  $user = get_user_by('login', $username); //Returns WP_User object if exists
447
 
@@ -455,11 +478,15 @@ class AIOWPSecurity_Utility {
455
  $user_id = $user->ID;
456
  }
457
 
458
- $ip_str = esc_sql($ip);
459
- $insert = "INSERT INTO " . $login_lockdown_table . " (user_id, user_login, lockdown_date, release_date, failed_login_IP, lock_reason) " .
460
- "VALUES ('" . $user_id . "', '" . $username . "', now(), date_add(now(), INTERVAL " .
461
- $lockout_time_length . " MINUTE), '" . $ip_str . "', '" . $lock_reason . "')";
462
- $result = $wpdb->query($insert);
 
 
 
 
463
  if ($result > 0) {
464
  } elseif (false === $result) {
465
  $aio_wp_security->debug_logger->log_debug("lock_IP: Error inserting record into " . $login_lockdown_table, 4);//Log the highly unlikely event of DB error
@@ -475,7 +502,7 @@ class AIOWPSecurity_Utility {
475
  */
476
  public static function get_blog_ids() {
477
  global $wpdb;
478
- if (AIOWPSecurity_Utility::is_multisite_install()) {
479
  global $wpdb;
480
  $blog_ids = $wpdb->get_col("SELECT blog_id FROM " . $wpdb->prefix . "blogs");
481
  } else {
@@ -484,31 +511,56 @@ class AIOWPSecurity_Utility {
484
  return $blog_ids;
485
  }
486
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487
 
488
  /**
489
  * This function will delete the oldest rows from a table which are over the max amount of rows specified
490
  *
491
- * @global type $wpdb
492
- * @global type $aio_wp_security
493
- * @param type $table_name
494
- * @param type $max_rows
 
495
  * @return bool
496
  */
497
- public static function cleanup_table($table_name, $max_rows = '10000') {
498
  global $wpdb, $aio_wp_security;
499
 
500
  $num_rows = $wpdb->get_var("select count(*) from $table_name");
501
  $result = true;
502
  if ($num_rows > $max_rows) {
503
  //if the table has more than max entries delete oldest rows
504
-
505
  $del_sql = "DELETE FROM $table_name
506
- WHERE id <= (
507
- SELECT id
508
  FROM (
509
- SELECT id
510
  FROM $table_name
511
- ORDER BY id DESC
512
  LIMIT 1 OFFSET $max_rows
513
  ) foo_tmp
514
  )";
@@ -529,26 +581,28 @@ class AIOWPSecurity_Utility {
529
  *
530
  * @global wpdb $wpdb
531
  */
532
- public static function delete_expired_captcha_options() {
533
- global $wpdb;
534
- $current_unix_time = current_time('timestamp', true);
535
- $previous_hour = $current_unix_time - 3600;
536
- AIOWPSecurity_Utility::is_multisite_install() ? $tbl = $wpdb->sitemeta : $tbl = $wpdb->prefix . 'options';
537
- $query = $wpdb->prepare("SELECT * FROM {$tbl} WHERE option_name LIKE 'aiowps_captcha_string_info_time_%' AND option_value < %s", $previous_hour);
538
- $res = $wpdb->get_results($query, ARRAY_A);
539
- if (!empty($res)) {
540
- foreach ($res as $item) {
541
- $option_name = $item['option_name'];
542
- if (AIOWPSecurity_Utility::is_multisite_install()) {
543
- delete_site_option($option_name);
544
- delete_site_option(str_replace('time_', '', $option_name));
545
- } else {
546
- delete_option($option_name);
547
- delete_option(str_replace('time_', '', $option_name));
548
- }
 
549
  }
550
  }
551
  }
 
552
 
553
  /**
554
  * Get server type.
@@ -619,5 +673,74 @@ class AIOWPSecurity_Utility {
619
  if ($chars_unmasked >= $str_length) return $str;
620
  return preg_replace("/(.{".$chars_unmasked."}$)(*SKIP)(*F)|(.)/u", "*", $str);
621
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
622
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
623
  }
4
  }
5
 
6
  class AIOWPSecurity_Utility {
7
+
8
+ /**
9
+ * Returned when we can't detect the user's server software
10
+ *
11
+ * @var int
12
+ */
13
+ const UNSUPPORTED_SERVER_TYPE = -1;
14
+
15
+ /**
16
+ * Class constructor
17
+ */
18
  public function __construct() {
19
  //NOP
20
  }
21
 
22
+ /**
23
+ * Check whether the current logged in user has the capability to manage the AIOWPS plugin
24
+ *
25
+ * @return Boolean True if the logged in user has capability to manage the AIOWPS plugin, otherwise false
26
+ */
27
+ public static function has_manage_cap() {
28
+ // This filter will useful when the administrator would like to give permission to access AIOWPS to Security Analyst.
29
+ $cap = apply_filters('aiowps_management_capability', AIOWPSEC_MANAGEMENT_PERMISSION);
30
+ return current_user_can($cap);
31
+ }
32
+
33
  /**
34
  * Explode $string with $delimiter, trim all lines and filter out empty ones.
35
  *
108
  }
109
 
110
  //If multisite
111
+ if (is_multisite()) {
112
  $blog_id = get_current_blog_id();
113
  $admin_users = get_users('blog_id=' . $blog_id . '&orderby=login&role=administrator');
114
  foreach ($admin_users as $user) {
227
  return "";
228
  }
229
 
 
 
 
 
 
 
 
 
 
230
  /**
231
  * This is a general yellow box message for when we want to suppress a feature's config items because site is subsite of multi-site
232
  */
383
  $referer_info = isset($_SERVER['HTTP_REFERER']) ? esc_attr($_SERVER['HTTP_REFERER']) : '';
384
  }
385
 
386
+ $current_time = current_time('mysql', true);
387
  $data = array(
388
  'event_type' => $event_type,
389
  'username' => $username,
414
  public static function check_locked_ip($ip) {
415
  global $wpdb;
416
  $login_lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
417
+ $now = current_time('mysql', true);
418
+ $locked_ip = $wpdb->get_row($wpdb->prepare("SELECT * FROM $login_lockdown_table WHERE release_date > %s AND failed_login_ip = %s", $now, $ip), ARRAY_A);
419
  if (null != $locked_ip) {
420
  return true;
421
  } else {
432
  public static function get_locked_ips() {
433
  global $wpdb;
434
  $login_lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
435
+ $now = current_time('mysql', true);
436
  $locked_ips = $wpdb->get_results($wpdb->prepare("SELECT * FROM $login_lockdown_table WHERE release_date > %s", $now), ARRAY_A);
437
 
438
  if (empty($locked_ips)) {
444
 
445
 
446
  /**
447
+ * Locks an IP address - Adds an entry to the AIOWPSEC_TBL_LOGIN_LOCKDOWN table.
448
  *
449
+ * @global wpdb $wpdb
450
+ * @global AIO_WP_Security $aio_wp_security
451
+ *
452
+ * @param String $ip
453
+ * @param String $lock_reason
454
+ * @param String $username
455
+ *
456
+ * @return Void
457
  */
458
+ public static function lock_IP($ip, $lock_reason, $username = '') {
459
  global $wpdb, $aio_wp_security;
460
  $login_lockdown_table = AIOWPSEC_TBL_LOGIN_LOCKDOWN;
461
+
462
+ if ('404' == $lock_reason) {
463
+ $lock_minutes = $aio_wp_security->configs->get_value('aiowps_404_lockout_time_length');
464
+ } else {
465
+ $lock_minutes = $aio_wp_security->user_login_obj->get_dynamic_lockout_time_length();
466
+ }
467
+
468
  $username = sanitize_user($username);
469
  $user = get_user_by('login', $username); //Returns WP_User object if exists
470
 
478
  $user_id = $user->ID;
479
  }
480
 
481
+ $ip = esc_sql($ip);
482
+
483
+ $lock_time = current_time('mysql', true);
484
+ $release_time = date('Y-m-d H:i:s', time() + ($lock_minutes * MINUTE_IN_SECONDS));
485
+
486
+ $data = array('user_id' => $user_id, 'user_login' => $username, 'lockdown_date' => $lock_time, 'release_date' => $release_time, 'failed_login_IP' => $ip, 'lock_reason' => $lock_reason);
487
+ $format = array('%d', '%s', '%s', '%s', '%s', '%s');
488
+ $result = $wpdb->insert($login_lockdown_table, $data, $format);
489
+
490
  if ($result > 0) {
491
  } elseif (false === $result) {
492
  $aio_wp_security->debug_logger->log_debug("lock_IP: Error inserting record into " . $login_lockdown_table, 4);//Log the highly unlikely event of DB error
502
  */
503
  public static function get_blog_ids() {
504
  global $wpdb;
505
+ if (is_multisite()) {
506
  global $wpdb;
507
  $blog_ids = $wpdb->get_col("SELECT blog_id FROM " . $wpdb->prefix . "blogs");
508
  } else {
511
  return $blog_ids;
512
  }
513
 
514
+ /**
515
+ * Purges old records of table
516
+ *
517
+ * @global type $wpdb WP Database object
518
+ * @global type $aio_wp_security AIO WP Security object
519
+ * @param type $table_name Table name
520
+ * @param type $purge_records_after_days Records after days to be deleted
521
+ * @param type $date_field Date field of table
522
+ * @return void
523
+ */
524
+ public static function purge_table_records($table_name, $purge_records_after_days, $date_field) {
525
+ global $wpdb, $aio_wp_security;
526
+
527
+ $older_than_date_time = date('Y-m-d H:m:s', strtotime('-' . $purge_records_after_days . ' days', current_time('timestamp', true)));
528
+ $sql = $wpdb->prepare('DELETE FROM ' . $table_name . ' WHERE '.$date_field.' < %s', $older_than_date_time);
529
+ $ret_deleted = $wpdb->query($sql);
530
+ if (false === $ret_deleted) {
531
+ $err_db = !empty($wpdb->last_error) ? ' ('.$wpdb->last_error.' - '.$wpdb->last_query.')' : '';
532
+ // Status level 4 indicates failure status.
533
+ $aio_wp_security->debug_logger->log_debug_cron('Purge records error - failed to purge older records for ' . $table_name . '.' . $err_db, 4);
534
+ } else {
535
+ $aio_wp_security->debug_logger->log_debug_cron(sprintf('Purge records - %d records were deleted for ' . $table_name . '.', $ret_deleted));
536
+ }
537
+ }
538
 
539
  /**
540
  * This function will delete the oldest rows from a table which are over the max amount of rows specified
541
  *
542
+ * @global type $wpdb WP Database object
543
+ * @global type $aio_wp_security AIO WP Security object
544
+ * @param type $table_name Table name
545
+ * @param type $max_rows More than max to be deleted
546
+ * @param type $id_field Primary field of table
547
  * @return bool
548
  */
549
+ public static function cleanup_table($table_name, $max_rows = '10000', $id_field = 'id') {
550
  global $wpdb, $aio_wp_security;
551
 
552
  $num_rows = $wpdb->get_var("select count(*) from $table_name");
553
  $result = true;
554
  if ($num_rows > $max_rows) {
555
  //if the table has more than max entries delete oldest rows
556
+
557
  $del_sql = "DELETE FROM $table_name
558
+ WHERE ".$id_field." <= (
559
+ SELECT ".$id_field."
560
  FROM (
561
+ SELECT ".$id_field."
562
  FROM $table_name
563
+ ORDER BY ".$id_field." DESC
564
  LIMIT 1 OFFSET $max_rows
565
  ) foo_tmp
566
  )";
581
  *
582
  * @global wpdb $wpdb
583
  */
584
+ public static function delete_expired_captcha_options() {
585
+ global $wpdb;
586
+ $current_unix_time = current_time('timestamp', true);
587
+ $previous_hour = $current_unix_time - 3600;
588
+ $tbl = is_multisite() ? $wpdb->sitemeta : $wpdb->prefix . 'options';
589
+ $key_name = is_multisite() ? 'meta_key' : 'option_name';
590
+ $key_val = is_multisite() ? 'meta_value' : 'option_value';
591
+ $query = $wpdb->prepare("SELECT * FROM {$tbl} WHERE {$key_name} LIKE 'aiowps_captcha_string_info_time_%' AND {$key_val} < %s", $previous_hour);
592
+ $res = $wpdb->get_results($query, ARRAY_A);
593
+ if (!empty($res)) {
594
+ foreach ($res as $item) {
595
+ $option_name = $item[$key_name];
596
+ if (is_multisite()) {
597
+ delete_site_option($option_name);
598
+ delete_site_option(str_replace('time_', '', $option_name));
599
+ } else {
600
+ delete_option($option_name);
601
+ delete_option(str_replace('time_', '', $option_name));
602
  }
603
  }
604
  }
605
+ }
606
 
607
  /**
608
  * Get server type.
673
  if ($chars_unmasked >= $str_length) return $str;
674
  return preg_replace("/(.{".$chars_unmasked."}$)(*SKIP)(*F)|(.)/u", "*", $str);
675
  }
676
+
677
+ /**
678
+ * Create a php backtrace log file for login lockdown email
679
+ *
680
+ * @param Array $logs
681
+ * @global AIO_WP_Security $aio_wp_security
682
+ * @return string
683
+ */
684
+ public static function login_lockdown_email_backtrace_log_file($logs = array()) {
685
+ global $aio_wp_security;
686
+ $temp_dir = get_temp_dir();
687
+ $backtrace_filename = wp_unique_filename($temp_dir, 'log_backtrace_' . time() . '.txt');
688
+ $backtrace_filepath = $temp_dir.$backtrace_filename;
689
+ if (count($logs) > 0) {
690
+ $dbg = "";
691
+ foreach ($logs as $log) {
692
+ $dbg.= "############ BACKTRACE STARTS ########\n";
693
+ $dbg.= $log['backtrace_log'];
694
+ $dbg.= "############ BACKTRACE ENDS ########\n\n";
695
+ }
696
+ } else {
697
+ $dbg = debug_backtrace();
698
+ }
699
+ $is_log_file_written = file_put_contents($backtrace_filepath, print_r($dbg, true));
700
+ if ($is_log_file_written) {
701
+ return $backtrace_filepath;
702
+ } else {
703
+ $aio_wp_security->debug_logger->log_debug("Error in writing php backtrace file " . $backtrace_filepath . " to attach in email.", 4);
704
+ return '';
705
+ }
706
+ }
707
 
708
+ /**
709
+ * Check whether the WooCommerce plugin is active.
710
+ *
711
+ * @return Boolean True if the WooCommerce plugin is active, otherwise false.
712
+ */
713
+ public static function is_woocommerce_plugin_active() {
714
+ return class_exists('WooCommerce');
715
+ }
716
+
717
+ /**
718
+ * Check whether incompatible TFA premium plugin version active.
719
+ *
720
+ * @return boolean True if the incompatible TFA premium plugin version active, otherwise false.
721
+ */
722
+ public static function is_incopatible_tfa_premium_version_active() {
723
+ if (!function_exists('get_plugins')) {
724
+ require_once(ABSPATH.'/wp-admin/includes/plugin.php');
725
+ }
726
+ foreach (get_plugins() as $plugin_slug => $plugin_info) {
727
+ if (is_plugin_active($plugin_slug) && strpos($plugin_slug, '/') && is_dir(WP_PLUGIN_DIR.'/'.explode('/', $plugin_slug)[0].'/simba-tfa/premium') && version_compare($plugin_info['Version'], AIOS_TFA_PREMIUM_LATEST_INCOMPATIBLE_VERSION, '<=')) {
728
+ return true;
729
+ }
730
+ }
731
+
732
+ return false;
733
+ }
734
+
735
+ /**
736
+ * Check whether TFA plugin activating.
737
+ *
738
+ * @return boolean True if the TFA plugin activating, otherwise false.
739
+ */
740
+ public static function is_tfa_or_self_plugin_activating() {
741
+ // The $GLOBALS['pagenow'] doesn't set in the network admin plugins page and it throws the warning "Notice: Undefined index: pagenow in ..." so we can't use it.
742
+ // https://core.trac.wordpress.org/ticket/42656
743
+ return is_admin() &&
744
+ preg_match('#/wp-admin/plugins.php$#i', $_SERVER['PHP_SELF']) && isset($_GET['plugin']) && (preg_match("/\/two-factor-login.php/", $_GET['plugin']) || preg_match("/all-in-one-wp-security-and-firewall/", $_GET['plugin']));
745
+ }
746
  }
classes/wp-security-wp-footer-content.php CHANGED
@@ -14,7 +14,7 @@ class AIOWPSecurity_WP_Footer_Content {
14
  if ($aio_wp_security->configs->get_value('aiowps_default_recaptcha')) {
15
  // For Woocommerce forms.
16
  // Only proceed if woocommerce installed and active
17
- if (in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')))) {
18
  if ($aio_wp_security->configs->get_value('aiowps_enable_woo_login_captcha') == '1' || $aio_wp_security->configs->get_value('aiowps_enable_woo_register_captcha') == '1' || $aio_wp_security->configs->get_value('aiowps_enable_woo_lostpassword_captcha') == '1') {
19
  $this->print_recaptcha_api_woo();
20
  }
14
  if ($aio_wp_security->configs->get_value('aiowps_default_recaptcha')) {
15
  // For Woocommerce forms.
16
  // Only proceed if woocommerce installed and active
17
+ if (AIOWPSecurity_Utility::is_woocommerce_plugin_active()) {
18
  if ($aio_wp_security->configs->get_value('aiowps_enable_woo_login_captcha') == '1' || $aio_wp_security->configs->get_value('aiowps_enable_woo_register_captcha') == '1' || $aio_wp_security->configs->get_value('aiowps_enable_woo_lostpassword_captcha') == '1') {
19
  $this->print_recaptcha_api_woo();
20
  }
css/wp-security-admin-styles.css CHANGED
@@ -412,4 +412,12 @@
412
  min-width: 17px;
413
  border: 1px solid #ccc;
414
  background: #f7f7f7;
 
 
 
 
 
 
 
 
415
  }
412
  min-width: 17px;
413
  border: 1px solid #ccc;
414
  background: #f7f7f7;
415
+ }
416
+
417
+ svg > g > g.google-visualization-tooltip {
418
+ pointer-events: none;
419
+ }
420
+
421
+ .wp-security_page_aiowpsec_settings h2, .wp-security_page_aiowpsec_settings #poststuff h2 {
422
+ padding-left: 0;
423
  }
includes/simba-tfa/includes/frontend-settings.js ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var tfa_query_leaving = false;
2
+
3
+ // Prevent accidental leaving if there are unsaved settings
4
+ window.onbeforeunload = function(e) {
5
+ if (tfa_query_leaving) {
6
+ e.returnValue = simba_tfa_frontend.ask;
7
+ return simba_tfa_frontend.ask;
8
+ }
9
+ }
10
+
11
+ jQuery(function($) {
12
+
13
+ $(".tfa_settings_form input, .tfa_settings_form textarea, .tfa_settings_form select" ).on('change', function() {
14
+ tfa_query_leaving = true;
15
+ });
16
+
17
+ $(".tfa_settings_form input[name='simbatfa_delivery_type']").on('change', function() {
18
+ $(".tfa_third_party_holder").slideToggle();
19
+ });
20
+
21
+ $(".simbatfa_settings_save").on('click', function() {
22
+
23
+ $.blockUI({ message: '<div style="margin: 8px;font-size:150%;">'+simba_tfa_frontend.saving+'</div>' });
24
+
25
+ // https://stackoverflow.com/questions/10147149/how-can-i-override-jquerys-serialize-to-include-unchecked-checkboxes
26
+ var form_data = $('.tfa_settings_form input, .tfa_settings_form textarea, .tfa_settings_form select').serialize();
27
+
28
+ // Include unchecked checkboxes. Use filter to only include unchecked boxes.
29
+ $.each($('.tfa_settings_form input[type=checkbox]')
30
+ .filter(function(idx) {
31
+ return $(this).prop('checked') === false
32
+ }),
33
+ function(idx, el){
34
+ // attach matched element names to the form_data with a chosen value.
35
+ var emptyVal = '0';
36
+ form_data += '&' + $(el).attr('name') + '=' + emptyVal;
37
+ }
38
+ );
39
+
40
+ $.post(simba_tfa_frontend.ajax_url, {
41
+ action: 'tfa_frontend',
42
+ subaction: 'savesettings',
43
+ settings: form_data,
44
+ nonce: simba_tfa_frontend.nonce
45
+ }, function(response) {
46
+ var settings_saved = false;
47
+ try {
48
+ var resp = JSON.parse(response);
49
+ if (resp.hasOwnProperty('result')) {
50
+ settings_saved = true;
51
+ tfa_query_leaving = false;
52
+ // Allow user code to respond
53
+ $(document).trigger('tfa_settings_saved', resp);
54
+ }
55
+
56
+ if (resp.hasOwnProperty('message')) {
57
+ alert(resp.message);
58
+ }
59
+
60
+ if (resp.hasOwnProperty('qr')) {
61
+ $('.simbaotp_qr_container').data('qrcode', resp['qr']).empty().qrcode({
62
+ "render": "image",
63
+ "text": resp['qr'],
64
+ });
65
+ }
66
+
67
+ if (resp.hasOwnProperty('al_type_disp')) {
68
+ $("#al_type_name").html(resp['al_type_disp']['disp']);
69
+ $("#al_type_desc").html(resp['al_type_disp']['desc']);
70
+ }
71
+
72
+ } catch(err) {
73
+ console.log(err);
74
+ console.log(response);
75
+ if ('' === simba_tfa_frontend.also_try) {
76
+ alert(simba_tfa_frontend.response+response);
77
+ }
78
+ }
79
+ if ('' != simba_tfa_frontend.also_try) {
80
+ if (!settings_saved) {
81
+ $.post(simba_tfa_frontend.also_try, {
82
+ action: 'tfa_frontend',
83
+ subaction: 'savesettings',
84
+ settings: form_data,
85
+ nonce: simba_tfa_frontend.nonce
86
+ }, function(response) {
87
+
88
+ try {
89
+ var resp = JSON.parse(response);
90
+ if (resp.hasOwnProperty('result')) {
91
+ settings_saved = true;
92
+ tfa_query_leaving = false;
93
+ // Allow user code to respond
94
+ $(document).trigger('tfa_settings_saved', resp);
95
+ }
96
+ if (resp.hasOwnProperty('message')) {
97
+ alert(resp.message);
98
+ }
99
+ if (resp.hasOwnProperty('qr')) {
100
+ $('.simbaotp_qr_container').data('qrcode', resp['qr']).empty().qrcode({
101
+ "render": "image",
102
+ "text": resp['qr'],
103
+ });
104
+ }
105
+ if (resp.hasOwnProperty('al_type_disp')) {
106
+ $("#al_type_name").html(resp['al_type_disp']['disp']);
107
+ $("#al_type_desc").html(resp['al_type_disp']['desc']);
108
+ }
109
+
110
+ } catch(err) {
111
+ console.log(err);
112
+ console.log(response);
113
+ alert(simba_tfa_frontend.response+response);
114
+ }
115
+ $.unblockUI();
116
+ });
117
+ } else {
118
+ $.unblockUI();
119
+ }
120
+ } else {
121
+ $.unblockUI();
122
+ }
123
+ });
124
+
125
+ });
126
+ });
includes/simba-tfa/includes/jquery-qrcode/README.md ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # jQuery.qrcode
2
+
3
+ [![license][license-img]][github] [![web][web-img]][web] [![github][github-img]][github] [![bower][bower-img]][github]
4
+
5
+ jQuery plugin to dynamically generate QR codes. Uses [QR Code Generator][qrcode] (MIT).
6
+
7
+
8
+ ## License
9
+ The MIT License (MIT)
10
+
11
+ Copyright (c) 2016 Lars Jung (https://larsjung.de)
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in
21
+ all copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29
+ THE SOFTWARE.
30
+
31
+
32
+ [web]: https://larsjung.de/qrcode/
33
+ [github]: https://github.com/lrsjng/jquery-qrcode
34
+
35
+ [license-img]: https://img.shields.io/badge/license-MIT-a0a060.svg?style=flat-square
36
+ [web-img]: https://img.shields.io/badge/web-larsjung.de/qrcode-a0a060.svg?style=flat-square
37
+ [github-img]: https://img.shields.io/badge/github-lrsjng/jquery--qrcode-a0a060.svg?style=flat-square
38
+ [bower-img]: https://img.shields.io/badge/bower-lrsjng/jquery--qrcode-a0a060.svg?style=flat-square
39
+
40
+ [qrcode]: https://github.com/kazuhikoarase/qrcode-generator
includes/simba-tfa/includes/jquery-qrcode/jquery-qrcode.js ADDED
@@ -0,0 +1,2332 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*! jquery-qrcode v0.14.0 - https://larsjung.de/jquery-qrcode/ */
2
+ (function (vendor_qrcode) {
3
+ 'use strict';
4
+
5
+ var jq = window.jQuery;
6
+
7
+ // Check if canvas is available in the browser (as Modernizr does)
8
+ var hasCanvas = (function () {
9
+ var elem = document.createElement('canvas');
10
+ return !!(elem.getContext && elem.getContext('2d'));
11
+ }());
12
+
13
+ // Wrapper for the original QR code generator.
14
+ function createQRCode(text, level, version, quiet) {
15
+ var qr = {};
16
+
17
+ var vqr = vendor_qrcode(version, level);
18
+ vqr.addData(text);
19
+ vqr.make();
20
+
21
+ quiet = quiet || 0;
22
+
23
+ var qrModuleCount = vqr.getModuleCount();
24
+ var quietModuleCount = vqr.getModuleCount() + 2 * quiet;
25
+
26
+ function isDark(row, col) {
27
+ row -= quiet;
28
+ col -= quiet;
29
+
30
+ if (row < 0 || row >= qrModuleCount || col < 0 || col >= qrModuleCount) {
31
+ return false;
32
+ }
33
+ return vqr.isDark(row, col);
34
+ }
35
+
36
+ function addBlank(l, t, r, b) {
37
+ var prevIsDark = qr.isDark;
38
+ var moduleSize = 1 / quietModuleCount;
39
+
40
+ qr.isDark = function (row, col) {
41
+ var ml = col * moduleSize;
42
+ var mt = row * moduleSize;
43
+ var mr = ml + moduleSize;
44
+ var mb = mt + moduleSize;
45
+
46
+ return prevIsDark(row, col) && (l > mr || ml > r || t > mb || mt > b);
47
+ };
48
+ }
49
+
50
+ qr.text = text;
51
+ qr.level = level;
52
+ qr.version = version;
53
+ qr.moduleCount = quietModuleCount;
54
+ qr.isDark = isDark;
55
+ qr.addBlank = addBlank;
56
+
57
+ return qr;
58
+ }
59
+
60
+ // Returns a minimal QR code for the given text starting with version `minVersion`.
61
+ // Returns `undefined` if `text` is too long to be encoded in `maxVersion`.
62
+ function createMinQRCode(text, level, minVersion, maxVersion, quiet) {
63
+ minVersion = Math.max(1, minVersion || 1);
64
+ maxVersion = Math.min(40, maxVersion || 40);
65
+ for (var version = minVersion; version <= maxVersion; version += 1) {
66
+ try {
67
+ return createQRCode(text, level, version, quiet);
68
+ } catch (err) {/* empty */}
69
+ }
70
+ return undefined;
71
+ }
72
+
73
+ function drawBackgroundLabel(qr, context, settings) {
74
+ var size = settings.size;
75
+ var font = 'bold ' + settings.mSize * size + 'px ' + settings.fontname;
76
+ var ctx = jq('<canvas/>')[0].getContext('2d');
77
+
78
+ ctx.font = font;
79
+
80
+ var w = ctx.measureText(settings.label).width;
81
+ var sh = settings.mSize;
82
+ var sw = w / size;
83
+ var sl = (1 - sw) * settings.mPosX;
84
+ var st = (1 - sh) * settings.mPosY;
85
+ var sr = sl + sw;
86
+ var sb = st + sh;
87
+ var pad = 0.01;
88
+
89
+ if (settings.mode === 1) {
90
+ // Strip
91
+ qr.addBlank(0, st - pad, size, sb + pad);
92
+ } else {
93
+ // Box
94
+ qr.addBlank(sl - pad, st - pad, sr + pad, sb + pad);
95
+ }
96
+
97
+ context.fillStyle = settings.fontcolor;
98
+ context.font = font;
99
+ context.fillText(settings.label, sl * size, st * size + 0.75 * settings.mSize * size);
100
+ }
101
+
102
+ function drawBackgroundImage(qr, context, settings) {
103
+ var size = settings.size;
104
+ var w = settings.image.naturalWidth || 1;
105
+ var h = settings.image.naturalHeight || 1;
106
+ var sh = settings.mSize;
107
+ var sw = sh * w / h;
108
+ var sl = (1 - sw) * settings.mPosX;
109
+ var st = (1 - sh) * settings.mPosY;
110
+ var sr = sl + sw;
111
+ var sb = st + sh;
112
+ var pad = 0.01;
113
+
114
+ if (settings.mode === 3) {
115
+ // Strip
116
+ qr.addBlank(0, st - pad, size, sb + pad);
117
+ } else {
118
+ // Box
119
+ qr.addBlank(sl - pad, st - pad, sr + pad, sb + pad);
120
+ }
121
+
122
+ context.drawImage(settings.image, sl * size, st * size, sw * size, sh * size);
123
+ }
124
+
125
+ function drawBackground(qr, context, settings) {
126
+ if (jq(settings.background).is('img')) {
127
+ context.drawImage(settings.background, 0, 0, settings.size, settings.size);
128
+ } else if (settings.background) {
129
+ context.fillStyle = settings.background;
130
+ context.fillRect(settings.left, settings.top, settings.size, settings.size);
131
+ }
132
+
133
+ var mode = settings.mode;
134
+ if (mode === 1 || mode === 2) {
135
+ drawBackgroundLabel(qr, context, settings);
136
+ } else if (mode === 3 || mode === 4) {
137
+ drawBackgroundImage(qr, context, settings);
138
+ }
139
+ }
140
+
141
+ function drawModuleDefault(qr, context, settings, left, top, width, row, col) {
142
+ if (qr.isDark(row, col)) {
143
+ context.rect(left, top, width, width);
144
+ }
145
+ }
146
+
147
+ function drawModuleRoundedDark(ctx, l, t, r, b, rad, nw, ne, se, sw) {
148
+ if (nw) {
149
+ ctx.moveTo(l + rad, t);
150
+ } else {
151
+ ctx.moveTo(l, t);
152
+ }
153
+
154
+ if (ne) {
155
+ ctx.lineTo(r - rad, t);
156
+ ctx.arcTo(r, t, r, b, rad);
157
+ } else {
158
+ ctx.lineTo(r, t);
159
+ }
160
+
161
+ if (se) {
162
+ ctx.lineTo(r, b - rad);
163
+ ctx.arcTo(r, b, l, b, rad);
164
+ } else {
165
+ ctx.lineTo(r, b);
166
+ }
167
+
168
+ if (sw) {
169
+ ctx.lineTo(l + rad, b);
170
+ ctx.arcTo(l, b, l, t, rad);
171
+ } else {
172
+ ctx.lineTo(l, b);
173
+ }
174
+
175
+ if (nw) {
176
+ ctx.lineTo(l, t + rad);
177
+ ctx.arcTo(l, t, r, t, rad);
178
+ } else {
179
+ ctx.lineTo(l, t);
180
+ }
181
+ }
182
+
183
+ function drawModuleRoundendLight(ctx, l, t, r, b, rad, nw, ne, se, sw) {
184
+ if (nw) {
185
+ ctx.moveTo(l + rad, t);
186
+ ctx.lineTo(l, t);
187
+ ctx.lineTo(l, t + rad);
188
+ ctx.arcTo(l, t, l + rad, t, rad);
189
+ }
190
+
191
+ if (ne) {
192
+ ctx.moveTo(r - rad, t);
193
+ ctx.lineTo(r, t);
194
+ ctx.lineTo(r, t + rad);
195
+ ctx.arcTo(r, t, r - rad, t, rad);
196
+ }
197
+
198
+ if (se) {
199
+ ctx.moveTo(r - rad, b);
200
+ ctx.lineTo(r, b);
201
+ ctx.lineTo(r, b - rad);
202
+ ctx.arcTo(r, b, r - rad, b, rad);
203
+ }
204
+
205
+ if (sw) {
206
+ ctx.moveTo(l + rad, b);
207
+ ctx.lineTo(l, b);
208
+ ctx.lineTo(l, b - rad);
209
+ ctx.arcTo(l, b, l + rad, b, rad);
210
+ }
211
+ }
212
+
213
+ function drawModuleRounded(qr, context, settings, left, top, width, row, col) {
214
+ var isDark = qr.isDark;
215
+ var right = left + width;
216
+ var bottom = top + width;
217
+ var radius = settings.radius * width;
218
+ var rowT = row - 1;
219
+ var rowB = row + 1;
220
+ var colL = col - 1;
221
+ var colR = col + 1;
222
+ var center = isDark(row, col);
223
+ var northwest = isDark(rowT, colL);
224
+ var north = isDark(rowT, col);
225
+ var northeast = isDark(rowT, colR);
226
+ var east = isDark(row, colR);
227
+ var southeast = isDark(rowB, colR);
228
+ var south = isDark(rowB, col);
229
+ var southwest = isDark(rowB, colL);
230
+ var west = isDark(row, colL);
231
+
232
+ if (center) {
233
+ drawModuleRoundedDark(context, left, top, right, bottom, radius, !north && !west, !north && !east, !south && !east, !south && !west);
234
+ } else {
235
+ drawModuleRoundendLight(context, left, top, right, bottom, radius, north && west && northwest, north && east && northeast, south && east && southeast, south && west && southwest);
236
+ }
237
+ }
238
+
239
+ function drawModules(qr, context, settings) {
240
+ var moduleCount = qr.moduleCount;
241
+ var moduleSize = settings.size / moduleCount;
242
+ var fn = drawModuleDefault;
243
+ var row;
244
+ var col;
245
+
246
+ if (settings.radius > 0 && settings.radius <= 0.5) {
247
+ fn = drawModuleRounded;
248
+ }
249
+
250
+ context.beginPath();
251
+ for (row = 0; row < moduleCount; row += 1) {
252
+ for (col = 0; col < moduleCount; col += 1) {
253
+ var l = settings.left + col * moduleSize;
254
+ var t = settings.top + row * moduleSize;
255
+ var w = moduleSize;
256
+
257
+ fn(qr, context, settings, l, t, w, row, col);
258
+ }
259
+ }
260
+ if (jq(settings.fill).is('img')) {
261
+ context.strokeStyle = 'rgba(0,0,0,0.5)';
262
+ context.lineWidth = 2;
263
+ context.stroke();
264
+ var prev = context.globalCompositeOperation;
265
+ context.globalCompositeOperation = 'destination-out';
266
+ context.fill();
267
+ context.globalCompositeOperation = prev;
268
+
269
+ context.clip();
270
+ context.drawImage(settings.fill, 0, 0, settings.size, settings.size);
271
+ context.restore();
272
+ } else {
273
+ context.fillStyle = settings.fill;
274
+ context.fill();
275
+ }
276
+ }
277
+
278
+ // Draws QR code to the given `canvas` and returns it.
279
+ function drawOnCanvas(canvas, settings) {
280
+ var qr = createMinQRCode(settings.text, settings.ecLevel, settings.minVersion, settings.maxVersion, settings.quiet);
281
+ if (!qr) {
282
+ return null;
283
+ }
284
+
285
+ var $canvas = jq(canvas).data('qrcode', qr);
286
+ var context = $canvas[0].getContext('2d');
287
+
288
+ drawBackground(qr, context, settings);
289
+ drawModules(qr, context, settings);
290
+
291
+ return $canvas;
292
+ }
293
+
294
+ // Returns a `canvas` element representing the QR code for the given settings.
295
+ function createCanvas(settings) {
296
+ var $canvas = jq('<canvas/>').attr('width', settings.size).attr('height', settings.size);
297
+ return drawOnCanvas($canvas, settings);
298
+ }
299
+
300
+ // Returns an `image` element representing the QR code for the given settings.
301
+ function createImage(settings) {
302
+ return jq('<img/>').attr('src', createCanvas(settings)[0].toDataURL('image/png'));
303
+ }
304
+
305
+ // Returns a `div` element representing the QR code for the given settings.
306
+ function createDiv(settings) {
307
+ var qr = createMinQRCode(settings.text, settings.ecLevel, settings.minVersion, settings.maxVersion, settings.quiet);
308
+ if (!qr) {
309
+ return null;
310
+ }
311
+
312
+ // some shortcuts to improve compression
313
+ var settings_size = settings.size;
314
+ var settings_bgColor = settings.background;
315
+ var math_floor = Math.floor;
316
+
317
+ var moduleCount = qr.moduleCount;
318
+ var moduleSize = math_floor(settings_size / moduleCount);
319
+ var offset = math_floor(0.5 * (settings_size - moduleSize * moduleCount));
320
+
321
+ var row;
322
+ var col;
323
+
324
+ var containerCSS = {
325
+ position: 'relative',
326
+ left: 0,
327
+ top: 0,
328
+ padding: 0,
329
+ margin: 0,
330
+ width: settings_size,
331
+ height: settings_size
332
+ };
333
+ var darkCSS = {
334
+ position: 'absolute',
335
+ padding: 0,
336
+ margin: 0,
337
+ width: moduleSize,
338
+ height: moduleSize,
339
+ 'background-color': settings.fill
340
+ };
341
+
342
+ var $div = jq('<div/>').data('qrcode', qr).css(containerCSS);
343
+
344
+ if (settings_bgColor) {
345
+ $div.css('background-color', settings_bgColor);
346
+ }
347
+
348
+ for (row = 0; row < moduleCount; row += 1) {
349
+ for (col = 0; col < moduleCount; col += 1) {
350
+ if (qr.isDark(row, col)) {
351
+ jq('<div/>')
352
+ .css(darkCSS)
353
+ .css({
354
+ left: offset + col * moduleSize,
355
+ top: offset + row * moduleSize
356
+ })
357
+ .appendTo($div);
358
+ }
359
+ }
360
+ }
361
+
362
+ return $div;
363
+ }
364
+
365
+ function createHTML(settings) {
366
+ if (hasCanvas && settings.render === 'canvas') {
367
+ return createCanvas(settings);
368
+ } else if (hasCanvas && settings.render === 'image') {
369
+ return createImage(settings);
370
+ }
371
+
372
+ return createDiv(settings);
373
+ }
374
+
375
+ // Plugin
376
+ // ======
377
+
378
+ // Default settings
379
+ // ----------------
380
+ var defaults = {
381
+ // render method: `'canvas'`, `'image'` or `'div'`
382
+ render: 'canvas',
383
+
384
+ // version range somewhere in 1 .. 40
385
+ minVersion: 1,
386
+ maxVersion: 40,
387
+
388
+ // error correction level: `'L'`, `'M'`, `'Q'` or `'H'`
389
+ ecLevel: 'L',
390
+
391
+ // offset in pixel if drawn onto existing canvas
392
+ left: 0,
393
+ top: 0,
394
+
395
+ // size in pixel
396
+ size: 200,
397
+
398
+ // code color or image element
399
+ fill: '#000',
400
+
401
+ // background color or image element, `null` for transparent background
402
+ background: null,
403
+
404
+ // content
405
+ text: 'no text',
406
+
407
+ // corner radius relative to module width: 0.0 .. 0.5
408
+ radius: 0,
409
+
410
+ // quiet zone in modules
411
+ quiet: 0,
412
+
413
+ // modes
414
+ // 0: normal
415
+ // 1: label strip
416
+ // 2: label box
417
+ // 3: image strip
418
+ // 4: image box
419
+ mode: 0,
420
+
421
+ mSize: 0.1,
422
+ mPosX: 0.5,
423
+ mPosY: 0.5,
424
+
425
+ label: 'no label',
426
+ fontname: 'sans',
427
+ fontcolor: '#000',
428
+
429
+ image: null
430
+ };
431
+
432
+ // Register the plugin
433
+ // -------------------
434
+ jq.fn.qrcode = function (options) {
435
+ var settings = jq.extend({}, defaults, options);
436
+
437
+ return this.each(function (idx, el) {
438
+ if (el.nodeName.toLowerCase() === 'canvas') {
439
+ drawOnCanvas(el, settings);
440
+ } else {
441
+ jq(el).append(createHTML(settings));
442
+ }
443
+ });
444
+ };
445
+ }(function () {
446
+ // `qrcode` is the single public function defined by the `QR Code Generator`
447
+ //---------------------------------------------------------------------
448
+ //
449
+ // QR Code Generator for JavaScript
450
+ //
451
+ // Copyright (c) 2009 Kazuhiko Arase
452
+ //
453
+ // URL: http://www.d-project.com/
454
+ //
455
+ // Licensed under the MIT license:
456
+ // http://www.opensource.org/licenses/mit-license.php
457
+ //
458
+ // The word 'QR Code' is registered trademark of
459
+ // DENSO WAVE INCORPORATED
460
+ // http://www.denso-wave.com/qrcode/faqpatent-e.html
461
+ //
462
+ //---------------------------------------------------------------------
463
+
464
+ var qrcode = function() {
465
+
466
+ //---------------------------------------------------------------------
467
+ // qrcode
468
+ //---------------------------------------------------------------------
469
+
470
+ /**
471
+ * qrcode
472
+ * @param typeNumber 1 to 40
473
+ * @param errorCorrectLevel 'L','M','Q','H'
474
+ */
475
+ var qrcode = function(typeNumber, errorCorrectLevel) {
476
+
477
+ var PAD0 = 0xEC;
478
+ var PAD1 = 0x11;
479
+
480
+ var _typeNumber = typeNumber;
481
+ var _errorCorrectLevel = QRErrorCorrectLevel[errorCorrectLevel];
482
+ var _modules = null;
483
+ var _moduleCount = 0;
484
+ var _dataCache = null;
485
+ var _dataList = new Array();
486
+
487
+ var _this = {};
488
+
489
+ var makeImpl = function(test, maskPattern) {
490
+
491
+ _moduleCount = _typeNumber * 4 + 17;
492
+ _modules = function(moduleCount) {
493
+ var modules = new Array(moduleCount);
494
+ for (var row = 0; row < moduleCount; row += 1) {
495
+ modules[row] = new Array(moduleCount);
496
+ for (var col = 0; col < moduleCount; col += 1) {
497
+ modules[row][col] = null;
498
+ }
499
+ }
500
+ return modules;
501
+ }(_moduleCount);
502
+
503
+ setupPositionProbePattern(0, 0);
504
+ setupPositionProbePattern(_moduleCount - 7, 0);
505
+ setupPositionProbePattern(0, _moduleCount - 7);
506
+ setupPositionAdjustPattern();
507
+ setupTimingPattern();
508
+ setupTypeInfo(test, maskPattern);
509
+
510
+ if (_typeNumber >= 7) {
511
+ setupTypeNumber(test);
512
+ }
513
+
514
+ if (_dataCache == null) {
515
+ _dataCache = createData(_typeNumber, _errorCorrectLevel, _dataList);
516
+ }
517
+
518
+ mapData(_dataCache, maskPattern);
519
+ };
520
+
521
+ var setupPositionProbePattern = function(row, col) {
522
+
523
+ for (var r = -1; r <= 7; r += 1) {
524
+
525
+ if (row + r <= -1 || _moduleCount <= row + r) continue;
526
+
527
+ for (var c = -1; c <= 7; c += 1) {
528
+
529
+ if (col + c <= -1 || _moduleCount <= col + c) continue;
530
+
531
+ if ( (0 <= r && r <= 6 && (c == 0 || c == 6) )
532
+ || (0 <= c && c <= 6 && (r == 0 || r == 6) )
533
+ || (2 <= r && r <= 4 && 2 <= c && c <= 4) ) {
534
+ _modules[row + r][col + c] = true;
535
+ } else {
536
+ _modules[row + r][col + c] = false;
537
+ }
538
+ }
539
+ }
540
+ };
541
+
542
+ var getBestMaskPattern = function() {
543
+
544
+ var minLostPoint = 0;
545
+ var pattern = 0;
546
+
547
+ for (var i = 0; i < 8; i += 1) {
548
+
549
+ makeImpl(true, i);
550
+
551
+ var lostPoint = QRUtil.getLostPoint(_this);
552
+
553
+ if (i == 0 || minLostPoint > lostPoint) {
554
+ minLostPoint = lostPoint;
555
+ pattern = i;
556
+ }
557
+ }
558
+
559
+ return pattern;
560
+ };
561
+
562
+ var setupTimingPattern = function() {
563
+
564
+ for (var r = 8; r < _moduleCount - 8; r += 1) {
565
+ if (_modules[r][6] != null) {
566
+ continue;
567
+ }
568
+ _modules[r][6] = (r % 2 == 0);
569
+ }
570
+
571
+ for (var c = 8; c < _moduleCount - 8; c += 1) {
572
+ if (_modules[6][c] != null) {
573
+ continue;
574
+ }
575
+ _modules[6][c] = (c % 2 == 0);
576
+ }
577
+ };
578
+
579
+ var setupPositionAdjustPattern = function() {
580
+
581
+ var pos = QRUtil.getPatternPosition(_typeNumber);
582
+
583
+ for (var i = 0; i < pos.length; i += 1) {
584
+
585
+ for (var j = 0; j < pos.length; j += 1) {
586
+
587
+ var row = pos[i];
588
+ var col = pos[j];
589
+
590
+ if (_modules[row][col] != null) {
591
+ continue;
592
+ }
593
+
594
+ for (var r = -2; r <= 2; r += 1) {
595
+
596
+ for (var c = -2; c <= 2; c += 1) {
597
+
598
+ if (r == -2 || r == 2 || c == -2 || c == 2
599
+ || (r == 0 && c == 0) ) {
600
+ _modules[row + r][col + c] = true;
601
+ } else {
602
+ _modules[row + r][col + c] = false;
603
+ }
604
+ }
605
+ }
606
+ }
607
+ }
608
+ };
609
+
610
+ var setupTypeNumber = function(test) {
611
+
612
+ var bits = QRUtil.getBCHTypeNumber(_typeNumber);
613
+
614
+ for (var i = 0; i < 18; i += 1) {
615
+ var mod = (!test && ( (bits >> i) & 1) == 1);
616
+ _modules[Math.floor(i / 3)][i % 3 + _moduleCount - 8 - 3] = mod;
617
+ }
618
+
619
+ for (var i = 0; i < 18; i += 1) {
620
+ var mod = (!test && ( (bits >> i) & 1) == 1);
621
+ _modules[i % 3 + _moduleCount - 8 - 3][Math.floor(i / 3)] = mod;
622
+ }
623
+ };
624
+
625
+ var setupTypeInfo = function(test, maskPattern) {
626
+
627
+ var data = (_errorCorrectLevel << 3) | maskPattern;
628
+ var bits = QRUtil.getBCHTypeInfo(data);
629
+
630
+ // vertical
631
+ for (var i = 0; i < 15; i += 1) {
632
+
633
+ var mod = (!test && ( (bits >> i) & 1) == 1);
634
+
635
+ if (i < 6) {
636
+ _modules[i][8] = mod;
637
+ } else if (i < 8) {
638
+ _modules[i + 1][8] = mod;
639
+ } else {
640
+ _modules[_moduleCount - 15 + i][8] = mod;
641
+ }
642
+ }
643
+
644
+ // horizontal
645
+ for (var i = 0; i < 15; i += 1) {
646
+
647
+ var mod = (!test && ( (bits >> i) & 1) == 1);
648
+
649
+ if (i < 8) {
650
+ _modules[8][_moduleCount - i - 1] = mod;
651
+ } else if (i < 9) {
652
+ _modules[8][15 - i - 1 + 1] = mod;
653
+ } else {
654
+ _modules[8][15 - i - 1] = mod;
655
+ }
656
+ }
657
+
658
+ // fixed module
659
+ _modules[_moduleCount - 8][8] = (!test);
660
+ };
661
+
662
+ var mapData = function(data, maskPattern) {
663
+
664
+ var inc = -1;
665
+ var row = _moduleCount - 1;
666
+ var bitIndex = 7;
667
+ var byteIndex = 0;
668
+ var maskFunc = QRUtil.getMaskFunction(maskPattern);
669
+
670
+ for (var col = _moduleCount - 1; col > 0; col -= 2) {
671
+
672
+ if (col == 6) col -= 1;
673
+
674
+ while (true) {
675
+
676
+ for (var c = 0; c < 2; c += 1) {
677
+
678
+ if (_modules[row][col - c] == null) {
679
+
680
+ var dark = false;
681
+
682
+ if (byteIndex < data.length) {
683
+ dark = ( ( (data[byteIndex] >>> bitIndex) & 1) == 1);
684
+ }
685
+
686
+ var mask = maskFunc(row, col - c);
687
+
688
+ if (mask) {
689
+ dark = !dark;
690
+ }
691
+
692
+ _modules[row][col - c] = dark;
693
+ bitIndex -= 1;
694
+
695
+ if (bitIndex == -1) {
696
+ byteIndex += 1;
697
+ bitIndex = 7;
698
+ }
699
+ }
700
+ }
701
+
702
+ row += inc;
703
+
704
+ if (row < 0 || _moduleCount <= row) {
705
+ row -= inc;
706
+ inc = -inc;
707
+ break;
708
+ }
709
+ }
710
+ }
711
+ };
712
+
713
+ var createBytes = function(buffer, rsBlocks) {
714
+
715
+ var offset = 0;
716
+
717
+ var maxDcCount = 0;
718
+ var maxEcCount = 0;
719
+
720
+ var dcdata = new Array(rsBlocks.length);
721
+ var ecdata = new Array(rsBlocks.length);
722
+
723
+ for (var r = 0; r < rsBlocks.length; r += 1) {
724
+
725
+ var dcCount = rsBlocks[r].dataCount;
726
+ var ecCount = rsBlocks[r].totalCount - dcCount;
727
+
728
+ maxDcCount = Math.max(maxDcCount, dcCount);
729
+ maxEcCount = Math.max(maxEcCount, ecCount);
730
+
731
+ dcdata[r] = new Array(dcCount);
732
+
733
+ for (var i = 0; i < dcdata[r].length; i += 1) {
734
+ dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset];
735
+ }
736
+ offset += dcCount;
737
+
738
+ var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount);
739
+ var rawPoly = qrPolynomial(dcdata[r], rsPoly.getLength() - 1);
740
+
741
+ var modPoly = rawPoly.mod(rsPoly);
742
+ ecdata[r] = new Array(rsPoly.getLength() - 1);
743
+ for (var i = 0; i < ecdata[r].length; i += 1) {
744
+ var modIndex = i + modPoly.getLength() - ecdata[r].length;
745
+ ecdata[r][i] = (modIndex >= 0)? modPoly.getAt(modIndex) : 0;
746
+ }
747
+ }
748
+
749
+ var totalCodeCount = 0;
750
+ for (var i = 0; i < rsBlocks.length; i += 1) {
751
+ totalCodeCount += rsBlocks[i].totalCount;
752
+ }
753
+
754
+ var data = new Array(totalCodeCount);
755
+ var index = 0;
756
+
757
+ for (var i = 0; i < maxDcCount; i += 1) {
758
+ for (var r = 0; r < rsBlocks.length; r += 1) {
759
+ if (i < dcdata[r].length) {
760
+ data[index] = dcdata[r][i];
761
+ index += 1;
762
+ }
763
+ }
764
+ }
765
+
766
+ for (var i = 0; i < maxEcCount; i += 1) {
767
+ for (var r = 0; r < rsBlocks.length; r += 1) {
768
+ if (i < ecdata[r].length) {
769
+ data[index] = ecdata[r][i];
770
+ index += 1;
771
+ }
772
+ }
773
+ }
774
+
775
+ return data;
776
+ };
777
+
778
+ var createData = function(typeNumber, errorCorrectLevel, dataList) {
779
+
780
+ var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel);
781
+
782
+ var buffer = qrBitBuffer();
783
+
784
+ for (var i = 0; i < dataList.length; i += 1) {
785
+ var data = dataList[i];
786
+ buffer.put(data.getMode(), 4);
787
+ buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber) );
788
+ data.write(buffer);
789
+ }
790
+
791
+ // calc num max data.
792
+ var totalDataCount = 0;
793
+ for (var i = 0; i < rsBlocks.length; i += 1) {
794
+ totalDataCount += rsBlocks[i].dataCount;
795
+ }
796
+
797
+ if (buffer.getLengthInBits() > totalDataCount * 8) {
798
+ throw new Error('code length overflow. ('
799
+ + buffer.getLengthInBits()
800
+ + '>'
801
+ + totalDataCount * 8
802
+ + ')');
803
+ }
804
+
805
+ // end code
806
+ if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) {
807
+ buffer.put(0, 4);
808
+ }
809
+
810
+ // padding
811
+ while (buffer.getLengthInBits() % 8 != 0) {
812
+ buffer.putBit(false);
813
+ }
814
+
815
+ // padding
816
+ while (true) {
817
+
818
+ if (buffer.getLengthInBits() >= totalDataCount * 8) {
819
+ break;
820
+ }
821
+ buffer.put(PAD0, 8);
822
+
823
+ if (buffer.getLengthInBits() >= totalDataCount * 8) {
824
+ break;
825
+ }
826
+ buffer.put(PAD1, 8);
827
+ }
828
+
829
+ return createBytes(buffer, rsBlocks);
830
+ };
831
+
832
+ _this.addData = function(data) {
833
+ var newData = qr8BitByte(data);
834
+ _dataList.push(newData);
835
+ _dataCache = null;
836
+ };
837
+
838
+ _this.isDark = function(row, col) {
839
+ if (row < 0 || _moduleCount <= row || col < 0 || _moduleCount <= col) {
840
+ throw new Error(row + ',' + col);
841
+ }
842
+ return _modules[row][col];
843
+ };
844
+
845
+ _this.getModuleCount = function() {
846
+ return _moduleCount;
847
+ };
848
+
849
+ _this.make = function() {
850
+ makeImpl(false, getBestMaskPattern() );
851
+ };
852
+
853
+ _this.createTableTag = function(cellSize, margin) {
854
+
855
+ cellSize = cellSize || 2;
856
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
857
+
858
+ var qrHtml = '';
859
+
860
+ qrHtml += '<table style="';
861
+ qrHtml += ' border-width: 0px; border-style: none;';
862
+ qrHtml += ' border-collapse: collapse;';
863
+ qrHtml += ' padding: 0px; margin: ' + margin + 'px;';
864
+ qrHtml += '">';
865
+ qrHtml += '<tbody>';
866
+
867
+ for (var r = 0; r < _this.getModuleCount(); r += 1) {
868
+
869
+ qrHtml += '<tr>';
870
+
871
+ for (var c = 0; c < _this.getModuleCount(); c += 1) {
872
+ qrHtml += '<td style="';
873
+ qrHtml += ' border-width: 0px; border-style: none;';
874
+ qrHtml += ' border-collapse: collapse;';
875
+ qrHtml += ' padding: 0px; margin: 0px;';
876
+ qrHtml += ' width: ' + cellSize + 'px;';
877
+ qrHtml += ' height: ' + cellSize + 'px;';
878
+ qrHtml += ' background-color: ';
879
+ qrHtml += _this.isDark(r, c)? '#000000' : '#ffffff';
880
+ qrHtml += ';';
881
+ qrHtml += '"/>';
882
+ }
883
+
884
+ qrHtml += '</tr>';
885
+ }
886
+
887
+ qrHtml += '</tbody>';
888
+ qrHtml += '</table>';
889
+
890
+ return qrHtml;
891
+ };
892
+
893
+ _this.createImgTag = function(cellSize, margin) {
894
+
895
+ cellSize = cellSize || 2;
896
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
897
+
898
+ var size = _this.getModuleCount() * cellSize + margin * 2;
899
+ var min = margin;
900
+ var max = size - margin;
901
+
902
+ return createImgTag(size, size, function(x, y) {
903
+ if (min <= x && x < max && min <= y && y < max) {
904
+ var c = Math.floor( (x - min) / cellSize);
905
+ var r = Math.floor( (y - min) / cellSize);
906
+ return _this.isDark(r, c)? 0 : 1;
907
+ } else {
908
+ return 1;
909
+ }
910
+ } );
911
+ };
912
+
913
+ return _this;
914
+ };
915
+
916
+ //---------------------------------------------------------------------
917
+ // qrcode.stringToBytes
918
+ //---------------------------------------------------------------------
919
+
920
+ qrcode.stringToBytes = function(s) {
921
+ var bytes = new Array();
922
+ for (var i = 0; i < s.length; i += 1) {
923
+ var c = s.charCodeAt(i);
924
+ bytes.push(c & 0xff);
925
+ }
926
+ return bytes;
927
+ };
928
+
929
+ //---------------------------------------------------------------------
930
+ // qrcode.createStringToBytes
931
+ //---------------------------------------------------------------------
932
+
933
+ /**
934
+ * @param unicodeData base64 string of byte array.
935
+ * [16bit Unicode],[16bit Bytes], ...
936
+ * @param numChars
937
+ */
938
+ qrcode.createStringToBytes = function(unicodeData, numChars) {
939
+
940
+ // create conversion map.
941
+
942
+ var unicodeMap = function() {
943
+
944
+ var bin = base64DecodeInputStream(unicodeData);
945
+ var read = function() {
946
+ var b = bin.read();
947
+ if (b == -1) throw new Error();
948
+ return b;
949
+ };
950
+
951
+ var count = 0;
952
+ var unicodeMap = {};
953
+ while (true) {
954
+ var b0 = bin.read();
955
+ if (b0 == -1) break;
956
+ var b1 = read();
957
+ var b2 = read();
958
+ var b3 = read();
959
+ var k = String.fromCharCode( (b0 << 8) | b1);
960
+ var v = (b2 << 8) | b3;
961
+ unicodeMap[k] = v;
962
+ count += 1;
963
+ }
964
+ if (count != numChars) {
965
+ throw new Error(count + ' != ' + numChars);
966
+ }
967
+
968
+ return unicodeMap;
969
+ }();
970
+
971
+ var unknownChar = '?'.charCodeAt(0);
972
+
973
+ return function(s) {
974
+ var bytes = new Array();
975
+ for (var i = 0; i < s.length; i += 1) {
976
+ var c = s.charCodeAt(i);
977
+ if (c < 128) {
978
+ bytes.push(c);
979
+ } else {
980
+ var b = unicodeMap[s.charAt(i)];
981
+ if (typeof b == 'number') {
982
+ if ( (b & 0xff) == b) {
983
+ // 1byte
984
+ bytes.push(b);
985
+ } else {
986
+ // 2bytes
987
+ bytes.push(b >>> 8);
988
+ bytes.push(b & 0xff);
989
+ }
990
+ } else {
991
+ bytes.push(unknownChar);
992
+ }
993
+ }
994
+ }
995
+ return bytes;
996
+ };
997
+ };
998
+
999
+ //---------------------------------------------------------------------
1000
+ // QRMode
1001
+ //---------------------------------------------------------------------
1002
+
1003
+ var QRMode = {
1004
+ MODE_NUMBER : 1 << 0,
1005
+ MODE_ALPHA_NUM : 1 << 1,
1006
+ MODE_8BIT_BYTE : 1 << 2,
1007
+ MODE_KANJI : 1 << 3
1008
+ };
1009
+
1010
+ //---------------------------------------------------------------------
1011
+ // QRErrorCorrectLevel
1012
+ //---------------------------------------------------------------------
1013
+
1014
+ var QRErrorCorrectLevel = {
1015
+ L : 1,
1016
+ M : 0,
1017
+ Q : 3,
1018
+ H : 2
1019
+ };
1020
+
1021
+ //---------------------------------------------------------------------
1022
+ // QRMaskPattern
1023
+ //---------------------------------------------------------------------
1024
+
1025
+ var QRMaskPattern = {
1026
+ PATTERN000 : 0,
1027
+ PATTERN001 : 1,
1028
+ PATTERN010 : 2,
1029
+ PATTERN011 : 3,
1030
+ PATTERN100 : 4,
1031
+ PATTERN101 : 5,
1032
+ PATTERN110 : 6,
1033
+ PATTERN111 : 7
1034
+ };
1035
+
1036
+ //---------------------------------------------------------------------
1037
+ // QRUtil
1038
+ //---------------------------------------------------------------------
1039
+
1040
+ var QRUtil = function() {
1041
+
1042
+ var PATTERN_POSITION_TABLE = [
1043
+ [],
1044
+ [6, 18],
1045
+ [6, 22],
1046
+ [6, 26],
1047
+ [6, 30],
1048
+ [6, 34],
1049
+ [6, 22, 38],
1050
+ [6, 24, 42],
1051
+ [6, 26, 46],
1052
+ [6, 28, 50],
1053
+ [6, 30, 54],
1054
+ [6, 32, 58],
1055
+ [6, 34, 62],
1056
+ [6, 26, 46, 66],
1057
+ [6, 26, 48, 70],
1058
+ [6, 26, 50, 74],
1059
+ [6, 30, 54, 78],
1060
+ [6, 30, 56, 82],
1061
+ [6, 30, 58, 86],
1062
+ [6, 34, 62, 90],
1063
+ [6, 28, 50, 72, 94],
1064
+ [6, 26, 50, 74, 98],
1065
+ [6, 30, 54, 78, 102],
1066
+ [6, 28, 54, 80, 106],
1067
+ [6, 32, 58, 84, 110],
1068
+ [6, 30, 58, 86, 114],
1069
+ [6, 34, 62, 90, 118],
1070
+ [6, 26, 50, 74, 98, 122],
1071
+ [6, 30, 54, 78, 102, 126],
1072
+ [6, 26, 52, 78, 104, 130],
1073
+ [6, 30, 56, 82, 108, 134],
1074
+ [6, 34, 60, 86, 112, 138],
1075
+ [6, 30, 58, 86, 114, 142],
1076
+ [6, 34, 62, 90, 118, 146],
1077
+ [6, 30, 54, 78, 102, 126, 150],
1078
+ [6, 24, 50, 76, 102, 128, 154],
1079
+ [6, 28, 54, 80, 106, 132, 158],
1080
+ [6, 32, 58, 84, 110, 136, 162],
1081
+ [6, 26, 54, 82, 110, 138, 166],
1082
+ [6, 30, 58, 86, 114, 142, 170]
1083
+ ];
1084
+ var G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0);
1085
+ var G18 = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0);
1086
+ var G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1);
1087
+
1088
+ var _this = {};
1089
+
1090
+ var getBCHDigit = function(data) {
1091
+ var digit = 0;
1092
+ while (data != 0) {
1093
+ digit += 1;
1094
+ data >>>= 1;
1095
+ }
1096
+ return digit;
1097
+ };
1098
+
1099
+ _this.getBCHTypeInfo = function(data) {
1100
+ var d = data << 10;
1101
+ while (getBCHDigit(d) - getBCHDigit(G15) >= 0) {
1102
+ d ^= (G15 << (getBCHDigit(d) - getBCHDigit(G15) ) );
1103
+ }
1104
+ return ( (data << 10) | d) ^ G15_MASK;
1105
+ };
1106
+
1107
+ _this.getBCHTypeNumber = function(data) {
1108
+ var d = data << 12;
1109
+ while (getBCHDigit(d) - getBCHDigit(G18) >= 0) {
1110
+ d ^= (G18 << (getBCHDigit(d) - getBCHDigit(G18) ) );
1111
+ }
1112
+ return (data << 12) | d;
1113
+ };
1114
+
1115
+ _this.getPatternPosition = function(typeNumber) {
1116
+ return PATTERN_POSITION_TABLE[typeNumber - 1];
1117
+ };
1118
+
1119
+ _this.getMaskFunction = function(maskPattern) {
1120
+
1121
+ switch (maskPattern) {
1122
+
1123
+ case QRMaskPattern.PATTERN000 :
1124
+ return function(i, j) { return (i + j) % 2 == 0; };
1125
+ case QRMaskPattern.PATTERN001 :
1126
+ return function(i, j) { return i % 2 == 0; };
1127
+ case QRMaskPattern.PATTERN010 :
1128
+ return function(i, j) { return j % 3 == 0; };
1129
+ case QRMaskPattern.PATTERN011 :
1130
+ return function(i, j) { return (i + j) % 3 == 0; };
1131
+ case QRMaskPattern.PATTERN100 :
1132
+ return function(i, j) { return (Math.floor(i / 2) + Math.floor(j / 3) ) % 2 == 0; };
1133
+ case QRMaskPattern.PATTERN101 :
1134
+ return function(i, j) { return (i * j) % 2 + (i * j) % 3 == 0; };
1135
+ case QRMaskPattern.PATTERN110 :
1136
+ return function(i, j) { return ( (i * j) % 2 + (i * j) % 3) % 2 == 0; };
1137
+ case QRMaskPattern.PATTERN111 :
1138
+ return function(i, j) { return ( (i * j) % 3 + (i + j) % 2) % 2 == 0; };
1139
+
1140
+ default :
1141
+ throw new Error('bad maskPattern:' + maskPattern);
1142
+ }
1143
+ };
1144
+
1145
+ _this.getErrorCorrectPolynomial = function(errorCorrectLength) {
1146
+ var a = qrPolynomial([1], 0);
1147
+ for (var i = 0; i < errorCorrectLength; i += 1) {
1148
+ a = a.multiply(qrPolynomial([1, QRMath.gexp(i)], 0) );
1149
+ }
1150
+ return a;
1151
+ };
1152
+
1153
+ _this.getLengthInBits = function(mode, type) {
1154
+
1155
+ if (1 <= type && type < 10) {
1156
+
1157
+ // 1 - 9
1158
+
1159
+ switch(mode) {
1160
+ case QRMode.MODE_NUMBER : return 10;
1161
+ case QRMode.MODE_ALPHA_NUM : return 9;
1162
+ case QRMode.MODE_8BIT_BYTE : return 8;
1163
+ case QRMode.MODE_KANJI : return 8;
1164
+ default :
1165
+ throw new Error('mode:' + mode);
1166
+ }
1167
+
1168
+ } else if (type < 27) {
1169
+
1170
+ // 10 - 26
1171
+
1172
+ switch(mode) {
1173
+ case QRMode.MODE_NUMBER : return 12;
1174
+ case QRMode.MODE_ALPHA_NUM : return 11;
1175
+ case QRMode.MODE_8BIT_BYTE : return 16;
1176
+ case QRMode.MODE_KANJI : return 10;
1177
+ default :
1178
+ throw new Error('mode:' + mode);
1179
+ }
1180
+
1181
+ } else if (type < 41) {
1182
+
1183
+ // 27 - 40
1184
+
1185
+ switch(mode) {
1186
+ case QRMode.MODE_NUMBER : return 14;
1187
+ case QRMode.MODE_ALPHA_NUM : return 13;
1188
+ case QRMode.MODE_8BIT_BYTE : return 16;
1189
+ case QRMode.MODE_KANJI : return 12;
1190
+ default :
1191
+ throw new Error('mode:' + mode);
1192
+ }
1193
+
1194
+ } else {
1195
+ throw new Error('type:' + type);
1196
+ }
1197
+ };
1198
+
1199
+ _this.getLostPoint = function(qrcode) {
1200
+
1201
+ var moduleCount = qrcode.getModuleCount();
1202
+
1203
+ var lostPoint = 0;
1204
+
1205
+ // LEVEL1
1206
+
1207
+ for (var row = 0; row < moduleCount; row += 1) {
1208
+ for (var col = 0; col < moduleCount; col += 1) {
1209
+
1210
+ var sameCount = 0;
1211
+ var dark = qrcode.isDark(row, col);
1212
+
1213
+ for (var r = -1; r <= 1; r += 1) {
1214
+
1215
+ if (row + r < 0 || moduleCount <= row + r) {
1216
+ continue;
1217
+ }
1218
+
1219
+ for (var c = -1; c <= 1; c += 1) {
1220
+
1221
+ if (col + c < 0 || moduleCount <= col + c) {
1222
+ continue;
1223
+ }
1224
+
1225
+ if (r == 0 && c == 0) {
1226
+ continue;
1227
+ }
1228
+
1229
+ if (dark == qrcode.isDark(row + r, col + c) ) {
1230
+ sameCount += 1;
1231
+ }
1232
+ }
1233
+ }
1234
+
1235
+ if (sameCount > 5) {
1236
+ lostPoint += (3 + sameCount - 5);
1237
+ }
1238
+ }
1239
+ };
1240
+
1241
+ // LEVEL2
1242
+
1243
+ for (var row = 0; row < moduleCount - 1; row += 1) {
1244
+ for (var col = 0; col < moduleCount - 1; col += 1) {
1245
+ var count = 0;
1246
+ if (qrcode.isDark(row, col) ) count += 1;
1247
+ if (qrcode.isDark(row + 1, col) ) count += 1;
1248
+ if (qrcode.isDark(row, col + 1) ) count += 1;
1249
+ if (qrcode.isDark(row + 1, col + 1) ) count += 1;
1250
+ if (count == 0 || count == 4) {
1251
+ lostPoint += 3;
1252
+ }
1253
+ }
1254
+ }
1255
+
1256
+ // LEVEL3
1257
+
1258
+ for (var row = 0; row < moduleCount; row += 1) {
1259
+ for (var col = 0; col < moduleCount - 6; col += 1) {
1260
+ if (qrcode.isDark(row, col)
1261
+ && !qrcode.isDark(row, col + 1)
1262
+ && qrcode.isDark(row, col + 2)
1263
+ && qrcode.isDark(row, col + 3)
1264
+ && qrcode.isDark(row, col + 4)
1265
+ && !qrcode.isDark(row, col + 5)
1266
+ && qrcode.isDark(row, col + 6) ) {
1267
+ lostPoint += 40;
1268
+ }
1269
+ }
1270
+ }
1271
+
1272
+ for (var col = 0; col < moduleCount; col += 1) {
1273
+ for (var row = 0; row < moduleCount - 6; row += 1) {
1274
+ if (qrcode.isDark(row, col)
1275
+ && !qrcode.isDark(row + 1, col)
1276
+ && qrcode.isDark(row + 2, col)
1277
+ && qrcode.isDark(row + 3, col)
1278
+ && qrcode.isDark(row + 4, col)
1279
+ && !qrcode.isDark(row + 5, col)
1280
+ && qrcode.isDark(row + 6, col) ) {
1281
+ lostPoint += 40;
1282
+ }
1283
+ }
1284
+ }
1285
+
1286
+ // LEVEL4
1287
+
1288
+ var darkCount = 0;
1289
+
1290
+ for (var col = 0; col < moduleCount; col += 1) {
1291
+ for (var row = 0; row < moduleCount; row += 1) {
1292
+ if (qrcode.isDark(row, col) ) {
1293
+ darkCount += 1;
1294
+ }
1295
+ }
1296
+ }
1297
+
1298
+ var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5;
1299
+ lostPoint += ratio * 10;
1300
+
1301
+ return lostPoint;
1302
+ };
1303
+
1304
+ return _this;
1305
+ }();
1306
+
1307
+ //---------------------------------------------------------------------
1308
+ // QRMath
1309
+ //---------------------------------------------------------------------
1310
+
1311
+ var QRMath = function() {
1312
+
1313
+ var EXP_TABLE = new Array(256);
1314
+ var LOG_TABLE = new Array(256);
1315
+
1316
+ // initialize tables
1317
+ for (var i = 0; i < 8; i += 1) {
1318
+ EXP_TABLE[i] = 1 << i;
1319
+ }
1320
+ for (var i = 8; i < 256; i += 1) {
1321
+ EXP_TABLE[i] = EXP_TABLE[i - 4]
1322
+ ^ EXP_TABLE[i - 5]
1323
+ ^ EXP_TABLE[i - 6]
1324
+ ^ EXP_TABLE[i - 8];
1325
+ }
1326
+ for (var i = 0; i < 255; i += 1) {
1327
+ LOG_TABLE[EXP_TABLE[i] ] = i;
1328
+ }
1329
+
1330
+ var _this = {};
1331
+
1332
+ _this.glog = function(n) {
1333
+
1334
+ if (n < 1) {
1335
+ throw new Error('glog(' + n + ')');
1336
+ }
1337
+
1338
+ return LOG_TABLE[n];
1339
+ };
1340
+
1341
+ _this.gexp = function(n) {
1342
+
1343
+ while (n < 0) {
1344
+ n += 255;
1345
+ }
1346
+
1347
+ while (n >= 256) {
1348
+ n -= 255;
1349
+ }
1350
+
1351
+ return EXP_TABLE[n];
1352
+ };
1353
+
1354
+ return _this;
1355
+ }();
1356
+
1357
+ //---------------------------------------------------------------------
1358
+ // qrPolynomial
1359
+ //---------------------------------------------------------------------
1360
+
1361
+ function qrPolynomial(num, shift) {
1362
+
1363
+ if (typeof num.length == 'undefined') {
1364
+ throw new Error(num.length + '/' + shift);
1365
+ }
1366
+
1367
+ var _num = function() {
1368
+ var offset = 0;
1369
+ while (offset < num.length && num[offset] == 0) {
1370
+ offset += 1;
1371
+ }
1372
+ var _num = new Array(num.length - offset + shift);
1373
+ for (var i = 0; i < num.length - offset; i += 1) {
1374
+ _num[i] = num[i + offset];
1375
+ }
1376
+ return _num;
1377
+ }();
1378
+
1379
+ var _this = {};
1380
+
1381
+ _this.getAt = function(index) {
1382
+ return _num[index];
1383
+ };
1384
+
1385
+ _this.getLength = function() {
1386
+ return _num.length;
1387
+ };
1388
+
1389
+ _this.multiply = function(e) {
1390
+
1391
+ var num = new Array(_this.getLength() + e.getLength() - 1);
1392
+
1393
+ for (var i = 0; i < _this.getLength(); i += 1) {
1394
+ for (var j = 0; j < e.getLength(); j += 1) {
1395
+ num[i + j] ^= QRMath.gexp(QRMath.glog(_this.getAt(i) ) + QRMath.glog(e.getAt(j) ) );
1396
+ }
1397
+ }
1398
+
1399
+ return qrPolynomial(num, 0);
1400
+ };
1401
+
1402
+ _this.mod = function(e) {
1403
+
1404
+ if (_this.getLength() - e.getLength() < 0) {
1405
+ return _this;
1406
+ }
1407
+
1408
+ var ratio = QRMath.glog(_this.getAt(0) ) - QRMath.glog(e.getAt(0) );
1409
+
1410
+ var num = new Array(_this.getLength() );
1411
+ for (var i = 0; i < _this.getLength(); i += 1) {
1412
+ num[i] = _this.getAt(i);
1413
+ }
1414
+
1415
+ for (var i = 0; i < e.getLength(); i += 1) {
1416
+ num[i] ^= QRMath.gexp(QRMath.glog(e.getAt(i) ) + ratio);
1417
+ }
1418
+
1419
+ // recursive call
1420
+ return qrPolynomial(num, 0).mod(e);
1421
+ };
1422
+
1423
+ return _this;
1424
+ };
1425
+
1426
+ //---------------------------------------------------------------------
1427
+ // QRRSBlock
1428
+ //---------------------------------------------------------------------
1429
+
1430
+ var QRRSBlock = function() {
1431
+
1432
+ var RS_BLOCK_TABLE = [
1433
+
1434
+ // L
1435
+ // M
1436
+ // Q
1437
+ // H
1438
+
1439
+ // 1
1440
+ [1, 26, 19],
1441
+ [1, 26, 16],
1442
+ [1, 26, 13],
1443
+ [1, 26, 9],
1444
+
1445
+ // 2
1446
+ [1, 44, 34],
1447
+ [1, 44, 28],
1448
+ [1, 44, 22],
1449
+ [1, 44, 16],
1450
+
1451
+ // 3
1452
+ [1, 70, 55],
1453
+ [1, 70, 44],
1454
+ [2, 35, 17],
1455
+ [2, 35, 13],
1456
+
1457
+ // 4
1458
+ [1, 100, 80],
1459
+ [2, 50, 32],
1460
+ [2, 50, 24],
1461
+ [4, 25, 9],
1462
+
1463
+ // 5
1464
+ [1, 134, 108],
1465
+ [2, 67, 43],
1466
+ [2, 33, 15, 2, 34, 16],
1467
+ [2, 33, 11, 2, 34, 12],
1468
+
1469
+ // 6
1470
+ [2, 86, 68],
1471
+ [4, 43, 27],
1472
+ [4, 43, 19],
1473
+ [4, 43, 15],
1474
+
1475
+ // 7
1476
+ [2, 98, 78],
1477
+ [4, 49, 31],
1478
+ [2, 32, 14, 4, 33, 15],
1479
+ [4, 39, 13, 1, 40, 14],
1480
+
1481
+ // 8
1482
+ [2, 121, 97],
1483
+ [2, 60, 38, 2, 61, 39],
1484
+ [4, 40, 18, 2, 41, 19],
1485
+ [4, 40, 14, 2, 41, 15],
1486
+
1487
+ // 9
1488
+ [2, 146, 116],
1489
+ [3, 58, 36, 2, 59, 37],
1490
+ [4, 36, 16, 4, 37, 17],
1491
+ [4, 36, 12, 4, 37, 13],
1492
+
1493
+ // 10
1494
+ [2, 86, 68, 2, 87, 69],
1495
+ [4, 69, 43, 1, 70, 44],
1496
+ [6, 43, 19, 2, 44, 20],
1497
+ [6, 43, 15, 2, 44, 16],
1498
+
1499
+ // 11
1500
+ [4, 101, 81],
1501
+ [1, 80, 50, 4, 81, 51],
1502
+ [4, 50, 22, 4, 51, 23],
1503
+ [3, 36, 12, 8, 37, 13],
1504
+
1505
+ // 12
1506
+ [2, 116, 92, 2, 117, 93],
1507
+ [6, 58, 36, 2, 59, 37],
1508
+ [4, 46, 20, 6, 47, 21],
1509
+ [7, 42, 14, 4, 43, 15],
1510
+
1511
+ // 13
1512
+ [4, 133, 107],
1513
+ [8, 59, 37, 1, 60, 38],
1514
+ [8, 44, 20, 4, 45, 21],
1515
+ [12, 33, 11, 4, 34, 12],
1516
+
1517
+ // 14
1518
+ [3, 145, 115, 1, 146, 116],
1519
+ [4, 64, 40, 5, 65, 41],
1520
+ [11, 36, 16, 5, 37, 17],
1521
+ [11, 36, 12, 5, 37, 13],
1522
+
1523
+ // 15
1524
+ [5, 109, 87, 1, 110, 88],
1525
+ [5, 65, 41, 5, 66, 42],
1526
+ [5, 54, 24, 7, 55, 25],
1527
+ [11, 36, 12, 7, 37, 13],
1528
+
1529
+ // 16
1530
+ [5, 122, 98, 1, 123, 99],
1531
+ [7, 73, 45, 3, 74, 46],
1532
+ [15, 43, 19, 2, 44, 20],
1533
+ [3, 45, 15, 13, 46, 16],
1534
+
1535
+ // 17
1536
+ [1, 135, 107, 5, 136, 108],
1537
+ [10, 74, 46, 1, 75, 47],
1538
+ [1, 50, 22, 15, 51, 23],
1539
+ [2, 42, 14, 17, 43, 15],
1540
+
1541
+ // 18
1542
+ [5, 150, 120, 1, 151, 121],
1543
+ [9, 69, 43, 4, 70, 44],
1544
+ [17, 50, 22, 1, 51, 23],
1545
+ [2, 42, 14, 19, 43, 15],
1546
+
1547
+ // 19
1548
+ [3, 141, 113, 4, 142, 114],
1549
+ [3, 70, 44, 11, 71, 45],
1550
+ [17, 47, 21, 4, 48, 22],
1551
+ [9, 39, 13, 16, 40, 14],
1552
+
1553
+ // 20
1554
+ [3, 135, 107, 5, 136, 108],
1555
+ [3, 67, 41, 13, 68, 42],
1556
+ [15, 54, 24, 5, 55, 25],
1557
+ [15, 43, 15, 10, 44, 16],
1558
+
1559
+ // 21
1560
+ [4, 144, 116, 4, 145, 117],
1561
+ [17, 68, 42],
1562
+ [17, 50, 22, 6, 51, 23],
1563
+ [19, 46, 16, 6, 47, 17],
1564
+
1565
+ // 22
1566
+ [2, 139, 111, 7, 140, 112],
1567
+ [17, 74, 46],
1568
+ [7, 54, 24, 16, 55, 25],
1569
+ [34, 37, 13],
1570
+
1571
+ // 23
1572
+ [4, 151, 121, 5, 152, 122],
1573
+ [4, 75, 47, 14, 76, 48],
1574
+ [11, 54, 24, 14, 55, 25],
1575
+ [16, 45, 15, 14, 46, 16],
1576
+
1577
+ // 24
1578
+ [6, 147, 117, 4, 148, 118],
1579
+ [6, 73, 45, 14, 74, 46],
1580
+ [11, 54, 24, 16, 55, 25],
1581
+ [30, 46, 16, 2, 47, 17],
1582
+
1583
+ // 25
1584
+ [8, 132, 106, 4, 133, 107],
1585
+ [8, 75, 47, 13, 76, 48],
1586
+ [7, 54, 24, 22, 55, 25],
1587
+ [22, 45, 15, 13, 46, 16],
1588
+
1589
+ // 26
1590
+ [10, 142, 114, 2, 143, 115],
1591
+ [19, 74, 46, 4, 75, 47],
1592
+ [28, 50, 22, 6, 51, 23],
1593
+ [33, 46, 16, 4, 47, 17],
1594
+
1595
+ // 27
1596
+ [8, 152, 122, 4, 153, 123],
1597
+ [22, 73, 45, 3, 74, 46],
1598
+ [8, 53, 23, 26, 54, 24],
1599
+ [12, 45, 15, 28, 46, 16],
1600
+
1601
+ // 28
1602
+ [3, 147, 117, 10, 148, 118],
1603
+ [3, 73, 45, 23, 74, 46],
1604
+ [4, 54, 24, 31, 55, 25],
1605
+ [11, 45, 15, 31, 46, 16],
1606
+
1607
+ // 29
1608
+ [7, 146, 116, 7, 147, 117],
1609
+ [21, 73, 45, 7, 74, 46],
1610
+ [1, 53, 23, 37, 54, 24],
1611
+ [19, 45, 15, 26, 46, 16],
1612
+
1613
+ // 30
1614
+ [5, 145, 115, 10, 146, 116],
1615
+ [19, 75, 47, 10, 76, 48],
1616
+ [15, 54, 24, 25, 55, 25],
1617
+ [23, 45, 15, 25, 46, 16],
1618
+
1619
+ // 31
1620
+ [13, 145, 115, 3, 146, 116],
1621
+ [2, 74, 46, 29, 75, 47],
1622
+ [42, 54, 24, 1, 55, 25],
1623
+ [23, 45, 15, 28, 46, 16],
1624
+
1625
+ // 32
1626
+ [17, 145, 115],
1627
+ [10, 74, 46, 23, 75, 47],
1628
+ [10, 54, 24, 35, 55, 25],
1629
+ [19, 45, 15, 35, 46, 16],
1630
+
1631
+ // 33
1632
+ [17, 145, 115, 1, 146, 116],
1633
+ [14, 74, 46, 21, 75, 47],
1634
+ [29, 54, 24, 19, 55, 25],
1635
+ [11, 45, 15, 46, 46, 16],
1636
+
1637
+ // 34
1638
+ [13, 145, 115, 6, 146, 116],
1639
+ [14, 74, 46, 23, 75, 47],
1640
+ [44, 54, 24, 7, 55, 25],
1641
+ [59, 46, 16, 1, 47, 17],
1642
+
1643
+ // 35
1644
+ [12, 151, 121, 7, 152, 122],
1645
+ [12, 75, 47, 26, 76, 48],
1646
+ [39, 54, 24, 14, 55, 25],
1647
+ [22, 45, 15, 41, 46, 16],
1648
+
1649
+ // 36
1650
+ [6, 151, 121, 14, 152, 122],
1651
+ [6, 75, 47, 34, 76, 48],
1652
+ [46, 54, 24, 10, 55, 25],
1653
+ [2, 45, 15, 64, 46, 16],
1654
+
1655
+ // 37
1656
+ [17, 152, 122, 4, 153, 123],
1657
+ [29, 74, 46, 14, 75, 47],
1658
+ [49, 54, 24, 10, 55, 25],
1659
+ [24, 45, 15, 46, 46, 16],
1660
+
1661
+ // 38
1662
+ [4, 152, 122, 18, 153, 123],
1663
+ [13, 74, 46, 32, 75, 47],
1664
+ [48, 54, 24, 14, 55, 25],
1665
+ [42, 45, 15, 32, 46, 16],
1666
+
1667
+ // 39
1668
+ [20, 147, 117, 4, 148, 118],
1669
+ [40, 75, 47, 7, 76, 48],
1670
+ [43, 54, 24, 22, 55, 25],
1671
+ [10, 45, 15, 67, 46, 16],
1672
+
1673
+ // 40
1674
+ [19, 148, 118, 6, 149, 119],
1675
+ [18, 75, 47, 31, 76, 48],
1676
+ [34, 54, 24, 34, 55, 25],
1677
+ [20, 45, 15, 61, 46, 16]
1678
+ ];
1679
+
1680
+ var qrRSBlock = function(totalCount, dataCount) {
1681
+ var _this = {};
1682
+ _this.totalCount = totalCount;
1683
+ _this.dataCount = dataCount;
1684
+ return _this;
1685
+ };
1686
+
1687
+ var _this = {};
1688
+
1689
+ var getRsBlockTable = function(typeNumber, errorCorrectLevel) {
1690
+
1691
+ switch(errorCorrectLevel) {
1692
+ case QRErrorCorrectLevel.L :
1693
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0];
1694
+ case QRErrorCorrectLevel.M :
1695
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1];
1696
+ case QRErrorCorrectLevel.Q :
1697
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2];
1698
+ case QRErrorCorrectLevel.H :
1699
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3];
1700
+ default :
1701
+ return undefined;
1702
+ }
1703
+ };
1704
+
1705
+ _this.getRSBlocks = function(typeNumber, errorCorrectLevel) {
1706
+
1707
+ var rsBlock = getRsBlockTable(typeNumber, errorCorrectLevel);
1708
+
1709
+ if (typeof rsBlock == 'undefined') {
1710
+ throw new Error('bad rs block @ typeNumber:' + typeNumber +
1711
+ '/errorCorrectLevel:' + errorCorrectLevel);
1712
+ }
1713
+
1714
+ var length = rsBlock.length / 3;
1715
+
1716
+ var list = new Array();
1717
+
1718
+ for (var i = 0; i < length; i += 1) {
1719
+
1720
+ var count = rsBlock[i * 3 + 0];
1721
+ var totalCount = rsBlock[i * 3 + 1];
1722
+ var dataCount = rsBlock[i * 3 + 2];
1723
+
1724
+ for (var j = 0; j < count; j += 1) {
1725
+ list.push(qrRSBlock(totalCount, dataCount) );
1726
+ }
1727
+ }
1728
+
1729
+ return list;
1730
+ };
1731
+
1732
+ return _this;
1733
+ }();
1734
+
1735
+ //---------------------------------------------------------------------
1736
+ // qrBitBuffer
1737
+ //---------------------------------------------------------------------
1738
+
1739
+ var qrBitBuffer = function() {
1740
+
1741
+ var _buffer = new Array();
1742
+ var _length = 0;
1743
+
1744
+ var _this = {};
1745
+
1746
+ _this.getBuffer = function() {
1747
+ return _buffer;
1748
+ };
1749
+
1750
+ _this.getAt = function(index) {
1751
+ var bufIndex = Math.floor(index / 8);
1752
+ return ( (_buffer[bufIndex] >>> (7 - index % 8) ) & 1) == 1;
1753
+ };
1754
+
1755
+ _this.put = function(num, length) {
1756
+ for (var i = 0; i < length; i += 1) {
1757
+ _this.putBit( ( (num >>> (length - i - 1) ) & 1) == 1);
1758
+ }
1759
+ };
1760
+
1761
+ _this.getLengthInBits = function() {
1762
+ return _length;
1763
+ };
1764
+
1765
+ _this.putBit = function(bit) {
1766
+
1767
+ var bufIndex = Math.floor(_length / 8);
1768
+ if (_buffer.length <= bufIndex) {
1769
+ _buffer.push(0);
1770
+ }
1771
+
1772
+ if (bit) {
1773
+ _buffer[bufIndex] |= (0x80 >>> (_length % 8) );
1774
+ }
1775
+
1776
+ _length += 1;
1777
+ };
1778
+
1779
+ return _this;
1780
+ };
1781
+
1782
+ //---------------------------------------------------------------------
1783
+ // qr8BitByte
1784
+ //---------------------------------------------------------------------
1785
+
1786
+ var qr8BitByte = function(data) {
1787
+
1788
+ var _mode = QRMode.MODE_8BIT_BYTE;
1789
+ var _data = data;
1790
+ var _bytes = qrcode.stringToBytes(data);
1791
+
1792
+ var _this = {};
1793
+
1794
+ _this.getMode = function() {
1795
+ return _mode;
1796
+ };
1797
+
1798
+ _this.getLength = function(buffer) {
1799
+ return _bytes.length;
1800
+ };
1801
+
1802
+ _this.write = function(buffer) {
1803
+ for (var i = 0; i < _bytes.length; i += 1) {
1804
+ buffer.put(_bytes[i], 8);
1805
+ }
1806
+ };
1807
+
1808
+ return _this;
1809
+ };
1810
+
1811
+ //=====================================================================
1812
+ // GIF Support etc.
1813
+ //
1814
+
1815
+ //---------------------------------------------------------------------
1816
+ // byteArrayOutputStream
1817
+ //---------------------------------------------------------------------
1818
+
1819
+ var byteArrayOutputStream = function() {
1820
+
1821
+ var _bytes = new Array();
1822
+
1823
+ var _this = {};
1824
+
1825
+ _this.writeByte = function(b) {
1826
+ _bytes.push(b & 0xff);
1827
+ };
1828
+
1829
+ _this.writeShort = function(i) {
1830
+ _this.writeByte(i);
1831
+ _this.writeByte(i >>> 8);
1832
+ };
1833
+
1834
+ _this.writeBytes = function(b, off, len) {
1835
+ off = off || 0;
1836
+ len = len || b.length;
1837
+ for (var i = 0; i < len; i += 1) {
1838
+ _this.writeByte(b[i + off]);
1839
+ }
1840
+ };
1841
+
1842
+ _this.writeString = function(s) {
1843
+ for (var i = 0; i < s.length; i += 1) {
1844
+ _this.writeByte(s.charCodeAt(i) );
1845
+ }
1846
+ };
1847
+
1848
+ _this.toByteArray = function() {
1849
+ return _bytes;
1850
+ };
1851
+
1852
+ _this.toString = function() {
1853
+ var s = '';
1854
+ s += '[';
1855
+ for (var i = 0; i < _bytes.length; i += 1) {
1856
+ if (i > 0) {
1857
+ s += ',';
1858
+ }
1859
+ s += _bytes[i];
1860
+ }
1861
+ s += ']';
1862
+ return s;
1863
+ };
1864
+
1865
+ return _this;
1866
+ };
1867
+
1868
+ //---------------------------------------------------------------------
1869
+ // base64EncodeOutputStream
1870
+ //---------------------------------------------------------------------
1871
+
1872
+ var base64EncodeOutputStream = function() {
1873
+
1874
+ var _buffer = 0;
1875
+ var _buflen = 0;
1876
+ var _length = 0;
1877
+ var _base64 = '';
1878
+
1879
+ var _this = {};
1880
+
1881
+ var writeEncoded = function(b) {
1882
+ _base64 += String.fromCharCode(encode(b & 0x3f) );
1883
+ };
1884
+
1885
+ var encode = function(n) {
1886
+ if (n < 0) {
1887
+ // error.
1888
+ } else if (n < 26) {
1889
+ return 0x41 + n;
1890
+ } else if (n < 52) {
1891
+ return 0x61 + (n - 26);
1892
+ } else if (n < 62) {
1893
+ return 0x30 + (n - 52);
1894
+ } else if (n == 62) {
1895
+ return 0x2b;
1896
+ } else if (n == 63) {
1897
+ return 0x2f;
1898
+ }
1899
+ throw new Error('n:' + n);
1900
+ };
1901
+
1902
+ _this.writeByte = function(n) {
1903
+
1904
+ _buffer = (_buffer << 8) | (n & 0xff);
1905
+ _buflen += 8;
1906
+ _length += 1;
1907
+
1908
+ while (_buflen >= 6) {
1909
+ writeEncoded(_buffer >>> (_buflen - 6) );
1910
+ _buflen -= 6;
1911
+ }
1912
+ };
1913
+
1914
+ _this.flush = function() {
1915
+
1916
+ if (_buflen > 0) {
1917
+ writeEncoded(_buffer << (6 - _buflen) );
1918
+ _buffer = 0;
1919
+ _buflen = 0;
1920
+ }
1921
+
1922
+ if (_length % 3 != 0) {
1923
+ // padding
1924
+ var padlen = 3 - _length % 3;
1925
+ for (var i = 0; i < padlen; i += 1) {
1926
+ _base64 += '=';
1927
+ }
1928
+ }
1929
+ };
1930
+
1931
+ _this.toString = function() {
1932
+ return _base64;
1933
+ };
1934
+
1935
+ return _this;
1936
+ };
1937
+
1938
+ //---------------------------------------------------------------------
1939
+ // base64DecodeInputStream
1940
+ //---------------------------------------------------------------------
1941
+
1942
+ var base64DecodeInputStream = function(str) {
1943
+
1944
+ var _str = str;
1945
+ var _pos = 0;
1946
+ var _buffer = 0;
1947
+ var _buflen = 0;
1948
+
1949
+ var _this = {};
1950
+
1951
+ _this.read = function() {
1952
+
1953
+ while (_buflen < 8) {
1954
+
1955
+ if (_pos >= _str.length) {
1956
+ if (_buflen == 0) {
1957
+ return -1;
1958
+ }
1959
+ throw new Error('unexpected end of file./' + _buflen);
1960
+ }
1961
+
1962
+ var c = _str.charAt(_pos);
1963
+ _pos += 1;
1964
+
1965
+ if (c == '=') {
1966
+ _buflen = 0;
1967
+ return -1;
1968
+ } else if (c.match(/^\s$/) ) {
1969
+ // ignore if whitespace.
1970
+ continue;
1971
+ }
1972
+
1973
+ _buffer = (_buffer << 6) | decode(c.charCodeAt(0) );
1974
+ _buflen += 6;
1975
+ }
1976
+
1977
+ var n = (_buffer >>> (_buflen - 8) ) & 0xff;
1978
+ _buflen -= 8;
1979
+ return n;
1980
+ };
1981
+
1982
+ var decode = function(c) {
1983
+ if (0x41 <= c && c <= 0x5a) {
1984
+ return c - 0x41;
1985
+ } else if (0x61 <= c && c <= 0x7a) {
1986
+ return c - 0x61 + 26;
1987
+ } else if (0x30 <= c && c <= 0x39) {
1988
+ return c - 0x30 + 52;
1989
+ } else if (c == 0x2b) {
1990
+ return 62;
1991
+ } else if (c == 0x2f) {
1992
+ return 63;
1993
+ } else {
1994
+ throw new Error('c:' + c);
1995
+ }
1996
+ };
1997
+
1998
+ return _this;
1999
+ };
2000
+
2001
+ //---------------------------------------------------------------------
2002
+ // gifImage (B/W)
2003
+ //---------------------------------------------------------------------
2004
+
2005
+ var gifImage = function(width, height) {
2006
+
2007
+ var _width = width;
2008
+ var _height = height;
2009
+ var _data = new Array(width * height);
2010
+
2011
+ var _this = {};
2012
+
2013
+ _this.setPixel = function(x, y, pixel) {
2014
+ _data[y * _width + x] = pixel;
2015
+ };
2016
+
2017
+ _this.write = function(out) {
2018
+
2019
+ //---------------------------------
2020
+ // GIF Signature
2021
+
2022
+ out.writeString('GIF87a');
2023
+
2024
+ //---------------------------------
2025
+ // Screen Descriptor
2026
+
2027
+ out.writeShort(_width);
2028
+ out.writeShort(_height);
2029
+
2030
+ out.writeByte(0x80); // 2bit
2031
+ out.writeByte(0);
2032
+ out.writeByte(0);
2033
+
2034
+ //---------------------------------
2035
+ // Global Color Map
2036
+
2037
+ // black
2038
+ out.writeByte(0x00);
2039
+ out.writeByte(0x00);
2040
+ out.writeByte(0x00);
2041
+
2042
+ // white
2043
+ out.writeByte(0xff);
2044
+ out.writeByte(0xff);
2045
+ out.writeByte(0xff);
2046
+
2047
+ //---------------------------------
2048
+ // Image Descriptor
2049
+
2050
+ out.writeString(',');
2051
+ out.writeShort(0);
2052
+ out.writeShort(0);
2053
+ out.writeShort(_width);
2054
+ out.writeShort(_height);
2055
+ out.writeByte(0);
2056
+
2057
+ //---------------------------------
2058
+ // Local Color Map
2059
+
2060
+ //---------------------------------
2061
+ // Raster Data
2062
+
2063
+ var lzwMinCodeSize = 2;
2064
+ var raster = getLZWRaster(lzwMinCodeSize);
2065
+
2066
+ out.writeByte(lzwMinCodeSize);
2067
+
2068
+ var offset = 0;
2069
+
2070
+ while (raster.length - offset > 255) {
2071
+ out.writeByte(255);
2072
+ out.writeBytes(raster, offset, 255);
2073
+ offset += 255;
2074
+ }
2075
+
2076
+ out.writeByte(raster.length - offset);
2077
+ out.writeBytes(raster, offset, raster.length - offset);
2078
+ out.writeByte(0x00);
2079
+
2080
+ //---------------------------------
2081
+ // GIF Terminator
2082
+ out.writeString(';');
2083
+ };
2084
+
2085
+ var bitOutputStream = function(out) {
2086
+
2087
+ var _out = out;
2088
+ var _bitLength = 0;
2089
+ var _bitBuffer = 0;
2090
+
2091
+ var _this = {};
2092
+
2093
+ _this.write = function(data, length) {
2094
+
2095
+ if ( (data >>> length) != 0) {
2096
+ throw new Error('length over');
2097
+ }
2098
+
2099
+ while (_bitLength + length >= 8) {
2100
+ _out.writeByte(0xff & ( (data << _bitLength) | _bitBuffer) );
2101
+ length -= (8 - _bitLength);
2102
+ data >>>= (8 - _bitLength);
2103
+ _bitBuffer = 0;
2104
+ _bitLength = 0;
2105
+ }
2106
+
2107
+ _bitBuffer = (data << _bitLength) | _bitBuffer;
2108
+ _bitLength = _bitLength + length;
2109
+ };
2110
+
2111
+ _this.flush = function() {
2112
+ if (_bitLength > 0) {
2113
+ _out.writeByte(_bitBuffer);
2114
+ }
2115
+ };
2116
+
2117
+ return _this;
2118
+ };
2119
+
2120
+ var getLZWRaster = function(lzwMinCodeSize) {
2121
+
2122
+ var clearCode = 1 << lzwMinCodeSize;
2123
+ var endCode = (1 << lzwMinCodeSize) + 1;
2124
+ var bitLength = lzwMinCodeSize + 1;
2125
+
2126
+ // Setup LZWTable
2127
+ var table = lzwTable();
2128
+
2129
+ for (var i = 0; i < clearCode; i += 1) {
2130
+ table.add(String.fromCharCode(i) );
2131
+ }
2132
+ table.add(String.fromCharCode(clearCode) );
2133
+ table.add(String.fromCharCode(endCode) );
2134
+
2135
+ var byteOut = byteArrayOutputStream();
2136
+ var bitOut = bitOutputStream(byteOut);
2137
+
2138
+ // clear code
2139
+ bitOut.write(clearCode, bitLength);
2140
+
2141
+ var dataIndex = 0;
2142
+
2143
+ var s = String.fromCharCode(_data[dataIndex]);
2144
+ dataIndex += 1;
2145
+
2146
+ while (dataIndex < _data.length) {
2147
+
2148
+ var c = String.fromCharCode(_data[dataIndex]);
2149
+ dataIndex += 1;
2150
+
2151
+ if (table.contains(s + c) ) {
2152
+
2153
+ s = s + c;
2154
+
2155
+ } else {
2156
+
2157
+ bitOut.write(table.indexOf(s), bitLength);
2158
+
2159
+ if (table.size() < 0xfff) {
2160
+
2161
+ if (table.size() == (1 << bitLength) ) {
2162
+ bitLength += 1;
2163
+ }
2164
+
2165
+ table.add(s + c);
2166
+ }
2167
+
2168
+ s = c;
2169
+ }
2170
+ }
2171
+
2172
+ bitOut.write(table.indexOf(s), bitLength);
2173
+
2174
+ // end code
2175
+ bitOut.write(endCode, bitLength);
2176
+
2177
+ bitOut.flush();
2178
+
2179
+ return byteOut.toByteArray();
2180
+ };
2181
+
2182
+ var lzwTable = function() {
2183
+
2184
+ var _map = {};
2185
+ var _size = 0;
2186
+
2187
+ var _this = {};
2188
+
2189
+ _this.add = function(key) {
2190
+ if (_this.contains(key) ) {
2191
+ throw new Error('dup key:' + key);
2192
+ }
2193
+ _map[key] = _size;
2194
+ _size += 1;
2195
+ };
2196
+
2197
+ _this.size = function() {
2198
+ return _size;
2199
+ };
2200
+
2201
+ _this.indexOf = function(key) {
2202
+ return _map[key];
2203
+ };
2204
+
2205
+ _this.contains = function(key) {
2206
+ return typeof _map[key] != 'undefined';
2207
+ };
2208
+
2209
+ return _this;
2210
+ };
2211
+
2212
+ return _this;
2213
+ };
2214
+
2215
+ var createImgTag = function(width, height, getPixel, alt) {
2216
+
2217
+ var gif = gifImage(width, height);
2218
+ for (var y = 0; y < height; y += 1) {
2219
+ for (var x = 0; x < width; x += 1) {
2220
+ gif.setPixel(x, y, getPixel(x, y) );
2221
+ }
2222
+ }
2223
+
2224
+ var b = byteArrayOutputStream();
2225
+ gif.write(b);
2226
+
2227
+ var base64 = base64EncodeOutputStream();
2228
+ var bytes = b.toByteArray();
2229
+ for (var i = 0; i < bytes.length; i += 1) {
2230
+ base64.writeByte(bytes[i]);
2231
+ }
2232
+ base64.flush();
2233
+
2234
+ var img = '';
2235
+ img += '<img';
2236
+ img += '\u0020src="';
2237
+ img += 'data:image/gif;base64,';
2238
+ img += base64;
2239
+ img += '"';
2240
+ img += '\u0020width="';
2241
+ img += width;
2242
+ img += '"';
2243
+ img += '\u0020height="';
2244
+ img += height;
2245
+ img += '"';
2246
+ if (alt) {
2247
+ img += '\u0020alt="';
2248
+ img += alt;
2249
+ img += '"';
2250
+ }
2251
+ img += '/>';
2252
+
2253
+ return img;
2254
+ };
2255
+
2256
+ //---------------------------------------------------------------------
2257
+ // returns qrcode function.
2258
+
2259
+ return qrcode;
2260
+ }();
2261
+
2262
+ (function (factory) {
2263
+ if (typeof define === 'function' && define.amd) {
2264
+ define([], factory);
2265
+ } else if (typeof exports === 'object') {
2266
+ module.exports = factory();
2267
+ }
2268
+ }(function () {
2269
+ return qrcode;
2270
+ }));
2271
+ //---------------------------------------------------------------------
2272
+ //
2273
+ // QR Code Generator for JavaScript UTF8 Support (optional)
2274
+ //
2275
+ // Copyright (c) 2011 Kazuhiko Arase
2276
+ //
2277
+ // URL: http://www.d-project.com/
2278
+ //
2279
+ // Licensed under the MIT license:
2280
+ // http://www.opensource.org/licenses/mit-license.php
2281
+ //
2282
+ // The word 'QR Code' is registered trademark of
2283
+ // DENSO WAVE INCORPORATED
2284
+ // http://www.denso-wave.com/qrcode/faqpatent-e.html
2285
+ //
2286
+ //---------------------------------------------------------------------
2287
+
2288
+ !function(qrcode) {
2289
+
2290
+ //---------------------------------------------------------------------
2291
+ // overwrite qrcode.stringToBytes
2292
+ //---------------------------------------------------------------------
2293
+
2294
+ qrcode.stringToBytes = function(s) {
2295
+ // http://stackoverflow.com/questions/18729405/how-to-convert-utf8-string-to-byte-array
2296
+ function toUTF8Array(str) {
2297
+ var utf8 = [];
2298
+ for (var i=0; i < str.length; i++) {
2299
+ var charcode = str.charCodeAt(i);
2300
+ if (charcode < 0x80) utf8.push(charcode);
2301
+ else if (charcode < 0x800) {
2302
+ utf8.push(0xc0 | (charcode >> 6),
2303
+ 0x80 | (charcode & 0x3f));
2304
+ }
2305
+ else if (charcode < 0xd800 || charcode >= 0xe000) {
2306
+ utf8.push(0xe0 | (charcode >> 12),
2307
+ 0x80 | ((charcode>>6) & 0x3f),
2308
+ 0x80 | (charcode & 0x3f));
2309
+ }
2310
+ // surrogate pair
2311
+ else {
2312
+ i++;
2313
+ // UTF-16 encodes 0x10000-0x10FFFF by
2314
+ // subtracting 0x10000 and splitting the
2315
+ // 20 bits of 0x0-0xFFFFF into two halves
2316
+ charcode = 0x10000 + (((charcode & 0x3ff)<<10)
2317
+ | (str.charCodeAt(i) & 0x3ff));
2318
+ utf8.push(0xf0 | (charcode >>18),
2319
+ 0x80 | ((charcode>>12) & 0x3f),
2320
+ 0x80 | ((charcode>>6) & 0x3f),
2321
+ 0x80 | (charcode & 0x3f));
2322
+ }
2323
+ }
2324
+ return utf8;
2325
+ }
2326
+ return toUTF8Array(s);
2327
+ };
2328
+
2329
+ }(qrcode);
2330
+
2331
+ return qrcode; // eslint-disable-line no-undef
2332
+ }()));
includes/simba-tfa/includes/jquery-qrcode/jquery-qrcode.min.js ADDED
@@ -0,0 +1,2 @@
 
 
1
+ /*! jquery-qrcode v0.14.0 - https://larsjung.de/jquery-qrcode/ */
2
+ !function(r){"use strict";function t(t,e,n,o){function a(r,t){return r-=o,t-=o,0>r||r>=c||0>t||t>=c?!1:f.isDark(r,t)}function i(r,t,e,n){var o=u.isDark,a=1/l;u.isDark=function(i,u){var f=u*a,c=i*a,l=f+a,g=c+a;return o(i,u)&&(r>l||f>e||t>g||c>n)}}var u={},f=r(n,e);f.addData(t),f.make(),o=o||0;var c=f.getModuleCount(),l=f.getModuleCount()+2*o;return u.text=t,u.level=e,u.version=n,u.moduleCount=l,u.isDark=a,u.addBlank=i,u}function e(r,e,n,o,a){n=Math.max(1,n||1),o=Math.min(40,o||40);for(var i=n;o>=i;i+=1)try{return t(r,e,i,a)}catch(u){}}function n(r,t,e){var n=e.size,o="bold "+e.mSize*n+"px "+e.fontname,a=w("<canvas/>")[0].getContext("2d");a.font=o;var i=a.measureText(e.label).width,u=e.mSize,f=i/n,c=(1-f)*e.mPosX,l=(1-u)*e.mPosY,g=c+f,s=l+u,v=.01;1===e.mode?r.addBlank(0,l-v,n,s+v):r.addBlank(c-v,l-v,g+v,s+v),t.fillStyle=e.fontcolor,t.font=o,t.fillText(e.label,c*n,l*n+.75*e.mSize*n)}function o(r,t,e){var n=e.size,o=e.image.naturalWidth||1,a=e.image.naturalHeight||1,i=e.mSize,u=i*o/a,f=(1-u)*e.mPosX,c=(1-i)*e.mPosY,l=f+u,g=c+i,s=.01;3===e.mode?r.addBlank(0,c-s,n,g+s):r.addBlank(f-s,c-s,l+s,g+s),t.drawImage(e.image,f*n,c*n,u*n,i*n)}function a(r,t,e){w(e.background).is("img")?t.drawImage(e.background,0,0,e.size,e.size):e.background&&(t.fillStyle=e.background,t.fillRect(e.left,e.top,e.size,e.size));var a=e.mode;1===a||2===a?n(r,t,e):(3===a||4===a)&&o(r,t,e)}function i(r,t,e,n,o,a,i,u){r.isDark(i,u)&&t.rect(n,o,a,a)}function u(r,t,e,n,o,a,i,u,f,c){i?r.moveTo(t+a,e):r.moveTo(t,e),u?(r.lineTo(n-a,e),r.arcTo(n,e,n,o,a)):r.lineTo(n,e),f?(r.lineTo(n,o-a),r.arcTo(n,o,t,o,a)):r.lineTo(n,o),c?(r.lineTo(t+a,o),r.arcTo(t,o,t,e,a)):r.lineTo(t,o),i?(r.lineTo(t,e+a),r.arcTo(t,e,n,e,a)):r.lineTo(t,e)}function f(r,t,e,n,o,a,i,u,f,c){i&&(r.moveTo(t+a,e),r.lineTo(t,e),r.lineTo(t,e+a),r.arcTo(t,e,t+a,e,a)),u&&(r.moveTo(n-a,e),r.lineTo(n,e),r.lineTo(n,e+a),r.arcTo(n,e,n-a,e,a)),f&&(r.moveTo(n-a,o),r.lineTo(n,o),r.lineTo(n,o-a),r.arcTo(n,o,n-a,o,a)),c&&(r.moveTo(t+a,o),r.lineTo(t,o),r.lineTo(t,o-a),r.arcTo(t,o,t+a,o,a))}function c(r,t,e,n,o,a,i,c){var l=r.isDark,g=n+a,s=o+a,v=e.radius*a,h=i-1,d=i+1,w=c-1,m=c+1,y=l(i,c),T=l(h,w),p=l(h,c),B=l(h,m),A=l(i,m),E=l(d,m),k=l(d,c),M=l(d,w),C=l(i,w);y?u(t,n,o,g,s,v,!p&&!C,!p&&!A,!k&&!A,!k&&!C):f(t,n,o,g,s,v,p&&C&&T,p&&A&&B,k&&A&&E,k&&C&&M)}function l(r,t,e){var n,o,a=r.moduleCount,u=e.size/a,f=i;for(e.radius>0&&e.radius<=.5&&(f=c),t.beginPath(),n=0;a>n;n+=1)for(o=0;a>o;o+=1){var l=e.left+o*u,g=e.top+n*u,s=u;f(r,t,e,l,g,s,n,o)}if(w(e.fill).is("img")){t.strokeStyle="rgba(0,0,0,0.5)",t.lineWidth=2,t.stroke();var v=t.globalCompositeOperation;t.globalCompositeOperation="destination-out",t.fill(),t.globalCompositeOperation=v,t.clip(),t.drawImage(e.fill,0,0,e.size,e.size),t.restore()}else t.fillStyle=e.fill,t.fill()}function g(r,t){var n=e(t.text,t.ecLevel,t.minVersion,t.maxVersion,t.quiet);if(!n)return null;var o=w(r).data("qrcode",n),i=o[0].getContext("2d");return a(n,i,t),l(n,i,t),o}function s(r){var t=w("<canvas/>").attr("width",r.size).attr("height",r.size);return g(t,r)}function v(r){return w("<img/>").attr("src",s(r)[0].toDataURL("image/png"))}function h(r){var t=e(r.text,r.ecLevel,r.minVersion,r.maxVersion,r.quiet);if(!t)return null;var n,o,a=r.size,i=r.background,u=Math.floor,f=t.moduleCount,c=u(a/f),l=u(.5*(a-c*f)),g={position:"relative",left:0,top:0,padding:0,margin:0,width:a,height:a},s={position:"absolute",padding:0,margin:0,width:c,height:c,"background-color":r.fill},v=w("<div/>").data("qrcode",t).css(g);for(i&&v.css("background-color",i),n=0;f>n;n+=1)for(o=0;f>o;o+=1)t.isDark(n,o)&&w("<div/>").css(s).css({left:l+o*c,top:l+n*c}).appendTo(v);return v}function d(r){return m&&"canvas"===r.render?s(r):m&&"image"===r.render?v(r):h(r)}var w=window.jQuery,m=function(){var r=document.createElement("canvas");return!(!r.getContext||!r.getContext("2d"))}(),y={render:"canvas",minVersion:1,maxVersion:40,ecLevel:"L",left:0,top:0,size:200,fill:"#000",background:null,text:"no text",radius:0,quiet:0,mode:0,mSize:.1,mPosX:.5,mPosY:.5,label:"no label",fontname:"sans",fontcolor:"#000",image:null};w.fn.qrcode=function(r){var t=w.extend({},y,r);return this.each(function(r,e){"canvas"===e.nodeName.toLowerCase()?g(e,t):w(e).append(d(t))})}}(function(){var r=function(){function r(t,e){if("undefined"==typeof t.length)throw new Error(t.length+"/"+e);var n=function(){for(var r=0;r<t.length&&0==t[r];)r+=1;for(var n=new Array(t.length-r+e),o=0;o<t.length-r;o+=1)n[o]=t[o+r];return n}(),o={};return o.getAt=function(r){return n[r]},o.getLength=function(){return n.length},o.multiply=function(t){for(var e=new Array(o.getLength()+t.getLength()-1),n=0;n<o.getLength();n+=1)for(var a=0;a<t.getLength();a+=1)e[n+a]^=i.gexp(i.glog(o.getAt(n))+i.glog(t.getAt(a)));return r(e,0)},o.mod=function(t){if(o.getLength()-t.getLength()<0)return o;for(var e=i.glog(o.getAt(0))-i.glog(t.getAt(0)),n=new Array(o.getLength()),a=0;a<o.getLength();a+=1)n[a]=o.getAt(a);for(var a=0;a<t.getLength();a+=1)n[a]^=i.gexp(i.glog(t.getAt(a))+e);return r(n,0).mod(t)},o}var t=function(t,e){var o=236,i=17,l=t,g=n[e],s=null,v=0,d=null,w=new Array,m={},y=function(r,t){v=4*l+17,s=function(r){for(var t=new Array(r),e=0;r>e;e+=1){t[e]=new Array(r);for(var n=0;r>n;n+=1)t[e][n]=null}return t}(v),T(0,0),T(v-7,0),T(0,v-7),A(),B(),k(r,t),l>=7&&E(r),null==d&&(d=D(l,g,w)),M(d,t)},T=function(r,t){for(var e=-1;7>=e;e+=1)if(!(-1>=r+e||r+e>=v))for(var n=-1;7>=n;n+=1)-1>=t+n||t+n>=v||(e>=0&&6>=e&&(0==n||6==n)||n>=0&&6>=n&&(0==e||6==e)||e>=2&&4>=e&&n>=2&&4>=n?s[r+e][t+n]=!0:s[r+e][t+n]=!1)},p=function(){for(var r=0,t=0,e=0;8>e;e+=1){y(!0,e);var n=a.getLostPoint(m);(0==e||r>n)&&(r=n,t=e)}return t},B=function(){for(var r=8;v-8>r;r+=1)null==s[r][6]&&(s[r][6]=r%2==0);for(var t=8;v-8>t;t+=1)null==s[6][t]&&(s[6][t]=t%2==0)},A=function(){for(var r=a.getPatternPosition(l),t=0;t<r.length;t+=1)for(var e=0;e<r.length;e+=1){var n=r[t],o=r[e];if(null==s[n][o])for(var i=-2;2>=i;i+=1)for(var u=-2;2>=u;u+=1)-2==i||2==i||-2==u||2==u||0==i&&0==u?s[n+i][o+u]=!0:s[n+i][o+u]=!1}},E=function(r){for(var t=a.getBCHTypeNumber(l),e=0;18>e;e+=1){var n=!r&&1==(t>>e&1);s[Math.floor(e/3)][e%3+v-8-3]=n}for(var e=0;18>e;e+=1){var n=!r&&1==(t>>e&1);s[e%3+v-8-3][Math.floor(e/3)]=n}},k=function(r,t){for(var e=g<<3|t,n=a.getBCHTypeInfo(e),o=0;15>o;o+=1){var i=!r&&1==(n>>o&1);6>o?s[o][8]=i:8>o?s[o+1][8]=i:s[v-15+o][8]=i}for(var o=0;15>o;o+=1){var i=!r&&1==(n>>o&1);8>o?s[8][v-o-1]=i:9>o?s[8][15-o-1+1]=i:s[8][15-o-1]=i}s[v-8][8]=!r},M=function(r,t){for(var e=-1,n=v-1,o=7,i=0,u=a.getMaskFunction(t),f=v-1;f>0;f-=2)for(6==f&&(f-=1);;){for(var c=0;2>c;c+=1)if(null==s[n][f-c]){var l=!1;i<r.length&&(l=1==(r[i]>>>o&1));var g=u(n,f-c);g&&(l=!l),s[n][f-c]=l,o-=1,-1==o&&(i+=1,o=7)}if(n+=e,0>n||n>=v){n-=e,e=-e;break}}},C=function(t,e){for(var n=0,o=0,i=0,u=new Array(e.length),f=new Array(e.length),c=0;c<e.length;c+=1){var l=e[c].dataCount,g=e[c].totalCount-l;o=Math.max(o,l),i=Math.max(i,g),u[c]=new Array(l);for(var s=0;s<u[c].length;s+=1)u[c][s]=255&t.getBuffer()[s+n];n+=l;var v=a.getErrorCorrectPolynomial(g),h=r(u[c],v.getLength()-1),d=h.mod(v);f[c]=new Array(v.getLength()-1);for(var s=0;s<f[c].length;s+=1){var w=s+d.getLength()-f[c].length;f[c][s]=w>=0?d.getAt(w):0}}for(var m=0,s=0;s<e.length;s+=1)m+=e[s].totalCount;for(var y=new Array(m),T=0,s=0;o>s;s+=1)for(var c=0;c<e.length;c+=1)s<u[c].length&&(y[T]=u[c][s],T+=1);for(var s=0;i>s;s+=1)for(var c=0;c<e.length;c+=1)s<f[c].length&&(y[T]=f[c][s],T+=1);return y},D=function(r,t,e){for(var n=u.getRSBlocks(r,t),c=f(),l=0;l<e.length;l+=1){var g=e[l];c.put(g.getMode(),4),c.put(g.getLength(),a.getLengthInBits(g.getMode(),r)),g.write(c)}for(var s=0,l=0;l<n.length;l+=1)s+=n[l].dataCount;if(c.getLengthInBits()>8*s)throw new Error("code length overflow. ("+c.getLengthInBits()+">"+8*s+")");for(c.getLengthInBits()+4<=8*s&&c.put(0,4);c.getLengthInBits()%8!=0;)c.putBit(!1);for(;;){if(c.getLengthInBits()>=8*s)break;if(c.put(o,8),c.getLengthInBits()>=8*s)break;c.put(i,8)}return C(c,n)};return m.addData=function(r){var t=c(r);w.push(t),d=null},m.isDark=function(r,t){if(0>r||r>=v||0>t||t>=v)throw new Error(r+","+t);return s[r][t]},m.getModuleCount=function(){return v},m.make=function(){y(!1,p())},m.createTableTag=function(r,t){r=r||2,t="undefined"==typeof t?4*r:t;var e="";e+='<table style="',e+=" border-width: 0px; border-style: none;",e+=" border-collapse: collapse;",e+=" padding: 0px; margin: "+t+"px;",e+='">',e+="<tbody>";for(var n=0;n<m.getModuleCount();n+=1){e+="<tr>";for(var o=0;o<m.getModuleCount();o+=1)e+='<td style="',e+=" border-width: 0px; border-style: none;",e+=" border-collapse: collapse;",e+=" padding: 0px; margin: 0px;",e+=" width: "+r+"px;",e+=" height: "+r+"px;",e+=" background-color: ",e+=m.isDark(n,o)?"#000000":"#ffffff",e+=";",e+='"/>';e+="</tr>"}return e+="</tbody>",e+="</table>"},m.createImgTag=function(r,t){r=r||2,t="undefined"==typeof t?4*r:t;var e=m.getModuleCount()*r+2*t,n=t,o=e-t;return h(e,e,function(t,e){if(t>=n&&o>t&&e>=n&&o>e){var a=Math.floor((t-n)/r),i=Math.floor((e-n)/r);return m.isDark(i,a)?0:1}return 1})},m};t.stringToBytes=function(r){for(var t=new Array,e=0;e<r.length;e+=1){var n=r.charCodeAt(e);t.push(255&n)}return t},t.createStringToBytes=function(r,t){var e=function(){for(var e=s(r),n=function(){var r=e.read();if(-1==r)throw new Error;return r},o=0,a={};;){var i=e.read();if(-1==i)break;var u=n(),f=n(),c=n(),l=String.fromCharCode(i<<8|u),g=f<<8|c;a[l]=g,o+=1}if(o!=t)throw new Error(o+" != "+t);return a}(),n="?".charCodeAt(0);return function(r){for(var t=new Array,o=0;o<r.length;o+=1){var a=r.charCodeAt(o);if(128>a)t.push(a);else{var i=e[r.charAt(o)];"number"==typeof i?(255&i)==i?t.push(i):(t.push(i>>>8),t.push(255&i)):t.push(n)}}return t}};var e={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},n={L:1,M:0,Q:3,H:2},o={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7},a=function(){var t=[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],n=1335,a=7973,u=21522,f={},c=function(r){for(var t=0;0!=r;)t+=1,r>>>=1;return t};return f.getBCHTypeInfo=function(r){for(var t=r<<10;c(t)-c(n)>=0;)t^=n<<c(t)-c(n);return(r<<10|t)^u},f.getBCHTypeNumber=function(r){for(var t=r<<12;c(t)-c(a)>=0;)t^=a<<c(t)-c(a);return r<<12|t},f.getPatternPosition=function(r){return t[r-1]},f.getMaskFunction=function(r){switch(r){case o.PATTERN000:return function(r,t){return(r+t)%2==0};case o.PATTERN001:return function(r,t){return r%2==0};case o.PATTERN010:return function(r,t){return t%3==0};case o.PATTERN011:return function(r,t){return(r+t)%3==0};case o.PATTERN100:return function(r,t){return(Math.floor(r/2)+Math.floor(t/3))%2==0};case o.PATTERN101:return function(r,t){return r*t%2+r*t%3==0};case o.PATTERN110:return function(r,t){return(r*t%2+r*t%3)%2==0};case o.PATTERN111:return function(r,t){return(r*t%3+(r+t)%2)%2==0};default:throw new Error("bad maskPattern:"+r)}},f.getErrorCorrectPolynomial=function(t){for(var e=r([1],0),n=0;t>n;n+=1)e=e.multiply(r([1,i.gexp(n)],0));return e},f.getLengthInBits=function(r,t){if(t>=1&&10>t)switch(r){case e.MODE_NUMBER:return 10;case e.MODE_ALPHA_NUM:return 9;case e.MODE_8BIT_BYTE:return 8;case e.MODE_KANJI:return 8;default:throw new Error("mode:"+r)}else if(27>t)switch(r){case e.MODE_NUMBER:return 12;case e.MODE_ALPHA_NUM:return 11;case e.MODE_8BIT_BYTE:return 16;case e.MODE_KANJI:return 10;default:throw new Error("mode:"+r)}else{if(!(41>t))throw new Error("type:"+t);switch(r){case e.MODE_NUMBER:return 14;case e.MODE_ALPHA_NUM:return 13;case e.MODE_8BIT_BYTE:return 16;case e.MODE_KANJI:return 12;default:throw new Error("mode:"+r)}}},f.getLostPoint=function(r){for(var t=r.getModuleCount(),e=0,n=0;t>n;n+=1)for(var o=0;t>o;o+=1){for(var a=0,i=r.isDark(n,o),u=-1;1>=u;u+=1)if(!(0>n+u||n+u>=t))for(var f=-1;1>=f;f+=1)0>o+f||o+f>=t||(0!=u||0!=f)&&i==r.isDark(n+u,o+f)&&(a+=1);a>5&&(e+=3+a-5)}for(var n=0;t-1>n;n+=1)for(var o=0;t-1>o;o+=1){var c=0;r.isDark(n,o)&&(c+=1),r.isDark(n+1,o)&&(c+=1),r.isDark(n,o+1)&&(c+=1),r.isDark(n+1,o+1)&&(c+=1),(0==c||4==c)&&(e+=3)}for(var n=0;t>n;n+=1)for(var o=0;t-6>o;o+=1)r.isDark(n,o)&&!r.isDark(n,o+1)&&r.isDark(n,o+2)&&r.isDark(n,o+3)&&r.isDark(n,o+4)&&!r.isDark(n,o+5)&&r.isDark(n,o+6)&&(e+=40);for(var o=0;t>o;o+=1)for(var n=0;t-6>n;n+=1)r.isDark(n,o)&&!r.isDark(n+1,o)&&r.isDark(n+2,o)&&r.isDark(n+3,o)&&r.isDark(n+4,o)&&!r.isDark(n+5,o)&&r.isDark(n+6,o)&&(e+=40);for(var l=0,o=0;t>o;o+=1)for(var n=0;t>n;n+=1)r.isDark(n,o)&&(l+=1);var g=Math.abs(100*l/t/t-50)/5;return e+=10*g},f}(),i=function(){for(var r=new Array(256),t=new Array(256),e=0;8>e;e+=1)r[e]=1<<e;for(var e=8;256>e;e+=1)r[e]=r[e-4]^r[e-5]^r[e-6]^r[e-8];for(var e=0;255>e;e+=1)t[r[e]]=e;var n={};return n.glog=function(r){if(1>r)throw new Error("glog("+r+")");return t[r]},n.gexp=function(t){for(;0>t;)t+=255;for(;t>=256;)t-=255;return r[t]},n}(),u=function(){var r=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12,7,37,13],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],t=function(r,t){var e={};return e.totalCount=r,e.dataCount=t,e},e={},o=function(t,e){switch(e){case n.L:return r[4*(t-1)+0];case n.M:return r[4*(t-1)+1];case n.Q:return r[4*(t-1)+2];case n.H:return r[4*(t-1)+3];default:return}};return e.getRSBlocks=function(r,e){var n=o(r,e);if("undefined"==typeof n)throw new Error("bad rs block @ typeNumber:"+r+"/errorCorrectLevel:"+e);for(var a=n.length/3,i=new Array,u=0;a>u;u+=1)for(var f=n[3*u+0],c=n[3*u+1],l=n[3*u+2],g=0;f>g;g+=1)i.push(t(c,l));return i},e}(),f=function(){var r=new Array,t=0,e={};return e.getBuffer=function(){return r},e.getAt=function(t){var e=Math.floor(t/8);return 1==(r[e]>>>7-t%8&1)},e.put=function(r,t){for(var n=0;t>n;n+=1)e.putBit(1==(r>>>t-n-1&1))},e.getLengthInBits=function(){return t},e.putBit=function(e){var n=Math.floor(t/8);r.length<=n&&r.push(0),e&&(r[n]|=128>>>t%8),t+=1},e},c=function(r){var n=e.MODE_8BIT_BYTE,o=t.stringToBytes(r),a={};return a.getMode=function(){return n},a.getLength=function(r){return o.length},a.write=function(r){for(var t=0;t<o.length;t+=1)r.put(o[t],8)},a},l=function(){var r=new Array,t={};return t.writeByte=function(t){r.push(255&t)},t.writeShort=function(r){t.writeByte(r),t.writeByte(r>>>8)},t.writeBytes=function(r,e,n){e=e||0,n=n||r.length;for(var o=0;n>o;o+=1)t.writeByte(r[o+e])},t.writeString=function(r){for(var e=0;e<r.length;e+=1)t.writeByte(r.charCodeAt(e))},t.toByteArray=function(){return r},t.toString=function(){var t="";t+="[";for(var e=0;e<r.length;e+=1)e>0&&(t+=","),t+=r[e];return t+="]"},t},g=function(){var r=0,t=0,e=0,n="",o={},a=function(r){n+=String.fromCharCode(i(63&r))},i=function(r){if(0>r);else{if(26>r)return 65+r;if(52>r)return 97+(r-26);if(62>r)return 48+(r-52);if(62==r)return 43;if(63==r)return 47}throw new Error("n:"+r)};return o.writeByte=function(n){for(r=r<<8|255&n,t+=8,e+=1;t>=6;)a(r>>>t-6),t-=6},o.flush=function(){if(t>0&&(a(r<<6-t),r=0,t=0),e%3!=0)for(var o=3-e%3,i=0;o>i;i+=1)n+="="},o.toString=function(){return n},o},s=function(r){var t=r,e=0,n=0,o=0,a={};a.read=function(){for(;8>o;){if(e>=t.length){if(0==o)return-1;throw new Error("unexpected end of file./"+o)}var r=t.charAt(e);if(e+=1,"="==r)return o=0,-1;r.match(/^\s$/)||(n=n<<6|i(r.charCodeAt(0)),o+=6)}var a=n>>>o-8&255;return o-=8,a};var i=function(r){if(r>=65&&90>=r)return r-65;if(r>=97&&122>=r)return r-97+26;if(r>=48&&57>=r)return r-48+52;if(43==r)return 62;if(47==r)return 63;throw new Error("c:"+r)};return a},v=function(r,t){var e=r,n=t,o=new Array(r*t),a={};a.setPixel=function(r,t,n){o[t*e+r]=n},a.write=function(r){r.writeString("GIF87a"),r.writeShort(e),r.writeShort(n),r.writeByte(128),r.writeByte(0),r.writeByte(0),r.writeByte(0),r.writeByte(0),r.writeByte(0),r.writeByte(255),r.writeByte(255),r.writeByte(255),r.writeString(","),r.writeShort(0),r.writeShort(0),r.writeShort(e),r.writeShort(n),r.writeByte(0);var t=2,o=u(t);r.writeByte(t);for(var a=0;o.length-a>255;)r.writeByte(255),r.writeBytes(o,a,255),a+=255;r.writeByte(o.length-a),r.writeBytes(o,a,o.length-a),r.writeByte(0),r.writeString(";")};var i=function(r){var t=r,e=0,n=0,o={};return o.write=function(r,o){if(r>>>o!=0)throw new Error("length over");for(;e+o>=8;)t.writeByte(255&(r<<e|n)),o-=8-e,r>>>=8-e,n=0,e=0;n=r<<e|n,e+=o},o.flush=function(){e>0&&t.writeByte(n)},o},u=function(r){for(var t=1<<r,e=(1<<r)+1,n=r+1,a=f(),u=0;t>u;u+=1)a.add(String.fromCharCode(u));a.add(String.fromCharCode(t)),a.add(String.fromCharCode(e));var c=l(),g=i(c);g.write(t,n);var s=0,v=String.fromCharCode(o[s]);for(s+=1;s<o.length;){var h=String.fromCharCode(o[s]);s+=1,a.contains(v+h)?v+=h:(g.write(a.indexOf(v),n),a.size()<4095&&(a.size()==1<<n&&(n+=1),a.add(v+h)),v=h)}return g.write(a.indexOf(v),n),g.write(e,n),g.flush(),c.toByteArray()},f=function(){var r={},t=0,e={};return e.add=function(n){if(e.contains(n))throw new Error("dup key:"+n);r[n]=t,t+=1},e.size=function(){return t},e.indexOf=function(t){return r[t]},e.contains=function(t){return"undefined"!=typeof r[t]},e};return a},h=function(r,t,e,n){for(var o=v(r,t),a=0;t>a;a+=1)for(var i=0;r>i;i+=1)o.setPixel(i,a,e(i,a));var u=l();o.write(u);for(var f=g(),c=u.toByteArray(),s=0;s<c.length;s+=1)f.writeByte(c[s]);f.flush();var h="";return h+="<img",h+=' src="',h+="data:image/gif;base64,",h+=f,h+='"',h+=' width="',h+=r,h+='"',h+=' height="',h+=t,h+='"',n&&(h+=' alt="',h+=n,h+='"'),h+="/>"};return t}();return function(r){"function"==typeof define&&define.amd?define([],r):"object"==typeof exports&&(module.exports=r())}(function(){return r}),!function(r){r.stringToBytes=function(r){function t(r){for(var t=[],e=0;e<r.length;e++){var n=r.charCodeAt(e);128>n?t.push(n):2048>n?t.push(192|n>>6,128|63&n):55296>n||n>=57344?t.push(224|n>>12,128|n>>6&63,128|63&n):(e++,n=65536+((1023&n)<<10|1023&r.charCodeAt(e)),t.push(240|n>>18,128|n>>12&63,128|n>>6&63,128|63&n))}return t}return t(r)}}(r),r}());
includes/simba-tfa/includes/jquery.blockUI.js ADDED
@@ -0,0 +1,619 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * jQuery blockUI plugin
3
+ * Version 2.70.0-2014.11.23
4
+ * Requires jQuery v1.7 or later
5
+ *
6
+ * Examples at: http://malsup.com/jquery/block/
7
+ * Copyright (c) 2007-2013 M. Alsup
8
+ * Dual licensed under the MIT and GPL licenses:
9
+ * http://www.opensource.org/licenses/mit-license.php
10
+ * http://www.gnu.org/licenses/gpl.html
11
+ *
12
+ * Thanks to Amir-Hossein Sobhi for some excellent contributions!
13
+ */
14
+ ;(function() {
15
+ /*jshint eqeqeq:false curly:false latedef:false */
16
+ "use strict";
17
+
18
+ function setup($) {
19
+ $.fn._fadeIn = $.fn.fadeIn;
20
+
21
+ var noOp = $.noop || function() {};
22
+
23
+ // this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle
24
+ // confusing userAgent strings on Vista)
25
+ var msie = /MSIE/.test(navigator.userAgent);
26
+ var ie6 = /MSIE 6.0/.test(navigator.userAgent) && ! /MSIE 8.0/.test(navigator.userAgent);
27
+ var mode = document.documentMode || 0;
28
+ var setExpr = $.isFunction( document.createElement('div').style.setExpression );
29
+
30
+ // global $ methods for blocking/unblocking the entire page
31
+ $.blockUI = function(opts) { install(window, opts); };
32
+ $.unblockUI = function(opts) { remove(window, opts); };
33
+
34
+ // convenience method for quick growl-like notifications (http://www.google.com/search?q=growl)
35
+ $.growlUI = function(title, message, timeout, onClose) {
36
+ var $m = $('<div class="growlUI"></div>');
37
+ if (title) $m.append('<h1>'+title+'</h1>');
38
+ if (message) $m.append('<h2>'+message+'</h2>');
39
+ if (timeout === undefined) timeout = 3000;
40
+
41
+ // Added by konapun: Set timeout to 30 seconds if this growl is moused over, like normal toast notifications
42
+ var callBlock = function(opts) {
43
+ opts = opts || {};
44
+
45
+ $.blockUI({
46
+ message: $m,
47
+ fadeIn : typeof opts.fadeIn !== 'undefined' ? opts.fadeIn : 700,
48
+ fadeOut: typeof opts.fadeOut !== 'undefined' ? opts.fadeOut : 1000,
49
+ timeout: typeof opts.timeout !== 'undefined' ? opts.timeout : timeout,
50
+ centerY: false,
51
+ showOverlay: false,
52
+ onUnblock: onClose,
53
+ css: $.blockUI.defaults.growlCSS
54
+ });
55
+ };
56
+
57
+ callBlock();
58
+ var nonmousedOpacity = $m.css('opacity');
59
+ $m.mouseover(function() {
60
+ callBlock({
61
+ fadeIn: 0,
62
+ timeout: 30000
63
+ });
64
+
65
+ var displayBlock = $('.blockMsg');
66
+ displayBlock.stop(); // cancel fadeout if it has started
67
+ displayBlock.fadeTo(300, 1); // make it easier to read the message by removing transparency
68
+ }).mouseout(function() {
69
+ $('.blockMsg').fadeOut(1000);
70
+ });
71
+ // End konapun additions
72
+ };
73
+
74
+ // plugin method for blocking element content
75
+ $.fn.block = function(opts) {
76
+ if ( this[0] === window ) {
77
+ $.blockUI( opts );
78
+ return this;
79
+ }
80
+ var fullOpts = $.extend({}, $.blockUI.defaults, opts || {});
81
+ this.each(function() {
82
+ var $el = $(this);
83
+ if (fullOpts.ignoreIfBlocked && $el.data('blockUI.isBlocked'))
84
+ return;
85
+ $el.unblock({ fadeOut: 0 });
86
+ });
87
+
88
+ return this.each(function() {
89
+ if ($.css(this,'position') == 'static') {
90
+ this.style.position = 'relative';
91
+ $(this).data('blockUI.static', true);
92
+ }
93
+ this.style.zoom = 1; // force 'hasLayout' in ie
94
+ install(this, opts);
95
+ });
96
+ };
97
+
98
+ // plugin method for unblocking element content
99
+ $.fn.unblock = function(opts) {
100
+ if ( this[0] === window ) {
101
+ $.unblockUI( opts );
102
+ return this;
103
+ }
104
+ return this.each(function() {
105
+ remove(this, opts);
106
+ });
107
+ };
108
+
109
+ $.blockUI.version = 2.70; // 2nd generation blocking at no extra cost!
110
+
111
+ // override these in your code to change the default behavior and style
112
+ $.blockUI.defaults = {
113
+ // message displayed when blocking (use null for no message)
114
+ message: '<h1>Please wait...</h1>',
115
+
116
+ title: null, // title string; only used when theme == true
117
+ draggable: true, // only used when theme == true (requires jquery-ui.js to be loaded)
118
+
119
+ theme: false, // set to true to use with jQuery UI themes
120
+
121
+ // styles for the message when blocking; if you wish to disable
122
+ // these and use an external stylesheet then do this in your code:
123
+ // $.blockUI.defaults.css = {};
124
+ css: {
125
+ padding: 0,
126
+ margin: 0,
127
+ width: '30%',
128
+ top: '40%',
129
+ left: '35%',
130
+ textAlign: 'center',
131
+ color: '#000',
132
+ border: '3px solid #aaa',
133
+ backgroundColor:'#fff',
134
+ cursor: 'wait'
135
+ },
136
+
137
+ // minimal style set used when themes are used
138
+ themedCSS: {
139
+ width: '30%',
140
+ top: '40%',
141
+ left: '35%'
142
+ },
143
+
144
+ // styles for the overlay
145
+ overlayCSS: {
146
+ backgroundColor: '#000',
147
+ opacity: 0.6,
148
+ cursor: 'wait'
149
+ },
150
+
151
+ // style to replace wait cursor before unblocking to correct issue
152
+ // of lingering wait cursor
153
+ cursorReset: 'default',
154
+
155
+ // styles applied when using $.growlUI
156
+ growlCSS: {
157
+ width: '350px',
158
+ top: '10px',
159
+ left: '',
160
+ right: '10px',
161
+ border: 'none',
162
+ padding: '5px',
163
+ opacity: 0.6,
164
+ cursor: 'default',
165
+ color: '#fff',
166
+ backgroundColor: '#000',
167
+ '-webkit-border-radius':'10px',
168
+ '-moz-border-radius': '10px',
169
+ 'border-radius': '10px'
170
+ },
171
+
172
+ // IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w
173
+ // (hat tip to Jorge H. N. de Vasconcelos)
174
+ /*jshint scripturl:true */
175
+ iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank',
176
+
177
+ // force usage of iframe in non-IE browsers (handy for blocking applets)
178
+ forceIframe: false,
179
+
180
+ // z-index for the blocking overlay
181
+ baseZ: 1000,
182
+
183
+ // set these to true to have the message automatically centered
184
+ centerX: true, // <-- only effects element blocking (page block controlled via css above)
185
+ centerY: true,
186
+
187
+ // allow body element to be stetched in ie6; this makes blocking look better
188
+ // on "short" pages. disable if you wish to prevent changes to the body height
189
+ allowBodyStretch: true,
190
+
191
+ // enable if you want key and mouse events to be disabled for content that is blocked
192
+ bindEvents: true,
193
+
194
+ // be default blockUI will supress tab navigation from leaving blocking content
195
+ // (if bindEvents is true)
196
+ constrainTabKey: true,
197
+
198
+ // fadeIn time in millis; set to 0 to disable fadeIn on block
199
+ fadeIn: 200,
200
+
201
+ // fadeOut time in millis; set to 0 to disable fadeOut on unblock
202
+ fadeOut: 400,
203
+
204
+ // time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock
205
+ timeout: 0,
206
+
207
+ // disable if you don't want to show the overlay
208
+ showOverlay: true,
209
+
210
+ // if true, focus will be placed in the first available input field when
211
+ // page blocking
212
+ focusInput: true,
213
+
214
+ // elements that can receive focus
215
+ focusableElements: ':input:enabled:visible',
216
+
217
+ // suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity)
218
+ // no longer needed in 2012
219
+ // applyPlatformOpacityRules: true,
220
+
221
+ // callback method invoked when fadeIn has completed and blocking message is visible
222
+ onBlock: null,
223
+
224
+ // callback method invoked when unblocking has completed; the callback is
225
+ // passed the element that has been unblocked (which is the window object for page
226
+ // blocks) and the options that were passed to the unblock call:
227
+ // onUnblock(element, options)
228
+ onUnblock: null,
229
+
230
+ // callback method invoked when the overlay area is clicked.
231
+ // setting this will turn the cursor to a pointer, otherwise cursor defined in overlayCss will be used.
232
+ onOverlayClick: null,
233
+
234
+ // don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493
235
+ quirksmodeOffsetHack: 4,
236
+
237
+ // class name of the message block
238
+ blockMsgClass: 'blockMsg',
239
+
240
+ // if it is already blocked, then ignore it (don't unblock and reblock)
241
+ ignoreIfBlocked: false
242
+ };
243
+
244
+ // private data and functions follow...
245
+
246
+ var pageBlock = null;
247
+ var pageBlockEls = [];
248
+
249
+ function install(el, opts) {
250
+ var css, themedCSS;
251
+ var full = (el == window);
252
+ var msg = (opts && opts.message !== undefined ? opts.message : undefined);
253
+ opts = $.extend({}, $.blockUI.defaults, opts || {});
254
+
255
+ if (opts.ignoreIfBlocked && $(el).data('blockUI.isBlocked'))
256
+ return;
257
+
258
+ opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {});
259
+ css = $.extend({}, $.blockUI.defaults.css, opts.css || {});
260
+ if (opts.onOverlayClick)
261
+ opts.overlayCSS.cursor = 'pointer';
262
+
263
+ themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {});
264
+ msg = msg === undefined ? opts.message : msg;
265
+
266
+ // remove the current block (if there is one)
267
+ if (full && pageBlock)
268
+ remove(window, {fadeOut:0});
269
+
270
+ // if an existing element is being used as the blocking content then we capture
271
+ // its current place in the DOM (and current display style) so we can restore
272
+ // it when we unblock
273
+ if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) {
274
+ var node = msg.jquery ? msg[0] : msg;
275
+ var data = {};
276
+ $(el).data('blockUI.history', data);
277
+ data.el = node;
278
+ data.parent = node.parentNode;
279
+ data.display = node.style.display;
280
+ data.position = node.style.position;
281
+ if (data.parent)
282
+ data.parent.removeChild(node);
283
+ }
284
+
285
+ $(el).data('blockUI.onUnblock', opts.onUnblock);
286
+ var z = opts.baseZ;
287
+
288
+ // blockUI uses 3 layers for blocking, for simplicity they are all used on every platform;
289
+ // layer1 is the iframe layer which is used to supress bleed through of underlying content
290
+ // layer2 is the overlay layer which has opacity and a wait cursor (by default)
291
+ // layer3 is the message content that is displayed while blocking
292
+ var lyr1, lyr2, lyr3, s;
293
+ if (msie || opts.forceIframe)
294
+ lyr1 = $('<iframe class="blockUI" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="'+opts.iframeSrc+'"></iframe>');
295
+ else
296
+ lyr1 = $('<div class="blockUI" style="display:none"></div>');
297
+
298
+ if (opts.theme)
299
+ lyr2 = $('<div class="blockUI blockOverlay ui-widget-overlay" style="z-index:'+ (z++) +';display:none"></div>');
300
+ else
301
+ lyr2 = $('<div class="blockUI blockOverlay" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>');
302
+
303
+ if (opts.theme && full) {
304
+ s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:fixed">';
305
+ if ( opts.title ) {
306
+ s += '<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>';
307
+ }
308
+ s += '<div class="ui-widget-content ui-dialog-content"></div>';
309
+ s += '</div>';
310
+ }
311
+ else if (opts.theme) {
312
+ s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:absolute">';
313
+ if ( opts.title ) {
314
+ s += '<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>';
315
+ }
316
+ s += '<div class="ui-widget-content ui-dialog-content"></div>';
317
+ s += '</div>';
318
+ }
319
+ else if (full) {
320
+ s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage" style="z-index:'+(z+10)+';display:none;position:fixed"></div>';
321
+ }
322
+ else {
323
+ s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement" style="z-index:'+(z+10)+';display:none;position:absolute"></div>';
324
+ }
325
+ lyr3 = $(s);
326
+
327
+ // if we have a message, style it
328
+ if (msg) {
329
+ if (opts.theme) {
330
+ lyr3.css(themedCSS);
331
+ lyr3.addClass('ui-widget-content');
332
+ }
333
+ else
334
+ lyr3.css(css);
335
+ }
336
+
337
+ // style the overlay
338
+ if (!opts.theme /*&& (!opts.applyPlatformOpacityRules)*/)
339
+ lyr2.css(opts.overlayCSS);
340
+ lyr2.css('position', full ? 'fixed' : 'absolute');
341
+
342
+ // make iframe layer transparent in IE
343
+ if (msie || opts.forceIframe)
344
+ lyr1.css('opacity',0.0);
345
+
346
+ //$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el);
347
+ var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el);
348
+ $.each(layers, function() {
349
+ this.appendTo($par);
350
+ });
351
+
352
+ if (opts.theme && opts.draggable && $.fn.draggable) {
353
+ lyr3.draggable({
354
+ handle: '.ui-dialog-titlebar',
355
+ cancel: 'li'
356
+ });
357
+ }
358
+
359
+ // ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling)
360
+ var expr = setExpr && (!$.support.boxModel || $('object,embed', full ? null : el).length > 0);
361
+ if (ie6 || expr) {
362
+ // give body 100% height
363
+ if (full && opts.allowBodyStretch && $.support.boxModel)
364
+ $('html,body').css('height','100%');
365
+
366
+ // fix ie6 issue when blocked element has a border width
367
+ if ((ie6 || !$.support.boxModel) && !full) {
368
+ var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth');
369
+ var fixT = t ? '(0 - '+t+')' : 0;
370
+ var fixL = l ? '(0 - '+l+')' : 0;
371
+ }
372
+
373
+ // simulate fixed position
374
+ $.each(layers, function(i,o) {
375
+ var s = o[0].style;
376
+ s.position = 'absolute';
377
+ if (i < 2) {
378
+ if (full)
379
+ s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"');
380
+ else
381
+ s.setExpression('height','this.parentNode.offsetHeight + "px"');
382
+ if (full)
383
+ s.setExpression('width','jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"');
384
+ else
385
+ s.setExpression('width','this.parentNode.offsetWidth + "px"');
386
+ if (fixL) s.setExpression('left', fixL);
387
+ if (fixT) s.setExpression('top', fixT);
388
+ }
389
+ else if (opts.centerY) {
390
+ if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"');
391
+ s.marginTop = 0;
392
+ }
393
+ else if (!opts.centerY && full) {
394
+ var top = (opts.css && opts.css.top) ? parseInt(opts.css.top, 10) : 0;
395
+ var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"';
396
+ s.setExpression('top',expression);
397
+ }
398
+ });
399
+ }
400
+
401
+ // show the message
402
+ if (msg) {
403
+ if (opts.theme)
404
+ lyr3.find('.ui-widget-content').append(msg);
405
+ else
406
+ lyr3.append(msg);
407
+ if (msg.jquery || msg.nodeType)
408
+ $(msg).show();
409
+ }
410
+
411
+ if ((msie || opts.forceIframe) && opts.showOverlay)
412
+ lyr1.show(); // opacity is zero
413
+ if (opts.fadeIn) {
414
+ var cb = opts.onBlock ? opts.onBlock : noOp;
415
+ var cb1 = (opts.showOverlay && !msg) ? cb : noOp;
416
+ var cb2 = msg ? cb : noOp;
417
+ if (opts.showOverlay)
418
+ lyr2._fadeIn(opts.fadeIn, cb1);
419
+ if (msg)
420
+ lyr3._fadeIn(opts.fadeIn, cb2);
421
+ }
422
+ else {
423
+ if (opts.showOverlay)
424
+ lyr2.show();
425
+ if (msg)
426
+ lyr3.show();
427
+ if (opts.onBlock)
428
+ opts.onBlock.bind(lyr3)();
429
+ }
430
+
431
+ // bind key and mouse events
432
+ bind(1, el, opts);
433
+
434
+ if (full) {
435
+ pageBlock = lyr3[0];
436
+ pageBlockEls = $(opts.focusableElements,pageBlock);
437
+ if (opts.focusInput)
438
+ setTimeout(focus, 20);
439
+ }
440
+ else
441
+ center(lyr3[0], opts.centerX, opts.centerY);
442
+
443
+ if (opts.timeout) {
444
+ // auto-unblock
445
+ var to = setTimeout(function() {
446
+ if (full)
447
+ $.unblockUI(opts);
448
+ else
449
+ $(el).unblock(opts);
450
+ }, opts.timeout);
451
+ $(el).data('blockUI.timeout', to);
452
+ }
453
+ }
454
+
455
+ // remove the block
456
+ function remove(el, opts) {
457
+ var count;
458
+ var full = (el == window);
459
+ var $el = $(el);
460
+ var data = $el.data('blockUI.history');
461
+ var to = $el.data('blockUI.timeout');
462
+ if (to) {
463
+ clearTimeout(to);
464
+ $el.removeData('blockUI.timeout');
465
+ }
466
+ opts = $.extend({}, $.blockUI.defaults, opts || {});
467
+ bind(0, el, opts); // unbind events
468
+
469
+ if (opts.onUnblock === null) {
470
+ opts.onUnblock = $el.data('blockUI.onUnblock');
471
+ $el.removeData('blockUI.onUnblock');
472
+ }
473
+
474
+ var els;
475
+ if (full) // crazy selector to handle odd field errors in ie6/7
476
+ els = $('body').children().filter('.blockUI').add('body > .blockUI');
477
+ else
478
+ els = $el.find('>.blockUI');
479
+
480
+ // fix cursor issue
481
+ if ( opts.cursorReset ) {
482
+ if ( els.length > 1 )
483
+ els[1].style.cursor = opts.cursorReset;
484
+ if ( els.length > 2 )
485
+ els[2].style.cursor = opts.cursorReset;
486
+ }
487
+
488
+ if (full)
489
+ pageBlock = pageBlockEls = null;
490
+
491
+ if (opts.fadeOut) {
492
+ count = els.length;
493
+ els.stop().fadeOut(opts.fadeOut, function() {
494
+ if ( --count === 0)
495
+ reset(els,data,opts,el);
496
+ });
497
+ }
498
+ else
499
+ reset(els, data, opts, el);
500
+ }
501
+
502
+ // move blocking element back into the DOM where it started
503
+ function reset(els,data,opts,el) {
504
+ var $el = $(el);
505
+ if ( $el.data('blockUI.isBlocked') )
506
+ return;
507
+
508
+ els.each(function(i,o) {
509
+ // remove via DOM calls so we don't lose event handlers
510
+ if (this.parentNode)
511
+ this.parentNode.removeChild(this);
512
+ });
513
+
514
+ if (data && data.el) {
515
+ data.el.style.display = data.display;
516
+ data.el.style.position = data.position;
517
+ data.el.style.cursor = 'default'; // #59
518
+ if (data.parent)
519
+ data.parent.appendChild(data.el);
520
+ $el.removeData('blockUI.history');
521
+ }
522
+
523
+ if ($el.data('blockUI.static')) {
524
+ $el.css('position', 'static'); // #22
525
+ }
526
+
527
+ if (typeof opts.onUnblock == 'function')
528
+ opts.onUnblock(el,opts);
529
+
530
+ // fix issue in Safari 6 where block artifacts remain until reflow
531
+ var body = $(document.body), w = body.width(), cssW = body[0].style.width;
532
+ body.width(w-1).width(w);
533
+ body[0].style.width = cssW;
534
+ }
535
+
536
+ // bind/unbind the handler
537
+ function bind(b, el, opts) {
538
+ var full = el == window, $el = $(el);
539
+
540
+ // don't bother unbinding if there is nothing to unbind
541
+ if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked')))
542
+ return;
543
+
544
+ $el.data('blockUI.isBlocked', b);
545
+
546
+ // don't bind events when overlay is not in use or if bindEvents is false
547
+ if (!full || !opts.bindEvents || (b && !opts.showOverlay))
548
+ return;
549
+
550
+ // bind anchors and inputs for mouse and key events
551
+ var events = 'mousedown mouseup keydown keypress keyup touchstart touchend touchmove';
552
+ if (b)
553
+ $(document).bind(events, opts, handler);
554
+ else
555
+ $(document).unbind(events, handler);
556
+
557
+ // former impl...
558
+ // var $e = $('a,:input');
559
+ // b ? $e.bind(events, opts, handler) : $e.unbind(events, handler);
560
+ }
561
+
562
+ // event handler to suppress keyboard/mouse events when blocking
563
+ function handler(e) {
564
+ // allow tab navigation (conditionally)
565
+ if (e.type === 'keydown' && e.keyCode && e.keyCode == 9) {
566
+ if (pageBlock && e.data.constrainTabKey) {
567
+ var els = pageBlockEls;
568
+ var fwd = !e.shiftKey && e.target === els[els.length-1];
569
+ var back = e.shiftKey && e.target === els[0];
570
+ if (fwd || back) {
571
+ setTimeout(function(){focus(back);},10);
572
+ return false;
573
+ }
574
+ }
575
+ }
576
+ var opts = e.data;
577
+ var target = $(e.target);
578
+ if (target.hasClass('blockOverlay') && opts.onOverlayClick)
579
+ opts.onOverlayClick(e);
580
+
581
+ // allow events within the message content
582
+ if (target.parents('div.' + opts.blockMsgClass).length > 0)
583
+ return true;
584
+
585
+ // allow events for content that is not being blocked
586
+ return target.parents().children().filter('div.blockUI').length === 0;
587
+ }
588
+
589
+ function focus(back) {
590
+ if (!pageBlockEls)
591
+ return;
592
+ var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0];
593
+ if (e)
594
+ e.focus();
595
+ }
596
+
597
+ function center(el, x, y) {
598
+ var p = el.parentNode, s = el.style;
599
+ var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth');
600
+ var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth');
601
+ if (x) s.left = l > 0 ? (l+'px') : '0';
602
+ if (y) s.top = t > 0 ? (t+'px') : '0';
603
+ }
604
+
605
+ function sz(el, p) {
606
+ return parseInt($.css(el,p),10)||0;
607
+ }
608
+
609
+ }
610
+
611
+
612
+ /*global define:true */
613
+ if (typeof define === 'function' && define.amd && define.amd.jQuery) {
614
+ define(['jquery'], setup);
615
+ } else {
616
+ setup(jQuery);
617
+ }
618
+
619
+ })();
includes/simba-tfa/includes/jquery.blockUI.min.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * jQuery blockUI plugin
3
+ * Version 2.70.0-2014.11.23
4
+ * Requires jQuery v1.7 or later
5
+ *
6
+ * Examples at: http://malsup.com/jquery/block/
7
+ * Copyright (c) 2007-2013 M. Alsup
8
+ * Dual licensed under the MIT and GPL licenses:
9
+ * http://www.opensource.org/licenses/mit-license.php
10
+ * http://www.gnu.org/licenses/gpl.html
11
+ *
12
+ * Thanks to Amir-Hossein Sobhi for some excellent contributions!
13
+ */
14
+ !function(){"use strict";function a(a){function b(b,d){var f,p,q=b==window,r=d&&void 0!==d.message?d.message:void 0;if(d=a.extend({},a.blockUI.defaults,d||{}),!d.ignoreIfBlocked||!a(b).data("blockUI.isBlocked")){if(d.overlayCSS=a.extend({},a.blockUI.defaults.overlayCSS,d.overlayCSS||{}),f=a.extend({},a.blockUI.defaults.css,d.css||{}),d.onOverlayClick&&(d.overlayCSS.cursor="pointer"),p=a.extend({},a.blockUI.defaults.themedCSS,d.themedCSS||{}),r=void 0===r?d.message:r,q&&n&&c(window,{fadeOut:0}),r&&"string"!=typeof r&&(r.parentNode||r.jquery)){var s=r.jquery?r[0]:r,t={};a(b).data("blockUI.history",t),t.el=s,t.parent=s.parentNode,t.display=s.style.display,t.position=s.style.position,t.parent&&t.parent.removeChild(s)}a(b).data("blockUI.onUnblock",d.onUnblock);var u,v,w,x,y=d.baseZ;u=a(k||d.forceIframe?'<iframe class="blockUI" style="z-index:'+y++ +';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="'+d.iframeSrc+'"></iframe>':'<div class="blockUI" style="display:none"></div>'),v=a(d.theme?'<div class="blockUI blockOverlay ui-widget-overlay" style="z-index:'+y++ +';display:none"></div>':'<div class="blockUI blockOverlay" style="z-index:'+y++ +';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>'),d.theme&&q?(x='<div class="blockUI '+d.blockMsgClass+' blockPage ui-dialog ui-widget ui-corner-all" style="z-index:'+(y+10)+';display:none;position:fixed">',d.title&&(x+='<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(d.title||"&nbsp;")+"</div>"),x+='<div class="ui-widget-content ui-dialog-content"></div>',x+="</div>"):d.theme?(x='<div class="blockUI '+d.blockMsgClass+' blockElement ui-dialog ui-widget ui-corner-all" style="z-index:'+(y+10)+';display:none;position:absolute">',d.title&&(x+='<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(d.title||"&nbsp;")+"</div>"),x+='<div class="ui-widget-content ui-dialog-content"></div>',x+="</div>"):x=q?'<div class="blockUI '+d.blockMsgClass+' blockPage" style="z-index:'+(y+10)+';display:none;position:fixed"></div>':'<div class="blockUI '+d.blockMsgClass+' blockElement" style="z-index:'+(y+10)+';display:none;position:absolute"></div>',w=a(x),r&&(d.theme?(w.css(p),w.addClass("ui-widget-content")):w.css(f)),d.theme||v.css(d.overlayCSS),v.css("position",q?"fixed":"absolute"),(k||d.forceIframe)&&u.css("opacity",0);var z=[u,v,w],A=a(q?"body":b);a.each(z,function(){this.appendTo(A)}),d.theme&&d.draggable&&a.fn.draggable&&w.draggable({handle:".ui-dialog-titlebar",cancel:"li"});var B=m&&(!a.support.boxModel||a("object,embed",q?null:b).length>0);if(l||B){if(q&&d.allowBodyStretch&&a.support.boxModel&&a("html,body").css("height","100%"),(l||!a.support.boxModel)&&!q)var C=i(b,"borderTopWidth"),D=i(b,"borderLeftWidth"),E=C?"(0 - "+C+")":0,F=D?"(0 - "+D+")":0;a.each(z,function(a,b){var c=b[0].style;if(c.position="absolute",2>a)q?c.setExpression("height","Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:"+d.quirksmodeOffsetHack+') + "px"'):c.setExpression("height",'this.parentNode.offsetHeight + "px"'),q?c.setExpression("width",'jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"'):c.setExpression("width",'this.parentNode.offsetWidth + "px"'),F&&c.setExpression("left",F),E&&c.setExpression("top",E);else if(d.centerY)q&&c.setExpression("top",'(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"'),c.marginTop=0;else if(!d.centerY&&q){var e=d.css&&d.css.top?parseInt(d.css.top,10):0,f="((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "+e+') + "px"';c.setExpression("top",f)}})}if(r&&(d.theme?w.find(".ui-widget-content").append(r):w.append(r),(r.jquery||r.nodeType)&&a(r).show()),(k||d.forceIframe)&&d.showOverlay&&u.show(),d.fadeIn){var G=d.onBlock?d.onBlock:j,H=d.showOverlay&&!r?G:j,I=r?G:j;d.showOverlay&&v._fadeIn(d.fadeIn,H),r&&w._fadeIn(d.fadeIn,I)}else d.showOverlay&&v.show(),r&&w.show(),d.onBlock&&d.onBlock.bind(w)();if(e(1,b,d),q?(n=w[0],o=a(d.focusableElements,n),d.focusInput&&setTimeout(g,20)):h(w[0],d.centerX,d.centerY),d.timeout){var J=setTimeout(function(){q?a.unblockUI(d):a(b).unblock(d)},d.timeout);a(b).data("blockUI.timeout",J)}}}function c(b,c){var f,g=b==window,h=a(b),i=h.data("blockUI.history"),j=h.data("blockUI.timeout");j&&(clearTimeout(j),h.removeData("blockUI.timeout")),c=a.extend({},a.blockUI.defaults,c||{}),e(0,b,c),null===c.onUnblock&&(c.onUnblock=h.data("blockUI.onUnblock"),h.removeData("blockUI.onUnblock"));var k;k=g?a("body").children().filter(".blockUI").add("body > .blockUI"):h.find(">.blockUI"),c.cursorReset&&(k.length>1&&(k[1].style.cursor=c.cursorReset),k.length>2&&(k[2].style.cursor=c.cursorReset)),g&&(n=o=null),c.fadeOut?(f=k.length,k.stop().fadeOut(c.fadeOut,function(){0===--f&&d(k,i,c,b)})):d(k,i,c,b)}function d(b,c,d,e){var f=a(e);if(!f.data("blockUI.isBlocked")){b.each(function(){this.parentNode&&this.parentNode.removeChild(this)}),c&&c.el&&(c.el.style.display=c.display,c.el.style.position=c.position,c.el.style.cursor="default",c.parent&&c.parent.appendChild(c.el),f.removeData("blockUI.history")),f.data("blockUI.static")&&f.css("position","static"),"function"==typeof d.onUnblock&&d.onUnblock(e,d);var g=a(document.body),h=g.width(),i=g[0].style.width;g.width(h-1).width(h),g[0].style.width=i}}function e(b,c,d){var e=c==window,g=a(c);if((b||(!e||n)&&(e||g.data("blockUI.isBlocked")))&&(g.data("blockUI.isBlocked",b),e&&d.bindEvents&&(!b||d.showOverlay))){var h="mousedown mouseup keydown keypress keyup touchstart touchend touchmove";b?a(document).bind(h,d,f):a(document).unbind(h,f)}}function f(b){if("keydown"===b.type&&b.keyCode&&9==b.keyCode&&n&&b.data.constrainTabKey){var c=o,d=!b.shiftKey&&b.target===c[c.length-1],e=b.shiftKey&&b.target===c[0];if(d||e)return setTimeout(function(){g(e)},10),!1}var f=b.data,h=a(b.target);return h.hasClass("blockOverlay")&&f.onOverlayClick&&f.onOverlayClick(b),h.parents("div."+f.blockMsgClass).length>0?!0:0===h.parents().children().filter("div.blockUI").length}function g(a){if(o){var b=o[a===!0?o.length-1:0];b&&b.focus()}}function h(a,b,c){var d=a.parentNode,e=a.style,f=(d.offsetWidth-a.offsetWidth)/2-i(d,"borderLeftWidth"),g=(d.offsetHeight-a.offsetHeight)/2-i(d,"borderTopWidth");b&&(e.left=f>0?f+"px":"0"),c&&(e.top=g>0?g+"px":"0")}function i(b,c){return parseInt(a.css(b,c),10)||0}a.fn._fadeIn=a.fn.fadeIn;var j=a.noop||function(){},k=/MSIE/.test(navigator.userAgent),l=/MSIE 6.0/.test(navigator.userAgent)&&!/MSIE 8.0/.test(navigator.userAgent),m=(document.documentMode||0,a.isFunction(document.createElement("div").style.setExpression));a.blockUI=function(a){b(window,a)},a.unblockUI=function(a){c(window,a)},a.growlUI=function(b,c,d,e){var f=a('<div class="growlUI"></div>');b&&f.append("<h1>"+b+"</h1>"),c&&f.append("<h2>"+c+"</h2>"),void 0===d&&(d=3e3);var g=function(b){b=b||{},a.blockUI({message:f,fadeIn:"undefined"!=typeof b.fadeIn?b.fadeIn:700,fadeOut:"undefined"!=typeof b.fadeOut?b.fadeOut:1e3,timeout:"undefined"!=typeof b.timeout?b.timeout:d,centerY:!1,showOverlay:!1,onUnblock:e,css:a.blockUI.defaults.growlCSS})};g();f.css("opacity");f.mouseover(function(){g({fadeIn:0,timeout:3e4});var b=a(".blockMsg");b.stop(),b.fadeTo(300,1)}).mouseout(function(){a(".blockMsg").fadeOut(1e3)})},a.fn.block=function(c){if(this[0]===window)return a.blockUI(c),this;var d=a.extend({},a.blockUI.defaults,c||{});return this.each(function(){var b=a(this);d.ignoreIfBlocked&&b.data("blockUI.isBlocked")||b.unblock({fadeOut:0})}),this.each(function(){"static"==a.css(this,"position")&&(this.style.position="relative",a(this).data("blockUI.static",!0)),this.style.zoom=1,b(this,c)})},a.fn.unblock=function(b){return this[0]===window?(a.unblockUI(b),this):this.each(function(){c(this,b)})},a.blockUI.version=2.7,a.blockUI.defaults={message:"<h1>Please wait...</h1>",title:null,draggable:!0,theme:!1,css:{padding:0,margin:0,width:"30%",top:"40%",left:"35%",textAlign:"center",color:"#000",border:"3px solid #aaa",backgroundColor:"#fff",cursor:"wait"},themedCSS:{width:"30%",top:"40%",left:"35%"},overlayCSS:{backgroundColor:"#000",opacity:.6,cursor:"wait"},cursorReset:"default",growlCSS:{width:"350px",top:"10px",left:"",right:"10px",border:"none",padding:"5px",opacity:.6,cursor:"default",color:"#fff",backgroundColor:"#000","-webkit-border-radius":"10px","-moz-border-radius":"10px","border-radius":"10px"},iframeSrc:/^https/i.test(window.location.href||"")?"javascript:false":"about:blank",forceIframe:!1,baseZ:1e3,centerX:!0,centerY:!0,allowBodyStretch:!0,bindEvents:!0,constrainTabKey:!0,fadeIn:200,fadeOut:400,timeout:0,showOverlay:!0,focusInput:!0,focusableElements:":input:enabled:visible",onBlock:null,onUnblock:null,onOverlayClick:null,quirksmodeOffsetHack:4,blockMsgClass:"blockMsg",ignoreIfBlocked:!1};var n=null,o=[]}"function"==typeof define&&define.amd&&define.amd.jQuery?define(["jquery"],a):a(jQuery)}();
includes/simba-tfa/includes/login-form-integrations.php ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if (!defined('ABSPATH')) die('No direct access.');
4
+
5
+ /**
6
+ * Purpose of this class: abstract out code handling integrations with login forms
7
+ */
8
+
9
+ class Simba_TFA_Login_Form_Integrations {
10
+
11
+ // Main class
12
+ private $tfa;
13
+
14
+ /**
15
+ * Plugin constructor
16
+ *
17
+ * @param Object $tfa
18
+ */
19
+ public function __construct($tfa) {
20
+
21
+ $this->tfa = $tfa;
22
+
23
+ $enqueue_upon_actions = array(
24
+ // This is needed for the login form on the dedicated payment page (e.g. /checkout/order-pay/123456/?pay_for_order=true&key=wc_order_blahblahblah)
25
+ 'woocommerce_login_form_start',
26
+ 'woocommerce_before_customer_login_form',
27
+ // The login form on the checkout doesn't call the woocommerce_before_customer_login_form action
28
+ 'woocommerce_before_checkout_form',
29
+ 'affwp_login_fields_before',
30
+ );
31
+
32
+ foreach ($enqueue_upon_actions as $action) {
33
+ add_action($action, array($this->tfa, 'login_enqueue_scripts'));
34
+ }
35
+
36
+ if (!defined('TWO_FACTOR_DISABLE') || !TWO_FACTOR_DISABLE) {
37
+ add_action('affwp_process_login_form', array($this, 'affwp_process_login_form'));
38
+ }
39
+
40
+ add_filter('tml_display', array($this, 'tml_display'));
41
+
42
+ // We want to run first if possible, so that we're not aborted by JavaScript exceptions in other components (our code is critical to the login process for TFA users)
43
+ // Unfortunately, though, people start enqueuing from init onwards (before that is buggy - https://core.trac.wordpress.org/ticket/11526), so, we try to detect the login page and go earlier there.
44
+ if (isset($GLOBALS['pagenow']) && 'wp-login.php' === $GLOBALS['pagenow']) {
45
+ add_action('init', array($this->tfa, 'login_enqueue_scripts'), -99999999999);
46
+ } else {
47
+ add_action('login_enqueue_scripts', array($this->tfa, 'login_enqueue_scripts'), -99999999999);
48
+ }
49
+
50
+ add_filter('do_shortcode_tag', array($this, 'do_shortcode_tag'), 10, 2);
51
+
52
+ add_filter('simba_tfa_login_enqueue_localize', array($this, 'simba_tfa_login_enqueue_localize'), 9);
53
+
54
+ }
55
+
56
+ /**
57
+ * Catch TML login widgets (other TML login forms already trigger)
58
+ *
59
+ * @param Mixed $whatever
60
+ *
61
+ * @return Mixed
62
+ */
63
+ public function tml_display($whatever) {
64
+ $this->tfa->login_enqueue_scripts();
65
+ return $whatever;
66
+ }
67
+
68
+ /**
69
+ * Runs upon the WP filter simba_tfa_login_enqueue_localize.
70
+ *
71
+ * @param Array $localize
72
+ *
73
+ * @return Array
74
+ */
75
+ public function simba_tfa_login_enqueue_localize($localize) {
76
+ // WP login form is #loginform
77
+ // Ultimate Membership Pro - April 2018
78
+ // Theme My Login 6.x - .tml-login form[name="loginform"]
79
+ // Theme My Login 7.x - .tml-login form[name="login"] (July 2018)
80
+ // WP Members - March 2018
81
+ // bbPress - June 2021
82
+ // WooCommerce - ported over from the separate wooextend.js code, June 2021
83
+ // Affiliates WP - ported over from the separate wooextend.js code, June 2021
84
+ $localize['login_form_selectors'] .= '.tml-login form[name="loginform"], .tml-login form[name="login"], #loginform, #wpmem_login form, form#ihc_login_form, .bbp-login-form, .woocommerce form.login, #affwp-login-form';
85
+ $localize['login_form_off_selectors'] .= '#ihc_login_form';
86
+ return $localize;
87
+ }
88
+
89
+ /**
90
+ * Runs upon the WP action affwp_process_login_form
91
+ */
92
+ public function affwp_process_login_form() {
93
+
94
+ if (!function_exists('affiliate_wp')) return;
95
+
96
+ $affiliate_wp = affiliate_wp();
97
+ $login = $affiliate_wp->login;
98
+
99
+ $params = array(
100
+ 'log' => stripslashes($_POST['affwp_user_login']),
101
+ 'caller'=> $_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : $_SERVER['REQUEST_URI'],
102
+ 'two_factor_code' => stripslashes((string) $_POST['two_factor_code'])
103
+ );
104
+ $code_ok = $this->tfa->authorise_user_from_login($params, true);
105
+
106
+ $code_ok = apply_filters('simbatfa_affwp_process_login_form_auth_result', $code_ok, $params);
107
+
108
+ if (is_wp_error($code_ok)) {
109
+ $login->add_error($code_ok->get_error_code, $code_ok->get_error_message());
110
+ } elseif (!$code_ok) {
111
+ $login->add_error('authentication_failed', __('Error:', 'all-in-one-wp-security-and-firewall').' '.__('The one-time password (TFA code) you entered was incorrect.', 'all-in-one-wp-security-and-firewall'));
112
+ }
113
+
114
+ }
115
+
116
+ /**
117
+ * Ultimate Membership Pro support
118
+ *
119
+ * @param String $output
120
+ * @param String $tag
121
+ *
122
+ * @return String
123
+ */
124
+ public function do_shortcode_tag($output, $tag) {
125
+ if ('ihc-login-form' == $tag) $this->tfa->login_enqueue_scripts();
126
+ return $output;
127
+ }
128
+
129
+ }
includes/simba-tfa/includes/select2.css ADDED
@@ -0,0 +1,482 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .select2-container {
2
+ box-sizing: border-box;
3
+ display: inline-block;
4
+ margin: 0;
5
+ position: relative;
6
+ vertical-align: middle; }
7
+ .select2-container .select2-selection--single {
8
+ box-sizing: border-box;
9
+ cursor: pointer;
10
+ display: block;
11
+ height: 28px;
12
+ user-select: none;
13
+ -webkit-user-select: none; }
14
+ .select2-container .select2-selection--single .select2-selection__rendered {
15
+ display: block;
16
+ padding-left: 8px;
17
+ padding-right: 20px;
18
+ overflow: hidden;
19
+ text-overflow: ellipsis;
20
+ white-space: nowrap; }
21
+ .select2-container .select2-selection--single .select2-selection__clear {
22
+ position: relative; }
23
+ .select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered {
24
+ padding-right: 8px;
25
+ padding-left: 20px; }
26
+ .select2-container .select2-selection--multiple {
27
+ box-sizing: border-box;
28
+ cursor: pointer;
29
+ display: block;
30
+ min-height: 32px;
31
+ user-select: none;
32
+ -webkit-user-select: none; }
33
+ .select2-container .select2-selection--multiple .select2-selection__rendered {
34
+ display: inline-block;
35
+ overflow: hidden;
36
+ padding-left: 8px;
37
+ text-overflow: ellipsis;
38
+ white-space: nowrap; }
39
+ .select2-container .select2-search--inline {
40
+ float: left; }
41
+ .select2-container .select2-search--inline .select2-search__field {
42
+ box-sizing: border-box;
43
+ border: none;
44
+ font-size: 100%;
45
+ margin-top: 5px;
46
+ padding: 0; }
47
+ .select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button {
48
+ -webkit-appearance: none; }
49
+
50
+ .select2-dropdown {
51
+ background-color: white;
52
+ border: 1px solid #aaa;
53
+ border-radius: 4px;
54
+ box-sizing: border-box;
55
+ display: block;
56
+ position: absolute;
57
+ left: -100000px;
58
+ width: 100%;
59
+ z-index: 1051; }
60
+
61
+ .select2-results {
62
+ display: block; }
63
+
64
+ .select2-results__options {
65
+ list-style: none;
66
+ margin: 0;
67
+ padding: 0; }
68
+
69
+ .select2-results__option {
70
+ padding: 6px;
71
+ user-select: none;
72
+ -webkit-user-select: none; }
73
+ .select2-results__option[aria-selected] {
74
+ cursor: pointer; }
75
+
76
+ .select2-container--open .select2-dropdown {
77
+ left: 0; }
78
+
79
+ .select2-container--open .select2-dropdown--above {
80
+ border-bottom: none;
81
+ border-bottom-left-radius: 0;
82
+ border-bottom-right-radius: 0; }
83
+
84
+ .select2-container--open .select2-dropdown--below {
85
+ border-top: none;
86
+ border-top-left-radius: 0;
87
+ border-top-right-radius: 0; }
88
+
89
+ .select2-search--dropdown {
90
+ display: block;
91
+ padding: 4px; }
92
+ .select2-search--dropdown .select2-search__field {
93
+ padding: 4px;
94
+ width: 100%;
95
+ box-sizing: border-box; }
96
+ .select2-search--dropdown .select2-search__field::-webkit-search-cancel-button {
97
+ -webkit-appearance: none; }
98
+ .select2-search--dropdown.select2-search--hide {
99
+ display: none; }
100
+
101
+ .select2-close-mask {
102
+ border: 0;
103
+ margin: 0;
104
+ padding: 0;
105
+ display: block;
106
+ position: fixed;
107
+ left: 0;
108
+ top: 0;
109
+ min-height: 100%;
110
+ min-width: 100%;
111
+ height: auto;
112
+ width: auto;
113
+ opacity: 0;
114
+ z-index: 99;
115
+ background-color: #fff;
116
+ filter: alpha(opacity=0); }
117
+
118
+ .select2-hidden-accessible {
119
+ border: 0 !important;
120
+ clip: rect(0 0 0 0) !important;
121
+ height: 1px !important;
122
+ margin: -1px !important;
123
+ overflow: hidden !important;
124
+ padding: 0 !important;
125
+ position: absolute !important;
126
+ width: 1px !important; }
127
+
128
+ .select2-container--default .select2-selection--single {
129
+ background-color: #fff;
130
+ border: 1px solid #aaa;
131
+ border-radius: 4px; }
132
+ .select2-container--default .select2-selection--single .select2-selection__rendered {
133
+ color: #444;
134
+ line-height: 28px; }
135
+ .select2-container--default .select2-selection--single .select2-selection__clear {
136
+ cursor: pointer;
137
+ float: right;
138
+ font-weight: bold; }
139
+ .select2-container--default .select2-selection--single .select2-selection__placeholder {
140
+ color: #999; }
141
+ .select2-container--default .select2-selection--single .select2-selection__arrow {
142
+ height: 26px;
143
+ position: absolute;
144
+ top: 1px;
145
+ right: 1px;
146
+ width: 20px; }
147
+ .select2-container--default .select2-selection--single .select2-selection__arrow b {
148
+ border-color: #888 transparent transparent transparent;
149
+ border-style: solid;
150
+ border-width: 5px 4px 0 4px;
151
+ height: 0;
152
+ left: 50%;
153
+ margin-left: -4px;
154
+ margin-top: -2px;
155
+ position: absolute;
156
+ top: 50%;
157
+ width: 0; }
158
+
159
+ .select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear {
160
+ float: left; }
161
+
162
+ .select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow {
163
+ left: 1px;
164
+ right: auto; }
165
+
166
+ .select2-container--default.select2-container--disabled .select2-selection--single {
167
+ background-color: #eee;
168
+ cursor: default; }
169
+ .select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear {
170
+ display: none; }
171
+
172
+ .select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b {
173
+ border-color: transparent transparent #888 transparent;
174
+ border-width: 0 4px 5px 4px; }
175
+
176
+ .select2-container--default .select2-selection--multiple {
177
+ background-color: white;
178
+ border: 1px solid #aaa;
179
+ border-radius: 4px;
180
+ cursor: text; }
181
+ .select2-container--default .select2-selection--multiple .select2-selection__rendered {
182
+ box-sizing: border-box;
183
+ list-style: none;
184
+ margin: 0;
185
+ padding: 0 5px;
186
+ width: 100%; }
187
+ .select2-container--default .select2-selection--multiple .select2-selection__placeholder {
188
+ color: #999;
189
+ margin-top: 5px;
190
+ float: left; }
191
+ .select2-container--default .select2-selection--multiple .select2-selection__clear {
192
+ cursor: pointer;
193
+ float: right;
194
+ font-weight: bold;
195
+ margin-top: 5px;
196
+ margin-right: 10px; }
197
+ .select2-container--default .select2-selection--multiple .select2-selection__choice {
198
+ background-color: #e4e4e4;
199
+ border: 1px solid #aaa;
200
+ border-radius: 4px;
201
+ cursor: default;
202
+ float: left;
203
+ margin-right: 5px;
204
+ margin-top: 5px;
205
+ padding: 0 5px; }
206
+ .select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
207
+ color: #999;
208
+ cursor: pointer;
209
+ display: inline-block;
210
+ font-weight: bold;
211
+ margin-right: 2px; }
212
+ .select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
213
+ color: #333; }
214
+
215
+ .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline {
216
+ float: right; }
217
+
218
+ .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
219
+ margin-left: 5px;
220
+ margin-right: auto; }
221
+
222
+ .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
223
+ margin-left: 2px;
224
+ margin-right: auto; }
225
+
226
+ .select2-container--default.select2-container--focus .select2-selection--multiple {
227
+ border: solid black 1px;
228
+ outline: 0; }
229
+
230
+ .select2-container--default.select2-container--disabled .select2-selection--multiple {
231
+ background-color: #eee;
232
+ cursor: default; }
233
+
234
+ .select2-container--default.select2-container--disabled .select2-selection__choice__remove {
235
+ display: none; }
236
+
237
+ .select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple {
238
+ border-top-left-radius: 0;
239
+ border-top-right-radius: 0; }
240
+
241
+ .select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple {
242
+ border-bottom-left-radius: 0;
243
+ border-bottom-right-radius: 0; }
244
+
245
+ .select2-container--default .select2-search--dropdown .select2-search__field {
246
+ border: 1px solid #aaa; }
247
+
248
+ .select2-container--default .select2-search--inline .select2-search__field {
249
+ background: transparent;
250
+ border: none;
251
+ outline: 0;
252
+ box-shadow: none;
253
+ -webkit-appearance: textfield; }
254
+
255
+ .select2-container--default .select2-results > .select2-results__options {
256
+ max-height: 200px;
257
+ overflow-y: auto; }
258
+
259
+ .select2-container--default .select2-results__option[role=group] {
260
+ padding: 0; }
261
+
262
+ .select2-container--default .select2-results__option[aria-disabled=true] {
263
+ color: #999; }
264
+
265
+ .select2-container--default .select2-results__option[aria-selected=true] {
266
+ background-color: #ddd; }
267
+
268
+ .select2-container--default .select2-results__option .select2-results__option {
269
+ padding-left: 1em; }
270
+ .select2-container--default .select2-results__option .select2-results__option .select2-results__group {
271
+ padding-left: 0; }
272
+ .select2-container--default .select2-results__option .select2-results__option .select2-results__option {
273
+ margin-left: -1em;
274
+ padding-left: 2em; }
275
+ .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
276
+ margin-left: -2em;
277
+ padding-left: 3em; }
278
+ .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
279
+ margin-left: -3em;
280
+ padding-left: 4em; }
281
+ .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
282
+ margin-left: -4em;
283
+ padding-left: 5em; }
284
+ .select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
285
+ margin-left: -5em;
286
+ padding-left: 6em; }
287
+
288
+ .select2-container--default .select2-results__option--highlighted[aria-selected] {
289
+ background-color: #5897fb;
290
+ color: white; }
291
+
292
+ .select2-container--default .select2-results__group {
293
+ cursor: default;
294
+ display: block;
295
+ padding: 6px; }
296
+
297
+ .select2-container--classic .select2-selection--single {
298
+ background-color: #f7f7f7;
299
+ border: 1px solid #aaa;
300
+ border-radius: 4px;
301
+ outline: 0;
302
+ background-image: -webkit-linear-gradient(top, white 50%, #eeeeee 100%);
303
+ background-image: -o-linear-gradient(top, white 50%, #eeeeee 100%);
304
+ background-image: linear-gradient(to bottom, white 50%, #eeeeee 100%);
305
+ background-repeat: repeat-x;
306
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
307
+ .select2-container--classic .select2-selection--single:focus {
308
+ border: 1px solid #5897fb; }
309
+ .select2-container--classic .select2-selection--single .select2-selection__rendered {
310
+ color: #444;
311
+ line-height: 28px; }
312
+ .select2-container--classic .select2-selection--single .select2-selection__clear {
313
+ cursor: pointer;
314
+ float: right;
315
+ font-weight: bold;
316
+ margin-right: 10px; }
317
+ .select2-container--classic .select2-selection--single .select2-selection__placeholder {
318
+ color: #999; }
319
+ .select2-container--classic .select2-selection--single .select2-selection__arrow {
320
+ background-color: #ddd;
321
+ border: none;
322
+ border-left: 1px solid #aaa;
323
+ border-top-right-radius: 4px;
324
+ border-bottom-right-radius: 4px;
325
+ height: 26px;
326
+ position: absolute;
327
+ top: 1px;
328
+ right: 1px;
329
+ width: 20px;
330
+ background-image: -webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
331
+ background-image: -o-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
332
+ background-image: linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%);
333
+ background-repeat: repeat-x;
334
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0); }
335
+ .select2-container--classic .select2-selection--single .select2-selection__arrow b {
336
+ border-color: #888 transparent transparent transparent;
337
+ border-style: solid;
338
+ border-width: 5px 4px 0 4px;
339
+ height: 0;
340
+ left: 50%;
341
+ margin-left: -4px;
342
+ margin-top: -2px;
343
+ position: absolute;
344
+ top: 50%;
345
+ width: 0; }
346
+
347
+ .select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear {
348
+ float: left; }
349
+
350
+ .select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow {
351
+ border: none;
352
+ border-right: 1px solid #aaa;
353
+ border-radius: 0;
354
+ border-top-left-radius: 4px;
355
+ border-bottom-left-radius: 4px;
356
+ left: 1px;
357
+ right: auto; }
358
+
359
+ .select2-container--classic.select2-container--open .select2-selection--single {
360
+ border: 1px solid #5897fb; }
361
+ .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow {
362
+ background: transparent;
363
+ border: none; }
364
+ .select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b {
365
+ border-color: transparent transparent #888 transparent;
366
+ border-width: 0 4px 5px 4px; }
367
+
368
+ .select2-container--classic.select2-container--open.select2-container--above .select2-selection--single {
369
+ border-top: none;
370
+ border-top-left-radius: 0;
371
+ border-top-right-radius: 0;
372
+ background-image: -webkit-linear-gradient(top, white 0%, #eeeeee 50%);
373
+ background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%);
374
+ background-image: linear-gradient(to bottom, white 0%, #eeeeee 50%);
375
+ background-repeat: repeat-x;
376
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
377
+
378
+ .select2-container--classic.select2-container--open.select2-container--below .select2-selection--single {
379
+ border-bottom: none;
380
+ border-bottom-left-radius: 0;
381
+ border-bottom-right-radius: 0;
382
+ background-image: -webkit-linear-gradient(top, #eeeeee 50%, white 100%);
383
+ background-image: -o-linear-gradient(top, #eeeeee 50%, white 100%);
384
+ background-image: linear-gradient(to bottom, #eeeeee 50%, white 100%);
385
+ background-repeat: repeat-x;
386
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0); }
387
+
388
+ .select2-container--classic .select2-selection--multiple {
389
+ background-color: white;
390
+ border: 1px solid #aaa;
391
+ border-radius: 4px;
392
+ cursor: text;
393
+ outline: 0; }
394
+ .select2-container--classic .select2-selection--multiple:focus {
395
+ border: 1px solid #5897fb; }
396
+ .select2-container--classic .select2-selection--multiple .select2-selection__rendered {
397
+ list-style: none;
398
+ margin: 0;
399
+ padding: 0 5px; }
400
+ .select2-container--classic .select2-selection--multiple .select2-selection__clear {
401
+ display: none; }
402
+ .select2-container--classic .select2-selection--multiple .select2-selection__choice {
403
+ background-color: #e4e4e4;
404
+ border: 1px solid #aaa;
405
+ border-radius: 4px;
406
+ cursor: default;
407
+ float: left;
408
+ margin-right: 5px;
409
+ margin-top: 5px;
410
+ padding: 0 5px; }
411
+ .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove {
412
+ color: #888;
413
+ cursor: pointer;
414
+ display: inline-block;
415
+ font-weight: bold;
416
+ margin-right: 2px; }
417
+ .select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover {
418
+ color: #555; }
419
+
420
+ .select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
421
+ float: right; }
422
+
423
+ .select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
424
+ margin-left: 5px;
425
+ margin-right: auto; }
426
+
427
+ .select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
428
+ margin-left: 2px;
429
+ margin-right: auto; }
430
+
431
+ .select2-container--classic.select2-container--open .select2-selection--multiple {
432
+ border: 1px solid #5897fb; }
433
+
434
+ .select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple {
435
+ border-top: none;
436
+ border-top-left-radius: 0;
437
+ border-top-right-radius: 0; }
438
+
439
+ .select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple {
440
+ border-bottom: none;
441
+ border-bottom-left-radius: 0;
442
+ border-bottom-right-radius: 0; }
443
+
444
+ .select2-container--classic .select2-search--dropdown .select2-search__field {
445
+ border: 1px solid #aaa;
446
+ outline: 0; }
447
+
448
+ .select2-container--classic .select2-search--inline .select2-search__field {
449
+ outline: 0;
450
+ box-shadow: none; }
451
+
452
+ .select2-container--classic .select2-dropdown {
453
+ background-color: white;
454
+ border: 1px solid transparent; }
455
+
456
+ .select2-container--classic .select2-dropdown--above {
457
+ border-bottom: none; }
458
+
459
+ .select2-container--classic .select2-dropdown--below {
460
+ border-top: none; }
461
+
462
+ .select2-container--classic .select2-results > .select2-results__options {
463
+ max-height: 200px;
464
+ overflow-y: auto; }
465
+
466
+ .select2-container--classic .select2-results__option[role=group] {
467
+ padding: 0; }
468
+
469
+ .select2-container--classic .select2-results__option[aria-disabled=true] {
470
+ color: grey; }
471
+
472
+ .select2-container--classic .select2-results__option--highlighted[aria-selected] {
473
+ background-color: #3875d7;
474
+ color: white; }
475
+
476
+ .select2-container--classic .select2-results__group {
477
+ cursor: default;
478
+ display: block;
479
+ padding: 6px; }
480
+
481
+ .select2-container--classic.select2-container--open .select2-dropdown {
482
+ border-color: #5897fb; }
includes/simba-tfa/includes/select2.js ADDED
@@ -0,0 +1,5580 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * Select2 4.0.2
3
+ * https://select2.github.io
4
+ *
5
+ * Released under the MIT license
6
+ * https://github.com/select2/select2/blob/master/LICENSE.md
7
+ */
8
+ (function (factory) {
9
+ if (typeof define === 'function' && define.amd) {
10
+ // AMD. Register as an anonymous module.
11
+ define(['jquery'], factory);
12
+ } else if (typeof exports === 'object') {
13
+ // Node/CommonJS
14
+ factory(require('jquery'));
15
+ } else {
16
+ // Browser globals
17
+ factory(jQuery);
18
+ }
19
+ }(function (jQuery) {
20
+ // This is needed so we can catch the AMD loader configuration and use it
21
+ // The inner file should be wrapped (by `banner.start.js`) in a function that
22
+ // returns the AMD loader references.
23
+ var S2 =
24
+ (function () {
25
+ // Restore the Select2 AMD loader so it can be used
26
+ // Needed mostly in the language files, where the loader is not inserted
27
+ if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) {
28
+ var S2 = jQuery.fn.select2.amd;
29
+ }
30
+ var S2;(function () { if (!S2 || !S2.requirejs) {
31
+ if (!S2) { S2 = {}; } else { require = S2; }
32
+ /**
33
+ * @license almond 0.3.1 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved.
34
+ * Available via the MIT or new BSD license.
35
+ * see: http://github.com/jrburke/almond for details
36
+ */
37
+ //Going sloppy to avoid 'use strict' string cost, but strict practices should
38
+ //be followed.
39
+ /*jslint sloppy: true */
40
+ /*global setTimeout: false */
41
+
42
+ var requirejs, require, define;
43
+ (function (undef) {
44
+ var main, req, makeMap, handlers,
45
+ defined = {},
46
+ waiting = {},
47
+ config = {},
48
+ defining = {},
49
+ hasOwn = Object.prototype.hasOwnProperty,
50
+ aps = [].slice,
51
+ jsSuffixRegExp = /\.js$/;
52
+
53
+ function hasProp(obj, prop) {
54
+ return hasOwn.call(obj, prop);
55
+ }
56
+
57
+ /**
58
+ * Given a relative module name, like ./something, normalize it to
59
+ * a real name that can be mapped to a path.
60
+ * @param {String} name the relative name
61
+ * @param {String} baseName a real name that the name arg is relative
62
+ * to.
63
+ * @returns {String} normalized name
64
+ */
65
+ function normalize(name, baseName) {
66
+ var nameParts, nameSegment, mapValue, foundMap, lastIndex,
67
+ foundI, foundStarMap, starI, i, j, part,
68
+ baseParts = baseName && baseName.split("/"),
69
+ map = config.map,
70
+ starMap = (map && map['*']) || {};
71
+
72
+ //Adjust any relative paths.
73
+ if (name && name.charAt(0) === ".") {
74
+ //If have a base name, try to normalize against it,
75
+ //otherwise, assume it is a top-level require that will
76
+ //be relative to baseUrl in the end.
77
+ if (baseName) {
78
+ name = name.split('/');
79
+ lastIndex = name.length - 1;
80
+
81
+ // Node .js allowance:
82
+ if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
83
+ name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
84
+ }
85
+
86
+ //Lop off the last part of baseParts, so that . matches the
87
+ //"directory" and not name of the baseName's module. For instance,
88
+ //baseName of "one/two/three", maps to "one/two/three.js", but we
89
+ //want the directory, "one/two" for this normalization.
90
+ name = baseParts.slice(0, baseParts.length - 1).concat(name);
91
+
92
+ //start trimDots
93
+ for (i = 0; i < name.length; i += 1) {
94
+ part = name[i];
95
+ if (part === ".") {
96
+ name.splice(i, 1);
97
+ i -= 1;
98
+ } else if (part === "..") {
99
+ if (i === 1 && (name[2] === '..' || name[0] === '..')) {
100
+ //End of the line. Keep at least one non-dot
101
+ //path segment at the front so it can be mapped
102
+ //correctly to disk. Otherwise, there is likely
103
+ //no path mapping for a path starting with '..'.
104
+ //This can still fail, but catches the most reasonable
105
+ //uses of ..
106
+ break;
107
+ } else if (i > 0) {
108
+ name.splice(i - 1, 2);
109
+ i -= 2;
110
+ }
111
+ }
112
+ }
113
+ //end trimDots
114
+
115
+ name = name.join("/");
116
+ } else if (name.indexOf('./') === 0) {
117
+ // No baseName, so this is ID is resolved relative
118
+ // to baseUrl, pull off the leading dot.
119
+ name = name.substring(2);
120
+ }
121
+ }
122
+
123
+ //Apply map config if available.
124
+ if ((baseParts || starMap) && map) {
125
+ nameParts = name.split('/');
126
+
127
+ for (i = nameParts.length; i > 0; i -= 1) {
128
+ nameSegment = nameParts.slice(0, i).join("/");
129
+
130
+ if (baseParts) {
131
+ //Find the longest baseName segment match in the config.
132
+ //So, do joins on the biggest to smallest lengths of baseParts.
133
+ for (j = baseParts.length; j > 0; j -= 1) {
134
+ mapValue = map[baseParts.slice(0, j).join('/')];
135
+
136
+ //baseName segment has config, find if it has one for
137
+ //this name.
138
+ if (mapValue) {
139
+ mapValue = mapValue[nameSegment];
140
+ if (mapValue) {
141
+ //Match, update name to the new value.
142
+ foundMap = mapValue;
143
+ foundI = i;
144
+ break;
145
+ }
146
+ }
147
+ }
148
+ }
149
+
150
+ if (foundMap) {
151
+ break;
152
+ }
153
+
154
+ //Check for a star map match, but just hold on to it,
155
+ //if there is a shorter segment match later in a matching
156
+ //config, then favor over this star map.
157
+ if (!foundStarMap && starMap && starMap[nameSegment]) {
158
+ foundStarMap = starMap[nameSegment];
159
+ starI = i;
160
+ }
161
+ }
162
+
163
+ if (!foundMap && foundStarMap) {
164
+ foundMap = foundStarMap;
165
+ foundI = starI;
166
+ }
167
+
168
+ if (foundMap) {
169
+ nameParts.splice(0, foundI, foundMap);
170
+ name = nameParts.join('/');
171
+ }
172
+ }
173
+
174
+ return name;
175
+ }
176
+
177
+ function makeRequire(relName, forceSync) {
178
+ return function () {
179
+ //A version of a require function that passes a moduleName
180
+ //value for items that may need to
181
+ //look up paths relative to the moduleName
182
+ var args = aps.call(arguments, 0);
183
+
184
+ //If first arg is not require('string'), and there is only
185
+ //one arg, it is the array form without a callback. Insert
186
+ //a null so that the following concat is correct.
187
+ if (typeof args[0] !== 'string' && args.length === 1) {
188
+ args.push(null);
189
+ }
190
+ return req.apply(undef, args.concat([relName, forceSync]));
191
+ };
192
+ }
193
+
194
+ function makeNormalize(relName) {
195
+ return function (name) {
196
+ return normalize(name, relName);
197
+ };
198
+ }
199
+
200
+ function makeLoad(depName) {
201
+ return function (value) {
202
+ defined[depName] = value;
203
+ };
204
+ }
205
+
206
+ function callDep(name) {
207
+ if (hasProp(waiting, name)) {
208
+ var args = waiting[name];
209
+ delete waiting[name];
210
+ defining[name] = true;
211
+ main.apply(undef, args);
212
+ }
213
+
214
+ if (!hasProp(defined, name) && !hasProp(defining, name)) {
215
+ throw new Error('No ' + name);
216
+ }
217
+ return defined[name];
218
+ }
219
+
220
+ //Turns a plugin!resource to [plugin, resource]
221
+ //with the plugin being undefined if the name
222
+ //did not have a plugin prefix.
223
+ function splitPrefix(name) {
224
+ var prefix,
225
+ index = name ? name.indexOf('!') : -1;
226
+ if (index > -1) {
227
+ prefix = name.substring(0, index);
228
+ name = name.substring(index + 1, name.length);
229
+ }
230
+ return [prefix, name];
231
+ }
232
+
233
+ /**
234
+ * Makes a name map, normalizing the name, and using a plugin
235
+ * for normalization if necessary. Grabs a ref to plugin
236
+ * too, as an optimization.
237
+ */
238
+ makeMap = function (name, relName) {
239
+ var plugin,
240
+ parts = splitPrefix(name),
241
+ prefix = parts[0];
242
+
243
+ name = parts[1];
244
+
245
+ if (prefix) {
246
+ prefix = normalize(prefix, relName);
247
+ plugin = callDep(prefix);
248
+ }
249
+
250
+ //Normalize according
251
+ if (prefix) {
252
+ if (plugin && plugin.normalize) {
253
+ name = plugin.normalize(name, makeNormalize(relName));
254
+ } else {
255
+ name = normalize(name, relName);
256
+ }
257
+ } else {
258
+ name = normalize(name, relName);
259
+ parts = splitPrefix(name);
260
+ prefix = parts[0];
261
+ name = parts[1];
262
+ if (prefix) {
263
+ plugin = callDep(prefix);
264
+ }
265
+ }
266
+
267
+ //Using ridiculous property names for space reasons
268
+ return {
269
+ f: prefix ? prefix + '!' + name : name, //fullName
270
+ n: name,
271
+ pr: prefix,
272
+ p: plugin
273
+ };
274
+ };
275
+
276
+ function makeConfig(name) {
277
+ return function () {
278
+ return (config && config.config && config.config[name]) || {};
279
+ };
280
+ }
281
+
282
+ handlers = {
283
+ require: function (name) {
284
+ return makeRequire(name);
285
+ },
286
+ exports: function (name) {
287
+ var e = defined[name];
288
+ if (typeof e !== 'undefined') {
289
+ return e;
290
+ } else {
291
+ return (defined[name] = {});
292
+ }
293
+ },
294
+ module: function (name) {
295
+ return {
296
+ id: name,
297
+ uri: '',
298
+ exports: defined[name],
299
+ config: makeConfig(name)
300
+ };
301
+ }
302
+ };
303
+
304
+ main = function (name, deps, callback, relName) {
305
+ var cjsModule, depName, ret, map, i,
306
+ args = [],
307
+ callbackType = typeof callback,
308
+ usingExports;
309
+
310
+ //Use name if no relName
311
+ relName = relName || name;
312
+
313
+ //Call the callback to define the module, if necessary.
314
+ if (callbackType === 'undefined' || callbackType === 'function') {
315
+ //Pull out the defined dependencies and pass the ordered
316
+ //values to the callback.
317
+ //Default to [require, exports, module] if no deps
318
+ deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
319
+ for (i = 0; i < deps.length; i += 1) {
320
+ map = makeMap(deps[i], relName);
321
+ depName = map.f;
322
+
323
+ //Fast path CommonJS standard dependencies.
324
+ if (depName === "require") {
325
+ args[i] = handlers.require(name);
326
+ } else if (depName === "exports") {
327
+ //CommonJS module spec 1.1
328
+ args[i] = handlers.exports(name);
329
+ usingExports = true;
330
+ } else if (depName === "module") {
331
+ //CommonJS module spec 1.1
332
+ cjsModule = args[i] = handlers.module(name);
333
+ } else if (hasProp(defined, depName) ||
334
+ hasProp(waiting, depName) ||
335
+ hasProp(defining, depName)) {
336
+ args[i] = callDep(depName);
337
+ } else if (map.p) {
338
+ map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
339
+ args[i] = defined[depName];
340
+ } else {
341
+ throw new Error(name + ' missing ' + depName);
342
+ }
343
+ }
344
+
345
+ ret = callback ? callback.apply(defined[name], args) : undefined;
346
+
347
+ if (name) {
348
+ //If setting exports via "module" is in play,
349
+ //favor that over return value and exports. After that,
350
+ //favor a non-undefined return value over exports use.
351
+ if (cjsModule && cjsModule.exports !== undef &&
352
+ cjsModule.exports !== defined[name]) {
353
+ defined[name] = cjsModule.exports;
354
+ } else if (ret !== undef || !usingExports) {
355
+ //Use the return value from the function.
356
+ defined[name] = ret;
357
+ }
358
+ }
359
+ } else if (name) {
360
+ //May just be an object definition for the module. Only
361
+ //worry about defining if have a module name.
362
+ defined[name] = callback;
363
+ }
364
+ };
365
+
366
+ requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
367
+ if (typeof deps === "string") {
368
+ if (handlers[deps]) {
369
+ //callback in this case is really relName
370
+ return handlers[deps](callback);
371
+ }
372
+ //Just return the module wanted. In this scenario, the
373
+ //deps arg is the module name, and second arg (if passed)
374
+ //is just the relName.
375
+ //Normalize module name, if it contains . or ..
376
+ return callDep(makeMap(deps, callback).f);
377
+ } else if (!deps.splice) {
378
+ //deps is a config object, not an array.
379
+ config = deps;
380
+ if (config.deps) {
381
+ req(config.deps, config.callback);
382
+ }
383
+ if (!callback) {
384
+ return;
385
+ }
386
+
387
+ if (callback.splice) {
388
+ //callback is an array, which means it is a dependency list.
389
+ //Adjust args if there are dependencies
390
+ deps = callback;
391
+ callback = relName;
392
+ relName = null;
393
+ } else {
394
+ deps = undef;
395
+ }
396
+ }
397
+
398
+ //Support require(['a'])
399
+ callback = callback || function () {};
400
+
401
+ //If relName is a function, it is an errback handler,
402
+ //so remove it.
403
+ if (typeof relName === 'function') {
404
+ relName = forceSync;
405
+ forceSync = alt;
406
+ }
407
+
408
+ //Simulate async callback;
409
+ if (forceSync) {
410
+ main(undef, deps, callback, relName);
411
+ } else {
412
+ //Using a non-zero value because of concern for what old browsers
413
+ //do, and latest browsers "upgrade" to 4 if lower value is used:
414
+ //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
415
+ //If want a value immediately, use require('id') instead -- something
416
+ //that works in almond on the global level, but not guaranteed and
417
+ //unlikely to work in other AMD implementations.
418
+ setTimeout(function () {
419
+ main(undef, deps, callback, relName);
420
+ }, 4);
421
+ }
422
+
423
+ return req;
424
+ };
425
+
426
+ /**
427
+ * Just drops the config on the floor, but returns req in case
428
+ * the config return value is used.
429
+ */
430
+ req.config = function (cfg) {
431
+ return req(cfg);
432
+ };
433
+
434
+ /**
435
+ * Expose module registry for debugging and tooling
436
+ */
437
+ requirejs._defined = defined;
438
+
439
+ define = function (name, deps, callback) {
440
+ if (typeof name !== 'string') {
441
+ throw new Error('See almond README: incorrect module build, no module name');
442
+ }
443
+
444
+ //This module may not have dependencies
445
+ if (!deps.splice) {
446
+ //deps is not an array, so probably means
447
+ //an object literal or factory function for
448
+ //the value. Adjust args.
449
+ callback = deps;
450
+ deps = [];
451
+ }
452
+
453
+ if (!hasProp(defined, name) && !hasProp(waiting, name)) {
454
+ waiting[name] = [name, deps, callback];
455
+ }
456
+ };
457
+
458
+ define.amd = {
459
+ jQuery: true
460
+ };
461
+ }());
462
+
463
+ S2.requirejs = requirejs;S2.require = require;S2.define = define;
464
+ }
465
+ }());
466
+ S2.define("almond", function(){});
467
+
468
+ /* global jQuery:false, $:false */
469
+ S2.define('jquery',[],function () {
470
+ var _$ = jQuery || $;
471
+
472
+ if (_$ == null && console && console.error) {
473
+ console.error(
474
+ 'Select2: An instance of jQuery or a jQuery-compatible library was not ' +
475
+ 'found. Make sure that you are including jQuery before Select2 on your ' +
476
+ 'web page.'
477
+ );
478
+ }
479
+
480
+ return _$;
481
+ });
482
+
483
+ S2.define('select2/utils',[
484
+ 'jquery'
485
+ ], function ($) {
486
+ var Utils = {};
487
+
488
+ Utils.Extend = function (ChildClass, SuperClass) {
489
+ var __hasProp = {}.hasOwnProperty;
490
+
491
+ function BaseConstructor () {
492
+ this.constructor = ChildClass;
493
+ }
494
+
495
+ for (var key in SuperClass) {
496
+ if (__hasProp.call(SuperClass, key)) {
497
+ ChildClass[key] = SuperClass[key];
498
+ }
499
+ }
500
+
501
+ BaseConstructor.prototype = SuperClass.prototype;
502
+ ChildClass.prototype = new BaseConstructor();
503
+ ChildClass.__super__ = SuperClass.prototype;
504
+
505
+ return ChildClass;
506
+ };
507
+
508
+ function getMethods (theClass) {
509
+ var proto = theClass.prototype;
510
+
511
+ var methods = [];
512
+
513
+ for (var methodName in proto) {
514
+ var m = proto[methodName];
515
+
516
+ if (typeof m !== 'function') {
517
+ continue;
518
+ }
519
+
520
+ if (methodName === 'constructor') {
521
+ continue;
522
+ }
523
+
524
+ methods.push(methodName);
525
+ }
526
+
527
+ return methods;
528
+ }
529
+
530
+ Utils.Decorate = function (SuperClass, DecoratorClass) {
531
+ var decoratedMethods = getMethods(DecoratorClass);
532
+ var superMethods = getMethods(SuperClass);
533
+
534
+ function DecoratedClass () {
535
+ var unshift = Array.prototype.unshift;
536
+
537
+ var argCount = DecoratorClass.prototype.constructor.length;
538
+
539
+ var calledConstructor = SuperClass.prototype.constructor;
540
+
541
+ if (argCount > 0) {
542
+ unshift.call(arguments, SuperClass.prototype.constructor);
543
+
544
+ calledConstructor = DecoratorClass.prototype.constructor;
545
+ }
546
+
547
+ calledConstructor.apply(this, arguments);
548
+ }
549
+
550
+ DecoratorClass.displayName = SuperClass.displayName;
551
+
552
+ function ctr () {
553
+ this.constructor = DecoratedClass;
554
+ }
555
+
556
+ DecoratedClass.prototype = new ctr();
557
+
558
+ for (var m = 0; m < superMethods.length; m++) {
559
+ var superMethod = superMethods[m];
560
+
561
+ DecoratedClass.prototype[superMethod] =
562
+ SuperClass.prototype[superMethod];
563
+ }
564
+
565
+ var calledMethod = function (methodName) {
566
+ // Stub out the original method if it's not decorating an actual method
567
+ var originalMethod = function () {};
568
+
569
+ if (methodName in DecoratedClass.prototype) {
570
+ originalMethod = DecoratedClass.prototype[methodName];
571
+ }
572
+
573
+ var decoratedMethod = DecoratorClass.prototype[methodName];
574
+
575
+ return function () {
576
+ var unshift = Array.prototype.unshift;
577
+
578
+ unshift.call(arguments, originalMethod);
579
+
580
+ return decoratedMethod.apply(this, arguments);
581
+ };
582
+ };
583
+
584
+ for (var d = 0; d < decoratedMethods.length; d++) {
585
+ var decoratedMethod = decoratedMethods[d];
586
+
587
+ DecoratedClass.prototype[decoratedMethod] = calledMethod(decoratedMethod);
588
+ }
589
+
590
+ return DecoratedClass;
591
+ };
592
+
593
+ var Observable = function () {
594
+ this.listeners = {};
595
+ };
596
+
597
+ Observable.prototype.on = function (event, callback) {
598
+ this.listeners = this.listeners || {};
599
+
600
+ if (event in this.listeners) {
601
+ this.listeners[event].push(callback);
602
+ } else {
603
+ this.listeners[event] = [callback];
604
+ }
605
+ };
606
+
607
+ Observable.prototype.trigger = function (event) {
608
+ var slice = Array.prototype.slice;
609
+
610
+ this.listeners = this.listeners || {};
611
+
612
+ if (event in this.listeners) {
613
+ this.invoke(this.listeners[event], slice.call(arguments, 1));
614
+ }
615
+
616
+ if ('*' in this.listeners) {
617
+ this.invoke(this.listeners['*'], arguments);
618
+ }
619
+ };
620
+
621
+ Observable.prototype.invoke = function (listeners, params) {
622
+ for (var i = 0, len = listeners.length; i < len; i++) {
623
+ listeners[i].apply(this, params);
624
+ }
625
+ };
626
+
627
+ Utils.Observable = Observable;
628
+
629
+ Utils.generateChars = function (length) {
630
+ var chars = '';
631
+
632
+ for (var i = 0; i < length; i++) {
633
+ var randomChar = Math.floor(Math.random() * 36);
634
+ chars += randomChar.toString(36);
635
+ }
636
+
637
+ return chars;
638
+ };
639
+
640
+ Utils.bind = function (func, context) {
641
+ return function () {
642
+ func.apply(context, arguments);
643
+ };
644
+ };
645
+
646
+ Utils._convertData = function (data) {
647
+ for (var originalKey in data) {
648
+ var keys = originalKey.split('-');
649
+
650
+ var dataLevel = data;
651
+
652
+ if (keys.length === 1) {
653
+ continue;
654
+ }
655
+
656
+ for (var k = 0; k < keys.length; k++) {
657
+ var key = keys[k];
658
+
659
+ // Lowercase the first letter
660
+ // By default, dash-separated becomes camelCase
661
+ key = key.substring(0, 1).toLowerCase() + key.substring(1);
662
+
663
+ if (!(key in dataLevel)) {
664
+ dataLevel[key] = {};
665
+ }
666
+
667
+ if (k == keys.length - 1) {
668
+ dataLevel[key] = data[originalKey];
669
+ }
670
+
671
+ dataLevel = dataLevel[key];
672
+ }
673
+
674
+ delete data[originalKey];
675
+ }
676
+
677
+ return data;
678
+ };
679
+
680
+ Utils.hasScroll = function (index, el) {
681
+ // Adapted from the function created by @ShadowScripter
682
+ // and adapted by @BillBarry on the Stack Exchange Code Review website.
683
+ // The original code can be found at
684
+ // http://codereview.stackexchange.com/q/13338
685
+ // and was designed to be used with the Sizzle selector engine.
686
+
687
+ var $el = $(el);
688
+ var overflowX = el.style.overflowX;
689
+ var overflowY = el.style.overflowY;
690
+
691
+ //Check both x and y declarations
692
+ if (overflowX === overflowY &&
693
+ (overflowY === 'hidden' || overflowY === 'visible')) {
694
+ return false;
695
+ }
696
+
697
+ if (overflowX === 'scroll' || overflowY === 'scroll') {
698
+ return true;
699
+ }
700
+
701
+ return ($el.innerHeight() < el.scrollHeight ||
702
+ $el.innerWidth() < el.scrollWidth);
703
+ };
704
+
705
+ Utils.escapeMarkup = function (markup) {
706
+ var replaceMap = {
707
+ '\\': '&#92;',
708
+ '&': '&amp;',
709
+ '<': '&lt;',
710
+ '>': '&gt;',
711
+ '"': '&quot;',
712
+ '\'': '&#39;',
713
+ '/': '&#47;'
714
+ };
715
+
716
+ // Do not try to escape the markup if it's not a string
717
+ if (typeof markup !== 'string') {
718
+ return markup;
719
+ }
720
+
721
+ return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
722
+ return replaceMap[match];
723
+ });
724
+ };
725
+
726
+ // Append an array of jQuery nodes to a given element.
727
+ Utils.appendMany = function ($element, $nodes) {
728
+ // jQuery 1.7.x does not support $.fn.append() with an array
729
+ // Fall back to a jQuery object collection using $.fn.add()
730
+ if ($.fn.jquery.substr(0, 3) === '1.7') {
731
+ var $jqNodes = $();
732
+
733
+ $.map($nodes, function (node) {
734
+ $jqNodes = $jqNodes.add(node);
735
+ });
736
+
737
+ $nodes = $jqNodes;
738
+ }
739
+
740
+ $element.append($nodes);
741
+ };
742
+
743
+ return Utils;
744
+ });
745
+
746
+ S2.define('select2/results',[
747
+ 'jquery',
748
+ './utils'
749
+ ], function ($, Utils) {
750
+ function Results ($element, options, dataAdapter) {
751
+ this.$element = $element;
752
+ this.data = dataAdapter;
753
+ this.options = options;
754
+
755
+ Results.__super__.constructor.call(this);
756
+ }
757
+
758
+ Utils.Extend(Results, Utils.Observable);
759
+
760
+ Results.prototype.render = function () {
761
+ var $results = $(
762
+ '<ul class="select2-results__options" role="tree"></ul>'
763
+ );
764
+
765
+ if (this.options.get('multiple')) {
766
+ $results.attr('aria-multiselectable', 'true');
767
+ }
768
+
769
+ this.$results = $results;
770
+
771
+ return $results;
772
+ };
773
+
774
+ Results.prototype.clear = function () {
775
+ this.$results.empty();
776
+ };
777
+
778
+ Results.prototype.displayMessage = function (params) {
779
+ var escapeMarkup = this.options.get('escapeMarkup');
780
+
781
+ this.clear();
782
+ this.hideLoading();
783
+
784
+ var $message = $(
785
+ '<li role="treeitem" aria-live="assertive"' +
786
+ ' class="select2-results__option"></li>'
787
+ );
788
+
789
+ var message = this.options.get('translations').get(params.message);
790
+
791
+ $message.append(
792
+ escapeMarkup(
793
+ message(params.args)
794
+ )
795
+ );
796
+
797
+ $message[0].className += ' select2-results__message';
798
+
799
+ this.$results.append($message);
800
+ };
801
+
802
+ Results.prototype.hideMessages = function () {
803
+ this.$results.find('.select2-results__message').remove();
804
+ };
805
+
806
+ Results.prototype.append = function (data) {
807
+ this.hideLoading();
808
+
809
+ var $options = [];
810
+
811
+ if (data.results == null || data.results.length === 0) {
812
+ if (this.$results.children().length === 0) {
813
+ this.trigger('results:message', {
814
+ message: 'noResults'
815
+ });
816
+ }
817
+
818
+ return;
819
+ }
820
+
821
+ data.results = this.sort(data.results);
822
+
823
+ for (var d = 0; d < data.results.length; d++) {
824
+ var item = data.results[d];
825
+
826
+ var $option = this.option(item);
827
+
828
+ $options.push($option);
829
+ }
830
+
831
+ this.$results.append($options);
832
+ };
833
+
834
+ Results.prototype.position = function ($results, $dropdown) {
835
+ var $resultsContainer = $dropdown.find('.select2-results');
836
+ $resultsContainer.append($results);
837
+ };
838
+
839
+ Results.prototype.sort = function (data) {
840
+ var sorter = this.options.get('sorter');
841
+
842
+ return sorter(data);
843
+ };
844
+
845
+ Results.prototype.setClasses = function () {
846
+ var self = this;
847
+
848
+ this.data.current(function (selected) {
849
+ var selectedIds = $.map(selected, function (s) {
850
+ return s.id.toString();
851
+ });
852
+
853
+ var $options = self.$results
854
+ .find('.select2-results__option[aria-selected]');
855
+
856
+ $options.each(function () {
857
+ var $option = $(this);
858
+
859
+ var item = $.data(this, 'data');
860
+
861
+ // id needs to be converted to a string when comparing
862
+ var id = '' + item.id;
863
+
864
+ if ((item.element != null && item.element.selected) ||
865
+ (item.element == null && $.inArray(id, selectedIds) > -1)) {
866
+ $option.attr('aria-selected', 'true');
867
+ } else {
868
+ $option.attr('aria-selected', 'false');
869
+ }
870
+ });
871
+
872
+ var $selected = $options.filter('[aria-selected=true]');
873
+
874
+ // Check if there are any selected options
875
+ if ($selected.length > 0) {
876
+ // If there are selected options, highlight the first
877
+ $selected.first().trigger('mouseenter');
878
+ } else {
879
+ // If there are no selected options, highlight the first option
880
+ // in the dropdown
881
+ $options.first().trigger('mouseenter');
882
+ }
883
+ });
884
+ };
885
+
886
+ Results.prototype.showLoading = function (params) {
887
+ this.hideLoading();
888
+
889
+ var loadingMore = this.options.get('translations').get('searching');
890
+
891
+ var loading = {
892
+ disabled: true,
893
+ loading: true,
894
+ text: loadingMore(params)
895
+ };
896
+ var $loading = this.option(loading);
897
+ $loading.className += ' loading-results';
898
+
899
+ this.$results.prepend($loading);
900
+ };
901
+
902
+ Results.prototype.hideLoading = function () {
903
+ this.$results.find('.loading-results').remove();
904
+ };
905
+
906
+ Results.prototype.option = function (data) {
907
+ var option = document.createElement('li');
908
+ option.className = 'select2-results__option';
909
+
910
+ var attrs = {
911
+ 'role': 'treeitem',
912
+ 'aria-selected': 'false'
913
+ };
914
+
915
+ if (data.disabled) {
916
+ delete attrs['aria-selected'];
917
+ attrs['aria-disabled'] = 'true';
918
+ }
919
+
920
+ if (data.id == null) {
921
+ delete attrs['aria-selected'];
922
+ }
923
+
924
+ if (data._resultId != null) {
925
+ option.id = data._resultId;
926
+ }
927
+
928
+ if (data.title) {
929
+ option.title = data.title;
930
+ }
931
+
932
+ if (data.children) {
933
+ attrs.role = 'group';
934
+ attrs['aria-label'] = data.text;
935
+ delete attrs['aria-selected'];
936
+ }
937
+
938
+ for (var attr in attrs) {
939
+ var val = attrs[attr];
940
+
941
+ option.setAttribute(attr, val);
942
+ }
943
+
944
+ if (data.children) {
945
+ var $option = $(option);
946
+
947
+ var label = document.createElement('strong');
948
+ label.className = 'select2-results__group';
949
+
950
+ var $label = $(label);
951
+ this.template(data, label);
952
+
953
+ var $children = [];
954
+
955
+ for (var c = 0; c < data.children.length; c++) {
956
+ var child = data.children[c];
957
+
958
+ var $child = this.option(child);
959
+
960
+ $children.push($child);
961
+ }
962
+
963
+ var $childrenContainer = $('<ul></ul>', {
964
+ 'class': 'select2-results__options select2-results__options--nested'
965
+ });
966
+
967
+ $childrenContainer.append($children);
968
+
969
+ $option.append(label);
970
+ $option.append($childrenContainer);
971
+ } else {
972
+ this.template(data, option);
973
+ }
974
+
975
+ $.data(option, 'data', data);
976
+
977
+ return option;
978
+ };
979
+
980
+ Results.prototype.bind = function (container, $container) {
981
+ var self = this;
982
+
983
+ var id = container.id + '-results';
984
+
985
+ this.$results.attr('id', id);
986
+
987
+ container.on('results:all', function (params) {
988
+ self.clear();
989
+ self.append(params.data);
990
+
991
+ if (container.isOpen()) {
992
+ self.setClasses();
993
+ }
994
+ });
995
+
996
+ container.on('results:append', function (params) {
997
+ self.append(params.data);
998
+
999
+ if (container.isOpen()) {
1000
+ self.setClasses();
1001
+ }
1002
+ });
1003
+
1004
+ container.on('query', function (params) {
1005
+ self.hideMessages();
1006
+ self.showLoading(params);
1007
+ });
1008
+
1009
+ container.on('select', function () {
1010
+ if (!container.isOpen()) {
1011
+ return;
1012
+ }
1013
+
1014
+ self.setClasses();
1015
+ });
1016
+
1017
+ container.on('unselect', function () {
1018
+ if (!container.isOpen()) {
1019
+ return;
1020
+ }
1021
+
1022
+ self.setClasses();
1023
+ });
1024
+
1025
+ container.on('open', function () {
1026
+ // When the dropdown is open, aria-expended="true"
1027
+ self.$results.attr('aria-expanded', 'true');
1028
+ self.$results.attr('aria-hidden', 'false');
1029
+
1030
+ self.setClasses();
1031
+ self.ensureHighlightVisible();
1032
+ });
1033
+
1034
+ container.on('close', function () {
1035
+ // When the dropdown is closed, aria-expended="false"
1036
+ self.$results.attr('aria-expanded', 'false');
1037
+ self.$results.attr('aria-hidden', 'true');
1038
+ self.$results.removeAttr('aria-activedescendant');
1039
+ });
1040
+
1041
+ container.on('results:toggle', function () {
1042
+ var $highlighted = self.getHighlightedResults();
1043
+
1044
+ if ($highlighted.length === 0) {
1045
+ return;
1046
+ }
1047
+
1048
+ $highlighted.trigger('mouseup');
1049
+ });
1050
+
1051
+ container.on('results:select', function () {
1052
+ var $highlighted = self.getHighlightedResults();
1053
+
1054
+ if ($highlighted.length === 0) {
1055
+ return;
1056
+ }
1057
+
1058
+ var data = $highlighted.data('data');
1059
+
1060
+ if ($highlighted.attr('aria-selected') == 'true') {
1061
+ self.trigger('close', {});
1062
+ } else {
1063
+ self.trigger('select', {
1064
+ data: data
1065
+ });
1066
+ }
1067
+ });
1068
+
1069
+ container.on('results:previous', function () {
1070
+ var $highlighted = self.getHighlightedResults();
1071
+
1072
+ var $options = self.$results.find('[aria-selected]');
1073
+
1074
+ var currentIndex = $options.index($highlighted);
1075
+
1076
+ // If we are already at te top, don't move further
1077
+ if (currentIndex === 0) {
1078
+ return;
1079
+ }
1080
+
1081
+ var nextIndex = currentIndex - 1;
1082
+
1083
+ // If none are highlighted, highlight the first
1084
+ if ($highlighted.length === 0) {
1085
+ nextIndex = 0;
1086
+ }
1087
+
1088
+ var $next = $options.eq(nextIndex);
1089
+
1090
+ $next.trigger('mouseenter');
1091
+
1092
+ var currentOffset = self.$results.offset().top;
1093
+ var nextTop = $next.offset().top;
1094
+ var nextOffset = self.$results.scrollTop() + (nextTop - currentOffset);
1095
+
1096
+ if (nextIndex === 0) {
1097
+ self.$results.scrollTop(0);
1098
+ } else if (nextTop - currentOffset < 0) {
1099
+ self.$results.scrollTop(nextOffset);
1100
+ }
1101
+ });
1102
+
1103
+ container.on('results:next', function () {
1104
+ var $highlighted = self.getHighlightedResults();
1105
+
1106
+ var $options = self.$results.find('[aria-selected]');
1107
+
1108
+ var currentIndex = $options.index($highlighted);
1109
+
1110
+ var nextIndex = currentIndex + 1;
1111
+
1112
+ // If we are at the last option, stay there
1113
+ if (nextIndex >= $options.length) {
1114
+ return;
1115
+ }
1116
+
1117
+ var $next = $options.eq(nextIndex);
1118
+
1119
+ $next.trigger('mouseenter');
1120
+
1121
+ var currentOffset = self.$results.offset().top +
1122
+ self.$results.outerHeight(false);
1123
+ var nextBottom = $next.offset().top + $next.outerHeight(false);
1124
+ var nextOffset = self.$results.scrollTop() + nextBottom - currentOffset;
1125
+
1126
+ if (nextIndex === 0) {
1127
+ self.$results.scrollTop(0);
1128
+ } else if (nextBottom > currentOffset) {
1129
+ self.$results.scrollTop(nextOffset);
1130
+ }
1131
+ });
1132
+
1133
+ container.on('results:focus', function (params) {
1134
+ params.element.addClass('select2-results__option--highlighted');
1135
+ });
1136
+
1137
+ container.on('results:message', function (params) {
1138
+ self.displayMessage(params);
1139
+ });
1140
+
1141
+ if ($.fn.mousewheel) {
1142
+ this.$results.on('mousewheel', function (e) {
1143
+ var top = self.$results.scrollTop();
1144
+
1145
+ var bottom = self.$results.get(0).scrollHeight - top + e.deltaY;
1146
+
1147
+ var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0;
1148
+ var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height();
1149
+
1150
+ if (isAtTop) {
1151
+ self.$results.scrollTop(0);
1152
+
1153
+ e.preventDefault();
1154
+ e.stopPropagation();
1155
+ } else if (isAtBottom) {
1156
+ self.$results.scrollTop(
1157
+ self.$results.get(0).scrollHeight - self.$results.height()
1158
+ );
1159
+
1160
+ e.preventDefault();
1161
+ e.stopPropagation();
1162
+ }
1163
+ });
1164
+ }
1165
+
1166
+ this.$results.on('mouseup', '.select2-results__option[aria-selected]',
1167
+ function (evt) {
1168
+ var $this = $(this);
1169
+
1170
+ var data = $this.data('data');
1171
+
1172
+ if ($this.attr('aria-selected') === 'true') {
1173
+ if (self.options.get('multiple')) {
1174
+ self.trigger('unselect', {
1175
+ originalEvent: evt,
1176
+ data: data
1177
+ });
1178
+ } else {
1179
+ self.trigger('close', {});
1180
+ }
1181
+
1182
+ return;
1183
+ }
1184
+
1185
+ self.trigger('select', {
1186
+ originalEvent: evt,
1187
+ data: data
1188
+ });
1189
+ });
1190
+
1191
+ this.$results.on('mouseenter', '.select2-results__option[aria-selected]',
1192
+ function (evt) {
1193
+ var data = $(this).data('data');
1194
+
1195
+ self.getHighlightedResults()
1196
+ .removeClass('select2-results__option--highlighted');
1197
+
1198
+ self.trigger('results:focus', {
1199
+ data: data,
1200
+ element: $(this)
1201
+ });
1202
+ });
1203
+ };
1204
+
1205
+ Results.prototype.getHighlightedResults = function () {
1206
+ var $highlighted = this.$results
1207
+ .find('.select2-results__option--highlighted');
1208
+
1209
+ return $highlighted;
1210
+ };
1211
+
1212
+ Results.prototype.destroy = function () {
1213
+ this.$results.remove();
1214
+ };
1215
+
1216
+ Results.prototype.ensureHighlightVisible = function () {
1217
+ var $highlighted = this.getHighlightedResults();
1218
+
1219
+ if ($highlighted.length === 0) {
1220
+ return;
1221
+ }
1222
+
1223
+ var $options = this.$results.find('[aria-selected]');
1224
+
1225
+ var currentIndex = $options.index($highlighted);
1226
+
1227
+ var currentOffset = this.$results.offset().top;
1228
+ var nextTop = $highlighted.offset().top;
1229
+ var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset);
1230
+
1231
+ var offsetDelta = nextTop - currentOffset;
1232
+ nextOffset -= $highlighted.outerHeight(false) * 2;
1233
+
1234
+ if (currentIndex <= 2) {
1235
+ this.$results.scrollTop(0);
1236
+ } else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) {
1237
+ this.$results.scrollTop(nextOffset);
1238
+ }
1239
+ };
1240
+
1241
+ Results.prototype.template = function (result, container) {
1242
+ var template = this.options.get('templateResult');
1243
+ var escapeMarkup = this.options.get('escapeMarkup');
1244
+
1245
+ var content = template(result, container);
1246
+
1247
+ if (content == null) {
1248
+ container.style.display = 'none';
1249
+ } else if (typeof content === 'string') {
1250
+ container.innerHTML = escapeMarkup(content);
1251
+ } else {
1252
+ $(container).append(content);
1253
+ }
1254
+ };
1255
+
1256
+ return Results;
1257
+ });
1258
+
1259
+ S2.define('select2/keys',[
1260
+
1261
+ ], function () {
1262
+ var KEYS = {
1263
+ BACKSPACE: 8,
1264
+ TAB: 9,
1265
+ ENTER: 13,
1266
+ SHIFT: 16,
1267
+ CTRL: 17,
1268
+ ALT: 18,
1269
+ ESC: 27,
1270
+ SPACE: 32,
1271
+ PAGE_UP: 33,
1272
+ PAGE_DOWN: 34,
1273
+ END: 35,
1274
+ HOME: 36,
1275
+ LEFT: 37,
1276
+ UP: 38,
1277
+ RIGHT: 39,
1278
+ DOWN: 40,
1279
+ DELETE: 46
1280
+ };
1281
+
1282
+ return KEYS;
1283
+ });
1284
+
1285
+ S2.define('select2/selection/base',[
1286
+ 'jquery',
1287
+ '../utils',
1288
+ '../keys'
1289
+ ], function ($, Utils, KEYS) {
1290
+ function BaseSelection ($element, options) {
1291
+ this.$element = $element;
1292
+ this.options = options;
1293
+
1294
+ BaseSelection.__super__.constructor.call(this);
1295
+ }
1296
+
1297
+ Utils.Extend(BaseSelection, Utils.Observable);
1298
+
1299
+ BaseSelection.prototype.render = function () {
1300
+ var $selection = $(
1301
+ '<span class="select2-selection" role="combobox" ' +
1302
+ ' aria-haspopup="true" aria-expanded="false">' +
1303
+ '</span>'
1304
+ );
1305
+
1306
+ this._tabindex = 0;
1307
+
1308
+ if (this.$element.data('old-tabindex') != null) {
1309
+ this._tabindex = this.$element.data('old-tabindex');
1310
+ } else if (this.$element.attr('tabindex') != null) {
1311
+ this._tabindex = this.$element.attr('tabindex');
1312
+ }
1313
+
1314
+ $selection.attr('title', this.$element.attr('title'));
1315
+ $selection.attr('tabindex', this._tabindex);
1316
+
1317
+ this.$selection = $selection;
1318
+
1319
+ return $selection;
1320
+ };
1321
+
1322
+ BaseSelection.prototype.bind = function (container, $container) {
1323
+ var self = this;
1324
+
1325
+ var id = container.id + '-container';
1326
+ var resultsId = container.id + '-results';
1327
+
1328
+ this.container = container;
1329
+
1330
+ this.$selection.on('focus', function (evt) {
1331
+ self.trigger('focus', evt);
1332
+ });
1333
+
1334
+ this.$selection.on('blur', function (evt) {
1335
+ self._handleBlur(evt);
1336
+ });
1337
+
1338
+ this.$selection.on('keydown', function (evt) {
1339
+ self.trigger('keypress', evt);
1340
+
1341
+ if (evt.which === KEYS.SPACE) {
1342
+ evt.preventDefault();
1343
+ }
1344
+ });
1345
+
1346
+ container.on('results:focus', function (params) {
1347
+ self.$selection.attr('aria-activedescendant', params.data._resultId);
1348
+ });
1349
+
1350
+ container.on('selection:update', function (params) {
1351
+ self.update(params.data);
1352
+ });
1353
+
1354
+ container.on('open', function () {
1355
+ // When the dropdown is open, aria-expanded="true"
1356
+ self.$selection.attr('aria-expanded', 'true');
1357
+ self.$selection.attr('aria-owns', resultsId);
1358
+
1359
+ self._attachCloseHandler(container);
1360
+ });
1361
+
1362
+ container.on('close', function () {
1363
+ // When the dropdown is closed, aria-expanded="false"
1364
+ self.$selection.attr('aria-expanded', 'false');
1365
+ self.$selection.removeAttr('aria-activedescendant');
1366
+ self.$selection.removeAttr('aria-owns');
1367
+
1368
+ self.$selection.focus();
1369
+
1370
+ self._detachCloseHandler(container);
1371
+ });
1372
+
1373
+ container.on('enable', function () {
1374
+ self.$selection.attr('tabindex', self._tabindex);
1375
+ });
1376
+
1377
+ container.on('disable', function () {
1378
+ self.$selection.attr('tabindex', '-1');
1379
+ });
1380
+ };
1381
+
1382
+ BaseSelection.prototype._handleBlur = function (evt) {
1383
+ var self = this;
1384
+
1385
+ // This needs to be delayed as the active element is the body when the tab
1386
+ // key is pressed, possibly along with others.
1387
+ window.setTimeout(function () {
1388
+ // Don't trigger `blur` if the focus is still in the selection
1389
+ if (
1390
+ (document.activeElement == self.$selection[0]) ||
1391
+ ($.contains(self.$selection[0], document.activeElement))
1392
+ ) {
1393
+ return;
1394
+ }
1395
+
1396
+ self.trigger('blur', evt);
1397
+ }, 1);
1398
+ };
1399
+
1400
+ BaseSelection.prototype._attachCloseHandler = function (container) {
1401
+ var self = this;
1402
+
1403
+ $(document.body).on('mousedown.select2.' + container.id, function (e) {
1404
+ var $target = $(e.target);
1405
+
1406
+ var $select = $target.closest('.select2');
1407
+
1408
+ var $all = $('.select2.select2-container--open');
1409
+
1410
+ $all.each(function () {
1411
+ var $this = $(this);
1412
+
1413
+ if (this == $select[0]) {
1414
+ return;
1415
+ }
1416
+
1417
+ var $element = $this.data('element');
1418
+
1419
+ $element.select2('close');
1420
+ });
1421
+ });
1422
+ };
1423
+
1424
+ BaseSelection.prototype._detachCloseHandler = function (container) {
1425
+ $(document.body).off('mousedown.select2.' + container.id);
1426
+ };
1427
+
1428
+ BaseSelection.prototype.position = function ($selection, $container) {
1429
+ var $selectionContainer = $container.find('.selection');
1430
+ $selectionContainer.append($selection);
1431
+ };
1432
+
1433
+ BaseSelection.prototype.destroy = function () {
1434
+ this._detachCloseHandler(this.container);
1435
+ };
1436
+
1437
+ BaseSelection.prototype.update = function (data) {
1438
+ throw new Error('The `update` method must be defined in child classes.');
1439
+ };
1440
+
1441
+ return BaseSelection;
1442
+ });
1443
+
1444
+ S2.define('select2/selection/single',[
1445
+ 'jquery',
1446
+ './base',
1447
+ '../utils',
1448
+ '../keys'
1449
+ ], function ($, BaseSelection, Utils, KEYS) {
1450
+ function SingleSelection () {
1451
+ SingleSelection.__super__.constructor.apply(this, arguments);
1452
+ }
1453
+
1454
+ Utils.Extend(SingleSelection, BaseSelection);
1455
+
1456
+ SingleSelection.prototype.render = function () {
1457
+ var $selection = SingleSelection.__super__.render.call(this);
1458
+
1459
+ $selection.addClass('select2-selection--single');
1460
+
1461
+ $selection.html(
1462
+ '<span class="select2-selection__rendered"></span>' +
1463
+ '<span class="select2-selection__arrow" role="presentation">' +
1464
+ '<b role="presentation"></b>' +
1465
+ '</span>'
1466
+ );
1467
+
1468
+ return $selection;
1469
+ };
1470
+
1471
+ SingleSelection.prototype.bind = function (container, $container) {
1472
+ var self = this;
1473
+
1474
+ SingleSelection.__super__.bind.apply(this, arguments);
1475
+
1476
+ var id = container.id + '-container';
1477
+
1478
+ this.$selection.find('.select2-selection__rendered').attr('id', id);
1479
+ this.$selection.attr('aria-labelledby', id);
1480
+
1481
+ this.$selection.on('mousedown', function (evt) {
1482
+ // Only respond to left clicks
1483
+ if (evt.which !== 1) {
1484
+ return;
1485
+ }
1486
+
1487
+ self.trigger('toggle', {
1488
+ originalEvent: evt
1489
+ });
1490
+ });
1491
+
1492
+ this.$selection.on('focus', function (evt) {
1493
+ // User focuses on the container
1494
+ });
1495
+
1496
+ this.$selection.on('blur', function (evt) {
1497
+ // User exits the container
1498
+ });
1499
+
1500
+ container.on('selection:update', function (params) {
1501
+ self.update(params.data);
1502
+ });
1503
+ };
1504
+
1505
+ SingleSelection.prototype.clear = function () {
1506
+ this.$selection.find('.select2-selection__rendered').empty();
1507
+ };
1508
+
1509
+ SingleSelection.prototype.display = function (data, container) {
1510
+ var template = this.options.get('templateSelection');
1511
+ var escapeMarkup = this.options.get('escapeMarkup');
1512
+
1513
+ return escapeMarkup(template(data, container));
1514
+ };
1515
+
1516
+ SingleSelection.prototype.selectionContainer = function () {
1517
+ return $('<span></span>');
1518
+ };
1519
+
1520
+ SingleSelection.prototype.update = function (data) {
1521
+ if (data.length === 0) {
1522
+ this.clear();
1523
+ return;
1524
+ }
1525
+
1526
+ var selection = data[0];
1527
+
1528
+ var $rendered = this.$selection.find('.select2-selection__rendered');
1529
+ var formatted = this.display(selection, $rendered);
1530
+
1531
+ $rendered.empty().append(formatted);
1532
+ $rendered.prop('title', selection.title || selection.text);
1533
+ };
1534
+
1535
+ return SingleSelection;
1536
+ });
1537
+
1538
+ S2.define('select2/selection/multiple',[
1539
+ 'jquery',
1540
+ './base',
1541
+ '../utils'
1542
+ ], function ($, BaseSelection, Utils) {
1543
+ function MultipleSelection ($element, options) {
1544
+ MultipleSelection.__super__.constructor.apply(this, arguments);
1545
+ }
1546
+
1547
+ Utils.Extend(MultipleSelection, BaseSelection);
1548
+
1549
+ MultipleSelection.prototype.render = function () {
1550
+ var $selection = MultipleSelection.__super__.render.call(this);
1551
+
1552
+ $selection.addClass('select2-selection--multiple');
1553
+
1554
+ $selection.html(
1555
+ '<ul class="select2-selection__rendered"></ul>'
1556
+ );
1557
+
1558
+ return $selection;
1559
+ };
1560
+
1561
+ MultipleSelection.prototype.bind = function (container, $container) {
1562
+ var self = this;
1563
+
1564
+ MultipleSelection.__super__.bind.apply(this, arguments);
1565
+
1566
+ this.$selection.on('click', function (evt) {
1567
+ self.trigger('toggle', {
1568
+ originalEvent: evt
1569
+ });
1570
+ });
1571
+
1572
+ this.$selection.on(
1573
+ 'click',
1574
+ '.select2-selection__choice__remove',
1575
+ function (evt) {
1576
+ // Ignore the event if it is disabled
1577
+ if (self.options.get('disabled')) {
1578
+ return;
1579
+ }
1580
+
1581
+ var $remove = $(this);
1582
+ var $selection = $remove.parent();
1583
+
1584
+ var data = $selection.data('data');
1585
+
1586
+ self.trigger('unselect', {
1587
+ originalEvent: evt,
1588
+ data: data
1589
+ });
1590
+ }
1591
+ );
1592
+ };
1593
+
1594
+ MultipleSelection.prototype.clear = function () {
1595
+ this.$selection.find('.select2-selection__rendered').empty();
1596
+ };
1597
+
1598
+ MultipleSelection.prototype.display = function (data, container) {
1599
+ var template = this.options.get('templateSelection');
1600
+ var escapeMarkup = this.options.get('escapeMarkup');
1601
+
1602
+ return escapeMarkup(template(data, container));
1603
+ };
1604
+
1605
+ MultipleSelection.prototype.selectionContainer = function () {
1606
+ var $container = $(
1607
+ '<li class="select2-selection__choice">' +
1608
+ '<span class="select2-selection__choice__remove" role="presentation">' +
1609
+ '&times;' +
1610
+ '</span>' +
1611
+ '</li>'
1612
+ );
1613
+
1614
+ return $container;
1615
+ };
1616
+
1617
+ MultipleSelection.prototype.update = function (data) {
1618
+ this.clear();
1619
+
1620
+ if (data.length === 0) {
1621
+ return;
1622
+ }
1623
+
1624
+ var $selections = [];
1625
+
1626
+ for (var d = 0; d < data.length; d++) {
1627
+ var selection = data[d];
1628
+
1629
+ var $selection = this.selectionContainer();
1630
+ var formatted = this.display(selection, $selection);
1631
+
1632
+ $selection.append(formatted);
1633
+ $selection.prop('title', selection.title || selection.text);
1634
+
1635
+ $selection.data('data', selection);
1636
+
1637
+ $selections.push($selection);
1638
+ }
1639
+
1640
+ var $rendered = this.$selection.find('.select2-selection__rendered');
1641
+
1642
+ Utils.appendMany($rendered, $selections);
1643
+ };
1644
+
1645
+ return MultipleSelection;
1646
+ });
1647
+
1648
+ S2.define('select2/selection/placeholder',[
1649
+ '../utils'
1650
+ ], function (Utils) {
1651
+ function Placeholder (decorated, $element, options) {
1652
+ this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
1653
+
1654
+ decorated.call(this, $element, options);
1655
+ }
1656
+
1657
+ Placeholder.prototype.normalizePlaceholder = function (_, placeholder) {
1658
+ if (typeof placeholder === 'string') {
1659
+ placeholder = {
1660
+ id: '',
1661
+ text: placeholder
1662
+ };
1663
+ }
1664
+
1665
+ return placeholder;
1666
+ };
1667
+
1668
+ Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
1669
+ var $placeholder = this.selectionContainer();
1670
+
1671
+ $placeholder.html(this.display(placeholder));
1672
+ $placeholder.addClass('select2-selection__placeholder')
1673
+ .removeClass('select2-selection__choice');
1674
+
1675
+ return $placeholder;
1676
+ };
1677
+
1678
+ Placeholder.prototype.update = function (decorated, data) {
1679
+ var singlePlaceholder = (
1680
+ data.length == 1 && data[0].id != this.placeholder.id
1681
+ );
1682
+ var multipleSelections = data.length > 1;
1683
+
1684
+ if (multipleSelections || singlePlaceholder) {
1685
+ return decorated.call(this, data);
1686
+ }
1687
+
1688
+ this.clear();
1689
+
1690
+ var $placeholder = this.createPlaceholder(this.placeholder);
1691
+
1692
+ this.$selection.find('.select2-selection__rendered').append($placeholder);
1693
+ };
1694
+
1695
+ return Placeholder;
1696
+ });
1697
+
1698
+ S2.define('select2/selection/allowClear',[
1699
+ 'jquery',
1700
+ '../keys'
1701
+ ], function ($, KEYS) {
1702
+ function AllowClear () { }
1703
+
1704
+ AllowClear.prototype.bind = function (decorated, container, $container) {
1705
+ var self = this;
1706
+
1707
+ decorated.call(this, container, $container);
1708
+
1709
+ if (this.placeholder == null) {
1710
+ if (this.options.get('debug') && window.console && console.error) {
1711
+ console.error(
1712
+ 'Select2: The `allowClear` option should be used in combination ' +
1713
+ 'with the `placeholder` option.'
1714
+ );
1715
+ }
1716
+ }
1717
+
1718
+ this.$selection.on('mousedown', '.select2-selection__clear',
1719
+ function (evt) {
1720
+ self._handleClear(evt);
1721
+ });
1722
+
1723
+ container.on('keypress', function (evt) {
1724
+ self._handleKeyboardClear(evt, container);
1725
+ });
1726
+ };
1727
+
1728
+ AllowClear.prototype._handleClear = function (_, evt) {
1729
+ // Ignore the event if it is disabled
1730
+ if (this.options.get('disabled')) {
1731
+ return;
1732
+ }
1733
+
1734
+ var $clear = this.$selection.find('.select2-selection__clear');
1735
+
1736
+ // Ignore the event if nothing has been selected
1737
+ if ($clear.length === 0) {
1738
+ return;
1739
+ }
1740
+
1741
+ evt.stopPropagation();
1742
+
1743
+ var data = $clear.data('data');
1744
+
1745
+ for (var d = 0; d < data.length; d++) {
1746
+ var unselectData = {
1747
+ data: data[d]
1748
+ };
1749
+
1750
+ // Trigger the `unselect` event, so people can prevent it from being
1751
+ // cleared.
1752
+ this.trigger('unselect', unselectData);
1753
+
1754
+ // If the event was prevented, don't clear it out.
1755
+ if (unselectData.prevented) {
1756
+ return;
1757
+ }
1758
+ }
1759
+
1760
+ this.$element.val(this.placeholder.id).trigger('change');
1761
+
1762
+ this.trigger('toggle', {});
1763
+ };
1764
+
1765
+ AllowClear.prototype._handleKeyboardClear = function (_, evt, container) {
1766
+ if (container.isOpen()) {
1767
+ return;
1768
+ }
1769
+
1770
+ if (evt.which == KEYS.DELETE || evt.which == KEYS.BACKSPACE) {
1771
+ this._handleClear(evt);
1772
+ }
1773
+ };
1774
+
1775
+ AllowClear.prototype.update = function (decorated, data) {
1776
+ decorated.call(this, data);
1777
+
1778
+ if (this.$selection.find('.select2-selection__placeholder').length > 0 ||
1779
+ data.length === 0) {
1780
+ return;
1781
+ }
1782
+
1783
+ var $remove = $(
1784
+ '<span class="select2-selection__clear">' +
1785
+ '&times;' +
1786
+ '</span>'
1787
+ );
1788
+ $remove.data('data', data);
1789
+
1790
+ this.$selection.find('.select2-selection__rendered').prepend($remove);
1791
+ };
1792
+
1793
+ return AllowClear;
1794
+ });
1795
+
1796
+ S2.define('select2/selection/search',[
1797
+ 'jquery',
1798
+ '../utils',
1799
+ '../keys'
1800
+ ], function ($, Utils, KEYS) {
1801
+ function Search (decorated, $element, options) {
1802
+ decorated.call(this, $element, options);
1803
+ }
1804
+
1805
+ Search.prototype.render = function (decorated) {
1806
+ var $search = $(
1807
+ '<li class="select2-search select2-search--inline">' +
1808
+ '<input class="select2-search__field" type="search" tabindex="-1"' +
1809
+ ' autocomplete="off" autocorrect="off" autocapitalize="off"' +
1810
+ ' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
1811
+ '</li>'
1812
+ );
1813
+
1814
+ this.$searchContainer = $search;
1815
+ this.$search = $search.find('input');
1816
+
1817
+ var $rendered = decorated.call(this);
1818
+
1819
+ this._transferTabIndex();
1820
+
1821
+ return $rendered;
1822
+ };
1823
+
1824
+ Search.prototype.bind = function (decorated, container, $container) {
1825
+ var self = this;
1826
+
1827
+ decorated.call(this, container, $container);
1828
+
1829
+ container.on('open', function () {
1830
+ self.$search.trigger('focus');
1831
+ });
1832
+
1833
+ container.on('close', function () {
1834
+ self.$search.val('');
1835
+ self.$search.removeAttr('aria-activedescendant');
1836
+ self.$search.trigger('focus');
1837
+ });
1838
+
1839
+ container.on('enable', function () {
1840
+ self.$search.prop('disabled', false);
1841
+
1842
+ self._transferTabIndex();
1843
+ });
1844
+
1845
+ container.on('disable', function () {
1846
+ self.$search.prop('disabled', true);
1847
+ });
1848
+
1849
+ container.on('focus', function (evt) {
1850
+ self.$search.trigger('focus');
1851
+ });
1852
+
1853
+ container.on('results:focus', function (params) {
1854
+ self.$search.attr('aria-activedescendant', params.id);
1855
+ });
1856
+
1857
+ this.$selection.on('focusin', '.select2-search--inline', function (evt) {
1858
+ self.trigger('focus', evt);
1859
+ });
1860
+
1861
+ this.$selection.on('focusout', '.select2-search--inline', function (evt) {
1862
+ self._handleBlur(evt);
1863
+ });
1864
+
1865
+ this.$selection.on('keydown', '.select2-search--inline', function (evt) {
1866
+ evt.stopPropagation();
1867
+
1868
+ self.trigger('keypress', evt);
1869
+
1870
+ self._keyUpPrevented = evt.isDefaultPrevented();
1871
+
1872
+ var key = evt.which;
1873
+
1874
+ if (key === KEYS.BACKSPACE && self.$search.val() === '') {
1875
+ var $previousChoice = self.$searchContainer
1876
+ .prev('.select2-selection__choice');
1877
+
1878
+ if ($previousChoice.length > 0) {
1879
+ var item = $previousChoice.data('data');
1880
+
1881
+ self.searchRemoveChoice(item);
1882
+
1883
+ evt.preventDefault();
1884
+ }
1885
+ }
1886
+ });
1887
+
1888
+ // Try to detect the IE version should the `documentMode` property that
1889
+ // is stored on the document. This is only implemented in IE and is
1890
+ // slightly cleaner than doing a user agent check.
1891
+ // This property is not available in Edge, but Edge also doesn't have
1892
+ // this bug.
1893
+ var msie = document.documentMode;
1894
+ var disableInputEvents = msie && msie <= 11;
1895
+
1896
+ // Workaround for browsers which do not support the `input` event
1897
+ // This will prevent double-triggering of events for browsers which support
1898
+ // both the `keyup` and `input` events.
1899
+ this.$selection.on(
1900
+ 'input.searchcheck',
1901
+ '.select2-search--inline',
1902
+ function (evt) {
1903
+ // IE will trigger the `input` event when a placeholder is used on a
1904
+ // search box. To get around this issue, we are forced to ignore all
1905
+ // `input` events in IE and keep using `keyup`.
1906
+ if (disableInputEvents) {
1907
+ self.$selection.off('input.search input.searchcheck');
1908
+ return;
1909
+ }
1910
+
1911
+ // Unbind the duplicated `keyup` event
1912
+ self.$selection.off('keyup.search');
1913
+ }
1914
+ );
1915
+
1916
+ this.$selection.on(
1917
+ 'keyup.search input.search',
1918
+ '.select2-search--inline',
1919
+ function (evt) {
1920
+ // IE will trigger the `input` event when a placeholder is used on a
1921
+ // search box. To get around this issue, we are forced to ignore all
1922
+ // `input` events in IE and keep using `keyup`.
1923
+ if (disableInputEvents && evt.type === 'input') {
1924
+ self.$selection.off('input.search input.searchcheck');
1925
+ return;
1926
+ }
1927
+
1928
+ var key = evt.which;
1929
+
1930
+ // We can freely ignore events from modifier keys
1931
+ if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) {
1932
+ return;
1933
+ }
1934
+
1935
+ // Tabbing will be handled during the `keydown` phase
1936
+ if (key == KEYS.TAB) {
1937
+ return;
1938
+ }
1939
+
1940
+ self.handleSearch(evt);
1941
+ }
1942
+ );
1943
+ };
1944
+
1945
+ /**
1946
+ * This method will transfer the tabindex attribute from the rendered
1947
+ * selection to the search box. This allows for the search box to be used as
1948
+ * the primary focus instead of the selection container.
1949
+ *
1950
+ * @private
1951
+ */
1952
+ Search.prototype._transferTabIndex = function (decorated) {
1953
+ this.$search.attr('tabindex', this.$selection.attr('tabindex'));
1954
+ this.$selection.attr('tabindex', '-1');
1955
+ };
1956
+
1957
+ Search.prototype.createPlaceholder = function (decorated, placeholder) {
1958
+ this.$search.attr('placeholder', placeholder.text);
1959
+ };
1960
+
1961
+ Search.prototype.update = function (decorated, data) {
1962
+ var searchHadFocus = this.$search[0] == document.activeElement;
1963
+
1964
+ this.$search.attr('placeholder', '');
1965
+
1966
+ decorated.call(this, data);
1967
+
1968
+ this.$selection.find('.select2-selection__rendered')
1969
+ .append(this.$searchContainer);
1970
+
1971
+ this.resizeSearch();
1972
+ if (searchHadFocus) {
1973
+ this.$search.focus();
1974
+ }
1975
+ };
1976
+
1977
+ Search.prototype.handleSearch = function () {
1978
+ this.resizeSearch();
1979
+
1980
+ if (!this._keyUpPrevented) {
1981
+ var input = this.$search.val();
1982
+
1983
+ this.trigger('query', {
1984
+ term: input
1985
+ });
1986
+ }
1987
+
1988
+ this._keyUpPrevented = false;
1989
+ };
1990
+
1991
+ Search.prototype.searchRemoveChoice = function (decorated, item) {
1992
+ this.trigger('unselect', {
1993
+ data: item
1994
+ });
1995
+
1996
+ this.$search.val(item.text);
1997
+ this.handleSearch();
1998
+ };
1999
+
2000
+ Search.prototype.resizeSearch = function () {
2001
+ this.$search.css('width', '25px');
2002
+
2003
+ var width = '';
2004
+
2005
+ if (this.$search.attr('placeholder') !== '') {
2006
+ width = this.$selection.find('.select2-selection__rendered').innerWidth();
2007
+ } else {
2008
+ var minimumWidth = this.$search.val().length + 1;
2009
+
2010
+ width = (minimumWidth * 0.75) + 'em';
2011
+ }
2012
+
2013
+ this.$search.css('width', width);
2014
+ };
2015
+
2016
+ return Search;
2017
+ });
2018
+
2019
+ S2.define('select2/selection/eventRelay',[
2020
+ 'jquery'
2021
+ ], function ($) {
2022
+ function EventRelay () { }
2023
+
2024
+ EventRelay.prototype.bind = function (decorated, container, $container) {
2025
+ var self = this;
2026
+ var relayEvents = [
2027
+ 'open', 'opening',
2028
+ 'close', 'closing',
2029
+ 'select', 'selecting',
2030
+ 'unselect', 'unselecting'
2031
+ ];
2032
+
2033
+ var preventableEvents = ['opening', 'closing', 'selecting', 'unselecting'];
2034
+
2035
+ decorated.call(this, container, $container);
2036
+
2037
+ container.on('*', function (name, params) {
2038
+ // Ignore events that should not be relayed
2039
+ if ($.inArray(name, relayEvents) === -1) {
2040
+ return;
2041
+ }
2042
+
2043
+ // The parameters should always be an object
2044
+ params = params || {};
2045
+
2046
+ // Generate the jQuery event for the Select2 event
2047
+ var evt = $.Event('select2:' + name, {
2048
+ params: params
2049
+ });
2050
+
2051
+ self.$element.trigger(evt);
2052
+
2053
+ // Only handle preventable events if it was one
2054
+ if ($.inArray(name, preventableEvents) === -1) {
2055
+ return;
2056
+ }
2057
+
2058
+ params.prevented = evt.isDefaultPrevented();
2059
+ });
2060
+ };
2061
+
2062
+ return EventRelay;
2063
+ });
2064
+
2065
+ S2.define('select2/translation',[
2066
+ 'jquery',
2067
+ 'require'
2068
+ ], function ($, require) {
2069
+ function Translation (dict) {
2070
+ this.dict = dict || {};
2071
+ }
2072
+
2073
+ Translation.prototype.all = function () {
2074
+ return this.dict;
2075
+ };
2076
+
2077
+ Translation.prototype.get = function (key) {
2078
+ return this.dict[key];
2079
+ };
2080
+
2081
+ Translation.prototype.extend = function (translation) {
2082
+ this.dict = $.extend({}, translation.all(), this.dict);
2083
+ };
2084
+
2085
+ // Static functions
2086
+
2087
+ Translation._cache = {};
2088
+
2089
+ Translation.loadPath = function (path) {
2090
+ if (!(path in Translation._cache)) {
2091
+ var translations = require(path);
2092
+
2093
+ Translation._cache[path] = translations;
2094
+ }
2095
+
2096
+ return new Translation(Translation._cache[path]);
2097
+ };
2098
+
2099
+ return Translation;
2100
+ });
2101
+
2102
+ S2.define('select2/diacritics',[
2103
+
2104
+ ], function () {
2105
+ var diacritics = {
2106
+ '\u24B6': 'A',
2107
+ '\uFF21': 'A',
2108
+ '\u00C0': 'A',
2109
+ '\u00C1': 'A',
2110
+ '\u00C2': 'A',
2111
+ '\u1EA6': 'A',
2112
+ '\u1EA4': 'A',
2113
+ '\u1EAA': 'A',
2114
+ '\u1EA8': 'A',
2115
+ '\u00C3': 'A',
2116
+ '\u0100': 'A',
2117
+ '\u0102': 'A',
2118
+ '\u1EB0': 'A',
2119
+ '\u1EAE': 'A',
2120
+ '\u1EB4': 'A',
2121
+ '\u1EB2': 'A',
2122
+ '\u0226': 'A',
2123
+ '\u01E0': 'A',
2124
+ '\u00C4': 'A',
2125
+ '\u01DE': 'A',
2126
+ '\u1EA2': 'A',
2127
+ '\u00C5': 'A',
2128
+ '\u01FA': 'A',
2129
+ '\u01CD': 'A',
2130
+ '\u0200': 'A',
2131
+ '\u0202': 'A',
2132
+ '\u1EA0': 'A',
2133
+ '\u1EAC': 'A',
2134
+ '\u1EB6': 'A',
2135
+ '\u1E00': 'A',
2136
+ '\u0104': 'A',
2137
+ '\u023A': 'A',
2138
+ '\u2C6F': 'A',
2139
+ '\uA732': 'AA',
2140
+ '\u00C6': 'AE',
2141
+ '\u01FC': 'AE',
2142
+ '\u01E2': 'AE',
2143
+ '\uA734': 'AO',
2144
+ '\uA736': 'AU',
2145
+ '\uA738': 'AV',
2146
+ '\uA73A': 'AV',
2147
+ '\uA73C': 'AY',
2148
+ '\u24B7': 'B',
2149
+ '\uFF22': 'B',
2150
+ '\u1E02': 'B',
2151
+ '\u1E04': 'B',
2152
+ '\u1E06': 'B',
2153
+ '\u0243': 'B',
2154
+ '\u0182': 'B',
2155
+ '\u0181': 'B',
2156
+ '\u24B8': 'C',
2157
+ '\uFF23': 'C',
2158
+ '\u0106': 'C',
2159
+ '\u0108': 'C',
2160
+ '\u010A': 'C',
2161
+ '\u010C': 'C',
2162
+ '\u00C7': 'C',
2163
+ '\u1E08': 'C',
2164
+ '\u0187': 'C',
2165
+ '\u023B': 'C',
2166
+ '\uA73E': 'C',
2167
+ '\u24B9': 'D',
2168
+ '\uFF24': 'D',
2169
+ '\u1E0A': 'D',
2170
+ '\u010E': 'D',
2171
+ '\u1E0C': 'D',
2172
+ '\u1E10': 'D',
2173
+ '\u1E12': 'D',
2174
+ '\u1E0E': 'D',
2175
+ '\u0110': 'D',
2176
+ '\u018B': 'D',
2177
+ '\u018A': 'D',
2178
+ '\u0189': 'D',
2179
+ '\uA779': 'D',
2180
+ '\u01F1': 'DZ',
2181
+ '\u01C4': 'DZ',
2182
+ '\u01F2': 'Dz',
2183
+ '\u01C5': 'Dz',
2184
+ '\u24BA': 'E',
2185
+ '\uFF25': 'E',
2186
+ '\u00C8': 'E',
2187
+ '\u00C9': 'E',
2188
+ '\u00CA': 'E',
2189
+ '\u1EC0': 'E',
2190
+ '\u1EBE': 'E',
2191
+ '\u1EC4': 'E',
2192
+ '\u1EC2': 'E',
2193
+ '\u1EBC': 'E',
2194
+ '\u0112': 'E',
2195
+ '\u1E14': 'E',
2196
+ '\u1E16': 'E',
2197
+ '\u0114': 'E',
2198
+ '\u0116': 'E',
2199
+ '\u00CB': 'E',
2200
+ '\u1EBA': 'E',
2201
+ '\u011A': 'E',
2202
+ '\u0204': 'E',
2203
+ '\u0206': 'E',
2204
+ '\u1EB8': 'E',
2205
+ '\u1EC6': 'E',
2206
+ '\u0228': 'E',
2207
+ '\u1E1C': 'E',
2208
+ '\u0118': 'E',
2209
+ '\u1E18': 'E',
2210
+ '\u1E1A': 'E',
2211
+ '\u0190': 'E',
2212
+ '\u018E': 'E',
2213
+ '\u24BB': 'F',
2214
+ '\uFF26': 'F',
2215
+ '\u1E1E': 'F',
2216
+ '\u0191': 'F',
2217
+ '\uA77B': 'F',
2218
+ '\u24BC': 'G',
2219
+ '\uFF27': 'G',
2220
+ '\u01F4': 'G',
2221
+ '\u011C': 'G',
2222
+ '\u1E20': 'G',
2223
+ '\u011E': 'G',
2224
+ '\u0120': 'G',
2225
+ '\u01E6': 'G',
2226
+ '\u0122': 'G',
2227
+ '\u01E4': 'G',
2228
+ '\u0193': 'G',
2229
+ '\uA7A0': 'G',
2230
+ '\uA77D': 'G',
2231
+ '\uA77E': 'G',
2232
+ '\u24BD': 'H',
2233
+ '\uFF28': 'H',
2234
+ '\u0124': 'H',
2235
+ '\u1E22': 'H',
2236
+ '\u1E26': 'H',
2237
+ '\u021E': 'H',
2238
+ '\u1E24': 'H',
2239
+ '\u1E28': 'H',
2240
+ '\u1E2A': 'H',
2241
+ '\u0126': 'H',
2242
+ '\u2C67': 'H',
2243
+ '\u2C75': 'H',
2244
+ '\uA78D': 'H',
2245
+ '\u24BE': 'I',
2246
+ '\uFF29': 'I',
2247
+ '\u00CC': 'I',
2248
+ '\u00CD': 'I',
2249
+ '\u00CE': 'I',
2250
+ '\u0128': 'I',
2251
+ '\u012A': 'I',
2252
+ '\u012C': 'I',
2253
+ '\u0130': 'I',
2254
+ '\u00CF': 'I',
2255
+ '\u1E2E': 'I',
2256
+ '\u1EC8': 'I',
2257
+ '\u01CF': 'I',
2258
+ '\u0208': 'I',
2259
+ '\u020A': 'I',
2260
+ '\u1ECA': 'I',
2261
+ '\u012E': 'I',
2262
+ '\u1E2C': 'I',
2263
+ '\u0197': 'I',
2264
+ '\u24BF': 'J',
2265
+ '\uFF2A': 'J',
2266
+ '\u0134': 'J',
2267
+ '\u0248': 'J',
2268
+ '\u24C0': 'K',
2269
+ '\uFF2B': 'K',
2270
+ '\u1E30': 'K',
2271
+ '\u01E8': 'K',
2272
+ '\u1E32': 'K',
2273
+ '\u0136': 'K',
2274
+ '\u1E34': 'K',
2275
+ '\u0198': 'K',
2276
+ '\u2C69': 'K',
2277
+ '\uA740': 'K',
2278
+ '\uA742': 'K',
2279
+ '\uA744': 'K',
2280
+ '\uA7A2': 'K',
2281
+ '\u24C1': 'L',
2282
+ '\uFF2C': 'L',
2283
+ '\u013F': 'L',
2284
+ '\u0139': 'L',
2285
+ '\u013D': 'L',
2286
+ '\u1E36': 'L',
2287
+ '\u1E38': 'L',
2288
+ '\u013B': 'L',
2289
+ '\u1E3C': 'L',
2290
+ '\u1E3A': 'L',
2291
+ '\u0141': 'L',
2292
+ '\u023D': 'L',
2293
+ '\u2C62': 'L',
2294
+ '\u2C60': 'L',
2295
+ '\uA748': 'L',
2296
+ '\uA746': 'L',
2297
+ '\uA780': 'L',
2298
+ '\u01C7': 'LJ',
2299
+ '\u01C8': 'Lj',
2300
+ '\u24C2': 'M',
2301
+ '\uFF2D': 'M',
2302
+ '\u1E3E': 'M',
2303
+ '\u1E40': 'M',
2304
+ '\u1E42': 'M',
2305
+ '\u2C6E': 'M',
2306
+ '\u019C': 'M',
2307
+ '\u24C3': 'N',
2308
+ '\uFF2E': 'N',
2309
+ '\u01F8': 'N',
2310
+ '\u0143': 'N',
2311
+ '\u00D1': 'N',
2312
+ '\u1E44': 'N',
2313
+ '\u0147': 'N',
2314
+ '\u1E46': 'N',
2315
+ '\u0145': 'N',
2316
+ '\u1E4A': 'N',
2317
+ '\u1E48': 'N',
2318
+ '\u0220': 'N',
2319
+ '\u019D': 'N',
2320
+ '\uA790': 'N',
2321
+ '\uA7A4': 'N',
2322
+ '\u01CA': 'NJ',
2323
+ '\u01CB': 'Nj',
2324
+ '\u24C4': 'O',
2325
+ '\uFF2F': 'O',
2326
+ '\u00D2': 'O',
2327
+ '\u00D3': 'O',
2328
+ '\u00D4': 'O',
2329
+ '\u1ED2': 'O',
2330
+ '\u1ED0': 'O',
2331
+ '\u1ED6': 'O',
2332
+ '\u1ED4': 'O',
2333
+ '\u00D5': 'O',
2334
+ '\u1E4C': 'O',
2335
+ '\u022C': 'O',
2336
+ '\u1E4E': 'O',
2337
+ '\u014C': 'O',
2338
+ '\u1E50': 'O',
2339
+ '\u1E52': 'O',
2340
+ '\u014E': 'O',
2341
+ '\u022E': 'O',
2342
+ '\u0230': 'O',
2343
+ '\u00D6': 'O',
2344
+ '\u022A': 'O',
2345
+ '\u1ECE': 'O',
2346
+ '\u0150': 'O',
2347
+ '\u01D1': 'O',
2348
+ '\u020C': 'O',
2349
+ '\u020E': 'O',
2350
+ '\u01A0': 'O',
2351
+ '\u1EDC': 'O',
2352
+ '\u1EDA': 'O',
2353
+ '\u1EE0': 'O',
2354
+ '\u1EDE': 'O',
2355
+ '\u1EE2': 'O',
2356
+ '\u1ECC': 'O',
2357
+ '\u1ED8': 'O',
2358
+ '\u01EA': 'O',
2359
+ '\u01EC': 'O',
2360
+ '\u00D8': 'O',
2361
+ '\u01FE': 'O',
2362
+ '\u0186': 'O',
2363
+ '\u019F': 'O',
2364
+ '\uA74A': 'O',
2365
+ '\uA74C': 'O',
2366
+ '\u01A2': 'OI',
2367
+ '\uA74E': 'OO',
2368
+ '\u0222': 'OU',
2369
+ '\u24C5': 'P',
2370
+ '\uFF30': 'P',
2371
+ '\u1E54': 'P',
2372
+ '\u1E56': 'P',
2373
+ '\u01A4': 'P',
2374
+ '\u2C63': 'P',
2375
+ '\uA750': 'P',
2376
+ '\uA752': 'P',
2377
+ '\uA754': 'P',
2378
+ '\u24C6': 'Q',
2379
+ '\uFF31': 'Q',
2380
+ '\uA756': 'Q',
2381
+ '\uA758': 'Q',
2382
+ '\u024A': 'Q',
2383
+ '\u24C7': 'R',
2384
+ '\uFF32': 'R',
2385
+ '\u0154': 'R',
2386
+ '\u1E58': 'R',
2387
+ '\u0158': 'R',
2388
+ '\u0210': 'R',
2389
+ '\u0212': 'R',
2390
+ '\u1E5A': 'R',
2391
+ '\u1E5C': 'R',
2392
+ '\u0156': 'R',
2393
+ '\u1E5E': 'R',
2394
+ '\u024C': 'R',
2395
+ '\u2C64': 'R',
2396
+ '\uA75A': 'R',
2397
+ '\uA7A6': 'R',
2398
+ '\uA782': 'R',
2399
+ '\u24C8': 'S',
2400
+ '\uFF33': 'S',
2401
+ '\u1E9E': 'S',
2402
+ '\u015A': 'S',
2403
+ '\u1E64': 'S',
2404
+ '\u015C': 'S',
2405
+ '\u1E60': 'S',
2406
+ '\u0160': 'S',
2407
+ '\u1E66': 'S',
2408
+ '\u1E62': 'S',
2409
+ '\u1E68': 'S',
2410
+ '\u0218': 'S',
2411
+ '\u015E': 'S',
2412
+ '\u2C7E': 'S',
2413
+ '\uA7A8': 'S',
2414
+ '\uA784': 'S',
2415
+ '\u24C9': 'T',
2416
+ '\uFF34': 'T',
2417
+ '\u1E6A': 'T',
2418
+ '\u0164': 'T',
2419
+ '\u1E6C': 'T',
2420
+ '\u021A': 'T',
2421
+ '\u0162': 'T',
2422
+ '\u1E70': 'T',
2423
+ '\u1E6E': 'T',
2424
+ '\u0166': 'T',
2425
+ '\u01AC': 'T',
2426
+ '\u01AE': 'T',
2427
+ '\u023E': 'T',
2428
+ '\uA786': 'T',
2429
+ '\uA728': 'TZ',
2430
+ '\u24CA': 'U',
2431
+ '\uFF35': 'U',
2432
+ '\u00D9': 'U',
2433
+ '\u00DA': 'U',
2434
+ '\u00DB': 'U',
2435
+ '\u0168': 'U',
2436
+ '\u1E78': 'U',
2437
+ '\u016A': 'U',
2438
+ '\u1E7A': 'U',
2439
+ '\u016C': 'U',
2440
+ '\u00DC': 'U',
2441
+ '\u01DB': 'U',
2442
+ '\u01D7': 'U',
2443
+ '\u01D5': 'U',
2444
+ '\u01D9': 'U',
2445
+ '\u1EE6': 'U',
2446
+ '\u016E': 'U',
2447
+ '\u0170': 'U',
2448
+ '\u01D3': 'U',
2449
+ '\u0214': 'U',
2450
+ '\u0216': 'U',
2451
+ '\u01AF': 'U',
2452
+ '\u1EEA': 'U',
2453
+ '\u1EE8': 'U',
2454
+ '\u1EEE': 'U',
2455
+ '\u1EEC': 'U',
2456
+ '\u1EF0': 'U',
2457
+ '\u1EE4': 'U',
2458
+ '\u1E72': 'U',
2459
+ '\u0172': 'U',
2460
+ '\u1E76': 'U',
2461
+ '\u1E74': 'U',
2462
+ '\u0244': 'U',
2463
+ '\u24CB': 'V',
2464
+ '\uFF36': 'V',
2465
+ '\u1E7C': 'V',
2466
+ '\u1E7E': 'V',
2467
+ '\u01B2': 'V',
2468
+ '\uA75E': 'V',
2469
+ '\u0245': 'V',
2470
+ '\uA760': 'VY',
2471
+ '\u24CC': 'W',
2472
+ '\uFF37': 'W',
2473
+ '\u1E80': 'W',
2474
+ '\u1E82': 'W',
2475
+ '\u0174': 'W',
2476
+ '\u1E86': 'W',
2477
+ '\u1E84': 'W',
2478
+ '\u1E88': 'W',
2479
+ '\u2C72': 'W',
2480
+ '\u24CD': 'X',
2481
+ '\uFF38': 'X',
2482
+ '\u1E8A': 'X',
2483
+ '\u1E8C': 'X',
2484
+ '\u24CE': 'Y',
2485
+ '\uFF39': 'Y',
2486
+ '\u1EF2': 'Y',
2487
+ '\u00DD': 'Y',
2488
+ '\u0176': 'Y',
2489
+ '\u1EF8': 'Y',
2490
+ '\u0232': 'Y',
2491
+ '\u1E8E': 'Y',
2492
+ '\u0178': 'Y',
2493
+ '\u1EF6': 'Y',
2494
+ '\u1EF4': 'Y',
2495
+ '\u01B3': 'Y',
2496
+ '\u024E': 'Y',
2497
+ '\u1EFE': 'Y',
2498
+ '\u24CF': 'Z',
2499
+ '\uFF3A': 'Z',
2500
+ '\u0179': 'Z',
2501
+ '\u1E90': 'Z',
2502
+ '\u017B': 'Z',
2503
+ '\u017D': 'Z',
2504
+ '\u1E92': 'Z',
2505
+ '\u1E94': 'Z',
2506
+ '\u01B5': 'Z',
2507
+ '\u0224': 'Z',
2508
+ '\u2C7F': 'Z',
2509
+ '\u2C6B': 'Z',
2510
+ '\uA762': 'Z',
2511
+ '\u24D0': 'a',
2512
+ '\uFF41': 'a',
2513
+ '\u1E9A': 'a',
2514
+ '\u00E0': 'a',
2515
+ '\u00E1': 'a',
2516
+ '\u00E2': 'a',
2517
+ '\u1EA7': 'a',
2518
+ '\u1EA5': 'a',
2519
+ '\u1EAB': 'a',
2520
+ '\u1EA9': 'a',
2521
+ '\u00E3': 'a',
2522
+ '\u0101': 'a',
2523
+ '\u0103': 'a',
2524
+ '\u1EB1': 'a',
2525
+ '\u1EAF': 'a',
2526
+ '\u1EB5': 'a',
2527
+ '\u1EB3': 'a',
2528
+ '\u0227': 'a',
2529
+ '\u01E1': 'a',
2530
+ '\u00E4': 'a',
2531
+ '\u01DF': 'a',
2532
+ '\u1EA3': 'a',
2533
+ '\u00E5': 'a',
2534
+ '\u01FB': 'a',
2535
+ '\u01CE': 'a',
2536
+ '\u0201': 'a',
2537
+ '\u0203': 'a',
2538
+ '\u1EA1': 'a',
2539
+ '\u1EAD': 'a',
2540
+ '\u1EB7': 'a',
2541
+ '\u1E01': 'a',
2542
+ '\u0105': 'a',
2543
+ '\u2C65': 'a',
2544
+ '\u0250': 'a',
2545
+ '\uA733': 'aa',
2546
+ '\u00E6': 'ae',
2547
+ '\u01FD': 'ae',
2548
+ '\u01E3': 'ae',
2549
+ '\uA735': 'ao',
2550
+ '\uA737': 'au',
2551
+ '\uA739': 'av',
2552
+ '\uA73B': 'av',
2553
+ '\uA73D': 'ay',
2554
+ '\u24D1': 'b',
2555
+ '\uFF42': 'b',
2556
+ '\u1E03': 'b',
2557
+ '\u1E05': 'b',
2558
+ '\u1E07': 'b',
2559
+ '\u0180': 'b',
2560
+ '\u0183': 'b',
2561
+ '\u0253': 'b',
2562
+ '\u24D2': 'c',
2563
+ '\uFF43': 'c',
2564
+ '\u0107': 'c',
2565
+ '\u0109': 'c',
2566
+ '\u010B': 'c',
2567
+ '\u010D': 'c',
2568
+ '\u00E7': 'c',
2569
+ '\u1E09': 'c',
2570
+ '\u0188': 'c',
2571
+ '\u023C': 'c',
2572
+ '\uA73F': 'c',
2573
+ '\u2184': 'c',
2574
+ '\u24D3': 'd',
2575
+ '\uFF44': 'd',
2576
+ '\u1E0B': 'd',
2577
+ '\u010F': 'd',
2578
+ '\u1E0D': 'd',
2579
+ '\u1E11': 'd',
2580
+ '\u1E13': 'd',
2581
+ '\u1E0F': 'd',
2582
+ '\u0111': 'd',
2583
+ '\u018C': 'd',
2584
+ '\u0256': 'd',
2585
+ '\u0257': 'd',
2586
+ '\uA77A': 'd',
2587
+ '\u01F3': 'dz',
2588
+ '\u01C6': 'dz',
2589
+ '\u24D4': 'e',
2590
+ '\uFF45': 'e',
2591
+ '\u00E8': 'e',
2592
+ '\u00E9': 'e',
2593
+ '\u00EA': 'e',
2594
+ '\u1EC1': 'e',
2595
+ '\u1EBF': 'e',
2596
+ '\u1EC5': 'e',
2597
+ '\u1EC3': 'e',
2598
+ '\u1EBD': 'e',
2599
+ '\u0113': 'e',
2600
+ '\u1E15': 'e',
2601
+ '\u1E17': 'e',
2602
+ '\u0115': 'e',
2603
+ '\u0117': 'e',
2604
+ '\u00EB': 'e',
2605
+ '\u1EBB': 'e',
2606
+ '\u011B': 'e',
2607
+ '\u0205': 'e',
2608
+ '\u0207': 'e',
2609
+ '\u1EB9': 'e',
2610
+ '\u1EC7': 'e',
2611
+ '\u0229': 'e',
2612
+ '\u1E1D': 'e',
2613
+ '\u0119': 'e',
2614
+ '\u1E19': 'e',
2615
+ '\u1E1B': 'e',
2616
+ '\u0247': 'e',
2617
+ '\u025B': 'e',
2618
+ '\u01DD': 'e',
2619
+ '\u24D5': 'f',
2620
+ '\uFF46': 'f',
2621
+ '\u1E1F': 'f',
2622
+ '\u0192': 'f',
2623
+ '\uA77C': 'f',
2624
+ '\u24D6': 'g',
2625
+ '\uFF47': 'g',
2626
+ '\u01F5': 'g',
2627
+ '\u011D': 'g',
2628
+ '\u1E21': 'g',
2629
+ '\u011F': 'g',
2630
+ '\u0121': 'g',
2631
+ '\u01E7': 'g',
2632
+ '\u0123': 'g',
2633
+ '\u01E5': 'g',
2634
+ '\u0260': 'g',
2635
+ '\uA7A1': 'g',
2636
+ '\u1D79': 'g',
2637
+ '\uA77F': 'g',
2638
+ '\u24D7': 'h',
2639
+ '\uFF48': 'h',
2640
+ '\u0125': 'h',
2641
+ '\u1E23': 'h',
2642
+ '\u1E27': 'h',
2643
+ '\u021F': 'h',
2644
+ '\u1E25': 'h',
2645
+ '\u1E29': 'h',
2646
+ '\u1E2B': 'h',
2647
+ '\u1E96': 'h',
2648
+ '\u0127': 'h',
2649
+ '\u2C68': 'h',
2650
+ '\u2C76': 'h',
2651
+ '\u0265': 'h',
2652
+ '\u0195': 'hv',
2653
+ '\u24D8': 'i',
2654
+ '\uFF49': 'i',
2655
+ '\u00EC': 'i',
2656
+ '\u00ED': 'i',
2657
+ '\u00EE': 'i',
2658
+ '\u0129': 'i',
2659
+ '\u012B': 'i',
2660
+ '\u012D': 'i',
2661
+ '\u00EF': 'i',
2662
+ '\u1E2F': 'i',
2663
+ '\u1EC9': 'i',
2664
+ '\u01D0': 'i',
2665
+ '\u0209': 'i',
2666
+ '\u020B': 'i',
2667
+ '\u1ECB': 'i',
2668
+ '\u012F': 'i',
2669
+ '\u1E2D': 'i',
2670
+ '\u0268': 'i',
2671
+ '\u0131': 'i',
2672
+ '\u24D9': 'j',
2673
+ '\uFF4A': 'j',
2674
+ '\u0135': 'j',
2675
+ '\u01F0': 'j',
2676
+ '\u0249': 'j',
2677
+ '\u24DA': 'k',
2678
+ '\uFF4B': 'k',
2679
+ '\u1E31': 'k',
2680
+ '\u01E9': 'k',
2681
+ '\u1E33': 'k',
2682
+ '\u0137': 'k',
2683
+ '\u1E35': 'k',
2684
+ '\u0199': 'k',
2685
+ '\u2C6A': 'k',
2686
+ '\uA741': 'k',
2687
+ '\uA743': 'k',
2688
+ '\uA745': 'k',
2689
+ '\uA7A3': 'k',
2690
+ '\u24DB': 'l',
2691
+ '\uFF4C': 'l',
2692
+ '\u0140': 'l',
2693
+ '\u013A': 'l',
2694
+ '\u013E': 'l',
2695
+ '\u1E37': 'l',
2696
+ '\u1E39': 'l',
2697
+ '\u013C': 'l',
2698
+ '\u1E3D': 'l',
2699
+ '\u1E3B': 'l',
2700
+ '\u017F': 'l',
2701
+ '\u0142': 'l',
2702
+ '\u019A': 'l',
2703
+ '\u026B': 'l',
2704
+ '\u2C61': 'l',
2705
+ '\uA749': 'l',
2706
+ '\uA781': 'l',
2707
+ '\uA747': 'l',
2708
+ '\u01C9': 'lj',
2709
+ '\u24DC': 'm',
2710
+ '\uFF4D': 'm',
2711
+ '\u1E3F': 'm',
2712
+ '\u1E41': 'm',
2713
+ '\u1E43': 'm',
2714
+ '\u0271': 'm',
2715
+ '\u026F': 'm',
2716
+ '\u24DD': 'n',
2717
+ '\uFF4E': 'n',
2718
+ '\u01F9': 'n',
2719
+ '\u0144': 'n',
2720
+ '\u00F1': 'n',
2721
+ '\u1E45': 'n',
2722
+ '\u0148': 'n',
2723
+ '\u1E47': 'n',
2724
+ '\u0146': 'n',
2725
+ '\u1E4B': 'n',
2726
+ '\u1E49': 'n',
2727
+ '\u019E': 'n',
2728
+ '\u0272': 'n',
2729
+ '\u0149': 'n',
2730
+ '\uA791': 'n',
2731
+ '\uA7A5': 'n',
2732
+ '\u01CC': 'nj',
2733
+ '\u24DE': 'o',
2734
+ '\uFF4F': 'o',
2735
+ '\u00F2': 'o',
2736
+ '\u00F3': 'o',
2737
+ '\u00F4': 'o',
2738
+ '\u1ED3': 'o',
2739
+ '\u1ED1': 'o',
2740
+ '\u1ED7': 'o',
2741
+ '\u1ED5': 'o',
2742
+ '\u00F5': 'o',
2743
+ '\u1E4D': 'o',
2744
+ '\u022D': 'o',
2745
+ '\u1E4F': 'o',
2746
+ '\u014D': 'o',
2747
+ '\u1E51': 'o',
2748
+ '\u1E53': 'o',
2749
+ '\u014F': 'o',
2750
+ '\u022F': 'o',
2751
+ '\u0231': 'o',
2752
+ '\u00F6': 'o',
2753
+ '\u022B': 'o',
2754
+ '\u1ECF': 'o',
2755
+ '\u0151': 'o',
2756
+ '\u01D2': 'o',
2757
+ '\u020D': 'o',
2758
+ '\u020F': 'o',
2759
+ '\u01A1': 'o',
2760
+ '\u1EDD': 'o',
2761
+ '\u1EDB': 'o',
2762
+ '\u1EE1': 'o',
2763
+ '\u1EDF': 'o',
2764
+ '\u1EE3': 'o',
2765
+ '\u1ECD': 'o',
2766
+ '\u1ED9': 'o',
2767
+ '\u01EB': 'o',
2768
+ '\u01ED': 'o',
2769
+ '\u00F8': 'o',
2770
+ '\u01FF': 'o',
2771
+ '\u0254': 'o',
2772
+ '\uA74B': 'o',
2773
+ '\uA74D': 'o',
2774
+ '\u0275': 'o',
2775
+ '\u01A3': 'oi',
2776
+ '\u0223': 'ou',
2777
+ '\uA74F': 'oo',
2778
+ '\u24DF': 'p',
2779
+ '\uFF50': 'p',
2780
+ '\u1E55': 'p',
2781
+ '\u1E57': 'p',
2782
+ '\u01A5': 'p',
2783
+ '\u1D7D': 'p',
2784
+ '\uA751': 'p',
2785
+ '\uA753': 'p',
2786
+ '\uA755': 'p',
2787
+ '\u24E0': 'q',
2788
+ '\uFF51': 'q',
2789
+ '\u024B': 'q',
2790
+ '\uA757': 'q',
2791
+ '\uA759': 'q',
2792
+ '\u24E1': 'r',
2793
+ '\uFF52': 'r',
2794
+ '\u0155': 'r',
2795
+ '\u1E59': 'r',
2796
+ '\u0159': 'r',
2797
+ '\u0211': 'r',
2798
+ '\u0213': 'r',
2799
+ '\u1E5B': 'r',
2800
+ '\u1E5D': 'r',
2801
+ '\u0157': 'r',
2802
+ '\u1E5F': 'r',
2803
+ '\u024D': 'r',
2804
+ '\u027D': 'r',
2805
+ '\uA75B': 'r',
2806
+ '\uA7A7': 'r',
2807
+ '\uA783': 'r',
2808
+ '\u24E2': 's',
2809
+ '\uFF53': 's',
2810
+ '\u00DF': 's',
2811
+ '\u015B': 's',
2812
+ '\u1E65': 's',
2813
+ '\u015D': 's',
2814
+ '\u1E61': 's',
2815
+ '\u0161': 's',
2816
+ '\u1E67': 's',
2817
+ '\u1E63': 's',
2818
+ '\u1E69': 's',
2819
+ '\u0219': 's',
2820
+ '\u015F': 's',
2821
+ '\u023F': 's',
2822
+ '\uA7A9': 's',
2823
+ '\uA785': 's',
2824
+ '\u1E9B': 's',
2825
+ '\u24E3': 't',
2826
+ '\uFF54': 't',
2827
+ '\u1E6B': 't',
2828
+ '\u1E97': 't',
2829
+ '\u0165': 't',
2830
+ '\u1E6D': 't',
2831
+ '\u021B': 't',
2832
+ '\u0163': 't',
2833
+ '\u1E71': 't',
2834
+ '\u1E6F': 't',
2835
+ '\u0167': 't',
2836
+ '\u01AD': 't',
2837
+ '\u0288': 't',
2838
+ '\u2C66': 't',
2839
+ '\uA787': 't',
2840
+ '\uA729': 'tz',
2841
+ '\u24E4': 'u',
2842
+ '\uFF55': 'u',
2843
+ '\u00F9': 'u',
2844
+ '\u00FA': 'u',
2845
+ '\u00FB': 'u',
2846
+ '\u0169': 'u',
2847
+ '\u1E79': 'u',
2848
+ '\u016B': 'u',
2849
+ '\u1E7B': 'u',
2850
+ '\u016D': 'u',
2851
+ '\u00FC': 'u',
2852
+ '\u01DC': 'u',
2853
+ '\u01D8': 'u',
2854
+ '\u01D6': 'u',
2855
+ '\u01DA': 'u',
2856
+ '\u1EE7': 'u',
2857
+ '\u016F': 'u',
2858
+ '\u0171': 'u',
2859
+ '\u01D4': 'u',
2860
+ '\u0215': 'u',
2861
+ '\u0217': 'u',
2862
+ '\u01B0': 'u',
2863
+ '\u1EEB': 'u',
2864
+ '\u1EE9': 'u',
2865
+ '\u1EEF': 'u',
2866
+ '\u1EED': 'u',
2867
+ '\u1EF1': 'u',
2868
+ '\u1EE5': 'u',
2869
+ '\u1E73': 'u',
2870
+ '\u0173': 'u',
2871
+ '\u1E77': 'u',
2872
+ '\u1E75': 'u',
2873
+ '\u0289': 'u',
2874
+ '\u24E5': 'v',
2875
+ '\uFF56': 'v',
2876
+ '\u1E7D': 'v',
2877
+ '\u1E7F': 'v',
2878
+ '\u028B': 'v',
2879
+ '\uA75F': 'v',
2880
+ '\u028C': 'v',
2881
+ '\uA761': 'vy',
2882
+ '\u24E6': 'w',
2883
+ '\uFF57': 'w',
2884
+ '\u1E81': 'w',
2885
+ '\u1E83': 'w',
2886
+ '\u0175': 'w',
2887
+ '\u1E87': 'w',
2888
+ '\u1E85': 'w',
2889
+ '\u1E98': 'w',
2890
+ '\u1E89': 'w',
2891
+ '\u2C73': 'w',
2892
+ '\u24E7': 'x',
2893
+ '\uFF58': 'x',
2894
+ '\u1E8B': 'x',
2895
+ '\u1E8D': 'x',
2896
+ '\u24E8': 'y',
2897
+ '\uFF59': 'y',
2898
+ '\u1EF3': 'y',
2899
+ '\u00FD': 'y',
2900
+ '\u0177': 'y',
2901
+ '\u1EF9': 'y',
2902
+ '\u0233': 'y',
2903
+ '\u1E8F': 'y',
2904
+ '\u00FF': 'y',
2905
+ '\u1EF7': 'y',
2906
+ '\u1E99': 'y',
2907
+ '\u1EF5': 'y',
2908
+ '\u01B4': 'y',
2909
+ '\u024F': 'y',
2910
+ '\u1EFF': 'y',
2911
+ '\u24E9': 'z',
2912
+ '\uFF5A': 'z',
2913
+ '\u017A': 'z',
2914
+ '\u1E91': 'z',
2915
+ '\u017C': 'z',
2916
+ '\u017E': 'z',
2917
+ '\u1E93': 'z',
2918
+ '\u1E95': 'z',
2919
+ '\u01B6': 'z',
2920
+ '\u0225': 'z',
2921
+ '\u0240': 'z',
2922
+ '\u2C6C': 'z',
2923
+ '\uA763': 'z',
2924
+ '\u0386': '\u0391',
2925
+ '\u0388': '\u0395',
2926
+ '\u0389': '\u0397',
2927
+ '\u038A': '\u0399',
2928
+ '\u03AA': '\u0399',
2929
+ '\u038C': '\u039F',
2930
+ '\u038E': '\u03A5',
2931
+ '\u03AB': '\u03A5',
2932
+ '\u038F': '\u03A9',
2933
+ '\u03AC': '\u03B1',
2934
+ '\u03AD': '\u03B5',
2935
+ '\u03AE': '\u03B7',
2936
+ '\u03AF': '\u03B9',
2937
+ '\u03CA': '\u03B9',
2938
+ '\u0390': '\u03B9',
2939
+ '\u03CC': '\u03BF',
2940
+ '\u03CD': '\u03C5',
2941
+ '\u03CB': '\u03C5',
2942
+ '\u03B0': '\u03C5',
2943
+ '\u03C9': '\u03C9',
2944
+ '\u03C2': '\u03C3'
2945
+ };
2946
+
2947
+ return diacritics;
2948
+ });
2949
+
2950
+ S2.define('select2/data/base',[
2951
+ '../utils'
2952
+ ], function (Utils) {
2953
+ function BaseAdapter ($element, options) {
2954
+ BaseAdapter.__super__.constructor.call(this);
2955
+ }
2956
+
2957
+ Utils.Extend(BaseAdapter, Utils.Observable);
2958
+
2959
+ BaseAdapter.prototype.current = function (callback) {
2960
+ throw new Error('The `current` method must be defined in child classes.');
2961
+ };
2962
+
2963
+ BaseAdapter.prototype.query = function (params, callback) {
2964
+ throw new Error('The `query` method must be defined in child classes.');
2965
+ };
2966
+
2967
+ BaseAdapter.prototype.bind = function (container, $container) {
2968
+ // Can be implemented in subclasses
2969
+ };
2970
+
2971
+ BaseAdapter.prototype.destroy = function () {
2972
+ // Can be implemented in subclasses
2973
+ };
2974
+
2975
+ BaseAdapter.prototype.generateResultId = function (container, data) {
2976
+ var id = container.id + '-result-';
2977
+
2978
+ id += Utils.generateChars(4);
2979
+
2980
+ if (data.id != null) {
2981
+ id += '-' + data.id.toString();
2982
+ } else {
2983
+ id += '-' + Utils.generateChars(4);
2984
+ }
2985
+ return id;
2986
+ };
2987
+
2988
+ return BaseAdapter;
2989
+ });
2990
+
2991
+ S2.define('select2/data/select',[
2992
+ './base',
2993
+ '../utils',
2994
+ 'jquery'
2995
+ ], function (BaseAdapter, Utils, $) {
2996
+ function SelectAdapter ($element, options) {
2997
+ this.$element = $element;
2998
+ this.options = options;
2999
+
3000
+ SelectAdapter.__super__.constructor.call(this);
3001
+ }
3002
+
3003
+ Utils.Extend(SelectAdapter, BaseAdapter);
3004
+
3005
+ SelectAdapter.prototype.current = function (callback) {
3006
+ var data = [];
3007
+ var self = this;
3008
+
3009
+ this.$element.find(':selected').each(function () {
3010
+ var $option = $(this);
3011
+
3012
+ var option = self.item($option);
3013
+
3014
+ data.push(option);
3015
+ });
3016
+
3017
+ callback(data);
3018
+ };
3019
+
3020
+ SelectAdapter.prototype.select = function (data) {
3021
+ var self = this;
3022
+
3023
+ data.selected = true;
3024
+
3025
+ // If data.element is a DOM node, use it instead
3026
+ if ($(data.element).is('option')) {
3027
+ data.element.selected = true;
3028
+
3029
+ this.$element.trigger('change');
3030
+
3031
+ return;
3032
+ }
3033
+
3034
+ if (this.$element.prop('multiple')) {
3035
+ this.current(function (currentData) {
3036
+ var val = [];
3037
+
3038
+ data = [data];
3039
+ data.push.apply(data, currentData);
3040
+
3041
+ for (var d = 0; d < data.length; d++) {
3042
+ var id = data[d].id;
3043
+
3044
+ if ($.inArray(id, val) === -1) {
3045
+ val.push(id);
3046
+ }
3047
+ }
3048
+
3049
+ self.$element.val(val);
3050
+ self.$element.trigger('change');
3051
+ });
3052
+ } else {
3053
+ var val = data.id;
3054
+
3055
+ this.$element.val(val);
3056
+ this.$element.trigger('change');
3057
+ }
3058
+ };
3059
+
3060
+ SelectAdapter.prototype.unselect = function (data) {
3061
+ var self = this;
3062
+
3063
+ if (!this.$element.prop('multiple')) {
3064
+ return;
3065
+ }
3066
+
3067
+ data.selected = false;
3068
+
3069
+ if ($(data.element).is('option')) {
3070
+ data.element.selected = false;
3071
+
3072
+ this.$element.trigger('change');
3073
+
3074
+ return;
3075
+ }
3076
+
3077
+ this.current(function (currentData) {
3078
+ var val = [];
3079
+
3080
+ for (var d = 0; d < currentData.length; d++) {
3081
+ var id = currentData[d].id;
3082
+
3083
+ if (id !== data.id && $.inArray(id, val) === -1) {
3084
+ val.push(id);
3085
+ }
3086
+ }
3087
+
3088
+ self.$element.val(val);
3089
+
3090
+ self.$element.trigger('change');
3091
+ });
3092
+ };
3093
+
3094
+ SelectAdapter.prototype.bind = function (container, $container) {
3095
+ var self = this;
3096
+
3097
+ this.container = container;
3098
+
3099
+ container.on('select', function (params) {
3100
+ self.select(params.data);
3101
+ });
3102
+
3103
+ container.on('unselect', function (params) {
3104
+ self.unselect(params.data);
3105
+ });
3106
+ };
3107
+
3108
+ SelectAdapter.prototype.destroy = function () {
3109
+ // Remove anything added to child elements
3110
+ this.$element.find('*').each(function () {
3111
+ // Remove any custom data set by Select2
3112
+ $.removeData(this, 'data');
3113
+ });
3114
+ };
3115
+
3116
+ SelectAdapter.prototype.query = function (params, callback) {
3117
+ var data = [];
3118
+ var self = this;
3119
+
3120
+ var $options = this.$element.children();
3121
+
3122
+ $options.each(function () {
3123
+ var $option = $(this);
3124
+
3125
+ if (!$option.is('option') && !$option.is('optgroup')) {
3126
+ return;
3127
+ }
3128
+
3129
+ var option = self.item($option);
3130
+
3131
+ var matches = self.matches(params, option);
3132
+
3133
+ if (matches !== null) {
3134
+ data.push(matches);
3135
+ }
3136
+ });
3137
+
3138
+ callback({
3139
+ results: data
3140
+ });
3141
+ };
3142
+
3143
+ SelectAdapter.prototype.addOptions = function ($options) {
3144
+ Utils.appendMany(this.$element, $options);
3145
+ };
3146
+
3147
+ SelectAdapter.prototype.option = function (data) {
3148
+ var option;
3149
+
3150
+ if (data.children) {
3151
+ option = document.createElement('optgroup');
3152
+ option.label = data.text;
3153
+ } else {
3154
+ option = document.createElement('option');
3155
+
3156
+ if (option.textContent !== undefined) {
3157
+ option.textContent = data.text;
3158
+ } else {
3159
+ option.innerText = data.text;
3160
+ }
3161
+ }
3162
+
3163
+ if (data.id) {
3164
+ option.value = data.id;
3165
+ }
3166
+
3167
+ if (data.disabled) {
3168
+ option.disabled = true;
3169
+ }
3170
+
3171
+ if (data.selected) {
3172
+ option.selected = true;
3173
+ }
3174
+
3175
+ if (data.title) {
3176
+ option.title = data.title;
3177
+ }
3178
+
3179
+ var $option = $(option);
3180
+
3181
+ var normalizedData = this._normalizeItem(data);
3182
+ normalizedData.element = option;
3183
+
3184
+ // Override the option's data with the combined data
3185
+ $.data(option, 'data', normalizedData);
3186
+
3187
+ return $option;
3188
+ };
3189
+
3190
+ SelectAdapter.prototype.item = function ($option) {
3191
+ var data = {};
3192
+
3193
+ data = $.data($option[0], 'data');
3194
+
3195
+ if (data != null) {
3196
+ return data;
3197
+ }
3198
+
3199
+ if ($option.is('option')) {
3200
+ data = {
3201
+ id: $option.val(),
3202
+ text: $option.text(),
3203
+ disabled: $option.prop('disabled'),
3204
+ selected: $option.prop('selected'),
3205
+ title: $option.prop('title')
3206
+ };
3207
+ } else if ($option.is('optgroup')) {
3208
+ data = {
3209
+ text: $option.prop('label'),
3210
+ children: [],
3211
+ title: $option.prop('title')
3212
+ };
3213
+
3214
+ var $children = $option.children('option');
3215
+ var children = [];
3216
+
3217
+ for (var c = 0; c < $children.length; c++) {
3218
+ var $child = $($children[c]);
3219
+
3220
+ var child = this.item($child);
3221
+
3222
+ children.push(child);
3223
+ }
3224
+
3225
+ data.children = children;
3226
+ }
3227
+
3228
+ data = this._normalizeItem(data);
3229
+ data.element = $option[0];
3230
+
3231
+ $.data($option[0], 'data', data);
3232
+
3233
+ return data;
3234
+ };
3235
+
3236
+ SelectAdapter.prototype._normalizeItem = function (item) {
3237
+ if (!$.isPlainObject(item)) {
3238
+ item = {
3239
+ id: item,
3240
+ text: item
3241
+ };
3242
+ }
3243
+
3244
+ item = $.extend({}, {
3245
+ text: ''
3246
+ }, item);
3247
+
3248
+ var defaults = {
3249
+ selected: false,
3250
+ disabled: false
3251
+ };
3252
+
3253
+ if (item.id != null) {
3254
+ item.id = item.id.toString();
3255
+ }
3256
+
3257
+ if (item.text != null) {
3258
+ item.text = item.text.toString();
3259
+ }
3260
+
3261
+ if (item._resultId == null && item.id && this.container != null) {
3262
+ item._resultId = this.generateResultId(this.container, item);
3263
+ }
3264
+
3265
+ return $.extend({}, defaults, item);
3266
+ };
3267
+
3268
+ SelectAdapter.prototype.matches = function (params, data) {
3269
+ var matcher = this.options.get('matcher');
3270
+
3271
+ return matcher(params, data);
3272
+ };
3273
+
3274
+ return SelectAdapter;
3275
+ });
3276
+
3277
+ S2.define('select2/data/array',[
3278
+ './select',
3279
+ '../utils',
3280
+ 'jquery'
3281
+ ], function (SelectAdapter, Utils, $) {
3282
+ function ArrayAdapter ($element, options) {
3283
+ var data = options.get('data') || [];
3284
+
3285
+ ArrayAdapter.__super__.constructor.call(this, $element, options);
3286
+
3287
+ this.addOptions(this.convertToOptions(data));
3288
+ }
3289
+
3290
+ Utils.Extend(ArrayAdapter, SelectAdapter);
3291
+
3292
+ ArrayAdapter.prototype.select = function (data) {
3293
+ var $option = this.$element.find('option').filter(function (i, elm) {
3294
+ return elm.value == data.id.toString();
3295
+ });
3296
+
3297
+ if ($option.length === 0) {
3298
+ $option = this.option(data);
3299
+
3300
+ this.addOptions($option);
3301
+ }
3302
+
3303
+ ArrayAdapter.__super__.select.call(this, data);
3304
+ };
3305
+
3306
+ ArrayAdapter.prototype.convertToOptions = function (data) {
3307
+ var self = this;
3308
+
3309
+ var $existing = this.$element.find('option');
3310
+ var existingIds = $existing.map(function () {
3311
+ return self.item($(this)).id;
3312
+ }).get();
3313
+
3314
+ var $options = [];
3315
+
3316
+ // Filter out all items except for the one passed in the argument
3317
+ function onlyItem (item) {
3318
+ return function () {
3319
+ return $(this).val() == item.id;
3320
+ };
3321
+ }
3322
+
3323
+ for (var d = 0; d < data.length; d++) {
3324
+ var item = this._normalizeItem(data[d]);
3325
+
3326
+ // Skip items which were pre-loaded, only merge the data
3327
+ if ($.inArray(item.id, existingIds) >= 0) {
3328
+ var $existingOption = $existing.filter(onlyItem(item));
3329
+
3330
+ var existingData = this.item($existingOption);
3331
+ var newData = $.extend(true, {}, item, existingData);
3332
+
3333
+ var $newOption = this.option(newData);
3334
+
3335
+ $existingOption.replaceWith($newOption);
3336
+
3337
+ continue;
3338
+ }
3339
+
3340
+ var $option = this.option(item);
3341
+
3342
+ if (item.children) {
3343
+ var $children = this.convertToOptions(item.children);
3344
+
3345
+ Utils.appendMany($option, $children);
3346
+ }
3347
+
3348
+ $options.push($option);
3349
+ }
3350
+
3351
+ return $options;
3352
+ };
3353
+
3354
+ return ArrayAdapter;
3355
+ });
3356
+
3357
+ S2.define('select2/data/ajax',[
3358
+ './array',
3359
+ '../utils',
3360
+ 'jquery'
3361
+ ], function (ArrayAdapter, Utils, $) {
3362
+ function AjaxAdapter ($element, options) {
3363
+ this.ajaxOptions = this._applyDefaults(options.get('ajax'));
3364
+
3365
+ if (this.ajaxOptions.processResults != null) {
3366
+ this.processResults = this.ajaxOptions.processResults;
3367
+ }
3368
+
3369
+ AjaxAdapter.__super__.constructor.call(this, $element, options);
3370
+ }
3371
+
3372
+ Utils.Extend(AjaxAdapter, ArrayAdapter);
3373
+
3374
+ AjaxAdapter.prototype._applyDefaults = function (options) {
3375
+ var defaults = {
3376
+ data: function (params) {
3377
+ return $.extend({}, params, {
3378
+ q: params.term
3379
+ });
3380
+ },
3381
+ transport: function (params, success, failure) {
3382
+ var $request = $.ajax(params);
3383
+
3384
+ $request.then(success);
3385
+ $request.fail(failure);
3386
+
3387
+ return $request;
3388
+ }
3389
+ };
3390
+
3391
+ return $.extend({}, defaults, options, true);
3392
+ };
3393
+
3394
+ AjaxAdapter.prototype.processResults = function (results) {
3395
+ return results;
3396
+ };
3397
+
3398
+ AjaxAdapter.prototype.query = function (params, callback) {
3399
+ var matches = [];
3400
+ var self = this;
3401
+
3402
+ if (this._request != null) {
3403
+ // JSONP requests cannot always be aborted
3404
+ if ($.isFunction(this._request.abort)) {
3405
+ this._request.abort();
3406
+ }
3407
+
3408
+ this._request = null;
3409
+ }
3410
+
3411
+ var options = $.extend({
3412
+ type: 'GET'
3413
+ }, this.ajaxOptions);
3414
+
3415
+ if (typeof options.url === 'function') {
3416
+ options.url = options.url.call(this.$element, params);
3417
+ }
3418
+
3419
+ if (typeof options.data === 'function') {
3420
+ options.data = options.data.call(this.$element, params);
3421
+ }
3422
+
3423
+ function request () {
3424
+ var $request = options.transport(options, function (data) {
3425
+ var results = self.processResults(data, params);
3426
+
3427
+ if (self.options.get('debug') && window.console && console.error) {
3428
+ // Check to make sure that the response included a `results` key.
3429
+ if (!results || !results.results || !$.isArray(results.results)) {
3430
+ console.error(
3431
+ 'Select2: The AJAX results did not return an array in the ' +
3432
+ '`results` key of the response.'
3433
+ );
3434
+ }
3435
+ }
3436
+
3437
+ callback(results);
3438
+ }, function () {
3439
+ self.trigger('results:message', {
3440
+ message: 'errorLoading'
3441
+ });
3442
+ });
3443
+
3444
+ self._request = $request;
3445
+ }
3446
+
3447
+ if (this.ajaxOptions.delay && params.term !== '') {
3448
+ if (this._queryTimeout) {
3449
+ window.clearTimeout(this._queryTimeout);
3450
+ }
3451
+
3452
+ this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay);
3453
+ } else {
3454
+ request();
3455
+ }
3456
+ };
3457
+
3458
+ return AjaxAdapter;
3459
+ });
3460
+
3461
+ S2.define('select2/data/tags',[
3462
+ 'jquery'
3463
+ ], function ($) {
3464
+ function Tags (decorated, $element, options) {
3465
+ var tags = options.get('tags');
3466
+
3467
+ var createTag = options.get('createTag');
3468
+
3469
+ if (createTag !== undefined) {
3470
+ this.createTag = createTag;
3471
+ }
3472
+
3473
+ var insertTag = options.get('insertTag');
3474
+
3475
+ if (insertTag !== undefined) {
3476
+ this.insertTag = insertTag;
3477
+ }
3478
+
3479
+ decorated.call(this, $element, options);
3480
+
3481
+ if ($.isArray(tags)) {
3482
+ for (var t = 0; t < tags.length; t++) {
3483
+ var tag = tags[t];
3484
+ var item = this._normalizeItem(tag);
3485
+
3486
+ var $option = this.option(item);
3487
+
3488
+ this.$element.append($option);
3489
+ }
3490
+ }
3491
+ }
3492
+
3493
+ Tags.prototype.query = function (decorated, params, callback) {
3494
+ var self = this;
3495
+
3496
+ this._removeOldTags();
3497
+
3498
+ if (params.term == null || params.page != null) {
3499
+ decorated.call(this, params, callback);
3500
+ return;
3501
+ }
3502
+
3503
+ function wrapper (obj, child) {
3504
+ var data = obj.results;
3505
+
3506
+ for (var i = 0; i < data.length; i++) {
3507
+ var option = data[i];
3508
+
3509
+ var checkChildren = (
3510
+ option.children != null &&
3511
+ !wrapper({
3512
+ results: option.children
3513
+ }, true)
3514
+ );
3515
+
3516
+ var checkText = option.text === params.term;
3517
+
3518
+ if (checkText || checkChildren) {
3519
+ if (child) {
3520
+ return false;
3521
+ }
3522
+
3523
+ obj.data = data;
3524
+ callback(obj);
3525
+
3526
+ return;
3527
+ }
3528
+ }
3529
+
3530
+ if (child) {
3531
+ return true;
3532
+ }
3533
+
3534
+ var tag = self.createTag(params);
3535
+
3536
+ if (tag != null) {
3537
+ var $option = self.option(tag);
3538
+ $option.attr('data-select2-tag', true);
3539
+
3540
+ self.addOptions([$option]);
3541
+
3542
+ self.insertTag(data, tag);
3543
+ }
3544
+
3545
+ obj.results = data;
3546
+
3547
+ callback(obj);
3548
+ }
3549
+
3550
+ decorated.call(this, params, wrapper);
3551
+ };
3552
+
3553
+ Tags.prototype.createTag = function (decorated, params) {
3554
+ var term = $.trim(params.term);
3555
+
3556
+ if (term === '') {
3557
+ return null;
3558
+ }
3559
+
3560
+ return {
3561
+ id: term,
3562
+ text: term
3563
+ };
3564
+ };
3565
+
3566
+ Tags.prototype.insertTag = function (_, data, tag) {
3567
+ data.unshift(tag);
3568
+ };
3569
+
3570
+ Tags.prototype._removeOldTags = function (_) {
3571
+ var tag = this._lastTag;
3572
+
3573
+ var $options = this.$element.find('option[data-select2-tag]');
3574
+
3575
+ $options.each(function () {
3576
+ if (this.selected) {
3577
+ return;
3578
+ }
3579
+
3580
+ $(this).remove();
3581
+ });
3582
+ };
3583
+
3584
+ return Tags;
3585
+ });
3586
+
3587
+ S2.define('select2/data/tokenizer',[
3588
+ 'jquery'
3589
+ ], function ($) {
3590
+ function Tokenizer (decorated, $element, options) {
3591
+ var tokenizer = options.get('tokenizer');
3592
+
3593
+ if (tokenizer !== undefined) {
3594
+ this.tokenizer = tokenizer;
3595
+ }
3596
+
3597
+ decorated.call(this, $element, options);
3598
+ }
3599
+
3600
+ Tokenizer.prototype.bind = function (decorated, container, $container) {
3601
+ decorated.call(this, container, $container);
3602
+
3603
+ this.$search = container.dropdown.$search || container.selection.$search ||
3604
+ $container.find('.select2-search__field');
3605
+ };
3606
+
3607
+ Tokenizer.prototype.query = function (decorated, params, callback) {
3608
+ var self = this;
3609
+
3610
+ function select (data) {
3611
+ self.trigger('select', {
3612
+ data: data
3613
+ });
3614
+ }
3615
+
3616
+ params.term = params.term || '';
3617
+
3618
+ var tokenData = this.tokenizer(params, this.options, select);
3619
+
3620
+ if (tokenData.term !== params.term) {
3621
+ // Replace the search term if we have the search box
3622
+ if (this.$search.length) {
3623
+ this.$search.val(tokenData.term);
3624
+ this.$search.focus();
3625
+ }
3626
+
3627
+ params.term = tokenData.term;
3628
+ }
3629
+
3630
+ decorated.call(this, params, callback);
3631
+ };
3632
+
3633
+ Tokenizer.prototype.tokenizer = function (_, params, options, callback) {
3634
+ var separators = options.get('tokenSeparators') || [];
3635
+ var term = params.term;
3636
+ var i = 0;
3637
+
3638
+ var createTag = this.createTag || function (params) {
3639
+ return {
3640
+ id: params.term,
3641
+ text: params.term
3642
+ };
3643
+ };
3644
+
3645
+ while (i < term.length) {
3646
+ var termChar = term[i];
3647
+
3648
+ if ($.inArray(termChar, separators) === -1) {
3649
+ i++;
3650
+
3651
+ continue;
3652
+ }
3653
+
3654
+ var part = term.substr(0, i);
3655
+ var partParams = $.extend({}, params, {
3656
+ term: part
3657
+ });
3658
+
3659
+ var data = createTag(partParams);
3660
+
3661
+ if (data == null) {
3662
+ i++;
3663
+ continue;
3664
+ }
3665
+
3666
+ callback(data);
3667
+
3668
+ // Reset the term to not include the tokenized portion
3669
+ term = term.substr(i + 1) || '';
3670
+ i = 0;
3671
+ }
3672
+
3673
+ return {
3674
+ term: term
3675
+ };
3676
+ };
3677
+
3678
+ return Tokenizer;
3679
+ });
3680
+
3681
+ S2.define('select2/data/minimumInputLength',[
3682
+
3683
+ ], function () {
3684
+ function MinimumInputLength (decorated, $e, options) {
3685
+ this.minimumInputLength = options.get('minimumInputLength');
3686
+
3687
+ decorated.call(this, $e, options);
3688
+ }
3689
+
3690
+ MinimumInputLength.prototype.query = function (decorated, params, callback) {
3691
+ params.term = params.term || '';
3692
+
3693
+ if (params.term.length < this.minimumInputLength) {
3694
+ this.trigger('results:message', {
3695
+ message: 'inputTooShort',
3696
+ args: {
3697
+ minimum: this.minimumInputLength,
3698
+ input: params.term,
3699
+ params: params
3700
+ }
3701
+ });
3702
+
3703
+ return;
3704
+ }
3705
+
3706
+ decorated.call(this, params, callback);
3707
+ };
3708
+
3709
+ return MinimumInputLength;
3710
+ });
3711
+
3712
+ S2.define('select2/data/maximumInputLength',[
3713
+
3714
+ ], function () {
3715
+ function MaximumInputLength (decorated, $e, options) {
3716
+ this.maximumInputLength = options.get('maximumInputLength');
3717
+
3718
+ decorated.call(this, $e, options);
3719
+ }
3720
+
3721
+ MaximumInputLength.prototype.query = function (decorated, params, callback) {
3722
+ params.term = params.term || '';
3723
+
3724
+ if (this.maximumInputLength > 0 &&
3725
+ params.term.length > this.maximumInputLength) {
3726
+ this.trigger('results:message', {
3727
+ message: 'inputTooLong',
3728
+ args: {
3729
+ maximum: this.maximumInputLength,
3730
+ input: params.term,
3731
+ params: params
3732
+ }
3733
+ });
3734
+
3735
+ return;
3736
+ }
3737
+
3738
+ decorated.call(this, params, callback);
3739
+ };
3740
+
3741
+ return MaximumInputLength;
3742
+ });
3743
+
3744
+ S2.define('select2/data/maximumSelectionLength',[
3745
+
3746
+ ], function (){
3747
+ function MaximumSelectionLength (decorated, $e, options) {
3748
+ this.maximumSelectionLength = options.get('maximumSelectionLength');
3749
+
3750
+ decorated.call(this, $e, options);
3751
+ }
3752
+
3753
+ MaximumSelectionLength.prototype.query =
3754
+ function (decorated, params, callback) {
3755
+ var self = this;
3756
+
3757
+ this.current(function (currentData) {
3758
+ var count = currentData != null ? currentData.length : 0;
3759
+ if (self.maximumSelectionLength > 0 &&
3760
+ count >= self.maximumSelectionLength) {
3761
+ self.trigger('results:message', {
3762
+ message: 'maximumSelected',
3763
+ args: {
3764
+ maximum: self.maximumSelectionLength
3765
+ }
3766
+ });
3767
+ return;
3768
+ }
3769
+ decorated.call(self, params, callback);
3770
+ });
3771
+ };
3772
+
3773
+ return MaximumSelectionLength;
3774
+ });
3775
+
3776
+ S2.define('select2/dropdown',[
3777
+ 'jquery',
3778
+ './utils'
3779
+ ], function ($, Utils) {
3780
+ function Dropdown ($element, options) {
3781
+ this.$element = $element;
3782
+ this.options = options;
3783
+
3784
+ Dropdown.__super__.constructor.call(this);
3785
+ }
3786
+
3787
+ Utils.Extend(Dropdown, Utils.Observable);
3788
+
3789
+ Dropdown.prototype.render = function () {
3790
+ var $dropdown = $(
3791
+ '<span class="select2-dropdown">' +
3792
+ '<span class="select2-results"></span>' +
3793
+ '</span>'
3794
+ );
3795
+
3796
+ $dropdown.attr('dir', this.options.get('dir'));
3797
+
3798
+ this.$dropdown = $dropdown;
3799
+
3800
+ return $dropdown;
3801
+ };
3802
+
3803
+ Dropdown.prototype.bind = function () {
3804
+ // Should be implemented in subclasses
3805
+ };
3806
+
3807
+ Dropdown.prototype.position = function ($dropdown, $container) {
3808
+ // Should be implmented in subclasses
3809
+ };
3810
+
3811
+ Dropdown.prototype.destroy = function () {
3812
+ // Remove the dropdown from the DOM
3813
+ this.$dropdown.remove();
3814
+ };
3815
+
3816
+ return Dropdown;
3817
+ });
3818
+
3819
+ S2.define('select2/dropdown/search',[
3820
+ 'jquery',
3821
+ '../utils'
3822
+ ], function ($, Utils) {
3823
+ function Search () { }
3824
+
3825
+ Search.prototype.render = function (decorated) {
3826
+ var $rendered = decorated.call(this);
3827
+
3828
+ var $search = $(
3829
+ '<span class="select2-search select2-search--dropdown">' +
3830
+ '<input class="select2-search__field" type="search" tabindex="-1"' +
3831
+ ' autocomplete="off" autocorrect="off" autocapitalize="off"' +
3832
+ ' spellcheck="false" role="textbox" />' +
3833
+ '</span>'
3834
+ );
3835
+
3836
+ this.$searchContainer = $search;
3837
+ this.$search = $search.find('input');
3838
+
3839
+ $rendered.prepend($search);
3840
+
3841
+ return $rendered;
3842
+ };
3843
+
3844
+ Search.prototype.bind = function (decorated, container, $container) {
3845
+ var self = this;
3846
+
3847
+ decorated.call(this, container, $container);
3848
+
3849
+ this.$search.on('keydown', function (evt) {
3850
+ self.trigger('keypress', evt);
3851
+
3852
+ self._keyUpPrevented = evt.isDefaultPrevented();
3853
+ });
3854
+
3855
+ // Workaround for browsers which do not support the `input` event
3856
+ // This will prevent double-triggering of events for browsers which support
3857
+ // both the `keyup` and `input` events.
3858
+ this.$search.on('input', function (evt) {
3859
+ // Unbind the duplicated `keyup` event
3860
+ $(this).off('keyup');
3861
+ });
3862
+
3863
+ this.$search.on('keyup input', function (evt) {
3864
+ self.handleSearch(evt);
3865
+ });
3866
+
3867
+ container.on('open', function () {
3868
+ self.$search.attr('tabindex', 0);
3869
+
3870
+ self.$search.focus();
3871
+
3872
+ window.setTimeout(function () {
3873
+ self.$search.focus();
3874
+ }, 0);
3875
+ });
3876
+
3877
+ container.on('close', function () {
3878
+ self.$search.attr('tabindex', -1);
3879
+
3880
+ self.$search.val('');
3881
+ });
3882
+
3883
+ container.on('results:all', function (params) {
3884
+ if (params.query.term == null || params.query.term === '') {
3885
+ var showSearch = self.showSearch(params);
3886
+
3887
+ if (showSearch) {
3888
+ self.$searchContainer.removeClass('select2-search--hide');
3889
+ } else {
3890
+ self.$searchContainer.addClass('select2-search--hide');
3891
+ }
3892
+ }
3893
+ });
3894
+ };
3895
+
3896
+ Search.prototype.handleSearch = function (evt) {
3897
+ if (!this._keyUpPrevented) {
3898
+ var input = this.$search.val();
3899
+
3900
+ this.trigger('query', {
3901
+ term: input
3902
+ });
3903
+ }
3904
+
3905
+ this._keyUpPrevented = false;
3906
+ };
3907
+
3908
+ Search.prototype.showSearch = function (_, params) {
3909
+ return true;
3910
+ };
3911
+
3912
+ return Search;
3913
+ });
3914
+
3915
+ S2.define('select2/dropdown/hidePlaceholder',[
3916
+
3917
+ ], function () {
3918
+ function HidePlaceholder (decorated, $element, options, dataAdapter) {
3919
+ this.placeholder = this.normalizePlaceholder(options.get('placeholder'));
3920
+
3921
+ decorated.call(this, $element, options, dataAdapter);
3922
+ }
3923
+
3924
+ HidePlaceholder.prototype.append = function (decorated, data) {
3925
+ data.results = this.removePlaceholder(data.results);
3926
+
3927
+ decorated.call(this, data);
3928
+ };
3929
+
3930
+ HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) {
3931
+ if (typeof placeholder === 'string') {
3932
+ placeholder = {
3933
+ id: '',
3934
+ text: placeholder
3935
+ };
3936
+ }
3937
+
3938
+ return placeholder;
3939
+ };
3940
+
3941
+ HidePlaceholder.prototype.removePlaceholder = function (_, data) {
3942
+ var modifiedData = data.slice(0);
3943
+
3944
+ for (var d = data.length - 1; d >= 0; d--) {
3945
+ var item = data[d];
3946
+
3947
+ if (this.placeholder.id === item.id) {
3948
+ modifiedData.splice(d, 1);
3949
+ }
3950
+ }
3951
+
3952
+ return modifiedData;
3953
+ };
3954
+
3955
+ return HidePlaceholder;
3956
+ });
3957
+
3958
+ S2.define('select2/dropdown/infiniteScroll',[
3959
+ 'jquery'
3960
+ ], function ($) {
3961
+ function InfiniteScroll (decorated, $element, options, dataAdapter) {
3962
+ this.lastParams = {};
3963
+
3964
+ decorated.call(this, $element, options, dataAdapter);
3965
+
3966
+ this.$loadingMore = this.createLoadingMore();
3967
+ this.loading = false;
3968
+ }
3969
+
3970
+ InfiniteScroll.prototype.append = function (decorated, data) {
3971
+ this.$loadingMore.remove();
3972
+ this.loading = false;
3973
+
3974
+ decorated.call(this, data);
3975
+
3976
+ if (this.showLoadingMore(data)) {
3977
+ this.$results.append(this.$loadingMore);
3978
+ }
3979
+ };
3980
+
3981
+ InfiniteScroll.prototype.bind = function (decorated, container, $container) {
3982
+ var self = this;
3983
+
3984
+ decorated.call(this, container, $container);
3985
+
3986
+ container.on('query', function (params) {
3987
+ self.lastParams = params;
3988
+ self.loading = true;
3989
+ });
3990
+
3991
+ container.on('query:append', function (params) {
3992
+ self.lastParams = params;
3993
+ self.loading = true;
3994
+ });
3995
+
3996
+ this.$results.on('scroll', function () {
3997
+ var isLoadMoreVisible = $.contains(
3998
+ document.documentElement,
3999
+ self.$loadingMore[0]
4000
+ );
4001
+
4002
+ if (self.loading || !isLoadMoreVisible) {
4003
+ return;
4004
+ }
4005
+
4006
+ var currentOffset = self.$results.offset().top +
4007
+ self.$results.outerHeight(false);
4008
+ var loadingMoreOffset = self.$loadingMore.offset().top +
4009
+ self.$loadingMore.outerHeight(false);
4010
+
4011
+ if (currentOffset + 50 >= loadingMoreOffset) {
4012
+ self.loadMore();
4013
+ }
4014
+ });
4015
+ };
4016
+
4017
+ InfiniteScroll.prototype.loadMore = function () {
4018
+ this.loading = true;
4019
+
4020
+ var params = $.extend({}, {page: 1}, this.lastParams);
4021
+
4022
+ params.page++;
4023
+
4024
+ this.trigger('query:append', params);
4025
+ };
4026
+
4027
+ InfiniteScroll.prototype.showLoadingMore = function (_, data) {
4028
+ return data.pagination && data.pagination.more;
4029
+ };
4030
+
4031
+ InfiniteScroll.prototype.createLoadingMore = function () {
4032
+ var $option = $(
4033
+ '<li ' +
4034
+ 'class="select2-results__option select2-results__option--load-more"' +
4035
+ 'role="treeitem" aria-disabled="true"></li>'
4036
+ );
4037
+
4038
+ var message = this.options.get('translations').get('loadingMore');
4039
+
4040
+ $option.html(message(this.lastParams));
4041
+
4042
+ return $option;
4043
+ };
4044
+
4045
+ return InfiniteScroll;
4046
+ });
4047
+
4048
+ S2.define('select2/dropdown/attachBody',[
4049
+ 'jquery',
4050
+ '../utils'
4051
+ ], function ($, Utils) {
4052
+ function AttachBody (decorated, $element, options) {
4053
+ this.$dropdownParent = options.get('dropdownParent') || $(document.body);
4054
+
4055
+ decorated.call(this, $element, options);
4056
+ }
4057
+
4058
+ AttachBody.prototype.bind = function (decorated, container, $container) {
4059
+ var self = this;
4060
+
4061
+ var setupResultsEvents = false;
4062
+
4063
+ decorated.call(this, container, $container);
4064
+
4065
+ container.on('open', function () {
4066
+ self._showDropdown();
4067
+ self._attachPositioningHandler(container);
4068
+
4069
+ if (!setupResultsEvents) {
4070
+ setupResultsEvents = true;
4071
+
4072
+ container.on('results:all', function () {
4073
+ self._positionDropdown();
4074
+ self._resizeDropdown();
4075
+ });
4076
+
4077
+ container.on('results:append', function () {
4078
+ self._positionDropdown();
4079
+ self._resizeDropdown();
4080
+ });
4081
+ }
4082
+ });
4083
+
4084
+ container.on('close', function () {
4085
+ self._hideDropdown();
4086
+ self._detachPositioningHandler(container);
4087
+ });
4088
+
4089
+ this.$dropdownContainer.on('mousedown', function (evt) {
4090
+ evt.stopPropagation();
4091
+ });
4092
+ };
4093
+
4094
+ AttachBody.prototype.destroy = function (decorated) {
4095
+ decorated.call(this);
4096
+
4097
+ this.$dropdownContainer.remove();
4098
+ };
4099
+
4100
+ AttachBody.prototype.position = function (decorated, $dropdown, $container) {
4101
+ // Clone all of the container classes
4102
+ $dropdown.attr('class', $container.attr('class'));
4103
+
4104
+ $dropdown.removeClass('select2');
4105
+ $dropdown.addClass('select2-container--open');
4106
+
4107
+ $dropdown.css({
4108
+ position: 'absolute',
4109
+ top: -999999
4110
+ });
4111
+
4112
+ this.$container = $container;
4113
+ };
4114
+
4115
+ AttachBody.prototype.render = function (decorated) {
4116
+ var $container = $('<span></span>');
4117
+
4118
+ var $dropdown = decorated.call(this);
4119
+ $container.append($dropdown);
4120
+
4121
+ this.$dropdownContainer = $container;
4122
+
4123
+ return $container;
4124
+ };
4125
+
4126
+ AttachBody.prototype._hideDropdown = function (decorated) {
4127
+ this.$dropdownContainer.detach();
4128
+ };
4129
+
4130
+ AttachBody.prototype._attachPositioningHandler =
4131
+ function (decorated, container) {
4132
+ var self = this;
4133
+
4134
+ var scrollEvent = 'scroll.select2.' + container.id;
4135
+ var resizeEvent = 'resize.select2.' + container.id;
4136
+ var orientationEvent = 'orientationchange.select2.' + container.id;
4137
+
4138
+ var $watchers = this.$container.parents().filter(Utils.hasScroll);
4139
+ $watchers.each(function () {
4140
+ $(this).data('select2-scroll-position', {
4141
+ x: $(this).scrollLeft(),
4142
+ y: $(this).scrollTop()
4143
+ });
4144
+ });
4145
+
4146
+ $watchers.on(scrollEvent, function (ev) {
4147
+ var position = $(this).data('select2-scroll-position');
4148
+ $(this).scrollTop(position.y);
4149
+ });
4150
+
4151
+ $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
4152
+ function (e) {
4153
+ self._positionDropdown();
4154
+ self._resizeDropdown();
4155
+ });
4156
+ };
4157
+
4158
+ AttachBody.prototype._detachPositioningHandler =
4159
+ function (decorated, container) {
4160
+ var scrollEvent = 'scroll.select2.' + container.id;
4161
+ var resizeEvent = 'resize.select2.' + container.id;
4162
+ var orientationEvent = 'orientationchange.select2.' + container.id;
4163
+
4164
+ var $watchers = this.$container.parents().filter(Utils.hasScroll);
4165
+ $watchers.off(scrollEvent);
4166
+
4167
+ $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);
4168
+ };
4169
+
4170
+ AttachBody.prototype._positionDropdown = function () {
4171
+ var $window = $(window);
4172
+
4173
+ var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');
4174
+ var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');
4175
+
4176
+ var newDirection = null;
4177
+
4178
+ var offset = this.$container.offset();
4179
+
4180
+ offset.bottom = offset.top + this.$container.outerHeight(false);
4181
+
4182
+ var container = {
4183
+ height: this.$container.outerHeight(false)
4184
+ };
4185
+
4186
+ container.top = offset.top;
4187
+ container.bottom = offset.top + container.height;
4188
+
4189
+ var dropdown = {
4190
+ height: this.$dropdown.outerHeight(false)
4191
+ };
4192
+
4193
+ var viewport = {
4194
+ top: $window.scrollTop(),
4195
+ bottom: $window.scrollTop() + $window.height()
4196
+ };
4197
+
4198
+ var enoughRoomAbove = viewport.top < (offset.top - dropdown.height);
4199
+ var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height);
4200
+
4201
+ var css = {
4202
+ left: offset.left,
4203
+ top: container.bottom
4204
+ };
4205
+
4206
+ // Determine what the parent element is to use for calciulating the offset
4207
+ var $offsetParent = this.$dropdownParent;
4208
+
4209
+ // For statically positoned elements, we need to get the element
4210
+ // that is determining the offset
4211
+ if ($offsetParent.css('position') === 'static') {
4212
+ $offsetParent = $offsetParent.offsetParent();
4213
+ }
4214
+
4215
+ var parentOffset = $offsetParent.offset();
4216
+
4217
+ css.top -= parentOffset.top;
4218
+ css.left -= parentOffset.left;
4219
+
4220
+ if (!isCurrentlyAbove && !isCurrentlyBelow) {
4221
+ newDirection = 'below';
4222
+ }
4223
+
4224
+ if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) {
4225
+ newDirection = 'above';
4226
+ } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) {
4227
+ newDirection = 'below';
4228
+ }
4229
+
4230
+ if (newDirection == 'above' ||
4231
+ (isCurrentlyAbove && newDirection !== 'below')) {
4232
+ css.top = container.top - dropdown.height;
4233
+ }
4234
+
4235
+ if (newDirection != null) {
4236
+ this.$dropdown
4237
+ .removeClass('select2-dropdown--below select2-dropdown--above')
4238
+ .addClass('select2-dropdown--' + newDirection);
4239
+ this.$container
4240
+ .removeClass('select2-container--below select2-container--above')
4241
+ .addClass('select2-container--' + newDirection);
4242
+ }
4243
+
4244
+ this.$dropdownContainer.css(css);
4245
+ };
4246
+
4247
+ AttachBody.prototype._resizeDropdown = function () {
4248
+ var css = {
4249
+ width: this.$container.outerWidth(false) + 'px'
4250
+ };
4251
+
4252
+ if (this.options.get('dropdownAutoWidth')) {
4253
+ css.minWidth = css.width;
4254
+ css.width = 'auto';
4255
+ }
4256
+
4257
+ this.$dropdown.css(css);
4258
+ };
4259
+
4260
+ AttachBody.prototype._showDropdown = function (decorated) {
4261
+ this.$dropdownContainer.appendTo(this.$dropdownParent);
4262
+
4263
+ this._positionDropdown();
4264
+ this._resizeDropdown();
4265
+ };
4266
+
4267
+ return AttachBody;
4268
+ });
4269
+
4270
+ S2.define('select2/dropdown/minimumResultsForSearch',[
4271
+
4272
+ ], function () {
4273
+ function countResults (data) {
4274
+ var count = 0;
4275
+
4276
+ for (var d = 0; d < data.length; d++) {
4277
+ var item = data[d];
4278
+
4279
+ if (item.children) {
4280
+ count += countResults(item.children);
4281
+ } else {
4282
+ count++;
4283
+ }
4284
+ }
4285
+
4286
+ return count;
4287
+ }
4288
+
4289
+ function MinimumResultsForSearch (decorated, $element, options, dataAdapter) {
4290
+ this.minimumResultsForSearch = options.get('minimumResultsForSearch');
4291
+
4292
+ if (this.minimumResultsForSearch < 0) {
4293
+ this.minimumResultsForSearch = Infinity;
4294
+ }
4295
+
4296
+ decorated.call(this, $element, options, dataAdapter);
4297
+ }
4298
+
4299
+ MinimumResultsForSearch.prototype.showSearch = function (decorated, params) {
4300
+ if (countResults(params.data.results) < this.minimumResultsForSearch) {
4301
+ return false;
4302
+ }
4303
+
4304
+ return decorated.call(this, params);
4305
+ };
4306
+
4307
+ return MinimumResultsForSearch;
4308
+ });
4309
+
4310
+ S2.define('select2/dropdown/selectOnClose',[
4311
+
4312
+ ], function () {
4313
+ function SelectOnClose () { }
4314
+
4315
+ SelectOnClose.prototype.bind = function (decorated, container, $container) {
4316
+ var self = this;
4317
+
4318
+ decorated.call(this, container, $container);
4319
+
4320
+ container.on('close', function () {
4321
+ self._handleSelectOnClose();
4322
+ });
4323
+ };
4324
+
4325
+ SelectOnClose.prototype._handleSelectOnClose = function () {
4326
+ var $highlightedResults = this.getHighlightedResults();
4327
+
4328
+ // Only select highlighted results
4329
+ if ($highlightedResults.length < 1) {
4330
+ return;
4331
+ }
4332
+
4333
+ var data = $highlightedResults.data('data');
4334
+
4335
+ // Don't re-select already selected resulte
4336
+ if (
4337
+ (data.element != null && data.element.selected) ||
4338
+ (data.element == null && data.selected)
4339
+ ) {
4340
+ return;
4341
+ }
4342
+
4343
+ this.trigger('select', {
4344
+ data: data
4345
+ });
4346
+ };
4347
+
4348
+ return SelectOnClose;
4349
+ });
4350
+
4351
+ S2.define('select2/dropdown/closeOnSelect',[
4352
+
4353
+ ], function () {
4354
+ function CloseOnSelect () { }
4355
+
4356
+ CloseOnSelect.prototype.bind = function (decorated, container, $container) {
4357
+ var self = this;
4358
+
4359
+ decorated.call(this, container, $container);
4360
+
4361
+ container.on('select', function (evt) {
4362
+ self._selectTriggered(evt);
4363
+ });
4364
+
4365
+ container.on('unselect', function (evt) {
4366
+ self._selectTriggered(evt);
4367
+ });
4368
+ };
4369
+
4370
+ CloseOnSelect.prototype._selectTriggered = function (_, evt) {
4371
+ var originalEvent = evt.originalEvent;
4372
+
4373
+ // Don't close if the control key is being held
4374
+ if (originalEvent && originalEvent.ctrlKey) {
4375
+ return;
4376
+ }
4377
+
4378
+ this.trigger('close', {});
4379
+ };
4380
+
4381
+ return CloseOnSelect;
4382
+ });
4383
+
4384
+ S2.define('select2/i18n/en',[],function () {
4385
+ // English
4386
+ return {
4387
+ errorLoading: function () {
4388
+ return 'The results could not be loaded.';
4389
+ },
4390
+ inputTooLong: function (args) {
4391
+ var overChars = args.input.length - args.maximum;
4392
+
4393
+ var message = 'Please delete ' + overChars + ' character';
4394
+
4395
+ if (overChars != 1) {
4396
+ message += 's';
4397
+ }
4398
+
4399
+ return message;
4400
+ },
4401
+ inputTooShort: function (args) {
4402
+ var remainingChars = args.minimum - args.input.length;
4403
+
4404
+ var message = 'Please enter ' + remainingChars + ' or more characters';
4405
+
4406
+ return message;
4407
+ },
4408
+ loadingMore: function () {
4409
+ return 'Loading more results…';
4410
+ },
4411
+ maximumSelected: function (args) {
4412
+ var message = 'You can only select ' + args.maximum + ' item';
4413
+
4414
+ if (args.maximum != 1) {
4415
+ message += 's';
4416
+ }
4417
+
4418
+ return message;
4419
+ },
4420
+ noResults: function () {
4421
+ return 'No results found';
4422
+ },
4423
+ searching: function () {
4424
+ return 'Searching…';
4425
+ }
4426
+ };
4427
+ });
4428
+
4429
+ S2.define('select2/defaults',[
4430
+ 'jquery',
4431
+ 'require',
4432
+
4433
+ './results',
4434
+
4435
+ './selection/single',
4436
+ './selection/multiple',
4437
+ './selection/placeholder',
4438
+ './selection/allowClear',
4439
+ './selection/search',
4440
+ './selection/eventRelay',
4441
+
4442
+ './utils',
4443
+ './translation',
4444
+ './diacritics',
4445
+
4446
+ './data/select',
4447
+ './data/array',
4448
+ './data/ajax',
4449
+ './data/tags',
4450
+ './data/tokenizer',
4451
+ './data/minimumInputLength',
4452
+ './data/maximumInputLength',
4453
+ './data/maximumSelectionLength',
4454
+
4455
+ './dropdown',
4456
+ './dropdown/search',
4457
+ './dropdown/hidePlaceholder',
4458
+ './dropdown/infiniteScroll',
4459
+ './dropdown/attachBody',
4460
+ './dropdown/minimumResultsForSearch',
4461
+ './dropdown/selectOnClose',
4462
+ './dropdown/closeOnSelect',
4463
+
4464
+ './i18n/en'
4465
+ ], function ($, require,
4466
+
4467
+ ResultsList,
4468
+
4469
+ SingleSelection, MultipleSelection, Placeholder, AllowClear,
4470
+ SelectionSearch, EventRelay,
4471
+
4472
+ Utils, Translation, DIACRITICS,
4473
+
4474
+ SelectData, ArrayData, AjaxData, Tags, Tokenizer,
4475
+ MinimumInputLength, MaximumInputLength, MaximumSelectionLength,
4476
+
4477
+ Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll,
4478
+ AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect,
4479
+
4480
+ EnglishTranslation) {
4481
+ function Defaults () {
4482
+ this.reset();
4483
+ }
4484
+
4485
+ Defaults.prototype.apply = function (options) {
4486
+ options = $.extend(true, {}, this.defaults, options);
4487
+
4488
+ if (options.dataAdapter == null) {
4489
+ if (options.ajax != null) {
4490
+ options.dataAdapter = AjaxData;
4491
+ } else if (options.data != null) {
4492
+ options.dataAdapter = ArrayData;
4493
+ } else {
4494
+ options.dataAdapter = SelectData;
4495
+ }
4496
+
4497
+ if (options.minimumInputLength > 0) {
4498
+ options.dataAdapter = Utils.Decorate(
4499
+ options.dataAdapter,
4500
+ MinimumInputLength
4501
+ );
4502
+ }
4503
+
4504
+ if (options.maximumInputLength > 0) {
4505
+ options.dataAdapter = Utils.Decorate(
4506
+ options.dataAdapter,
4507
+ MaximumInputLength
4508
+ );
4509
+ }
4510
+
4511
+ if (options.maximumSelectionLength > 0) {
4512
+ options.dataAdapter = Utils.Decorate(
4513
+ options.dataAdapter,
4514
+ MaximumSelectionLength
4515
+ );
4516
+ }
4517
+
4518
+ if (options.tags) {
4519
+ options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags);
4520
+ }
4521
+
4522
+ if (options.tokenSeparators != null || options.tokenizer != null) {
4523
+ options.dataAdapter = Utils.Decorate(
4524
+ options.dataAdapter,
4525
+ Tokenizer
4526
+ );
4527
+ }
4528
+
4529
+ if (options.query != null) {
4530
+ var Query = require(options.amdBase + 'compat/query');
4531
+
4532
+ options.dataAdapter = Utils.Decorate(
4533
+ options.dataAdapter,
4534
+ Query
4535
+ );
4536
+ }
4537
+
4538
+ if (options.initSelection != null) {
4539
+ var InitSelection = require(options.amdBase + 'compat/initSelection');
4540
+
4541
+ options.dataAdapter = Utils.Decorate(
4542
+ options.dataAdapter,
4543
+ InitSelection
4544
+ );
4545
+ }
4546
+ }
4547
+
4548
+ if (options.resultsAdapter == null) {
4549
+ options.resultsAdapter = ResultsList;
4550
+
4551
+ if (options.ajax != null) {
4552
+ options.resultsAdapter = Utils.Decorate(
4553
+ options.resultsAdapter,
4554
+ InfiniteScroll
4555
+ );
4556
+ }
4557
+
4558
+ if (options.placeholder != null) {
4559
+ options.resultsAdapter = Utils.Decorate(
4560
+ options.resultsAdapter,
4561
+ HidePlaceholder
4562
+ );
4563
+ }
4564
+
4565
+ if (options.selectOnClose) {
4566
+ options.resultsAdapter = Utils.Decorate(
4567
+ options.resultsAdapter,
4568
+ SelectOnClose
4569
+ );
4570
+ }
4571
+ }
4572
+
4573
+ if (options.dropdownAdapter == null) {
4574
+ if (options.multiple) {
4575
+ options.dropdownAdapter = Dropdown;
4576
+ } else {
4577
+ var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch);
4578
+
4579
+ options.dropdownAdapter = SearchableDropdown;
4580
+ }
4581
+
4582
+ if (options.minimumResultsForSearch !== 0) {
4583
+ options.dropdownAdapter = Utils.Decorate(
4584
+ options.dropdownAdapter,
4585
+ MinimumResultsForSearch
4586
+ );
4587
+ }
4588
+
4589
+ if (options.closeOnSelect) {
4590
+ options.dropdownAdapter = Utils.Decorate(
4591
+ options.dropdownAdapter,
4592
+ CloseOnSelect
4593
+ );
4594
+ }
4595
+
4596
+ if (
4597
+ options.dropdownCssClass != null ||
4598
+ options.dropdownCss != null ||
4599
+ options.adaptDropdownCssClass != null
4600
+ ) {
4601
+ var DropdownCSS = require(options.amdBase + 'compat/dropdownCss');
4602
+
4603
+ options.dropdownAdapter = Utils.Decorate(
4604
+ options.dropdownAdapter,
4605
+ DropdownCSS
4606
+ );
4607
+ }
4608
+
4609
+ options.dropdownAdapter = Utils.Decorate(
4610
+ options.dropdownAdapter,
4611
+ AttachBody
4612
+ );
4613
+ }
4614
+
4615
+ if (options.selectionAdapter == null) {
4616
+ if (options.multiple) {
4617
+ options.selectionAdapter = MultipleSelection;
4618
+ } else {
4619
+ options.selectionAdapter = SingleSelection;
4620
+ }
4621
+
4622
+ // Add the placeholder mixin if a placeholder was specified
4623
+ if (options.placeholder != null) {
4624
+ options.selectionAdapter = Utils.Decorate(
4625
+ options.selectionAdapter,
4626
+ Placeholder
4627
+ );
4628
+ }
4629
+
4630
+ if (options.allowClear) {
4631
+ options.selectionAdapter = Utils.Decorate(
4632
+ options.selectionAdapter,
4633
+ AllowClear
4634
+ );
4635
+ }
4636
+
4637
+ if (options.multiple) {
4638
+ options.selectionAdapter = Utils.Decorate(
4639
+ options.selectionAdapter,
4640
+ SelectionSearch
4641
+ );
4642
+ }
4643
+
4644
+ if (
4645
+ options.containerCssClass != null ||
4646
+ options.containerCss != null ||
4647
+ options.adaptContainerCssClass != null
4648
+ ) {
4649
+ var ContainerCSS = require(options.amdBase + 'compat/containerCss');
4650
+
4651
+ options.selectionAdapter = Utils.Decorate(
4652
+ options.selectionAdapter,
4653
+ ContainerCSS
4654
+ );
4655
+ }
4656
+
4657
+ options.selectionAdapter = Utils.Decorate(
4658
+ options.selectionAdapter,
4659
+ EventRelay
4660
+ );
4661
+ }
4662
+
4663
+ if (typeof options.language === 'string') {
4664
+ // Check if the language is specified with a region
4665
+ if (options.language.indexOf('-') > 0) {
4666
+ // Extract the region information if it is included
4667
+ var languageParts = options.language.split('-');
4668
+ var baseLanguage = languageParts[0];
4669
+
4670
+ options.language = [options.language, baseLanguage];
4671
+ } else {
4672
+ options.language = [options.language];
4673
+ }
4674
+ }
4675
+
4676
+ if ($.isArray(options.language)) {
4677
+ var languages = new Translation();
4678
+ options.language.push('en');
4679
+
4680
+ var languageNames = options.language;
4681
+
4682
+ for (var l = 0; l < languageNames.length; l++) {
4683
+ var name = languageNames[l];
4684
+ var language = {};
4685
+
4686
+ try {
4687
+ // Try to load it with the original name
4688
+ language = Translation.loadPath(name);
4689
+ } catch (e) {
4690
+ try {
4691
+ // If we couldn't load it, check if it wasn't the full path
4692
+ name = this.defaults.amdLanguageBase + name;
4693
+ language = Translation.loadPath(name);
4694
+ } catch (ex) {
4695
+ // The translation could not be loaded at all. Sometimes this is
4696
+ // because of a configuration problem, other times this can be
4697
+ // because of how Select2 helps load all possible translation files.
4698
+ if (options.debug && window.console && console.warn) {
4699
+ console.warn(
4700
+ 'Select2: The language file for "' + name + '" could not be ' +
4701
+ 'automatically loaded. A fallback will be used instead.'
4702
+ );
4703
+ }
4704
+
4705
+ continue;
4706
+ }
4707
+ }
4708
+
4709
+ languages.extend(language);
4710
+ }
4711
+
4712
+ options.translations = languages;
4713
+ } else {
4714
+ var baseTranslation = Translation.loadPath(
4715
+ this.defaults.amdLanguageBase + 'en'
4716
+ );
4717
+ var customTranslation = new Translation(options.language);
4718
+
4719
+ customTranslation.extend(baseTranslation);
4720
+
4721
+ options.translations = customTranslation;
4722
+ }
4723
+
4724
+ return options;
4725
+ };
4726
+
4727
+ Defaults.prototype.reset = function () {
4728
+ function stripDiacritics (text) {
4729
+ // Used 'uni range + named function' from http://jsperf.com/diacritics/18
4730
+ function match(a) {
4731
+ return DIACRITICS[a] || a;
4732
+ }
4733
+
4734
+ return text.replace(/[^\u0000-\u007E]/g, match);
4735
+ }
4736
+
4737
+ function matcher (params, data) {
4738
+ // Always return the object if there is nothing to compare
4739
+ if ($.trim(params.term) === '') {
4740
+ return data;
4741
+ }
4742
+
4743
+ // Do a recursive check for options with children
4744
+ if (data.children && data.children.length > 0) {
4745
+ // Clone the data object if there are children
4746
+ // This is required as we modify the object to remove any non-matches
4747
+ var match = $.extend(true, {}, data);
4748
+
4749
+ // Check each child of the option
4750
+ for (var c = data.children.length - 1; c >= 0; c--) {
4751
+ var child = data.children[c];
4752
+
4753
+ var matches = matcher(params, child);
4754
+
4755
+ // If there wasn't a match, remove the object in the array
4756
+ if (matches == null) {
4757
+ match.children.splice(c, 1);
4758
+ }
4759
+ }
4760
+
4761
+ // If any children matched, return the new object
4762
+ if (match.children.length > 0) {
4763
+ return match;
4764
+ }
4765
+
4766
+ // If there were no matching children, check just the plain object
4767
+ return matcher(params, match);
4768
+ }
4769
+
4770
+ var original = stripDiacritics(data.text).toUpperCase();
4771
+ var term = stripDiacritics(params.term).toUpperCase();
4772
+
4773
+ // Check if the text contains the term
4774
+ if (original.indexOf(term) > -1) {
4775
+ return data;
4776
+ }
4777
+
4778
+ // If it doesn't contain the term, don't return anything
4779
+ return null;
4780
+ }
4781
+
4782
+ this.defaults = {
4783
+ amdBase: './',
4784
+ amdLanguageBase: './i18n/',
4785
+ closeOnSelect: true,
4786
+ debug: false,
4787
+ dropdownAutoWidth: false,
4788
+ escapeMarkup: Utils.escapeMarkup,
4789
+ language: EnglishTranslation,
4790
+ matcher: matcher,
4791
+ minimumInputLength: 0,
4792
+ maximumInputLength: 0,
4793
+ maximumSelectionLength: 0,
4794
+ minimumResultsForSearch: 0,
4795
+ selectOnClose: false,
4796
+ sorter: function (data) {
4797
+ return data;
4798
+ },
4799
+ templateResult: function (result) {
4800
+ return result.text;
4801
+ },
4802
+ templateSelection: function (selection) {
4803
+ return selection.text;
4804
+ },
4805
+ theme: 'default',
4806
+ width: 'resolve'
4807
+ };
4808
+ };
4809
+
4810
+ Defaults.prototype.set = function (key, value) {
4811
+ var camelKey = $.camelCase(key);
4812
+
4813
+ var data = {};
4814
+ data[camelKey] = value;
4815
+
4816
+ var convertedData = Utils._convertData(data);
4817
+
4818
+ $.extend(this.defaults, convertedData);
4819
+ };
4820
+
4821
+ var defaults = new Defaults();
4822
+
4823
+ return defaults;
4824
+ });
4825
+
4826
+ S2.define('select2/options',[
4827
+ 'require',
4828
+ 'jquery',
4829
+ './defaults',
4830
+ './utils'
4831
+ ], function (require, $, Defaults, Utils) {
4832
+ function Options (options, $element) {
4833
+ this.options = options;
4834
+
4835
+ if ($element != null) {
4836
+ this.fromElement($element);
4837
+ }
4838
+
4839
+ this.options = Defaults.apply(this.options);
4840
+
4841
+ if ($element && $element.is('input')) {
4842
+ var InputCompat = require(this.get('amdBase') + 'compat/inputData');
4843
+
4844
+ this.options.dataAdapter = Utils.Decorate(
4845
+ this.options.dataAdapter,
4846
+ InputCompat
4847
+ );
4848
+ }
4849
+ }
4850
+
4851
+ Options.prototype.fromElement = function ($e) {
4852
+ var excludedData = ['select2'];
4853
+
4854
+ if (this.options.multiple == null) {
4855
+ this.options.multiple = $e.prop('multiple');
4856
+ }
4857
+
4858
+ if (this.options.disabled == null) {
4859
+ this.options.disabled = $e.prop('disabled');
4860
+ }
4861
+
4862
+ if (this.options.language == null) {
4863
+ if ($e.prop('lang')) {
4864
+ this.options.language = $e.prop('lang').toLowerCase();
4865
+ } else if ($e.closest('[lang]').prop('lang')) {
4866
+ this.options.language = $e.closest('[lang]').prop('lang');
4867
+ }
4868
+ }
4869
+
4870
+ if (this.options.dir == null) {
4871
+ if ($e.prop('dir')) {
4872
+ this.options.dir = $e.prop('dir');
4873
+ } else if ($e.closest('[dir]').prop('dir')) {
4874
+ this.options.dir = $e.closest('[dir]').prop('dir');
4875
+ } else {
4876
+ this.options.dir = 'ltr';
4877
+ }
4878
+ }
4879
+
4880
+ $e.prop('disabled', this.options.disabled);
4881
+ $e.prop('multiple', this.options.multiple);
4882
+
4883
+ if ($e.data('select2Tags')) {
4884
+ if (this.options.debug && window.console && console.warn) {
4885
+ console.warn(
4886
+ 'Select2: The `data-select2-tags` attribute has been changed to ' +
4887
+ 'use the `data-data` and `data-tags="true"` attributes and will be ' +
4888
+ 'removed in future versions of Select2.'
4889
+ );
4890
+ }
4891
+
4892
+ $e.data('data', $e.data('select2Tags'));
4893
+ $e.data('tags', true);
4894
+ }
4895
+
4896
+ if ($e.data('ajaxUrl')) {
4897
+ if (this.options.debug && window.console && console.warn) {
4898
+ console.warn(
4899
+ 'Select2: The `data-ajax-url` attribute has been changed to ' +
4900
+ '`data-ajax--url` and support for the old attribute will be removed' +
4901
+ ' in future versions of Select2.'
4902
+ );
4903
+ }
4904
+
4905
+ $e.attr('ajax--url', $e.data('ajaxUrl'));
4906
+ $e.data('ajax--url', $e.data('ajaxUrl'));
4907
+ }
4908
+
4909
+ var dataset = {};
4910
+
4911
+ // Prefer the element's `dataset` attribute if it exists
4912
+ // jQuery 1.x does not correctly handle data attributes with multiple dashes
4913
+ if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) {
4914
+ dataset = $.extend(true, {}, $e[0].dataset, $e.data());
4915
+ } else {
4916
+ dataset = $e.data();
4917
+ }
4918
+
4919
+ var data = $.extend(true, {}, dataset);
4920
+
4921
+ data = Utils._convertData(data);
4922
+
4923
+ for (var key in data) {
4924
+ if ($.inArray(key, excludedData) > -1) {
4925
+ continue;
4926
+ }
4927
+
4928
+ if ($.isPlainObject(this.options[key])) {
4929
+ $.extend(this.options[key], data[key]);
4930
+ } else {
4931
+ this.options[key] = data[key];
4932
+ }
4933
+ }
4934
+
4935
+ return this;
4936
+ };
4937
+
4938
+ Options.prototype.get = function (key) {
4939
+ return this.options[key];
4940
+ };
4941
+
4942
+ Options.prototype.set = function (key, val) {
4943
+ this.options[key] = val;
4944
+ };
4945
+
4946
+ return Options;
4947
+ });
4948
+
4949
+ S2.define('select2/core',[
4950
+ 'jquery',
4951
+ './options',
4952
+ './utils',
4953
+ './keys'
4954
+ ], function ($, Options, Utils, KEYS) {
4955
+ var Select2 = function ($element, options) {
4956
+ if ($element.data('select2') != null) {
4957
+ $element.data('select2').destroy();
4958
+ }
4959
+
4960
+ this.$element = $element;
4961
+
4962
+ this.id = this._generateId($element);
4963
+
4964
+ options = options || {};
4965
+
4966
+ this.options = new Options(options, $element);
4967
+
4968
+ Select2.__super__.constructor.call(this);
4969
+
4970
+ // Set up the tabindex
4971
+
4972
+ var tabindex = $element.attr('tabindex') || 0;
4973
+ $element.data('old-tabindex', tabindex);
4974
+ $element.attr('tabindex', '-1');
4975
+
4976
+ // Set up containers and adapters
4977
+
4978
+ var DataAdapter = this.options.get('dataAdapter');
4979
+ this.dataAdapter = new DataAdapter($element, this.options);
4980
+
4981
+ var $container = this.render();
4982
+
4983
+ this._placeContainer($container);
4984
+
4985
+ var SelectionAdapter = this.options.get('selectionAdapter');
4986
+ this.selection = new SelectionAdapter($element, this.options);
4987
+ this.$selection = this.selection.render();
4988
+
4989
+ this.selection.position(this.$selection, $container);
4990
+
4991
+ var DropdownAdapter = this.options.get('dropdownAdapter');
4992
+ this.dropdown = new DropdownAdapter($element, this.options);
4993
+ this.$dropdown = this.dropdown.render();
4994
+
4995
+ this.dropdown.position(this.$dropdown, $container);
4996
+
4997
+ var ResultsAdapter = this.options.get('resultsAdapter');
4998
+ this.results = new ResultsAdapter($element, this.options, this.dataAdapter);
4999
+ this.$results = this.results.render();
5000
+
5001
+ this.results.position(this.$results, this.$dropdown);
5002
+
5003
+ // Bind events
5004
+
5005
+ var self = this;
5006
+
5007
+ // Bind the container to all of the adapters
5008
+ this._bindAdapters();
5009
+
5010
+ // Register any DOM event handlers
5011
+ this._registerDomEvents();
5012
+
5013
+ // Register any internal event handlers
5014
+ this._registerDataEvents();
5015
+ this._registerSelectionEvents();
5016
+ this._registerDropdownEvents();
5017
+ this._registerResultsEvents();
5018
+ this._registerEvents();
5019
+
5020
+ // Set the initial state
5021
+ this.dataAdapter.current(function (initialData) {
5022
+ self.trigger('selection:update', {
5023
+ data: initialData
5024
+ });
5025
+ });
5026
+
5027
+ // Hide the original select
5028
+ $element.addClass('select2-hidden-accessible');
5029
+ $element.attr('aria-hidden', 'true');
5030
+
5031
+ // Synchronize any monitored attributes
5032
+ this._syncAttributes();
5033
+
5034
+ $element.data('select2', this);
5035
+ };
5036
+
5037
+ Utils.Extend(Select2, Utils.Observable);
5038
+
5039
+ Select2.prototype._generateId = function ($element) {
5040
+ var id = '';
5041
+
5042
+ if ($element.attr('id') != null) {
5043
+ id = $element.attr('id');
5044
+ } else if ($element.attr('name') != null) {
5045
+ id = $element.attr('name') + '-' + Utils.generateChars(2);
5046
+ } else {
5047
+ id = Utils.generateChars(4);
5048
+ }
5049
+
5050
+ id = id.replace(/(:|\.|\[|\]|,)/g, '');
5051
+ id = 'select2-' + id;
5052
+
5053
+ return id;
5054
+ };
5055
+
5056
+ Select2.prototype._placeContainer = function ($container) {
5057
+ $container.insertAfter(this.$element);
5058
+
5059
+ var width = this._resolveWidth(this.$element, this.options.get('width'));
5060
+
5061
+ if (width != null) {
5062
+ $container.css('width', width);
5063
+ }
5064
+ };
5065
+
5066
+ Select2.prototype._resolveWidth = function ($element, method) {
5067
+ var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;
5068
+
5069
+ if (method == 'resolve') {
5070
+ var styleWidth = this._resolveWidth($element, 'style');
5071
+
5072
+ if (styleWidth != null) {
5073
+ return styleWidth;
5074
+ }
5075
+
5076
+ return this._resolveWidth($element, 'element');
5077
+ }
5078
+
5079
+ if (method == 'element') {
5080
+ var elementWidth = $element.outerWidth(false);
5081
+
5082
+ if (elementWidth <= 0) {
5083
+ return 'auto';
5084
+ }
5085
+
5086
+ return elementWidth + 'px';
5087
+ }
5088
+
5089
+ if (method == 'style') {
5090
+ var style = $element.attr('style');
5091
+
5092
+ if (typeof(style) !== 'string') {
5093
+ return null;
5094
+ }
5095
+
5096
+ var attrs = style.split(';');
5097
+
5098
+ for (var i = 0, l = attrs.length; i < l; i = i + 1) {
5099
+ var attr = attrs[i].replace(/\s/g, '');
5100
+ var matches = attr.match(WIDTH);
5101
+
5102
+ if (matches !== null && matches.length >= 1) {
5103
+ return matches[1];
5104
+ }
5105
+ }
5106
+
5107
+ return null;
5108
+ }
5109
+
5110
+ return method;
5111
+ };
5112
+
5113
+ Select2.prototype._bindAdapters = function () {
5114
+ this.dataAdapter.bind(this, this.$container);
5115
+ this.selection.bind(this, this.$container);
5116
+
5117
+ this.dropdown.bind(this, this.$container);
5118
+ this.results.bind(this, this.$container);
5119
+ };
5120
+
5121
+ Select2.prototype._registerDomEvents = function () {
5122
+ var self = this;
5123
+
5124
+ this.$element.on('change.select2', function () {
5125
+ self.dataAdapter.current(function (data) {
5126
+ self.trigger('selection:update', {
5127
+ data: data
5128
+ });
5129
+ });
5130
+ });
5131
+
5132
+ this._sync = Utils.bind(this._syncAttributes, this);
5133
+
5134
+ if (this.$element[0].attachEvent) {
5135
+ this.$element[0].attachEvent('onpropertychange', this._sync);
5136
+ }
5137
+
5138
+ var observer = window.MutationObserver ||
5139
+ window.WebKitMutationObserver ||
5140
+ window.MozMutationObserver
5141
+ ;
5142
+
5143
+ if (observer != null) {
5144
+ this._observer = new observer(function (mutations) {
5145
+ $.each(mutations, self._sync);
5146
+ });
5147
+ this._observer.observe(this.$element[0], {
5148
+ attributes: true,
5149
+ subtree: false
5150
+ });
5151
+ } else if (this.$element[0].addEventListener) {
5152
+ this.$element[0].addEventListener('DOMAttrModified', self._sync, false);
5153
+ }
5154
+ };
5155
+
5156
+ Select2.prototype._registerDataEvents = function () {
5157
+ var self = this;
5158
+
5159
+ this.dataAdapter.on('*', function (name, params) {
5160
+ self.trigger(name, params);
5161
+ });
5162
+ };
5163
+
5164
+ Select2.prototype._registerSelectionEvents = function () {
5165
+ var self = this;
5166
+ var nonRelayEvents = ['toggle', 'focus'];
5167
+
5168
+ this.selection.on('toggle', function () {
5169
+ self.toggleDropdown();
5170
+ });
5171
+
5172
+ this.selection.on('focus', function (params) {
5173
+ self.focus(params);
5174
+ });
5175
+
5176
+ this.selection.on('*', function (name, params) {
5177
+ if ($.inArray(name, nonRelayEvents) !== -1) {
5178
+ return;
5179
+ }
5180
+
5181
+ self.trigger(name, params);
5182
+ });
5183
+ };
5184
+
5185
+ Select2.prototype._registerDropdownEvents = function () {
5186
+ var self = this;
5187
+
5188
+ this.dropdown.on('*', function (name, params) {
5189
+ self.trigger(name, params);
5190
+ });
5191
+ };
5192
+
5193
+ Select2.prototype._registerResultsEvents = function () {
5194
+ var self = this;
5195
+
5196
+ this.results.on('*', function (name, params) {
5197
+ self.trigger(name, params);
5198
+ });
5199
+ };
5200
+
5201
+ Select2.prototype._registerEvents = function () {
5202
+ var self = this;
5203
+
5204
+ this.on('open', function () {
5205
+ self.$container.addClass('select2-container--open');
5206
+ });
5207
+
5208
+ this.on('close', function () {
5209
+ self.$container.removeClass('select2-container--open');
5210
+ });
5211
+
5212
+ this.on('enable', function () {
5213
+ self.$container.removeClass('select2-container--disabled');
5214
+ });
5215
+
5216
+ this.on('disable', function () {
5217
+ self.$container.addClass('select2-container--disabled');
5218
+ });
5219
+
5220
+ this.on('blur', function () {
5221
+ self.$container.removeClass('select2-container--focus');
5222
+ });
5223
+
5224
+ this.on('query', function (params) {
5225
+ if (!self.isOpen()) {
5226
+ self.trigger('open', {});
5227
+ }
5228
+
5229
+ this.dataAdapter.query(params, function (data) {
5230
+ self.trigger('results:all', {
5231
+ data: data,
5232
+ query: params
5233
+ });
5234
+ });
5235
+ });
5236
+
5237
+ this.on('query:append', function (params) {
5238
+ this.dataAdapter.query(params, function (data) {
5239
+ self.trigger('results:append', {
5240
+ data: data,
5241
+ query: params
5242
+ });
5243
+ });
5244
+ });
5245
+
5246
+ this.on('keypress', function (evt) {
5247
+ var key = evt.which;
5248
+
5249
+ if (self.isOpen()) {
5250
+ if (key === KEYS.ESC || key === KEYS.TAB ||
5251
+ (key === KEYS.UP && evt.altKey)) {
5252
+ self.close();
5253
+
5254
+ evt.preventDefault();
5255
+ } else if (key === KEYS.ENTER) {
5256
+ self.trigger('results:select', {});
5257
+
5258
+ evt.preventDefault();
5259
+ } else if ((key === KEYS.SPACE && evt.ctrlKey)) {
5260
+ self.trigger('results:toggle', {});
5261
+
5262
+ evt.preventDefault();
5263
+ } else if (key === KEYS.UP) {
5264
+ self.trigger('results:previous', {});
5265
+
5266
+ evt.preventDefault();
5267
+ } else if (key === KEYS.DOWN) {
5268
+ self.trigger('results:next', {});
5269
+
5270
+ evt.preventDefault();
5271
+ }
5272
+ } else {
5273
+ if (key === KEYS.ENTER || key === KEYS.SPACE ||
5274
+ (key === KEYS.DOWN && evt.altKey)) {
5275
+ self.open();
5276
+
5277
+ evt.preventDefault();
5278
+ }
5279
+ }
5280
+ });
5281
+ };
5282
+
5283
+ Select2.prototype._syncAttributes = function () {
5284
+ this.options.set('disabled', this.$element.prop('disabled'));
5285
+
5286
+ if (this.options.get('disabled')) {
5287
+ if (this.isOpen()) {
5288
+ this.close();
5289
+ }
5290
+
5291
+ this.trigger('disable', {});
5292
+ } else {
5293
+ this.trigger('enable', {});
5294
+ }
5295
+ };
5296
+
5297
+ /**
5298
+ * Override the trigger method to automatically trigger pre-events when
5299
+ * there are events that can be prevented.
5300
+ */
5301
+ Select2.prototype.trigger = function (name, args) {
5302
+ var actualTrigger = Select2.__super__.trigger;
5303
+ var preTriggerMap = {
5304
+ 'open': 'opening',
5305
+ 'close': 'closing',
5306
+ 'select': 'selecting',
5307
+ 'unselect': 'unselecting'
5308
+ };
5309
+
5310
+ if (args === undefined) {
5311
+ args = {};
5312
+ }
5313
+
5314
+ if (name in preTriggerMap) {
5315
+ var preTriggerName = preTriggerMap[name];
5316
+ var preTriggerArgs = {
5317
+ prevented: false,
5318
+ name: name,
5319
+ args: args
5320
+ };
5321
+
5322
+ actualTrigger.call(this, preTriggerName, preTriggerArgs);
5323
+
5324
+ if (preTriggerArgs.prevented) {
5325
+ args.prevented = true;
5326
+
5327
+ return;
5328
+ }
5329
+ }
5330
+
5331
+ actualTrigger.call(this, name, args);
5332
+ };
5333
+
5334
+ Select2.prototype.toggleDropdown = function () {
5335
+ if (this.options.get('disabled')) {
5336
+ return;
5337
+ }
5338
+
5339
+ if (this.isOpen()) {
5340
+ this.close();
5341
+ } else {
5342
+ this.open();
5343
+ }
5344
+ };
5345
+
5346
+ Select2.prototype.open = function () {
5347
+ if (this.isOpen()) {
5348
+ return;
5349
+ }
5350
+
5351
+ this.trigger('query', {});
5352
+ };
5353
+
5354
+ Select2.prototype.close = function () {
5355
+ if (!this.isOpen()) {
5356
+ return;
5357
+ }
5358
+
5359
+ this.trigger('close', {});
5360
+ };
5361
+
5362
+ Select2.prototype.isOpen = function () {
5363
+ return this.$container.hasClass('select2-container--open');
5364
+ };
5365
+
5366
+ Select2.prototype.hasFocus = function () {
5367
+ return this.$container.hasClass('select2-container--focus');
5368
+ };
5369
+
5370
+ Select2.prototype.focus = function (data) {
5371
+ // No need to re-trigger focus events if we are already focused
5372
+ if (this.hasFocus()) {
5373
+ return;
5374
+ }
5375
+
5376
+ this.$container.addClass('select2-container--focus');
5377
+ this.trigger('focus', {});
5378
+ };
5379
+
5380
+ Select2.prototype.enable = function (args) {
5381
+ if (this.options.get('debug') && window.console && console.warn) {
5382
+ console.warn(
5383
+ 'Select2: The `select2("enable")` method has been deprecated and will' +
5384
+ ' be removed in later Select2 versions. Use $element.prop("disabled")' +
5385
+ ' instead.'
5386
+ );
5387
+ }
5388
+
5389
+ if (args == null || args.length === 0) {
5390
+ args = [true];
5391
+ }
5392
+
5393
+ var disabled = !args[0];
5394
+
5395
+ this.$element.prop('disabled', disabled);
5396
+ };
5397
+
5398
+ Select2.prototype.data = function () {
5399
+ if (this.options.get('debug') &&
5400
+ arguments.length > 0 && window.console && console.warn) {
5401
+ console.warn(
5402
+ 'Select2: Data can no longer be set using `select2("data")`. You ' +
5403
+ 'should consider setting the value instead using `$element.val()`.'
5404
+ );
5405
+ }
5406
+
5407
+ var data = [];
5408
+
5409
+ this.dataAdapter.current(function (currentData) {
5410
+ data = currentData;
5411
+ });
5412
+
5413
+ return data;
5414
+ };
5415
+
5416
+ Select2.prototype.val = function (args) {
5417
+ if (this.options.get('debug') && window.console && console.warn) {
5418
+ console.warn(
5419
+ 'Select2: The `select2("val")` method has been deprecated and will be' +
5420
+ ' removed in later Select2 versions. Use $element.val() instead.'
5421
+ );
5422
+ }
5423
+
5424
+ if (args == null || args.length === 0) {
5425
+ return this.$element.val();
5426
+ }
5427
+
5428
+ var newVal = args[0];
5429
+
5430
+ if ($.isArray(newVal)) {
5431
+ newVal = $.map(newVal, function (obj) {
5432
+ return obj.toString();
5433
+ });
5434
+ }
5435
+
5436
+ this.$element.val(newVal).trigger('change');
5437
+ };
5438
+
5439
+ Select2.prototype.destroy = function () {
5440
+ this.$container.remove();
5441
+
5442
+ if (this.$element[0].detachEvent) {
5443
+ this.$element[0].detachEvent('onpropertychange', this._sync);
5444
+ }
5445
+
5446
+ if (this._observer != null) {
5447
+ this._observer.disconnect();
5448
+ this._observer = null;
5449
+ } else if (this.$element[0].removeEventListener) {
5450
+ this.$element[0]
5451
+ .removeEventListener('DOMAttrModified', this._sync, false);
5452
+ }
5453
+
5454
+ this._sync = null;
5455
+
5456
+ this.$element.off('.select2');
5457
+ this.$element.attr('tabindex', this.$element.data('old-tabindex'));
5458
+
5459
+ this.$element.removeClass('select2-hidden-accessible');
5460
+ this.$element.attr('aria-hidden', 'false');
5461
+ this.$element.removeData('select2');
5462
+
5463
+ this.dataAdapter.destroy();
5464
+ this.selection.destroy();
5465
+ this.dropdown.destroy();
5466
+ this.results.destroy();
5467
+
5468
+ this.dataAdapter = null;
5469
+ this.selection = null;
5470
+ this.dropdown = null;
5471
+ this.results = null;
5472
+ };
5473
+
5474
+ Select2.prototype.render = function () {
5475
+ var $container = $(
5476
+ '<span class="select2 select2-container">' +
5477
+ '<span class="selection"></span>' +
5478
+ '<span class="dropdown-wrapper" aria-hidden="true"></span>' +
5479
+ '</span>'
5480
+ );
5481
+
5482
+ $container.attr('dir', this.options.get('dir'));
5483
+
5484
+ this.$container = $container;
5485
+
5486
+ this.$container.addClass('select2-container--' + this.options.get('theme'));
5487
+
5488
+ $container.data('element', this.$element);
5489
+
5490
+ return $container;
5491
+ };
5492
+
5493
+ return Select2;
5494
+ });
5495
+
5496
+ S2.define('jquery-mousewheel',[
5497
+ 'jquery'
5498
+ ], function ($) {
5499
+ // Used to shim jQuery.mousewheel for non-full builds.
5500
+ return $;
5501
+ });
5502
+
5503
+ S2.define('jquery.select2',[
5504
+ 'jquery',
5505
+ 'jquery-mousewheel',
5506
+
5507
+ './select2/core',
5508
+ './select2/defaults'
5509
+ ], function ($, _, Select2, Defaults) {
5510
+ if ($.fn.select2 == null) {
5511
+ // All methods that should return the element
5512
+ var thisMethods = ['open', 'close', 'destroy'];
5513
+
5514
+ $.fn.select2 = function (options) {
5515
+ options = options || {};
5516
+
5517
+ if (typeof options === 'object') {
5518
+ this.each(function () {
5519
+ var instanceOptions = $.extend(true, {}, options);
5520
+
5521
+ var instance = new Select2($(this), instanceOptions);
5522
+ });
5523
+
5524
+ return this;
5525
+ } else if (typeof options === 'string') {
5526
+ var ret;
5527
+
5528
+ this.each(function () {
5529
+ var instance = $(this).data('select2');
5530
+
5531
+ if (instance == null && window.console && console.error) {
5532
+ console.error(
5533
+ 'The select2(\'' + options + '\') method was called on an ' +
5534
+ 'element that is not using Select2.'
5535
+ );
5536
+ }
5537
+
5538
+ var args = Array.prototype.slice.call(arguments, 1);
5539
+
5540
+ ret = instance[options].apply(instance, args);
5541
+ });
5542
+
5543
+ // Check if we should be returning `this`
5544
+ if ($.inArray(options, thisMethods) > -1) {
5545
+ return this;
5546
+ }
5547
+
5548
+ return ret;
5549
+ } else {
5550
+ throw new Error('Invalid arguments for Select2: ' + options);
5551
+ }
5552
+ };
5553
+ }
5554
+
5555
+ if ($.fn.select2.defaults == null) {
5556
+ $.fn.select2.defaults = Defaults;
5557
+ }
5558
+
5559
+ return Select2;
5560
+ });
5561
+
5562
+ // Return the AMD loader configuration so it can be used outside of this file
5563
+ return {
5564
+ define: S2.define,
5565
+ require: S2.require
5566
+ };
5567
+ }());
5568
+
5569
+ // Autoload the jQuery bindings
5570
+ // We know that all of the modules exist above this, so we're safe
5571
+ var select2 = S2.require('jquery.select2');
5572
+
5573
+ // Hold the AMD module references on the jQuery function that was just loaded
5574
+ // This allows Select2 to use the internal loader outside of this file, such
5575
+ // as in the language files.
5576
+ jQuery.fn.select2.amd = S2;
5577
+
5578
+ // Return the Select2 instance for anyone who is importing it.
5579
+ return select2;
5580
+ }));
includes/simba-tfa/includes/select2.min.js ADDED
@@ -0,0 +1,2 @@
 
 
1
+ /*! Select2 4.0.2 | https://github.com/select2/select2/blob/master/LICENSE.md */!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a){var b=function(){if(a&&a.fn&&a.fn.select2&&a.fn.select2.amd)var b=a.fn.select2.amd;var b;return function(){if(!b||!b.requirejs){b?c=b:b={};var a,c,d;!function(b){function e(a,b){return u.call(a,b)}function f(a,b){var c,d,e,f,g,h,i,j,k,l,m,n=b&&b.split("/"),o=s.map,p=o&&o["*"]||{};if(a&&"."===a.charAt(0))if(b){for(a=a.split("/"),g=a.length-1,s.nodeIdCompat&&w.test(a[g])&&(a[g]=a[g].replace(w,"")),a=n.slice(0,n.length-1).concat(a),k=0;k<a.length;k+=1)if(m=a[k],"."===m)a.splice(k,1),k-=1;else if(".."===m){if(1===k&&(".."===a[2]||".."===a[0]))break;k>0&&(a.splice(k-1,2),k-=2)}a=a.join("/")}else 0===a.indexOf("./")&&(a=a.substring(2));if((n||p)&&o){for(c=a.split("/"),k=c.length;k>0;k-=1){if(d=c.slice(0,k).join("/"),n)for(l=n.length;l>0;l-=1)if(e=o[n.slice(0,l).join("/")],e&&(e=e[d])){f=e,h=k;break}if(f)break;!i&&p&&p[d]&&(i=p[d],j=k)}!f&&i&&(f=i,h=j),f&&(c.splice(0,h,f),a=c.join("/"))}return a}function g(a,c){return function(){var d=v.call(arguments,0);return"string"!=typeof d[0]&&1===d.length&&d.push(null),n.apply(b,d.concat([a,c]))}}function h(a){return function(b){return f(b,a)}}function i(a){return function(b){q[a]=b}}function j(a){if(e(r,a)){var c=r[a];delete r[a],t[a]=!0,m.apply(b,c)}if(!e(q,a)&&!e(t,a))throw new Error("No "+a);return q[a]}function k(a){var b,c=a?a.indexOf("!"):-1;return c>-1&&(b=a.substring(0,c),a=a.substring(c+1,a.length)),[b,a]}function l(a){return function(){return s&&s.config&&s.config[a]||{}}}var m,n,o,p,q={},r={},s={},t={},u=Object.prototype.hasOwnProperty,v=[].slice,w=/\.js$/;o=function(a,b){var c,d=k(a),e=d[0];return a=d[1],e&&(e=f(e,b),c=j(e)),e?a=c&&c.normalize?c.normalize(a,h(b)):f(a,b):(a=f(a,b),d=k(a),e=d[0],a=d[1],e&&(c=j(e))),{f:e?e+"!"+a:a,n:a,pr:e,p:c}},p={require:function(a){return g(a)},exports:function(a){var b=q[a];return"undefined"!=typeof b?b:q[a]={}},module:function(a){return{id:a,uri:"",exports:q[a],config:l(a)}}},m=function(a,c,d,f){var h,k,l,m,n,s,u=[],v=typeof d;if(f=f||a,"undefined"===v||"function"===v){for(c=!c.length&&d.length?["require","exports","module"]:c,n=0;n<c.length;n+=1)if(m=o(c[n],f),k=m.f,"require"===k)u[n]=p.require(a);else if("exports"===k)u[n]=p.exports(a),s=!0;else if("module"===k)h=u[n]=p.module(a);else if(e(q,k)||e(r,k)||e(t,k))u[n]=j(k);else{if(!m.p)throw new Error(a+" missing "+k);m.p.load(m.n,g(f,!0),i(k),{}),u[n]=q[k]}l=d?d.apply(q[a],u):void 0,a&&(h&&h.exports!==b&&h.exports!==q[a]?q[a]=h.exports:l===b&&s||(q[a]=l))}else a&&(q[a]=d)},a=c=n=function(a,c,d,e,f){if("string"==typeof a)return p[a]?p[a](c):j(o(a,c).f);if(!a.splice){if(s=a,s.deps&&n(s.deps,s.callback),!c)return;c.splice?(a=c,c=d,d=null):a=b}return c=c||function(){},"function"==typeof d&&(d=e,e=f),e?m(b,a,c,d):setTimeout(function(){m(b,a,c,d)},4),n},n.config=function(a){return n(a)},a._defined=q,d=function(a,b,c){if("string"!=typeof a)throw new Error("See almond README: incorrect module build, no module name");b.splice||(c=b,b=[]),e(q,a)||e(r,a)||(r[a]=[a,b,c])},d.amd={jQuery:!0}}(),b.requirejs=a,b.require=c,b.define=d}}(),b.define("almond",function(){}),b.define("jquery",[],function(){var b=a||$;return null==b&&console&&console.error&&console.error("Select2: An instance of jQuery or a jQuery-compatible library was not found. Make sure that you are including jQuery before Select2 on your web page."),b}),b.define("select2/utils",["jquery"],function(a){function b(a){var b=a.prototype,c=[];for(var d in b){var e=b[d];"function"==typeof e&&"constructor"!==d&&c.push(d)}return c}var c={};c.Extend=function(a,b){function c(){this.constructor=a}var d={}.hasOwnProperty;for(var e in b)d.call(b,e)&&(a[e]=b[e]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a},c.Decorate=function(a,c){function d(){var b=Array.prototype.unshift,d=c.prototype.constructor.length,e=a.prototype.constructor;d>0&&(b.call(arguments,a.prototype.constructor),e=c.prototype.constructor),e.apply(this,arguments)}function e(){this.constructor=d}var f=b(c),g=b(a);c.displayName=a.displayName,d.prototype=new e;for(var h=0;h<g.length;h++){var i=g[h];d.prototype[i]=a.prototype[i]}for(var j=(function(a){var b=function(){};a in d.prototype&&(b=d.prototype[a]);var e=c.prototype[a];return function(){var a=Array.prototype.unshift;return a.call(arguments,b),e.apply(this,arguments)}}),k=0;k<f.length;k++){var l=f[k];d.prototype[l]=j(l)}return d};var d=function(){this.listeners={}};return d.prototype.on=function(a,b){this.listeners=this.listeners||{},a in this.listeners?this.listeners[a].push(b):this.listeners[a]=[b]},d.prototype.trigger=function(a){var b=Array.prototype.slice;this.listeners=this.listeners||{},a in this.listeners&&this.invoke(this.listeners[a],b.call(arguments,1)),"*"in this.listeners&&this.invoke(this.listeners["*"],arguments)},d.prototype.invoke=function(a,b){for(var c=0,d=a.length;d>c;c++)a[c].apply(this,b)},c.Observable=d,c.generateChars=function(a){for(var b="",c=0;a>c;c++){var d=Math.floor(36*Math.random());b+=d.toString(36)}return b},c.bind=function(a,b){return function(){a.apply(b,arguments)}},c._convertData=function(a){for(var b in a){var c=b.split("-"),d=a;if(1!==c.length){for(var e=0;e<c.length;e++){var f=c[e];f=f.substring(0,1).toLowerCase()+f.substring(1),f in d||(d[f]={}),e==c.length-1&&(d[f]=a[b]),d=d[f]}delete a[b]}}return a},c.hasScroll=function(b,c){var d=a(c),e=c.style.overflowX,f=c.style.overflowY;return e!==f||"hidden"!==f&&"visible"!==f?"scroll"===e||"scroll"===f?!0:d.innerHeight()<c.scrollHeight||d.innerWidth()<c.scrollWidth:!1},c.escapeMarkup=function(a){var b={"\\":"&#92;","&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#47;"};return"string"!=typeof a?a:String(a).replace(/[&<>"'\/\\]/g,function(a){return b[a]})},c.appendMany=function(b,c){if("1.7"===a.fn.jquery.substr(0,3)){var d=a();a.map(c,function(a){d=d.add(a)}),c=d}b.append(c)},c}),b.define("select2/results",["jquery","./utils"],function(a,b){function c(a,b,d){this.$element=a,this.data=d,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('<ul class="select2-results__options" role="tree"></ul>');return this.options.get("multiple")&&b.attr("aria-multiselectable","true"),this.$results=b,b},c.prototype.clear=function(){this.$results.empty()},c.prototype.displayMessage=function(b){var c=this.options.get("escapeMarkup");this.clear(),this.hideLoading();var d=a('<li role="treeitem" aria-live="assertive" class="select2-results__option"></li>'),e=this.options.get("translations").get(b.message);d.append(c(e(b.args))),d[0].className+=" select2-results__message",this.$results.append(d)},c.prototype.hideMessages=function(){this.$results.find(".select2-results__message").remove()},c.prototype.append=function(a){this.hideLoading();var b=[];if(null==a.results||0===a.results.length)return void(0===this.$results.children().length&&this.trigger("results:message",{message:"noResults"}));a.results=this.sort(a.results);for(var c=0;c<a.results.length;c++){var d=a.results[c],e=this.option(d);b.push(e)}this.$results.append(b)},c.prototype.position=function(a,b){var c=b.find(".select2-results");c.append(a)},c.prototype.sort=function(a){var b=this.options.get("sorter");return b(a)},c.prototype.setClasses=function(){var b=this;this.data.current(function(c){var d=a.map(c,function(a){return a.id.toString()}),e=b.$results.find(".select2-results__option[aria-selected]");e.each(function(){var b=a(this),c=a.data(this,"data"),e=""+c.id;null!=c.element&&c.element.selected||null==c.element&&a.inArray(e,d)>-1?b.attr("aria-selected","true"):b.attr("aria-selected","false")});var f=e.filter("[aria-selected=true]");f.length>0?f.first().trigger("mouseenter"):e.first().trigger("mouseenter")})},c.prototype.showLoading=function(a){this.hideLoading();var b=this.options.get("translations").get("searching"),c={disabled:!0,loading:!0,text:b(a)},d=this.option(c);d.className+=" loading-results",this.$results.prepend(d)},c.prototype.hideLoading=function(){this.$results.find(".loading-results").remove()},c.prototype.option=function(b){var c=document.createElement("li");c.className="select2-results__option";var d={role:"treeitem","aria-selected":"false"};b.disabled&&(delete d["aria-selected"],d["aria-disabled"]="true"),null==b.id&&delete d["aria-selected"],null!=b._resultId&&(c.id=b._resultId),b.title&&(c.title=b.title),b.children&&(d.role="group",d["aria-label"]=b.text,delete d["aria-selected"]);for(var e in d){var f=d[e];c.setAttribute(e,f)}if(b.children){var g=a(c),h=document.createElement("strong");h.className="select2-results__group";a(h);this.template(b,h);for(var i=[],j=0;j<b.children.length;j++){var k=b.children[j],l=this.option(k);i.push(l)}var m=a("<ul></ul>",{"class":"select2-results__options select2-results__options--nested"});m.append(i),g.append(h),g.append(m)}else this.template(b,c);return a.data(c,"data",b),c},c.prototype.bind=function(b,c){var d=this,e=b.id+"-results";this.$results.attr("id",e),b.on("results:all",function(a){d.clear(),d.append(a.data),b.isOpen()&&d.setClasses()}),b.on("results:append",function(a){d.append(a.data),b.isOpen()&&d.setClasses()}),b.on("query",function(a){d.hideMessages(),d.showLoading(a)}),b.on("select",function(){b.isOpen()&&d.setClasses()}),b.on("unselect",function(){b.isOpen()&&d.setClasses()}),b.on("open",function(){d.$results.attr("aria-expanded","true"),d.$results.attr("aria-hidden","false"),d.setClasses(),d.ensureHighlightVisible()}),b.on("close",function(){d.$results.attr("aria-expanded","false"),d.$results.attr("aria-hidden","true"),d.$results.removeAttr("aria-activedescendant")}),b.on("results:toggle",function(){var a=d.getHighlightedResults();0!==a.length&&a.trigger("mouseup")}),b.on("results:select",function(){var a=d.getHighlightedResults();if(0!==a.length){var b=a.data("data");"true"==a.attr("aria-selected")?d.trigger("close",{}):d.trigger("select",{data:b})}}),b.on("results:previous",function(){var a=d.getHighlightedResults(),b=d.$results.find("[aria-selected]"),c=b.index(a);if(0!==c){var e=c-1;0===a.length&&(e=0);var f=b.eq(e);f.trigger("mouseenter");var g=d.$results.offset().top,h=f.offset().top,i=d.$results.scrollTop()+(h-g);0===e?d.$results.scrollTop(0):0>h-g&&d.$results.scrollTop(i)}}),b.on("results:next",function(){var a=d.getHighlightedResults(),b=d.$results.find("[aria-selected]"),c=b.index(a),e=c+1;if(!(e>=b.length)){var f=b.eq(e);f.trigger("mouseenter");var g=d.$results.offset().top+d.$results.outerHeight(!1),h=f.offset().top+f.outerHeight(!1),i=d.$results.scrollTop()+h-g;0===e?d.$results.scrollTop(0):h>g&&d.$results.scrollTop(i)}}),b.on("results:focus",function(a){a.element.addClass("select2-results__option--highlighted")}),b.on("results:message",function(a){d.displayMessage(a)}),a.fn.mousewheel&&this.$results.on("mousewheel",function(a){var b=d.$results.scrollTop(),c=d.$results.get(0).scrollHeight-b+a.deltaY,e=a.deltaY>0&&b-a.deltaY<=0,f=a.deltaY<0&&c<=d.$results.height();e?(d.$results.scrollTop(0),a.preventDefault(),a.stopPropagation()):f&&(d.$results.scrollTop(d.$results.get(0).scrollHeight-d.$results.height()),a.preventDefault(),a.stopPropagation())}),this.$results.on("mouseup",".select2-results__option[aria-selected]",function(b){var c=a(this),e=c.data("data");return"true"===c.attr("aria-selected")?void(d.options.get("multiple")?d.trigger("unselect",{originalEvent:b,data:e}):d.trigger("close",{})):void d.trigger("select",{originalEvent:b,data:e})}),this.$results.on("mouseenter",".select2-results__option[aria-selected]",function(b){var c=a(this).data("data");d.getHighlightedResults().removeClass("select2-results__option--highlighted"),d.trigger("results:focus",{data:c,element:a(this)})})},c.prototype.getHighlightedResults=function(){var a=this.$results.find(".select2-results__option--highlighted");return a},c.prototype.destroy=function(){this.$results.remove()},c.prototype.ensureHighlightVisible=function(){var a=this.getHighlightedResults();if(0!==a.length){var b=this.$results.find("[aria-selected]"),c=b.index(a),d=this.$results.offset().top,e=a.offset().top,f=this.$results.scrollTop()+(e-d),g=e-d;f-=2*a.outerHeight(!1),2>=c?this.$results.scrollTop(0):(g>this.$results.outerHeight()||0>g)&&this.$results.scrollTop(f)}},c.prototype.template=function(b,c){var d=this.options.get("templateResult"),e=this.options.get("escapeMarkup"),f=d(b,c);null==f?c.style.display="none":"string"==typeof f?c.innerHTML=e(f):a(c).append(f)},c}),b.define("select2/keys",[],function(){var a={BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46};return a}),b.define("select2/selection/base",["jquery","../utils","../keys"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,b.Observable),d.prototype.render=function(){var b=a('<span class="select2-selection" role="combobox" aria-haspopup="true" aria-expanded="false"></span>');return this._tabindex=0,null!=this.$element.data("old-tabindex")?this._tabindex=this.$element.data("old-tabindex"):null!=this.$element.attr("tabindex")&&(this._tabindex=this.$element.attr("tabindex")),b.attr("title",this.$element.attr("title")),b.attr("tabindex",this._tabindex),this.$selection=b,b},d.prototype.bind=function(a,b){var d=this,e=(a.id+"-container",a.id+"-results");this.container=a,this.$selection.on("focus",function(a){d.trigger("focus",a)}),this.$selection.on("blur",function(a){d._handleBlur(a)}),this.$selection.on("keydown",function(a){d.trigger("keypress",a),a.which===c.SPACE&&a.preventDefault()}),a.on("results:focus",function(a){d.$selection.attr("aria-activedescendant",a.data._resultId)}),a.on("selection:update",function(a){d.update(a.data)}),a.on("open",function(){d.$selection.attr("aria-expanded","true"),d.$selection.attr("aria-owns",e),d._attachCloseHandler(a)}),a.on("close",function(){d.$selection.attr("aria-expanded","false"),d.$selection.removeAttr("aria-activedescendant"),d.$selection.removeAttr("aria-owns"),d.$selection.focus(),d._detachCloseHandler(a)}),a.on("enable",function(){d.$selection.attr("tabindex",d._tabindex)}),a.on("disable",function(){d.$selection.attr("tabindex","-1")})},d.prototype._handleBlur=function(b){var c=this;window.setTimeout(function(){document.activeElement==c.$selection[0]||a.contains(c.$selection[0],document.activeElement)||c.trigger("blur",b)},1)},d.prototype._attachCloseHandler=function(b){a(document.body).on("mousedown.select2."+b.id,function(b){var c=a(b.target),d=c.closest(".select2"),e=a(".select2.select2-container--open");e.each(function(){var b=a(this);if(this!=d[0]){var c=b.data("element");c.select2("close")}})})},d.prototype._detachCloseHandler=function(b){a(document.body).off("mousedown.select2."+b.id)},d.prototype.position=function(a,b){var c=b.find(".selection");c.append(a)},d.prototype.destroy=function(){this._detachCloseHandler(this.container)},d.prototype.update=function(a){throw new Error("The `update` method must be defined in child classes.")},d}),b.define("select2/selection/single",["jquery","./base","../utils","../keys"],function(a,b,c,d){function e(){e.__super__.constructor.apply(this,arguments)}return c.Extend(e,b),e.prototype.render=function(){var a=e.__super__.render.call(this);return a.addClass("select2-selection--single"),a.html('<span class="select2-selection__rendered"></span><span class="select2-selection__arrow" role="presentation"><b role="presentation"></b></span>'),a},e.prototype.bind=function(a,b){var c=this;e.__super__.bind.apply(this,arguments);var d=a.id+"-container";this.$selection.find(".select2-selection__rendered").attr("id",d),this.$selection.attr("aria-labelledby",d),this.$selection.on("mousedown",function(a){1===a.which&&c.trigger("toggle",{originalEvent:a})}),this.$selection.on("focus",function(a){}),this.$selection.on("blur",function(a){}),a.on("selection:update",function(a){c.update(a.data)})},e.prototype.clear=function(){this.$selection.find(".select2-selection__rendered").empty()},e.prototype.display=function(a,b){var c=this.options.get("templateSelection"),d=this.options.get("escapeMarkup");return d(c(a,b))},e.prototype.selectionContainer=function(){return a("<span></span>")},e.prototype.update=function(a){if(0===a.length)return void this.clear();var b=a[0],c=this.$selection.find(".select2-selection__rendered"),d=this.display(b,c);c.empty().append(d),c.prop("title",b.title||b.text)},e}),b.define("select2/selection/multiple",["jquery","./base","../utils"],function(a,b,c){function d(a,b){d.__super__.constructor.apply(this,arguments)}return c.Extend(d,b),d.prototype.render=function(){var a=d.__super__.render.call(this);return a.addClass("select2-selection--multiple"),a.html('<ul class="select2-selection__rendered"></ul>'),a},d.prototype.bind=function(b,c){var e=this;d.__super__.bind.apply(this,arguments),this.$selection.on("click",function(a){e.trigger("toggle",{originalEvent:a})}),this.$selection.on("click",".select2-selection__choice__remove",function(b){if(!e.options.get("disabled")){var c=a(this),d=c.parent(),f=d.data("data");e.trigger("unselect",{originalEvent:b,data:f})}})},d.prototype.clear=function(){this.$selection.find(".select2-selection__rendered").empty()},d.prototype.display=function(a,b){var c=this.options.get("templateSelection"),d=this.options.get("escapeMarkup");return d(c(a,b))},d.prototype.selectionContainer=function(){var b=a('<li class="select2-selection__choice"><span class="select2-selection__choice__remove" role="presentation">&times;</span></li>');return b},d.prototype.update=function(a){if(this.clear(),0!==a.length){for(var b=[],d=0;d<a.length;d++){var e=a[d],f=this.selectionContainer(),g=this.display(e,f);f.append(g),f.prop("title",e.title||e.text),f.data("data",e),b.push(f)}var h=this.$selection.find(".select2-selection__rendered");c.appendMany(h,b)}},d}),b.define("select2/selection/placeholder",["../utils"],function(a){function b(a,b,c){this.placeholder=this.normalizePlaceholder(c.get("placeholder")),a.call(this,b,c)}return b.prototype.normalizePlaceholder=function(a,b){return"string"==typeof b&&(b={id:"",text:b}),b},b.prototype.createPlaceholder=function(a,b){var c=this.selectionContainer();return c.html(this.display(b)),c.addClass("select2-selection__placeholder").removeClass("select2-selection__choice"),c},b.prototype.update=function(a,b){var c=1==b.length&&b[0].id!=this.placeholder.id,d=b.length>1;if(d||c)return a.call(this,b);this.clear();var e=this.createPlaceholder(this.placeholder);this.$selection.find(".select2-selection__rendered").append(e)},b}),b.define("select2/selection/allowClear",["jquery","../keys"],function(a,b){function c(){}return c.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),null==this.placeholder&&this.options.get("debug")&&window.console&&console.error&&console.error("Select2: The `allowClear` option should be used in combination with the `placeholder` option."),this.$selection.on("mousedown",".select2-selection__clear",function(a){d._handleClear(a)}),b.on("keypress",function(a){d._handleKeyboardClear(a,b)})},c.prototype._handleClear=function(a,b){if(!this.options.get("disabled")){var c=this.$selection.find(".select2-selection__clear");if(0!==c.length){b.stopPropagation();for(var d=c.data("data"),e=0;e<d.length;e++){var f={data:d[e]};if(this.trigger("unselect",f),f.prevented)return}this.$element.val(this.placeholder.id).trigger("change"),this.trigger("toggle",{})}}},c.prototype._handleKeyboardClear=function(a,c,d){d.isOpen()||(c.which==b.DELETE||c.which==b.BACKSPACE)&&this._handleClear(c)},c.prototype.update=function(b,c){if(b.call(this,c),!(this.$selection.find(".select2-selection__placeholder").length>0||0===c.length)){var d=a('<span class="select2-selection__clear">&times;</span>');d.data("data",c),this.$selection.find(".select2-selection__rendered").prepend(d)}},c}),b.define("select2/selection/search",["jquery","../utils","../keys"],function(a,b,c){function d(a,b,c){a.call(this,b,c)}return d.prototype.render=function(b){var c=a('<li class="select2-search select2-search--inline"><input class="select2-search__field" type="search" tabindex="-1" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" role="textbox" aria-autocomplete="list" /></li>');this.$searchContainer=c,this.$search=c.find("input");var d=b.call(this);return this._transferTabIndex(),d},d.prototype.bind=function(a,b,d){var e=this;a.call(this,b,d),b.on("open",function(){e.$search.trigger("focus")}),b.on("close",function(){e.$search.val(""),e.$search.removeAttr("aria-activedescendant"),e.$search.trigger("focus")}),b.on("enable",function(){e.$search.prop("disabled",!1),e._transferTabIndex()}),b.on("disable",function(){e.$search.prop("disabled",!0)}),b.on("focus",function(a){e.$search.trigger("focus")}),b.on("results:focus",function(a){e.$search.attr("aria-activedescendant",a.id)}),this.$selection.on("focusin",".select2-search--inline",function(a){e.trigger("focus",a)}),this.$selection.on("focusout",".select2-search--inline",function(a){e._handleBlur(a)}),this.$selection.on("keydown",".select2-search--inline",function(a){a.stopPropagation(),e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented();var b=a.which;if(b===c.BACKSPACE&&""===e.$search.val()){var d=e.$searchContainer.prev(".select2-selection__choice");if(d.length>0){var f=d.data("data");e.searchRemoveChoice(f),a.preventDefault()}}});var f=document.documentMode,g=f&&11>=f;this.$selection.on("input.searchcheck",".select2-search--inline",function(a){return g?void e.$selection.off("input.search input.searchcheck"):void e.$selection.off("keyup.search")}),this.$selection.on("keyup.search input.search",".select2-search--inline",function(a){if(g&&"input"===a.type)return void e.$selection.off("input.search input.searchcheck");var b=a.which;b!=c.SHIFT&&b!=c.CTRL&&b!=c.ALT&&b!=c.TAB&&e.handleSearch(a)})},d.prototype._transferTabIndex=function(a){this.$search.attr("tabindex",this.$selection.attr("tabindex")),this.$selection.attr("tabindex","-1")},d.prototype.createPlaceholder=function(a,b){this.$search.attr("placeholder",b.text)},d.prototype.update=function(a,b){var c=this.$search[0]==document.activeElement;this.$search.attr("placeholder",""),a.call(this,b),this.$selection.find(".select2-selection__rendered").append(this.$searchContainer),this.resizeSearch(),c&&this.$search.focus()},d.prototype.handleSearch=function(){if(this.resizeSearch(),!this._keyUpPrevented){var a=this.$search.val();this.trigger("query",{term:a})}this._keyUpPrevented=!1},d.prototype.searchRemoveChoice=function(a,b){this.trigger("unselect",{data:b}),this.$search.val(b.text),this.handleSearch()},d.prototype.resizeSearch=function(){this.$search.css("width","25px");var a="";if(""!==this.$search.attr("placeholder"))a=this.$selection.find(".select2-selection__rendered").innerWidth();else{var b=this.$search.val().length+1;a=.75*b+"em"}this.$search.css("width",a)},d}),b.define("select2/selection/eventRelay",["jquery"],function(a){function b(){}return b.prototype.bind=function(b,c,d){var e=this,f=["open","opening","close","closing","select","selecting","unselect","unselecting"],g=["opening","closing","selecting","unselecting"];b.call(this,c,d),c.on("*",function(b,c){if(-1!==a.inArray(b,f)){c=c||{};var d=a.Event("select2:"+b,{params:c});e.$element.trigger(d),-1!==a.inArray(b,g)&&(c.prevented=d.isDefaultPrevented())}})},b}),b.define("select2/translation",["jquery","require"],function(a,b){function c(a){this.dict=a||{}}return c.prototype.all=function(){return this.dict},c.prototype.get=function(a){return this.dict[a]},c.prototype.extend=function(b){this.dict=a.extend({},b.all(),this.dict)},c._cache={},c.loadPath=function(a){if(!(a in c._cache)){var d=b(a);c._cache[a]=d}return new c(c._cache[a])},c}),b.define("select2/diacritics",[],function(){var a={"Ⓐ":"A","A":"A","À":"A","Á":"A","Â":"A","Ầ":"A","Ấ":"A","Ẫ":"A","Ẩ":"A","Ã":"A","Ā":"A","Ă":"A","Ằ":"A","Ắ":"A","Ẵ":"A","Ẳ":"A","Ȧ":"A","Ǡ":"A","Ä":"A","Ǟ":"A","Ả":"A","Å":"A","Ǻ":"A","Ǎ":"A","Ȁ":"A","Ȃ":"A","Ạ":"A","Ậ":"A","Ặ":"A","Ḁ":"A","Ą":"A","Ⱥ":"A","Ɐ":"A","Ꜳ":"AA","Æ":"AE","Ǽ":"AE","Ǣ":"AE","Ꜵ":"AO","Ꜷ":"AU","Ꜹ":"AV","Ꜻ":"AV","Ꜽ":"AY","Ⓑ":"B","B":"B","Ḃ":"B","Ḅ":"B","Ḇ":"B","Ƀ":"B","Ƃ":"B","Ɓ":"B","Ⓒ":"C","C":"C","Ć":"C","Ĉ":"C","Ċ":"C","Č":"C","Ç":"C","Ḉ":"C","Ƈ":"C","Ȼ":"C","Ꜿ":"C","Ⓓ":"D","D":"D","Ḋ":"D","Ď":"D","Ḍ":"D","Ḑ":"D","Ḓ":"D","Ḏ":"D","Đ":"D","Ƌ":"D","Ɗ":"D","Ɖ":"D","Ꝺ":"D","DZ":"DZ","DŽ":"DZ","Dz":"Dz","Dž":"Dz","Ⓔ":"E","E":"E","È":"E","É":"E","Ê":"E","Ề":"E","Ế":"E","Ễ":"E","Ể":"E","Ẽ":"E","Ē":"E","Ḕ":"E","Ḗ":"E","Ĕ":"E","Ė":"E","Ë":"E","Ẻ":"E","Ě":"E","Ȅ":"E","Ȇ":"E","Ẹ":"E","Ệ":"E","Ȩ":"E","Ḝ":"E","Ę":"E","Ḙ":"E","Ḛ":"E","Ɛ":"E","Ǝ":"E","Ⓕ":"F","F":"F","Ḟ":"F","Ƒ":"F","Ꝼ":"F","Ⓖ":"G","G":"G","Ǵ":"G","Ĝ":"G","Ḡ":"G","Ğ":"G","Ġ":"G","Ǧ":"G","Ģ":"G","Ǥ":"G","Ɠ":"G","Ꞡ":"G","Ᵹ":"G","Ꝿ":"G","Ⓗ":"H","H":"H","Ĥ":"H","Ḣ":"H","Ḧ":"H","Ȟ":"H","Ḥ":"H","Ḩ":"H","Ḫ":"H","Ħ":"H","Ⱨ":"H","Ⱶ":"H","Ɥ":"H","Ⓘ":"I","I":"I","Ì":"I","Í":"I","Î":"I","Ĩ":"I","Ī":"I","Ĭ":"I","İ":"I","Ï":"I","Ḯ":"I","Ỉ":"I","Ǐ":"I","Ȉ":"I","Ȋ":"I","Ị":"I","Į":"I","Ḭ":"I","Ɨ":"I","Ⓙ":"J","J":"J","Ĵ":"J","Ɉ":"J","Ⓚ":"K","K":"K","Ḱ":"K","Ǩ":"K","Ḳ":"K","Ķ":"K","Ḵ":"K","Ƙ":"K","Ⱪ":"K","Ꝁ":"K","Ꝃ":"K","Ꝅ":"K","Ꞣ":"K","Ⓛ":"L","L":"L","Ŀ":"L","Ĺ":"L","Ľ":"L","Ḷ":"L","Ḹ":"L","Ļ":"L","Ḽ":"L","Ḻ":"L","Ł":"L","Ƚ":"L","Ɫ":"L","Ⱡ":"L","Ꝉ":"L","Ꝇ":"L","Ꞁ":"L","LJ":"LJ","Lj":"Lj","Ⓜ":"M","M":"M","Ḿ":"M","Ṁ":"M","Ṃ":"M","Ɱ":"M","Ɯ":"M","Ⓝ":"N","N":"N","Ǹ":"N","Ń":"N","Ñ":"N","Ṅ":"N","Ň":"N","Ṇ":"N","Ņ":"N","Ṋ":"N","Ṉ":"N","Ƞ":"N","Ɲ":"N","Ꞑ":"N","Ꞥ":"N","NJ":"NJ","Nj":"Nj","Ⓞ":"O","O":"O","Ò":"O","Ó":"O","Ô":"O","Ồ":"O","Ố":"O","Ỗ":"O","Ổ":"O","Õ":"O","Ṍ":"O","Ȭ":"O","Ṏ":"O","Ō":"O","Ṑ":"O","Ṓ":"O","Ŏ":"O","Ȯ":"O","Ȱ":"O","Ö":"O","Ȫ":"O","Ỏ":"O","Ő":"O","Ǒ":"O","Ȍ":"O","Ȏ":"O","Ơ":"O","Ờ":"O","Ớ":"O","Ỡ":"O","Ở":"O","Ợ":"O","Ọ":"O","Ộ":"O","Ǫ":"O","Ǭ":"O","Ø":"O","Ǿ":"O","Ɔ":"O","Ɵ":"O","Ꝋ":"O","Ꝍ":"O","Ƣ":"OI","Ꝏ":"OO","Ȣ":"OU","Ⓟ":"P","P":"P","Ṕ":"P","Ṗ":"P","Ƥ":"P","Ᵽ":"P","Ꝑ":"P","Ꝓ":"P","Ꝕ":"P","Ⓠ":"Q","Q":"Q","Ꝗ":"Q","Ꝙ":"Q","Ɋ":"Q","Ⓡ":"R","R":"R","Ŕ":"R","Ṙ":"R","Ř":"R","Ȑ":"R","Ȓ":"R","Ṛ":"R","Ṝ":"R","Ŗ":"R","Ṟ":"R","Ɍ":"R","Ɽ":"R","Ꝛ":"R","Ꞧ":"R","Ꞃ":"R","Ⓢ":"S","S":"S","ẞ":"S","Ś":"S","Ṥ":"S","Ŝ":"S","Ṡ":"S","Š":"S","Ṧ":"S","Ṣ":"S","Ṩ":"S","Ș":"S","Ş":"S","Ȿ":"S","Ꞩ":"S","Ꞅ":"S","Ⓣ":"T","T":"T","Ṫ":"T","Ť":"T","Ṭ":"T","Ț":"T","Ţ":"T","Ṱ":"T","Ṯ":"T","Ŧ":"T","Ƭ":"T","Ʈ":"T","Ⱦ":"T","Ꞇ":"T","Ꜩ":"TZ","Ⓤ":"U","U":"U","Ù":"U","Ú":"U","Û":"U","Ũ":"U","Ṹ":"U","Ū":"U","Ṻ":"U","Ŭ":"U","Ü":"U","Ǜ":"U","Ǘ":"U","Ǖ":"U","Ǚ":"U","Ủ":"U","Ů":"U","Ű":"U","Ǔ":"U","Ȕ":"U","Ȗ":"U","Ư":"U","Ừ":"U","Ứ":"U","Ữ":"U","Ử":"U","Ự":"U","Ụ":"U","Ṳ":"U","Ų":"U","Ṷ":"U","Ṵ":"U","Ʉ":"U","Ⓥ":"V","V":"V","Ṽ":"V","Ṿ":"V","Ʋ":"V","Ꝟ":"V","Ʌ":"V","Ꝡ":"VY","Ⓦ":"W","W":"W","Ẁ":"W","Ẃ":"W","Ŵ":"W","Ẇ":"W","Ẅ":"W","Ẉ":"W","Ⱳ":"W","Ⓧ":"X","X":"X","Ẋ":"X","Ẍ":"X","Ⓨ":"Y","Y":"Y","Ỳ":"Y","Ý":"Y","Ŷ":"Y","Ỹ":"Y","Ȳ":"Y","Ẏ":"Y","Ÿ":"Y","Ỷ":"Y","Ỵ":"Y","Ƴ":"Y","Ɏ":"Y","Ỿ":"Y","Ⓩ":"Z","Z":"Z","Ź":"Z","Ẑ":"Z","Ż":"Z","Ž":"Z","Ẓ":"Z","Ẕ":"Z","Ƶ":"Z","Ȥ":"Z","Ɀ":"Z","Ⱬ":"Z","Ꝣ":"Z","ⓐ":"a","a":"a","ẚ":"a","à":"a","á":"a","â":"a","ầ":"a","ấ":"a","ẫ":"a","ẩ":"a","ã":"a","ā":"a","ă":"a","ằ":"a","ắ":"a","ẵ":"a","ẳ":"a","ȧ":"a","ǡ":"a","ä":"a","ǟ":"a","ả":"a","å":"a","ǻ":"a","ǎ":"a","ȁ":"a","ȃ":"a","ạ":"a","ậ":"a","ặ":"a","ḁ":"a","ą":"a","ⱥ":"a","ɐ":"a","ꜳ":"aa","æ":"ae","ǽ":"ae","ǣ":"ae","ꜵ":"ao","ꜷ":"au","ꜹ":"av","ꜻ":"av","ꜽ":"ay","ⓑ":"b","b":"b","ḃ":"b","ḅ":"b","ḇ":"b","ƀ":"b","ƃ":"b","ɓ":"b","ⓒ":"c","c":"c","ć":"c","ĉ":"c","ċ":"c","č":"c","ç":"c","ḉ":"c","ƈ":"c","ȼ":"c","ꜿ":"c","ↄ":"c","ⓓ":"d","d":"d","ḋ":"d","ď":"d","ḍ":"d","ḑ":"d","ḓ":"d","ḏ":"d","đ":"d","ƌ":"d","ɖ":"d","ɗ":"d","ꝺ":"d","dz":"dz","dž":"dz","ⓔ":"e","e":"e","è":"e","é":"e","ê":"e","ề":"e","ế":"e","ễ":"e","ể":"e","ẽ":"e","ē":"e","ḕ":"e","ḗ":"e","ĕ":"e","ė":"e","ë":"e","ẻ":"e","ě":"e","ȅ":"e","ȇ":"e","ẹ":"e","ệ":"e","ȩ":"e","ḝ":"e","ę":"e","ḙ":"e","ḛ":"e","ɇ":"e","ɛ":"e","ǝ":"e","ⓕ":"f","f":"f","ḟ":"f","ƒ":"f","ꝼ":"f","ⓖ":"g","g":"g","ǵ":"g","ĝ":"g","ḡ":"g","ğ":"g","ġ":"g","ǧ":"g","ģ":"g","ǥ":"g","ɠ":"g","ꞡ":"g","ᵹ":"g","ꝿ":"g","ⓗ":"h","h":"h","ĥ":"h","ḣ":"h","ḧ":"h","ȟ":"h","ḥ":"h","ḩ":"h","ḫ":"h","ẖ":"h","ħ":"h","ⱨ":"h","ⱶ":"h","ɥ":"h","ƕ":"hv","ⓘ":"i","i":"i","ì":"i","í":"i","î":"i","ĩ":"i","ī":"i","ĭ":"i","ï":"i","ḯ":"i","ỉ":"i","ǐ":"i","ȉ":"i","ȋ":"i","ị":"i","į":"i","ḭ":"i","ɨ":"i","ı":"i","ⓙ":"j","j":"j","ĵ":"j","ǰ":"j","ɉ":"j","ⓚ":"k","k":"k","ḱ":"k","ǩ":"k","ḳ":"k","ķ":"k","ḵ":"k","ƙ":"k","ⱪ":"k","ꝁ":"k","ꝃ":"k","ꝅ":"k","ꞣ":"k","ⓛ":"l","l":"l","ŀ":"l","ĺ":"l","ľ":"l","ḷ":"l","ḹ":"l","ļ":"l","ḽ":"l","ḻ":"l","ſ":"l","ł":"l","ƚ":"l","ɫ":"l","ⱡ":"l","ꝉ":"l","ꞁ":"l","ꝇ":"l","lj":"lj","ⓜ":"m","m":"m","ḿ":"m","ṁ":"m","ṃ":"m","ɱ":"m","ɯ":"m","ⓝ":"n","n":"n","ǹ":"n","ń":"n","ñ":"n","ṅ":"n","ň":"n","ṇ":"n","ņ":"n","ṋ":"n","ṉ":"n","ƞ":"n","ɲ":"n","ʼn":"n","ꞑ":"n","ꞥ":"n","nj":"nj","ⓞ":"o","o":"o","ò":"o","ó":"o","ô":"o","ồ":"o","ố":"o","ỗ":"o","ổ":"o","õ":"o","ṍ":"o","ȭ":"o","ṏ":"o","ō":"o","ṑ":"o","ṓ":"o","ŏ":"o","ȯ":"o","ȱ":"o","ö":"o","ȫ":"o","ỏ":"o","ő":"o","ǒ":"o","ȍ":"o","ȏ":"o","ơ":"o","ờ":"o","ớ":"o","ỡ":"o","ở":"o","ợ":"o","ọ":"o","ộ":"o","ǫ":"o","ǭ":"o","ø":"o","ǿ":"o","ɔ":"o","ꝋ":"o","ꝍ":"o","ɵ":"o","ƣ":"oi","ȣ":"ou","ꝏ":"oo","ⓟ":"p","p":"p","ṕ":"p","ṗ":"p","ƥ":"p","ᵽ":"p","ꝑ":"p","ꝓ":"p","ꝕ":"p","ⓠ":"q","q":"q","ɋ":"q","ꝗ":"q","ꝙ":"q","ⓡ":"r","r":"r","ŕ":"r","ṙ":"r","ř":"r","ȑ":"r","ȓ":"r","ṛ":"r","ṝ":"r","ŗ":"r","ṟ":"r","ɍ":"r","ɽ":"r","ꝛ":"r","ꞧ":"r","ꞃ":"r","ⓢ":"s","s":"s","ß":"s","ś":"s","ṥ":"s","ŝ":"s","ṡ":"s","š":"s","ṧ":"s","ṣ":"s","ṩ":"s","ș":"s","ş":"s","ȿ":"s","ꞩ":"s","ꞅ":"s","ẛ":"s","ⓣ":"t","t":"t","ṫ":"t","ẗ":"t","ť":"t","ṭ":"t","ț":"t","ţ":"t","ṱ":"t","ṯ":"t","ŧ":"t","ƭ":"t","ʈ":"t","ⱦ":"t","ꞇ":"t","ꜩ":"tz","ⓤ":"u","u":"u","ù":"u","ú":"u","û":"u","ũ":"u","ṹ":"u","ū":"u","ṻ":"u","ŭ":"u","ü":"u","ǜ":"u","ǘ":"u","ǖ":"u","ǚ":"u","ủ":"u","ů":"u","ű":"u","ǔ":"u","ȕ":"u","ȗ":"u","ư":"u","ừ":"u","ứ":"u","ữ":"u","ử":"u","ự":"u","ụ":"u","ṳ":"u","ų":"u","ṷ":"u","ṵ":"u","ʉ":"u","ⓥ":"v","v":"v","ṽ":"v","ṿ":"v","ʋ":"v","ꝟ":"v","ʌ":"v","ꝡ":"vy","ⓦ":"w","w":"w","ẁ":"w","ẃ":"w","ŵ":"w","ẇ":"w","ẅ":"w","ẘ":"w","ẉ":"w","ⱳ":"w","ⓧ":"x","x":"x","ẋ":"x","ẍ":"x","ⓨ":"y","y":"y","ỳ":"y","ý":"y","ŷ":"y","ỹ":"y","ȳ":"y","ẏ":"y","ÿ":"y","ỷ":"y","ẙ":"y","ỵ":"y","ƴ":"y","ɏ":"y","ỿ":"y","ⓩ":"z","z":"z","ź":"z","ẑ":"z","ż":"z","ž":"z","ẓ":"z","ẕ":"z","ƶ":"z","ȥ":"z","ɀ":"z","ⱬ":"z","ꝣ":"z","Ά":"Α","Έ":"Ε","Ή":"Η","Ί":"Ι","Ϊ":"Ι","Ό":"Ο","Ύ":"Υ","Ϋ":"Υ","Ώ":"Ω","ά":"α","έ":"ε","ή":"η","ί":"ι","ϊ":"ι","ΐ":"ι","ό":"ο","ύ":"υ","ϋ":"υ","ΰ":"υ","ω":"ω","ς":"σ"};return a}),b.define("select2/data/base",["../utils"],function(a){function b(a,c){b.__super__.constructor.call(this)}return a.Extend(b,a.Observable),b.prototype.current=function(a){throw new Error("The `current` method must be defined in child classes.")},b.prototype.query=function(a,b){throw new Error("The `query` method must be defined in child classes.")},b.prototype.bind=function(a,b){},b.prototype.destroy=function(){},b.prototype.generateResultId=function(b,c){var d=b.id+"-result-";return d+=a.generateChars(4),d+=null!=c.id?"-"+c.id.toString():"-"+a.generateChars(4)},b}),b.define("select2/data/select",["./base","../utils","jquery"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,a),d.prototype.current=function(a){var b=[],d=this;this.$element.find(":selected").each(function(){var a=c(this),e=d.item(a);b.push(e)}),a(b)},d.prototype.select=function(a){var b=this;if(a.selected=!0,c(a.element).is("option"))return a.element.selected=!0,void this.$element.trigger("change");if(this.$element.prop("multiple"))this.current(function(d){var e=[];a=[a],a.push.apply(a,d);for(var f=0;f<a.length;f++){var g=a[f].id;-1===c.inArray(g,e)&&e.push(g)}b.$element.val(e),b.$element.trigger("change")});else{var d=a.id;this.$element.val(d),this.$element.trigger("change")}},d.prototype.unselect=function(a){var b=this;if(this.$element.prop("multiple"))return a.selected=!1,
2
+ c(a.element).is("option")?(a.element.selected=!1,void this.$element.trigger("change")):void this.current(function(d){for(var e=[],f=0;f<d.length;f++){var g=d[f].id;g!==a.id&&-1===c.inArray(g,e)&&e.push(g)}b.$element.val(e),b.$element.trigger("change")})},d.prototype.bind=function(a,b){var c=this;this.container=a,a.on("select",function(a){c.select(a.data)}),a.on("unselect",function(a){c.unselect(a.data)})},d.prototype.destroy=function(){this.$element.find("*").each(function(){c.removeData(this,"data")})},d.prototype.query=function(a,b){var d=[],e=this,f=this.$element.children();f.each(function(){var b=c(this);if(b.is("option")||b.is("optgroup")){var f=e.item(b),g=e.matches(a,f);null!==g&&d.push(g)}}),b({results:d})},d.prototype.addOptions=function(a){b.appendMany(this.$element,a)},d.prototype.option=function(a){var b;a.children?(b=document.createElement("optgroup"),b.label=a.text):(b=document.createElement("option"),void 0!==b.textContent?b.textContent=a.text:b.innerText=a.text),a.id&&(b.value=a.id),a.disabled&&(b.disabled=!0),a.selected&&(b.selected=!0),a.title&&(b.title=a.title);var d=c(b),e=this._normalizeItem(a);return e.element=b,c.data(b,"data",e),d},d.prototype.item=function(a){var b={};if(b=c.data(a[0],"data"),null!=b)return b;if(a.is("option"))b={id:a.val(),text:a.text(),disabled:a.prop("disabled"),selected:a.prop("selected"),title:a.prop("title")};else if(a.is("optgroup")){b={text:a.prop("label"),children:[],title:a.prop("title")};for(var d=a.children("option"),e=[],f=0;f<d.length;f++){var g=c(d[f]),h=this.item(g);e.push(h)}b.children=e}return b=this._normalizeItem(b),b.element=a[0],c.data(a[0],"data",b),b},d.prototype._normalizeItem=function(a){c.isPlainObject(a)||(a={id:a,text:a}),a=c.extend({},{text:""},a);var b={selected:!1,disabled:!1};return null!=a.id&&(a.id=a.id.toString()),null!=a.text&&(a.text=a.text.toString()),null==a._resultId&&a.id&&null!=this.container&&(a._resultId=this.generateResultId(this.container,a)),c.extend({},b,a)},d.prototype.matches=function(a,b){var c=this.options.get("matcher");return c(a,b)},d}),b.define("select2/data/array",["./select","../utils","jquery"],function(a,b,c){function d(a,b){var c=b.get("data")||[];d.__super__.constructor.call(this,a,b),this.addOptions(this.convertToOptions(c))}return b.Extend(d,a),d.prototype.select=function(a){var b=this.$element.find("option").filter(function(b,c){return c.value==a.id.toString()});0===b.length&&(b=this.option(a),this.addOptions(b)),d.__super__.select.call(this,a)},d.prototype.convertToOptions=function(a){function d(a){return function(){return c(this).val()==a.id}}for(var e=this,f=this.$element.find("option"),g=f.map(function(){return e.item(c(this)).id}).get(),h=[],i=0;i<a.length;i++){var j=this._normalizeItem(a[i]);if(c.inArray(j.id,g)>=0){var k=f.filter(d(j)),l=this.item(k),m=c.extend(!0,{},j,l),n=this.option(m);k.replaceWith(n)}else{var o=this.option(j);if(j.children){var p=this.convertToOptions(j.children);b.appendMany(o,p)}h.push(o)}}return h},d}),b.define("select2/data/ajax",["./array","../utils","jquery"],function(a,b,c){function d(a,b){this.ajaxOptions=this._applyDefaults(b.get("ajax")),null!=this.ajaxOptions.processResults&&(this.processResults=this.ajaxOptions.processResults),d.__super__.constructor.call(this,a,b)}return b.Extend(d,a),d.prototype._applyDefaults=function(a){var b={data:function(a){return c.extend({},a,{q:a.term})},transport:function(a,b,d){var e=c.ajax(a);return e.then(b),e.fail(d),e}};return c.extend({},b,a,!0)},d.prototype.processResults=function(a){return a},d.prototype.query=function(a,b){function d(){var d=f.transport(f,function(d){var f=e.processResults(d,a);e.options.get("debug")&&window.console&&console.error&&(f&&f.results&&c.isArray(f.results)||console.error("Select2: The AJAX results did not return an array in the `results` key of the response.")),b(f)},function(){e.trigger("results:message",{message:"errorLoading"})});e._request=d}var e=this;null!=this._request&&(c.isFunction(this._request.abort)&&this._request.abort(),this._request=null);var f=c.extend({type:"GET"},this.ajaxOptions);"function"==typeof f.url&&(f.url=f.url.call(this.$element,a)),"function"==typeof f.data&&(f.data=f.data.call(this.$element,a)),this.ajaxOptions.delay&&""!==a.term?(this._queryTimeout&&window.clearTimeout(this._queryTimeout),this._queryTimeout=window.setTimeout(d,this.ajaxOptions.delay)):d()},d}),b.define("select2/data/tags",["jquery"],function(a){function b(b,c,d){var e=d.get("tags"),f=d.get("createTag");void 0!==f&&(this.createTag=f);var g=d.get("insertTag");if(void 0!==g&&(this.insertTag=g),b.call(this,c,d),a.isArray(e))for(var h=0;h<e.length;h++){var i=e[h],j=this._normalizeItem(i),k=this.option(j);this.$element.append(k)}}return b.prototype.query=function(a,b,c){function d(a,f){for(var g=a.results,h=0;h<g.length;h++){var i=g[h],j=null!=i.children&&!d({results:i.children},!0),k=i.text===b.term;if(k||j)return f?!1:(a.data=g,void c(a))}if(f)return!0;var l=e.createTag(b);if(null!=l){var m=e.option(l);m.attr("data-select2-tag",!0),e.addOptions([m]),e.insertTag(g,l)}a.results=g,c(a)}var e=this;return this._removeOldTags(),null==b.term||null!=b.page?void a.call(this,b,c):void a.call(this,b,d)},b.prototype.createTag=function(b,c){var d=a.trim(c.term);return""===d?null:{id:d,text:d}},b.prototype.insertTag=function(a,b,c){b.unshift(c)},b.prototype._removeOldTags=function(b){var c=(this._lastTag,this.$element.find("option[data-select2-tag]"));c.each(function(){this.selected||a(this).remove()})},b}),b.define("select2/data/tokenizer",["jquery"],function(a){function b(a,b,c){var d=c.get("tokenizer");void 0!==d&&(this.tokenizer=d),a.call(this,b,c)}return b.prototype.bind=function(a,b,c){a.call(this,b,c),this.$search=b.dropdown.$search||b.selection.$search||c.find(".select2-search__field")},b.prototype.query=function(a,b,c){function d(a){e.trigger("select",{data:a})}var e=this;b.term=b.term||"";var f=this.tokenizer(b,this.options,d);f.term!==b.term&&(this.$search.length&&(this.$search.val(f.term),this.$search.focus()),b.term=f.term),a.call(this,b,c)},b.prototype.tokenizer=function(b,c,d,e){for(var f=d.get("tokenSeparators")||[],g=c.term,h=0,i=this.createTag||function(a){return{id:a.term,text:a.term}};h<g.length;){var j=g[h];if(-1!==a.inArray(j,f)){var k=g.substr(0,h),l=a.extend({},c,{term:k}),m=i(l);null!=m?(e(m),g=g.substr(h+1)||"",h=0):h++}else h++}return{term:g}},b}),b.define("select2/data/minimumInputLength",[],function(){function a(a,b,c){this.minimumInputLength=c.get("minimumInputLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){return b.term=b.term||"",b.term.length<this.minimumInputLength?void this.trigger("results:message",{message:"inputTooShort",args:{minimum:this.minimumInputLength,input:b.term,params:b}}):void a.call(this,b,c)},a}),b.define("select2/data/maximumInputLength",[],function(){function a(a,b,c){this.maximumInputLength=c.get("maximumInputLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){return b.term=b.term||"",this.maximumInputLength>0&&b.term.length>this.maximumInputLength?void this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:b.term,params:b}}):void a.call(this,b,c)},a}),b.define("select2/data/maximumSelectionLength",[],function(){function a(a,b,c){this.maximumSelectionLength=c.get("maximumSelectionLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){var d=this;this.current(function(e){var f=null!=e?e.length:0;return d.maximumSelectionLength>0&&f>=d.maximumSelectionLength?void d.trigger("results:message",{message:"maximumSelected",args:{maximum:d.maximumSelectionLength}}):void a.call(d,b,c)})},a}),b.define("select2/dropdown",["jquery","./utils"],function(a,b){function c(a,b){this.$element=a,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('<span class="select2-dropdown"><span class="select2-results"></span></span>');return b.attr("dir",this.options.get("dir")),this.$dropdown=b,b},c.prototype.bind=function(){},c.prototype.position=function(a,b){},c.prototype.destroy=function(){this.$dropdown.remove()},c}),b.define("select2/dropdown/search",["jquery","../utils"],function(a,b){function c(){}return c.prototype.render=function(b){var c=b.call(this),d=a('<span class="select2-search select2-search--dropdown"><input class="select2-search__field" type="search" tabindex="-1" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" role="textbox" /></span>');return this.$searchContainer=d,this.$search=d.find("input"),c.prepend(d),c},c.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),this.$search.on("keydown",function(a){e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented()}),this.$search.on("input",function(b){a(this).off("keyup")}),this.$search.on("keyup input",function(a){e.handleSearch(a)}),c.on("open",function(){e.$search.attr("tabindex",0),e.$search.focus(),window.setTimeout(function(){e.$search.focus()},0)}),c.on("close",function(){e.$search.attr("tabindex",-1),e.$search.val("")}),c.on("results:all",function(a){if(null==a.query.term||""===a.query.term){var b=e.showSearch(a);b?e.$searchContainer.removeClass("select2-search--hide"):e.$searchContainer.addClass("select2-search--hide")}})},c.prototype.handleSearch=function(a){if(!this._keyUpPrevented){var b=this.$search.val();this.trigger("query",{term:b})}this._keyUpPrevented=!1},c.prototype.showSearch=function(a,b){return!0},c}),b.define("select2/dropdown/hidePlaceholder",[],function(){function a(a,b,c,d){this.placeholder=this.normalizePlaceholder(c.get("placeholder")),a.call(this,b,c,d)}return a.prototype.append=function(a,b){b.results=this.removePlaceholder(b.results),a.call(this,b)},a.prototype.normalizePlaceholder=function(a,b){return"string"==typeof b&&(b={id:"",text:b}),b},a.prototype.removePlaceholder=function(a,b){for(var c=b.slice(0),d=b.length-1;d>=0;d--){var e=b[d];this.placeholder.id===e.id&&c.splice(d,1)}return c},a}),b.define("select2/dropdown/infiniteScroll",["jquery"],function(a){function b(a,b,c,d){this.lastParams={},a.call(this,b,c,d),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return b.prototype.append=function(a,b){this.$loadingMore.remove(),this.loading=!1,a.call(this,b),this.showLoadingMore(b)&&this.$results.append(this.$loadingMore)},b.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),c.on("query",function(a){e.lastParams=a,e.loading=!0}),c.on("query:append",function(a){e.lastParams=a,e.loading=!0}),this.$results.on("scroll",function(){var b=a.contains(document.documentElement,e.$loadingMore[0]);if(!e.loading&&b){var c=e.$results.offset().top+e.$results.outerHeight(!1),d=e.$loadingMore.offset().top+e.$loadingMore.outerHeight(!1);c+50>=d&&e.loadMore()}})},b.prototype.loadMore=function(){this.loading=!0;var b=a.extend({},{page:1},this.lastParams);b.page++,this.trigger("query:append",b)},b.prototype.showLoadingMore=function(a,b){return b.pagination&&b.pagination.more},b.prototype.createLoadingMore=function(){var b=a('<li class="select2-results__option select2-results__option--load-more"role="treeitem" aria-disabled="true"></li>'),c=this.options.get("translations").get("loadingMore");return b.html(c(this.lastParams)),b},b}),b.define("select2/dropdown/attachBody",["jquery","../utils"],function(a,b){function c(b,c,d){this.$dropdownParent=d.get("dropdownParent")||a(document.body),b.call(this,c,d)}return c.prototype.bind=function(a,b,c){var d=this,e=!1;a.call(this,b,c),b.on("open",function(){d._showDropdown(),d._attachPositioningHandler(b),e||(e=!0,b.on("results:all",function(){d._positionDropdown(),d._resizeDropdown()}),b.on("results:append",function(){d._positionDropdown(),d._resizeDropdown()}))}),b.on("close",function(){d._hideDropdown(),d._detachPositioningHandler(b)}),this.$dropdownContainer.on("mousedown",function(a){a.stopPropagation()})},c.prototype.destroy=function(a){a.call(this),this.$dropdownContainer.remove()},c.prototype.position=function(a,b,c){b.attr("class",c.attr("class")),b.removeClass("select2"),b.addClass("select2-container--open"),b.css({position:"absolute",top:-999999}),this.$container=c},c.prototype.render=function(b){var c=a("<span></span>"),d=b.call(this);return c.append(d),this.$dropdownContainer=c,c},c.prototype._hideDropdown=function(a){this.$dropdownContainer.detach()},c.prototype._attachPositioningHandler=function(c,d){var e=this,f="scroll.select2."+d.id,g="resize.select2."+d.id,h="orientationchange.select2."+d.id,i=this.$container.parents().filter(b.hasScroll);i.each(function(){a(this).data("select2-scroll-position",{x:a(this).scrollLeft(),y:a(this).scrollTop()})}),i.on(f,function(b){var c=a(this).data("select2-scroll-position");a(this).scrollTop(c.y)}),a(window).on(f+" "+g+" "+h,function(a){e._positionDropdown(),e._resizeDropdown()})},c.prototype._detachPositioningHandler=function(c,d){var e="scroll.select2."+d.id,f="resize.select2."+d.id,g="orientationchange.select2."+d.id,h=this.$container.parents().filter(b.hasScroll);h.off(e),a(window).off(e+" "+f+" "+g)},c.prototype._positionDropdown=function(){var b=a(window),c=this.$dropdown.hasClass("select2-dropdown--above"),d=this.$dropdown.hasClass("select2-dropdown--below"),e=null,f=this.$container.offset();f.bottom=f.top+this.$container.outerHeight(!1);var g={height:this.$container.outerHeight(!1)};g.top=f.top,g.bottom=f.top+g.height;var h={height:this.$dropdown.outerHeight(!1)},i={top:b.scrollTop(),bottom:b.scrollTop()+b.height()},j=i.top<f.top-h.height,k=i.bottom>f.bottom+h.height,l={left:f.left,top:g.bottom},m=this.$dropdownParent;"static"===m.css("position")&&(m=m.offsetParent());var n=m.offset();l.top-=n.top,l.left-=n.left,c||d||(e="below"),k||!j||c?!j&&k&&c&&(e="below"):e="above",("above"==e||c&&"below"!==e)&&(l.top=g.top-h.height),null!=e&&(this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+e),this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+e)),this.$dropdownContainer.css(l)},c.prototype._resizeDropdown=function(){var a={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(a.minWidth=a.width,a.width="auto"),this.$dropdown.css(a)},c.prototype._showDropdown=function(a){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},c}),b.define("select2/dropdown/minimumResultsForSearch",[],function(){function a(b){for(var c=0,d=0;d<b.length;d++){var e=b[d];e.children?c+=a(e.children):c++}return c}function b(a,b,c,d){this.minimumResultsForSearch=c.get("minimumResultsForSearch"),this.minimumResultsForSearch<0&&(this.minimumResultsForSearch=1/0),a.call(this,b,c,d)}return b.prototype.showSearch=function(b,c){return a(c.data.results)<this.minimumResultsForSearch?!1:b.call(this,c)},b}),b.define("select2/dropdown/selectOnClose",[],function(){function a(){}return a.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),b.on("close",function(){d._handleSelectOnClose()})},a.prototype._handleSelectOnClose=function(){var a=this.getHighlightedResults();if(!(a.length<1)){var b=a.data("data");null!=b.element&&b.element.selected||null==b.element&&b.selected||this.trigger("select",{data:b})}},a}),b.define("select2/dropdown/closeOnSelect",[],function(){function a(){}return a.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),b.on("select",function(a){d._selectTriggered(a)}),b.on("unselect",function(a){d._selectTriggered(a)})},a.prototype._selectTriggered=function(a,b){var c=b.originalEvent;c&&c.ctrlKey||this.trigger("close",{})},a}),b.define("select2/i18n/en",[],function(){return{errorLoading:function(){return"The results could not be loaded."},inputTooLong:function(a){var b=a.input.length-a.maximum,c="Please delete "+b+" character";return 1!=b&&(c+="s"),c},inputTooShort:function(a){var b=a.minimum-a.input.length,c="Please enter "+b+" or more characters";return c},loadingMore:function(){return"Loading more results…"},maximumSelected:function(a){var b="You can only select "+a.maximum+" item";return 1!=a.maximum&&(b+="s"),b},noResults:function(){return"No results found"},searching:function(){return"Searching…"}}}),b.define("select2/defaults",["jquery","require","./results","./selection/single","./selection/multiple","./selection/placeholder","./selection/allowClear","./selection/search","./selection/eventRelay","./utils","./translation","./diacritics","./data/select","./data/array","./data/ajax","./data/tags","./data/tokenizer","./data/minimumInputLength","./data/maximumInputLength","./data/maximumSelectionLength","./dropdown","./dropdown/search","./dropdown/hidePlaceholder","./dropdown/infiniteScroll","./dropdown/attachBody","./dropdown/minimumResultsForSearch","./dropdown/selectOnClose","./dropdown/closeOnSelect","./i18n/en"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C){function D(){this.reset()}D.prototype.apply=function(l){if(l=a.extend(!0,{},this.defaults,l),null==l.dataAdapter){if(null!=l.ajax?l.dataAdapter=o:null!=l.data?l.dataAdapter=n:l.dataAdapter=m,l.minimumInputLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,r)),l.maximumInputLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,s)),l.maximumSelectionLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,t)),l.tags&&(l.dataAdapter=j.Decorate(l.dataAdapter,p)),(null!=l.tokenSeparators||null!=l.tokenizer)&&(l.dataAdapter=j.Decorate(l.dataAdapter,q)),null!=l.query){var C=b(l.amdBase+"compat/query");l.dataAdapter=j.Decorate(l.dataAdapter,C)}if(null!=l.initSelection){var D=b(l.amdBase+"compat/initSelection");l.dataAdapter=j.Decorate(l.dataAdapter,D)}}if(null==l.resultsAdapter&&(l.resultsAdapter=c,null!=l.ajax&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,x)),null!=l.placeholder&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,w)),l.selectOnClose&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,A))),null==l.dropdownAdapter){if(l.multiple)l.dropdownAdapter=u;else{var E=j.Decorate(u,v);l.dropdownAdapter=E}if(0!==l.minimumResultsForSearch&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,z)),l.closeOnSelect&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,B)),null!=l.dropdownCssClass||null!=l.dropdownCss||null!=l.adaptDropdownCssClass){var F=b(l.amdBase+"compat/dropdownCss");l.dropdownAdapter=j.Decorate(l.dropdownAdapter,F)}l.dropdownAdapter=j.Decorate(l.dropdownAdapter,y)}if(null==l.selectionAdapter){if(l.multiple?l.selectionAdapter=e:l.selectionAdapter=d,null!=l.placeholder&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,f)),l.allowClear&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,g)),l.multiple&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,h)),null!=l.containerCssClass||null!=l.containerCss||null!=l.adaptContainerCssClass){var G=b(l.amdBase+"compat/containerCss");l.selectionAdapter=j.Decorate(l.selectionAdapter,G)}l.selectionAdapter=j.Decorate(l.selectionAdapter,i)}if("string"==typeof l.language)if(l.language.indexOf("-")>0){var H=l.language.split("-"),I=H[0];l.language=[l.language,I]}else l.language=[l.language];if(a.isArray(l.language)){var J=new k;l.language.push("en");for(var K=l.language,L=0;L<K.length;L++){var M=K[L],N={};try{N=k.loadPath(M)}catch(O){try{M=this.defaults.amdLanguageBase+M,N=k.loadPath(M)}catch(P){l.debug&&window.console&&console.warn&&console.warn('Select2: The language file for "'+M+'" could not be automatically loaded. A fallback will be used instead.');continue}}J.extend(N)}l.translations=J}else{var Q=k.loadPath(this.defaults.amdLanguageBase+"en"),R=new k(l.language);R.extend(Q),l.translations=R}return l},D.prototype.reset=function(){function b(a){function b(a){return l[a]||a}return a.replace(/[^\u0000-\u007E]/g,b)}function c(d,e){if(""===a.trim(d.term))return e;if(e.children&&e.children.length>0){for(var f=a.extend(!0,{},e),g=e.children.length-1;g>=0;g--){var h=e.children[g],i=c(d,h);null==i&&f.children.splice(g,1)}return f.children.length>0?f:c(d,f)}var j=b(e.text).toUpperCase(),k=b(d.term).toUpperCase();return j.indexOf(k)>-1?e:null}this.defaults={amdBase:"./",amdLanguageBase:"./i18n/",closeOnSelect:!0,debug:!1,dropdownAutoWidth:!1,escapeMarkup:j.escapeMarkup,language:C,matcher:c,minimumInputLength:0,maximumInputLength:0,maximumSelectionLength:0,minimumResultsForSearch:0,selectOnClose:!1,sorter:function(a){return a},templateResult:function(a){return a.text},templateSelection:function(a){return a.text},theme:"default",width:"resolve"}},D.prototype.set=function(b,c){var d=a.camelCase(b),e={};e[d]=c;var f=j._convertData(e);a.extend(this.defaults,f)};var E=new D;return E}),b.define("select2/options",["require","jquery","./defaults","./utils"],function(a,b,c,d){function e(b,e){if(this.options=b,null!=e&&this.fromElement(e),this.options=c.apply(this.options),e&&e.is("input")){var f=a(this.get("amdBase")+"compat/inputData");this.options.dataAdapter=d.Decorate(this.options.dataAdapter,f)}}return e.prototype.fromElement=function(a){var c=["select2"];null==this.options.multiple&&(this.options.multiple=a.prop("multiple")),null==this.options.disabled&&(this.options.disabled=a.prop("disabled")),null==this.options.language&&(a.prop("lang")?this.options.language=a.prop("lang").toLowerCase():a.closest("[lang]").prop("lang")&&(this.options.language=a.closest("[lang]").prop("lang"))),null==this.options.dir&&(a.prop("dir")?this.options.dir=a.prop("dir"):a.closest("[dir]").prop("dir")?this.options.dir=a.closest("[dir]").prop("dir"):this.options.dir="ltr"),a.prop("disabled",this.options.disabled),a.prop("multiple",this.options.multiple),a.data("select2Tags")&&(this.options.debug&&window.console&&console.warn&&console.warn('Select2: The `data-select2-tags` attribute has been changed to use the `data-data` and `data-tags="true"` attributes and will be removed in future versions of Select2.'),a.data("data",a.data("select2Tags")),a.data("tags",!0)),a.data("ajaxUrl")&&(this.options.debug&&window.console&&console.warn&&console.warn("Select2: The `data-ajax-url` attribute has been changed to `data-ajax--url` and support for the old attribute will be removed in future versions of Select2."),a.attr("ajax--url",a.data("ajaxUrl")),a.data("ajax--url",a.data("ajaxUrl")));var e={};e=b.fn.jquery&&"1."==b.fn.jquery.substr(0,2)&&a[0].dataset?b.extend(!0,{},a[0].dataset,a.data()):a.data();var f=b.extend(!0,{},e);f=d._convertData(f);for(var g in f)b.inArray(g,c)>-1||(b.isPlainObject(this.options[g])?b.extend(this.options[g],f[g]):this.options[g]=f[g]);return this},e.prototype.get=function(a){return this.options[a]},e.prototype.set=function(a,b){this.options[a]=b},e}),b.define("select2/core",["jquery","./options","./utils","./keys"],function(a,b,c,d){var e=function(a,c){null!=a.data("select2")&&a.data("select2").destroy(),this.$element=a,this.id=this._generateId(a),c=c||{},this.options=new b(c,a),e.__super__.constructor.call(this);var d=a.attr("tabindex")||0;a.data("old-tabindex",d),a.attr("tabindex","-1");var f=this.options.get("dataAdapter");this.dataAdapter=new f(a,this.options);var g=this.render();this._placeContainer(g);var h=this.options.get("selectionAdapter");this.selection=new h(a,this.options),this.$selection=this.selection.render(),this.selection.position(this.$selection,g);var i=this.options.get("dropdownAdapter");this.dropdown=new i(a,this.options),this.$dropdown=this.dropdown.render(),this.dropdown.position(this.$dropdown,g);var j=this.options.get("resultsAdapter");this.results=new j(a,this.options,this.dataAdapter),this.$results=this.results.render(),this.results.position(this.$results,this.$dropdown);var k=this;this._bindAdapters(),this._registerDomEvents(),this._registerDataEvents(),this._registerSelectionEvents(),this._registerDropdownEvents(),this._registerResultsEvents(),this._registerEvents(),this.dataAdapter.current(function(a){k.trigger("selection:update",{data:a})}),a.addClass("select2-hidden-accessible"),a.attr("aria-hidden","true"),this._syncAttributes(),a.data("select2",this)};return c.Extend(e,c.Observable),e.prototype._generateId=function(a){var b="";return b=null!=a.attr("id")?a.attr("id"):null!=a.attr("name")?a.attr("name")+"-"+c.generateChars(2):c.generateChars(4),b=b.replace(/(:|\.|\[|\]|,)/g,""),b="select2-"+b},e.prototype._placeContainer=function(a){a.insertAfter(this.$element);var b=this._resolveWidth(this.$element,this.options.get("width"));null!=b&&a.css("width",b)},e.prototype._resolveWidth=function(a,b){var c=/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;if("resolve"==b){var d=this._resolveWidth(a,"style");return null!=d?d:this._resolveWidth(a,"element")}if("element"==b){var e=a.outerWidth(!1);return 0>=e?"auto":e+"px"}if("style"==b){var f=a.attr("style");if("string"!=typeof f)return null;for(var g=f.split(";"),h=0,i=g.length;i>h;h+=1){var j=g[h].replace(/\s/g,""),k=j.match(c);if(null!==k&&k.length>=1)return k[1]}return null}return b},e.prototype._bindAdapters=function(){this.dataAdapter.bind(this,this.$container),this.selection.bind(this,this.$container),this.dropdown.bind(this,this.$container),this.results.bind(this,this.$container)},e.prototype._registerDomEvents=function(){var b=this;this.$element.on("change.select2",function(){b.dataAdapter.current(function(a){b.trigger("selection:update",{data:a})})}),this._sync=c.bind(this._syncAttributes,this),this.$element[0].attachEvent&&this.$element[0].attachEvent("onpropertychange",this._sync);var d=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver;null!=d?(this._observer=new d(function(c){a.each(c,b._sync)}),this._observer.observe(this.$element[0],{attributes:!0,subtree:!1})):this.$element[0].addEventListener&&this.$element[0].addEventListener("DOMAttrModified",b._sync,!1)},e.prototype._registerDataEvents=function(){var a=this;this.dataAdapter.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerSelectionEvents=function(){var b=this,c=["toggle","focus"];this.selection.on("toggle",function(){b.toggleDropdown()}),this.selection.on("focus",function(a){b.focus(a)}),this.selection.on("*",function(d,e){-1===a.inArray(d,c)&&b.trigger(d,e)})},e.prototype._registerDropdownEvents=function(){var a=this;this.dropdown.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerResultsEvents=function(){var a=this;this.results.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerEvents=function(){var a=this;this.on("open",function(){a.$container.addClass("select2-container--open")}),this.on("close",function(){a.$container.removeClass("select2-container--open")}),this.on("enable",function(){a.$container.removeClass("select2-container--disabled")}),this.on("disable",function(){a.$container.addClass("select2-container--disabled")}),this.on("blur",function(){a.$container.removeClass("select2-container--focus")}),this.on("query",function(b){a.isOpen()||a.trigger("open",{}),this.dataAdapter.query(b,function(c){a.trigger("results:all",{data:c,query:b})})}),this.on("query:append",function(b){this.dataAdapter.query(b,function(c){a.trigger("results:append",{data:c,query:b})})}),this.on("keypress",function(b){var c=b.which;a.isOpen()?c===d.ESC||c===d.TAB||c===d.UP&&b.altKey?(a.close(),b.preventDefault()):c===d.ENTER?(a.trigger("results:select",{}),b.preventDefault()):c===d.SPACE&&b.ctrlKey?(a.trigger("results:toggle",{}),b.preventDefault()):c===d.UP?(a.trigger("results:previous",{}),b.preventDefault()):c===d.DOWN&&(a.trigger("results:next",{}),b.preventDefault()):(c===d.ENTER||c===d.SPACE||c===d.DOWN&&b.altKey)&&(a.open(),b.preventDefault())})},e.prototype._syncAttributes=function(){this.options.set("disabled",this.$element.prop("disabled")),this.options.get("disabled")?(this.isOpen()&&this.close(),this.trigger("disable",{})):this.trigger("enable",{})},e.prototype.trigger=function(a,b){var c=e.__super__.trigger,d={open:"opening",close:"closing",select:"selecting",unselect:"unselecting"};if(void 0===b&&(b={}),a in d){var f=d[a],g={prevented:!1,name:a,args:b};if(c.call(this,f,g),g.prevented)return void(b.prevented=!0)}c.call(this,a,b)},e.prototype.toggleDropdown=function(){this.options.get("disabled")||(this.isOpen()?this.close():this.open())},e.prototype.open=function(){this.isOpen()||this.trigger("query",{})},e.prototype.close=function(){this.isOpen()&&this.trigger("close",{})},e.prototype.isOpen=function(){return this.$container.hasClass("select2-container--open")},e.prototype.hasFocus=function(){return this.$container.hasClass("select2-container--focus")},e.prototype.focus=function(a){this.hasFocus()||(this.$container.addClass("select2-container--focus"),this.trigger("focus",{}))},e.prototype.enable=function(a){this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("enable")` method has been deprecated and will be removed in later Select2 versions. Use $element.prop("disabled") instead.'),(null==a||0===a.length)&&(a=[!0]);var b=!a[0];this.$element.prop("disabled",b)},e.prototype.data=function(){this.options.get("debug")&&arguments.length>0&&window.console&&console.warn&&console.warn('Select2: Data can no longer be set using `select2("data")`. You should consider setting the value instead using `$element.val()`.');var a=[];return this.dataAdapter.current(function(b){a=b}),a},e.prototype.val=function(b){if(this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("val")` method has been deprecated and will be removed in later Select2 versions. Use $element.val() instead.'),null==b||0===b.length)return this.$element.val();var c=b[0];a.isArray(c)&&(c=a.map(c,function(a){return a.toString()})),this.$element.val(c).trigger("change")},e.prototype.destroy=function(){this.$container.remove(),this.$element[0].detachEvent&&this.$element[0].detachEvent("onpropertychange",this._sync),null!=this._observer?(this._observer.disconnect(),this._observer=null):this.$element[0].removeEventListener&&this.$element[0].removeEventListener("DOMAttrModified",this._sync,!1),this._sync=null,this.$element.off(".select2"),this.$element.attr("tabindex",this.$element.data("old-tabindex")),this.$element.removeClass("select2-hidden-accessible"),this.$element.attr("aria-hidden","false"),this.$element.removeData("select2"),this.dataAdapter.destroy(),this.selection.destroy(),this.dropdown.destroy(),this.results.destroy(),this.dataAdapter=null,this.selection=null,this.dropdown=null,this.results=null},e.prototype.render=function(){var b=a('<span class="select2 select2-container"><span class="selection"></span><span class="dropdown-wrapper" aria-hidden="true"></span></span>');return b.attr("dir",this.options.get("dir")),this.$container=b,this.$container.addClass("select2-container--"+this.options.get("theme")),b.data("element",this.$element),b},e}),b.define("jquery-mousewheel",["jquery"],function(a){return a}),b.define("jquery.select2",["jquery","jquery-mousewheel","./select2/core","./select2/defaults"],function(a,b,c,d){if(null==a.fn.select2){var e=["open","close","destroy"];a.fn.select2=function(b){if(b=b||{},"object"==typeof b)return this.each(function(){var d=a.extend(!0,{},b);new c(a(this),d)}),this;if("string"==typeof b){var d;return this.each(function(){var c=a(this).data("select2");null==c&&window.console&&console.error&&console.error("The select2('"+b+"') method was called on an element that is not using Select2.");var e=Array.prototype.slice.call(arguments,1);d=c[b].apply(c,e)}),a.inArray(b,e)>-1?this:d}throw new Error("Invalid arguments for Select2: "+b)}}return null==a.fn.select2.defaults&&(a.fn.select2.defaults=d),c}),{define:b.define,require:b.require}}(),c=b.require("jquery.select2");return a.fn.select2.amd=b,c});
includes/simba-tfa/includes/tfa.js ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery(function($) {
2
+
3
+ /**
4
+ * Check if the user requires an OTP field and if so, display it
5
+ *
6
+ * @param String form - DOM selector string
7
+ *
8
+ * @uses show_otp_field()
9
+ *
10
+ * @return Boolean - true if we got involved
11
+ */
12
+ function check_and_possibly_show_otp_field(form) {
13
+
14
+ // If this is a "lost password" form, then exit
15
+ if ($(form).attr('id') === 'lostpasswordform' || $(form).attr('id') === 'resetpasswordform') return false;
16
+
17
+ // 'username' is used by WooCommerce
18
+ var username = $(form).find('[name="log"], [name="username"], #user_login, #affwp-login-user-login, #affwp-user-login').first().val();
19
+
20
+ if (!username.length) return false;
21
+
22
+ var $submit_button = $(form).find('input[name="wp-submit"], input[type="submit"], button[type="submit"]').first();
23
+
24
+ if (simba_tfasettings.hasOwnProperty('spinnerimg')) {
25
+ var styling = 'float:right; margin:6px 12px; width: 20px; height: 20px;';
26
+ if ($('#theme-my-login #wp-submit').length >0) {
27
+ styling = 'margin-left: 4px; position: relative; top: 4px; width: 20px; height: 20px; border:0px; box-shadow:none;';
28
+ }
29
+ $submit_button.after('<img class="simbaotp_spinner" src="'+simba_tfasettings.spinnerimg+'" style="'+styling+'">');
30
+ }
31
+
32
+ $.ajax({
33
+ url: simba_tfasettings.ajaxurl,
34
+ type: 'POST',
35
+ data: {
36
+ action: 'simbatfa-init-otp',
37
+ user: username
38
+ },
39
+ dataType: 'text',
40
+ success: function(resp) {
41
+ try {
42
+ var json_begins = resp.search('{"jsonstarter":"justhere"');
43
+ if (json_begins > -1) {
44
+ if (json_begins > 0) {
45
+ console.log("Expected JSON marker found at position: "+json_begins);
46
+ resp = resp.substring(json_begins);
47
+ }
48
+ } else {
49
+ console.log("Expected JSON marker not found");
50
+ console.log(resp);
51
+ }
52
+
53
+ response = JSON.parse(resp);
54
+
55
+ if (response.hasOwnProperty('php_output')) {
56
+ console.log("PHP output was returned (follows)");
57
+ console.log(response.php_output);
58
+ }
59
+
60
+ if (response.hasOwnProperty('extra_output')) {
61
+ console.log("Extra output was returned (follows)");
62
+ console.log(response.extra_output);
63
+ }
64
+
65
+ if (true === response.status) {
66
+ // Don't bother to remove the spinner if the form is being submitted.
67
+ $('.simbaotp_spinner').remove();
68
+
69
+ var user_can_trust = (response.hasOwnProperty('user_can_trust') && response.user_can_trust) ? true : false;
70
+
71
+ var user_already_trusted = (response.hasOwnProperty('user_already_trusted') && response.user_can_trust) ? true : false;
72
+
73
+ console.log("Simba TFA: User has OTP enabled: showing OTP field (user_can_trust="+user_can_trust+")");
74
+
75
+ show_otp_field(form, user_can_trust, user_already_trusted);
76
+
77
+ } else {
78
+ console.log("Simba TFA: User does not have OTP enabled: submitting form");
79
+
80
+ // For some reason, .submit() stopped working with TML 7.x. N.B. Used to do this only for form_type == 2 ("TML shortcode or widget, WP Members, bbPress, Ultimate Membership Pro, WooCommerce or Elementor login form")
81
+ $(form).find('input[type="submit"], button[type="submit"]').first().trigger('click');
82
+ // $('#wp-submit').parents('form').first().trigger('submit');
83
+ }
84
+
85
+ } catch(err) {
86
+ $('#login').html(resp);
87
+ console.log("Simba TFA: Error when processing response");
88
+ console.log(err);
89
+ console.log(resp);
90
+ }
91
+ },
92
+ error: function(jq_xhr, text_status, error_thrown) {
93
+ console.log("Simba TFA: AJAX error: "+error_thrown+": "+text_status);
94
+ console.log(jq_xhr);
95
+ if (jq_xhr.hasOwnProperty('responseText')) {
96
+ console.log(jq_xhr.responseText);
97
+ }
98
+ }
99
+ });
100
+ return true;
101
+ }
102
+
103
+ // Parameters: see check_and_possibly_show_otp_field
104
+ function show_otp_field(form, user_can_trust, user_already_trusted) {
105
+
106
+ var $submit_button;
107
+
108
+ user_can_trust = ('undefined' == typeof user_can_trust) ? false : user_can_trust;
109
+ user_already_trusted = ('undefined' == typeof user_already_trusted) ? false : user_already_trusted;
110
+
111
+ // name="Submit" is WP-Members. 'submit' is Theme My Login starting from 7.x
112
+ $submit_button = $(form).find('input[name="wp-submit"], input[name="Submit"], input[name="submit"]');
113
+ // This hasn't been needed for anything yet (Jul 2018), but is a decent back-stop that would have prevented some breakage in the past that needed manual attention:
114
+ if (0 == $submit_button.length) {
115
+ $submit_button = $(form).find('input[type="submit"], button[type="submit"]').first();
116
+ }
117
+
118
+ // Hide all elements in a browser-safe way
119
+ // .user-pass-wrap is the wrapper used (instead of a paragraph) on wp-login.php from WP 5.3
120
+ $submit_button.parents('form').first().find('p, .impu-form-line-fr, .tml-field-wrap, .user-pass-wrap, .elementor-field-type-text, .elementor-field-type-submit, .elementor-remember-me, .bbp-username, .bbp-password, .bbp-submit-wrapper').each(function(i) {
121
+ $(this).css('visibility', 'hidden').css('position', 'absolute');
122
+ // On the WooCommerce form, the 'required' asterisk in the child <span> still shows without this
123
+ $(this).find('span').css('visibility', 'hidden').css('position', 'absolute');
124
+ });
125
+
126
+ // WP-Members
127
+ $submit_button.parents('#wpmem_login').find('fieldset').css('visibility', 'hidden').css('position', 'absolute');
128
+
129
+ // Add new field and controls
130
+ var html = '';
131
+
132
+ html += '<label for="simba_two_factor_auth">' + simba_tfasettings.otp + '<br><input type="text" name="two_factor_code" id="simba_two_factor_auth" autocomplete="off" data-lpignore="true"';
133
+
134
+ if ($(form).hasClass('woocommerce-form-login')) {
135
+ // Retain compatibility with previous full-width layout
136
+ html += ' style="width: 100%;"';
137
+ }
138
+
139
+ html += '></label>';
140
+
141
+ html += '<p class="forgetmenot" style="font-size:small;';
142
+
143
+ if (!$(form).hasClass('woocommerce-form-login')) {
144
+ // Retain compatibility with previous full-width layout
145
+ html += ' max-width: 60%;';
146
+ }
147
+
148
+ html += '">'+simba_tfasettings.otp_login_help;
149
+
150
+ if (user_can_trust && ('https:' == window.location.protocol || 'localhost' === location.hostname || '127.0.0.1' === location.hostname)) {
151
+
152
+ html += '<br><input type="checkbox" name="simba_tfa_mark_as_trusted" id="simba_tfa_mark_as_trusted" value="1"><label for="simba_tfa_mark_as_trusted">'+ simba_tfasettings.mark_as_trusted+'</label>';
153
+
154
+ } else {
155
+ user_already_trusted = false;
156
+ }
157
+
158
+ html += '</p>';
159
+
160
+ var submit_button_text;
161
+ var submit_button_name;
162
+
163
+ if ('button' == $submit_button.prop('nodeName').toLowerCase()) {
164
+ submit_button_text = $submit_button.text().trim();
165
+ submit_button_name = $submit_button.attr('name');
166
+ } else {
167
+ submit_button_text = $submit_button.val();
168
+ submit_button_name = $submit_button.attr('name');
169
+ }
170
+
171
+ html += '<p class="submit"><input id="tfa_login_btn" class="button button-primary button-large" type="submit" ';
172
+
173
+ if ('undefined' !== typeof submit_button_name && '' != submit_button_name) { html += 'name="'+submit_button_name+'" '; }
174
+
175
+ html += 'value="' + submit_button_text + '"></p>';
176
+
177
+ $submit_button.prop('disabled', true);
178
+
179
+ $submit_button.parents('form').first().prepend(html);
180
+ $('#simba_two_factor_auth').trigger('focus');
181
+
182
+ if (user_can_trust && user_already_trusted) {
183
+ $('#simba_two_factor_auth').val(simba_tfasettings.is_trusted);
184
+ $('#tfa_login_btn').trigger('click');
185
+ }
186
+
187
+ }
188
+
189
+ /**
190
+ * This function gets attached to a form submission handler and decides whether to add an OTP field or not.
191
+ *
192
+ * @param Object e - submission event
193
+ *
194
+ * @return Boolean - whether to proceed with the submission or not
195
+ */
196
+ var form_submit_handler = function(e) {
197
+
198
+ console.log('Simba TFA: form submit request');
199
+
200
+ var form = e.target;
201
+ $(form).off();
202
+
203
+ if (check_and_possibly_show_otp_field(form)) {
204
+ e.preventDefault();
205
+ return false;
206
+ }
207
+
208
+ return true;
209
+
210
+ };
211
+
212
+ if (simba_tfasettings.login_form_off_selectors) {
213
+ $(simba_tfasettings.login_form_off_selectors).off('submit');
214
+ }
215
+
216
+ $(simba_tfasettings.login_form_selectors).on('submit', form_submit_handler);
217
+
218
+ });
includes/simba-tfa/includes/tfa_admin_icon_16x16.png ADDED
Binary file
includes/simba-tfa/includes/tfa_admin_icon_32x32.png ADDED
Binary file
includes/simba-tfa/includes/tfa_frontend.php ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if (!defined('ABSPATH')) die('Access denied.');
3
+
4
+ class Simba_TFA_Frontend {
5
+
6
+ private $mother;
7
+
8
+ /**
9
+ * Class constructor
10
+ *
11
+ * @param Object $mother
12
+ */
13
+ public function __construct($mother) {
14
+
15
+ $this->mother = $mother;
16
+ add_action('wp_ajax_tfa_frontend', array($this, 'ajax'));
17
+ add_shortcode('twofactor_user_settings', array($this, 'tfa_user_settings_front'));
18
+ }
19
+
20
+ /**
21
+ * Runs upon the WP action wp_ajax_tfa_frontend
22
+ *
23
+ * @uses die()
24
+ */
25
+ public function ajax() {
26
+ $totp_controller = $this->mother->get_totp_controller();
27
+ global $current_user;
28
+
29
+ $return_array = array();
30
+
31
+ if (empty($_POST) || empty($_POST['subaction']) || !isset($_POST['nonce']) || !is_user_logged_in() || !wp_verify_nonce($_POST['nonce'], 'tfa_frontend_nonce')) die('Security check');
32
+
33
+ if ('savesettings' == $_POST['subaction']) {
34
+ if (empty($_POST['settings']) || !is_string($_POST['settings'])) die;
35
+
36
+ parse_str(stripslashes($_POST['settings']), $posted_settings);
37
+
38
+ if (isset($posted_settings['tfa_algorithm_type'])) {
39
+ $old_algorithm = $totp_controller->get_user_otp_algorithm($current_user->ID);
40
+
41
+ if ($old_algorithm != $posted_settings['tfa_algorithm_type'])
42
+ $totp_controller->changeUserAlgorithmTo($current_user->ID, $posted_settings['tfa_algorithm_type']);
43
+
44
+ //Re-fetch the algorithm type, url and private string
45
+ $variables = $this->tfa_fetch_assort_vars();
46
+
47
+ $return_array['qr'] = $totp_controller->tfa_qr_code_url($variables['algorithm_type'], $variables['url'], $variables['tfa_priv_key']);
48
+ $return_array['al_type_disp'] = $this->tfa_algorithm_info($variables['algorithm_type']);
49
+ }
50
+
51
+ if (isset($posted_settings['tfa_enable_tfa'])) {
52
+
53
+ $allow_enable_or_disable = false;
54
+
55
+ if (empty($posted_settings['require_current']) || !$posted_settings['tfa_enable_tfa']) {
56
+ $allow_enable_or_disable = true;
57
+ } else {
58
+
59
+ if (!isset($posted_settings['tfa_enable_current']) || '' == $posted_settings['tfa_enable_current']) {
60
+ $return_array['message'] = __('To enable TFA, you must enter the current code.', 'all-in-one-wp-security-and-firewall');
61
+ $return_array['error'] = 'code_absent';
62
+ } else {
63
+ // Third parameter: don't allow emergency codes
64
+ if ($totp_controller->check_code_for_user($current_user->ID, $posted_settings['tfa_enable_current'], false)) {
65
+ $allow_enable_or_disable = true;
66
+ } else {
67
+ $return_array['error'] = 'code_wrong';
68
+ $return_array['message'] = __('The TFA code you entered was incorrect.', 'all-in-one-wp-security-and-firewall');
69
+ }
70
+ }
71
+
72
+ }
73
+
74
+ if ($allow_enable_or_disable) $this->mother->change_tfa_enabled_status($current_user->ID, $posted_settings['tfa_enable_tfa']);
75
+ }
76
+
77
+ $return_array['result'] = 'saved';
78
+
79
+ echo json_encode($return_array);
80
+ }
81
+
82
+ die;
83
+ }
84
+
85
+ /**
86
+ * Make the algorithm information string easier to update
87
+ *
88
+ * @param String $algorithm_type - totp|hotp
89
+ */
90
+ public function tfa_algorithm_info($algorithm_type) {
91
+ $al_type_disp = strtoupper($algorithm_type);
92
+ $al_type_desc = ($algorithm_type == 'totp' ? __('a time based', 'all-in-one-wp-security-and-firewall') : __('an event based', 'all-in-one-wp-security-and-firewall'));
93
+
94
+ return array('disp' => $al_type_disp, 'desc' => $al_type_desc);
95
+ }
96
+
97
+ /**
98
+ * Make the assorted required variables more accessible for ajax
99
+ *
100
+ * Returns: Site URL, private key, emergency codes, algorithm type
101
+ *
102
+ * @return Array
103
+ */
104
+ public function tfa_fetch_assort_vars() {
105
+ global $current_user;
106
+ $totp_controller = $this->mother->get_totp_controller();
107
+
108
+ $url = preg_replace('/^https?:\/\//i', '', site_url());
109
+
110
+ $tfa_priv_key_64 = get_user_meta($current_user->ID, 'tfa_priv_key_64', true);
111
+
112
+ if (!$tfa_priv_key_64) $tfa_priv_key_64 = $totp_controller->addPrivateKey($current_user->ID);
113
+
114
+ $tfa_priv_key = trim($totp_controller->getPrivateKeyPlain($tfa_priv_key_64, $current_user->ID));
115
+
116
+ $algorithm_type = $totp_controller->get_user_otp_algorithm($current_user->ID);
117
+
118
+ return apply_filters('simba_tfa_fetch_assort_vars', array(
119
+ 'url' => $url,
120
+ 'tfa_priv_key_64' => $tfa_priv_key_64,
121
+ 'tfa_priv_key' => $tfa_priv_key,
122
+ 'emergency_str' => '<em>'.__('No emergency codes left. Sorry.', 'all-in-one-wp-security-and-firewall').'</em>',
123
+ 'algorithm_type' => $algorithm_type
124
+ ), $totp_controller, $current_user);
125
+ }
126
+
127
+ /**
128
+ * Paints out the 'save settings' button
129
+ */
130
+ public function save_settings_button() {
131
+ echo '<button style="margin-left: 4px;margin-bottom: 10px" class="simbatfa_settings_save button button-primary">'.__('Save Settings', 'all-in-one-wp-security-and-firewall').'</button>';
132
+ }
133
+
134
+ /**
135
+ * Paint output for the TFA on/off radio
136
+ *
137
+ * @param String $style - valid values are 'show_current' and 'require_current'
138
+ */
139
+ public function settings_enable_or_disable_output($style = 'show_current') {
140
+ $this->save_settings_javascript_output();
141
+ global $current_user;
142
+ ?>
143
+ <div class="simbatfa_frontend_settings_box tfa_settings_form">
144
+ <p><?php $this->mother->paint_enable_tfa_radios($current_user->ID, true, $style); ?></p>
145
+ <button style="margin-left: 4px; margin-bottom: 10px;" class="button button-primary simbatfa_settings_save"><?php _e('Save Settings', 'all-in-one-wp-security-and-firewall'); ?></button>
146
+ </div>
147
+ <?php
148
+ }
149
+
150
+ /**
151
+ * Enqueue scripts
152
+ */
153
+ public function save_settings_javascript_output() {
154
+
155
+ static $is_already_added = false;
156
+ if ($is_already_added) return;
157
+ $is_already_added = true;
158
+
159
+ $suffix = defined('SCRIPT_DEBUG') && SCRIPT_DEBUG ? '' : '.min';
160
+ wp_register_script('jquery-blockui', $this->mother->includes_url().'/jquery.blockUI' . $suffix . '.js', array('jquery'), '2.60');
161
+
162
+ $script_ver = (defined('WP_DEBUG') && WP_DEBUG) ? time() : filemtime($this->mother->includes_dir().'/frontend-settings.js');
163
+
164
+ wp_enqueue_script('simba-tfa-frontend-settings', $this->mother->includes_url().'/frontend-settings.js', array('jquery-blockui'), $script_ver);
165
+
166
+ $ajax_url = admin_url('admin-ajax.php');
167
+ // It's possible that FORCE_ADMIN_SSL will make that SSL, whilst the user is on the front-end having logged in over non-SSL - and as a result, their login cookies won't get sent, and they're not registered as logged in.
168
+ if (!is_admin() && substr(strtolower($ajax_url), 0, 6) == 'https:' && !is_ssl()) {
169
+ $also_try = 'http:'.substr($ajax_url, 6);
170
+ } else {
171
+ $also_try = '';
172
+ }
173
+
174
+ $localize = array(
175
+ 'ask' => __('You have unsaved settings.', 'all-in-one-wp-security-and-firewall'),
176
+ 'saving' => __('Saving...', 'all-in-one-wp-security-and-firewall'),
177
+ 'ajax_url' => $ajax_url,
178
+ 'also_try' => $also_try,
179
+ 'nonce' => wp_create_nonce('tfa_frontend_nonce'),
180
+ 'response' => __('Response:', 'all-in-one-wp-security-and-firewall'),
181
+ );
182
+
183
+ wp_localize_script('simba-tfa-frontend-settings', 'simba_tfa_frontend', $localize);
184
+
185
+ }
186
+
187
+ /**
188
+ * Shortcode function for twofactor_user_settings
189
+ *
190
+ * @param Array $atts
191
+ * @param Null|String $content
192
+ *
193
+ * @return String
194
+ */
195
+ public function tfa_user_settings_front($atts, $content = null) {
196
+
197
+ if (!is_user_logged_in()) return '';
198
+
199
+ global $current_user;
200
+
201
+ return $this->mother->include_template('shortcode-tfa-user-settings.php', array('is_activated_for_user' => $current_user->ID, 'tfa_frontend' => $this), true);
202
+
203
+ }
204
+ }
includes/simba-tfa/includes/users.css ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* TFA users list column */
2
+ th.column-tfa-status,
3
+ td.column-tfa-status {
4
+ text-align: center;
5
+ }
6
+
7
+ td.column-tfa-status span.dashicons-no {
8
+ color: red;
9
+ }
10
+
11
+ td.column-tfa-status span.dashicons-yes {
12
+ color: green;
13
+ }
includes/simba-tfa/providers/totp-hotp/Base32/Base32.php ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ //namespace Base32;
3
+
4
+ /**
5
+ * Base32 encoder and decoder
6
+ *
7
+ * Last update: 2012-06-20
8
+ *
9
+ * RFC 4648 compliant
10
+ * @link http://www.ietf.org/rfc/rfc4648.txt
11
+ *
12
+ * Some groundwork based on this class
13
+ * https://github.com/NTICompass/PHP-Base32
14
+ *
15
+ * @author Christian Riesen <chris.riesen@gmail.com>
16
+ * @link http://christianriesen.com
17
+ * @license MIT License see LICENSE file
18
+ */
19
+ class Base32
20
+ {
21
+ /**
22
+ * Table for encoding base32
23
+ *
24
+ * @var array
25
+ */
26
+ private static $encode = array(
27
+ 0 => 'A',
28
+ 1 => 'B',
29
+ 2 => 'C',
30
+ 3 => 'D',
31
+ 4 => 'E',
32
+ 5 => 'F',
33
+ 6 => 'G',
34
+ 7 => 'H',
35
+ 8 => 'I',
36
+ 9 => 'J',
37
+ 10 => 'K',
38
+ 11 => 'L',
39
+ 12 => 'M',
40
+ 13 => 'N',
41
+ 14 => 'O',
42
+ 15 => 'P',
43
+ 16 => 'Q',
44
+ 17 => 'R',
45
+ 18 => 'S',
46
+ 19 => 'T',
47
+ 20 => 'U',
48
+ 21 => 'V',
49
+ 22 => 'W',
50
+ 23 => 'X',
51
+ 24 => 'Y',
52
+ 25 => 'Z',
53
+ 26 => 2,
54
+ 27 => 3,
55
+ 28 => 4,
56
+ 29 => 5,
57
+ 30 => 6,
58
+ 31 => 7,
59
+ 32 => '=',
60
+ );
61
+
62
+ /**
63
+ * Table for decoding base32
64
+ *
65
+ * @var array
66
+ */
67
+ private static $decode = array(
68
+ 'A' => 0,
69
+ 'B' => 1,
70
+ 'C' => 2,
71
+ 'D' => 3,
72
+ 'E' => 4,
73
+ 'F' => 5,
74
+ 'G' => 6,
75
+ 'H' => 7,
76
+ 'I' => 8,
77
+ 'J' => 9,
78
+ 'K' => 10,
79
+ 'L' => 11,
80
+ 'M' => 12,
81
+ 'N' => 13,
82
+ 'O' => 14,
83
+ 'P' => 15,
84
+ 'Q' => 16,
85
+ 'R' => 17,
86
+ 'S' => 18,
87
+ 'T' => 19,
88
+ 'U' => 20,
89
+ 'V' => 21,
90
+ 'W' => 22,
91
+ 'X' => 23,
92
+ 'Y' => 24,
93
+ 'Z' => 25,
94
+ 2 => 26,
95
+ 3 => 27,
96
+ 4 => 28,
97
+ 5 => 29,
98
+ 6 => 30,
99
+ 7 => 31,
100
+ '=' => 32,
101
+ );
102
+
103
+ /**
104
+ * Creates an array from a binary string into a given chunk size
105
+ *
106
+ * @param string $binaryString String to chunk
107
+ * @param integer $bits Number of bits per chunk
108
+ * @return array
109
+ */
110
+ private static function chunk($binaryString, $bits)
111
+ {
112
+ $binaryString = chunk_split($binaryString, $bits, ' ');
113
+
114
+ if (substr($binaryString, (strlen($binaryString)) - 1) == ' ') {
115
+ $binaryString = substr($binaryString, 0, strlen($binaryString)-1);
116
+ }
117
+
118
+ return explode(' ', $binaryString);
119
+ }
120
+
121
+ /**
122
+ * Encodes into base32
123
+ *
124
+ * @param string $string Clear text string
125
+ * @return string Base32 encoded string
126
+ */
127
+ public static function encode($string)
128
+ {
129
+ if (strlen($string) == 0) {
130
+ // Gives an empty string
131
+ return '';
132
+ }
133
+
134
+ // Convert string to binary
135
+ $binaryString = '';
136
+
137
+ foreach (str_split($string) as $s) {
138
+ // Return each character as an 8-bit binary string
139
+ $s = decbin(ord($s));
140
+ $binaryString .= str_pad($s, 8, 0, STR_PAD_LEFT);
141
+ }
142
+
143
+ // Break into 5-bit chunks, then break that into an array
144
+ $binaryArray = self::chunk($binaryString, 5);
145
+
146
+ // Pad array to be divisible by 8
147
+ while (count($binaryArray) % 8 !== 0) {
148
+ $binaryArray[] = null;
149
+ }
150
+
151
+ $base32String = '';
152
+
153
+ // Encode in base32
154
+ foreach ($binaryArray as $bin) {
155
+ $char = 32;
156
+
157
+ if (!is_null($bin)) {
158
+ // Pad the binary strings
159
+ $bin = str_pad($bin, 5, 0, STR_PAD_RIGHT);
160
+ $char = bindec($bin);
161
+ }
162
+
163
+ // Base32 character
164
+ $base32String .= self::$encode[$char];
165
+ }
166
+
167
+ return $base32String;
168
+ }
169
+
170
+ /**
171
+ * Decodes base32
172
+ *
173
+ * @param string $base32String Base32 encoded string
174
+ * @return string Clear text string
175
+ */
176
+ public static function decode($base32String)
177
+ {
178
+ if (strlen($base32String) == 0) {
179
+ // Gives an empty string
180
+ return '';
181
+ }
182
+
183
+ // Only work in upper cases
184
+ $base32String = strtoupper($base32String);
185
+
186
+ // Remove anything that is not base32 alphabet
187
+ $pattern = '/[^A-Z2-7]/';
188
+ $replacement = '';
189
+
190
+ $base32String = preg_replace($pattern, '', $base32String);
191
+
192
+ $base32Array = str_split($base32String);
193
+
194
+ $binaryArray = array();
195
+ $string = '';
196
+
197
+ foreach ($base32Array as $str) {
198
+ $char = self::$decode[$str];
199
+
200
+ // Ignore the padding character
201
+ if ($char !== 32) {
202
+ $char = decbin($char);
203
+ $string .= str_pad($char, 5, 0, STR_PAD_LEFT);
204
+ }
205
+ }
206
+
207
+ while (strlen($string) %8 !== 0) {
208
+ $string = substr($string, 0, strlen($string)-1);
209
+ }
210
+
211
+ $binaryArray = self::chunk($string, 8);
212
+
213
+ $realString = '';
214
+
215
+ foreach ($binaryArray as $bin) {
216
+ // Pad each value to 8 bits
217
+ $bin = str_pad($bin, 8, 0, STR_PAD_RIGHT);
218
+ // Convert binary strings to ascii
219
+ $realString .= chr(bindec($bin));
220
+ }
221
+
222
+ return $realString;
223
+ }
224
+ }
225
+
includes/simba-tfa/providers/totp-hotp/hotp-php-master/LICENSE ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (c) 2008, Jakob Heuser
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+ * Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ * Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+ * Neither the name of HOTP-PHP nor the
12
+ names of its contributors may be used to endorse or promote products
13
+ derived from this software without specific prior written permission.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY Jakob Heuser ''AS IS'' AND ANY
16
+ EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
19
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
includes/simba-tfa/providers/totp-hotp/hotp-php-master/README.markdown ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ HOTP - PHP Based HMAC One Time Passwords
2
+ ========================================
3
+
4
+ **What is HOTP**:
5
+ HOTP is a class that simplifies One Time Password systems for PHP Authentication. The HOTP/TOTP Algorithms have been around for a bit, so this is a straightforward class to meet the test vector requirements.
6
+
7
+ **What works with HOTP/TOTP**:
8
+ It's been tested to the test vectors, and I've verified the time-sync hashes against the following:
9
+
10
+ * Android: Mobile-OTP
11
+ * iPhone: OATH Token
12
+
13
+ **Why would I use this**:
14
+ Who wouldn't love a simple drop-in class for HMAC Based One Time Passwords? It's a great extra layer of security (creating two-factor auth) and it's pretty darn zippy.
15
+
16
+ **Okay you sold me. Give me some docs**:
17
+
18
+ * $result = HOTP::generateByCounter($key, $counter); // event based
19
+ * $result = HOTP::generateByTime($key, $window); // time based within a "window" of time
20
+ * $result = HOTP::generateByTimeWindow($key, $window, $min, $max); // same as generateByTime, but for $min windows before and $max windows after
21
+
22
+ with $result, you can do all sorts of neat things...
23
+
24
+ * $result->toString();
25
+ * $result->toHex();
26
+ * $result->doDec();
27
+ * $result->toHotp($length); // how many digits in your OTP?
includes/simba-tfa/providers/totp-hotp/hotp-php-master/example.php ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ HOTP Example File
5
+ */
6
+ require_once 'hotp.php';
7
+
8
+ $key = '12345678901234567890';
9
+
10
+ $table = array(
11
+ 'HOTP' => array(
12
+ 0 => array(
13
+ 'HMAC' => 'cc93cf18508d94934c64b65d8ba7667fb7cde4b0',
14
+ 'hex' => '4c93cf18',
15
+ 'dec' => '1284755224',
16
+ 'hotp' => '755224',
17
+ ),
18
+ 1 => array(
19
+ 'HMAC' => '75a48a19d4cbe100644e8ac1397eea747a2d33ab',
20
+ 'hex' => '41397eea',
21
+ 'dec' => '1094287082',
22
+ 'hotp' => '287082',
23
+ ),
24
+ 2 => array(
25
+ 'HMAC' => '0bacb7fa082fef30782211938bc1c5e70416ff44',
26
+ 'hex' => '82fef30',
27
+ 'dec' => '137359152',
28
+ 'hotp' => '359152',
29
+ ),
30
+ 3 => array(
31
+ 'HMAC' => '66c28227d03a2d5529262ff016a1e6ef76557ece',
32
+ 'hex' => '66ef7655',
33
+ 'dec' => '1726969429',
34
+ 'hotp' => '969429',
35
+ ),
36
+ 4 => array(
37
+ 'HMAC' => 'a904c900a64b35909874b33e61c5938a8e15ed1c',
38
+ 'hex' => '61c5938a',
39
+ 'dec' => '1640338314',
40
+ 'hotp' => '338314',
41
+ ),
42
+ 5 => array(
43
+ 'HMAC' => 'a37e783d7b7233c083d4f62926c7a25f238d0316',
44
+ 'hex' => '33c083d4',
45
+ 'dec' => '868254676',
46
+ 'hotp' => '254676',
47
+ ),
48
+ 6 => array(
49
+ 'HMAC' => 'bc9cd28561042c83f219324d3c607256c03272ae',
50
+ 'hex' => '7256c032',
51
+ 'dec' => '1918287922',
52
+ 'hotp' => '287922',
53
+ ),
54
+ 7 => array(
55
+ 'HMAC' => 'a4fb960c0bc06e1eabb804e5b397cdc4b45596fa',
56
+ 'hex' => '4e5b397',
57
+ 'dec' => '82162583',
58
+ 'hotp' => '162583',
59
+ ),
60
+ 8 => array(
61
+ 'HMAC' => '1b3c89f65e6c9e883012052823443f048b4332db',
62
+ 'hex' => '2823443f',
63
+ 'dec' => '673399871',
64
+ 'hotp' => '399871',
65
+ ),
66
+ 9 => array(
67
+ 'HMAC' => '1637409809a679dc698207310c8c7fc07290d9e5',
68
+ 'hex' => '2679dc69',
69
+ 'dec' => '645520489',
70
+ 'hotp' => '520489',
71
+ ),
72
+ ),
73
+ 'TOTP' => array(
74
+ '59' => array(
75
+ 'totp' => '94287082',
76
+ ),
77
+ '1111111109' => array(
78
+ 'totp' => '07081804',
79
+ ),
80
+ '1111111111' => array(
81
+ 'totp' => '14050471',
82
+ ),
83
+ '1234567890' => array(
84
+ 'totp' => '89005924',
85
+ ),
86
+ '2000000000' => array(
87
+ 'totp' => '69279037',
88
+ ),
89
+ ),
90
+ );
91
+
92
+ echo <<<DOCBLOCK
93
+ <!DOCTYPE><html><head></head><body><pre>
94
+ http://www.ietf.org/rfc/rfc4226.txt
95
+ http://tools.ietf.org/html/draft-mraihi-totp-timebased-06
96
+
97
+ TEST VECTOR VERIFICATION
98
+
99
+ HOTP Tests:
100
+
101
+ DOCBLOCK;
102
+
103
+ echo "Count Method Value Pass/Fail\n";
104
+ echo "----------------------------------------------------------------------\n";
105
+
106
+ // loop over all HOTP table results, and calculate the matching value
107
+ foreach ($table['HOTP'] as $seed => $results) {
108
+ $hotp = HOTP::generateByCounter($key, $seed);
109
+ $first = true;
110
+ foreach ($results as $type => $calc) {
111
+ if ($first) {
112
+ echo str_pad($seed, 4, ' ', STR_PAD_LEFT);
113
+ $first = false;
114
+ }
115
+ else {
116
+ echo ' ';
117
+ }
118
+ echo ' ';
119
+ echo str_pad($type, 5, ' ', STR_PAD_RIGHT);
120
+ echo ' ';
121
+ echo str_pad($calc, 47, ' ', STR_PAD_RIGHT);
122
+ echo ' ';
123
+ $method = 'to'.(ucfirst(str_replace('HMAC', 'string', $type)));
124
+ echo str_pad(($calc == $hotp->$method(6)) ? '[OK]' : '[FAIL]', 9, ' ', STR_PAD_LEFT);
125
+ echo "\n";
126
+ }
127
+ }
128
+
129
+ echo <<<DOCBLOCK
130
+
131
+ TOTP Tests:
132
+
133
+ DOCBLOCK;
134
+
135
+ echo "Time (sec) Value Pass/Fail\n";
136
+ echo "----------------------------------------------------------------------\n";
137
+
138
+ // now echo over the TOTP table
139
+ foreach ($table['TOTP'] as $seed => $results) {
140
+ $totp = HOTP::generateByTime($key, 30, $seed);
141
+ $first = true;
142
+ foreach ($results as $type => $calc) {
143
+ if ($first) {
144
+ echo str_pad($seed, 10, ' ', STR_PAD_LEFT);
145
+ $first = false;
146
+ }
147
+ else {
148
+ echo ' ';
149
+ }
150
+ echo ' ';
151
+ echo str_pad($calc, 47, ' ', STR_PAD_RIGHT);
152
+ echo ' ';
153
+ $method = 'to'.(ucfirst(str_replace('totp', 'hotp', $type)));
154
+ echo str_pad(($calc == $totp->$method(8)) ? '[OK]' : '[FAIL]', 9, ' ', STR_PAD_LEFT);
155
+ echo "\n";
156
+ }
157
+ }
158
+
159
+ echo '</pre></body></html>';
160
+
includes/simba-tfa/providers/totp-hotp/hotp-php-master/hotp.php ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * HOTP Class
4
+ * Based on the work of OAuth, and the sample implementation of HMAC OTP
5
+ * http://tools.ietf.org/html/draft-mraihi-oath-hmac-otp-04#appendix-D
6
+ * @author Jakob Heuser (firstname)@felocity.com
7
+ * @copyright 2011
8
+ * @license BSD
9
+ * @version 1.0
10
+ */
11
+ class HOTP {
12
+ /**
13
+ * Generate a HOTP key based on a counter value (event based HOTP)
14
+ * @param string $key the key to use for hashing
15
+ * @param int $counter the number of attempts represented in this hashing
16
+ * @return HOTPResult a HOTP Result which can be truncated or output
17
+ */
18
+ public static function generateByCounter($key, $counter) {
19
+ // the counter value can be more than one byte long,
20
+ // so we need to pack it down properly.
21
+ $cur_counter = array(0, 0, 0, 0, 0, 0, 0, 0);
22
+ for($i = 7; $i >= 0; $i--) {
23
+ $cur_counter[$i] = pack ('C*', $counter);
24
+ $counter = $counter >> 8;
25
+ }
26
+
27
+ $bin_counter = implode($cur_counter);
28
+
29
+ // Pad to 8 chars
30
+ if (strlen($bin_counter) < 8) {
31
+ $bin_counter = str_repeat (chr(0), 8 - strlen ($bin_counter)) . $bin_counter;
32
+ }
33
+
34
+ // HMAC
35
+ $hash = hash_hmac('sha1', $bin_counter, $key);
36
+
37
+ return new HOTPResult($hash);
38
+ }
39
+
40
+ /**
41
+ * Generate a HOTP key based on a timestamp and window size
42
+ * @param string $key the key to use for hashing
43
+ * @param int $window the size of the window a key is valid for in seconds
44
+ * @param int $timestamp a timestamp to calculate for, defaults to time()
45
+ * @return HOTPResult a HOTP Result which can be truncated or output
46
+ */
47
+ public static function generateByTime($key, $window, $timestamp = false) {
48
+ if (!$timestamp && $timestamp !== 0) {
49
+ $timestamp = HOTP::getTime();
50
+ }
51
+
52
+ $counter = intval($timestamp / $window);
53
+
54
+ return HOTP::generateByCounter($key, $counter);
55
+ }
56
+
57
+ /**
58
+ * Generate a HOTP key collection based on a timestamp and window size
59
+ * all keys that could exist between a start and end time will be included
60
+ * in the returned array
61
+ * @param string $key the key to use for hashing
62
+ * @param int $window the size of the window a key is valid for in seconds
63
+ * @param int $min the minimum window to accept before $timestamp
64
+ * @param int $max the maximum window to accept after $timestamp
65
+ * @param int $timestamp a timestamp to calculate for, defaults to time()
66
+ * @return array of HOTPResult
67
+ */
68
+ public static function generateByTimeWindow($key, $window, $min = -1, $max = 1, $timestamp = false) {
69
+ if (!$timestamp && $timestamp !== 0) {
70
+ $timestamp = HOTP::getTime();
71
+ }
72
+
73
+ $counter = intval($timestamp / $window);
74
+ $window = range($min, $max);
75
+
76
+ $out = array();
77
+ for ($i = 0; $i < count($window); $i++) {
78
+ $shift_counter = $window[$i];
79
+ $out[$shift_counter] = HOTP::generateByCounter($key, $counter + $shift_counter);
80
+ }
81
+
82
+ return $out;
83
+ }
84
+
85
+ /**
86
+ * Gets the current time
87
+ * Ensures we are operating in UTC for the entire framework
88
+ * Restores the timezone on exit.
89
+ * @return int the current time
90
+ */
91
+ public static function getTime() {
92
+ return time(); // PHP's time is always UTC
93
+ }
94
+ }
95
+
96
+ /**
97
+ * The HOTPResult Class converts an HOTP item to various forms
98
+ * Supported formats include hex, decimal, string, and HOTP
99
+ * @author Jakob Heuser (firstname)@felocity.com
100
+ */
101
+ class HOTPResult {
102
+ protected $hash;
103
+ protected $binary;
104
+ protected $decimal;
105
+
106
+ /**
107
+ * Build an HOTP Result
108
+ * @param string $value the value to construct with
109
+ */
110
+ public function __construct($value) {
111
+ // store raw
112
+ $this->hash = $value;
113
+ }
114
+
115
+ /**
116
+ * Returns the string version of the HOTP
117
+ * @return string
118
+ */
119
+ public function toString() {
120
+ return $this->hash;
121
+ }
122
+
123
+ /**
124
+ * Returns the hex version of the HOTP
125
+ * @return string
126
+ */
127
+ public function toHex() {
128
+ if( !$this->hex )
129
+ {
130
+ $this->hex = dechex($this->toDec());
131
+ }
132
+ return $this->hex;
133
+ }
134
+
135
+ /**
136
+ * Returns the decimal version of the HOTP
137
+ * @return int
138
+ */
139
+ public function toDec() {
140
+ if( !$this->decimal )
141
+ {
142
+ // store calculate decimal
143
+ $hmac_result = array();
144
+
145
+ // Convert to decimal
146
+ foreach(str_split($this->hash,2) as $hex)
147
+ {
148
+ $hmac_result[] = hexdec($hex);
149
+ }
150
+
151
+ $offset = $hmac_result[19] & 0xf;
152
+
153
+ $this->decimal = (
154
+ (($hmac_result[$offset+0] & 0x7f) << 24 ) |
155
+ (($hmac_result[$offset+1] & 0xff) << 16 ) |
156
+ (($hmac_result[$offset+2] & 0xff) << 8 ) |
157
+ ($hmac_result[$offset+3] & 0xff)
158
+ );
159
+ }
160
+ return $this->decimal;
161
+ }
162
+
163
+ /**
164
+ * Returns the truncated decimal form of the HOTP
165
+ * @param int $length the length of the HOTP to return
166
+ * @return string
167
+ */
168
+ public function toHOTP($length) {
169
+ $str = str_pad($this->toDec(), $length, "0", STR_PAD_LEFT);
170
+ $str = substr($str, (-1 * $length));
171
+ return $str;
172
+ }
173
+
174
+ }
includes/simba-tfa/providers/totp-hotp/loader.php ADDED
@@ -0,0 +1,1015 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if (!defined('ABSPATH')) die('No direct access.');
4
+
5
+ if (!class_exists('HOTP')) require_once(__DIR__.'/hotp-php-master/hotp.php');
6
+ if (!class_exists('Base32')) require_once(__DIR__.'/Base32/Base32.php');
7
+
8
+ class Simba_TFA_Provider_TOTP {
9
+
10
+ // @var Simba_Two_Factor_Authentication
11
+ private $tfa;
12
+
13
+ // @var String
14
+ private $salt_prefix;
15
+
16
+ // @var String
17
+ private $pw_prefix;
18
+
19
+ // @var Integer
20
+ private $time_window_size;
21
+
22
+ // @var Integer
23
+ private $check_back_time_windows;
24
+
25
+ // @var Integer
26
+ private $check_forward_time_windows;
27
+
28
+ // @var Integer
29
+ private $otp_length = 6;
30
+
31
+ // @var Integer
32
+ private $emergency_codes_length = 8;
33
+
34
+ // @var String
35
+ public $default_hmac = 'totp';
36
+
37
+ // @var Boolean
38
+ private $settings_saved = false;
39
+
40
+ /**
41
+ * Class constructor
42
+ *
43
+ * @param Simba_Two_Factor_Authentication main plugin class
44
+ */
45
+ public function __construct($tfa) {
46
+ $this->tfa = $tfa;
47
+
48
+ $this->otp_helper = new HOTP();
49
+
50
+ add_action('plugins_loaded', array($this, 'plugins_loaded'));
51
+
52
+ add_action('admin_init', array($this, 'admin_init'));
53
+
54
+ if (!is_admin()) {
55
+ add_action('init', array($this, 'check_possible_reset'));
56
+ }
57
+
58
+ // Potentially show off-sync message for hotp
59
+ add_action('admin_notices', array($this, 'tfa_show_hotp_off_sync_message'));
60
+ }
61
+
62
+ /**
63
+ * Return whether or not this class detected and saved new settings
64
+ *
65
+ * @return Boolean
66
+ */
67
+ public function were_settings_saved() {
68
+ return $this->settings_saved;
69
+ }
70
+
71
+ /**
72
+ * Runs upon the WP action admin_init
73
+ */
74
+ public function admin_init() {
75
+
76
+ $this->check_possible_reset();
77
+
78
+ global $current_user;
79
+
80
+ if (!empty($_REQUEST['_tfa_activate_nonce']) && !empty($_POST['tfa_enable_tfa']) && wp_verify_nonce($_REQUEST['_tfa_activate_nonce'], 'tfa_activate') && !empty($_GET['settings-updated'])) {
81
+ $this->tfa->change_tfa_enabled_status($current_user->ID, $_POST['tfa_enable_tfa']);
82
+ $this->settings_saved = true;
83
+ }
84
+
85
+ if (!empty($_REQUEST['_tfa_algorithm_nonce']) && !empty($_POST['tfa_algorithm_type']) && !empty($_GET['settings-updated']) && wp_verify_nonce($_REQUEST['_tfa_algorithm_nonce'], 'tfa_algorithm')) {
86
+
87
+ $old_algorithm = $this->get_user_otp_algorithm($current_user->ID);
88
+
89
+ if ($old_algorithm != $_POST['tfa_algorithm_type']) {
90
+ $this->changeUserAlgorithmTo($current_user->ID, $_POST['tfa_algorithm_type']);
91
+ }
92
+
93
+ $this->settings_saved = true;
94
+ }
95
+
96
+ if (!empty($_GET['warning_button_clicked']) && !empty($_REQUEST['resyncnonce']) && wp_verify_nonce($_REQUEST['resyncnonce'], 'tfaresync')) {
97
+ delete_user_meta($current_user->ID, 'tfa_hotp_off_sync');
98
+ }
99
+
100
+ }
101
+
102
+ /**
103
+ * Enqueue adding of JavaScript for footer
104
+ *
105
+ */
106
+ public function add_footer() {
107
+
108
+ static $added_footer = false;
109
+ if ($added_footer) return;
110
+ $added_footer = true;
111
+
112
+ $script_file = (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) ? 'jquery-qrcode.js' : 'jquery-qrcode.min.js';
113
+ $script_ver = (defined('WP_DEBUG') && WP_DEBUG) ? time() : filemtime($this->tfa->includes_dir()."/jquery-qrcode/$script_file");
114
+ wp_enqueue_script('jquery-qrcode', $this->tfa->includes_url()."/jquery-qrcode/$script_file", array('jquery'), $script_ver);
115
+ add_action(is_admin() ? 'admin_footer' : 'wp_footer', array($this, 'footer'));
116
+
117
+ }
118
+
119
+ /**
120
+ * Runs upon the WP actions wp_footer and admin_footer. Adds the necessary JavaScript for rendering and updating QR codes, and handling trusted devices removal in the admin area
121
+ */
122
+ public function footer() {
123
+ $ajax_url = admin_url('admin-ajax.php');
124
+ // It's possible that FORCE_ADMIN_SSL will make that SSL, whilst the user is on the front-end having logged in over non-SSL - and as a result, their login cookies won't get sent, and they're not registered as logged in.
125
+ if (!is_admin() && substr(strtolower($ajax_url), 0, 6) == 'https:' && !is_ssl()) {
126
+ $also_try = 'http:'.substr($ajax_url, 6);
127
+ }
128
+ ?>
129
+ <script>
130
+ jQuery(function($) {
131
+
132
+ // Render any QR codes
133
+ $('.simbaotp_qr_container').qrcode({
134
+ 'render': 'image',
135
+ 'text': $('.simbaotp_qr_container:first').data('qrcode'),
136
+ });
137
+
138
+ function update_otp_code() {
139
+ $('.simba_current_otp').html('<em><?php echo esc_attr(__('Updating...', 'all-in-one-wp-security-and-firewall'));?></em>');
140
+
141
+ $.post('<?php echo esc_js($ajax_url);?>', {
142
+ action: 'simbatfa_shared_ajax',
143
+ subaction: 'refreshotp',
144
+ nonce: '<?php echo esc_js(wp_create_nonce('tfa_shared_nonce'));?>'
145
+ }, function(response) {
146
+ var got_code = '';
147
+ try {
148
+ var resp = JSON.parse(response);
149
+ got_code = resp.code;
150
+ } catch(err) {
151
+ <?php if (!isset($also_try)) { ?>
152
+ alert("<?php echo esc_js(__('Response:', 'all-in-one-wp-security-and-firewall')); ?> "+response);
153
+ <?php } ?>
154
+ console.log(response);
155
+ console.log(err);
156
+ }
157
+ <?php
158
+ if (isset($also_try)) {
159
+ ?>
160
+ $.post('<?php echo esc_js($also_try);?>', {
161
+ action: 'simbatfa_shared_ajax',
162
+ subaction: 'refreshotp',
163
+ nonce: '<?php echo esc_js(wp_create_nonce('tfa_shared_nonce'));?>'
164
+ }, function(response) {
165
+ try {
166
+ var resp = JSON.parse(response);
167
+ if (resp.code) {
168
+ $('.simba_current_otp').html(resp.code);
169
+ } else {
170
+ console.log(response);
171
+ console.log("TFA: no code found");
172
+ }
173
+ } catch(err) {
174
+ alert("<?php echo esc_js(__('Response:', 'all-in-one-wp-security-and-firewall')); ?> "+response);
175
+ console.log(response);
176
+ console.log(err);
177
+ }
178
+ });
179
+ <?php } else { ?>
180
+ if ('' != got_code) {
181
+ $('.simba_current_otp').html(got_code);
182
+ } else {
183
+ console.log("TFA: no code found");
184
+ }
185
+ <?php } ?>
186
+ });
187
+ }
188
+
189
+ var min_refresh_after = 30;
190
+
191
+ if (0 == $('body.settings_page_two-factor-auth').length) {
192
+ $('.simba_current_otp').each(function(ind, obj) {
193
+ var refresh_after = $(obj).data('refresh_after');
194
+ if (refresh_after > 0 && refresh_after < min_refresh_after) {
195
+ min_refresh_after = refresh_after;
196
+ }
197
+ });
198
+
199
+ // Update after the given seconds, and then every 30 seconds
200
+ setTimeout(function() {
201
+ setInterval(update_otp_code, 30000)
202
+ update_otp_code();
203
+ }, min_refresh_after * 1000);
204
+ }
205
+
206
+ // Handle clicks on the 'refresh' link
207
+ $('.simbaotp_refresh').on('click', function(e) {
208
+ e.preventDefault();
209
+ update_otp_code();
210
+ });
211
+
212
+ $('#tfa_trusted_devices_box').on('click', '.simbatfa-trust-remove', function(e) {
213
+ e.preventDefault();
214
+ var device_id = $(this).data('trusted-device-id');
215
+ $(this).parents('.simbatfa_trusted_device').css('opacity', '0.5');
216
+ if ('undefined' !== typeof device_id) {
217
+ $.post('<?php echo esc_js($ajax_url);?>', {
218
+ action: 'simbatfa_shared_ajax',
219
+ subaction: 'untrust_device',
220
+ nonce: '<?php echo esc_js(wp_create_nonce('tfa_shared_nonce'));?>',
221
+ device_id: device_id
222
+ }, function(response) {
223
+ var resp = JSON.parse(response);
224
+ if (resp.hasOwnProperty('trusted_list')) {
225
+ $('#tfa_trusted_devices_box_inner').html(resp.trusted_list);
226
+ }
227
+ });
228
+ }
229
+ });
230
+ });
231
+ </script>
232
+ <?php
233
+ }
234
+
235
+ /**
236
+ * Return a link to refresh the current OTP code
237
+ *
238
+ * @return String
239
+ */
240
+ public function refresh_current_otp_link() {
241
+ return '<a href="#" class="simbaotp_refresh">'.__('(update)', 'all-in-one-wp-security-and-firewall').'</a>';
242
+ }
243
+
244
+ /**
245
+ * Echo the radio buttons for changing between TOTP/HOTP
246
+ *
247
+ * TODO: Hide this choice on new installs (TOTP only)
248
+ *
249
+ * @param Integer $user_id
250
+ */
251
+ protected function print_algorithm_choice_radios($user_id) {
252
+ if (!$user_id) return;
253
+
254
+ $types = array(
255
+ 'totp' => __('TOTP (time based - most common algorithm; used by Google Authenticator)', 'all-in-one-wp-security-and-firewall'),
256
+ 'hotp' => __('HOTP (event based)', 'all-in-one-wp-security-and-firewall')
257
+ );
258
+
259
+ $setting = $this->get_user_otp_algorithm($user_id);
260
+
261
+ foreach ($types as $id => $name) {
262
+ print '<input type="radio" id="tfa_algorithm_type_'.esc_attr($id).'" name="tfa_algorithm_type" value="'.$id.'" '.($setting == $id ? 'checked="checked"' :'').'> <label for="tfa_algorithm_type_'.esc_attr($id).'">'.$name."</label><br>\n";
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Print out the advanced settings box - choice of algorithm
268
+ *
269
+ * @param Boolean|Callable $submit_button_callback - if not a callback, then <form> tags will be added
270
+ */
271
+ public function advanced_settings_box($submit_button_callback = false) {
272
+
273
+ global $current_user;
274
+ $algorithm_type = $this->get_user_otp_algorithm($current_user->ID);
275
+
276
+ ?>
277
+ <h2 id="tfa_advanced_heading" style="clear:both;"><?php _e('Advanced settings', 'all-in-one-wp-security-and-firewall'); ?></h2>
278
+
279
+ <div id="tfa_advanced_box" class="tfa_settings_form" style="margin-top: 20px;">
280
+
281
+ <?php if (false === $submit_button_callback) { ?>
282
+ <form method="post" action="<?php print esc_url(add_query_arg('settings-updated', 'true', $_SERVER['REQUEST_URI'])); ?>">
283
+ <?php wp_nonce_field('tfa_algorithm', '_tfa_algorithm_nonce', false, true); ?>
284
+ <?php } ?>
285
+
286
+ <?php _e('Choose which algorithm for One Time Passwords you want to use.', 'all-in-one-wp-security-and-firewall'); ?>
287
+ <p>
288
+ <?php
289
+ $this->print_algorithm_choice_radios($current_user->ID);
290
+ if ('hotp' == $algorithm_type) {
291
+ $counter = $this->getUserCounter($current_user->ID);
292
+ print '<br>'.__('Your counter on the server is currently on', 'all-in-one-wp-security-and-firewall').': '.$counter;
293
+ }
294
+ ?>
295
+
296
+ </p>
297
+ <?php if (false === $submit_button_callback) { submit_button(); echo '</form>'; } else { call_user_func($submit_button_callback); } ?>
298
+
299
+ </div>
300
+ <?php
301
+ }
302
+
303
+ /**
304
+ * Return an HTML snippet for the current OTP code
305
+ *
306
+ * @param Integer|Boolean $user_id
307
+ *
308
+ * @return String
309
+ */
310
+ public function current_otp_code($user_id = false) {
311
+ global $current_user;
312
+ if (false == $user_id) $user_id = $current_user->ID;
313
+ $tfa_priv_key_64 = get_user_meta($user_id, 'tfa_priv_key_64', true);
314
+ if (!$tfa_priv_key_64) $tfa_priv_key_64 = $this->addPrivateKey($user_id);
315
+ $time_now = time();
316
+ $refresh_after = 30 - ($time_now % 30);
317
+ return '<span class="simba_current_otp" data-refresh_after="'.$refresh_after.'">'.$this->generateOTP($user_id, $tfa_priv_key_64).'</span>';
318
+ }
319
+
320
+ /**
321
+ * Runs upon the WP 'init' action - check for possible private key reset request from the user
322
+ */
323
+ public function check_possible_reset() {
324
+ if (!empty($_GET['simbatfa_priv_key_reset']) && !empty($_REQUEST['nonce']) && wp_verify_nonce($_REQUEST['nonce'], 'simbatfa_reset_private_key')) {
325
+ $this->reset_private_key_and_emergency_codes();
326
+ exit;
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Remove private key and emergency codes for the specified (or logged-in) user
332
+ *
333
+ * @param Boolean|Integer $user_id - WP user ID, or false for the currently logged-in user
334
+ * @param Boolean $redirect - if this is not false, then a redirection will occur - where to depends upon the value of $_REQUEST['noredirect']
335
+ */
336
+ public function reset_private_key_and_emergency_codes($user_id = false, $redirect = true) {
337
+
338
+ if (!$user_id) {
339
+ global $current_user;
340
+ $user_id = $current_user->ID;
341
+ }
342
+
343
+ delete_user_meta($user_id, 'tfa_priv_key_64');
344
+ delete_user_meta($user_id, 'simba_tfa_emergency_codes_64');
345
+
346
+ if (!$redirect) return;
347
+
348
+ if (empty($_REQUEST['noredirect'])) {
349
+ // TODO: Re-factoring
350
+ wp_safe_redirect(admin_url('admin.php').'?page='. $this->tfa->get_user_settings_page_slug() .'&settings-updated=1');
351
+ } else {
352
+ $url = (is_ssl() ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . remove_query_arg(array('simbatfa_priv_key_reset', 'noredirect', 'nonce'));
353
+ wp_redirect(esc_url_raw($url));
354
+ }
355
+ }
356
+
357
+ /**
358
+ * Return HTML for a link to reset the current user's private key
359
+ *
360
+ * @return String
361
+ */
362
+ public function reset_link() {
363
+
364
+ // TODO: Refactoring
365
+ $url_base = is_admin() ? admin_url('admin.php').'?page='. $this->tfa->get_user_settings_page_slug() .'&settings-updated=1' : (( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST']);
366
+
367
+ $add_query_args = array('simbatfa_priv_key_reset' => 1);
368
+
369
+ if (!is_admin()) $add_query_args['noredirect'] = 1;
370
+
371
+ $url = $url_base.add_query_arg($add_query_args);
372
+
373
+ $url = wp_nonce_url($url, 'simbatfa_reset_private_key', 'nonce');
374
+
375
+ return '<a href="javascript:if(confirm(\''.__('Warning: if you reset this key you will have to update your apps with the new one. Are you sure you want this?', 'all-in-one-wp-security-and-firewall').'\')) { window.location = \''.esc_js($url).'\'; }">'.__('Reset private key', 'all-in-one-wp-security-and-firewall').'</a>';
376
+
377
+ }
378
+
379
+ /**
380
+ * Output the current codes box
381
+ *
382
+ * @param Boolean|Integer $user_id
383
+ */
384
+ public function current_codes_box($user_id = false) {
385
+
386
+ global $current_user;
387
+ if (false == $user_id) $user_id = $current_user->ID;
388
+
389
+ $admin = is_admin();
390
+
391
+ $this->add_footer();
392
+
393
+ $url = preg_replace('/^https?:\/\//i', '', site_url());
394
+
395
+ // TODO Replace this with an appropriate method
396
+ $tfa_priv_key_64 = get_user_meta($user_id, 'tfa_priv_key_64', true);
397
+ if (!$tfa_priv_key_64) $tfa_priv_key_64 = $this->addPrivateKey($user_id);
398
+
399
+ $tfa_priv_key = trim($this->getPrivateKeyPlain($tfa_priv_key_64, $user_id), "\x00..\x1F");
400
+
401
+ $tfa_priv_key_32 = Base32::encode($tfa_priv_key);
402
+
403
+ $algorithm_type = $this->get_user_otp_algorithm($user_id);
404
+
405
+ if ($admin && $current_user->ID != $user_id) {
406
+ $user = get_user_by('id', $user_id);
407
+ $user_descrip = htmlspecialchars($user->user_nicename.' - '.$user->user_email);
408
+ echo '<h2>'.sprintf(__('Current codes (login: %s)', 'all-in-one-wp-security-and-firewall'), $user_descrip).'</h2>';
409
+ }
410
+
411
+ ?>
412
+ <div class="postbox" style="clear:both;">
413
+
414
+ <?php if ($admin) { ?>
415
+ <h3 style="padding: 10px 6px 0px; margin:4px 0 0; cursor: default;">
416
+ <span style="cursor: default;"><?php echo __('Current one-time password', 'all-in-one-wp-security-and-firewall').' ';
417
+ if ($current_user->ID == $user_id) { echo $this->refresh_current_otp_link(); } ?>
418
+ </span>
419
+ <div class="inside">
420
+ <p><strong style="font-size: 3em;"><?php echo $this->current_otp_code($user_id); ?></strong></p>
421
+ </div>
422
+ </h3>
423
+ <?php } else { ?>
424
+
425
+ <div class="inside">
426
+ <p class="simbatfa-frontend-current-otp" style="font-size: 1.5em; margin-top:6px;">
427
+ <strong><?php echo __('Current one-time password', 'all-in-one-wp-security-and-firewall').' '.$this->refresh_current_otp_link(); ?></strong> :
428
+
429
+ <?php
430
+ // TODO: Compare this with what's in current_otp_code() - why are we not using that?
431
+ $time_now = time();
432
+ $refresh_after = 30 - ($time_now % 30);
433
+ ?><span class="simba_current_otp" data-refresh_after="<?php echo $refresh_after; ?>"><?php print $this->generateOTP($user_id, $tfa_priv_key_64); ?></span>
434
+
435
+ </p>
436
+ </div>
437
+
438
+ <?php } ?>
439
+
440
+ <?php if ($admin) { ?>
441
+ <h3 style="padding-left: 10px; cursor: default;">
442
+ <span style="cursor: default;"><?php _e('Setting up - either scan the code, or type in the private key', 'all-in-one-wp-security-and-firewall'); ?></span>
443
+ </h3>
444
+ <?php } else { ?>
445
+ <h2><?php _e('Setting up', 'all-in-one-wp-security-and-firewall'); ?></h2>
446
+ <?php } ?>
447
+
448
+ <div class="inside">
449
+ <p>
450
+ <?php
451
+ _e('For OTP apps that support using a camera to scan a setup code (below), that is the quickest way to set the app up (e.g. with Duo Mobile, Google Authenticator).', 'all-in-one-wp-security-and-firewall');
452
+ echo ' ';
453
+ _e('Otherwise, you can type the textual private key (shown below) into your app. Always keep private keys secret.', 'all-in-one-wp-security-and-firewall');
454
+ ?>
455
+
456
+ <?php printf(__('You are currently using %s, %s', 'all-in-one-wp-security-and-firewall'), strtoupper($algorithm_type), ($algorithm_type == 'totp') ? __('a time based algorithm', 'all-in-one-wp-security-and-firewall') : __('an event based algorithm', 'all-in-one-wp-security-and-firewall')); ?>.
457
+ </p>
458
+
459
+ <?php $qr_url = $this->tfa_qr_code_url($algorithm_type, $url, $tfa_priv_key, $user_id); ?>
460
+ <div style="float: left; padding-right: 20px;" class="simbaotp_qr_container" data-qrcode="<?php echo esc_attr($qr_url); ?>"></div>
461
+
462
+ <p>
463
+ <?php
464
+ $this->print_private_keys('full', $user_id);
465
+ if ($current_user->ID == $user_id) {
466
+ echo $this->reset_link($admin);
467
+ } else {
468
+ echo '<a id="tfa-reset-privkey-for-user" data-user_id="'.$user_id.'" href="#">'.__('Reset private key', 'all-in-one-wp-security-and-firewall').'</a>';
469
+ }
470
+ ?>
471
+ </p>
472
+
473
+ <?php
474
+ if ($admin || false !== apply_filters('simba_tfa_emergency_codes_user_settings', false, $user_id)) {
475
+ ?>
476
+
477
+ <div style="min-height: 100px;">
478
+ <h3 class="normal" style="cursor: default"><?php _e('Emergency codes', 'all-in-one-wp-security-and-firewall'); ?></h3>
479
+ <?php
480
+ $default_text = '<a href="'.esc_url($this->tfa->get_premium_version_url()).'">'.__('One-time emergency codes are a feature of the Premium version of this plugin.', 'all-in-one-wp-security-and-firewall').'</a>';
481
+ echo apply_filters('simba_tfa_emergency_codes_user_settings', $default_text, $user_id);
482
+ ?>
483
+ </div>
484
+
485
+ <?php } ?>
486
+ </div>
487
+
488
+ </div>
489
+ <?php
490
+ }
491
+
492
+ /**
493
+ * Print out HTML showing the specified user's private key
494
+ *
495
+ * @param String $type
496
+ * @param Boolean|Integer $user_id
497
+ */
498
+ public function print_private_keys($type = 'full', $user_id = false) {
499
+
500
+ global $current_user;
501
+ if ($user_id == false) $user_id = $current_user->ID;
502
+
503
+ $tfa_priv_key_64 = get_user_meta($user_id, 'tfa_priv_key_64', true);
504
+ if (!$tfa_priv_key_64) $tfa_priv_key_64 = $this->addPrivateKey($user_id);
505
+
506
+ $tfa_priv_key = trim($this->getPrivateKeyPlain($tfa_priv_key_64, $user_id), "\x00..\x1F");
507
+
508
+ $tfa_priv_key_32 = Base32::encode($tfa_priv_key);
509
+
510
+ if ('full' == $type) {
511
+ ?>
512
+ <strong><?php echo __('Private key (base 32 - used by Google Authenticator and Authy):', 'all-in-one-wp-security-and-firewall');?></strong>
513
+ <?php echo htmlspecialchars($tfa_priv_key_32); ?><br>
514
+
515
+ <strong><?php echo __('Private key:', 'all-in-one-wp-security-and-firewall');?></strong>
516
+ <?php echo htmlspecialchars($tfa_priv_key); ?><br>
517
+ <?php
518
+ } elseif ('plain' == $type) {
519
+ echo htmlspecialchars($tfa_priv_key);
520
+ } elseif ('base32' == $type) {
521
+ echo htmlspecialchars($tfa_priv_key_32);
522
+ } elseif ('base64' == $type) {
523
+ echo htmlspecialchars($tfa_priv_key_64);
524
+ }
525
+ }
526
+
527
+ /**
528
+ * Return the URL for a QR code image
529
+ *
530
+ * @param String $algorithm_type - 'totp' or 'hotp'
531
+ * @param String $url
532
+ * @param String $tfa_priv_key
533
+ * @param Boolean|Integer $user_id
534
+ *
535
+ * @return String
536
+ */
537
+ public function tfa_qr_code_url($algorithm_type, $url, $tfa_priv_key, $user_id = false) {
538
+ global $current_user;
539
+
540
+ $user = (false == $user_id) ? $current_user : get_user_by('id', $user_id);
541
+
542
+ $encode = 'otpauth://'.$algorithm_type.'/'.$url.':'.rawurlencode($user->user_login).'?secret='.Base32::encode($tfa_priv_key).'&issuer='.$url.'&counter='.$this->getUserCounter($user->ID);
543
+
544
+ return $encode;
545
+ }
546
+
547
+ /**
548
+ * See if HOTP is off sync, and if show, print out a message
549
+ */
550
+ public function tfa_show_hotp_off_sync_message() {
551
+
552
+ global $current_user;
553
+ $is_off_sync = get_user_meta($current_user->ID, 'tfa_hotp_off_sync', true);
554
+ if (!$is_off_sync) return;
555
+
556
+ ?>
557
+ <div class="error">
558
+ <h3><?php _e('Two Factor Authentication re-sync needed', 'all-in-one-wp-security-and-firewall');?></h3>
559
+ <p>
560
+ <?php _e('You need to resync your device for Two Factor Authentication since the OTP you last used is many steps ahead of the server.', 'all-in-one-wp-security-and-firewall'); ?>
561
+ <br>
562
+ <?php _e('Please re-sync or you might not be able to log in if you generate more OTPs without logging in.', 'all-in-one-wp-security-and-firewall');?>
563
+ <br><br>
564
+ <a href="<?php echo esc_url(wp_nonce_url('admin.php?page='. $this->tfa->get_user_settings_page_slug() .'&warning_button_clicked=1', 'tfaresync', 'resyncnonce')); ?>" class="button"><?php _e('Click here and re-scan the QR-Code', 'all-in-one-wp-security-and-firewall');?></a>
565
+ </p>
566
+ </div>
567
+
568
+ <?php
569
+
570
+ }
571
+
572
+ /**
573
+ * Runs upon the WP action plugins_loaded
574
+ */
575
+ public function plugins_loaded() {
576
+ $this->time_window_size = apply_filters('simbatfa_time_window_size', 30);
577
+ $this->check_back_time_windows = apply_filters('simbatfa_check_back_time_windows', 2);
578
+ $this->check_forward_time_windows = apply_filters('simbatfa_check_forward_time_windows', 1);
579
+ $this->check_forward_counter_window = apply_filters('simbatfa_check_forward_counter_window', 20);
580
+
581
+ $this->salt_prefix = defined('AUTH_SALT') ? AUTH_SALT : wp_salt('auth');
582
+ $this->pw_prefix = defined('AUTH_KEY') ? AUTH_KEY : get_site_option('auth_key');
583
+ }
584
+
585
+ /**
586
+ * Generate the current code for a specified user
587
+ *
588
+ * @param $user_id Integer - WordPress user ID
589
+ *
590
+ * @return String|Boolean - false if not set up
591
+ */
592
+ public function get_current_code($user_id) {
593
+
594
+ $tfa_priv_key_64 = get_user_meta($user_id, 'tfa_priv_key_64', true);
595
+
596
+ if (!$tfa_priv_key_64) return false;
597
+
598
+ return $this->generateOTP($user_id, $tfa_priv_key_64);
599
+
600
+ }
601
+
602
+ public function print_default_hmac_radios() {
603
+
604
+ $setting = $this->tfa->get_option('tfa_default_hmac');
605
+ if (!$setting) $setting = $this->default_hmac;
606
+
607
+ $types = array('totp' => __('TOTP (time based - most common algorithm; used by Google Authenticator)', 'all-in-one-wp-security-and-firewall'), 'hotp' => __('HOTP (event based)', 'all-in-one-wp-security-and-firewall'));
608
+
609
+ foreach ($types as $id => $name) {
610
+ print '<input type="radio" id="tfa_default_hmac_'.esc_attr($id).'" name="tfa_default_hmac" value="'.$id.'" '.($setting == $id ? 'checked="checked"' :'').'> '.'<label for="tfa_default_hmac_'.esc_attr($id).'">'."$name</label><br>\n";
611
+ }
612
+ }
613
+
614
+ public function generateOTP($user_ID, $key_b64, $length = 6, $counter = false) {
615
+
616
+ $length = $length ? (int)$length : 6;
617
+
618
+ $key = $this->decryptString($key_b64, $user_ID);
619
+ $alg = $this->get_user_otp_algorithm($user_ID);
620
+
621
+ if ('hotp' == $alg) {
622
+ $db_counter = $this->getUserCounter($user_ID);
623
+
624
+ $counter = $counter ? $counter : $db_counter;
625
+ $otp_res = $this->otp_helper->generateByCounter($key, $counter);
626
+ } else {
627
+ //time() is supposed to be UTC
628
+ $time = $counter ? $counter : time();
629
+ $otp_res = $this->otp_helper->generateByTime($key, $this->time_window_size, $time);
630
+ }
631
+ $code = $otp_res->toHotp($length);
632
+
633
+ return $code;
634
+ }
635
+
636
+ /**
637
+ * Generate a list of OTP codes based on the user, key and time window
638
+ *
639
+ * @param Integer $user_ID - user ID
640
+ * @param String $key_b64 - the user's private key, in base64 format
641
+ *
642
+ * @return Array
643
+ */
644
+ private function generate_otps_for_login_check($user_ID, $key_b64) {
645
+ $key = trim($this->decryptString($key_b64, $user_ID));
646
+ $alg = $this->get_user_otp_algorithm($user_ID);
647
+
648
+ if ('totp' == $alg) {
649
+ $otp_res = $this->otp_helper->generateByTimeWindow($key, $this->time_window_size, -1*$this->check_back_time_windows, $this->check_forward_time_windows);
650
+ } elseif ('hotp' == $alg) {
651
+
652
+ $counter = $this->getUserCounter($user_ID);
653
+
654
+ $otp_res = array();
655
+
656
+ for ($i = 0; $i < $this->check_forward_counter_window; $i++) {
657
+ $otp_res[] = $this->otp_helper->generateByCounter($key, $counter+$i);
658
+ }
659
+ }
660
+ return $otp_res;
661
+ }
662
+
663
+
664
+ /**
665
+ * Generate a private key for the user.
666
+ *
667
+ * @param Integer $user_id - WordPress user ID
668
+ * @param Boolean|String $key
669
+ *
670
+ * @return String
671
+ */
672
+ public function addPrivateKey($user_id, $key = false) {
673
+
674
+ // To work with Google Authenticator it has to be 10 bytes = 16 chars in base32
675
+ $code = $key ? $key : strtoupper($this->randString(10));
676
+
677
+ // Encrypt the key
678
+ $code = $this->encryptString($code, $user_id);
679
+
680
+ // Add private key to usermeta
681
+ update_user_meta($user_id, 'tfa_priv_key_64', $code);
682
+
683
+ $alg = $this->get_user_otp_algorithm($user_id);
684
+
685
+ // This hook is used for generation of emergency codes to accompany the key
686
+ do_action('simba_tfa_adding_private_key', $alg, $user_id, $code, $this);
687
+
688
+ $this->changeUserAlgorithmTo($user_id, $alg);
689
+
690
+ return $code;
691
+ }
692
+
693
+ /**
694
+ * Port over keys that were encrypted with mcrypt and its non-compliant padding scheme, so that if the site is ever migrated to a server without mcrypt, they can still be decrypted
695
+ */
696
+ public function potentially_port_private_keys() {
697
+
698
+ $simba_tfa_priv_key_format = get_site_option('simba_tfa_priv_key_format', false);
699
+
700
+ if ($simba_tfa_priv_key_format >= 1 || !function_exists('openssl_encrypt')) return;
701
+
702
+ $attempts = 0;
703
+ $successes = 0;
704
+
705
+ error_log("TFA: Beginning attempt to port private key encryption over to openssl");
706
+
707
+ global $wpdb;
708
+
709
+ $sql = "SELECT user_id, meta_value FROM ".$wpdb->usermeta." WHERE meta_key = 'tfa_priv_key_64'";
710
+
711
+ $user_results = $wpdb->get_results($sql);
712
+
713
+ foreach ($user_results as $u) {
714
+ $dec_openssl = $this->decryptString($u->meta_value, $u->user_id, true);
715
+
716
+ $ported = false;
717
+ if ('' == $dec_openssl) {
718
+
719
+ $attempts++;
720
+
721
+ $dec_default = $this->decryptString($u->meta_value, $u->user_id);
722
+
723
+ if ('' != $dec_default) {
724
+
725
+ $enc = $this->encryptString($dec_default, $u->user_id);
726
+
727
+ if ($enc) {
728
+
729
+ $ported = true;
730
+ $successes++;
731
+ update_user_meta($u->user_id, 'tfa_priv_key_64', $enc);
732
+ }
733
+ }
734
+
735
+ }
736
+
737
+ if ($ported) {
738
+ error_log("TFA: Successfully ported the key for user with ID ".$u->user_id." over to openssl");
739
+ } else {
740
+ error_log("TFA: Failed to port the key for user with ID ".$u->user_id." over to openssl");
741
+ }
742
+ }
743
+
744
+ if ($attempts == 0 || $successes > 0) update_site_option('simba_tfa_priv_key_format', 1);
745
+
746
+ }
747
+
748
+ public function getPrivateKeyPlain($enc, $user_ID) {
749
+ $dec = $this->decryptString($enc, $user_ID);
750
+ $this->potentially_port_private_keys();
751
+ return $dec;
752
+ }
753
+
754
+ /**
755
+ * @param Integer $user_id - WP user ID
756
+ * @param Boolean $generate_if_empty - generate some new codes if the list is empty
757
+ *
758
+ * @return String - human-usable codes, separated by ', ' (or a human-readable message, if there were none)
759
+ */
760
+ public function get_emergency_codes_as_string($user_id, $generate_if_empty = false) {
761
+
762
+ $codes = get_user_meta($user_id, 'simba_tfa_emergency_codes_64', true);
763
+ if (!is_array($codes)) $codes = array();
764
+
765
+ if ($generate_if_empty && empty($codes)) {
766
+ $tfa_priv_key = get_user_meta($user_id, 'tfa_priv_key_64', true);
767
+ $algorithm = get_user_meta($user_id, 'tfa_algorithm_type', true);
768
+ do_action('simba_tfa_emergency_codes_empty', $algorithm, $user_id, $tfa_priv_key, $this);
769
+ $codes = get_user_meta($user_id, 'simba_tfa_emergency_codes_64', true);
770
+ if (!is_array($codes)) $codes = array();
771
+ }
772
+
773
+ $emergency_str = '';
774
+
775
+ foreach ($codes as $p_code) {
776
+ $emergency_str .= $this->decryptString($p_code, $user_id).', ';
777
+ }
778
+
779
+ $emergency_str = rtrim($emergency_str, ', ');
780
+
781
+ $emergency_str = $emergency_str ? $emergency_str : '<em>'.__('There are no emergency codes left. You will need to reset your private key.', 'all-in-one-wp-security-and-firewall').'</em>';
782
+
783
+ return $emergency_str;
784
+ }
785
+
786
+ /**
787
+ * Check a code for a user (checks the code only - does not check activation status etc.)
788
+ *
789
+ * @param Integer $user_id - WP user ID
790
+ * @param String $user_code - the code to check
791
+ * @param Boolean $allow_emergency_code - whether to check against emergency codes
792
+ *
793
+ * @return Boolean
794
+ */
795
+ public function check_code_for_user($user_id, $user_code, $allow_emergency_code = true) {
796
+
797
+ $tfa_priv_key = get_user_meta($user_id, 'tfa_priv_key_64', true);
798
+ // $tfa_last_login = get_user_meta($user_id, 'tfa_last_login', true); // Unused
799
+ $tfa_last_pws_arr = get_user_meta($user_id, 'tfa_last_pws', true);
800
+ $tfa_last_pws = @$tfa_last_pws_arr ? $tfa_last_pws_arr : array();
801
+ $alg = $this->get_user_otp_algorithm($user_id);
802
+
803
+ $current_time_window = intval(time()/30);
804
+
805
+ //Give the user 1,5 minutes time span to enter/retrieve the code
806
+ //Or check $this->check_forward_counter_window number of events if hotp
807
+ $codes = $this->generate_otps_for_login_check($user_id, $tfa_priv_key);
808
+
809
+ //A recently used code was entered; that's not OK.
810
+ if (in_array($this->hash($user_code, $user_id), $tfa_last_pws)) return false;
811
+
812
+ $match = false;
813
+ foreach ($codes as $index => $code) {
814
+ if (trim($code->toHotp(6)) == trim($user_code)) {
815
+ $match = true;
816
+ $found_index = $index;
817
+ break;
818
+ }
819
+ }
820
+
821
+ // Check emergency codes
822
+ if (!$match) {
823
+ $emergency_codes = $allow_emergency_code ? get_user_meta($user_id, 'simba_tfa_emergency_codes_64', true) : array();
824
+
825
+ if (!$emergency_codes) return $match;
826
+
827
+ $dec = array();
828
+ foreach ($emergency_codes as $emergency_code)
829
+ $dec[] = trim($this->decryptString(trim($emergency_code), $user_id));
830
+
831
+ $in_array = array_search($user_code, $dec);
832
+ $match = $in_array !== false;
833
+
834
+ //Remove emergency code
835
+ if ($match) {
836
+ array_splice($emergency_codes, $in_array, 1);
837
+ update_user_meta($user_id, 'simba_tfa_emergency_codes_64', $emergency_codes);
838
+ do_action('simba_tfa_emergency_code_used', $user_id, $emergency_codes);
839
+ }
840
+
841
+ } else {
842
+ //Add the used code as well so it cant be used again
843
+ //Keep the two last codes
844
+ $tfa_last_pws[] = $this->hash($user_code, $user_id);
845
+ $nr_of_old_to_save = $alg == 'hotp' ? $this->check_forward_counter_window : $this->check_back_time_windows;
846
+
847
+ if (count($tfa_last_pws) > $nr_of_old_to_save) array_splice($tfa_last_pws, 0, 1);
848
+
849
+ update_user_meta($user_id, 'tfa_last_pws', $tfa_last_pws);
850
+ }
851
+
852
+ if ($match) {
853
+ //Save the time window when the last successful login took place
854
+ update_user_meta($user_id, 'tfa_last_login', $current_time_window);
855
+
856
+ //Update the counter if HOTP was used
857
+ if ($alg == 'hotp') {
858
+ $counter = $this->getUserCounter($user_id);
859
+
860
+ $enc_new_counter = $this->encryptString($counter+1, $user_id);
861
+ update_user_meta($user_id, 'tfa_hotp_counter', $enc_new_counter);
862
+
863
+ if ($found_index > 10) update_user_meta($user_id, 'tfa_hotp_off_sync', 1);
864
+ }
865
+ }
866
+
867
+ return $match;
868
+
869
+ }
870
+
871
+ public function getUserCounter($user_ID) {
872
+ $enc_counter = get_user_meta($user_ID, 'tfa_hotp_counter', true);
873
+ return $enc_counter ? trim($this->decryptString(trim($enc_counter), $user_ID)) : '';
874
+ }
875
+
876
+ public function changeUserAlgorithmTo($user_id, $new_algorithm) {
877
+ update_user_meta($user_id, 'tfa_algorithm_type', $new_algorithm);
878
+ delete_user_meta($user_id, 'tfa_hotp_off_sync');
879
+
880
+ $counter_start = rand(13, 999999999);
881
+ $enc_counter_start = $this->encryptString($counter_start, $user_id);
882
+
883
+ if ('hotp' == $new_algorithm) {
884
+ update_user_meta($user_id, 'tfa_hotp_counter', $enc_counter_start);
885
+ } else {
886
+ delete_user_meta($user_id, 'tfa_hotp_counter');
887
+ }
888
+ }
889
+
890
+ /**
891
+ * Whether HOTP or TOTP is being used
892
+ *
893
+ * @param Integer $user_id - WordPress user ID
894
+ *
895
+ * @return String - 'hotp' or 'totp'
896
+ */
897
+ public function get_user_otp_algorithm($user_id) {
898
+
899
+ $setting = get_user_meta($user_id, 'tfa_algorithm_type', true);
900
+
901
+ $default_hmac = $this->tfa->get_option('tfa_default_hmac');
902
+ if (!$default_hmac) $default_hmac = $this->default_hmac;
903
+
904
+ return $setting ? $setting : $default_hmac;
905
+ }
906
+
907
+ private function get_iv_size() {
908
+ // mcrypt first, for backwards compatibility
909
+ if (function_exists('mcrypt_get_iv_size')) {
910
+ return $GLOBALS['simba_two_factor_authentication']->is_mcrypt_deprecated() ? @mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC) : mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
911
+ } elseif (function_exists('openssl_cipher_iv_length')) {
912
+ return openssl_cipher_iv_length('AES-128-CBC');
913
+ }
914
+ throw new Exception('One of the mcrypt or openssl PHP modules needs to be installed');
915
+ }
916
+
917
+ private function encrypt($key, $string, $iv) {
918
+ // Prefer OpenSSL, because it uses correct padding, and its output can be decrypted by mcrypt - whereas, the converse is not true
919
+ if (function_exists('openssl_encrypt')) {
920
+ return openssl_encrypt($string, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv);
921
+ } elseif (function_exists('mcrypt_encrypt')) {
922
+ return $GLOBALS['simba_two_factor_authentication']->is_mcrypt_deprecated() ? @mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $string, MCRYPT_MODE_CBC, $iv) : mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $string, MCRYPT_MODE_CBC, $iv);
923
+ }
924
+ throw new Exception('One of the mcrypt or openssl PHP modules needs to be installed');
925
+ }
926
+
927
+ private function decrypt($key, $enc, $iv, $force_openssl = false) {
928
+ // Prefer mcrypt, because it can decrypt the output of both mcrypt_encrypt() and openssl_decrypt(), whereas (because of mcrypt_encrypt() using bad padding), the converse is not true
929
+ if (function_exists('mcrypt_decrypt') && !$force_openssl) {
930
+ return $GLOBALS['simba_two_factor_authentication']->is_mcrypt_deprecated() ? @mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $enc, MCRYPT_MODE_CBC, $iv) : mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $enc, MCRYPT_MODE_CBC, $iv);
931
+ } elseif (function_exists('openssl_decrypt')) {
932
+ $decrypted = openssl_decrypt($enc, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv);
933
+ if (false === $decrypted && !$force_openssl) {
934
+ $extra = function_exists('wp_debug_backtrace_summary') ? " backtrace: ".wp_debug_backtrace_summary() : '';
935
+ error_log("TFA decryption failure: was your site migrated to a server without mcrypt? You may need to install mcrypt, or disable TFA, in order to successfully decrypt data that was previously encrypted with mcrypt.$extra");
936
+ }
937
+ return $decrypted;
938
+ }
939
+ if ($force_openssl) return false;
940
+ throw new Exception('One of the mcrypt or openssl PHP modules needs to be installed');
941
+ }
942
+
943
+ public function encryptString($string, $salt_suffix) {
944
+ $key = $this->hashAndBin($this->pw_prefix.$salt_suffix, $this->salt_prefix.$salt_suffix);
945
+
946
+ $iv_size = $this->get_iv_size();
947
+ $iv = $GLOBALS['simba_two_factor_authentication']->random_bytes($iv_size);
948
+
949
+ $enc = $this->encrypt($key, $string, $iv);
950
+
951
+ if (false === $enc) return false;
952
+
953
+ $enc = $iv.$enc;
954
+ $enc_b64 = base64_encode($enc);
955
+ return $enc_b64;
956
+ }
957
+
958
+ private function decryptString($enc_b64, $salt_suffix, $force_openssl = false) {
959
+ $key = $this->hashAndBin($this->pw_prefix.$salt_suffix, $this->salt_prefix.$salt_suffix);
960
+
961
+ $iv_size = $this->get_iv_size();
962
+ $enc_conc = bin2hex(base64_decode($enc_b64));
963
+
964
+ $iv = hex2bin(substr($enc_conc, 0, $iv_size*2));
965
+ $enc = hex2bin(substr($enc_conc, $iv_size*2));
966
+
967
+ $string = $this->decrypt($key, $enc, $iv, $force_openssl);
968
+
969
+ // Remove padding bytes
970
+ return rtrim($string, "\x00..\x1F");
971
+ }
972
+
973
+ private function hashAndBin($pw, $salt) {
974
+ $key = $this->hash($pw, $salt);
975
+ $key = pack('H*', $key);
976
+ // Yes: it's a null encryption key. See: https://wordpress.org/support/topic/warning-mcrypt_decrypt-key-of-size-0-not-supported-by-this-algorithm-only-k?replies=5#post-6806922
977
+ // Basically: the original plugin had a bug here, which caused a null encryption key. This fails on PHP 5.6+. But, fixing it would break backwards compatibility for existing installs - and note that the only unknown once you have access to the encrypted data is the AUTH_SALT and AUTH_KEY constants... which means that actually the intended encryption was non-portable, + problematic if you lose your wp-config.php or try to migrate data to another site, or changes these values. (Normally changing these values only causes a compulsory re-log-in - but with the intended encryption in the original author's plugin, it'd actually cause a permanent lock-out until you disabled his plugin). If someone has read-access to the database, then it'd be reasonable to assume they have read-access to wp-config.php too: or at least, the number of attackers who can do one and not the other would be small. The "encryption's" not worth it.
978
+ // In summary: this isn't encryption, and is not intended to be.
979
+ return str_repeat(chr(0), 16);
980
+ }
981
+
982
+ private function hash($pw, $salt) {
983
+ //$hash = hash_pbkdf2('sha256', $pw, $salt, 10);
984
+ //$hash = crypt($pw, '$5$'.$salt.'$');
985
+ $hash = md5($salt.$pw);
986
+ return $hash;
987
+ }
988
+
989
+ private function randString($len = 10) {
990
+ $chars = '23456789QWERTYUPASDFGHJKLZXCVBNM';
991
+ $chars = str_split($chars);
992
+ shuffle($chars);
993
+ if (function_exists('random_int')) {
994
+ $code = '';
995
+ for ($i = 1; $i <= $len; $i++) {
996
+ $code .= $chars[random_int(0, count($chars)-1)];
997
+ }
998
+ } else {
999
+ $code = implode('', array_splice($chars, 0, $len));
1000
+ }
1001
+ return $code;
1002
+ }
1003
+
1004
+ public function setUserHMACTypes() {
1005
+ // We need this because we dont want to change third party apps users algorithm
1006
+ $users = get_users(array('meta_key' => 'simbatfa_delivery_type', 'meta_value' => 'third-party-apps'));
1007
+ if (empty($users)) return;
1008
+ foreach ($users as $user) {
1009
+ $tfa_algorithm_type = get_user_meta($user->ID, 'tfa_algorithm_type', true);
1010
+ if ($tfa_algorithm_type) continue;
1011
+ update_user_meta($user->ID, 'tfa_algorithm_type', $this->get_user_otp_algorithm($user->ID));
1012
+ }
1013
+ }
1014
+
1015
+ }
includes/simba-tfa/simba-tfa.php ADDED
@@ -0,0 +1,1250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if (!defined('ABSPATH')) die('Access denied.');
4
+
5
+ class Simba_Two_Factor_Authentication {
6
+
7
+ protected $frontend;
8
+
9
+ protected $totp_controller;
10
+
11
+ /**
12
+ * URL slug for the plugin's option page
13
+ *
14
+ * @var String
15
+ */
16
+ private $user_settings_page_slug;
17
+
18
+ /**
19
+ * Settings page heading for plugin's option page
20
+ *
21
+ * @var String
22
+ */
23
+ private $settings_page_heading;
24
+
25
+ /**
26
+ * Plugin translate url
27
+ *
28
+ * @var string
29
+ */
30
+ private $plugin_translate_url;
31
+
32
+ /**
33
+ * URL slug for the site-wide administration options
34
+ *
35
+ * @var String
36
+ */
37
+ private $site_wide_administration_url;
38
+
39
+ /**
40
+ * URL for the premium version
41
+ *
42
+ * @var String
43
+ */
44
+ private $premium_version_url;
45
+
46
+ /**
47
+ * URL for the FAQ
48
+ *
49
+ * @var String
50
+ */
51
+ private $faq_url;
52
+
53
+ /**
54
+ * Authentication slug. Verify that two-factor authentication should not be repeated for the same slug.
55
+ *
56
+ * @var String
57
+ */
58
+ private $authentication_slug = 'updraft';
59
+
60
+ private static $is_authenticated = array();
61
+
62
+ /**
63
+ * Class Constructor, Set basic settings.
64
+ *
65
+ * @return Void
66
+ */
67
+ public function __construct() {
68
+
69
+ require_once(__DIR__.'/providers/totp-hotp/loader.php');
70
+
71
+ $this->totp_controller = new Simba_TFA_Provider_TOTP($this);
72
+
73
+ // Process login form AJAX events
74
+ add_action('wp_ajax_nopriv_simbatfa-init-otp', array($this, 'tfaInitLogin'));
75
+ add_action('wp_ajax_simbatfa-init-otp', array($this, 'tfaInitLogin'));
76
+
77
+ add_action('wp_ajax_simbatfa_shared_ajax', array($this, 'shared_ajax'));
78
+
79
+ require_once($this->includes_dir().'/login-form-integrations.php');
80
+ new Simba_TFA_Login_Form_Integrations($this);
81
+
82
+ // Add TFA column on admin users list
83
+ add_action('manage_users_columns', array($this, 'manage_users_columns_tfa'));
84
+ add_action('wpmu_users_columns', array($this, 'manage_users_columns_tfa'));
85
+ add_action('manage_users_custom_column', array($this, 'manage_users_custom_column_tfa'), 10, 3);
86
+
87
+ // CSS for admin users screen
88
+ add_action('admin_print_styles-users.php', array($this, 'load_users_css'), 10, 0);
89
+
90
+ add_action('admin_init', array($this, 'register_two_factor_auth_settings'));
91
+ add_action('init', array($this, 'init'));
92
+
93
+ if (!defined('TWO_FACTOR_DISABLE') || !TWO_FACTOR_DISABLE) {
94
+ add_filter('authenticate', array($this, 'tfaVerifyCodeAndUser'), 99999999999, 3);
95
+ }
96
+
97
+ if (defined('DOING_AJAX') && DOING_AJAX && defined('WP_ADMIN') && WP_ADMIN && !empty($_REQUEST['action']) && 'simbatfa-init-otp' == $_REQUEST['action']) {
98
+ // Try to prevent PHP notices breaking the AJAX conversation
99
+ $this->output_buffering = true;
100
+ $this->logged = array();
101
+ set_error_handler(array($this, 'get_php_errors'), E_ALL & ~E_STRICT);
102
+ ob_start();
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Give the filesystem path to the plugin's includes directory
108
+ *
109
+ * @return String
110
+ */
111
+ public function includes_dir() {
112
+ return __DIR__.'/includes';
113
+ }
114
+
115
+ /**
116
+ * Give the URL for the plugin's includes directory
117
+ *
118
+ * @return String
119
+ */
120
+ public function includes_url() {
121
+ return plugins_url('', __FILE__).'/includes';
122
+ }
123
+
124
+ /**
125
+ * Set URL slug for the plugin's option page.
126
+ *
127
+ * @param String Setting page URL slug.
128
+ * @return Void
129
+ */
130
+ public function set_user_settings_page_slug($user_settings_page_slug) {
131
+ $this->user_settings_page_slug = $user_settings_page_slug;
132
+ }
133
+
134
+ /**
135
+ * Get URL slug for the plugin's option page.
136
+ *
137
+ * @return String Setting page URL slug.
138
+ */
139
+ public function get_user_settings_page_slug() {
140
+ return $this->user_settings_page_slug;
141
+ }
142
+
143
+ /**
144
+ * Set settings page heading for plugin's option page
145
+ *
146
+ * @param String $settings_page_heading String.
147
+ *
148
+ * @return String
149
+ */
150
+ public function set_settings_page_heading($settings_page_heading) {
151
+ $this->settings_page_heading = $settings_page_heading;
152
+ }
153
+
154
+ /**
155
+ * Get settings page heading for plugin's option page.
156
+ *
157
+ * @return String Setting page heading.
158
+ */
159
+ public function get_settings_page_heading() {
160
+ return $this->settings_page_heading;
161
+ }
162
+
163
+ /**
164
+ * Set plugin translate url
165
+ *
166
+ * @param String $plugin_translate_url Plugin translation URL.
167
+ * @return Void
168
+ */
169
+ public function set_plugin_translate_url($plugin_translate_url) {
170
+ $this->plugin_translate_url = $plugin_translate_url;
171
+ }
172
+
173
+ /**
174
+ * Get plugin translate url
175
+ *
176
+ * @return String Plugin translate URL
177
+ */
178
+ public function get_plugin_translate_url() {
179
+ return $this->plugin_translate_url;
180
+ }
181
+
182
+ /**
183
+ * Set plugin premium version url
184
+ *
185
+ * @param String $premium_version_url Plugin premium version url.
186
+ * @return Void
187
+ */
188
+ public function set_premium_version_url($premium_version_url) {
189
+ $this->premium_version_url = $premium_version_url;
190
+ }
191
+
192
+ /**
193
+ * Get plugin premium version URL.
194
+ *
195
+ * @return String Plugin premium version URL.
196
+ */
197
+ public function get_premium_version_url() {
198
+ return $this->premium_version_url;
199
+ }
200
+
201
+ /**
202
+ * Set plugin FAQ URL
203
+ *
204
+ * @param String $faq_url Plugin FAQ URL.
205
+ * @return Void
206
+ */
207
+ public function set_faq_url($faq_url) {
208
+ $this->faq_url = $faq_url;
209
+ }
210
+
211
+ /**
212
+ * Get plugin FAQ URL.
213
+ *
214
+ * @return String Plugin FAQ URL.
215
+ */
216
+ public function get_faq_url() {
217
+ return $this->faq_url;
218
+ }
219
+
220
+ /**
221
+ * Set plugin site wide administration URL
222
+ *
223
+ * @param String $site_wide_administration_url Plugin site wide administration URL.
224
+ * @return Void
225
+ */
226
+ public function set_site_wide_administration_url($site_wide_administration_url) {
227
+ $this->site_wide_administration_url = $site_wide_administration_url;
228
+ }
229
+
230
+ /**
231
+ * Get plugin site wide administration URL.
232
+ *
233
+ * @return String Plugin site wide administration URL
234
+ */
235
+ public function get_site_wide_administration_url() {
236
+ return $this->site_wide_administration_url;
237
+ }
238
+
239
+ /**
240
+ * Give the filesystem path to the plugin's templates directory
241
+ *
242
+ * @return String
243
+ */
244
+ public function templates_dir() {
245
+ return __DIR__.'/templates';
246
+ }
247
+
248
+ /**
249
+ * Include the user settings page code
250
+ */
251
+ public function show_dashboard_user_settings_page() {
252
+ $this->include_template('user-settings.php');
253
+ }
254
+
255
+ /**
256
+ * Enqueue CSS styling on the users page
257
+ */
258
+ public function load_users_css() {
259
+ wp_enqueue_style(
260
+ 'tfa-users-css',
261
+ $this->includes_url().'/users.css',
262
+ array(),
263
+ $this->version,
264
+ 'screen'
265
+ );
266
+ }
267
+
268
+ /**
269
+ * Add the 2FA label to the users list table header.
270
+ *
271
+ * @param Array $columns Table columns.
272
+ *
273
+ * @return Array
274
+ */
275
+ public function manage_users_columns_tfa($columns = array()) {
276
+ $columns['tfa-status'] = __('2FA', 'all-in-one-wp-security-and-firewall');
277
+ return $columns;
278
+ }
279
+
280
+ /**
281
+ * Add status into TFA column.
282
+ *
283
+ * @param String $value String.
284
+ * @param String $column_name Column name.
285
+ * @param Integer $user_id User ID.
286
+ *
287
+ * @return String
288
+ */
289
+ public function manage_users_custom_column_tfa($value = '', $column_name = '', $user_id = 0) {
290
+
291
+ // Only for this column name.
292
+ if ('tfa-status' === $column_name) {
293
+
294
+ if (!$this->is_activated_for_user($user_id)) {
295
+ $value = '&#8212;';
296
+ } elseif ($this->is_activated_by_user($user_id)) {
297
+ // Use value.
298
+ $value = '<span title="' . __( 'Enabled', 'all-in-one-wp-security-and-firewall' ) . '" class="dashicons dashicons-yes"></span>';
299
+ } else {
300
+ // No group.
301
+ $value = '<span title="' . __( 'Disabled', 'all-in-one-wp-security-and-firewall' ) . '" class="dashicons dashicons-no"></span>';
302
+ }
303
+ }
304
+
305
+ return $value;
306
+ }
307
+
308
+ /**
309
+ * Paint out an admin notice
310
+ *
311
+ * @param String $message - the caller should already have taken care of any escaping
312
+ * @param String $class
313
+ */
314
+ public function show_admin_warning($message, $class = 'updated') {
315
+ echo '<div class="tfamessage '.$class.'">'."<p>$message</p></div>";
316
+ }
317
+
318
+ /**
319
+ * Runs upon the WP action admin_init
320
+ */
321
+ public function register_two_factor_auth_settings() {
322
+ global $wp_roles;
323
+ if (!isset($wp_roles)) $wp_roles = new WP_Roles();
324
+
325
+ foreach ($wp_roles->role_names as $id => $name) {
326
+ register_setting('tfa_user_roles_group', 'tfa_'.$id);
327
+ register_setting('tfa_user_roles_trusted_group', 'tfa_trusted_'.$id);
328
+ register_setting('tfa_user_roles_required_group', 'tfa_required_'.$id);
329
+ }
330
+
331
+ if (is_multisite()) {
332
+ register_setting('tfa_user_roles_group', 'tfa__super_admin');
333
+ register_setting('tfa_user_roles_trusted_group', 'tfa_trusted__super_admin');
334
+ register_setting('tfa_user_roles_required_group', 'tfa_required__super_admin');
335
+ }
336
+
337
+ register_setting('tfa_user_roles_required_group', 'tfa_requireafter');
338
+ register_setting('tfa_user_roles_required_group', 'tfa_if_required_redirect_to');
339
+ register_setting('tfa_user_roles_required_group', 'tfa_hide_turn_off');
340
+ register_setting('tfa_user_roles_trusted_group', 'tfa_trusted_for');
341
+ register_setting('simba_tfa_woocommerce_group', 'tfa_wc_add_section');
342
+ register_setting('simba_tfa_woocommerce_group', 'tfa_bot_protection');
343
+ register_setting('simba_tfa_default_hmac_group', 'tfa_default_hmac');
344
+ register_setting('tfa_xmlrpc_status_group', 'tfa_xmlrpc_on');
345
+ }
346
+
347
+ /**
348
+ * See whether TFA is available or not for a particular user - i.e. whether the administrator has permitted it for their user level
349
+ *
350
+ * @param Integer $user_id - WordPress user ID
351
+ *
352
+ * @return Boolean
353
+ */
354
+ public function is_activated_for_user($user_id) {
355
+
356
+ if (empty($user_id)) return false;
357
+
358
+ // Super admin is not a role (they are admins with an extra attribute); needs separate handling
359
+ if (is_multisite() && is_super_admin($user_id)) {
360
+ // This is always a final decision - we don't want it to drop through to the 'admin' role's setting
361
+ $role = '_super_admin';
362
+ $db_val = $this->get_option('tfa_'.$role);
363
+ // Defaults to true if no setting has been saved
364
+ return (false === $db_val || $db_val) ? true : false;
365
+ }
366
+
367
+ $roles = $this->get_user_roles($user_id);
368
+
369
+ // N.B. This populates with roles on the current site within a multisite
370
+ foreach ($roles as $role) {
371
+ $db_val = $this->get_option('tfa_'.$role);
372
+ if (false === $db_val || $db_val) return true;
373
+ }
374
+
375
+ return false;
376
+
377
+ }
378
+
379
+ /**
380
+ * Get all user roles for a given user (if on multisite, amalgamates all roles from all sites)
381
+ *
382
+ * @param Integer $user_id - WordPress user ID
383
+ *
384
+ * @return Array
385
+ */
386
+ protected function get_user_roles($user_id) {
387
+
388
+ // Get roles on the main site
389
+ $user = new WP_User($user_id);
390
+ $roles = (array) $user->roles;
391
+
392
+ // On multisite, also check roles on non-main sites
393
+ if (is_multisite()) {
394
+ global $wpdb, $table_prefix;
395
+ $roles_db = $wpdb->get_results($wpdb->prepare("SELECT meta_key, meta_value FROM {$wpdb->usermeta} WHERE user_id=%d AND meta_key LIKE '".esc_sql($table_prefix)."%_capabilities'", $user_id));
396
+ if (is_array($roles_db)) {
397
+ foreach ($roles_db as $role_info) {
398
+ if (empty($role_info->meta_key) || !preg_match('/^'.$table_prefix.'\d+_capabilities$/', $role_info->meta_key) || empty($role_info->meta_value) || !preg_match('/^a:/', $role_info->meta_value)) continue;
399
+ $site_roles = unserialize($role_info->meta_value);
400
+ if (!is_array($site_roles)) continue;
401
+ foreach ($site_roles as $role => $active) {
402
+ if ($active && !in_array($role, $roles)) $roles[] = $role;
403
+ }
404
+ }
405
+ }
406
+ }
407
+
408
+ return $roles;
409
+ }
410
+
411
+ /**
412
+ * Check if TFA is required for a specified user
413
+ *
414
+ * N.B. - This doesn't check is_activated_for_user() - the caller would normally want to do that first
415
+ *
416
+ * @param $user_id Integer - the WP user ID
417
+ *
418
+ * @return Boolean
419
+ */
420
+ public function is_required_for_user($user_id) {
421
+ return apply_filters('simba_tfa_required_for_user', $this->user_property_active($user_id, 'required_'), $user_id);
422
+ }
423
+
424
+ /**
425
+ * See if a particular user property is active
426
+ *
427
+ * @param Integer $user_id
428
+ * @param String $prefix - e.g. "required_", "trusted_"
429
+ *
430
+ * @return Boolean
431
+ */
432
+ public function user_property_active($user_id, $prefix = 'required_') {
433
+
434
+ if (empty($user_id)) return false;
435
+
436
+ // Super admin is not a role (they are admins with an extra attribute); needs separate handling
437
+ if (is_multisite() && is_super_admin($user_id)) {
438
+ // This is always a final decision - we don't want it to drop through to the 'admin' role's setting
439
+ $role = '_super_admin';
440
+ $db_val = $this->get_option('tfa_'.$prefix.$role);
441
+ return $db_val ? true : false;
442
+ }
443
+
444
+ $roles = $this->get_user_roles($user_id);
445
+
446
+ foreach ($roles as $role) {
447
+ $db_val = $this->get_option('tfa_'.$prefix.$role);
448
+ if ($db_val) return true;
449
+ }
450
+
451
+ return false;
452
+
453
+ }
454
+
455
+ /**
456
+ * Whether TFA is activated by a specific user. Note that this doesn't check if TFA is enabled for the user's role; the caller should check that first.
457
+ *
458
+ * @param Integer $user_id
459
+ *
460
+ * @return Boolean
461
+ */
462
+ public function is_activated_by_user($user_id) {
463
+ $enabled = get_user_meta($user_id, 'tfa_enable_tfa', true);
464
+ return !empty($enabled);
465
+ }
466
+
467
+ /**
468
+ * Get a list of trusted devices for the user
469
+ *
470
+ * @param Integer|Boolean $user_id - WordPress user ID, or false for the current user
471
+ *
472
+ * @return Array
473
+ */
474
+ public function user_get_trusted_devices($user_id = false) {
475
+
476
+ if (false === $user_id) {
477
+ global $current_user;
478
+ $user_id = $current_user->ID;
479
+ }
480
+
481
+ $trusted_devices = get_user_meta($user_id, 'tfa_trusted_devices', true);
482
+
483
+ if (!is_array($trusted_devices)) $trusted_devices = array();
484
+
485
+ return $trusted_devices;
486
+ }
487
+
488
+ /**
489
+ * Trust the current device
490
+ *
491
+ * @param Integer $user_id - WordPress user ID
492
+ * @param Integer $trusted_for - time to trust for, in days
493
+ */
494
+ public function trust_device($user_id, $trusted_for) {
495
+
496
+ $trusted_devices = $this->user_get_trusted_devices($user_id);
497
+
498
+ $time_now = time();
499
+
500
+ foreach ($trusted_devices as $k => $device) {
501
+ if (empty($device['until']) || $device['until'] <= $time_now) unset($trusted_devices[$k]);
502
+ }
503
+
504
+ $until = $time_now + $trusted_for * 86400;
505
+
506
+ $token = bin2hex($this->random_bytes(40));
507
+
508
+ $trusted_devices[] = array(
509
+ 'ip' => $_SERVER['REMOTE_ADDR'],
510
+ 'until' => $until,
511
+ 'user_agent' => empty($_SERVER['HTTP_USER_AGENT']) ? '' : (string) $_SERVER['HTTP_USER_AGENT'],
512
+ 'token' => $token
513
+ );
514
+
515
+ $this->user_set_trusted_devices($user_id, $trusted_devices);
516
+
517
+ $this->set_cookie('simbatfa_trust_token', $token, $until);
518
+ }
519
+
520
+ /**
521
+ * Returns true if running on a PHP version on which mcrypt has been deprecated
522
+ *
523
+ * @return Boolean
524
+ */
525
+ public function is_mcrypt_deprecated() {
526
+ return (7 == PHP_MAJOR_VERSION && PHP_MINOR_VERSION >= 1);
527
+ }
528
+
529
+ /**
530
+ * Return the specified number of bytes
531
+ *
532
+ * @param Integer $bytes
533
+ *
534
+ * @throws Exception
535
+ *
536
+ * @return String
537
+ */
538
+ public function random_bytes($bytes) {
539
+ if (function_exists('random_bytes')) {
540
+ return random_bytes($bytes);
541
+ } elseif (function_exists('mcrypt_create_iv')) {
542
+ return $this->is_mcrypt_deprecated() ? @mcrypt_create_iv($bytes, MCRYPT_RAND) : mcrypt_create_iv($bytes, MCRYPT_RAND);
543
+ } elseif (function_exists('openssl_random_pseudo_bytes')) {
544
+ return openssl_random_pseudo_bytes($bytes);
545
+ }
546
+ throw new Exception('One of the mcrypt or openssl PHP modules needs to be installed');
547
+ }
548
+
549
+ /**
550
+ * Set a cookie so that, however we logged in, it can be found
551
+ *
552
+ * @param String $name - the cookie name
553
+ * @param String $value - the cookie value
554
+ * @param Integer $expires - when the cookie expires, in epoch time. Defaults to 24 hours' time. Values in the past cause cookie deletion.
555
+ */
556
+ protected function set_cookie($name, $value, $expires = null) {
557
+ if (null === $expires) $expires = time() + 86400;
558
+ $secure = is_ssl();
559
+ $secure_logged_in_cookie = ($secure && 'https' === parse_url(get_option('home'), PHP_URL_SCHEME));
560
+ $secure = apply_filters('secure_auth_cookie', $secure, get_current_user_id());
561
+ $secure_logged_in_cookie = apply_filters('secure_logged_in_cookie', $secure_logged_in_cookie, get_current_user_id(), $secure);
562
+
563
+ setcookie($name, $value, $expires, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, $secure, true);
564
+ setcookie($name, $value, $expires, COOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true);
565
+ if (COOKIEPATH != SITECOOKIEPATH) {
566
+ setcookie($name, $value, $expires, SITECOOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true);
567
+ }
568
+ }
569
+
570
+ /**
571
+ * Get a list of trusted devices for the user
572
+ *
573
+ * @param Integer $user_id - WordPress user ID
574
+ * @param Array $trusted_devices - the list of devices
575
+ */
576
+ public function user_set_trusted_devices($user_id, $trusted_devices) {
577
+ update_user_meta($user_id, 'tfa_trusted_devices', $trusted_devices);
578
+ }
579
+
580
+ /**
581
+ * Get the user capability needed for managing TFA users.
582
+ * You'll want to think carefully about changing this to a non-admin, as it can give the ability to lock admins out (though, if you have FTP/files access, you can always disable TFA or any plugin)
583
+ *
584
+ * @return String
585
+ */
586
+ public function get_management_capability() {
587
+ return apply_filters('simba_tfa_management_capability', 'manage_options');
588
+ }
589
+
590
+ /**
591
+ * Used with set_error_handler()
592
+ *
593
+ * @param Integer $errno
594
+ * @param String $errstr
595
+ * @param String $errfile
596
+ * @param Integer $errline
597
+ *
598
+ * @return Boolean
599
+ */
600
+ public function get_php_errors($errno, $errstr, $errfile, $errline) {
601
+ if (0 == error_reporting()) return true;
602
+ $logline = $this->php_error_to_logline($errno, $errstr, $errfile, $errline);
603
+ $this->logged[] = $logline;
604
+ # Don't pass it up the chain (since it's going to be output to the user always)
605
+ return true;
606
+ }
607
+
608
+ public function php_error_to_logline($errno, $errstr, $errfile, $errline) {
609
+ switch ($errno) {
610
+ case 1: $e_type = 'E_ERROR'; break;
611
+ case 2: $e_type = 'E_WARNING'; break;
612
+ case 4: $e_type = 'E_PARSE'; break;
613
+ case 8: $e_type = 'E_NOTICE'; break;
614
+ case 16: $e_type = 'E_CORE_ERROR'; break;
615
+ case 32: $e_type = 'E_CORE_WARNING'; break;
616
+ case 64: $e_type = 'E_COMPILE_ERROR'; break;
617
+ case 128: $e_type = 'E_COMPILE_WARNING'; break;
618
+ case 256: $e_type = 'E_USER_ERROR'; break;
619
+ case 512: $e_type = 'E_USER_WARNING'; break;
620
+ case 1024: $e_type = 'E_USER_NOTICE'; break;
621
+ case 2048: $e_type = 'E_STRICT'; break;
622
+ case 4096: $e_type = 'E_RECOVERABLE_ERROR'; break;
623
+ case 8192: $e_type = 'E_DEPRECATED'; break;
624
+ case 16384: $e_type = 'E_USER_DEPRECATED'; break;
625
+ case 30719: $e_type = 'E_ALL'; break;
626
+ default: $e_type = "E_UNKNOWN ($errno)"; break;
627
+ }
628
+
629
+ if (!is_string($errstr)) $errstr = serialize($errstr);
630
+
631
+ if (0 === strpos($errfile, ABSPATH)) $errfile = substr($errfile, strlen(ABSPATH));
632
+
633
+ return "PHP event: code $e_type: $errstr (line $errline, $errfile)";
634
+
635
+ }
636
+
637
+ /**
638
+ * Runs upon the WordPress 'init' action
639
+ */
640
+ public function init() {
641
+ if ((!is_admin() || (defined('DOING_AJAX') && DOING_AJAX)) && is_user_logged_in() && file_exists($this->includes_dir().'/tfa_frontend.php')) {
642
+ $this->load_frontend();
643
+ } else {
644
+ add_shortcode('twofactor_user_settings', array($this, 'shortcode_when_not_logged_in'));
645
+ }
646
+ }
647
+
648
+ /**
649
+ * Return the Simba_TFA_Provider_TOTP object.
650
+ *
651
+ * @returns Simba_TFA_Provider_TOTP
652
+ */
653
+ public function get_totp_controller() {
654
+ return $this->totp_controller;
655
+ }
656
+
657
+ /**
658
+ * "Shared" - i.e. could be called from either front-end or back-end
659
+ */
660
+ public function shared_ajax() {
661
+
662
+ if (empty($_POST['subaction']) || empty($_POST['nonce']) || !is_user_logged_in() || !wp_verify_nonce($_POST['nonce'], 'tfa_shared_nonce')) die('Security check (3).');
663
+
664
+ global $current_user;
665
+
666
+ $subaction = $_POST['subaction'];
667
+
668
+ if ('refreshotp' == $subaction) {
669
+
670
+ $code = $this->totp_controller->get_current_code($current_user->ID);
671
+
672
+ if (false === $code) die(json_encode(array('code' => '')));
673
+
674
+ die(json_encode(array('code' => $code)));
675
+
676
+ } elseif ('untrust_device' == $subaction && isset($_POST['device_id'])) {
677
+ $this->untrust_device(stripslashes($_POST['device_id']));
678
+ ob_start();
679
+ $this->include_template('trusted-devices-inner-box.php', array('trusted_devices' => $this->user_get_trusted_devices()));
680
+ echo json_encode(array('trusted_list' => ob_get_clean()));
681
+ }
682
+
683
+ exit;
684
+
685
+ }
686
+
687
+ /**
688
+ * Mark a device as untrusted for the current user
689
+ *
690
+ * @param String $device_id
691
+ */
692
+ protected function untrust_device($device_id) {
693
+
694
+ $trusted_devices = $this->user_get_trusted_devices();
695
+
696
+ unset($trusted_devices[$device_id]);
697
+
698
+ global $current_user;
699
+ $current_user_id = $current_user->ID;
700
+
701
+ $this->user_set_trusted_devices($current_user_id, $trusted_devices);
702
+
703
+ }
704
+
705
+ /**
706
+ * Called upon the AJAX action simbatfa-init-otp . Will die.
707
+ *
708
+ * Uses these keys from $_POST: user
709
+ */
710
+ public function tfaInitLogin() {
711
+
712
+ if (empty($_POST['user'])) die('Security check (2).');
713
+
714
+ if (defined('TWO_FACTOR_DISABLE') && TWO_FACTOR_DISABLE) {
715
+ $res = array('result' => false, 'user_can_trust' => false);
716
+ } else {
717
+
718
+ if (!function_exists('sanitize_user')) require_once ABSPATH.WPINC.'/formatting.php';
719
+
720
+ // WP's password-checking sanitizes the supplied user, so we must do the same to check if TFA is enabled for them
721
+ $auth_info = array('log' => sanitize_user(stripslashes((string)$_POST['user'])));
722
+
723
+ if (!empty($_COOKIE['simbatfa_trust_token'])) $auth_info['trust_token'] = (string) $_COOKIE['simbatfa_trust_token'];
724
+
725
+ $res = $this->pre_auth($auth_info, 'array');
726
+ }
727
+
728
+ $results = array(
729
+ 'jsonstarter' => 'justhere',
730
+ 'status' => $res['result'],
731
+ );
732
+
733
+ if (!empty($res['user_can_trust'])) {
734
+ $results['user_can_trust'] = 1;
735
+ if (!empty($res['user_already_trusted'])) $results['user_already_trusted'] = 1;
736
+ }
737
+
738
+
739
+ if (!empty($this->output_buffering)) {
740
+ if (!empty($this->logged)) {
741
+ $results['php_output'] = $this->logged;
742
+ }
743
+ restore_error_handler();
744
+ $buffered = ob_get_clean();
745
+ if ($buffered) $results['extra_output'] = $buffered;
746
+ }
747
+
748
+ $results = apply_filters('simbatfa_check_tfa_requirements_ajax_response', $results);
749
+
750
+ echo json_encode($results);
751
+
752
+ exit;
753
+ }
754
+
755
+ /**
756
+ * Enable or disable TFA for a user
757
+ *
758
+ * @param Integer $user_id - the WordPress user ID
759
+ * @param String $setting - either "true" (to turn on) or "false" (to turn off)
760
+ */
761
+ public function change_tfa_enabled_status($user_id, $setting) {
762
+ $previously_enabled = $this->is_activated_by_user($user_id) ? 1 : 0;
763
+ $setting = ('true' === $setting) ? 1 : 0;
764
+ update_user_meta($user_id, 'tfa_enable_tfa', $setting);
765
+ do_action('simba_tfa_activation_status_saved', $user_id, $setting, $previously_enabled, $this);
766
+ }
767
+
768
+ /**
769
+ * Here's where the login action happens. Called on the WP 'authenticate' action.
770
+ *
771
+ * @param WP_Error|WP_User $user
772
+ * @param String $username - this is not necessarily the WP username; it is whatever was typed in the form, so can be an email address
773
+ * @param String $password
774
+ *
775
+ * @return WP_Error|WP_User
776
+ */
777
+ public function tfaVerifyCodeAndUser($user, $username, $password) {
778
+ // When both AIOWPS and Two Factor Authentication plugins are active, this function called more than once, To prevent it, this code is written.
779
+ if (isset(self::$is_authenticated[$this->authentication_slug]) && self::$is_authenticated[$this->authentication_slug]) {
780
+ return $user;
781
+ }
782
+
783
+ $original_user = $user;
784
+ $params = stripslashes_deep($_POST);
785
+
786
+ // If (only) the error was a wrong password, but it looks like the user appended a TFA code to their password, then have another go
787
+ if (is_wp_error($user) && array('incorrect_password') == $user->get_error_codes() && !isset($params['two_factor_code']) && false !== ($from_password = apply_filters('simba_tfa_tfa_from_password', false, $password))) {
788
+ // This forces a new password authentication below
789
+ $user = false;
790
+ }
791
+
792
+ if (is_wp_error($user)) {
793
+ $ret = $user;
794
+ } else {
795
+
796
+ if (is_object($user) && isset($user->ID) && isset($user->user_login)) {
797
+ $params['log'] = $user->user_login;
798
+ // Confirm that this is definitely a username regardless of its format
799
+ $may_be_email = false;
800
+ } else {
801
+ $params['log'] = $username;
802
+ $may_be_email = true;
803
+ }
804
+
805
+ $params['caller'] = $_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : $_SERVER['REQUEST_URI'];
806
+ if (!empty($_COOKIE['simbatfa_trust_token'])) $params['trust_token'] = (string) $_COOKIE['simbatfa_trust_token'];
807
+
808
+ if (isset($from_password) && false !== $from_password) {
809
+ // Support login forms that can't be hooked via appending to the password
810
+ $speculatively_try_appendage = true;
811
+ $params['two_factor_code'] = $from_password['tfa_code'];
812
+ }
813
+
814
+ $code_ok = $this->authorise_user_from_login($params, $may_be_email);
815
+
816
+ if (is_wp_error($code_ok)) {
817
+ $ret = $code_ok;
818
+ } elseif (!$code_ok) {
819
+ $ret = new WP_Error('authentication_failed', '<strong>'.__('Error:', 'all-in-one-wp-security-and-firewall').'</strong> '.__('The one-time password (TFA code) you entered was incorrect.', 'all-in-one-wp-security-and-firewall'));
820
+ } elseif ($user) {
821
+ $ret = $user;
822
+ } else {
823
+
824
+ if (!empty($speculatively_try_appendage) && true === $code_ok) {
825
+ $password = $from_password['password'];
826
+ }
827
+
828
+ $username_is_email = false;
829
+
830
+ if (function_exists('wp_authenticate_username_password') && $may_be_email && filter_var($username, FILTER_VALIDATE_EMAIL)) {
831
+ global $wpdb;
832
+ // This has to match self::authorise_user_from_login()
833
+ $response = $wpdb->get_row($wpdb->prepare("SELECT ID, user_registered from ".$wpdb->users." WHERE user_email=%s", $username));
834
+ if (is_object($response)) $username_is_email = true;
835
+ }
836
+
837
+ $ret = $username_is_email ? wp_authenticate_email_password(null, $username, $password) : wp_authenticate_username_password(null, $username, $password);
838
+ }
839
+
840
+ }
841
+
842
+ $ret = apply_filters('simbatfa_verify_code_and_user_result', $ret, $original_user, $username, $password);
843
+
844
+ // If the TFA code was actually validated (not just not required, for example), then $code_ok is (boolean)true
845
+ if (isset($code_ok) && true === $code_ok && is_a($ret, 'WP_User')) {
846
+ if (!empty($params['simba_tfa_mark_as_trusted']) && $this->user_can_trust($ret->ID) && (is_ssl() || (!empty($_SERVER['SERVER_NAME']) && ('localhost' == $_SERVER['SERVER_NAME'] ||'127.0.0.1' == $_SERVER['SERVER_NAME'])))) {
847
+
848
+ $trusted_for = $this->get_option('tfa_trusted_for');
849
+ $trusted_for = (false === $trusted_for) ? 30 : (string) absint($trusted_for);
850
+
851
+ $this->trust_device($ret->ID, $trusted_for);
852
+ }
853
+ }
854
+
855
+ self::$is_authenticated[$this->authentication_slug] = true;
856
+
857
+ return $ret;
858
+ }
859
+
860
+ // N.B. - This doesn't check is_activated_for_user() - the caller would normally want to do that first
861
+ public function user_can_trust($user_id) {
862
+ // Default is false because this is a new feature and we don't want to surprise existing users by granting broader access than they expected upon an upgrade
863
+ return apply_filters('simba_tfa_user_can_trust', false, $user_id);
864
+ }
865
+
866
+ /**
867
+ * Should the user be asked for a TFA code? And optionally, is the user allowed to trust devices?
868
+ *
869
+ * @param Array $params - the key used is 'log', indicating the username or email address
870
+ * @param String $response_format - 'simple' (historic format) or 'array' (richer info)
871
+ *
872
+ * @return Boolean
873
+ */
874
+ public function pre_auth($params, $response_format = 'simple') {
875
+ global $wpdb;
876
+
877
+ $query = filter_var($params['log'], FILTER_VALIDATE_EMAIL) ? $wpdb->prepare("SELECT ID, user_email from ".$wpdb->users." WHERE user_email=%s", $params['log']) : $wpdb->prepare("SELECT ID, user_email from ".$wpdb->users." WHERE user_login=%s", $params['log']);
878
+ $user = $wpdb->get_row($query);
879
+
880
+ if (!$user && filter_var($params['log'], FILTER_VALIDATE_EMAIL)) {
881
+ // Corner-case: login looks like an email, but is a username rather than email address
882
+ $user = $wpdb->get_row($wpdb->prepare("SELECT ID, user_email from ".$wpdb->users." WHERE user_login=%s", $params['log']));
883
+ }
884
+
885
+ $is_activated_for_user = true;
886
+ $is_activated_by_user = false;
887
+
888
+ $result = false;
889
+
890
+ $totp_controller = $this->totp_controller;
891
+
892
+ if ($user) {
893
+ $tfa_priv_key = get_user_meta($user->ID, 'tfa_priv_key_64', true);
894
+ $is_activated_for_user = $this->is_activated_for_user($user->ID);
895
+ $is_activated_by_user = $this->is_activated_by_user($user->ID);
896
+
897
+ if ($is_activated_for_user && $is_activated_by_user) {
898
+
899
+ // No private key yet, generate one. This shouldn't really be possible.
900
+ if (!$tfa_priv_key) $tfa_priv_key = $totp_controller->addPrivateKey($user->ID);
901
+
902
+ $code = $totp_controller->generateOTP($user->ID, $tfa_priv_key);
903
+
904
+ $result = true;
905
+ }
906
+ }
907
+
908
+ if ('array' != $response_format) return $result;
909
+
910
+ $ret = array('result' => $result);
911
+
912
+ if ($result) {
913
+ $ret['user_can_trust'] = $this->user_can_trust($user->ID);
914
+ if (!empty($params['trust_token']) && $this->user_trust_token_valid($user->ID, $params['trust_token'])) {
915
+ $ret['user_already_trusted'] = 1;
916
+ }
917
+ }
918
+
919
+ return $ret;
920
+ }
921
+
922
+ /**
923
+ * Print the radio buttons for enabling/disabling TFA
924
+ *
925
+ * @param Integer $user_id - the WordPress user ID
926
+ * @param Boolean $long_label - whether to use a long label rather than a short one
927
+ * @param String $style - valid values are "show_current" and "require_current"
928
+ */
929
+ public function paint_enable_tfa_radios($user_id, $long_label = false, $style = 'show_current') {
930
+
931
+ if (!$user_id) return;
932
+
933
+ if ('require_current' != $style) $style = 'show_current';
934
+
935
+ $is_required = $this->is_required_for_user($user_id);
936
+ $is_activated = $this->is_activated_by_user($user_id);
937
+
938
+ if ($is_required) {
939
+ $require_after = absint($this->get_option('tfa_requireafter'));
940
+ echo '<p class="tfa_required_warning" style="font-weight:bold; font-style:italic;">'.sprintf(__('N.B. This site is configured to forbid you to log in if you disable two-factor authentication after your account is %d days old', 'all-in-one-wp-security-and-firewall'), $require_after).'</p>';
941
+ }
942
+
943
+ $tfa_enabled_label = $long_label ? __('Enable two-factor authentication', 'all-in-one-wp-security-and-firewall') : __('Enabled', 'all-in-one-wp-security-and-firewall');
944
+
945
+ if ('show_current' == $style) {
946
+ $tfa_enabled_label .= ' '.sprintf(__('(Current code: %s)', 'all-in-one-wp-security-and-firewall'), $this->get_totp_controller()->current_otp_code($user_id));
947
+ } elseif ('require_current' == $style) {
948
+ $tfa_enabled_label .= ' '.sprintf(__('(you must enter the current code: %s)', 'all-in-one-wp-security-and-firewall'), '<input type="text" class="tfa_enable_current" name="tfa_enable_current" size="6" style="height">');
949
+ }
950
+
951
+ $show_disable = ((is_multisite() && is_super_admin()) || (!is_multisite() && current_user_can($this->get_management_capability())) || false == $is_activated || !$is_required || !$this->get_option('tfa_hide_turn_off')) ? true : false;
952
+
953
+ $tfa_disabled_label = $long_label ? __('Disable two-factor authentication', 'all-in-one-wp-security-and-firewall') : __('Disabled', 'all-in-one-wp-security-and-firewall');
954
+
955
+ if ('require_current' == $style) echo '<input type="hidden" name="require_current" value="1">'."\n";
956
+
957
+ echo '<input type="radio" class="tfa_enable_radio" id="tfa_enable_tfa_true" name="tfa_enable_tfa" value="true" '.(true == $is_activated ? 'checked="checked"' : '').'> <label class="tfa_enable_radio_label" for="tfa_enable_tfa_true">'.apply_filters('simbatfa_radiolabel_enabled', $tfa_enabled_label, $long_label).'</label> <br>';
958
+
959
+ // Show the 'disabled' option if the user is an admin, or if it is currently set, or if TFA is not compulsory, or if the site owner doesn't require it to be hidden
960
+ // Note that this just hides the option in the UI. The user could POST to turn off TFA, but, since it's required, they won't be able to log in.
961
+ if ($show_disable) {
962
+ echo '<input type="radio" class="tfa_enable_radio" id="tfa_enable_tfa_false" name="tfa_enable_tfa" value="false" '.(false == $is_activated ? 'checked="checked"' :'').'> <label class="tfa_enable_radio_label" for="tfa_enable_tfa_false">'.apply_filters('simbatfa_radiolabel_disabled', $tfa_disabled_label, $long_label).'</label> <br>';
963
+ }
964
+ }
965
+
966
+ /**
967
+ * Retrieve a saved option
968
+ *
969
+ * @param String $key - option key
970
+ *
971
+ * @return Mixed
972
+ */
973
+ public function get_option($key) {
974
+ if (!is_multisite()) return get_option($key);
975
+ $main_site_id = function_exists('get_main_site_id') ? get_main_site_id() : 1;
976
+ $get_option_site_id = apply_filters('simba_tfa_get_option_site_id', $main_site_id);
977
+ switch_to_blog($get_option_site_id);
978
+ $value = get_option($key);
979
+ restore_current_blog();
980
+ return $value;
981
+ }
982
+
983
+ /**
984
+ * Paint a list of checkboxes, one for each role
985
+ *
986
+ * @param String $prefix
987
+ * @param Integer $default - default value (0 or 1)
988
+ */
989
+ public function list_user_roles_checkboxes($prefix = '', $default = 1) {
990
+ if (is_multisite()) {
991
+ // Not a real WP role; needs separate handling
992
+ $id = '_super_admin';
993
+ $name = __('Multisite Super Admin', 'all-in-one-wp-security-and-firewall');
994
+ $setting = $this->get_option('tfa_'.$prefix.$id);
995
+ $setting = ($setting === false) ? $default : ($setting ? 1 : 0);
996
+
997
+ echo '<input type="checkbox" id="tfa_'.$prefix.$id.'" name="tfa_'.$prefix.$id.'" value="1" '.($setting ? 'checked="checked"' :'').'> <label for="tfa_'.$prefix.$id.'">'.htmlspecialchars($name)."</label><br>\n";
998
+ }
999
+
1000
+ global $wp_roles;
1001
+ if (!isset($wp_roles)) $wp_roles = new WP_Roles();
1002
+
1003
+ foreach ($wp_roles->role_names as $id => $name) {
1004
+ $setting = $this->get_option('tfa_'.$prefix.$id);
1005
+ $setting = ($setting === false) ? $default : ($setting ? 1 : 0);
1006
+
1007
+ echo '<input type="checkbox" id="tfa_'.$prefix.$id.'" name="tfa_'.$prefix.$id.'" value="1" '.($setting ? 'checked="checked"' :'').'> <label for="tfa_'.$prefix.$id.'">'.htmlspecialchars($name)."</label><br>\n";
1008
+ }
1009
+
1010
+ }
1011
+
1012
+ public function tfa_list_xmlrpc_status_radios() {
1013
+
1014
+ $setting = $this->get_option('tfa_xmlrpc_on');
1015
+ $setting = $setting ? 1 : 0;
1016
+
1017
+ $types = array(
1018
+ '0' => __('Do not require 2FA over XMLRPC (best option if you must use XMLRPC and your client does not support 2FA)', 'all-in-one-wp-security-and-firewall'),
1019
+ '1' => __('Do require 2FA over XMLRPC (best option if you do not use XMLRPC or are unsure)', 'all-in-one-wp-security-and-firewall')
1020
+ );
1021
+
1022
+ foreach($types as $id => $name) {
1023
+ print '<input type="radio" name="tfa_xmlrpc_on" id="tfa_xmlrpc_on_'.$id.'" value="'.$id.'" '.($setting == $id ? 'checked="checked"' : '').'> <label for="tfa_xmlrpc_on_'.$id.'">'.htmlspecialchars($name)."</label><br>\n";
1024
+ }
1025
+ }
1026
+
1027
+ protected function is_caller_active() {
1028
+
1029
+ if (!defined('XMLRPC_REQUEST') || !XMLRPC_REQUEST) return true;
1030
+
1031
+ $saved_data = $this->get_option('tfa_xmlrpc_on');
1032
+
1033
+ return $saved_data ? true : false;
1034
+
1035
+ }
1036
+
1037
+ /**
1038
+ * @param Array $params
1039
+ * @param Boolean $may_be_email
1040
+ *
1041
+ * @return WP_Error|Boolean|Integer - WP_Error or false means failure; true or 1 means success, but true means the TFA code was validated
1042
+ */
1043
+ public function authorise_user_from_login($params, $may_be_email = false) {
1044
+
1045
+ $params = apply_filters('simbatfa_auth_user_from_login_params', $params);
1046
+
1047
+ global $wpdb;
1048
+
1049
+ if (!$this->is_caller_active()) return 1;
1050
+
1051
+ $query = ($may_be_email && filter_var($params['log'], FILTER_VALIDATE_EMAIL)) ? $wpdb->prepare("SELECT ID, user_registered from ".$wpdb->users." WHERE user_email=%s", $params['log']) : $wpdb->prepare("SELECT ID, user_registered from ".$wpdb->users." WHERE user_login=%s", $params['log']);
1052
+ $response = $wpdb->get_row($query);
1053
+
1054
+ if (!$response && $may_be_email && filter_var($params['log'], FILTER_VALIDATE_EMAIL)) {
1055
+ // Corner-case: login looks like an email, but is a username rather than email address
1056
+ $response = $wpdb->get_row($wpdb->prepare("SELECT ID, user_registered from ".$wpdb->users." WHERE user_login=%s", $params['log']));
1057
+ }
1058
+
1059
+ $user_ID = is_object($response) ? $response->ID : false;
1060
+ $user_registered = is_object($response) ? $response->user_registered : false;
1061
+
1062
+ $user_code = isset($params['two_factor_code']) ? str_replace(' ', '', trim($params['two_factor_code'])) : '';
1063
+
1064
+ // This condition in theory should not be possible
1065
+ if (!$user_ID) return new WP_Error('tfa_user_not_found', apply_filters('simbatfa_tfa_user_not_found', '<strong>'.__('Error:', 'all-in-one-wp-security-and-firewall').'</strong> '.__('The indicated user could not be found.', 'all-in-one-wp-security-and-firewall')));
1066
+
1067
+ if (!$this->is_activated_for_user($user_ID)) return 1;
1068
+
1069
+ if (!empty($params['trust_token']) && $this->user_trust_token_valid($user_ID, $params['trust_token'])) {
1070
+ return 1;
1071
+ }
1072
+
1073
+ if (!$this->is_activated_by_user($user_ID)) {
1074
+
1075
+ if (!$this->is_required_for_user($user_ID)) return 1;
1076
+
1077
+ $require_after = absint($this->get_option('tfa_requireafter')) * 86400;
1078
+
1079
+ $account_age = time() - strtotime($user_registered);
1080
+
1081
+ if ($account_age > $require_after && apply_filters('simbatfa_enforce_require_after_check', true, $user_ID, $require_after, $account_age)) {
1082
+ return new WP_Error('tfa_required', apply_filters('simbatfa_notfa_forbidden_login', '<strong>'.__('Error:', 'all-in-one-wp-security-and-firewall').'</strong> '.__('The site owner has forbidden you to login without two-factor authentication. Please contact the site owner to re-gain access.', 'all-in-one-wp-security-and-firewall')));
1083
+ }
1084
+
1085
+ return 1;
1086
+ }
1087
+
1088
+ $tfa_creds_user_id = !empty($params['creds_user_id']) ? $params['creds_user_id'] : $user_ID;
1089
+
1090
+ if ($tfa_creds_user_id != $user_ID) {
1091
+
1092
+ // Authenticating using a different user's credentials (e.g. https://wordpress.org/plugins/use-administrator-password/)
1093
+ // In this case, we require that different user to have TFA active - so that this mechanism can't be used to avoid TFA
1094
+
1095
+ if (!$this->is_activated_for_user($tfa_creds_user_id) || !$this->is_activated_by_user($tfa_creds_user_id)) {
1096
+ return new WP_Error('tfa_required', apply_filters('simbatfa_notfa_forbidden_login_altuser', '<strong>'.__('Error:', 'all-in-one-wp-security-and-firewall').'</strong> '.__('You are attempting to log in to an account that has two-factor authentication enabled; this requires you to also have two-factor authentication enabled on the account whose credentials you are using.', 'all-in-one-wp-security-and-firewall')));
1097
+ }
1098
+
1099
+ }
1100
+
1101
+ return $this->totp_controller->check_code_for_user($tfa_creds_user_id, $user_code);
1102
+
1103
+ }
1104
+
1105
+ /**
1106
+ * Evaluate whether a trust token is valid for a user
1107
+ *
1108
+ * @param Integer $user_id - WP user ID
1109
+ * @param String $trust_token - trust token
1110
+ *
1111
+ * @return Boolean
1112
+ */
1113
+ protected function user_trust_token_valid($user_id, $trust_token) {
1114
+
1115
+ if (!is_string($trust_token) || strlen($trust_token) < 30) return false;
1116
+
1117
+ $trusted_devices = $this->user_get_trusted_devices($user_id);
1118
+
1119
+ $time_now = time();
1120
+
1121
+ foreach ($trusted_devices as $device) {
1122
+ if (empty($device['until']) || $device['until'] <= $time_now) continue;
1123
+ if (!empty($device['token']) && $device['token'] === $trust_token) {
1124
+ return true;
1125
+ }
1126
+ }
1127
+
1128
+ return false;
1129
+ }
1130
+
1131
+ /**
1132
+ * This deals with the issue that wp-login.php does not redirect to a canonical URL. As a result, if a website is available under more than one host, then admin_url('admin-ajax.php') might return a different one than the visitor is using, resulting in AJAX failing due to CORS errors.
1133
+ *
1134
+ * @return String
1135
+ */
1136
+ protected function get_ajax_url() {
1137
+ $ajax_url = admin_url('admin-ajax.php');
1138
+ $parsed_url = parse_url($ajax_url);
1139
+ if (strtolower($parsed_url['host']) !== strtolower($_SERVER['HTTP_HOST']) && !empty($parsed_url['path'])) {
1140
+ // Mismatch - return the relative URL only
1141
+ $ajax_url = $parsed_url['path'];
1142
+ }
1143
+ return $ajax_url;
1144
+ }
1145
+
1146
+ /**
1147
+ * Called not only upon the WP action login_enqueue_scripts, but potentially upon the action 'init' and various others from other plugins too. It can handle being called multiple times.
1148
+ */
1149
+ public function login_enqueue_scripts() {
1150
+
1151
+ if (isset($_GET['action']) && 'logout ' != $_GET['action'] && 'login' != $_GET['action']) return;
1152
+
1153
+ static $already_done = false;
1154
+ if ($already_done) return;
1155
+ $already_done = true;
1156
+
1157
+ // Prevent cacheing when in debug mode
1158
+ $script_ver = (defined('WP_DEBUG') && WP_DEBUG) ? time() : filemtime($this->includes_dir().'/tfa.js');
1159
+
1160
+ wp_enqueue_script('tfa-ajax-request', $this->includes_url().'/tfa.js', array('jquery'), $script_ver);
1161
+
1162
+ $trusted_for = $this->get_option('tfa_trusted_for');
1163
+ $trusted_for = (false === $trusted_for) ? 30 : (string) absint($trusted_for);
1164
+
1165
+ $localize = array(
1166
+ 'ajaxurl' => $this->get_ajax_url(),
1167
+ 'click_to_enter_otp' => __("Click to enter One Time Password", 'all-in-one-wp-security-and-firewall'),
1168
+ 'enter_username_first' => __('You have to enter a username first.', 'all-in-one-wp-security-and-firewall'),
1169
+ 'otp' => __('One Time Password (i.e. 2FA)', 'all-in-one-wp-security-and-firewall'),
1170
+ 'otp_login_help' => __('(check your OTP app to get this password)', 'all-in-one-wp-security-and-firewall'),
1171
+ 'mark_as_trusted' => sprintf(_n('Trust this device (allow login without 2FA for %d day)', 'Trust this device (allow login without TFA for %d days)', $trusted_for, 'all-in-one-wp-security-and-firewall'), $trusted_for),
1172
+ 'is_trusted' => __('(Trusted device)', 'all-in-one-wp-security-and-firewall'),
1173
+ 'nonce' => wp_create_nonce('simba_tfa_loginform_nonce'),
1174
+ 'login_form_selectors' => '',
1175
+ 'login_form_off_selectors' => '',
1176
+ );
1177
+
1178
+ // Spinner exists since WC 3.8. Use the proper functions to avoid SSL warnings.
1179
+ if (file_exists(ABSPATH.'wp-admin/images/spinner-2x.gif')) {
1180
+ $localize['spinnerimg'] = admin_url('images/spinner-2x.gif');
1181
+ } elseif (file_exists(ABSPATH.WPINC.'/images/spinner-2x.gif')) {
1182
+ $localize['spinnerimg'] = includes_url('images/spinner-2x.gif');
1183
+ }
1184
+
1185
+ $localize = apply_filters('simba_tfa_login_enqueue_localize', $localize);
1186
+
1187
+ wp_localize_script('tfa-ajax-request', 'simba_tfasettings', $localize);
1188
+
1189
+ }
1190
+
1191
+ /**
1192
+ * Return or output view content
1193
+ *
1194
+ * @param String $path - path to template, usually relative to templates/ within the plugin directory
1195
+ * @param Array $extract_these - key/value pairs for substitution into the scope of the template
1196
+ * @param Boolean $return_instead_of_echo - what to do with the results
1197
+ *
1198
+ * @return String|Void
1199
+ */
1200
+ public function include_template($path, $extract_these = array(), $return_instead_of_echo = false) {
1201
+
1202
+ if ($return_instead_of_echo) ob_start();
1203
+
1204
+ $template_file = apply_filters('simatfa_template_file', $this->templates_dir().'/'.$path, $path, $extract_these, $return_instead_of_echo);
1205
+
1206
+ do_action('simbatfa_before_template', $path, $return_instead_of_echo, $extract_these, $template_file);
1207
+
1208
+ if (!file_exists($template_file)) {
1209
+ error_log("TFA: template not found: $template_file (from $path)");
1210
+ echo __('Error:', 'all-in-one-wp-security-and-firewall').' '.__('two-factor-authentication', 'wp-optimize')." (".$path.")";
1211
+ } else {
1212
+ extract($extract_these);
1213
+ // The following are useful variables which can be used in the template.
1214
+ // They appear as unused, but may be used in the $template_file.
1215
+ $wpdb = $GLOBALS['wpdb'];// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $wpdb might be used in the included template
1216
+ $simba_tfa = $this;// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $wp_optimize might be used in the included template
1217
+ include $template_file;
1218
+ }
1219
+
1220
+ do_action('simbatfa_after_template', $path, $return_instead_of_echo, $extract_these, $template_file);
1221
+
1222
+ if ($return_instead_of_echo) return ob_get_clean();
1223
+ }
1224
+
1225
+ /**
1226
+ * Make sure that self::$frontend is the instance of Simba_TFA_Frontend, and return it
1227
+ *
1228
+ * @return Simba_TFA_Frontend
1229
+ */
1230
+ public function load_frontend() {
1231
+ if (!class_exists('Simba_TFA_Frontend')) require_once($this->includes_dir().'/tfa_frontend.php');
1232
+ if (empty($this->frontend)) $this->frontend = new Simba_TFA_Frontend($this);
1233
+ return $this->frontend;
1234
+ }
1235
+
1236
+ // __return_empty_string() does not exist until WP 3.7
1237
+ public function shortcode_when_not_logged_in() {
1238
+ return '';
1239
+ }
1240
+
1241
+ /**
1242
+ * Set authentication slug.
1243
+ *
1244
+ * @param String $authentication_slug - Authentication slug. Verify that two-factor authentication should not be repeated for the same slug.
1245
+ */
1246
+ public function set_authentication_slug($authentication_slug) {
1247
+ $this->authentication_slug = $authentication_slug;
1248
+ }
1249
+
1250
+ }
includes/simba-tfa/templates/admin-settings.php ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if (!defined('ABSPATH')) die('Access denied.');
4
+
5
+ ?><div class="wrap">
6
+
7
+ <div>
8
+ <h1><?php echo esc_html(empty($settings_page_heading) ? __('Two Factor Authentication - Admin Settings', 'all-in-one-wp-security-and-firewall') : $settings_page_heading); ?></h1>
9
+ <?php
10
+ if (!empty($admin_settings_links) && is_array($admin_settings_links)) {
11
+ echo implode(' | ', array_map(function($val) {
12
+ return '<a href="'.esc_url($val['url']).'">' . esc_html($val['title']) . '</a>';
13
+ }, $admin_settings_links));
14
+ echo '<br>';
15
+ }
16
+ ?>
17
+ </div>
18
+
19
+ <?php if (defined('TWO_FACTOR_DISABLE') && TWO_FACTOR_DISABLE) { ?>
20
+ <div class="error">
21
+ <h3><?php _e('Two Factor Authentication currently disabled', 'all-in-one-wp-security-and-firewall');?></h3>
22
+ <p>
23
+ <?php _e('Two factor authentication is currently disabled via the TWO_FACTOR_DISABLE constant (which is mostly likely to be defined in your wp-config.php)', 'all-in-one-wp-security-and-firewall'); ?>
24
+ </p>
25
+ </div>
26
+ <?php } ?>
27
+
28
+ <div style="max-width:800px;">
29
+
30
+ <?php
31
+ if (is_multisite()) {
32
+ if (is_super_admin()) {
33
+ ?>
34
+ <p style="font-size: 120%; font-weight: bold;">
35
+ <?php _e('N.B. These two-factor settings apply to your entire WordPress network. (i.e. They are not localised to one particular site).', 'all-in-one-wp-security-and-firewall');?>
36
+ </p>
37
+ <?php
38
+ } else {
39
+ // Should not be possible to reach this; but an extra check does not hurt.
40
+ die('Security check');
41
+ }
42
+ }
43
+ ?>
44
+
45
+ <form method="post" action="options.php" style="margin-top: 12px">
46
+ <?php settings_fields('tfa_user_roles_group'); ?>
47
+ <h2><?php _e('User roles', 'all-in-one-wp-security-and-firewall'); ?></h2>
48
+ <?php _e('Choose which user roles will have two factor authentication available.', 'all-in-one-wp-security-and-firewall'); ?>
49
+ <p>
50
+ <?php $simba_tfa->list_user_roles_checkboxes(); ?>
51
+ </p>
52
+ <?php submit_button(); ?>
53
+ </form>
54
+
55
+ <hr>
56
+
57
+ <div class="tfa-premium">
58
+ <h2><?php _e('Make two factor authentication compulsory', 'all-in-one-wp-security-and-firewall'); ?></h2>
59
+
60
+ <?php
61
+
62
+ $output = '<p><a href="'.esc_url($simba_tfa->get_premium_version_url()).'">'.__('Requiring users to use two-factor authentication is a feature of the Premium version of this plugin.', 'all-in-one-wp-security-and-firewall').'</a><p>';
63
+ echo apply_filters('simba_tfa_after_user_roles', $output);
64
+
65
+ ?>
66
+
67
+ <hr>
68
+ <h2><?php _e('Trusted devices', 'all-in-one-wp-security-and-firewall'); ?></h2>
69
+
70
+ <form method="post" action="options.php" style="margin-top: 12px">
71
+ <?php settings_fields('tfa_user_roles_trusted_group'); ?>
72
+ <?php _e('Choose which user roles are permitted to mark devices they login on as trusted. This feature requires browser cookies and an https (i.e. SSL) connection to the website to work.', 'all-in-one-wp-security-and-firewall'); ?>
73
+
74
+ <?php
75
+ $output = '<p><a href="'.esc_url($simba_tfa->get_premium_version_url()).'">'.__('Allowing users to mark a device as trusted so that a two-factor code is only needed once in a specified number of days (instead of every login) is a feature of the Premium version of this plugin.', 'all-in-one-wp-security-and-firewall').'</a><p>';
76
+ echo apply_filters('simba_tfa_trusted_devices_config', $output);
77
+ ?>
78
+ </form>
79
+ </div>
80
+ </form>
81
+
82
+ <div>
83
+ <hr>
84
+ <form method="post" action="options.php" style="margin-top: 40px">
85
+ <?php
86
+ settings_fields('tfa_xmlrpc_status_group');
87
+ ?>
88
+ <h2><?php _e('XMLRPC requests', 'all-in-one-wp-security-and-firewall'); ?></h2>
89
+ <?php
90
+
91
+ echo '<p>';
92
+ echo __("XMLRPC is a feature within WordPress allowing other computers to talk to your WordPress install. For example, it could be used by an app on your tablet that allows you to blog directly from the app (instead of needing the WordPress dashboard).", 'all-in-one-wp-security-and-firewall');
93
+ echo '</p><p>';
94
+
95
+ echo __("Unfortunately, XMLRPC also provides a way for attackers to perform actions on your WordPress site, using only a password (i.e. without a two-factor password). More unfortunately, authors of legitimate programmes using XMLRPC have not yet added two-factor support to their code.", 'all-in-one-wp-security-and-firewall');
96
+ echo '</p><p>';
97
+
98
+ echo __("i.e. XMLRPC requests coming in to WordPress (whether from a legitimate app, or from an attacker) can only be verified using the password - not with a two-factor code. As a result, there not be an ideal option to pick below. You may have to choose between the convenience of using your apps, or the security of two factor authentication.", 'all-in-one-wp-security-and-firewall');
99
+ echo '</p>';
100
+
101
+ ?>
102
+ <p>
103
+ <?php $simba_tfa->tfa_list_xmlrpc_status_radios(); ?>
104
+ </p>
105
+ <?php submit_button(); ?>
106
+ </form>
107
+ </div>
108
+
109
+ <div id="simba-tfa-admin-settings-algorithm">
110
+ <hr>
111
+ <form method="post" action="options.php" style="margin-top: 40px">
112
+ <?php settings_fields('simba_tfa_default_hmac_group'); ?>
113
+ <h2><?php _e('Default algorithm', 'all-in-one-wp-security-and-firewall'); ?></h2>
114
+ <?php _e('Your users can change this in their own settings if they want.', 'all-in-one-wp-security-and-firewall'); ?>
115
+ <p>
116
+ <?php
117
+ $totp_controller->print_default_hmac_radios();
118
+ ?></p>
119
+ <?php submit_button(); ?>
120
+ </form>
121
+ </div>
122
+
123
+ <hr>
124
+
125
+ <?php
126
+ if (function_exists('WC')) {
127
+
128
+ ?>
129
+ <br><br>
130
+ <h2><?php _e("WooCommerce integration", 'all-in-one-wp-security-and-firewall'); ?></h2>
131
+ <p>
132
+ <?php echo apply_filters('simba_tfa_settings_woocommerce', '<a href="'.esc_url($simba_tfa->get_premium_version_url()).'">'.__('The Premium version of this plugin allows you to add a configuration tab for users in the WooCommerce "My account" area, and anti-bot protection on the WooCommerce login form.', 'all-in-one-wp-security-and-firewall').'</a>'); ?>
133
+ </p>
134
+ <hr>
135
+ <?php } ?>
136
+
137
+ <br>
138
+
139
+ <div class="tfa-premium">
140
+ <br>
141
+ <h2><?php _e("Users' settings", 'all-in-one-wp-security-and-firewall'); ?></h2>
142
+ <p>
143
+
144
+ <?php
145
+ if (!class_exists('Simba_Two_Factor_Authentication_Premium')) { ?>
146
+
147
+ <a href="<?php echo esc_url($simba_tfa->get_premium_version_url()); ?>"><?php _e("The Premium version of this plugin allows you to see and reset the TFA settings of other users.", 'all-in-one-wp-security-and-firewall'); ?></a>
148
+
149
+ <a href="https://wordpress.org/plugins/user-switching/"><?php _e('Another way to do that is by using a user-switching plugin like this one.', 'all-in-one-wp-security-and-firewall'); ?></a>
150
+
151
+ <?php } ?>
152
+
153
+ <?php do_action('simba_tfa_users_settings'); ?>
154
+
155
+ <hr>
156
+ <?php if (!class_exists('Simba_Two_Factor_Authentication_Premium')) { ?>
157
+ <h2><?php _e('Premium version', 'all-in-one-wp-security-and-firewall'); ?></h2>
158
+ <p>
159
+ <a href="<?php echo esc_url($simba_tfa->get_premium_version_url()); ?>"><?php _e("If you want to say 'thank you' or help this plugin's development, or get extra features, then please take a look at the premium version of this plugin.", 'all-in-one-wp-security-and-firewall'); ?></a> <?php _e('It comes with these extra features:', 'all-in-one-wp-security-and-firewall');?><br>
160
+ </p>
161
+ <p>
162
+ <ul style="list-style: disc inside;">
163
+ <li><strong><?php _e('Emergency codes', 'all-in-one-wp-security-and-firewall');?></strong> - <?php _e('provide your users with one-time codes to use in case they lose their device.', 'all-in-one-wp-security-and-firewall');?></li>
164
+ <li><strong><?php _e('Make TFA compulsory', 'all-in-one-wp-security-and-firewall');?></strong> - <?php _e('require your users to set up TFA to be able to log in, after an optional grace period.', 'all-in-one-wp-security-and-firewall');?></li>
165
+ <li><strong><?php _e('Trusted devices', 'all-in-one-wp-security-and-firewall');?></strong> - <?php _e('allow privileged (or all) users to mark a device as trusted and thereby only needing to supply a TFA code upon login every so-many days (e.g. every 30 days) instead of on each login.', 'all-in-one-wp-security-and-firewall');?></li>
166
+ <li><strong><?php _e('Manage all users centrally', 'all-in-one-wp-security-and-firewall');?></strong> - <?php _e('enable, disable or see TFA codes for all your users from one central location.', 'all-in-one-wp-security-and-firewall');?></li>
167
+ <li><strong><?php _e('More shortcodes', 'all-in-one-wp-security-and-firewall');?></strong> - <?php _e('flexible shortcodes allowing you to design your front-end settings page for your users exactly as you wish.', 'all-in-one-wp-security-and-firewall');?></li>
168
+ <li><strong><?php _e('More WooCommerce features', 'all-in-one-wp-security-and-firewall');?></strong> - <?php _e('automatically add TFA settings in the WooCommerce account settings, and WooCommerce login form bot protection.', 'all-in-one-wp-security-and-firewall');?></li>
169
+ <li><strong><?php _e('Elementor support', 'all-in-one-wp-security-and-firewall');?></strong> - <?php _e('adds support for Elementor login forms.', 'all-in-one-wp-security-and-firewall');?></li>
170
+ <li><strong><?php _e('Any-form support', 'all-in-one-wp-security-and-firewall');?></strong> - <?php _e('adds support for any login form from any plugin via appending your TFA code onto the end of your password.', 'all-in-one-wp-security-and-firewall');?></li>
171
+ <li><strong><?php _e('Personal support', 'all-in-one-wp-security-and-firewall');?></strong> - <?php _e('access to our personal support desk for 12 months.', 'all-in-one-wp-security-and-firewall');?></li>
172
+ </ul>
173
+ </p>
174
+ <hr>
175
+ <?php } ?>
176
+ </div>
177
+
178
+ <h2><?php _e('Translations', 'all-in-one-wp-security-and-firewall'); ?></h2>
179
+ <p>
180
+ <?php echo sprintf(__("If you want to translate this plugin, please go to %s", 'all-in-one-wp-security-and-firewall'), '<a href="'.esc_url($simba_tfa->get_plugin_translate_url()).'">'.__('the wordpress.org translation website.', 'all-in-one-wp-security-and-firewall').'</a>').' '.__("Don't send us the translation file directly - plugin authors do not have access to the wordpress.org translation system (local language teams do).", 'all-in-one-wp-security-and-firewall'); ?>
181
+ <br>
182
+ </p>
183
+
184
+ </div>
185
+ </div>
includes/simba-tfa/templates/settings-intro-notices.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <p class="simba_tfa_personal_settings_notice simba_tfa_intro_notice">
2
+ <?php
3
+
4
+ echo __('These are your personal settings.', 'all-in-one-wp-security-and-firewall').' '.__('Nothing you change here will have any effect on other users.', 'all-in-one-wp-security-and-firewall');
5
+
6
+ if (is_multisite()) {
7
+ if (is_super_admin()) {
8
+ // Since WP 4.9
9
+ $main_site_id = function_exists('get_main_site_id') ? get_main_site_id() : 1;
10
+ $switched = switch_to_blog($main_site_id);
11
+ echo ' <a href="'.esc_url($simba_tfa->get_site_wide_administration_url()).'">'.__('The site-wide administration options are here.', 'all-in-one-wp-security-and-firewall').'</a>';
12
+ if ($switched) restore_current_blog();
13
+ }
14
+ } elseif (current_user_can($simba_tfa->get_management_capability())) {
15
+ echo ' <a href="'.esc_url($simba_tfa->get_site_wide_administration_url()).'">'.__('The site-wide administration options are here.', 'all-in-one-wp-security-and-firewall').'</a>';
16
+ }
17
+
18
+ ?>
19
+ </p>
20
+
21
+ <p class="simba_tfa_verify_tfa_notice simba_tfa_intro_notice"><strong>
22
+
23
+ <?php echo apply_filters('simbatfa_message_you_should_verify', __('If you activate two-factor authentication, then verify that your two-factor application and this page show the same One-Time Password (within a minute of each other) before you log out.', 'all-in-one-wp-security-and-firewall')); ?></strong>
24
+
25
+ <?php if (current_user_can($simba_tfa->get_management_capability())) { ?>
26
+ <a href="<?php echo esc_url($simba_tfa->get_faq_url()); ?>"><?php _e('You should also bookmark the FAQs, which explain how to de-activate the plugin even if you cannot log in.', 'all-in-one-wp-security-and-firewall');?></a>
27
+ <?php } ?>
28
+ </p>
includes/simba-tfa/templates/shortcode-tfa-user-settings.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if (!defined('ABSPATH')) die('No direct access.');
4
+
5
+ if (!$is_activated_for_user) {
6
+ _e('Two factor authentication is not available for your user.', 'all-in-one-wp-security-and-firewall');
7
+ } else {
8
+
9
+ ?>
10
+
11
+ <div class="wrap" style="padding-bottom:10px">
12
+
13
+ <?php $simba_tfa->include_template('settings-intro-notices.php'); ?>
14
+
15
+ <?php $tfa_frontend->settings_enable_or_disable_output(); ?>
16
+
17
+ <?php $simba_tfa->get_totp_controller()->current_codes_box(); ?>
18
+
19
+ <?php $simba_tfa->get_totp_controller()->advanced_settings_box(array($tfa_frontend, 'save_settings_button')); ?>
20
+
21
+ </div>
22
+
23
+ <?php $tfa_frontend->save_settings_javascript_output(); ?>
24
+
25
+ <?php
26
+ }
includes/simba-tfa/templates/trusted-devices-inner-box.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php if (!defined('ABSPATH')) die('No direct access.'); ?>
2
+
3
+ <div id="tfa_trusted_devices_box_inner">
4
+
5
+ <p><?php _e('Trusted devices are devices which have previously logged in with a second factor, belonging to users who have been permitted to mark devices as trusted, and for which the user checked the checkbox on the login form to trust the device.', 'all-in-one-wp-security-and-firewall'); ?></p>
6
+
7
+ <?php
8
+
9
+ global $current_user;
10
+
11
+ $trusted_devices = $this->user_get_trusted_devices($current_user->ID);
12
+
13
+ if (empty($trusted_devices)) {
14
+ echo '<em>'.__('(none)', 'all-in-one-wp-security-and-firewall').'</em>';
15
+ }
16
+
17
+ foreach ($trusted_devices as $device_id => $device) {
18
+
19
+ if (!isset($device['token']) || '' == $device['token']) continue;
20
+
21
+ $user_agent = empty($device['user_agent']) ? __('(unspecified)', 'all-in-one-wp-security-and-firewall'): $device['user_agent'];
22
+
23
+ echo '<span class="simbatfa_trusted_device">'.sprintf(__('User agent %s logged in from IP address %s and is trusted until %s', 'all-in-one-wp-security-and-firewall'), '<strong>'.htmlspecialchars($user_agent).'</strong>', '<strong><a target="_blank" href="https://ipinfo.io/'.$device['ip'].'">'.htmlspecialchars($device['ip']).'</a></strong>', '<strong>'.date_i18n(get_option('time_format').' '.get_option('date_format'), $device['until']).'</strong>').' - <a href="#" class="simbatfa-trust-remove" data-trusted-device-id="'.esc_attr($device_id).'">'.__('Remove trust', 'all-in-one-wp-security-and-firewall').'</a></span><br>';
24
+
25
+ }
26
+
27
+ ?>
28
+
29
+ </div>
includes/simba-tfa/templates/user-settings.php ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if (!defined('ABSPATH')) die('Access denied.');
4
+
5
+ global $current_user;
6
+ $totp_controller = $simba_tfa->get_totp_controller();
7
+
8
+ ?>
9
+ <style>
10
+ #icon-tfa-plugin {
11
+ background: transparent url('<?php print plugin_dir_url(__FILE__); ?>img/tfa_admin_icon_32x32.png' ) no-repeat;
12
+ }
13
+ .inside > h3, .normal {
14
+ cursor: default;
15
+ margin-top: 20px;
16
+ }
17
+ </style>
18
+ <div class="wrap">
19
+
20
+ <h2><?php echo __('Two Factor Authentication', 'all-in-one-wp-security-and-firewall').' '.__('Settings', 'all-in-one-wp-security-and-firewall'); ?></h2>
21
+
22
+ <?php
23
+
24
+ if (!empty($totp_controller->were_settings_saved())) {
25
+ echo '<div class="updated notice is-dismissible">'."<p><strong>".__('Settings saved.', 'all-in-one-wp-security-and-firewall')."</strong></p></div>";
26
+ }
27
+
28
+ $simba_tfa->include_template('settings-intro-notices.php');
29
+
30
+ ?>
31
+
32
+ <form method="post" action="<?php print esc_url(add_query_arg('settings-updated', 'true', $_SERVER['REQUEST_URI'])); ?>">
33
+
34
+ <?php wp_nonce_field('tfa_activate', '_tfa_activate_nonce', false, true); ?>
35
+
36
+ <h2><?php _e('Activate two factor authentication', 'all-in-one-wp-security-and-firewall'); ?></h2>
37
+ <p>
38
+ <?php
39
+ $utc_date = gmdate('Y-m-d H:i:s');
40
+ $date_now = get_date_from_gmt($utc_date, 'Y-m-d H:i:s');
41
+ echo sprintf(__('N.B. Getting your TFA app/device to generate the correct code depends upon a) you first setting it up by entering or scanning the code below into it, and b) upon your web-server and your TFA app/device agreeing upon the UTC time (within a minute or so). The current UTC time according to the server when this page loaded: %s, and in the time-zone you have configured in your WordPress settings: %s', 'all-in-one-wp-security-and-firewall'), htmlspecialchars($utc_date), htmlspecialchars($date_now));
42
+ ?>
43
+ </p>
44
+ <p>
45
+ <?php
46
+ $simba_tfa->paint_enable_tfa_radios($current_user->ID);
47
+ ?></p>
48
+ <?php submit_button(); ?>
49
+ </form>
50
+
51
+ <?php
52
+
53
+ $totp_controller->current_codes_box();
54
+
55
+ $totp_controller->advanced_settings_box();
56
+
57
+ do_action('simba_tfa_user_settings_after_advanced_settings');
58
+
59
+ ?>
60
+
61
+ </div>
js/wp-security-admin-script.js CHANGED
@@ -3,7 +3,7 @@ jQuery(document).ready(function($){
3
 
4
  //Media Uploader - start
5
  function aiowps_attach_media_uploader(key) {
6
- jQuery('#' + key + '_button').click(function() {
7
  text_element = jQuery('#' + key).attr('name');
8
  button_element = jQuery('#' + key + '_button').attr('name');
9
  tb_show('All In One Security - Please Select a File', 'media-upload.php?referer=aiowpsec&amp;TB_iframe=true&amp;post_id=0width=640&amp;height=485');
@@ -31,7 +31,7 @@ jQuery(document).ready(function($){
31
 
32
  //Triggers the more info toggle link
33
  $(".aiowps_more_info_body").hide();//hide the more info on page load
34
- $(".aiowps_more_info_anchor").click(function(){
35
  $(this).next(".aiowps_more_info_body").animate({ "height": "toggle"});
36
  var toogle_char_ref = $(this).find(".aiowps_more_info_toggle_char");
37
  var toggle_char_value = toogle_char_ref.text();
@@ -59,6 +59,46 @@ jQuery(document).ready(function($){
59
  jQuery('input[name=aiowps_enable_brute_force_attack_prevention]').on('click', function() {
60
  jQuery('input[name=aiowps_brute_force_secret_word]').prop('disabled', !jQuery(this).prop('checked'));
61
  jQuery('input[name=aiowps_cookie_based_brute_force_redirect_url]').prop('disabled', !jQuery(this).prop('checked'));
 
 
62
  });
63
  // End of brute force attack prevention toggle handling
64
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  //Media Uploader - start
5
  function aiowps_attach_media_uploader(key) {
6
+ jQuery('#' + key + '_button').on('click', function() {
7
  text_element = jQuery('#' + key).attr('name');
8
  button_element = jQuery('#' + key + '_button').attr('name');
9
  tb_show('All In One Security - Please Select a File', 'media-upload.php?referer=aiowpsec&amp;TB_iframe=true&amp;post_id=0width=640&amp;height=485');
31
 
32
  //Triggers the more info toggle link
33
  $(".aiowps_more_info_body").hide();//hide the more info on page load
34
+ $('.aiowps_more_info_anchor').on('click', function() {
35
  $(this).next(".aiowps_more_info_body").animate({ "height": "toggle"});
36
  var toogle_char_ref = $(this).find(".aiowps_more_info_toggle_char");
37
  var toggle_char_value = toogle_char_ref.text();
59
  jQuery('input[name=aiowps_enable_brute_force_attack_prevention]').on('click', function() {
60
  jQuery('input[name=aiowps_brute_force_secret_word]').prop('disabled', !jQuery(this).prop('checked'));
61
  jQuery('input[name=aiowps_cookie_based_brute_force_redirect_url]').prop('disabled', !jQuery(this).prop('checked'));
62
+ jQuery('input[name=aiowps_brute_force_attack_prevention_pw_protected_exception]').prop('disabled', !jQuery(this).prop('checked'));
63
+ jQuery('input[name=aiowps_brute_force_attack_prevention_ajax_exception]').prop('disabled', !jQuery(this).prop('checked'));
64
  });
65
  // End of brute force attack prevention toggle handling
66
+
67
+ /**
68
+ * Take a backup with UpdraftPlus if possible.
69
+ *
70
+ * @param {String} file_entities
71
+ *
72
+ * @return void
73
+ */
74
+ function take_a_backup_with_updraftplus(file_entities) {
75
+ // Set default for file_entities to empty string
76
+ if ('undefined' == typeof file_entities) file_entities = '';
77
+ var exclude_files = file_entities ? 0 : 1;
78
+
79
+ if (typeof updraft_backupnow_inpage_go === 'function') {
80
+ updraft_backupnow_inpage_go(function () {
81
+ // Close the backup dialogue.
82
+ $('#updraft-backupnow-inpage-modal').dialog('close');
83
+ }, file_entities, 'autobackup', 0, exclude_files, 0);
84
+ }
85
+ }
86
+ if (jQuery('#aios-manual-db-backup-now').length) {
87
+ jQuery('#aios-manual-db-backup-now').on('click', function (e) {
88
+ e.preventDefault();
89
+ take_a_backup_with_updraftplus();
90
+ });
91
+ }
92
+
93
+ // Hide 2FA premium advertisement
94
+ if (jQuery('.tfa-premium').length) {
95
+ jQuery('.tfa-premium').hide();
96
+ }
97
+
98
+
99
+ // Start of trash spam comments toggle handling
100
+ jQuery('input[name=aiowps_enable_trash_spam_comments]').on('click', function() {
101
+ jQuery('input[name=aiowps_trash_spam_comments_after_days]').prop('disabled', !jQuery(this).prop('checked'));
102
+ });
103
+ // End of trash spam comments toggle handling
104
+ });
languages/all-in-one-wp-security-and-firewall-de_DE.po CHANGED
@@ -1225,13 +1225,13 @@ msgstr ""
1225
  #: admin/wp-security-database-menu.php:31
1226
  #: classes/grade-system/wp-security-feature-item-manager.php:62
1227
  #@ all-in-one-wp-security-and-firewall
1228
- msgid "DB Backup"
1229
  msgstr ""
1230
 
1231
  #: admin/wp-security-database-menu.php:30
1232
  #: classes/grade-system/wp-security-feature-item-manager.php:60
1233
  #@ all-in-one-wp-security-and-firewall
1234
- msgid "DB Prefix"
1235
  msgstr ""
1236
 
1237
  #: admin/wp-security-database-menu.php:93
@@ -1256,17 +1256,17 @@ msgstr ""
1256
 
1257
  #: admin/wp-security-database-menu.php:131
1258
  #@ all-in-one-wp-security-and-firewall
1259
- msgid "Change Database Prefix"
1260
  msgstr ""
1261
 
1262
  #: admin/wp-security-database-menu.php:134
1263
  #@ all-in-one-wp-security-and-firewall
1264
- msgid "Your WordPress DB is the most important asset of your website because it contains a lot of your site's precious information."
1265
  msgstr ""
1266
 
1267
  #: admin/wp-security-database-menu.php:135
1268
  #@ all-in-one-wp-security-and-firewall
1269
- msgid "The DB is also a target for hackers via methods such as SQL injections and malicious and automated code which targets certain tables."
1270
  msgstr ""
1271
 
1272
  #: admin/wp-security-database-menu.php:136
@@ -1281,7 +1281,7 @@ msgstr ""
1281
 
1282
  #: admin/wp-security-database-menu.php:143
1283
  #@ all-in-one-wp-security-and-firewall
1284
- msgid "DB Prefix Options"
1285
  msgstr ""
1286
 
1287
  #: admin/wp-security-database-menu.php:154
@@ -1292,7 +1292,7 @@ msgstr ""
1292
 
1293
  #: admin/wp-security-database-menu.php:163
1294
  #@ all-in-one-wp-security-and-firewall
1295
- msgid "Current DB Table Prefix"
1296
  msgstr ""
1297
 
1298
  #: admin/wp-security-database-menu.php:169
@@ -1304,7 +1304,7 @@ msgstr ""
1304
 
1305
  #: admin/wp-security-database-menu.php:176
1306
  #@ all-in-one-wp-security-and-firewall
1307
- msgid "Generate New DB Table Prefix"
1308
  msgstr ""
1309
 
1310
  #: admin/wp-security-database-menu.php:179
@@ -1324,7 +1324,7 @@ msgstr ""
1324
 
1325
  #: admin/wp-security-database-menu.php:186
1326
  #@ all-in-one-wp-security-and-firewall
1327
- msgid "Change DB Prefix"
1328
  msgstr ""
1329
 
1330
  #: admin/wp-security-database-menu.php:207
@@ -1532,7 +1532,7 @@ msgstr ""
1532
 
1533
  #: admin/wp-security-database-menu.php:513
1534
  #@ all-in-one-wp-security-and-firewall
1535
- msgid "DB prefix change tasks have been completed."
1536
  msgstr ""
1537
 
1538
  #: admin/wp-security-filescan-menu.php:23
@@ -3845,7 +3845,7 @@ msgstr ""
3845
 
3846
  #: admin/wp-security-user-login-menu.php:277
3847
  #@ all-in-one-wp-security-and-firewall
3848
- msgid "All records from the Failed Logins table were deleted successfully!"
3849
  msgstr ""
3850
 
3851
  #: admin/wp-security-user-login-menu.php:292
1225
  #: admin/wp-security-database-menu.php:31
1226
  #: classes/grade-system/wp-security-feature-item-manager.php:62
1227
  #@ all-in-one-wp-security-and-firewall
1228
+ msgid "Database backup"
1229
  msgstr ""
1230
 
1231
  #: admin/wp-security-database-menu.php:30
1232
  #: classes/grade-system/wp-security-feature-item-manager.php:60
1233
  #@ all-in-one-wp-security-and-firewall
1234
+ msgid "Database prefix"
1235
  msgstr ""
1236
 
1237
  #: admin/wp-security-database-menu.php:93
1256
 
1257
  #: admin/wp-security-database-menu.php:131
1258
  #@ all-in-one-wp-security-and-firewall
1259
+ msgid "Change database prefix"
1260
  msgstr ""
1261
 
1262
  #: admin/wp-security-database-menu.php:134
1263
  #@ all-in-one-wp-security-and-firewall
1264
+ msgid "Your WordPress database is the most important asset of your website because it contains a lot of your site's precious information."
1265
  msgstr ""
1266
 
1267
  #: admin/wp-security-database-menu.php:135
1268
  #@ all-in-one-wp-security-and-firewall
1269
+ msgid "The database is also a target for hackers via methods such as SQL injections and malicious and automated code which targets certain tables."
1270
  msgstr ""
1271
 
1272
  #: admin/wp-security-database-menu.php:136
1281
 
1282
  #: admin/wp-security-database-menu.php:143
1283
  #@ all-in-one-wp-security-and-firewall
1284
+ msgid "Database prefix options"
1285
  msgstr ""
1286
 
1287
  #: admin/wp-security-database-menu.php:154
1292
 
1293
  #: admin/wp-security-database-menu.php:163
1294
  #@ all-in-one-wp-security-and-firewall
1295
+ msgid "Current database table prefix"
1296
  msgstr ""
1297
 
1298
  #: admin/wp-security-database-menu.php:169
1304
 
1305
  #: admin/wp-security-database-menu.php:176
1306
  #@ all-in-one-wp-security-and-firewall
1307
+ msgid "Generate new database table prefix"
1308
  msgstr ""
1309
 
1310
  #: admin/wp-security-database-menu.php:179
1324
 
1325
  #: admin/wp-security-database-menu.php:186
1326
  #@ all-in-one-wp-security-and-firewall
1327
+ msgid "Change database prefix"
1328
  msgstr ""
1329
 
1330
  #: admin/wp-security-database-menu.php:207
1532
 
1533
  #: admin/wp-security-database-menu.php:513
1534
  #@ all-in-one-wp-security-and-firewall
1535
+ msgid "The database prefix change tasks have been completed."
1536
  msgstr ""
1537
 
1538
  #: admin/wp-security-filescan-menu.php:23
3845
 
3846
  #: admin/wp-security-user-login-menu.php:277
3847
  #@ all-in-one-wp-security-and-firewall
3848
+ msgid "All records from the Failed Logins table were deleted successfully."
3849
  msgstr ""
3850
 
3851
  #: admin/wp-security-user-login-menu.php:292
languages/all-in-one-wp-security-and-firewall-fr_FR.po CHANGED
@@ -1351,12 +1351,12 @@ msgstr "Plages et adresses IP actuellement en lock-out"
1351
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:26
1352
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:31
1353
  #: all-in-one-wp-security/classes/grade-system/wp-security-feature-item-manager.php:63
1354
- msgid "DB Backup"
1355
  msgstr "sauvegarde de BdD"
1356
 
1357
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:30
1358
  #: all-in-one-wp-security/classes/grade-system/wp-security-feature-item-manager.php:61
1359
- msgid "DB Prefix"
1360
  msgstr "Préfixe de BdD"
1361
 
1362
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:93
@@ -1385,7 +1385,7 @@ msgstr ""
1385
  "chiffres, des lettres et « _ »."
1386
 
1387
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:131
1388
- msgid "Change Database Prefix"
1389
  msgstr "Changer le préfixe de BdD"
1390
 
1391
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:134
@@ -1425,7 +1425,7 @@ msgstr ""
1425
  "l’extension."
1426
 
1427
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:143
1428
- msgid "DB Prefix Options"
1429
  msgstr "Options de préfixe BdD"
1430
 
1431
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:154
@@ -1435,7 +1435,7 @@ msgstr ""
1435
  "Il est recommandé d’effectuer une %s avant d’utiliser cette fonctionnalité"
1436
 
1437
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:163
1438
- msgid "Current DB Table Prefix"
1439
  msgstr "Préfixe actuel des tables de BdD"
1440
 
1441
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:169
@@ -1451,7 +1451,7 @@ msgstr ""
1451
  "valeur par une autre."
1452
 
1453
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:176
1454
- msgid "Generate New DB Table Prefix"
1455
  msgstr "Générer un nouveau préfixe de tables BdD"
1456
 
1457
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:179
@@ -1475,7 +1475,7 @@ msgstr ""
1475
  "des lettres et / ou des chiffres et / ou « _ ». Exemple : XyZ_3_"
1476
 
1477
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:186
1478
- msgid "Change DB Prefix"
1479
  msgstr "Changer le préfixe des tables de la BdD"
1480
 
1481
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:207
@@ -1719,7 +1719,7 @@ msgstr ""
1719
  "l’ancien préfixe BdD ont été actualisés avec succès !"
1720
 
1721
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:530
1722
- msgid "DB prefix change tasks have been completed."
1723
  msgstr "Les tâches de modification du préfixe BdD sont terminées."
1724
 
1725
  #: all-in-one-wp-security/admin/wp-security-filescan-menu.php:24
@@ -4893,7 +4893,7 @@ msgstr ""
4893
  "de tentatives de connexions infructueuses a échoué !"
4894
 
4895
  #: all-in-one-wp-security/admin/wp-security-user-login-menu.php:277
4896
- msgid "All records from the Failed Logins table were deleted successfully!"
4897
  msgstr ""
4898
  "Tous les enregistrements de la table des tentatives de connexions "
4899
  "infructueuses ont été supprimés avec succès !"
1351
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:26
1352
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:31
1353
  #: all-in-one-wp-security/classes/grade-system/wp-security-feature-item-manager.php:63
1354
+ msgid "Database backup"
1355
  msgstr "sauvegarde de BdD"
1356
 
1357
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:30
1358
  #: all-in-one-wp-security/classes/grade-system/wp-security-feature-item-manager.php:61
1359
+ msgid "Database prefix"
1360
  msgstr "Préfixe de BdD"
1361
 
1362
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:93
1385
  "chiffres, des lettres et « _ »."
1386
 
1387
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:131
1388
+ msgid "Change database prefix"
1389
  msgstr "Changer le préfixe de BdD"
1390
 
1391
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:134
1425
  "l’extension."
1426
 
1427
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:143
1428
+ msgid "Database prefix options"
1429
  msgstr "Options de préfixe BdD"
1430
 
1431
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:154
1435
  "Il est recommandé d’effectuer une %s avant d’utiliser cette fonctionnalité"
1436
 
1437
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:163
1438
+ msgid "Current database table prefix"
1439
  msgstr "Préfixe actuel des tables de BdD"
1440
 
1441
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:169
1451
  "valeur par une autre."
1452
 
1453
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:176
1454
+ msgid "Generate new database table prefix"
1455
  msgstr "Générer un nouveau préfixe de tables BdD"
1456
 
1457
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:179
1475
  "des lettres et / ou des chiffres et / ou « _ ». Exemple : XyZ_3_"
1476
 
1477
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:186
1478
+ msgid "Change database prefix"
1479
  msgstr "Changer le préfixe des tables de la BdD"
1480
 
1481
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:207
1719
  "l’ancien préfixe BdD ont été actualisés avec succès !"
1720
 
1721
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:530
1722
+ msgid "The database prefix change tasks have been completed."
1723
  msgstr "Les tâches de modification du préfixe BdD sont terminées."
1724
 
1725
  #: all-in-one-wp-security/admin/wp-security-filescan-menu.php:24
4893
  "de tentatives de connexions infructueuses a échoué !"
4894
 
4895
  #: all-in-one-wp-security/admin/wp-security-user-login-menu.php:277
4896
+ msgid "All records from the Failed Logins table were deleted successfully."
4897
  msgstr ""
4898
  "Tous les enregistrements de la table des tentatives de connexions "
4899
  "infructueuses ont été supprimés avec succès !"
languages/all-in-one-wp-security-and-firewall-hu_HU.po CHANGED
@@ -415,12 +415,12 @@ msgstr ""
415
 
416
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:23
417
  #: all-in-one-wp-security/classes/grade-system/wp-security-feature-item-manager.php:57
418
- msgid "DB Prefix"
419
  msgstr ""
420
 
421
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:24
422
  #: all-in-one-wp-security/classes/grade-system/wp-security-feature-item-manager.php:59
423
- msgid "DB Backup"
424
  msgstr ""
425
 
426
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:84
@@ -445,7 +445,7 @@ msgid ""
445
  msgstr ""
446
 
447
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:122
448
- msgid "Change Database Prefix"
449
  msgstr ""
450
 
451
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:125
@@ -474,7 +474,7 @@ msgid ""
474
  msgstr ""
475
 
476
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:134
477
- msgid "DB Prefix Options"
478
  msgstr ""
479
 
480
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:145
@@ -483,7 +483,7 @@ msgid "It is recommended that you perform a %s before using this feature"
483
  msgstr ""
484
 
485
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:154
486
- msgid "Current DB Table Prefix"
487
  msgstr ""
488
 
489
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:160
@@ -495,7 +495,7 @@ msgid ""
495
  msgstr ""
496
 
497
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:167
498
- msgid "Generate New DB Table Prefix"
499
  msgstr ""
500
 
501
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:170
@@ -515,7 +515,7 @@ msgid ""
515
  msgstr ""
516
 
517
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:177
518
- msgid "Change DB Prefix"
519
  msgstr ""
520
 
521
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:198
@@ -716,7 +716,7 @@ msgid ""
716
  msgstr ""
717
 
718
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:499
719
- msgid "DB prefix change tasks have been completed."
720
  msgstr ""
721
 
722
  #: all-in-one-wp-security/admin/wp-security-filescan-menu.php:22
@@ -2960,7 +2960,7 @@ msgid "User Login Feature - Delete all failed login records operation failed!"
2960
  msgstr ""
2961
 
2962
  #: all-in-one-wp-security/admin/wp-security-user-login-menu.php:498
2963
- msgid "All records from the Failed Logins table were deleted successfully!"
2964
  msgstr ""
2965
 
2966
  #: all-in-one-wp-security/admin/wp-security-user-login-menu.php:513
415
 
416
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:23
417
  #: all-in-one-wp-security/classes/grade-system/wp-security-feature-item-manager.php:57
418
+ msgid "Database prefix"
419
  msgstr ""
420
 
421
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:24
422
  #: all-in-one-wp-security/classes/grade-system/wp-security-feature-item-manager.php:59
423
+ msgid "Database backup"
424
  msgstr ""
425
 
426
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:84
445
  msgstr ""
446
 
447
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:122
448
+ msgid "Change database prefix"
449
  msgstr ""
450
 
451
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:125
474
  msgstr ""
475
 
476
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:134
477
+ msgid "Database prefix options"
478
  msgstr ""
479
 
480
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:145
483
  msgstr ""
484
 
485
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:154
486
+ msgid "Current database table prefix"
487
  msgstr ""
488
 
489
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:160
495
  msgstr ""
496
 
497
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:167
498
+ msgid "Generate new database table prefix"
499
  msgstr ""
500
 
501
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:170
515
  msgstr ""
516
 
517
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:177
518
+ msgid "Change database prefix"
519
  msgstr ""
520
 
521
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:198
716
  msgstr ""
717
 
718
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:499
719
+ msgid "The database prefix change tasks have been completed."
720
  msgstr ""
721
 
722
  #: all-in-one-wp-security/admin/wp-security-filescan-menu.php:22
2960
  msgstr ""
2961
 
2962
  #: all-in-one-wp-security/admin/wp-security-user-login-menu.php:498
2963
+ msgid "All records from the Failed Logins table were deleted successfully."
2964
  msgstr ""
2965
 
2966
  #: all-in-one-wp-security/admin/wp-security-user-login-menu.php:513
languages/all-in-one-wp-security-and-firewall-ko_KR.po CHANGED
@@ -1511,12 +1511,12 @@ msgstr "기록 파일이 비었어요."
1511
  #: admin/wp-security-database-menu.php:26
1512
  #: admin/wp-security-database-menu.php:31
1513
  #: classes/grade-system/wp-security-feature-item-manager.php:63
1514
- msgid "DB Backup"
1515
  msgstr "DB 백업"
1516
 
1517
  #: admin/wp-security-database-menu.php:30
1518
  #: classes/grade-system/wp-security-feature-item-manager.php:61
1519
- msgid "DB Prefix"
1520
  msgstr "DB 접두사"
1521
 
1522
  # @ all-in-one-wp-security-and-firewall
@@ -1551,7 +1551,7 @@ msgstr ""
1551
 
1552
  # @ all-in-one-wp-security-and-firewall
1553
  #: admin/wp-security-database-menu.php:133
1554
- msgid "Change Database Prefix"
1555
  msgstr "데이터베이스 접두사 변경"
1556
 
1557
  # @ all-in-one-wp-security-and-firewall
@@ -1593,7 +1593,7 @@ msgstr ""
1593
 
1594
  # @ all-in-one-wp-security-and-firewall
1595
  #: admin/wp-security-database-menu.php:145
1596
- msgid "DB Prefix Options"
1597
  msgstr "DB 접두사 옵션"
1598
 
1599
  # @ all-in-one-wp-security-and-firewall
@@ -1604,7 +1604,7 @@ msgstr "이 기능을 사용하기 전에 %s를 수행하는 것이 좋습니다
1604
 
1605
  # @ all-in-one-wp-security-and-firewall
1606
  #: admin/wp-security-database-menu.php:165
1607
- msgid "Current DB Table Prefix"
1608
  msgstr "현재 DB 테이블 접두사"
1609
 
1610
  # @ all-in-one-wp-security-and-firewall
@@ -1622,7 +1622,7 @@ msgstr ""
1622
 
1623
  # @ all-in-one-wp-security-and-firewall
1624
  #: admin/wp-security-database-menu.php:178
1625
- msgid "Generate New DB Table Prefix"
1626
  msgstr "새 DB 테이블 접두사 생성"
1627
 
1628
  # @ all-in-one-wp-security-and-firewall
@@ -1649,7 +1649,7 @@ msgstr ""
1649
 
1650
  # @ all-in-one-wp-security-and-firewall
1651
  #: admin/wp-security-database-menu.php:188
1652
- msgid "Change DB Prefix"
1653
  msgstr "DB 접두사 변경"
1654
 
1655
  # @ all-in-one-wp-security-and-firewall
@@ -1914,7 +1914,7 @@ msgstr ""
1914
 
1915
  # @ all-in-one-wp-security-and-firewall
1916
  #: admin/wp-security-database-menu.php:532
1917
- msgid "DB prefix change tasks have been completed."
1918
  msgstr "DB 접두사 변경 작업이 완료되었습니다."
1919
 
1920
  #: admin/wp-security-database-menu.php:575
@@ -5334,7 +5334,7 @@ msgstr ""
5334
 
5335
  # @ all-in-one-wp-security-and-firewall
5336
  #: admin/wp-security-user-login-menu.php:304
5337
- msgid "All records from the Failed Logins table were deleted successfully!"
5338
  msgstr ""
5339
 
5340
  # @ all-in-one-wp-security-and-firewall
1511
  #: admin/wp-security-database-menu.php:26
1512
  #: admin/wp-security-database-menu.php:31
1513
  #: classes/grade-system/wp-security-feature-item-manager.php:63
1514
+ msgid "Database backup"
1515
  msgstr "DB 백업"
1516
 
1517
  #: admin/wp-security-database-menu.php:30
1518
  #: classes/grade-system/wp-security-feature-item-manager.php:61
1519
+ msgid "Database prefix"
1520
  msgstr "DB 접두사"
1521
 
1522
  # @ all-in-one-wp-security-and-firewall
1551
 
1552
  # @ all-in-one-wp-security-and-firewall
1553
  #: admin/wp-security-database-menu.php:133
1554
+ msgid "Change database prefix"
1555
  msgstr "데이터베이스 접두사 변경"
1556
 
1557
  # @ all-in-one-wp-security-and-firewall
1593
 
1594
  # @ all-in-one-wp-security-and-firewall
1595
  #: admin/wp-security-database-menu.php:145
1596
+ msgid "Database prefix options"
1597
  msgstr "DB 접두사 옵션"
1598
 
1599
  # @ all-in-one-wp-security-and-firewall
1604
 
1605
  # @ all-in-one-wp-security-and-firewall
1606
  #: admin/wp-security-database-menu.php:165
1607
+ msgid "Current database table prefix"
1608
  msgstr "현재 DB 테이블 접두사"
1609
 
1610
  # @ all-in-one-wp-security-and-firewall
1622
 
1623
  # @ all-in-one-wp-security-and-firewall
1624
  #: admin/wp-security-database-menu.php:178
1625
+ msgid "Generate new database table prefix"
1626
  msgstr "새 DB 테이블 접두사 생성"
1627
 
1628
  # @ all-in-one-wp-security-and-firewall
1649
 
1650
  # @ all-in-one-wp-security-and-firewall
1651
  #: admin/wp-security-database-menu.php:188
1652
+ msgid "Change database prefix"
1653
  msgstr "DB 접두사 변경"
1654
 
1655
  # @ all-in-one-wp-security-and-firewall
1914
 
1915
  # @ all-in-one-wp-security-and-firewall
1916
  #: admin/wp-security-database-menu.php:532
1917
+ msgid "The database prefix change tasks have been completed."
1918
  msgstr "DB 접두사 변경 작업이 완료되었습니다."
1919
 
1920
  #: admin/wp-security-database-menu.php:575
5334
 
5335
  # @ all-in-one-wp-security-and-firewall
5336
  #: admin/wp-security-user-login-menu.php:304
5337
+ msgid "All records from the Failed Logins table were deleted successfully."
5338
  msgstr ""
5339
 
5340
  # @ all-in-one-wp-security-and-firewall
languages/all-in-one-wp-security-and-firewall-nl_NL.po CHANGED
@@ -2130,7 +2130,7 @@ msgid "The deletion of the import file failed. Please delete this file manually
2130
  msgstr "Het verwijderen van het importbestand is mislukt. Gelieve dit bestand handmatig te verwijderen via het media-menu voor beveiligingsdoeleinden."
2131
 
2132
  #: admin/wp-security-user-login-menu.php:395
2133
- msgid "All records from the Failed Logins table were deleted successfully!"
2134
  msgstr "Alle records uit de lijst mislukte inlogpogingen zijn met succes verwijderd!"
2135
 
2136
  #: admin/wp-security-user-login-menu.php:391
@@ -2821,7 +2821,7 @@ msgstr "Naam"
2821
  #: classes/grade-system/wp-security-feature-item-manager.php:67
2822
  #: admin/wp-security-database-menu.php:29
2823
  #: admin/wp-security-database-menu.php:34
2824
- msgid "DB Backup"
2825
  msgstr "DB back-up"
2826
 
2827
  #: admin/wp-security-database-menu.php:185
@@ -3119,15 +3119,15 @@ msgstr "Logs bekijken"
3119
 
3120
  #: classes/grade-system/wp-security-feature-item-manager.php:65
3121
  #: admin/wp-security-database-menu.php:33
3122
- msgid "DB Prefix"
3123
  msgstr "DB-prefix"
3124
 
3125
  #: admin/wp-security-database-menu.php:136
3126
- msgid "Change Database Prefix"
3127
  msgstr "Verander database-prefix"
3128
 
3129
  #: admin/wp-security-database-menu.php:148
3130
- msgid "DB Prefix Options"
3131
  msgstr "Opties database-prefix"
3132
 
3133
  #: admin/wp-security-dashboard-menu.php:204
@@ -3171,7 +3171,7 @@ msgid "wp-config.php file was updated successfully!"
3171
  msgstr "wp-config.php is succesvol bijgewerkt!"
3172
 
3173
  #: admin/wp-security-database-menu.php:535
3174
- msgid "DB prefix change tasks have been completed."
3175
  msgstr "Het veranderen van de DB-prefix is afgerond."
3176
 
3177
  #: admin/wp-security-dashboard-menu.php:423
@@ -3266,11 +3266,11 @@ msgid "<strong>ERROR</strong>: The table prefix can only contain numbers, letter
3266
  msgstr "<strong>FOUT</strong>: het tabel voorvoegsel kan alleen cijfers, letters en underscores bevatten."
3267
 
3268
  #: admin/wp-security-database-menu.php:139
3269
- msgid "Your WordPress DB is the most important asset of your website because it contains a lot of your site's precious information."
3270
  msgstr "Uw WordPress-DB is de belangrijkste troef van uw website, omdat deze heel veel kostbare informatie van uw site bevat."
3271
 
3272
  #: admin/wp-security-database-menu.php:140
3273
- msgid "The DB is also a target for hackers via methods such as SQL injections and malicious and automated code which targets certain tables."
3274
  msgstr "De DB is ook een doelwit voor hackers via methoden zoals SQL-injecties en kwaadaardige en geautomatiseerde code die zich op bepaalde tabellen richt."
3275
 
3276
  #: admin/wp-security-database-menu.php:141
@@ -3895,11 +3895,11 @@ msgid "Backup Time Interval"
3895
  msgstr "Backup-intervaltijd"
3896
 
3897
  #: admin/wp-security-database-menu.php:168
3898
- msgid "Current DB Table Prefix"
3899
  msgstr "Huidige database-prefix"
3900
 
3901
  #: admin/wp-security-database-menu.php:181
3902
- msgid "Generate New DB Table Prefix"
3903
  msgstr "Nieuwe database-prefix aanmaken"
3904
 
3905
  #: admin/wp-security-database-menu.php:184
@@ -3907,7 +3907,7 @@ msgid "Check this if you want the plugin to generate a random 6 character string
3907
  msgstr "Activeer dit als u wilt dat de plug-in een willekeurige tekenreeks van 6 karakters voor de tabel-prefix genereert"
3908
 
3909
  #: admin/wp-security-database-menu.php:191
3910
- msgid "Change DB Prefix"
3911
  msgstr "Verander database-prefix"
3912
 
3913
  #: admin/wp-security-database-menu.php:229
2130
  msgstr "Het verwijderen van het importbestand is mislukt. Gelieve dit bestand handmatig te verwijderen via het media-menu voor beveiligingsdoeleinden."
2131
 
2132
  #: admin/wp-security-user-login-menu.php:395
2133
+ msgid "All records from the Failed Logins table were deleted successfully."
2134
  msgstr "Alle records uit de lijst mislukte inlogpogingen zijn met succes verwijderd!"
2135
 
2136
  #: admin/wp-security-user-login-menu.php:391
2821
  #: classes/grade-system/wp-security-feature-item-manager.php:67
2822
  #: admin/wp-security-database-menu.php:29
2823
  #: admin/wp-security-database-menu.php:34
2824
+ msgid "Database backup"
2825
  msgstr "DB back-up"
2826
 
2827
  #: admin/wp-security-database-menu.php:185
3119
 
3120
  #: classes/grade-system/wp-security-feature-item-manager.php:65
3121
  #: admin/wp-security-database-menu.php:33
3122
+ msgid "Database prefix"
3123
  msgstr "DB-prefix"
3124
 
3125
  #: admin/wp-security-database-menu.php:136
3126
+ msgid "Change database prefix"
3127
  msgstr "Verander database-prefix"
3128
 
3129
  #: admin/wp-security-database-menu.php:148
3130
+ msgid "Database prefix options"
3131
  msgstr "Opties database-prefix"
3132
 
3133
  #: admin/wp-security-dashboard-menu.php:204
3171
  msgstr "wp-config.php is succesvol bijgewerkt!"
3172
 
3173
  #: admin/wp-security-database-menu.php:535
3174
+ msgid "The database prefix change tasks have been completed."
3175
  msgstr "Het veranderen van de DB-prefix is afgerond."
3176
 
3177
  #: admin/wp-security-dashboard-menu.php:423
3266
  msgstr "<strong>FOUT</strong>: het tabel voorvoegsel kan alleen cijfers, letters en underscores bevatten."
3267
 
3268
  #: admin/wp-security-database-menu.php:139
3269
+ msgid "Your WordPress database is the most important asset of your website because it contains a lot of your site's precious information."
3270
  msgstr "Uw WordPress-DB is de belangrijkste troef van uw website, omdat deze heel veel kostbare informatie van uw site bevat."
3271
 
3272
  #: admin/wp-security-database-menu.php:140
3273
+ msgid "The database is also a target for hackers via methods such as SQL injections and malicious and automated code which targets certain tables."
3274
  msgstr "De DB is ook een doelwit voor hackers via methoden zoals SQL-injecties en kwaadaardige en geautomatiseerde code die zich op bepaalde tabellen richt."
3275
 
3276
  #: admin/wp-security-database-menu.php:141
3895
  msgstr "Backup-intervaltijd"
3896
 
3897
  #: admin/wp-security-database-menu.php:168
3898
+ msgid "Current database table prefix"
3899
  msgstr "Huidige database-prefix"
3900
 
3901
  #: admin/wp-security-database-menu.php:181
3902
+ msgid "Generate new database table prefix"
3903
  msgstr "Nieuwe database-prefix aanmaken"
3904
 
3905
  #: admin/wp-security-database-menu.php:184
3907
  msgstr "Activeer dit als u wilt dat de plug-in een willekeurige tekenreeks van 6 karakters voor de tabel-prefix genereert"
3908
 
3909
  #: admin/wp-security-database-menu.php:191
3910
+ msgid "Change database prefix"
3911
  msgstr "Verander database-prefix"
3912
 
3913
  #: admin/wp-security-database-menu.php:229
languages/all-in-one-wp-security-and-firewall-pl_PL.po CHANGED
@@ -1218,13 +1218,13 @@ msgstr ""
1218
  #: admin/wp-security-database-menu.php:26
1219
  #: admin/wp-security-database-menu.php:31
1220
  #: classes/grade-system/wp-security-feature-item-manager.php:62
1221
- msgid "DB Backup"
1222
  msgstr ""
1223
 
1224
  # @ all-in-one-wp-security-and-firewall
1225
  #: admin/wp-security-database-menu.php:30
1226
  #: classes/grade-system/wp-security-feature-item-manager.php:60
1227
- msgid "DB Prefix"
1228
  msgstr ""
1229
 
1230
  # @ all-in-one-wp-security-and-firewall
@@ -1249,17 +1249,17 @@ msgstr ""
1249
 
1250
  # @ all-in-one-wp-security-and-firewall
1251
  #: admin/wp-security-database-menu.php:131
1252
- msgid "Change Database Prefix"
1253
  msgstr ""
1254
 
1255
  # @ all-in-one-wp-security-and-firewall
1256
  #: admin/wp-security-database-menu.php:134
1257
- msgid "Your WordPress DB is the most important asset of your website because it contains a lot of your site's precious information."
1258
  msgstr ""
1259
 
1260
  # @ all-in-one-wp-security-and-firewall
1261
  #: admin/wp-security-database-menu.php:135
1262
- msgid "The DB is also a target for hackers via methods such as SQL injections and malicious and automated code which targets certain tables."
1263
  msgstr ""
1264
 
1265
  # @ all-in-one-wp-security-and-firewall
@@ -1274,7 +1274,7 @@ msgstr ""
1274
 
1275
  # @ all-in-one-wp-security-and-firewall
1276
  #: admin/wp-security-database-menu.php:143
1277
- msgid "DB Prefix Options"
1278
  msgstr ""
1279
 
1280
  # @ all-in-one-wp-security-and-firewall
@@ -1285,7 +1285,7 @@ msgstr ""
1285
 
1286
  # @ all-in-one-wp-security-and-firewall
1287
  #: admin/wp-security-database-menu.php:163
1288
- msgid "Current DB Table Prefix"
1289
  msgstr ""
1290
 
1291
  # @ all-in-one-wp-security-and-firewall
@@ -1297,7 +1297,7 @@ msgstr ""
1297
 
1298
  # @ all-in-one-wp-security-and-firewall
1299
  #: admin/wp-security-database-menu.php:176
1300
- msgid "Generate New DB Table Prefix"
1301
  msgstr ""
1302
 
1303
  # @ all-in-one-wp-security-and-firewall
@@ -1317,7 +1317,7 @@ msgstr ""
1317
 
1318
  # @ all-in-one-wp-security-and-firewall
1319
  #: admin/wp-security-database-menu.php:186
1320
- msgid "Change DB Prefix"
1321
  msgstr ""
1322
 
1323
  # @ all-in-one-wp-security-and-firewall
@@ -1524,7 +1524,7 @@ msgstr ""
1524
 
1525
  # @ all-in-one-wp-security-and-firewall
1526
  #: admin/wp-security-database-menu.php:513
1527
- msgid "DB prefix change tasks have been completed."
1528
  msgstr ""
1529
 
1530
  # @ all-in-one-wp-security-and-firewall
@@ -3835,7 +3835,7 @@ msgstr ""
3835
 
3836
  # @ all-in-one-wp-security-and-firewall
3837
  #: admin/wp-security-user-login-menu.php:277
3838
- msgid "All records from the Failed Logins table were deleted successfully!"
3839
  msgstr ""
3840
 
3841
  # @ all-in-one-wp-security-and-firewall
1218
  #: admin/wp-security-database-menu.php:26
1219
  #: admin/wp-security-database-menu.php:31
1220
  #: classes/grade-system/wp-security-feature-item-manager.php:62
1221
+ msgid "Database backup"
1222
  msgstr ""
1223
 
1224
  # @ all-in-one-wp-security-and-firewall
1225
  #: admin/wp-security-database-menu.php:30
1226
  #: classes/grade-system/wp-security-feature-item-manager.php:60
1227
+ msgid "Database prefix"
1228
  msgstr ""
1229
 
1230
  # @ all-in-one-wp-security-and-firewall
1249
 
1250
  # @ all-in-one-wp-security-and-firewall
1251
  #: admin/wp-security-database-menu.php:131
1252
+ msgid "Change database prefix"
1253
  msgstr ""
1254
 
1255
  # @ all-in-one-wp-security-and-firewall
1256
  #: admin/wp-security-database-menu.php:134
1257
+ msgid "Your WordPress database is the most important asset of your website because it contains a lot of your site's precious information."
1258
  msgstr ""
1259
 
1260
  # @ all-in-one-wp-security-and-firewall
1261
  #: admin/wp-security-database-menu.php:135
1262
+ msgid "The database is also a target for hackers via methods such as SQL injections and malicious and automated code which targets certain tables."
1263
  msgstr ""
1264
 
1265
  # @ all-in-one-wp-security-and-firewall
1274
 
1275
  # @ all-in-one-wp-security-and-firewall
1276
  #: admin/wp-security-database-menu.php:143
1277
+ msgid "Database prefix options"
1278
  msgstr ""
1279
 
1280
  # @ all-in-one-wp-security-and-firewall
1285
 
1286
  # @ all-in-one-wp-security-and-firewall
1287
  #: admin/wp-security-database-menu.php:163
1288
+ msgid "Current database table prefix"
1289
  msgstr ""
1290
 
1291
  # @ all-in-one-wp-security-and-firewall
1297
 
1298
  # @ all-in-one-wp-security-and-firewall
1299
  #: admin/wp-security-database-menu.php:176
1300
+ msgid "Generate new database table prefix"
1301
  msgstr ""
1302
 
1303
  # @ all-in-one-wp-security-and-firewall
1317
 
1318
  # @ all-in-one-wp-security-and-firewall
1319
  #: admin/wp-security-database-menu.php:186
1320
+ msgid "Change database prefix"
1321
  msgstr ""
1322
 
1323
  # @ all-in-one-wp-security-and-firewall
1524
 
1525
  # @ all-in-one-wp-security-and-firewall
1526
  #: admin/wp-security-database-menu.php:513
1527
+ msgid "The database prefix change tasks have been completed."
1528
  msgstr ""
1529
 
1530
  # @ all-in-one-wp-security-and-firewall
3835
 
3836
  # @ all-in-one-wp-security-and-firewall
3837
  #: admin/wp-security-user-login-menu.php:277
3838
+ msgid "All records from the Failed Logins table were deleted successfully."
3839
  msgstr ""
3840
 
3841
  # @ all-in-one-wp-security-and-firewall
languages/all-in-one-wp-security-and-firewall-pt_BR.po CHANGED
@@ -1121,12 +1121,12 @@ msgstr "Arquivo de log está vazio!"
1121
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:26
1122
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:31
1123
  #: ../../plugins/all-in-one-wp-security-and-firewall/classes/grade-system/wp-security-feature-item-manager.php:63
1124
- msgid "DB Backup"
1125
  msgstr "Backup do BD"
1126
 
1127
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:30
1128
  #: ../../plugins/all-in-one-wp-security-and-firewall/classes/grade-system/wp-security-feature-item-manager.php:61
1129
- msgid "DB Prefix"
1130
  msgstr "Prefixo de BD"
1131
 
1132
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:94
@@ -1146,15 +1146,15 @@ msgid "<strong>ERROR</strong>: The table prefix can only contain numbers, letter
1146
  msgstr "<strong>ERRO</strong>: O prefixo da tabela pode conter apenas números, letras e sublinhados."
1147
 
1148
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:132
1149
- msgid "Change Database Prefix"
1150
  msgstr "Alterar prefixo do banco de dados"
1151
 
1152
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:135
1153
- msgid "Your WordPress DB is the most important asset of your website because it contains a lot of your site's precious information."
1154
  msgstr "Seu banco de dados WordPress é o ativo mais importante de seu site, porque ele contém muitas informações preciosas do seu site."
1155
 
1156
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:136
1157
- msgid "The DB is also a target for hackers via methods such as SQL injections and malicious and automated code which targets certain tables."
1158
  msgstr "O banco de dados também é um alvo para hackers através de métodos tais como injeções de SQL e código malicioso e automatizado que tem como alvo determinadas tabelas."
1159
 
1160
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:137
@@ -1166,7 +1166,7 @@ msgid "This feature allows you to easily change the prefix to a value of your ch
1166
  msgstr "Esse recurso permite que você altere facilmente o prefixo para um valor de sua escolha ou para um valor aleatório definido por este plugin."
1167
 
1168
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:144
1169
- msgid "DB Prefix Options"
1170
  msgstr "Opções de prefixo do banco de dados"
1171
 
1172
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:155
@@ -1175,7 +1175,7 @@ msgid "It is recommended that you perform a %s before using this feature"
1175
  msgstr "É recomendável que você execute um %s antes de usar este recurso"
1176
 
1177
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:164
1178
- msgid "Current DB Table Prefix"
1179
  msgstr "Atual prefixo da tabela BD"
1180
 
1181
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:170
@@ -1187,7 +1187,7 @@ msgstr ""
1187
  " Para aumentar a segurança do seu site, você deve considerar mudar o valor de prefixo do banco de dados para outro valor."
1188
 
1189
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:177
1190
- msgid "Generate New DB Table Prefix"
1191
  msgstr "Gerar novo prefixo da tabela BD"
1192
 
1193
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:180
@@ -1203,7 +1203,7 @@ msgid "Choose your own DB prefix by specifying a string which contains letters a
1203
  msgstr "Escolha o seu próprio prefixo do banco de dados especificando uma sequência de caracteres que contém letras, números ou sublinhados. Exemplo: xyz_"
1204
 
1205
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:187
1206
- msgid "Change DB Prefix"
1207
  msgstr "Alterar prefixo do banco de dados"
1208
 
1209
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:208
@@ -1380,7 +1380,7 @@ msgid "The usermeta table records which had references to the old DB prefix were
1380
  msgstr "Os registros da tabela usermeta que tinham referências ao antigo prefixo DB foram atualizados com sucesso!"
1381
 
1382
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:531
1383
- msgid "DB prefix change tasks have been completed."
1384
  msgstr "As tarefas de mudança de prefixo do banco de dados foram concluídas."
1385
 
1386
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-filescan-menu.php:24
@@ -3765,7 +3765,7 @@ msgid "User Login Feature - Delete all failed login records operation failed!"
3765
  msgstr "Recurso de login de usuário - Excluir todas as falhas de operação de registros de falhas de login!"
3766
 
3767
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-user-login-menu.php:277
3768
- msgid "All records from the Failed Logins table were deleted successfully!"
3769
  msgstr "Todos os registros da tabela falhas de logins foram excluídos com sucesso!"
3770
 
3771
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-user-login-menu.php:292
1121
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:26
1122
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:31
1123
  #: ../../plugins/all-in-one-wp-security-and-firewall/classes/grade-system/wp-security-feature-item-manager.php:63
1124
+ msgid "Database backup"
1125
  msgstr "Backup do BD"
1126
 
1127
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:30
1128
  #: ../../plugins/all-in-one-wp-security-and-firewall/classes/grade-system/wp-security-feature-item-manager.php:61
1129
+ msgid "Database prefix"
1130
  msgstr "Prefixo de BD"
1131
 
1132
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:94
1146
  msgstr "<strong>ERRO</strong>: O prefixo da tabela pode conter apenas números, letras e sublinhados."
1147
 
1148
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:132
1149
+ msgid "Change database prefix"
1150
  msgstr "Alterar prefixo do banco de dados"
1151
 
1152
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:135
1153
+ msgid "Your WordPress database is the most important asset of your website because it contains a lot of your site's precious information."
1154
  msgstr "Seu banco de dados WordPress é o ativo mais importante de seu site, porque ele contém muitas informações preciosas do seu site."
1155
 
1156
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:136
1157
+ msgid "The database is also a target for hackers via methods such as SQL injections and malicious and automated code which targets certain tables."
1158
  msgstr "O banco de dados também é um alvo para hackers através de métodos tais como injeções de SQL e código malicioso e automatizado que tem como alvo determinadas tabelas."
1159
 
1160
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:137
1166
  msgstr "Esse recurso permite que você altere facilmente o prefixo para um valor de sua escolha ou para um valor aleatório definido por este plugin."
1167
 
1168
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:144
1169
+ msgid "Database prefix options"
1170
  msgstr "Opções de prefixo do banco de dados"
1171
 
1172
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:155
1175
  msgstr "É recomendável que você execute um %s antes de usar este recurso"
1176
 
1177
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:164
1178
+ msgid "Current database table prefix"
1179
  msgstr "Atual prefixo da tabela BD"
1180
 
1181
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:170
1187
  " Para aumentar a segurança do seu site, você deve considerar mudar o valor de prefixo do banco de dados para outro valor."
1188
 
1189
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:177
1190
+ msgid "Generate new database table prefix"
1191
  msgstr "Gerar novo prefixo da tabela BD"
1192
 
1193
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:180
1203
  msgstr "Escolha o seu próprio prefixo do banco de dados especificando uma sequência de caracteres que contém letras, números ou sublinhados. Exemplo: xyz_"
1204
 
1205
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:187
1206
+ msgid "Change database prefix"
1207
  msgstr "Alterar prefixo do banco de dados"
1208
 
1209
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:208
1380
  msgstr "Os registros da tabela usermeta que tinham referências ao antigo prefixo DB foram atualizados com sucesso!"
1381
 
1382
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-database-menu.php:531
1383
+ msgid "The database prefix change tasks have been completed."
1384
  msgstr "As tarefas de mudança de prefixo do banco de dados foram concluídas."
1385
 
1386
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-filescan-menu.php:24
3765
  msgstr "Recurso de login de usuário - Excluir todas as falhas de operação de registros de falhas de login!"
3766
 
3767
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-user-login-menu.php:277
3768
+ msgid "All records from the Failed Logins table were deleted successfully."
3769
  msgstr "Todos os registros da tabela falhas de logins foram excluídos com sucesso!"
3770
 
3771
  #: ../../plugins/all-in-one-wp-security-and-firewall/admin/wp-security-user-login-menu.php:292
languages/all-in-one-wp-security-and-firewall-ru_RU.po CHANGED
@@ -1316,12 +1316,12 @@ msgstr "В настоящий момент заблокированны IP-ад
1316
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:26
1317
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:31
1318
  #: all-in-one-wp-security/classes/grade-system/wp-security-feature-item-manager.php:63
1319
- msgid "DB Backup"
1320
  msgstr "Резервное копирование БД"
1321
 
1322
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:30
1323
  #: all-in-one-wp-security/classes/grade-system/wp-security-feature-item-manager.php:61
1324
- msgid "DB Prefix"
1325
  msgstr "Префикс таблиц БД"
1326
 
1327
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:93
@@ -1351,7 +1351,7 @@ msgstr ""
1351
  "цифры и символ подчеркивания."
1352
 
1353
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:131
1354
- msgid "Change Database Prefix"
1355
  msgstr "Изменение префикса таблиц базы данных"
1356
 
1357
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:134
@@ -1390,7 +1390,7 @@ msgstr ""
1390
  "значение или на сгенерированное плагином."
1391
 
1392
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:143
1393
- msgid "DB Prefix Options"
1394
  msgstr "Опции изменения префикса таблиц БД"
1395
 
1396
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:154
@@ -1399,7 +1399,7 @@ msgid "It is recommended that you perform a %s before using this feature"
1399
  msgstr "Рекомендуется перед изменением префикса создать %s базы данных"
1400
 
1401
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:163
1402
- msgid "Current DB Table Prefix"
1403
  msgstr "Текущий префикс таблиц БД"
1404
 
1405
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:169
@@ -1413,7 +1413,7 @@ msgstr ""
1413
  "Для усиления безопасности, измените его на любое другое значение."
1414
 
1415
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:176
1416
- msgid "Generate New DB Table Prefix"
1417
  msgstr "Сгенерировать новый префикс таблиц БД"
1418
 
1419
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:179
@@ -1436,7 +1436,7 @@ msgstr ""
1436
  "символ подчеркивания. Например: xyz_"
1437
 
1438
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:186
1439
- msgid "Change DB Prefix"
1440
  msgstr "Изменить префикс таблиц"
1441
 
1442
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:207
@@ -1673,7 +1673,7 @@ msgstr ""
1673
  "данных, успешно обновлены!"
1674
 
1675
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:530
1676
- msgid "DB prefix change tasks have been completed."
1677
  msgstr "Префикс таблиц базы данных успешно изменен."
1678
 
1679
  #: all-in-one-wp-security/admin/wp-security-filescan-menu.php:24
@@ -4773,7 +4773,7 @@ msgid "User Login Feature - Delete all failed login records operation failed!"
4773
  msgstr "Записи об ошибочных попытках авторизации удалить не удалось!"
4774
 
4775
  #: all-in-one-wp-security/admin/wp-security-user-login-menu.php:277
4776
- msgid "All records from the Failed Logins table were deleted successfully!"
4777
  msgstr "Все записи об ошибочных попытках авторизации удалены!"
4778
 
4779
  #: all-in-one-wp-security/admin/wp-security-user-login-menu.php:292
1316
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:26
1317
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:31
1318
  #: all-in-one-wp-security/classes/grade-system/wp-security-feature-item-manager.php:63
1319
+ msgid "Database backup"
1320
  msgstr "Резервное копирование БД"
1321
 
1322
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:30
1323
  #: all-in-one-wp-security/classes/grade-system/wp-security-feature-item-manager.php:61
1324
+ msgid "Database prefix"
1325
  msgstr "Префикс таблиц БД"
1326
 
1327
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:93
1351
  "цифры и символ подчеркивания."
1352
 
1353
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:131
1354
+ msgid "Change database prefix"
1355
  msgstr "Изменение префикса таблиц базы данных"
1356
 
1357
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:134
1390
  "значение или на сгенерированное плагином."
1391
 
1392
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:143
1393
+ msgid "Database prefix options"
1394
  msgstr "Опции изменения префикса таблиц БД"
1395
 
1396
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:154
1399
  msgstr "Рекомендуется перед изменением префикса создать %s базы данных"
1400
 
1401
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:163
1402
+ msgid "Current database table prefix"
1403
  msgstr "Текущий префикс таблиц БД"
1404
 
1405
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:169
1413
  "Для усиления безопасности, измените его на любое другое значение."
1414
 
1415
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:176
1416
+ msgid "Generate new database table prefix"
1417
  msgstr "Сгенерировать новый префикс таблиц БД"
1418
 
1419
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:179
1436
  "символ подчеркивания. Например: xyz_"
1437
 
1438
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:186
1439
+ msgid "Change database prefix"
1440
  msgstr "Изменить префикс таблиц"
1441
 
1442
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:207
1673
  "данных, успешно обновлены!"
1674
 
1675
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:530
1676
+ msgid "The database prefix change tasks have been completed."
1677
  msgstr "Префикс таблиц базы данных успешно изменен."
1678
 
1679
  #: all-in-one-wp-security/admin/wp-security-filescan-menu.php:24
4773
  msgstr "Записи об ошибочных попытках авторизации удалить не удалось!"
4774
 
4775
  #: all-in-one-wp-security/admin/wp-security-user-login-menu.php:277
4776
+ msgid "All records from the Failed Logins table were deleted successfully."
4777
  msgstr "Все записи об ошибочных попытках авторизации удалены!"
4778
 
4779
  #: all-in-one-wp-security/admin/wp-security-user-login-menu.php:292
languages/all-in-one-wp-security-and-firewall-sv_SE.po CHANGED
@@ -1146,12 +1146,12 @@ msgstr ""
1146
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:26
1147
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:31
1148
  #: all-in-one-wp-security/classes/grade-system/wp-security-feature-item-manager.php:63
1149
- msgid "DB Backup"
1150
  msgstr ""
1151
 
1152
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:30
1153
  #: all-in-one-wp-security/classes/grade-system/wp-security-feature-item-manager.php:61
1154
- msgid "DB Prefix"
1155
  msgstr ""
1156
 
1157
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:93
@@ -1176,7 +1176,7 @@ msgid ""
1176
  msgstr ""
1177
 
1178
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:131
1179
- msgid "Change Database Prefix"
1180
  msgstr ""
1181
 
1182
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:134
@@ -1205,7 +1205,7 @@ msgid ""
1205
  msgstr ""
1206
 
1207
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:143
1208
- msgid "DB Prefix Options"
1209
  msgstr ""
1210
 
1211
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:154
@@ -1214,7 +1214,7 @@ msgid "It is recommended that you perform a %s before using this feature"
1214
  msgstr ""
1215
 
1216
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:163
1217
- msgid "Current DB Table Prefix"
1218
  m
1146
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:26
1147
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:31
1148
  #: all-in-one-wp-security/classes/grade-system/wp-security-feature-item-manager.php:63
1149
+ msgid "Database backup"
1150
  msgstr ""
1151
 
1152
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:30
1153
  #: all-in-one-wp-security/classes/grade-system/wp-security-feature-item-manager.php:61
1154
+ msgid "Database prefix"
1155
  msgstr ""
1156
 
1157
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:93
1176
  msgstr ""
1177
 
1178
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:131
1179
+ msgid "Change database prefix"
1180
  msgstr ""
1181
 
1182
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:134
1205
  msgstr ""
1206
 
1207
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:143
1208
+ msgid "Database prefix options"
1209
  msgstr ""
1210
 
1211
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:154
1214
  msgstr ""
1215
 
1216
  #: all-in-one-wp-security/admin/wp-security-database-menu.php:163
1217
+ msgid "Current database table prefix"
1218
  m