Antispam Bee - Version 2.10.0

Version Description

Download this release

Release Info

Developer pluginkollektiv
Plugin Icon 128x128 Antispam Bee
Version 2.10.0
Comparing to
See all releases

Code changes from version 2.9.4 to 2.10.0

CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
  ## Changelog ##
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  ### 2.9.4 ###
4
  * **English**
5
  * Enhancement: Add filter to allow ajax calls
@@ -136,7 +159,7 @@
136
  * Minor interface improvements
137
  * Remove old russian and Dutch translation files
138
  * For more details see https://github.com/pluginkollektiv/antispam-bee/milestone/4?closed=1
139
-
140
  * **Deutsch**
141
  - Entfernt stopforumspam.com zur Vorbeugung möglicher DSGVO-Verletzungen
142
  - Verändert den Umgang mit IP-Adressen um der DSGVO zu entsprechen
@@ -146,7 +169,7 @@
146
  - Verberesserungen an der Benutzeroberfläche
147
  - Entfernt alte russische und holländische Sprachversionen
148
  - Mehr Details: https://github.com/pluginkollektiv/antispam-bee/milestone/4?closed=1
149
-
150
  ### 2.7.1 ###
151
 
152
  * **English**
@@ -524,7 +547,7 @@
524
  * **Deutsch**
525
  * Kompatibilität mit WPtouch
526
  * Unterstützung für do_action hinzugefügt
527
- * Übersetzung auf brasilianisches Portugiesisch
528
 
529
  ### 1.4 ###
530
  * **English**
1
  ## Changelog ##
2
 
3
+ ### 2.10.0 ###
4
+ * **English**
5
+ * Fix: Switch from ip2country.info to iplocate.io for country check
6
+ * Enhancement: Use filter to add the honeypot field instead of output buffering for new installations and added option to switch between the both ways
7
+ * Tweak: Added comment user agent to regex pattern check
8
+ * Tweak: Make the ping detection filterable to support new comment types
9
+ * Tweak: Updated internal documentation links
10
+ * Tweak: Several updates and optimizations in the testing process
11
+ * Tweak: Adjust color palette to recent WP version
12
+ * Tweak: Adjust wording in variables and option names
13
+ * Readme: Add new contributor and clean up unused code
14
+
15
+ * **Deutsch**
16
+ * Fix: Wechsel von ip2country.info zu iplocate.io für die Länderprüfung
17
+ * Verbesserung: Bei neuen Installationen wird ein Filter zum Hinzufügen des Honeypot-Felds genutzt statt Output-Buffering. Es wurde eine Option hinzugefügt, zwischen den beiden Wegen zu wechseln
18
+ * Tweak: Kommentar User-Agent zu Regex-Pattern hinzugefügt
19
+ * Tweak: Die Ping-Erkennung ist jetzt filterbar, um neue Kommentartypen zu unterstützen
20
+ * Tweak: Aktualisierte Links zur internen Dokumentation
21
+ * Tweak: Verschiedene Aktualisierungen und Optimierungen im Testprozess
22
+ * Tweak: Farbpalette an aktuelle WP-Version anpassen
23
+ * Tweak: Wortlaut in Variablen und Optionsnamen wurden angepasst
24
+ * Readme: Neuer Contributor hinzugefügt und unbenutzten Code bereinigt
25
+
26
  ### 2.9.4 ###
27
  * **English**
28
  * Enhancement: Add filter to allow ajax calls
159
  * Minor interface improvements
160
  * Remove old russian and Dutch translation files
161
  * For more details see https://github.com/pluginkollektiv/antispam-bee/milestone/4?closed=1
162
+
163
  * **Deutsch**
164
  - Entfernt stopforumspam.com zur Vorbeugung möglicher DSGVO-Verletzungen
165
  - Verändert den Umgang mit IP-Adressen um der DSGVO zu entsprechen
169
  - Verberesserungen an der Benutzeroberfläche
170
  - Entfernt alte russische und holländische Sprachversionen
171
  - Mehr Details: https://github.com/pluginkollektiv/antispam-bee/milestone/4?closed=1
172
+
173
  ### 2.7.1 ###
174
 
175
  * **English**
547
  * **Deutsch**
548
  * Kompatibilität mit WPtouch
549
  * Unterstützung für do_action hinzugefügt
550
+ * Übersetzung auf brasilianisches Portugiesisch
551
 
552
  ### 1.4 ###
553
  * **English**
README.md DELETED
@@ -1,65 +0,0 @@
1
- # Antispam Bee #
2
-
3
- [![Build Status](https://travis-ci.org/pluginkollektiv/antispam-bee.svg?branch=master)](https://travis-ci.org/pluginkollektiv/antispam-bee) [![Current Antispam Bee version](https://img.shields.io/wordpress/plugin/v/antispam-bee.svg)](https://wordpress.org/plugins/antispam-bee/) [![Number of downloads](https://img.shields.io/wordpress/plugin/dt/antispam-bee.svg)](https://wordpress.org/plugins/antispam-bee/advanced/) [![Number of active installs](https://img.shields.io/wordpress/plugin/installs/antispam-bee.svg)](https://wordpress.org/plugins/antispam-bee/advanced/) [![WordPress plugin rating](https://img.shields.io/wordpress/plugin/r/antispam-bee.svg)](https://wordpress.org/plugins/antispam-bee/#reviews) [![Donate with PayPal](https://img.shields.io/badge/PayPal-Donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=TD4AMD2D8EMZW)
4
-
5
- Antispam plugin with a sophisticated toolset for effective day to day comment and trackback spam-fighting. Built with data protection and privacy in mind.
6
-
7
- ## Description ##
8
- Say Goodbye to comment spam on your WordPress blog or website. *Antispam Bee* blocks spam comments and trackbacks effectively and without captchas. It is free of charge, ad-free and compliant with European data privacy standards.
9
-
10
- ### Feature/Settings Overview ###
11
- * Trust approved commenters.
12
- * Trust commenters with a Gravatar.
13
- * Consider the comment time.
14
- * Treat BBCode links as spam.
15
- * Validate the IP address of commenters.
16
- * Use regular expressions.
17
- * Search local spam database for commenters previously marked as spammers.
18
- * Notify admins by e-mail about incoming spam.
19
- * Delete existing spam after n days.
20
- * Limit approval to comments/pings (will delete other comment types).
21
- * Select spam indicators to send comments to deletion directly.
22
- * Optionally exclude trackbacks and pingbacks from spam detection.
23
- * Optionally spam-check comment forms on archive pages.
24
- * Display spam statistics on the dashboard, including daily updates of spam detection rate and a total of blocked spam comments.
25
-
26
- ### Support ###
27
- * Community support via the [support forums on wordpress.org](https://wordpress.org/support/plugin/antispam-bee)
28
- * We don’t handle support via e-mail, Twitter, GitHub issues etc.
29
-
30
- ### Contribute ###
31
- * Active development of this plugin is handled [on GitHub](https://github.com/pluginkollektiv/antispam-bee).
32
- * Pull requests for documented bugs are highly appreciated.
33
- * If you think you’ve found a bug (e.g. you’re experiencing unexpected behavior), please post at the [support forums](https://wordpress.org/support/plugin/antispam-bee) first.
34
- * If you want to help us translate this plugin you can do so [on WordPress Translate](https://translate.wordpress.org/projects/wp-plugins/antispam-bee).
35
- * To test the plugin on your local machine, simply run `docker-compose up` in the root folder and open `http://localhost:8081/` in your browser.
36
-
37
- ### Donate
38
- [Donate for us via Paypal](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=TD4AMD2D8EMZW)
39
-
40
- ### Credits ###
41
- * Author: [Sergej Müller](https://sergejmueller.github.io/)
42
- * Maintainers: [pluginkollektiv](https://pluginkollektiv.org)
43
-
44
- ## Installation ##
45
- * If you don’t know how to install a plugin for WordPress, [here’s how](http://codex.wordpress.org/Managing_Plugins#Installing_Plugins).
46
-
47
- ### Requirements ###
48
- * PHP 5.2.4 or greater
49
- * WordPress 4.5 or greater
50
-
51
- ### Settings ###
52
- After you have activated *Antispam Bee* the plugin will block spam comments out of the box. However, you may want to visit *Settings → Antispam Bee* to configure your custom set of anti-spam options that works best for your site.
53
-
54
- ### Privacy Notice ###
55
- On sites operating from within the EU the option *Use a public antispam database* should not be activated for privacy reasons. When that option has been activated, *Antispam Bee* will match full IP addresses from comments against a public spam database. Technically it is not possible to encrypt those IPs, because spam databases only store and operate with complete, unencrypted IP addresses.
56
-
57
- ## Frequently Asked Questions ##
58
-
59
- Please have a look [in the FAQ pages](https://github.com/pluginkollektiv/antispam-bee/wiki/en-FAQ).
60
-
61
- A complete documentation is available in the [GitHub repository Wiki](https://github.com/pluginkollektiv/antispam-bee/wiki).
62
-
63
- ## Changelog ##
64
-
65
- [Changelog](https://github.com/pluginkollektiv/antispam-bee/blob/master/CHANGELOG.md).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
antispam_bee.php CHANGED
@@ -9,9 +9,7 @@
9
  * Domain Path: /lang
10
  * License: GPLv2 or later
11
  * License URI: http://www.gnu.org/licenses/gpl-2.0.html
12
- * Version: 2.9.4
13
- *
14
- * [](http://coderisk.com/wp/plugin/antispam-bee/RIPS-lAHLcgvqY8)
15
  *
16
  * @package Antispam Bee
17
  **/
@@ -41,8 +39,8 @@ defined( 'ABSPATH' ) || exit;
41
  /**
42
  * Antispam_Bee
43
  *
44
- * @since 0.1
45
- * @change 2.4
46
  */
47
  class Antispam_Bee {
48
 
@@ -58,7 +56,7 @@ class Antispam_Bee {
58
  *
59
  * @var int
60
  */
61
- private static $db_version = 1.01;
62
 
63
  /**
64
  * The base.
@@ -91,10 +89,31 @@ class Antispam_Bee {
91
  /**
92
  * "Constructor" of the class
93
  *
94
- * @since 0.1
95
- * @change 2.6.4
 
96
  */
97
  public static function init() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  add_action(
99
  'unspam_comment',
100
  array(
@@ -302,13 +321,24 @@ class Antispam_Bee {
302
  )
303
  );
304
 
305
- add_action(
306
- 'template_redirect',
307
- array(
308
- __CLASS__,
309
- 'prepare_comment_field',
310
- )
311
- );
 
 
 
 
 
 
 
 
 
 
 
312
  add_action(
313
  'init',
314
  array(
@@ -345,13 +375,16 @@ class Antispam_Bee {
345
  /**
346
  * Action during the activation of the Plugins
347
  *
348
- * @since 0.1
349
- * @change 2.4
 
350
  */
351
  public static function activate() {
352
  add_option(
353
  'antispam_bee',
354
- array(),
 
 
355
  '',
356
  'no'
357
  );
@@ -365,8 +398,8 @@ class Antispam_Bee {
365
  /**
366
  * Action to deactivate the plugin
367
  *
368
- * @since 0.1
369
- * @change 2.4
370
  */
371
  public static function deactivate() {
372
  self::clear_scheduled_hook();
@@ -376,8 +409,7 @@ class Antispam_Bee {
376
  /**
377
  * Action deleting the plugin
378
  *
379
- * @since 2.4
380
- * @change 2.4
381
  */
382
  public static function uninstall() {
383
  if ( ! self::get_option( 'delete_data_on_uninstall' ) ) {
@@ -388,10 +420,10 @@ class Antispam_Bee {
388
  delete_option( 'antispam_bee' );
389
  $wpdb->query( 'OPTIMIZE TABLE `' . $wpdb->options . '`' );
390
 
391
- //phpcs:disable WordPress.WP.PreparedSQL.NotPrepared
392
  $sql = 'delete from `' . $wpdb->commentmeta . '` where `meta_key` IN ("antispam_bee_iphash", "antispam_bee_reason")';
393
  $wpdb->query( $sql );
394
- //phpcs:enable WordPress.WP.PreparedSQL.NotPrepared
395
  }
396
 
397
 
@@ -405,8 +437,9 @@ class Antispam_Bee {
405
  /**
406
  * Initialization of the internal variables
407
  *
408
- * @since 2.4
409
- * @change 2.7.0
 
410
  */
411
  private static function _init_internal_vars() {
412
  self::$_base = plugin_basename( __FILE__ );
@@ -422,14 +455,13 @@ class Antispam_Bee {
422
  'gravatar_check' => 0,
423
  'time_check' => 0,
424
  'ignore_pings' => 0,
425
- 'always_allowed' => 0,
426
 
427
  'dashboard_chart' => 0,
428
  'dashboard_count' => 0,
429
 
430
  'country_code' => 0,
431
- 'country_black' => '',
432
- 'country_white' => '',
433
 
434
  'translate_api' => 0,
435
  'translate_lang' => array(),
@@ -470,26 +502,44 @@ class Antispam_Bee {
470
  * Check and return an array key
471
  *
472
  * @since 2.4.2
473
- * @change 2.4.2
474
  *
475
  * @param array $array Array with values.
476
  * @param string $key Name of the key.
477
  * @return mixed Value of the requested key.
478
  */
479
  public static function get_key( $array, $key ) {
480
- if ( empty( $array ) || empty( $key ) || empty( $array[ $key ] ) ) {
481
  return null;
482
  }
483
 
484
  return $array[ $key ];
485
  }
486
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487
 
488
  /**
489
  * Localization of the admin pages
490
  *
491
  * @since 0.1
492
- * @change 2.4
493
  *
494
  * @param string $page Mark the page.
495
  * @return boolean True on success.
@@ -522,8 +572,8 @@ class Antispam_Bee {
522
  /**
523
  * Integration of the localization file
524
  *
525
- * @since 0.1
526
- * @change 2.4
527
  */
528
  public static function load_plugin_lang() {
529
  load_plugin_textdomain(
@@ -535,8 +585,7 @@ class Antispam_Bee {
535
  /**
536
  * Add the link to the settings
537
  *
538
- * @since 1.1
539
- * @change 1.1
540
  *
541
  * @param array $data The action link array.
542
  * @return array $data The action link array.
@@ -566,8 +615,8 @@ class Antispam_Bee {
566
  /**
567
  * Meta links of the plugin
568
  *
569
- * @since 0.1
570
- * @change 2.6.2
571
  *
572
  * @param array $input Existing links.
573
  * @param string $file Current page.
@@ -596,8 +645,8 @@ class Antispam_Bee {
596
  /**
597
  * Registration of resources (CSS & JS)
598
  *
599
- * @since 1.6
600
- * @change 2.4.5
601
  */
602
  public static function init_plugin_sources() {
603
  $plugin = get_plugin_data( __FILE__ );
@@ -621,8 +670,8 @@ class Antispam_Bee {
621
  /**
622
  * Initialization of the option page
623
  *
624
- * @since 0.1
625
- * @change 2.4.3
626
  */
627
  public static function add_sidebar_menu() {
628
  $page = add_options_page(
@@ -665,8 +714,8 @@ class Antispam_Bee {
665
  /**
666
  * Initialization of JavaScript
667
  *
668
- * @since 1.6
669
- * @change 2.4
670
  */
671
  public static function add_options_script() {
672
  wp_enqueue_script( 'ab_script' );
@@ -676,8 +725,8 @@ class Antispam_Bee {
676
  /**
677
  * Initialization of Stylesheets
678
  *
679
- * @since 1.6
680
- * @change 2.4
681
  */
682
  public static function add_options_style() {
683
  wp_enqueue_style( 'ab_style' );
@@ -688,7 +737,6 @@ class Antispam_Bee {
688
  * Integration of the GUI
689
  *
690
  * @since 2.4
691
- * @change 2.4
692
  */
693
  public static function init_options_page() {
694
  require_once dirname( __FILE__ ) . '/inc/gui.class.php';
@@ -705,8 +753,8 @@ class Antispam_Bee {
705
  /**
706
  * Display the spam counter on the dashboard
707
  *
708
- * @since 0.1
709
- * @change 2.6.5
710
  *
711
  * @param array $items Initial array with dashboard items.
712
  * @return array $items Merged array with dashboard items.
@@ -732,8 +780,8 @@ class Antispam_Bee {
732
  /**
733
  * Initialize the dashboard chart
734
  *
735
- * @since 1.9
736
- * @change 2.5.6
737
  */
738
  public static function add_dashboard_chart() {
739
  if ( ! current_user_can( 'publish_posts' ) || ! self::get_option( 'dashboard_chart' ) ) {
@@ -761,8 +809,8 @@ class Antispam_Bee {
761
  /**
762
  * Print dashboard styles
763
  *
764
- * @since 1.9.0
765
- * @change 2.5.8
766
  */
767
  public static function add_dashboard_style() {
768
  $plugin = get_plugin_data( __FILE__ );
@@ -781,8 +829,8 @@ class Antispam_Bee {
781
  /**
782
  * Print dashboard scripts
783
  *
784
- * @since 1.9.0
785
- * @change 2.5.8
786
  */
787
  public static function add_dashboard_script() {
788
  if ( ! self::get_option( 'daily_stats' ) ) {
@@ -819,8 +867,8 @@ class Antispam_Bee {
819
  /**
820
  * Print dashboard html
821
  *
822
- * @since 1.9.0
823
- * @change 2.5.8
824
  */
825
  public static function show_spam_chart() {
826
  $items = (array) self::get_option( 'daily_stats' );
@@ -866,8 +914,8 @@ class Antispam_Bee {
866
  /**
867
  * Get all plugin options
868
  *
869
- * @since 2.4
870
- * @change 2.6.1
871
  *
872
  * @return array $options Array with option fields.
873
  */
@@ -893,8 +941,8 @@ class Antispam_Bee {
893
  /**
894
  * Get single option field
895
  *
896
- * @since 0.1
897
- * @change 2.4.2
898
  *
899
  * @param string $field Field name.
900
  * @return mixed Field value.
@@ -909,8 +957,8 @@ class Antispam_Bee {
909
  /**
910
  * Update single option field
911
  *
912
- * @since 0.1
913
- * @change 2.4
914
  *
915
  * @param string $field Field name.
916
  * @param mixed $value The Field value.
@@ -927,8 +975,8 @@ class Antispam_Bee {
927
  /**
928
  * Update multiple option fields
929
  *
930
- * @since 0.1
931
- * @change 2.6.1
932
  *
933
  * @param array $data Array with plugin option fields.
934
  */
@@ -966,8 +1014,8 @@ class Antispam_Bee {
966
  /**
967
  * Execution of the daily cronjobs
968
  *
969
- * @since 0.1
970
- * @change 2.4
971
  */
972
  public static function start_daily_cronjob() {
973
  if ( ! self::get_option( 'cronjob_enable' ) ) {
@@ -986,8 +1034,8 @@ class Antispam_Bee {
986
  /**
987
  * Delete old spam comments
988
  *
989
- * @since 0.1
990
- * @change 2.4
991
  */
992
  private static function _delete_old_spam() {
993
  $days = (int) self::get_option( 'cronjob_interval' );
@@ -1012,8 +1060,8 @@ class Antispam_Bee {
1012
  /**
1013
  * Initialization of the cronjobs
1014
  *
1015
- * @since 0.1
1016
- * @change 2.4
1017
  */
1018
  public static function init_scheduled_hook() {
1019
  if ( ! wp_next_scheduled( 'antispam_bee_daily_cronjob' ) ) {
@@ -1029,8 +1077,8 @@ class Antispam_Bee {
1029
  /**
1030
  * Deletion of the cronjobs
1031
  *
1032
- * @since 0.1
1033
- * @change 2.4
1034
  */
1035
  public static function clear_scheduled_hook() {
1036
  if ( wp_next_scheduled( 'antispam_bee_daily_cronjob' ) ) {
@@ -1049,11 +1097,11 @@ class Antispam_Bee {
1049
  /**
1050
  * Check POST values
1051
  *
1052
- * @since 0.1
1053
- * @change 2.6.3
1054
  */
1055
  public static function precheck_incoming_request() {
1056
- // phpcs:disable WordPress.CSRF.NonceVerification.NoNonceVerification
1057
  if ( is_feed() || is_trackback() || empty( $_POST ) || self::_is_mobile() ) {
1058
  return;
1059
  }
@@ -1075,15 +1123,16 @@ class Antispam_Bee {
1075
  } else {
1076
  $_POST['ab_spam__hidden_field'] = 1;
1077
  }
1078
- // phpcs:enable WordPress.CSRF.NonceVerification.NoNonceVerification
1079
  }
1080
 
1081
 
1082
  /**
1083
  * Check incoming requests for spam
1084
  *
1085
- * @since 0.1
1086
- * @change 2.6.3
 
1087
  *
1088
  * @param array $comment Untreated comment.
1089
  * @return array $comment Treated comment.
@@ -1101,15 +1150,12 @@ class Antispam_Bee {
1101
  );
1102
  }
1103
 
1104
- $ping = array(
1105
- 'types' => array( 'pingback', 'trackback', 'pings' ),
1106
- 'allowed' => ! self::get_option( 'ignore_pings' ),
1107
- );
1108
 
1109
- // phpcs:disable WordPress.CSRF.NonceVerification.NoNonceVerification
1110
  // Everybody can post.
1111
  if ( strpos( $request_path, 'wp-comments-post.php' ) !== false && ! empty( $_POST ) ) {
1112
- // phpcs:enable WordPress.CSRF.NonceVerification.NoNonceVerification
1113
  $status = self::_verify_comment_request( $comment );
1114
 
1115
  if ( ! empty( $status['reason'] ) ) {
@@ -1118,7 +1164,7 @@ class Antispam_Bee {
1118
  $status['reason']
1119
  );
1120
  }
1121
- } elseif ( in_array( self::get_key( $comment, 'comment_type' ), $ping['types'], true ) && $ping['allowed'] ) {
1122
  $status = self::_verify_trackback_request( $comment );
1123
 
1124
  if ( ! empty( $status['reason'] ) ) {
@@ -1133,43 +1179,37 @@ class Antispam_Bee {
1133
  return $comment;
1134
  }
1135
 
1136
-
1137
  /**
1138
- * Prepares the replacement of the comment field
1139
  *
1140
- * @since 0.1
1141
- * @change 2.4
1142
  */
1143
- public static function prepare_comment_field() {
1144
  if ( is_feed() || is_trackback() || is_robots() || self::_is_mobile() ) {
1145
  return;
1146
  }
1147
 
1148
- if ( ! is_singular() && ! self::get_option( 'always_allowed' ) ) {
1149
- return;
1150
- }
1151
-
1152
  ob_start(
1153
  array(
1154
  'Antispam_Bee',
1155
- 'replace_comment_field',
1156
  )
1157
  );
1158
  }
1159
 
1160
 
1161
  /**
1162
- * Replaces the comment field
1163
  *
1164
- * @since 2.4
1165
- * @change 2.6.4
 
1166
  *
1167
- * @param string $data HTML code of the website.
1168
- * @return string Treated HTML code.
1169
  */
1170
- public static function replace_comment_field( $data ) {
1171
  if ( empty( $data ) ) {
1172
- return;
1173
  }
1174
 
1175
  if ( ! preg_match( '#<textarea.+?name=["\']comment["\']#s', $data ) ) {
@@ -1250,8 +1290,8 @@ class Antispam_Bee {
1250
  /**
1251
  * Check the trackbacks
1252
  *
1253
- * @since 2.4
1254
- * @change 2.7.0
1255
  *
1256
  * @param array $comment Trackback data.
1257
  * @return array Array with suspected reason.
@@ -1370,18 +1410,20 @@ class Antispam_Bee {
1370
  /**
1371
  * Check the comment
1372
  *
1373
- * @since 2.4
1374
- * @change 2.7.0
 
1375
  *
1376
  * @param array $comment Data of the comment.
1377
  * @return array|void Array with suspected reason
1378
  */
1379
  private static function _verify_comment_request( $comment ) {
1380
- $ip = self::get_key( $comment, 'comment_author_IP' );
1381
- $url = self::get_key( $comment, 'comment_author_url' );
1382
- $body = self::get_key( $comment, 'comment_content' );
1383
- $email = self::get_key( $comment, 'comment_author_email' );
1384
- $author = self::get_key( $comment, 'comment_author' );
 
1385
 
1386
  if ( empty( $body ) ) {
1387
  return array(
@@ -1411,13 +1453,13 @@ class Antispam_Bee {
1411
  return;
1412
  }
1413
 
1414
- // phpcs:disable WordPress.CSRF.NonceVerification.NoNonceVerification
1415
  if ( ! empty( $_POST['ab_spam__hidden_field'] ) ) {
1416
  return array(
1417
  'reason' => 'css',
1418
  );
1419
  }
1420
- // phpcs:enable WordPress.CSRF.NonceVerification.NoNonceVerification
1421
 
1422
  if ( $options['time_check'] && self::_is_shortest_time() ) {
1423
  return array(
@@ -1433,12 +1475,13 @@ class Antispam_Bee {
1433
 
1434
  if ( $options['regexp_check'] && self::_is_regexp_spam(
1435
  array(
1436
- 'ip' => $ip,
1437
- 'rawurl' => $url,
1438
- 'host' => self::parse_url( $url, 'host' ),
1439
- 'body' => $body,
1440
- 'email' => $email,
1441
- 'author' => $author,
 
1442
  )
1443
  ) ) {
1444
  return array(
@@ -1469,8 +1512,7 @@ class Antispam_Bee {
1469
  /**
1470
  * Check for a Gravatar image
1471
  *
1472
- * @since 2.6.5
1473
- * @change 2.6.5
1474
  *
1475
  * @param string $email Input email.
1476
  * @return boolean Check status (true = Gravatar available).
@@ -1499,15 +1541,14 @@ class Antispam_Bee {
1499
  * Check for comment action time
1500
  *
1501
  * @since 2.6.4
1502
- * @change 2.6.4
1503
  *
1504
  * @return boolean TRUE if the action time is less than 5 seconds
1505
  */
1506
  private static function _is_shortest_time() {
1507
- // phpcs:disable WordPress.CSRF.NonceVerification.NoNonceVerification
1508
  // Everybody can Post.
1509
  $init_time = (int) self::get_key( $_POST, 'ab_init_time' );
1510
- // phpcs:enable WordPress.CSRF.NonceVerification.NoNonceVerification
1511
  if ( 0 === $init_time ) {
1512
  return false;
1513
  }
@@ -1541,8 +1582,9 @@ class Antispam_Bee {
1541
  /**
1542
  * Usage of regexp, also custom
1543
  *
1544
- * @since 2.5.2
1545
- * @change 2.5.6
 
1546
  *
1547
  * @param array $comment Array with commentary data.
1548
  * @return boolean True for suspicious comment.
@@ -1554,6 +1596,7 @@ class Antispam_Bee {
1554
  'body',
1555
  'email',
1556
  'author',
 
1557
  );
1558
 
1559
  $patterns = array(
@@ -1652,8 +1695,8 @@ class Antispam_Bee {
1652
  /**
1653
  * Review a comment on its existence in the local spam
1654
  *
1655
- * @since 2.0.0
1656
- * @change 2.5.4
1657
  *
1658
  * @param string $ip Comment IP.
1659
  * @param string $url Comment URL (optional).
@@ -1682,7 +1725,7 @@ class Antispam_Bee {
1682
  return false;
1683
  }
1684
 
1685
- // phpcs:disable WordPress.WP.PreparedSQL.NotPrepared
1686
  // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
1687
  $filter_sql = implode( ' OR ', $filter );
1688
 
@@ -1696,7 +1739,7 @@ class Antispam_Bee {
1696
  )
1697
  );
1698
  // phpcs:enable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
1699
- // phpcs:enable WordPress.WP.PreparedSQL.NotPrepared
1700
 
1701
  return ! empty( $result );
1702
  }
@@ -1705,8 +1748,8 @@ class Antispam_Bee {
1705
  /**
1706
  * Check for country spam by (anonymized) IP
1707
  *
1708
- * @since 2.6.9
1709
- * @change 2.6.9
1710
  *
1711
  * @param string $ip IP address.
1712
  * @return boolean True if the comment is spam based on country filter.
@@ -1714,28 +1757,58 @@ class Antispam_Bee {
1714
  private static function _is_country_spam( $ip ) {
1715
  $options = self::get_options();
1716
 
1717
- $white = preg_split(
1718
  '/[\s,;]+/',
1719
- $options['country_white'],
1720
  -1,
1721
  PREG_SPLIT_NO_EMPTY
1722
  );
1723
- $black = preg_split(
1724
  '/[\s,;]+/',
1725
- $options['country_black'],
1726
  -1,
1727
  PREG_SPLIT_NO_EMPTY
1728
  );
1729
 
1730
- if ( empty( $white ) && empty( $black ) ) {
1731
  return false;
1732
  }
1733
 
1734
- $response = wp_safe_remote_head(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1735
  esc_url_raw(
1736
  sprintf(
1737
- 'https://api.ip2country.info/ip?%s',
1738
- self::_anonymize_ip( $ip )
 
1739
  ),
1740
  'https'
1741
  )
@@ -1749,17 +1822,30 @@ class Antispam_Bee {
1749
  return false;
1750
  }
1751
 
1752
- $country = (string) wp_remote_retrieve_header( $response, 'x-country-code' );
 
 
 
 
 
 
 
 
 
 
 
 
 
1753
 
1754
  if ( empty( $country ) || strlen( $country ) !== 2 ) {
1755
  return false;
1756
  }
1757
 
1758
- if ( ! empty( $black ) ) {
1759
- return ( in_array( $country, $black, true ) );
1760
  }
1761
 
1762
- return ( ! in_array( $country, $white, true ) );
1763
  }
1764
 
1765
 
@@ -1767,7 +1853,6 @@ class Antispam_Bee {
1767
  * Check for BBCode spam
1768
  *
1769
  * @since 2.5.1
1770
- * @change 2.5.1
1771
  *
1772
  * @param string $body Content of a comment.
1773
  * @return boolean True for BBCode in content
@@ -1780,8 +1865,8 @@ class Antispam_Bee {
1780
  /**
1781
  * Check for an already approved e-mail address
1782
  *
1783
- * @since 2.0
1784
- * @change 2.5.1
1785
  *
1786
  * @param string $email E-mail address.
1787
  * @return boolean True for a found entry.
@@ -1806,9 +1891,9 @@ class Antispam_Bee {
1806
  /**
1807
  * Check for unwanted languages
1808
  *
1809
- * @since 2.0
1810
- * @change 2.6.6
1811
- * @change 2.8.2
1812
  *
1813
  * @param string $comment_content Content of the comment.
1814
  *
@@ -1846,7 +1931,7 @@ class Antispam_Bee {
1846
  * enter 'characters_excluding_spaces' or 'characters_including_spaces'. Otherwise, enter 'words'.
1847
  * Do not translate into your own language.
1848
  */
1849
- if ( strpos( _x( 'words', 'Word count type. Do not translate!' ), 'characters' ) === 0 && preg_match( '/^utf\-?8$/i', get_option( 'blog_charset' ) ) ) {
1850
  preg_match_all( '/./u', $text, $words_array );
1851
  if ( isset( $words_array[0] ) ) {
1852
  $word_count = count( $words_array[0] );
@@ -2091,8 +2176,8 @@ class Antispam_Bee {
2091
  /**
2092
  * Trim IP addresses
2093
  *
2094
- * @since 0.1
2095
- * @change 2.5.1
2096
  *
2097
  * @param string $ip Original IP.
2098
  * @param boolean $cut_end Shortening the end.
@@ -2113,7 +2198,6 @@ class Antispam_Bee {
2113
  * Anonymize the IP addresses
2114
  *
2115
  * @since 2.5.1
2116
- * @change 2.5.1
2117
  *
2118
  * @param string $ip Original IP.
2119
  * @return string Anonymous IP.
@@ -2131,7 +2215,6 @@ class Antispam_Bee {
2131
  * Rotates the IP address
2132
  *
2133
  * @since 2.4.5
2134
- * @change 2.4.5
2135
  *
2136
  * @param string $ip IP address.
2137
  * @return string Turned IP address.
@@ -2152,8 +2235,8 @@ class Antispam_Bee {
2152
  /**
2153
  * Check for an IPv4 address
2154
  *
2155
- * @since 2.4
2156
- * @change 2.6.4
2157
  *
2158
  * @param string $ip IP to validate.
2159
  * @return integer TRUE if IPv4.
@@ -2170,8 +2253,8 @@ class Antispam_Bee {
2170
  /**
2171
  * Check for an IPv6 address
2172
  *
2173
- * @since 2.6.2
2174
- * @change 2.6.4
2175
  *
2176
  * @param string $ip IP to validate.
2177
  * @return boolean TRUE if IPv6.
@@ -2188,8 +2271,8 @@ class Antispam_Bee {
2188
  /**
2189
  * Testing on mobile devices
2190
  *
2191
- * @since 0.1
2192
- * @change 2.4
2193
  *
2194
  * @return boolean TRUE if "wptouch" is active
2195
  */
@@ -2220,13 +2303,13 @@ class Antispam_Bee {
2220
  /**
2221
  * Execution of the delete/marking process
2222
  *
2223
- * @since 0.1
2224
- * @change 2.6.0
2225
  *
2226
  * @param array $comment Untreated commentary data.
2227
  * @param string $reason Reason for suspicion.
2228
  * @param boolean $is_ping Ping (optional).
2229
- * @return array $comment Treated commentary data.
2230
  */
2231
  private static function _handle_spam_request( $comment, $reason, $is_ping = false ) {
2232
 
@@ -2296,8 +2379,8 @@ class Antispam_Bee {
2296
  /**
2297
  * Logfile with detected spam
2298
  *
2299
- * @since 2.5.7
2300
- * @change 2.6.1
2301
  *
2302
  * @param array $comment Array with commentary data.
2303
  * @return mixed FALSE in case of error
@@ -2327,7 +2410,6 @@ class Antispam_Bee {
2327
  * Sends the 403 header and terminates the connection
2328
  *
2329
  * @since 2.5.6
2330
- * @change 2.5.6
2331
  */
2332
  private static function _go_in_peace() {
2333
  status_header( 403 );
@@ -2339,12 +2421,11 @@ class Antispam_Bee {
2339
  * Return real client IP
2340
  *
2341
  * @since 2.6.1
2342
- * @change 2.6.1
2343
  *
2344
  * @return mixed $ip Client IP
2345
  */
2346
  public static function get_client_ip() {
2347
- // phpcs:disable WordPress.VIP.ValidatedSanitizedInput.InputNotSanitized
2348
  // Sanitization of $ip takes place further down.
2349
  $ip = '';
2350
 
@@ -2371,7 +2452,7 @@ class Antispam_Bee {
2371
  }
2372
 
2373
  return '';
2374
- // phpcs:enable WordPress.VIP.ValidatedSanitizedInput.InputNotSanitized
2375
  }
2376
 
2377
  /**
@@ -2406,7 +2487,6 @@ class Antispam_Bee {
2406
  * Add spam reason as comment data
2407
  *
2408
  * @since 2.6.0
2409
- * @change 2.6.0
2410
  *
2411
  * @param integer $comment_id Comment ID.
2412
  */
@@ -2423,7 +2503,6 @@ class Antispam_Bee {
2423
  * Delete spam reason as comment data
2424
  *
2425
  * @since 2.6.0
2426
- * @change 2.6.0
2427
  *
2428
  * @param integer $comment_id Comment ID.
2429
  */
@@ -2461,8 +2540,9 @@ class Antispam_Bee {
2461
  /**
2462
  * Send notification via e-mail
2463
  *
2464
- * @since 0.1
2465
- * @change 2.5.7
 
2466
  *
2467
  * @hook string antispam_bee_notification_subject Custom subject for notification mails
2468
  *
@@ -2492,6 +2572,7 @@ class Antispam_Bee {
2492
  $subject = sprintf(
2493
  '[%s] %s',
2494
  stripslashes_deep(
 
2495
  html_entity_decode(
2496
  get_bloginfo( 'name' ),
2497
  ENT_QUOTES
@@ -2570,7 +2651,7 @@ class Antispam_Bee {
2570
  ) . sprintf(
2571
  "%s\r\n%s\r\n",
2572
  esc_html__( 'Notify message by Antispam Bee', 'antispam-bee' ),
2573
- esc_html__( 'https://antispambee.com', 'antispam-bee' )
2574
  );
2575
 
2576
  wp_mail(
@@ -2609,8 +2690,8 @@ class Antispam_Bee {
2609
  /**
2610
  * Return the number of spam comments
2611
  *
2612
- * @since 0.1
2613
- * @change 2.4
2614
  */
2615
  private static function _get_spam_count() {
2616
  // Init.
@@ -2624,8 +2705,8 @@ class Antispam_Bee {
2624
  /**
2625
  * Output the number of spam comments
2626
  *
2627
- * @since 0.1
2628
- * @change 2.4
2629
  */
2630
  public static function the_spam_count() {
2631
  echo esc_html( self::_get_spam_count() );
@@ -2635,8 +2716,8 @@ class Antispam_Bee {
2635
  /**
2636
  * Update the number of spam comments
2637
  *
2638
- * @since 0.1
2639
- * @change 2.6.1
2640
  */
2641
  private static function _update_spam_count() {
2642
  // Skip if not enabled.
@@ -2653,8 +2734,8 @@ class Antispam_Bee {
2653
  /**
2654
  * Update statistics
2655
  *
2656
- * @since 1.9
2657
- * @change 2.6.1
2658
  */
2659
  private static function _update_daily_stats() {
2660
  // Skip if not enabled.
@@ -2686,17 +2767,14 @@ class Antispam_Bee {
2686
  /**
2687
  * Returns the secret of a post used in the textarea name attribute.
2688
  *
 
 
2689
  * @param int $post_id The Post ID.
2690
  *
2691
  * @return string
2692
  */
2693
  public static function get_secret_name_for_post( $post_id ) {
2694
-
2695
- if ( self::get_option( 'always_allowed' ) ) {
2696
- $secret = substr( sha1( md5( 'comment-id' . self::$_salt ) ), 0, 10 );
2697
- } else {
2698
- $secret = substr( sha1( md5( 'comment-id' . self::$_salt . (int) $post_id ) ), 0, 10 );
2699
- }
2700
 
2701
  $secret = self::ensure_secret_starts_with_letter( $secret );
2702
 
@@ -2719,17 +2797,15 @@ class Antispam_Bee {
2719
  /**
2720
  * Returns the secret of a post used in the textarea id attribute.
2721
  *
 
 
2722
  * @param int $post_id The post ID.
2723
  *
2724
  * @return string
2725
  */
2726
  public static function get_secret_id_for_post( $post_id ) {
2727
 
2728
- if ( self::get_option( 'always_allowed' ) ) {
2729
- $secret = substr( sha1( md5( 'comment-id' . self::$_salt ) ), 0, 10 );
2730
- } else {
2731
- $secret = substr( sha1( md5( 'comment-id' . self::$_salt . (int) $post_id ) ), 0, 10 );
2732
- }
2733
 
2734
  $secret = self::ensure_secret_starts_with_letter( $secret );
2735
 
@@ -2795,22 +2871,51 @@ class Antispam_Bee {
2795
 
2796
  /**
2797
  * Updates the database structure if necessary
 
 
2798
  */
2799
  public static function update_database() {
2800
  if ( self::db_version_is_current() ) {
2801
  return;
2802
  }
2803
 
2804
- global $wpdb;
 
 
2805
 
2806
- /**
2807
- * In Version 2.9 the IP of the commenter was saved as a hash. We reverted this solution.
2808
- * Therefore, we need to delete this unused data.
2809
- */
2810
- //phpcs:disable WordPress.WP.PreparedSQL.NotPrepared
2811
- $sql = 'delete from `' . $wpdb->commentmeta . '` where `meta_key` IN ("antispam_bee_iphash")';
2812
- $wpdb->query( $sql );
2813
- //phpcs:enable WordPress.WP.PreparedSQL.NotPrepared
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2814
 
2815
  update_option( 'antispambee_db_version', self::$db_version );
2816
  }
@@ -2818,14 +2923,79 @@ class Antispam_Bee {
2818
  /**
2819
  * Whether the database structure is up to date.
2820
  *
 
 
2821
  * @return bool
2822
  */
2823
  private static function db_version_is_current() {
2824
 
2825
- $current_version = absint( get_option( 'antispambee_db_version', 0 ) );
2826
  return $current_version === self::$db_version;
2827
 
2828
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2829
  }
2830
 
2831
 
9
  * Domain Path: /lang
10
  * License: GPLv2 or later
11
  * License URI: http://www.gnu.org/licenses/gpl-2.0.html
12
+ * Version: 2.10.0
 
 
13
  *
14
  * @package Antispam Bee
15
  **/
39
  /**
40
  * Antispam_Bee
41
  *
42
+ * @since 0.1
43
+ * @since 2.4
44
  */
45
  class Antispam_Bee {
46
 
56
  *
57
  * @var int
58
  */
59
+ private static $db_version = 1.02;
60
 
61
  /**
62
  * The base.
89
  /**
90
  * "Constructor" of the class
91
  *
92
+ * @since 0.1
93
+ * @since 2.6.4
94
+ * @since 2.10.0 Change handling of comment field honeypot and call functions after completed upgrades
95
  */
96
  public static function init() {
97
+ add_action(
98
+ 'upgrader_process_complete',
99
+ array(
100
+ __CLASS__,
101
+ 'upgrades_completed',
102
+ ),
103
+ 10,
104
+ 2
105
+ );
106
+
107
+ add_action(
108
+ 'upgrader_overwrote_package',
109
+ array(
110
+ __CLASS__,
111
+ 'uploaded_upgrade_completed',
112
+ ),
113
+ 10,
114
+ 3
115
+ );
116
+
117
  add_action(
118
  'unspam_comment',
119
  array(
321
  )
322
  );
323
 
324
+ if ( 1 == self::get_option( 'use_output_buffer' ) || null === self::get_option( 'use_output_buffer' ) ) {
325
+ add_action(
326
+ 'template_redirect',
327
+ array(
328
+ __CLASS__,
329
+ 'prepare_comment_field_output_buffering',
330
+ )
331
+ );
332
+ } else {
333
+ add_filter(
334
+ 'comment_form_field_comment',
335
+ array(
336
+ __CLASS__,
337
+ 'prepare_comment_field',
338
+ )
339
+ );
340
+ }
341
+
342
  add_action(
343
  'init',
344
  array(
375
  /**
376
  * Action during the activation of the Plugins
377
  *
378
+ * @since 0.1
379
+ * @since 2.4
380
+ * @since 2.10.0 Set `use_output_buffer` option to `0`
381
  */
382
  public static function activate() {
383
  add_option(
384
  'antispam_bee',
385
+ array(
386
+ 'use_output_buffer' => 0,
387
+ ),
388
  '',
389
  'no'
390
  );
398
  /**
399
  * Action to deactivate the plugin
400
  *
401
+ * @since 0.1
402
+ * @since 2.4
403
  */
404
  public static function deactivate() {
405
  self::clear_scheduled_hook();
409
  /**
410
  * Action deleting the plugin
411
  *
412
+ * @since 2.4
 
413
  */
414
  public static function uninstall() {
415
  if ( ! self::get_option( 'delete_data_on_uninstall' ) ) {
420
  delete_option( 'antispam_bee' );
421
  $wpdb->query( 'OPTIMIZE TABLE `' . $wpdb->options . '`' );
422
 
423
+ //phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
424
  $sql = 'delete from `' . $wpdb->commentmeta . '` where `meta_key` IN ("antispam_bee_iphash", "antispam_bee_reason")';
425
  $wpdb->query( $sql );
426
+ //phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
427
  }
428
 
429
 
437
  /**
438
  * Initialization of the internal variables
439
  *
440
+ * @since 2.4
441
+ * @since 2.7.0
442
+ * @since 2.10.0 Change renamed country option names in options array
443
  */
444
  private static function _init_internal_vars() {
445
  self::$_base = plugin_basename( __FILE__ );
455
  'gravatar_check' => 0,
456
  'time_check' => 0,
457
  'ignore_pings' => 0,
 
458
 
459
  'dashboard_chart' => 0,
460
  'dashboard_count' => 0,
461
 
462
  'country_code' => 0,
463
+ 'country_denied' => '',
464
+ 'country_allowed' => '',
465
 
466
  'translate_api' => 0,
467
  'translate_lang' => array(),
502
  * Check and return an array key
503
  *
504
  * @since 2.4.2
505
+ * @since 2.10.0 Only return `null` if option does not exist.
506
  *
507
  * @param array $array Array with values.
508
  * @param string $key Name of the key.
509
  * @return mixed Value of the requested key.
510
  */
511
  public static function get_key( $array, $key ) {
512
+ if ( empty( $array ) || empty( $key ) || ! isset( $array[ $key ] ) ) {
513
  return null;
514
  }
515
 
516
  return $array[ $key ];
517
  }
518
 
519
+ /**
520
+ * Check if comment is a ping (pingback, trackback or something similar)
521
+ *
522
+ * @since 2.10.0
523
+ *
524
+ * @param array $comment Treated commentary data.
525
+ * @return boolean `true` if ping and `false` if classic comment
526
+ */
527
+ public static function is_ping( $comment ) {
528
+ $types = array( 'pingback', 'trackback', 'pings' );
529
+ $is_ping = false;
530
+
531
+ if ( in_array( self::get_key( $comment, 'comment_type' ), $types, true ) ) {
532
+ $is_ping = true;
533
+ }
534
+
535
+ return apply_filters( 'antispam_bee_is_ping', $is_ping, $comment );
536
+ }
537
 
538
  /**
539
  * Localization of the admin pages
540
  *
541
  * @since 0.1
542
+ * @since 2.4
543
  *
544
  * @param string $page Mark the page.
545
  * @return boolean True on success.
572
  /**
573
  * Integration of the localization file
574
  *
575
+ * @since 0.1
576
+ * @since 2.4
577
  */
578
  public static function load_plugin_lang() {
579
  load_plugin_textdomain(
585
  /**
586
  * Add the link to the settings
587
  *
588
+ * @since 1.1
 
589
  *
590
  * @param array $data The action link array.
591
  * @return array $data The action link array.
615
  /**
616
  * Meta links of the plugin
617
  *
618
+ * @since 0.1
619
+ * @since 2.6.2
620
  *
621
  * @param array $input Existing links.
622
  * @param string $file Current page.
645
  /**
646
  * Registration of resources (CSS & JS)
647
  *
648
+ * @since 1.6
649
+ * @since 2.4.5
650
  */
651
  public static function init_plugin_sources() {
652
  $plugin = get_plugin_data( __FILE__ );
670
  /**
671
  * Initialization of the option page
672
  *
673
+ * @since 0.1
674
+ * @since 2.4.3
675
  */
676
  public static function add_sidebar_menu() {
677
  $page = add_options_page(
714
  /**
715
  * Initialization of JavaScript
716
  *
717
+ * @since 1.6
718
+ * @since 2.4
719
  */
720
  public static function add_options_script() {
721
  wp_enqueue_script( 'ab_script' );
725
  /**
726
  * Initialization of Stylesheets
727
  *
728
+ * @since 1.6
729
+ * @since 2.4
730
  */
731
  public static function add_options_style() {
732
  wp_enqueue_style( 'ab_style' );
737
  * Integration of the GUI
738
  *
739
  * @since 2.4
 
740
  */
741
  public static function init_options_page() {
742
  require_once dirname( __FILE__ ) . '/inc/gui.class.php';
753
  /**
754
  * Display the spam counter on the dashboard
755
  *
756
+ * @since 0.1
757
+ * @since 2.6.5
758
  *
759
  * @param array $items Initial array with dashboard items.
760
  * @return array $items Merged array with dashboard items.
780
  /**
781
  * Initialize the dashboard chart
782
  *
783
+ * @since 1.9
784
+ * @since 2.5.6
785
  */
786
  public static function add_dashboard_chart() {
787
  if ( ! current_user_can( 'publish_posts' ) || ! self::get_option( 'dashboard_chart' ) ) {
809
  /**
810
  * Print dashboard styles
811
  *
812
+ * @since 1.9.0
813
+ * @since 2.5.8
814
  */
815
  public static function add_dashboard_style() {
816
  $plugin = get_plugin_data( __FILE__ );
829
  /**
830
  * Print dashboard scripts
831
  *
832
+ * @since 1.9.0
833
+ * @since 2.5.8
834
  */
835
  public static function add_dashboard_script() {
836
  if ( ! self::get_option( 'daily_stats' ) ) {
867
  /**
868
  * Print dashboard html
869
  *
870
+ * @since 1.9.0
871
+ * @since 2.5.8
872
  */
873
  public static function show_spam_chart() {
874
  $items = (array) self::get_option( 'daily_stats' );
914
  /**
915
  * Get all plugin options
916
  *
917
+ * @since 2.4
918
+ * @since 2.6.1
919
  *
920
  * @return array $options Array with option fields.
921
  */
941
  /**
942
  * Get single option field
943
  *
944
+ * @since 0.1
945
+ * @since 2.4.2
946
  *
947
  * @param string $field Field name.
948
  * @return mixed Field value.
957
  /**
958
  * Update single option field
959
  *
960
+ * @since 0.1
961
+ * @since 2.4
962
  *
963
  * @param string $field Field name.
964
  * @param mixed $value The Field value.
975
  /**
976
  * Update multiple option fields
977
  *
978
+ * @since 0.1
979
+ * @since 2.6.1
980
  *
981
  * @param array $data Array with plugin option fields.
982
  */
1014
  /**
1015
  * Execution of the daily cronjobs
1016
  *
1017
+ * @since 0.1
1018
+ * @since 2.4
1019
  */
1020
  public static function start_daily_cronjob() {
1021
  if ( ! self::get_option( 'cronjob_enable' ) ) {
1034
  /**
1035
  * Delete old spam comments
1036
  *
1037
+ * @since 0.1
1038
+ * @since 2.4
1039
  */
1040
  private static function _delete_old_spam() {
1041
  $days = (int) self::get_option( 'cronjob_interval' );
1060
  /**
1061
  * Initialization of the cronjobs
1062
  *
1063
+ * @since 0.1
1064
+ * @since 2.4
1065
  */
1066
  public static function init_scheduled_hook() {
1067
  if ( ! wp_next_scheduled( 'antispam_bee_daily_cronjob' ) ) {
1077
  /**
1078
  * Deletion of the cronjobs
1079
  *
1080
+ * @since 0.1
1081
+ * @since 2.4
1082
  */
1083
  public static function clear_scheduled_hook() {
1084
  if ( wp_next_scheduled( 'antispam_bee_daily_cronjob' ) ) {
1097
  /**
1098
  * Check POST values
1099
  *
1100
+ * @since 0.1
1101
+ * @since 2.6.3
1102
  */
1103
  public static function precheck_incoming_request() {
1104
+ // phpcs:disable WordPress.Security.NonceVerification.Missing
1105
  if ( is_feed() || is_trackback() || empty( $_POST ) || self::_is_mobile() ) {
1106
  return;
1107
  }
1123
  } else {
1124
  $_POST['ab_spam__hidden_field'] = 1;
1125
  }
1126
+ // phpcs:enable WordPress.Security.NonceVerification.Missing
1127
  }
1128
 
1129
 
1130
  /**
1131
  * Check incoming requests for spam
1132
  *
1133
+ * @since 0.1
1134
+ * @since 2.6.3
1135
+ * @since 2.10.0 Refactoring of code if pings are allowed and if is ping
1136
  *
1137
  * @param array $comment Untreated comment.
1138
  * @return array $comment Treated comment.
1150
  );
1151
  }
1152
 
1153
+ $pings_allowed = ! self::get_option( 'ignore_pings' );
 
 
 
1154
 
1155
+ // phpcs:disable WordPress.Security.NonceVerification.Missing
1156
  // Everybody can post.
1157
  if ( strpos( $request_path, 'wp-comments-post.php' ) !== false && ! empty( $_POST ) ) {
1158
+ // phpcs:enable WordPress.Security.NonceVerification.Missing
1159
  $status = self::_verify_comment_request( $comment );
1160
 
1161
  if ( ! empty( $status['reason'] ) ) {
1164
  $status['reason']
1165
  );
1166
  }
1167
+ } elseif ( self::is_ping( $comment ) && $pings_allowed ) {
1168
  $status = self::_verify_trackback_request( $comment );
1169
 
1170
  if ( ! empty( $status['reason'] ) ) {
1179
  return $comment;
1180
  }
1181
 
 
1182
  /**
1183
+ * Prepares the replacement of the comment field with output buffering.
1184
  *
1185
+ * @since 2.10.0
 
1186
  */
1187
+ public static function prepare_comment_field_output_buffering() {
1188
  if ( is_feed() || is_trackback() || is_robots() || self::_is_mobile() ) {
1189
  return;
1190
  }
1191
 
 
 
 
 
1192
  ob_start(
1193
  array(
1194
  'Antispam_Bee',
1195
+ 'prepare_comment_field',
1196
  )
1197
  );
1198
  }
1199
 
1200
 
1201
  /**
1202
+ * Prepares the replacement of the comment field
1203
  *
1204
+ * @since 0.1
1205
+ * @since 2.4
1206
+ * @since 2.10.0 Changes needed because of new way to add the honeypot field via filter instead of output buffering
1207
  *
1208
+ * @param string $data Markup of the comment field or whole page (depending on ob option).
 
1209
  */
1210
+ public static function prepare_comment_field( $data ) {
1211
  if ( empty( $data ) ) {
1212
+ return $data;
1213
  }
1214
 
1215
  if ( ! preg_match( '#<textarea.+?name=["\']comment["\']#s', $data ) ) {
1290
  /**
1291
  * Check the trackbacks
1292
  *
1293
+ * @since 2.4
1294
+ * @since 2.7.0
1295
  *
1296
  * @param array $comment Trackback data.
1297
  * @return array Array with suspected reason.
1410
  /**
1411
  * Check the comment
1412
  *
1413
+ * @since 2.4
1414
+ * @since 2.7.0
1415
+ * @since 2.10.0 Add useragent as data to regex check
1416
  *
1417
  * @param array $comment Data of the comment.
1418
  * @return array|void Array with suspected reason
1419
  */
1420
  private static function _verify_comment_request( $comment ) {
1421
+ $ip = self::get_key( $comment, 'comment_author_IP' );
1422
+ $url = self::get_key( $comment, 'comment_author_url' );
1423
+ $body = self::get_key( $comment, 'comment_content' );
1424
+ $email = self::get_key( $comment, 'comment_author_email' );
1425
+ $author = self::get_key( $comment, 'comment_author' );
1426
+ $useragent = self::get_key( $comment, 'comment_agent' );
1427
 
1428
  if ( empty( $body ) ) {
1429
  return array(
1453
  return;
1454
  }
1455
 
1456
+ // phpcs:disable WordPress.Security.NonceVerification.Missing
1457
  if ( ! empty( $_POST['ab_spam__hidden_field'] ) ) {
1458
  return array(
1459
  'reason' => 'css',
1460
  );
1461
  }
1462
+ // phpcs:enable WordPress.Security.NonceVerification.Missing
1463
 
1464
  if ( $options['time_check'] && self::_is_shortest_time() ) {
1465
  return array(
1475
 
1476
  if ( $options['regexp_check'] && self::_is_regexp_spam(
1477
  array(
1478
+ 'ip' => $ip,
1479
+ 'rawurl' => $url,
1480
+ 'host' => self::parse_url( $url, 'host' ),
1481
+ 'body' => $body,
1482
+ 'email' => $email,
1483
+ 'author' => $author,
1484
+ 'useragent' => $useragent,
1485
  )
1486
  ) ) {
1487
  return array(
1512
  /**
1513
  * Check for a Gravatar image
1514
  *
1515
+ * @since 2.6.5
 
1516
  *
1517
  * @param string $email Input email.
1518
  * @return boolean Check status (true = Gravatar available).
1541
  * Check for comment action time
1542
  *
1543
  * @since 2.6.4
 
1544
  *
1545
  * @return boolean TRUE if the action time is less than 5 seconds
1546
  */
1547
  private static function _is_shortest_time() {
1548
+ // phpcs:disable WordPress.Security.NonceVerification.Missing
1549
  // Everybody can Post.
1550
  $init_time = (int) self::get_key( $_POST, 'ab_init_time' );
1551
+ // phpcs:enable WordPress.Security.NonceVerification.Missing
1552
  if ( 0 === $init_time ) {
1553
  return false;
1554
  }
1582
  /**
1583
  * Usage of regexp, also custom
1584
  *
1585
+ * @since 2.5.2
1586
+ * @since 2.5.6
1587
+ * @since 2.10.0 Use useragent in check
1588
  *
1589
  * @param array $comment Array with commentary data.
1590
  * @return boolean True for suspicious comment.
1596
  'body',
1597
  'email',
1598
  'author',
1599
+ 'useragent',
1600
  );
1601
 
1602
  $patterns = array(
1695
  /**
1696
  * Review a comment on its existence in the local spam
1697
  *
1698
+ * @since 2.0.0
1699
+ * @since 2.5.4
1700
  *
1701
  * @param string $ip Comment IP.
1702
  * @param string $url Comment URL (optional).
1725
  return false;
1726
  }
1727
 
1728
+ // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
1729
  // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
1730
  $filter_sql = implode( ' OR ', $filter );
1731
 
1739
  )
1740
  );
1741
  // phpcs:enable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
1742
+ // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
1743
 
1744
  return ! empty( $result );
1745
  }
1748
  /**
1749
  * Check for country spam by (anonymized) IP
1750
  *
1751
+ * @since 2.6.9
1752
+ * @since 2.10.0 Make country check API filterable and use iplocate.io instead of ip2country.info
1753
  *
1754
  * @param string $ip IP address.
1755
  * @return boolean True if the comment is spam based on country filter.
1757
  private static function _is_country_spam( $ip ) {
1758
  $options = self::get_options();
1759
 
1760
+ $allowed = preg_split(
1761
  '/[\s,;]+/',
1762
+ $options['country_allowed'],
1763
  -1,
1764
  PREG_SPLIT_NO_EMPTY
1765
  );
1766
+ $denied = preg_split(
1767
  '/[\s,;]+/',
1768
+ $options['country_denied'],
1769
  -1,
1770
  PREG_SPLIT_NO_EMPTY
1771
  );
1772
 
1773
+ if ( empty( $allowed ) && empty( $denied ) ) {
1774
  return false;
1775
  }
1776
 
1777
+ /**
1778
+ * Filter to hook into the `_is_country_spam` functionality, to implement for example a custom IP check.
1779
+ *
1780
+ * @since 2.10.0
1781
+ *
1782
+ * @param null $is_country_spam The `is_country_spam` result.
1783
+ * @param string $ip The IP address.
1784
+ * @param array $allowed The list of allowed country codes.
1785
+ * @param array $denied The list of denied country codes.
1786
+ *
1787
+ * @return null|boolean The `is_country_spam` result or null.
1788
+ */
1789
+ $is_country_spam = apply_filters( 'antispam_bee_is_country_spam', null, $ip, $allowed, $denied );
1790
+
1791
+ if ( is_bool( $is_country_spam ) ) {
1792
+ return $is_country_spam;
1793
+ }
1794
+
1795
+ /**
1796
+ * Filters the IPLocate API key. With this filter, you can add your own IPLocate API key.
1797
+ *
1798
+ * @since 2.10.0
1799
+ *
1800
+ * @param string The current IPLocate API key. Default is `null`.
1801
+ *
1802
+ * @return string The changed IPLocate API key or null.
1803
+ */
1804
+ $apikey = apply_filters( 'antispam_bee_country_spam_apikey', '' );
1805
+
1806
+ $response = wp_safe_remote_get(
1807
  esc_url_raw(
1808
  sprintf(
1809
+ 'https://www.iplocate.io/api/lookup/%s?apikey=%s',
1810
+ self::_anonymize_ip( $ip ),
1811
+ $apikey
1812
  ),
1813
  'https'
1814
  )
1822
  return false;
1823
  }
1824
 
1825
+ $body = (string) wp_remote_retrieve_body( $response );
1826
+
1827
+ $json = json_decode( $body, true );
1828
+
1829
+ // Check if response is valid json.
1830
+ if ( ! is_array( $json ) ) {
1831
+ return false;
1832
+ }
1833
+
1834
+ if ( empty( $json['country_code'] ) ) {
1835
+ return false;
1836
+ }
1837
+
1838
+ $country = strtoupper( $json['country_code'] );
1839
 
1840
  if ( empty( $country ) || strlen( $country ) !== 2 ) {
1841
  return false;
1842
  }
1843
 
1844
+ if ( ! empty( $denied ) ) {
1845
+ return ( in_array( $country, $denied, true ) );
1846
  }
1847
 
1848
+ return ( ! in_array( $country, $allowed, true ) );
1849
  }
1850
 
1851
 
1853
  * Check for BBCode spam
1854
  *
1855
  * @since 2.5.1
 
1856
  *
1857
  * @param string $body Content of a comment.
1858
  * @return boolean True for BBCode in content
1865
  /**
1866
  * Check for an already approved e-mail address
1867
  *
1868
+ * @since 2.0
1869
+ * @since 2.5.1
1870
  *
1871
  * @param string $email E-mail address.
1872
  * @return boolean True for a found entry.
1891
  /**
1892
  * Check for unwanted languages
1893
  *
1894
+ * @since 2.0
1895
+ * @since 2.6.6
1896
+ * @since 2.8.2
1897
  *
1898
  * @param string $comment_content Content of the comment.
1899
  *
1931
  * enter 'characters_excluding_spaces' or 'characters_including_spaces'. Otherwise, enter 'words'.
1932
  * Do not translate into your own language.
1933
  */
1934
+ if ( strpos( _x( 'words', 'Word count type. Do not translate!' ), 'characters' ) === 0 && preg_match( '/^utf\-?8$/i', get_option( 'blog_charset' ) ) ) { // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1935
  preg_match_all( '/./u', $text, $words_array );
1936
  if ( isset( $words_array[0] ) ) {
1937
  $word_count = count( $words_array[0] );
2176
  /**
2177
  * Trim IP addresses
2178
  *
2179
+ * @since 0.1
2180
+ * @since 2.5.1
2181
  *
2182
  * @param string $ip Original IP.
2183
  * @param boolean $cut_end Shortening the end.
2198
  * Anonymize the IP addresses
2199
  *
2200
  * @since 2.5.1
 
2201
  *
2202
  * @param string $ip Original IP.
2203
  * @return string Anonymous IP.
2215
  * Rotates the IP address
2216
  *
2217
  * @since 2.4.5
 
2218
  *
2219
  * @param string $ip IP address.
2220
  * @return string Turned IP address.
2235
  /**
2236
  * Check for an IPv4 address
2237
  *
2238
+ * @since 2.4
2239
+ * @since 2.6.4
2240
  *
2241
  * @param string $ip IP to validate.
2242
  * @return integer TRUE if IPv4.
2253
  /**
2254
  * Check for an IPv6 address
2255
  *
2256
+ * @since 2.6.2
2257
+ * @since 2.6.4
2258
  *
2259
  * @param string $ip IP to validate.
2260
  * @return boolean TRUE if IPv6.
2271
  /**
2272
  * Testing on mobile devices
2273
  *
2274
+ * @since 0.1
2275
+ * @since 2.4
2276
  *
2277
  * @return boolean TRUE if "wptouch" is active
2278
  */
2303
  /**
2304
  * Execution of the delete/marking process
2305
  *
2306
+ * @since 0.1
2307
+ * @since 2.6.0
2308
  *
2309
  * @param array $comment Untreated commentary data.
2310
  * @param string $reason Reason for suspicion.
2311
  * @param boolean $is_ping Ping (optional).
2312
+ * @return array $comment Treated commentary data.
2313
  */
2314
  private static function _handle_spam_request( $comment, $reason, $is_ping = false ) {
2315
 
2379
  /**
2380
  * Logfile with detected spam
2381
  *
2382
+ * @since 2.5.7
2383
+ * @since 2.6.1
2384
  *
2385
  * @param array $comment Array with commentary data.
2386
  * @return mixed FALSE in case of error
2410
  * Sends the 403 header and terminates the connection
2411
  *
2412
  * @since 2.5.6
 
2413
  */
2414
  private static function _go_in_peace() {
2415
  status_header( 403 );
2421
  * Return real client IP
2422
  *
2423
  * @since 2.6.1
 
2424
  *
2425
  * @return mixed $ip Client IP
2426
  */
2427
  public static function get_client_ip() {
2428
+ // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
2429
  // Sanitization of $ip takes place further down.
2430
  $ip = '';
2431
 
2452
  }
2453
 
2454
  return '';
2455
+ // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
2456
  }
2457
 
2458
  /**
2487
  * Add spam reason as comment data
2488
  *
2489
  * @since 2.6.0
 
2490
  *
2491
  * @param integer $comment_id Comment ID.
2492
  */
2503
  * Delete spam reason as comment data
2504
  *
2505
  * @since 2.6.0
 
2506
  *
2507
  * @param integer $comment_id Comment ID.
2508
  */
2540
  /**
2541
  * Send notification via e-mail
2542
  *
2543
+ * @since 0.1
2544
+ * @since 2.5.7
2545
+ * @since 2.10.0 Change plugin website URL
2546
  *
2547
  * @hook string antispam_bee_notification_subject Custom subject for notification mails
2548
  *
2572
  $subject = sprintf(
2573
  '[%s] %s',
2574
  stripslashes_deep(
2575
+ // phpcs:ignore PHPCompatibility.ParameterValues.NewHTMLEntitiesEncodingDefault.NotSet
2576
  html_entity_decode(
2577
  get_bloginfo( 'name' ),
2578
  ENT_QUOTES
2651
  ) . sprintf(
2652
  "%s\r\n%s\r\n",
2653
  esc_html__( 'Notify message by Antispam Bee', 'antispam-bee' ),
2654
+ esc_html__( 'https://antispambee.pluginkollektiv.org/', 'antispam-bee' )
2655
  );
2656
 
2657
  wp_mail(
2690
  /**
2691
  * Return the number of spam comments
2692
  *
2693
+ * @since 0.1
2694
+ * @since 2.4
2695
  */
2696
  private static function _get_spam_count() {
2697
  // Init.
2705
  /**
2706
  * Output the number of spam comments
2707
  *
2708
+ * @since 0.1
2709
+ * @since 2.4
2710
  */
2711
  public static function the_spam_count() {
2712
  echo esc_html( self::_get_spam_count() );
2716
  /**
2717
  * Update the number of spam comments
2718
  *
2719
+ * @since 0.1
2720
+ * @since 2.6.1
2721
  */
2722
  private static function _update_spam_count() {
2723
  // Skip if not enabled.
2734
  /**
2735
  * Update statistics
2736
  *
2737
+ * @since 1.9
2738
+ * @since 2.6.1
2739
  */
2740
  private static function _update_daily_stats() {
2741
  // Skip if not enabled.
2767
  /**
2768
  * Returns the secret of a post used in the textarea name attribute.
2769
  *
2770
+ * @since 2.10.0 Modify secret generation because `always_allowed` option not longer exists
2771
+ *
2772
  * @param int $post_id The Post ID.
2773
  *
2774
  * @return string
2775
  */
2776
  public static function get_secret_name_for_post( $post_id ) {
2777
+ $secret = substr( sha1( md5( 'comment-id' . self::$_salt ) ), 0, 10 );
 
 
 
 
 
2778
 
2779
  $secret = self::ensure_secret_starts_with_letter( $secret );
2780
 
2797
  /**
2798
  * Returns the secret of a post used in the textarea id attribute.
2799
  *
2800
+ * @since 2.10.0 Modify secret generation because `always_allowed` option not longer exists
2801
+ *
2802
  * @param int $post_id The post ID.
2803
  *
2804
  * @return string
2805
  */
2806
  public static function get_secret_id_for_post( $post_id ) {
2807
 
2808
+ $secret = substr( sha1( md5( 'comment-id' . self::$_salt ) ), 0, 10 );
 
 
 
 
2809
 
2810
  $secret = self::ensure_secret_starts_with_letter( $secret );
2811
 
2871
 
2872
  /**
2873
  * Updates the database structure if necessary
2874
+ *
2875
+ * @since 2.10.0 Add update routine for country option names
2876
  */
2877
  public static function update_database() {
2878
  if ( self::db_version_is_current() ) {
2879
  return;
2880
  }
2881
 
2882
+ $version_from_db = floatval( get_option( 'antispambee_db_version', 0 ) );
2883
+ if ( $version_from_db < 1.01 ) {
2884
+ global $wpdb;
2885
 
2886
+ /**
2887
+ * In Version 2.9 the IP of the commenter was saved as a hash. We reverted this solution.
2888
+ * Therefore, we need to delete this unused data.
2889
+ */
2890
+ //phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
2891
+ $sql = 'delete from `' . $wpdb->commentmeta . '` where `meta_key` IN ("antispam_bee_iphash")';
2892
+ $wpdb->query( $sql );
2893
+ //phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
2894
+ }
2895
+
2896
+ // DB version was raised in ASB 2.10.0 to 1.02.
2897
+ if ( $version_from_db < 1.02 ) {
2898
+ // Update option names.
2899
+ $options = self::get_options();
2900
+ if ( isset( $options['country_black'] ) ) {
2901
+ $options['country_denied'] = $options['country_black'];
2902
+ unset( $options['country_black'] );
2903
+ }
2904
+ if ( isset( $options['country_white'] ) ) {
2905
+ $options['country_allowed'] = $options['country_white'];
2906
+ unset( $options['country_white'] );
2907
+ }
2908
+
2909
+ update_option(
2910
+ 'antispam_bee',
2911
+ $options
2912
+ );
2913
+
2914
+ wp_cache_set(
2915
+ 'antispam_bee',
2916
+ $options
2917
+ );
2918
+ }
2919
 
2920
  update_option( 'antispambee_db_version', self::$db_version );
2921
  }
2923
  /**
2924
  * Whether the database structure is up to date.
2925
  *
2926
+ * @since 2.10.0 Return a float instead of int
2927
+ *
2928
  * @return bool
2929
  */
2930
  private static function db_version_is_current() {
2931
 
2932
+ $current_version = floatval( get_option( 'antispambee_db_version', 0 ) );
2933
  return $current_version === self::$db_version;
2934
 
2935
  }
2936
+
2937
+ /**
2938
+ * Runs after upgrades are completed.
2939
+ *
2940
+ * @since 2.10.0
2941
+ *
2942
+ * @param \WP_Upgrader $wp_upgrader WP_Upgrader instance.
2943
+ * @param array $hook_extra Array of bulk item update data.
2944
+ */
2945
+ public static function upgrades_completed( $wp_upgrader, $hook_extra ) {
2946
+ if ( ! $wp_upgrader instanceof Plugin_Upgrader || ! isset( $hook_extra['plugins'] ) ) {
2947
+ return;
2948
+ }
2949
+
2950
+ $updated_plugins = $hook_extra['plugins'];
2951
+ $asb_updated = false;
2952
+ foreach ( $updated_plugins as $updated_plugin ) {
2953
+ if ( $updated_plugin !== self::$_base ) {
2954
+ continue;
2955
+ }
2956
+ $asb_updated = true;
2957
+ }
2958
+
2959
+ if ( false === $asb_updated ) {
2960
+ return;
2961
+ }
2962
+
2963
+ self::asb_updated();
2964
+ }
2965
+
2966
+ /**
2967
+ * Runs after an upgrade via an uploaded ZIP package was completed.
2968
+ *
2969
+ * @since 2.10.0
2970
+ *
2971
+ * @param string $package The package file.
2972
+ * @param array $data The new plugin or theme data.
2973
+ * @param string $package_type The package type.
2974
+ */
2975
+ public static function uploaded_upgrade_completed( $package, $data, $package_type ) {
2976
+ if ( 'plugin' !== $package_type ) {
2977
+ return;
2978
+ }
2979
+
2980
+ $text_domain = isset( $data['TextDomain'] ) ? $data['TextDomain'] : '';
2981
+
2982
+ if ( 'antispam-bee' !== $text_domain ) {
2983
+ return;
2984
+ }
2985
+
2986
+ self::asb_updated();
2987
+ }
2988
+
2989
+ /**
2990
+ * Runs after ASB was updated.
2991
+ *
2992
+ * @since 2.10.0
2993
+ *
2994
+ * @return void
2995
+ */
2996
+ private static function asb_updated() {
2997
+ self::update_database();
2998
+ }
2999
  }
3000
 
3001
 
behat.yml ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ default:
2
+ autoload:
3
+ '': tests/Acceptance/Behat/bootstrap
4
+ suites:
5
+ default:
6
+ paths:
7
+ features: tests/Acceptance/Behat
8
+ contexts:
9
+ # Keep WordpressContext first!
10
+ - PaulGibbs\WordpressBehatExtension\Context\WordpressContext
11
+ - FeatureContext
12
+ - CommentContext
13
+ - TrackbackContext
14
+ - PluginContext
15
+ - Behat\MinkExtension\Context\MinkContext
16
+ - PaulGibbs\WordpressBehatExtension\Context\ContentContext
17
+ - PaulGibbs\WordpressBehatExtension\Context\DashboardContext
18
+ - PaulGibbs\WordpressBehatExtension\Context\UserContext
19
+ - PaulGibbs\WordpressBehatExtension\Context\EditPostContext
20
+ extensions:
21
+ DMore\ChromeExtension\Behat\ServiceContainer\ChromeExtension: ~
22
+ Behat\MinkExtension:
23
+ base_url: http://localhost:8080 # [!] Change this for your local development.
24
+ browser_name: chrome
25
+ default_session: headless
26
+ javascript_session: javascript
27
+ sessions:
28
+ headless:
29
+ goutte:
30
+ guzzle_parameters:
31
+ verify: false
32
+ javascript:
33
+ chrome:
34
+ api_url: http://localhost:9222
35
+ validate_certificate: false
36
+
37
+ PaulGibbs\WordpressBehatExtension:
38
+ path: %paths.base%/tmp/antispam-bee/ # [!] Change this for your local development.
39
+ default_driver: wpcli
40
+ wpcli:
41
+ binary: %paths.base%/vendor/bin/wp
42
+ database:
43
+ restore_after_test: true
44
+ users:
45
+ admin:
46
+ username: admin
47
+ password: abc
css/dashboard.css CHANGED
@@ -1,9 +1,10 @@
1
  #ab_chart {
2
- color: #a0a5aa;
3
  height: 140px;
4
  margin: 0 -4px;
5
  text-align: center;
6
  }
 
7
  #ab_chart_data {
8
  display: none;
9
  }
1
  #ab_chart {
2
+ color: #a7aaad;
3
  height: 140px;
4
  margin: 0 -4px;
5
  text-align: center;
6
  }
7
+
8
  #ab_chart_data {
9
  display: none;
10
  }
css/dashboard.min.css CHANGED
@@ -1 +1 @@
1
- #ab_chart{color:#a0a5aa;height:140px;margin:0 -4px;text-align:center;}#ab_chart_data{display:none;}#ab_widget .inside{height:1%;margin:0;padding-bottom:0;overflow:hidden;position:relative;white-space:nowrap;}
1
+ #ab_chart{color:#a7aaad;height:140px;margin:0 -4px;text-align:center}#ab_chart_data{display:none}#ab_widget .inside{height:1%;margin:0;padding-bottom:0;overflow:hidden;position:relative;white-space:nowrap}
css/styles.css CHANGED
@@ -1,26 +1,15 @@
1
  /* @group General */
2
 
3
  .ab-main *,
4
- .ab-main *:after,
5
- .ab-main *:before {
6
- border: 0;
7
- margin: 0;
8
- padding: 0;
9
- outline: 0;
10
- -webkit-box-sizing: border-box;
11
- -moz-box-sizing: border-box;
12
- box-sizing: border-box;
13
- }
14
-
15
- /* @end group */
16
-
17
- /* @group Browse Happy */
18
-
19
- .browsehappy {
20
- margin: 0 0 20px;
21
- padding: 10px;
22
- border: 1px solid #e66f00;
23
- text-align: center;
24
  }
25
 
26
  /* @end group */
@@ -28,15 +17,15 @@
28
  /* @group Columns */
29
 
30
  .ab-wrap {
31
- margin: 0 0 0 -10px;
32
- padding: 20px 0 0 0;
33
- text-rendering: optimizeLegibility;
34
  }
35
 
36
  .ab-column {
37
- float: left;
38
- margin: 0 0 0 10px;
39
- position: relative;
40
  }
41
 
42
  /* @end group */
@@ -44,44 +33,44 @@
44
  /* @group Headlines + Icons */
45
 
46
  .ab-column h3 {
47
- margin: 0;
48
- font-size: 18px;
49
- font-weight: normal;
50
- line-height: 20px;
51
- color: #23282d;
52
  }
53
 
54
- .ab-column h3.icon:before {
55
- font: normal 30px/30px Dashicons;
56
- top: 4px;
57
- right: 20px;
58
- speak: none;
59
- width: 1em;
60
- color: #8e959c;
61
- position: absolute;
62
- text-align: center;
63
- -webkit-font-smoothing: antialiased;
64
- -moz-osx-font-smoothing: grayscale;
65
  }
66
 
67
- .ab-column.ab-arrow h3.icon:before {
68
- content: '\f536';
69
  }
70
 
71
- .ab-column.ab-join h3.icon:before {
72
- content: '\f108';
73
  }
74
 
75
- .ab-column.ab-diff h3.icon:before {
76
- content: '\f237';
77
  }
78
 
79
  .ab-column h6 {
80
- clear: both;
81
- color: #555d66;
82
- margin: 0 0 20px;
83
- font-weight: normal;
84
- font-size: 13px;
85
  }
86
 
87
  /* @end group */
@@ -91,18 +80,18 @@
91
  .ab-column input[type="text"],
92
  .ab-column input[type="number"],
93
  .ab-column select {
94
- font-size: 13px;
95
- text-align: center;
96
- background: #f8f8f9;
97
  }
98
 
99
  .ab-column input[type="number"] {
100
- padding: 0;
101
  }
102
 
103
  .ab-column select[multiple] {
104
- width: 175px;
105
- min-height: 60px;
106
  }
107
 
108
  .ab-column select[multiple][name="ab_ignore_reasons[]"] {
@@ -110,20 +99,20 @@
110
  }
111
 
112
  .ab-column input.ab-mini-field {
113
- width: 40px;
114
  }
115
 
116
  .ab-column .ab-medium-field {
117
- width: 100%;
118
- max-width: 285px;
119
  }
120
 
121
  .ab-column input[type="text"] + label,
122
  .ab-column select + label {
123
- color: #8e959c;
124
- margin: 0 0 0 7px;
125
- display: inline-block;
126
- text-transform: uppercase;
127
  }
128
 
129
  /* @end group */
@@ -131,86 +120,86 @@
131
  /* @group Column contents */
132
 
133
  .ab-column > ul {
134
- padding: 0 20px 0 0;
135
  }
136
 
137
  .ab-column:last-of-type > ul {
138
- border: 0;
139
  }
140
 
141
  .ab-column > ul > li {
142
- width: 330px;
143
- margin: 0 0 36px;
144
- padding: 10px 0 12px 12px;
145
- position: relative;
146
- background: #fff;
147
  }
148
 
149
  .ab-column > ul > li a {
150
- text-decoration: underline;
151
  }
152
 
153
  .ab-column > ul > li a:hover {
154
- border-color: inherit;
155
  }
156
 
157
  .ab-column > ul > li label {
158
- cursor: default;
159
- display: inline-block;
160
- font-size: 14px;
161
- max-width: 286px;
162
- color: #23282d;
163
  }
164
 
165
  .ab-column > ul > li label span {
166
- color: #555d66;
167
- display: block;
168
- font-size: 13px;
169
- line-height: 16px;
170
- margin-top: 5px;
171
  }
172
 
173
  /* @end group */
174
 
175
  /* @group Separator */
176
 
177
- .ab-column > ul > li:after,
178
- .ab-column > ul > li:before {
179
- width: 0;
180
- content: '';
181
- position: absolute;
182
  }
183
 
184
- .ab-column.ab-arrow > ul > li:before,
185
- .ab-column.ab-arrow > ul > li:after {
186
- left: 157px;
187
- border-width: 10px 10px 0;
188
- border-style: solid;
189
  }
190
 
191
- .ab-column.ab-arrow > ul > li:before {
192
- bottom: -24px;
193
- border-color: #fff transparent;
194
  }
195
 
196
- .ab-column.ab-arrow > ul > li:after {
197
- bottom: -22px;
198
- border-color: #f1f1f1 transparent;
199
  }
200
 
201
- .ab-column.ab-join > ul > li:before {
202
- left: 171px;
203
- bottom: -27px;
204
- height: 18px;
205
- border-right: 2px solid #fff;
206
  }
207
 
208
- .ab-column.ab-diff > ul > li:before {
209
- left: 162px;
210
- bottom: -19px;
211
- width: 18px;
212
- height: 0;
213
- border-bottom: 2px solid #fff;
214
  }
215
 
216
  /* @end group */
@@ -218,59 +207,59 @@
218
  /* @group Submit & Service */
219
 
220
  .ab-column--submit-service {
221
- width: 342px;
222
- margin-top: 20px;
223
- padding-right: 20px;
224
  }
225
 
226
  .ab-column--submit-service p {
227
- padding: 5px 0;
228
- margin: 0;
229
- text-align: center;
230
- width: 100%;
231
  }
232
 
233
  .ab-column--submit-service p:first-of-type {
234
- border-top: 1px solid #e0e5e9;
235
  }
236
 
237
  .ab-column--submit-service p:last-of-type {
238
- border-bottom: 1px solid #e0e5e9;
239
  }
240
 
241
  .ab-column--submit-service .button {
242
- width: 100%;
243
- margin: 35px 0 10px;
244
  }
245
 
246
  /* @end group */
247
 
248
  /* @group 2nd level */
249
 
250
- .ab-column > ul > li:last-of-type:after,
251
- .ab-column > ul > li:last-of-type:before {
252
- display: none;
253
  }
254
 
255
  .ab-column > ul > li > ul {
256
- margin: 10px 10px 0 26px;
257
- display: none;
258
  }
259
 
260
  .ab-column > ul > li > ul li {
261
- padding: 2px 0;
262
  }
263
 
264
  .ab-column > ul > li > ul label {
265
- margin: 0 0 0 7px;
266
  }
267
 
268
  .ab-column > ul > li > ul label[for="ab_ignore_reasons"] {
269
- margin: 0 0 5px 0;
270
  }
271
 
272
  .ab-column > ul > li > input[type="checkbox"]:checked ~ ul {
273
- display: block;
274
  }
275
 
276
  /* @end group */
1
  /* @group General */
2
 
3
  .ab-main *,
4
+ .ab-main *::after,
5
+ .ab-main *::before {
6
+ border: 0;
7
+ margin: 0;
8
+ padding: 0;
9
+ outline: 0;
10
+ -webkit-box-sizing: border-box;
11
+ -moz-box-sizing: border-box;
12
+ box-sizing: border-box;
 
 
 
 
 
 
 
 
 
 
 
13
  }
14
 
15
  /* @end group */
17
  /* @group Columns */
18
 
19
  .ab-wrap {
20
+ margin: 0 0 0 -10px;
21
+ padding: 20px 0 0 0;
22
+ text-rendering: optimizeLegibility;
23
  }
24
 
25
  .ab-column {
26
+ float: left;
27
+ margin: 0 0 0 10px;
28
+ position: relative;
29
  }
30
 
31
  /* @end group */
33
  /* @group Headlines + Icons */
34
 
35
  .ab-column h3 {
36
+ margin: 0;
37
+ font-size: 18px;
38
+ font-weight: normal;
39
+ line-height: 20px;
40
+ color: #1d2327;
41
  }
42
 
43
+ .ab-column h3.icon::before {
44
+ font: normal 30px/30px Dashicons;
45
+ top: 4px;
46
+ right: 20px;
47
+ speak: none;
48
+ width: 1em;
49
+ color: #8c8f94;
50
+ position: absolute;
51
+ text-align: center;
52
+ -webkit-font-smoothing: antialiased;
53
+ -moz-osx-font-smoothing: grayscale;
54
  }
55
 
56
+ .ab-column.ab-arrow h3.icon::before {
57
+ content: "\f536";
58
  }
59
 
60
+ .ab-column.ab-join h3.icon::before {
61
+ content: "\f108";
62
  }
63
 
64
+ .ab-column.ab-diff h3.icon::before {
65
+ content: "\f237";
66
  }
67
 
68
  .ab-column h6 {
69
+ clear: both;
70
+ color: #3c434a;
71
+ margin: 0 0 20px;
72
+ font-weight: normal;
73
+ font-size: 13px;
74
  }
75
 
76
  /* @end group */
80
  .ab-column input[type="text"],
81
  .ab-column input[type="number"],
82
  .ab-column select {
83
+ font-size: 13px;
84
+ text-align: center;
85
+ background: #f6f7f7;
86
  }
87
 
88
  .ab-column input[type="number"] {
89
+ padding: 0;
90
  }
91
 
92
  .ab-column select[multiple] {
93
+ width: 175px;
94
+ min-height: 60px;
95
  }
96
 
97
  .ab-column select[multiple][name="ab_ignore_reasons[]"] {
99
  }
100
 
101
  .ab-column input.ab-mini-field {
102
+ width: 40px;
103
  }
104
 
105
  .ab-column .ab-medium-field {
106
+ width: 100%;
107
+ max-width: 285px;
108
  }
109
 
110
  .ab-column input[type="text"] + label,
111
  .ab-column select + label {
112
+ color: #8c8f94;
113
+ margin: 0 0 0 7px;
114
+ display: inline-block;
115
+ text-transform: uppercase;
116
  }
117
 
118
  /* @end group */
120
  /* @group Column contents */
121
 
122
  .ab-column > ul {
123
+ padding: 0 20px 0 0;
124
  }
125
 
126
  .ab-column:last-of-type > ul {
127
+ border: 0;
128
  }
129
 
130
  .ab-column > ul > li {
131
+ width: 330px;
132
+ margin: 0 0 36px;
133
+ padding: 10px 0 12px 12px;
134
+ position: relative;
135
+ background: #fff;
136
  }
137
 
138
  .ab-column > ul > li a {
139
+ text-decoration: underline;
140
  }
141
 
142
  .ab-column > ul > li a:hover {
143
+ border-color: inherit;
144
  }
145
 
146
  .ab-column > ul > li label {
147
+ cursor: default;
148
+ display: inline-block;
149
+ font-size: 14px;
150
+ max-width: 286px;
151
+ color: #1d2327;
152
  }
153
 
154
  .ab-column > ul > li label span {
155
+ color: #3c434a;
156
+ display: block;
157
+ font-size: 13px;
158
+ line-height: 16px;
159
+ margin-top: 5px;
160
  }
161
 
162
  /* @end group */
163
 
164
  /* @group Separator */
165
 
166
+ .ab-column > ul > li::after,
167
+ .ab-column > ul > li::before {
168
+ width: 0;
169
+ content: "";
170
+ position: absolute;
171
  }
172
 
173
+ .ab-column.ab-arrow > ul > li::before,
174
+ .ab-column.ab-arrow > ul > li::after {
175
+ left: 157px;
176
+ border-width: 10px 10px 0;
177
+ border-style: solid;
178
  }
179
 
180
+ .ab-column.ab-arrow > ul > li::before {
181
+ bottom: -24px;
182
+ border-color: #fff transparent;
183
  }
184
 
185
+ .ab-column.ab-arrow > ul > li::after {
186
+ bottom: -22px;
187
+ border-color: #f0f0f1 transparent;
188
  }
189
 
190
+ .ab-column.ab-join > ul > li::before {
191
+ left: 171px;
192
+ bottom: -27px;
193
+ height: 18px;
194
+ border-right: 2px solid #fff;
195
  }
196
 
197
+ .ab-column.ab-diff > ul > li::before {
198
+ left: 162px;
199
+ bottom: -19px;
200
+ width: 18px;
201
+ height: 0;
202
+ border-bottom: 2px solid #fff;
203
  }
204
 
205
  /* @end group */
207
  /* @group Submit & Service */
208
 
209
  .ab-column--submit-service {
210
+ width: 342px;
211
+ margin-top: 20px;
212
+ padding-right: 20px;
213
  }
214
 
215
  .ab-column--submit-service p {
216
+ padding: 5px 0;
217
+ margin: 0;
218
+ text-align: center;
219
+ width: 100%;
220
  }
221
 
222
  .ab-column--submit-service p:first-of-type {
223
+ border-top: 1px solid #dcdcde;
224
  }
225
 
226
  .ab-column--submit-service p:last-of-type {
227
+ border-bottom: 1px solid #dcdcde;
228
  }
229
 
230
  .ab-column--submit-service .button {
231
+ width: 100%;
232
+ margin: 35px 0 10px;
233
  }
234
 
235
  /* @end group */
236
 
237
  /* @group 2nd level */
238
 
239
+ .ab-column > ul > li:last-of-type::after,
240
+ .ab-column > ul > li:last-of-type::before {
241
+ display: none;
242
  }
243
 
244
  .ab-column > ul > li > ul {
245
+ margin: 10px 10px 0 26px;
246
+ display: none;
247
  }
248
 
249
  .ab-column > ul > li > ul li {
250
+ padding: 2px 0;
251
  }
252
 
253
  .ab-column > ul > li > ul label {
254
+ margin: 0 0 0 7px;
255
  }
256
 
257
  .ab-column > ul > li > ul label[for="ab_ignore_reasons"] {
258
+ margin: 0 0 5px 0;
259
  }
260
 
261
  .ab-column > ul > li > input[type="checkbox"]:checked ~ ul {
262
+ display: block;
263
  }
264
 
265
  /* @end group */
css/styles.min.css CHANGED
@@ -1 +1 @@
1
- .ab-main *,.ab-main :after,.ab-main :before{border:0;margin:0;padding:0;outline:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.browsehappy{margin:0 0 20px;padding:10px;border:1px solid #e66f00;text-align:center}.ab-wrap{margin:0 0 0 -10px;padding:20px 0 0 0;text-rendering:optimizeLegibility}.ab-column{float:left;margin:0 0 0 10px;position:relative}.ab-column h3{margin:0;font-size:18px;font-weight:400;line-height:20px;color:#23282d}.ab-column h3.icon:before{font:normal 30px/30px Dashicons;top:4px;right:20px;speak:none;width:1em;color:#8e959c;position:absolute;text-align:center;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.ab-column.ab-arrow h3.icon:before{content:'\f536'}.ab-column.ab-join h3.icon:before{content:'\f108'}.ab-column.ab-diff h3.icon:before{content:'\f237'}.ab-column h6{clear:both;color:#555d66;margin:0 0 20px;font-weight:400;font-size:13px}.ab-column input[type=number],.ab-column input[type=text],.ab-column select{font-size:13px;text-align:center;background:#f8f8f9}.ab-column input[type=number]{padding:0}.ab-column select[multiple]{width:175px;min-height:60px}.ab-column select[multiple][name="ab_ignore_reasons[]"]{width:auto}.ab-column input.ab-mini-field{width:40px}.ab-column .ab-medium-field{width:100%;max-width:285px}.ab-column input[type=text]+label,.ab-column select+label{color:#8e959c;margin:0 0 0 7px;display:inline-block;text-transform:uppercase}.ab-column>ul{padding:0 20px 0 0}.ab-column:last-of-type>ul{border:0}.ab-column>ul>li{width:330px;margin:0 0 36px;padding:10px 0 12px 12px;position:relative;background:#fff}.ab-column>ul>li a{text-decoration:underline}.ab-column>ul>li a:hover{border-color:inherit}.ab-column>ul>li label{cursor:default;display:inline-block;font-size:14px;max-width:286px;color:#23282d}.ab-column>ul>li label span{color:#555d66;display:block;font-size:13px;line-height:16px;margin-top:5px}.ab-column>ul>li:after,.ab-column>ul>li:before{width:0;content:'';position:absolute}.ab-column.ab-arrow>ul>li:after,.ab-column.ab-arrow>ul>li:before{left:157px;border-width:10px 10px 0;border-style:solid}.ab-column.ab-arrow>ul>li:before{bottom:-24px;border-color:#fff transparent}.ab-column.ab-arrow>ul>li:after{bottom:-22px;border-color:#f1f1f1 transparent}.ab-column.ab-join>ul>li:before{left:171px;bottom:-27px;height:18px;border-right:2px solid #fff}.ab-column.ab-diff>ul>li:before{left:162px;bottom:-19px;width:18px;height:0;border-bottom:2px solid #fff}.ab-column--submit-service{width:342px;margin-top:20px;padding-right:20px}.ab-column--submit-service p{padding:5px 0;margin:0;text-align:center;width:100%}.ab-column--submit-service p:first-of-type{border-top:1px solid #e0e5e9}.ab-column--submit-service p:last-of-type{border-bottom:1px solid #e0e5e9}.ab-column--submit-service .button{width:100%;margin:35px 0 10px}.ab-column>ul>li:last-of-type:after,.ab-column>ul>li:last-of-type:before{display:none}.ab-column>ul>li>ul{margin:10px 10px 0 26px;display:none}.ab-column>ul>li>ul li{padding:2px 0}.ab-column>ul>li>ul label{margin:0 0 0 7px}.ab-column>ul>li>ul label[for=ab_ignore_reasons]{margin:0 0 5px 0}.ab-column>ul>li>input[type=checkbox]:checked~ul{display:block}
1
+ .ab-main *,.ab-main *::after,.ab-main *::before{border:0;margin:0;padding:0;outline:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.ab-wrap{margin:0 0 0 -10px;padding:20px 0 0 0;text-rendering:optimizeLegibility}.ab-column{float:left;margin:0 0 0 10px;position:relative}.ab-column h3{margin:0;font-size:18px;font-weight:400;line-height:20px;color:#1d2327}.ab-column h3.icon::before{font:normal 30px/30px Dashicons;top:4px;right:20px;speak:none;width:1em;color:#8c8f94;position:absolute;text-align:center;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.ab-column.ab-arrow h3.icon::before{content:"\f536"}.ab-column.ab-join h3.icon::before{content:"\f108"}.ab-column.ab-diff h3.icon::before{content:"\f237"}.ab-column h6{clear:both;color:#3c434a;margin:0 0 20px;font-weight:400;font-size:13px}.ab-column input[type="text"],.ab-column input[type="number"],.ab-column select{font-size:13px;text-align:center;background:#f6f7f7}.ab-column input[type="number"]{padding:0}.ab-column select[multiple]{width:175px;min-height:60px}.ab-column select[multiple][name="ab_ignore_reasons[]"]{width:auto}.ab-column input.ab-mini-field{width:40px}.ab-column .ab-medium-field{width:100%;max-width:285px}.ab-column input[type="text"]+label,.ab-column select+label{color:#8c8f94;margin:0 0 0 7px;display:inline-block;text-transform:uppercase}.ab-column>ul{padding:0 20px 0 0}.ab-column:last-of-type>ul{border:0}.ab-column>ul>li{width:330px;margin:0 0 36px;padding:10px 0 12px 12px;position:relative;background:#fff}.ab-column>ul>li a{text-decoration:underline}.ab-column>ul>li a:hover{border-color:inherit}.ab-column>ul>li label{cursor:default;display:inline-block;font-size:14px;max-width:286px;color:#1d2327}.ab-column>ul>li label span{color:#3c434a;display:block;font-size:13px;line-height:16px;margin-top:5px}.ab-column>ul>li::after,.ab-column>ul>li::before{width:0;content:"";position:absolute}.ab-column.ab-arrow>ul>li::before,.ab-column.ab-arrow>ul>li::after{left:157px;border-width:10px 10px 0;border-style:solid}.ab-column.ab-arrow>ul>li::before{bottom:-24px;border-color:#fff transparent}.ab-column.ab-arrow>ul>li::after{bottom:-22px;border-color:#f0f0f1 transparent}.ab-column.ab-join>ul>li::before{left:171px;bottom:-27px;height:18px;border-right:2px solid #fff}.ab-column.ab-diff>ul>li::before{left:162px;bottom:-19px;width:18px;height:0;border-bottom:2px solid #fff}.ab-column--submit-service{width:342px;margin-top:20px;padding-right:20px}.ab-column--submit-service p{padding:5px 0;margin:0;text-align:center;width:100%}.ab-column--submit-service p:first-of-type{border-top:1px solid #dcdcde}.ab-column--submit-service p:last-of-type{border-bottom:1px solid #dcdcde}.ab-column--submit-service .button{width:100%;margin:35px 0 10px}.ab-column>ul>li:last-of-type::after,.ab-column>ul>li:last-of-type::before{display:none}.ab-column>ul>li>ul{margin:10px 10px 0 26px;display:none}.ab-column>ul>li>ul li{padding:2px 0}.ab-column>ul>li>ul label{margin:0 0 0 7px}.ab-column>ul>li>ul label[for="ab_ignore_reasons"]{margin:0 0 5px 0}.ab-column>ul>li>input[type="checkbox"]:checked~ul{display:block}
docker_tag ADDED
@@ -0,0 +1 @@
 
1
+ docker.pkg.github.com/pluginkollektiv/antispam-bee/php-actions_composer_antispam-bee:php-latest-build2
inc/gui.class.php CHANGED
@@ -17,8 +17,9 @@ class Antispam_Bee_GUI extends Antispam_Bee {
17
  /**
18
  * Save the GUI
19
  *
20
- * @since 0.1
21
- * @change 2.7.0
 
22
  */
23
  public static function save_changes() {
24
  if ( empty( $_POST ) ) {
@@ -58,7 +59,6 @@ class Antispam_Bee_GUI extends Antispam_Bee {
58
  'spam_ip' => (int) ( ! empty( $_POST['ab_spam_ip'] ) ),
59
  'already_commented' => (int) ( ! empty( $_POST['ab_already_commented'] ) ),
60
  'time_check' => (int) ( ! empty( $_POST['ab_time_check'] ) ),
61
- 'always_allowed' => (int) ( ! empty( $_POST['ab_always_allowed'] ) ),
62
 
63
  'ignore_pings' => (int) ( ! empty( $_POST['ab_ignore_pings'] ) ),
64
  'ignore_filter' => (int) ( ! empty( $_POST['ab_ignore_filter'] ) ),
@@ -70,14 +70,16 @@ class Antispam_Bee_GUI extends Antispam_Bee {
70
  'bbcode_check' => (int) ( ! empty( $_POST['ab_bbcode_check'] ) ),
71
  'gravatar_check' => (int) ( ! empty( $_POST['ab_gravatar_check'] ) ),
72
  'country_code' => (int) ( ! empty( $_POST['ab_country_code'] ) ),
73
- 'country_black' => sanitize_text_field( wp_unslash( self::get_key( $_POST, 'ab_country_black' ) ) ),
74
- 'country_white' => sanitize_text_field( wp_unslash( self::get_key( $_POST, 'ab_country_white' ) ) ),
75
 
76
  'translate_api' => (int) ( ! empty( $_POST['ab_translate_api'] ) ),
77
  'translate_lang' => $selected_languages,
78
 
79
  'delete_data_on_uninstall' => (int) ( ! empty( $_POST['delete_data_on_uninstall'] ) ),
80
 
 
 
81
  );
82
 
83
  foreach ( $options['ignore_reasons'] as $key => $val ) {
@@ -98,23 +100,23 @@ class Antispam_Bee_GUI extends Antispam_Bee {
98
  $options['ignore_reasons'] = array();
99
  }
100
 
101
- if ( ! empty( $options['country_black'] ) ) {
102
- $options['country_black'] = preg_replace(
103
  '/[^A-Z ,;]/',
104
  '',
105
- strtoupper( $options['country_black'] )
106
  );
107
  }
108
 
109
- if ( ! empty( $options['country_white'] ) ) {
110
- $options['country_white'] = preg_replace(
111
  '/[^A-Z ,;]/',
112
  '',
113
- strtoupper( $options['country_white'] )
114
  );
115
  }
116
 
117
- if ( empty( $options['country_black'] ) && empty( $options['country_white'] ) ) {
118
  $options['country_code'] = 0;
119
  }
120
 
@@ -142,7 +144,6 @@ class Antispam_Bee_GUI extends Antispam_Bee {
142
  * Generation of a selectbox
143
  *
144
  * @since 2.4.5
145
- * @change 2.4.5
146
  *
147
  * @param string $name Name of the Selectbox.
148
  * @param array $data Array with values.
@@ -163,8 +164,9 @@ class Antispam_Bee_GUI extends Antispam_Bee {
163
  /**
164
  * Display the GUI
165
  *
166
- * @since 0.1
167
- * @change 2.7.0
 
168
  */
169
  public static function options_page() { ?>
170
  <div class="wrap" id="ab_main">
@@ -179,11 +181,6 @@ class Antispam_Bee_GUI extends Antispam_Bee {
179
 
180
  <?php $options = self::get_options(); ?>
181
  <div class="ab-wrap">
182
- <!--[if lt IE 9]>
183
- <p class="browsehappy">
184
- <a href="http://browsehappy.com">Browse Happy</a>
185
- </p>
186
- <![endif]-->
187
 
188
  <div class="ab-column ab-arrow">
189
  <h3 class="icon">
@@ -212,7 +209,7 @@ class Antispam_Bee_GUI extends Antispam_Bee {
212
  $link1 = sprintf(
213
  '<a href="%s" target="_blank" rel="noopener noreferrer">',
214
  esc_url(
215
- __( 'https://antispambee.pluginkollektiv.org/documentation#gravatar', 'antispam-bee' ),
216
  'https'
217
  )
218
  );
@@ -270,14 +267,15 @@ class Antispam_Bee_GUI extends Antispam_Bee {
270
  $link1 = sprintf(
271
  '<a href="%s" target="_blank" rel="noopener noreferrer">',
272
  esc_url(
273
- __( 'https://antispambee.pluginkollektiv.org/documentation#country', 'antispam-bee' ),
274
  'https'
275
  )
276
  );
277
  printf(
278
  /* translators: 1: opening <a> tag with link to documentation. 2: closing </a> tag. */
279
  esc_html__( 'Filtering the requests depending on country. Please note the %1$sprivacy notice%2$s for this option.', 'antispam-bee' ),
280
- wp_kses_post( $link1 ), '</a>'
 
281
  );
282
  ?>
283
  </span>
@@ -294,8 +292,8 @@ class Antispam_Bee_GUI extends Antispam_Bee {
294
  );
295
  ?>
296
  <li>
297
- <textarea name="ab_country_black" id="ab_country_black" class="ab-medium-field code" placeholder="<?php esc_attr_e( 'e.g. BF, SG, YE', 'antispam-bee' ); ?>"><?php echo esc_attr( $options['country_black'] ); ?></textarea>
298
- <label for="ab_country_black">
299
  <span>
300
  <?php
301
  printf(
@@ -309,8 +307,8 @@ class Antispam_Bee_GUI extends Antispam_Bee {
309
  </label>
310
  </li>
311
  <li>
312
- <textarea name="ab_country_white" id="ab_country_white" class="ab-medium-field code" placeholder="<?php esc_attr_e( 'e.g. BF, SG, YE', 'antispam-bee' ); ?>"><?php echo esc_attr( $options['country_white'] ); ?></textarea>
313
- <label for="ab_country_white">
314
  <span>
315
  <?php
316
  printf(
@@ -335,7 +333,7 @@ class Antispam_Bee_GUI extends Antispam_Bee {
335
  $link1 = sprintf(
336
  '<a href="%s" target="_blank" rel="noopener noreferrer">',
337
  esc_url(
338
- __( 'https://antispambee.pluginkollektiv.org/documentation#language', 'antispam-bee' ),
339
  'https'
340
  )
341
  );
@@ -423,7 +421,7 @@ class Antispam_Bee_GUI extends Antispam_Bee {
423
  <label for="ab_ignore_filter">
424
  <?php
425
  echo sprintf(
426
- // phpcs:disable WordPress.XSS.EscapeOutput.OutputNotEscaped
427
  // Output gets escaped in _build_select()
428
  // translators: %s is the select field.
429
  esc_html__( 'Limit approval to %s', 'antispam-bee' ),
@@ -435,7 +433,7 @@ class Antispam_Bee_GUI extends Antispam_Bee {
435
  ),
436
  $options['ignore_type']
437
  )
438
- // phpcs:enable _build_select
439
  );
440
  ?>
441
  <span><?php esc_html_e( 'Other types of spam will be deleted immediately', 'antispam-bee' ); ?></span>
@@ -509,10 +507,17 @@ class Antispam_Bee_GUI extends Antispam_Bee {
509
  </li>
510
 
511
  <li>
512
- <input type="checkbox" name="ab_always_allowed" id="ab_always_allowed" value="1" <?php checked( $options['always_allowed'], 1 ); ?> />
513
- <label for="ab_always_allowed">
514
- <?php esc_html_e( 'Comment form used outside of posts', 'antispam-bee' ); ?>
515
- <span><?php esc_html_e( 'Check for comment forms on archive pages', 'antispam-bee' ); ?></span>
 
 
 
 
 
 
 
516
  </label>
517
  </li>
518
  </ul>
17
  /**
18
  * Save the GUI
19
  *
20
+ * @since 0.1
21
+ * @since 2.7.0
22
+ * @since 2.10.0 Change country option names
23
  */
24
  public static function save_changes() {
25
  if ( empty( $_POST ) ) {
59
  'spam_ip' => (int) ( ! empty( $_POST['ab_spam_ip'] ) ),
60
  'already_commented' => (int) ( ! empty( $_POST['ab_already_commented'] ) ),
61
  'time_check' => (int) ( ! empty( $_POST['ab_time_check'] ) ),
 
62
 
63
  'ignore_pings' => (int) ( ! empty( $_POST['ab_ignore_pings'] ) ),
64
  'ignore_filter' => (int) ( ! empty( $_POST['ab_ignore_filter'] ) ),
70
  'bbcode_check' => (int) ( ! empty( $_POST['ab_bbcode_check'] ) ),
71
  'gravatar_check' => (int) ( ! empty( $_POST['ab_gravatar_check'] ) ),
72
  'country_code' => (int) ( ! empty( $_POST['ab_country_code'] ) ),
73
+ 'country_denied' => sanitize_text_field( wp_unslash( self::get_key( $_POST, 'ab_country_denied' ) ) ),
74
+ 'country_allowed' => sanitize_text_field( wp_unslash( self::get_key( $_POST, 'ab_country_allowed' ) ) ),
75
 
76
  'translate_api' => (int) ( ! empty( $_POST['ab_translate_api'] ) ),
77
  'translate_lang' => $selected_languages,
78
 
79
  'delete_data_on_uninstall' => (int) ( ! empty( $_POST['delete_data_on_uninstall'] ) ),
80
 
81
+ 'use_output_buffer' => (int) ( ! empty( $_POST['ab_use_output_buffer'] ) ),
82
+
83
  );
84
 
85
  foreach ( $options['ignore_reasons'] as $key => $val ) {
100
  $options['ignore_reasons'] = array();
101
  }
102
 
103
+ if ( ! empty( $options['country_denied'] ) ) {
104
+ $options['country_denied'] = preg_replace(
105
  '/[^A-Z ,;]/',
106
  '',
107
+ strtoupper( $options['country_denied'] )
108
  );
109
  }
110
 
111
+ if ( ! empty( $options['country_allowed'] ) ) {
112
+ $options['country_allowed'] = preg_replace(
113
  '/[^A-Z ,;]/',
114
  '',
115
+ strtoupper( $options['country_allowed'] )
116
  );
117
  }
118
 
119
+ if ( empty( $options['country_denied'] ) && empty( $options['country_allowed'] ) ) {
120
  $options['country_code'] = 0;
121
  }
122
 
144
  * Generation of a selectbox
145
  *
146
  * @since 2.4.5
 
147
  *
148
  * @param string $name Name of the Selectbox.
149
  * @param array $data Array with values.
164
  /**
165
  * Display the GUI
166
  *
167
+ * @since 0.1
168
+ * @since 2.7.0
169
+ * @since 2.10.0 Change documentation links, change country option name, and add option to parse complete markup for comment forms
170
  */
171
  public static function options_page() { ?>
172
  <div class="wrap" id="ab_main">
181
 
182
  <?php $options = self::get_options(); ?>
183
  <div class="ab-wrap">
 
 
 
 
 
184
 
185
  <div class="ab-column ab-arrow">
186
  <h3 class="icon">
209
  $link1 = sprintf(
210
  '<a href="%s" target="_blank" rel="noopener noreferrer">',
211
  esc_url(
212
+ __( 'https://antispambee.pluginkollektiv.org/documentation/#trust-commenters-with-a-gravatar', 'antispam-bee' ),
213
  'https'
214
  )
215
  );
267
  $link1 = sprintf(
268
  '<a href="%s" target="_blank" rel="noopener noreferrer">',
269
  esc_url(
270
+ __( 'https://antispambee.pluginkollektiv.org/documentation/#block-comments-from-specific-countries', 'antispam-bee' ),
271
  'https'
272
  )
273
  );
274
  printf(
275
  /* translators: 1: opening <a> tag with link to documentation. 2: closing </a> tag. */
276
  esc_html__( 'Filtering the requests depending on country. Please note the %1$sprivacy notice%2$s for this option.', 'antispam-bee' ),
277
+ wp_kses_post( $link1 ),
278
+ '</a>'
279
  );
280
  ?>
281
  </span>
292
  );
293
  ?>
294
  <li>
295
+ <textarea name="ab_country_denied" id="ab_country_denied" class="ab-medium-field code" placeholder="<?php esc_attr_e( 'e.g. BF, SG, YE', 'antispam-bee' ); ?>"><?php echo esc_attr( $options['country_denied'] ); ?></textarea>
296
+ <label for="ab_country_denied">
297
  <span>
298
  <?php
299
  printf(
307
  </label>
308
  </li>
309
  <li>
310
+ <textarea name="ab_country_allowed" id="ab_country_allowed" class="ab-medium-field code" placeholder="<?php esc_attr_e( 'e.g. BF, SG, YE', 'antispam-bee' ); ?>"><?php echo esc_attr( $options['country_allowed'] ); ?></textarea>
311
+ <label for="ab_country_allowed">
312
  <span>
313
  <?php
314
  printf(
333
  $link1 = sprintf(
334
  '<a href="%s" target="_blank" rel="noopener noreferrer">',
335
  esc_url(
336
+ __( 'https://antispambee.pluginkollektiv.org/documentation/#allow-comments-only-in-certain-language', 'antispam-bee' ),
337
  'https'
338
  )
339
  );
421
  <label for="ab_ignore_filter">
422
  <?php
423
  echo sprintf(
424
+ // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
425
  // Output gets escaped in _build_select()
426
  // translators: %s is the select field.
427
  esc_html__( 'Limit approval to %s', 'antispam-bee' ),
433
  ),
434
  $options['ignore_type']
435
  )
436
+ // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
437
  );
438
  ?>
439
  <span><?php esc_html_e( 'Other types of spam will be deleted immediately', 'antispam-bee' ); ?></span>
507
  </li>
508
 
509
  <li>
510
+ <input type="checkbox" name="ab_use_output_buffer" id="ab_use_output_buffer" value="1" <?php checked( ! isset( $options['use_output_buffer'] ) || 1 == $options['use_output_buffer'], true ); ?> />
511
+ <label for="ab_use_output_buffer">
512
+ <?php esc_html_e( 'Check complete site markup for comment forms', 'antispam-bee' ); ?>
513
+ <span>
514
+ <?php
515
+ printf( /* translators: s=filter name */
516
+ esc_html__( 'Uses output buffering instead of the %s filter.', 'antispam-bee' ),
517
+ '<code>comment_form_field_comment</code>'
518
+ );
519
+ ?>
520
+ </span>
521
  </label>
522
  </li>
523
  </ul>
js/dashboard.js CHANGED
@@ -1,111 +1,112 @@
1
- (function () {
 
 
 
 
 
 
 
 
 
2
 
3
- // Grab the data
4
- var labels = [],
5
- data = [];
6
- jQuery("#ab_chart_data tfoot th").each(function () {
7
- labels.push(jQuery(this).text());
8
- });
9
- jQuery("#ab_chart_data tbody td").each(function () {
10
- data.push(jQuery(this).text());
11
- });
 
 
 
12
 
13
- // Draw
14
- var width = jQuery('#ab_chart').parent().width() + 8,
15
- height = 140,
16
- leftgutter = 0,
17
- bottomgutter = 22,
18
- topgutter = 22,
19
- color = '#0073aa',
20
- r = Raphael("ab_chart", width, height),
21
- txt = {font: 'bold 12px "Open Sans", sans-serif', fill: "#32373c"},
22
- X = (width - leftgutter * 2) / labels.length,
23
- max = Math.max.apply(Math, data),
24
- Y = (height - bottomgutter - topgutter) / max;
25
 
26
- // Max value
27
- r
28
- .text(16, 16, max)
29
- .attr(
30
- {
31
- 'font': 'normal 10px "Open Sans", sans-serif',
32
- fill: "#b4b9be"
33
- }
34
- );
 
 
 
35
 
36
- var path = r.path().attr({stroke: color, "stroke-width": 2, "stroke-linejoin": "round"}),
37
- bgp = r.path().attr({stroke: "none", opacity: .3, fill: color}),
38
- label = r.set(),
39
- lx = 0, ly = 0,
40
- is_label_visible = false,
41
- leave_timer,
42
- blanket = r.set();
43
- label.push(r.text(60, 12, "").attr(txt));
44
- label.push(r.text(60, 27, "").attr(txt).attr({fill: color}));
45
- label.hide();
46
- var frame = r.popup(100, 100, label, "right").attr({fill: "#fff", stroke: "#444", "stroke-width": 1}).hide();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
- var p, bgpp;
49
- for (var i = 0, ii = labels.length; i < ii; i++) {
50
- var y = Math.round(height - bottomgutter - Y * data[i]),
51
- x = Math.round(leftgutter + X * (i + .5));
52
- if (!i) {
53
- p = ["M", x, y, "C", x, y];
54
- bgpp = ["M", leftgutter + X * .5, height - bottomgutter, "L", x, y, "C", x, y];
55
- }
56
- if (i && i < ii - 1) {
57
- var Y0 = Math.round(height - bottomgutter - Y * data[i - 1]),
58
- X0 = Math.round(leftgutter + X * (i - .5)),
59
- Y2 = Math.round(height - bottomgutter - Y * data[i + 1]),
60
- X2 = Math.round(leftgutter + X * (i + 1.5));
61
- var a = getAnchors(X0, Y0, x, y, X2, Y2);
62
- p = p.concat([a.x1, a.y1, x, y, a.x2, a.y2]);
63
- bgpp = bgpp.concat([a.x1, a.y1, x, y, a.x2, a.y2]);
64
- }
65
- var dot = r.circle(x, y, 4).attr({fill: "#fff", stroke: color, "stroke-width": 1});
66
- blanket.push(r.rect(leftgutter + X * i, 0, X, height - bottomgutter).attr({stroke: "none", fill: '#fff', opacity: .2}));
67
- var rect = blanket[blanket.length - 1];
68
- (function (x, y, data, date, dot) {
69
- var timer, i = 0;
70
- rect.hover(function () {
71
- clearTimeout(leave_timer);
72
- var side = "right";
73
- if (x + frame.getBBox().width > width) {
74
- side = "left";
75
- }
76
- // set label content to determine correct dimensions
77
- label[0].attr({text: date });
78
- label[1].attr({text: data + "× Spam"});
79
- var ppp = r.popup(x, y, label, side, 1),
80
- anim = Raphael.animation({
81
- path: ppp.path,
82
- transform: ["t", ppp.dx, ppp.dy]
83
- }, 200 * is_label_visible);
84
- lx = label[0].transform()[0][1] + ppp.dx;
85
- ly = label[0].transform()[0][2] + ppp.dy;
86
- frame.show().stop().animate(anim);
87
-
88
- label[0].show().stop().animateWith(frame, anim, {transform: ["t", lx, ly]}, 200 * is_label_visible);
89
- label[1].show().stop().animateWith(frame, anim, {transform: ["t", lx, ly]}, 200 * is_label_visible);
90
- dot.attr("r", 6);
91
- is_label_visible = true;
92
- }, function () {
93
- dot.attr("r", 4);
94
- leave_timer = setTimeout(function () {
95
- frame.hide();
96
- label[0].hide();
97
- label[1].hide();
98
- is_label_visible = false;
99
- }, 1);
100
- });
101
- })(x, y, data[i], labels[i], dot);
102
- }
103
- p = p.concat([x, y, x, y]);
104
- bgpp = bgpp.concat([x, y, x, y, "L", x, height - bottomgutter, "z"]);
105
- path.attr({path: p});
106
- bgp.attr({path: bgpp});
107
- frame.toFront();
108
- label[0].toFront();
109
- label[1].toFront();
110
- blanket.toFront();
111
- })();
1
+ ( function() {
2
+ // Grab the data
3
+ var labels = [],
4
+ data = [];
5
+ jQuery( '#ab_chart_data tfoot th' ).each( function() {
6
+ labels.push( jQuery( this ).text() );
7
+ } );
8
+ jQuery( '#ab_chart_data tbody td' ).each( function() {
9
+ data.push( jQuery( this ).text() );
10
+ } );
11
 
12
+ // Draw
13
+ var width = jQuery( '#ab_chart' ).parent().width() + 8,
14
+ height = 140,
15
+ leftgutter = 0,
16
+ bottomgutter = 22,
17
+ topgutter = 22,
18
+ color = '#135e96',
19
+ r = Raphael( 'ab_chart', width, height ),
20
+ txt = { font: 'bold 12px "Open Sans", sans-serif', fill: '#1d2327' },
21
+ X = ( width - leftgutter * 2 ) / labels.length,
22
+ max = Math.max.apply( Math, data ),
23
+ Y = ( height - bottomgutter - topgutter ) / max;
24
 
25
+ // Max value
26
+ r
27
+ .text( 16, 16, max )
28
+ .attr(
29
+ {
30
+ font: 'normal 10px "Open Sans", sans-serif',
31
+ fill: '#a7aaad',
32
+ }
33
+ );
 
 
 
34
 
35
+ var path = r.path().attr( { stroke: color, 'stroke-width': 2, 'stroke-linejoin': 'round' } ),
36
+ bgp = r.path().attr( { stroke: 'none', opacity: .3, fill: color } ),
37
+ label = r.set(),
38
+ lx = 0,
39
+ ly = 0,
40
+ is_label_visible = false,
41
+ leave_timer,
42
+ blanket = r.set();
43
+ label.push( r.text( 60, 12, '' ).attr( txt ) );
44
+ label.push( r.text( 60, 27, '' ).attr( txt ).attr( { fill: color } ) );
45
+ label.hide();
46
+ var frame = r.popup( 100, 100, label, 'right' ).attr( { fill: '#fff', stroke: '#444', 'stroke-width': 1 } ).hide();
47
 
48
+ var p, bgpp;
49
+ for ( var i = 0, ii = labels.length; i < ii; i++ ) {
50
+ var y = Math.round( height - bottomgutter - Y * data[i] ),
51
+ x = Math.round( leftgutter + X * ( i + .5 ) );
52
+ if ( ! i ) {
53
+ p = [ 'M', x, y, 'C', x, y ];
54
+ bgpp = [ 'M', leftgutter + X * .5, height - bottomgutter, 'L', x, y, 'C', x, y ];
55
+ }
56
+ if ( i && i < ii - 1 ) {
57
+ var Y0 = Math.round( height - bottomgutter - Y * data[i - 1] ),
58
+ X0 = Math.round( leftgutter + X * ( i - .5 ) ),
59
+ Y2 = Math.round( height - bottomgutter - Y * data[i + 1] ),
60
+ X2 = Math.round( leftgutter + X * ( i + 1.5 ) );
61
+ var a = getAnchors( X0, Y0, x, y, X2, Y2 );
62
+ p = p.concat( [ a.x1, a.y1, x, y, a.x2, a.y2 ] );
63
+ bgpp = bgpp.concat( [ a.x1, a.y1, x, y, a.x2, a.y2 ] );
64
+ }
65
+ var dot = r.circle( x, y, 4 ).attr( { fill: '#fff', stroke: color, 'stroke-width': 1 } );
66
+ blanket.push( r.rect( leftgutter + X * i, 0, X, height - bottomgutter ).attr( { stroke: 'none', fill: '#fff', opacity: .2 } ) );
67
+ var rect = blanket[blanket.length - 1];
68
+ ( function( x, y, data, date, dot ) {
69
+ var timer,
70
+ i = 0;
71
+ rect.hover( function() {
72
+ clearTimeout( leave_timer );
73
+ var side = 'right';
74
+ if ( x + frame.getBBox().width > width ) {
75
+ side = 'left';
76
+ }
77
+ // set label content to determine correct dimensions
78
+ label[0].attr( { text: date } );
79
+ label[1].attr( { text: data + '× Spam' } );
80
+ var ppp = r.popup( x, y, label, side, 1 ),
81
+ anim = Raphael.animation( {
82
+ path: ppp.path,
83
+ transform: [ 't', ppp.dx, ppp.dy ],
84
+ }, 200 * is_label_visible );
85
+ lx = label[0].transform()[0][1] + ppp.dx;
86
+ ly = label[0].transform()[0][2] + ppp.dy;
87
+ frame.show().stop().animate( anim );
88
 
89
+ label[0].show().stop().animateWith( frame, anim, { transform: [ 't', lx, ly ] }, 200 * is_label_visible );
90
+ label[1].show().stop().animateWith( frame, anim, { transform: [ 't', lx, ly ] }, 200 * is_label_visible );
91
+ dot.attr( 'r', 6 );
92
+ is_label_visible = true;
93
+ }, function() {
94
+ dot.attr( 'r', 4 );
95
+ leave_timer = setTimeout( function() {
96
+ frame.hide();
97
+ label[0].hide();
98
+ label[1].hide();
99
+ is_label_visible = false;
100
+ }, 1 );
101
+ } );
102
+ }( x, y, data[i], labels[i], dot ) );
103
+ }
104
+ p = p.concat( [ x, y, x, y ] );
105
+ bgpp = bgpp.concat( [ x, y, x, y, 'L', x, height - bottomgutter, 'z' ] );
106
+ path.attr( { path: p } );
107
+ bgp.attr( { path: bgpp } );
108
+ frame.toFront();
109
+ label[0].toFront();
110
+ label[1].toFront();
111
+ blanket.toFront();
112
+ }() );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/dashboard.min.js CHANGED
@@ -1,5 +1,5 @@
1
- (function(){var labels=[],data=[];jQuery("#ab_chart_data tfoot th").each(function(){labels.push(jQuery(this).text())});jQuery("#ab_chart_data tbody td").each(function(){data.push(jQuery(this).text())});var width=jQuery('#ab_chart').parent().width()+8,height=140,leftgutter=0,bottomgutter=22,topgutter=22,color='#0073aa',r=Raphael("ab_chart",width,height),txt={font:'bold 12px "Open Sans", sans-serif',fill:"#32373c"},X=(width-leftgutter*2)/labels.length,max=Math.max.apply(Math,data),Y=(height-bottomgutter-topgutter)/max;r.text(16,16,max).attr({'font':'normal 10px "Open Sans", sans-serif',fill:"#b4b9be"});var path=r.path().attr({stroke:color,"stroke-width":2,"stroke-linejoin":"round"}),bgp=r.path().attr({stroke:"none",opacity:.3,fill:color}),label=r.set(),lx=0,ly=0,is_label_visible=!1,leave_timer,blanket=r.set();label.push(r.text(60,12,"").attr(txt));label.push(r.text(60,27,"").attr(txt).attr({fill:color}));label.hide();var frame=r.popup(100,100,label,"right").attr({fill:"#fff",stroke:"#444","stroke-width":1}).hide();var p,bgpp;for(var i=0,ii=labels.length;i<ii;i++){var y=Math.round(height-bottomgutter-Y*data[i]),x=Math.round(leftgutter+X*(i+.5));if(!i){p=["M",x,y,"C",x,y];bgpp=["M",leftgutter+X*.5,height-bottomgutter,"L",x,y,"C",x,y]}
2
  if(i&&i<ii-1){var Y0=Math.round(height-bottomgutter-Y*data[i-1]),X0=Math.round(leftgutter+X*(i-.5)),Y2=Math.round(height-bottomgutter-Y*data[i+1]),X2=Math.round(leftgutter+X*(i+1.5));var a=getAnchors(X0,Y0,x,y,X2,Y2);p=p.concat([a.x1,a.y1,x,y,a.x2,a.y2]);bgpp=bgpp.concat([a.x1,a.y1,x,y,a.x2,a.y2])}
3
- var dot=r.circle(x,y,4).attr({fill:"#fff",stroke:color,"stroke-width":1});blanket.push(r.rect(leftgutter+X*i,0,X,height-bottomgutter).attr({stroke:"none",fill:'#fff',opacity:.2}));var rect=blanket[blanket.length-1];(function(x,y,data,date,dot){var timer,i=0;rect.hover(function(){clearTimeout(leave_timer);var side="right";if(x+frame.getBBox().width>width){side="left"}
4
- label[0].attr({text:date});label[1].attr({text:data+"× Spam"});var ppp=r.popup(x,y,label,side,1),anim=Raphael.animation({path:ppp.path,transform:["t",ppp.dx,ppp.dy]},200*is_label_visible);lx=label[0].transform()[0][1]+ppp.dx;ly=label[0].transform()[0][2]+ppp.dy;frame.show().stop().animate(anim);label[0].show().stop().animateWith(frame,anim,{transform:["t",lx,ly]},200*is_label_visible);label[1].show().stop().animateWith(frame,anim,{transform:["t",lx,ly]},200*is_label_visible);dot.attr("r",6);is_label_visible=!0},function(){dot.attr("r",4);leave_timer=setTimeout(function(){frame.hide();label[0].hide();label[1].hide();is_label_visible=!1},1)})})(x,y,data[i],labels[i],dot)}
5
- p=p.concat([x,y,x,y]);bgpp=bgpp.concat([x,y,x,y,"L",x,height-bottomgutter,"z"]);path.attr({path:p});bgp.attr({path:bgpp});frame.toFront();label[0].toFront();label[1].toFront();blanket.toFront()})()
1
+ (function(){var labels=[],data=[];jQuery('#ab_chart_data tfoot th').each(function(){labels.push(jQuery(this).text())});jQuery('#ab_chart_data tbody td').each(function(){data.push(jQuery(this).text())});var width=jQuery('#ab_chart').parent().width()+8,height=140,leftgutter=0,bottomgutter=22,topgutter=22,color='#135e96',r=Raphael('ab_chart',width,height),txt={font:'bold 12px "Open Sans", sans-serif',fill:'#1d2327'},X=(width-leftgutter*2)/labels.length,max=Math.max.apply(Math,data),Y=(height-bottomgutter-topgutter)/max;r.text(16,16,max).attr({font:'normal 10px "Open Sans", sans-serif',fill:'#a7aaad',});var path=r.path().attr({stroke:color,'stroke-width':2,'stroke-linejoin':'round'}),bgp=r.path().attr({stroke:'none',opacity:.3,fill:color}),label=r.set(),lx=0,ly=0,is_label_visible=!1,leave_timer,blanket=r.set();label.push(r.text(60,12,'').attr(txt));label.push(r.text(60,27,'').attr(txt).attr({fill:color}));label.hide();var frame=r.popup(100,100,label,'right').attr({fill:'#fff',stroke:'#444','stroke-width':1}).hide();var p,bgpp;for(var i=0,ii=labels.length;i<ii;i++){var y=Math.round(height-bottomgutter-Y*data[i]),x=Math.round(leftgutter+X*(i+.5));if(!i){p=['M',x,y,'C',x,y];bgpp=['M',leftgutter+X*.5,height-bottomgutter,'L',x,y,'C',x,y]}
2
  if(i&&i<ii-1){var Y0=Math.round(height-bottomgutter-Y*data[i-1]),X0=Math.round(leftgutter+X*(i-.5)),Y2=Math.round(height-bottomgutter-Y*data[i+1]),X2=Math.round(leftgutter+X*(i+1.5));var a=getAnchors(X0,Y0,x,y,X2,Y2);p=p.concat([a.x1,a.y1,x,y,a.x2,a.y2]);bgpp=bgpp.concat([a.x1,a.y1,x,y,a.x2,a.y2])}
3
+ var dot=r.circle(x,y,4).attr({fill:'#fff',stroke:color,'stroke-width':1});blanket.push(r.rect(leftgutter+X*i,0,X,height-bottomgutter).attr({stroke:'none',fill:'#fff',opacity:.2}));var rect=blanket[blanket.length-1];(function(x,y,data,date,dot){var timer,i=0;rect.hover(function(){clearTimeout(leave_timer);var side='right';if(x+frame.getBBox().width>width){side='left'}
4
+ label[0].attr({text:date});label[1].attr({text:data+'× Spam'});var ppp=r.popup(x,y,label,side,1),anim=Raphael.animation({path:ppp.path,transform:['t',ppp.dx,ppp.dy],},200*is_label_visible);lx=label[0].transform()[0][1]+ppp.dx;ly=label[0].transform()[0][2]+ppp.dy;frame.show().stop().animate(anim);label[0].show().stop().animateWith(frame,anim,{transform:['t',lx,ly]},200*is_label_visible);label[1].show().stop().animateWith(frame,anim,{transform:['t',lx,ly]},200*is_label_visible);dot.attr('r',6);is_label_visible=!0},function(){dot.attr('r',4);leave_timer=setTimeout(function(){frame.hide();label[0].hide();label[1].hide();is_label_visible=!1},1)})}(x,y,data[i],labels[i],dot))}
5
+ p=p.concat([x,y,x,y]);bgpp=bgpp.concat([x,y,x,y,'L',x,height-bottomgutter,'z']);path.attr({path:p});bgp.attr({path:bgpp});frame.toFront();label[0].toFront();label[1].toFront();blanket.toFront()}())
js/raphael.helper.js CHANGED
@@ -1,142 +1,140 @@
1
  var tokenRegex = /\{([^\}]+)\}/g,
2
- objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,
3
- replacer = function (all, key, obj) {
4
- var res = obj;
5
- key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) {
6
- name = name || quotedName;
7
- if (res) {
8
- if (name in res) {
9
- res = res[name];
10
- }
11
- typeof res == "function" && isFunc && (res = res());
12
- }
13
- });
14
- res = (res == null || res == obj ? all : res) + "";
15
- return res;
16
- },
17
- fill = function (str, obj) {
18
- return String(str).replace(tokenRegex, function (all, key) {
19
- return replacer(all, key, obj);
20
- });
21
- };
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
- Raphael.fn.popup = function (X, Y, set, pos, ret) {
25
- pos = String(pos || "top-middle").split("-");
26
- pos[1] = pos[1] || "middle";
27
- var r = 5,
28
- bb = set.getBBox(),
29
- w = Math.round(bb.width),
30
- h = Math.round(bb.height),
31
- x = Math.round(bb.x) - r,
32
- y = Math.round(bb.y) - r,
33
- gap = Math.min(h / 2, w / 2, 10),
34
- shapes = {
35
- top: "M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}l-{right},0-{gap},{gap}-{gap}-{gap}-{left},0a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z",
36
- bottom: "M{x},{y}l{left},0,{gap}-{gap},{gap},{gap},{right},0a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z",
37
- right: "M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}l0-{bottom}-{gap}-{gap},{gap}-{gap},0-{top}a{r},{r},0,0,1,{r}-{r}z",
38
- left: "M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}l0,{top},{gap},{gap}-{gap},{gap},0,{bottom}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z"
39
- },
40
- offset = {
41
- hx0: X - (x + r + w - gap * 2),
42
- hx1: X - (x + r + w / 2 - gap),
43
- hx2: X - (x + r + gap),
44
- vhy: Y - (y + r + h + r + gap),
45
- "^hy": Y - (y - gap)
46
-
47
- },
48
- mask = [{
49
- x: x + r,
50
- y: y,
51
- w: w,
52
- w4: w / 4,
53
- h4: h / 4,
54
- right: 0,
55
- left: w - gap * 2,
56
- bottom: 0,
57
- top: h - gap * 2,
58
- r: r,
59
- h: h,
60
- gap: gap
61
- }, {
62
- x: x + r,
63
- y: y,
64
- w: w,
65
- w4: w / 4,
66
- h4: h / 4,
67
- left: w / 2 - gap,
68
- right: w / 2 - gap,
69
- top: h / 2 - gap,
70
- bottom: h / 2 - gap,
71
- r: r,
72
- h: h,
73
- gap: gap
74
- }, {
75
- x: x + r,
76
- y: y,
77
- w: w,
78
- w4: w / 4,
79
- h4: h / 4,
80
- left: 0,
81
- right: w - gap * 2,
82
- top: 0,
83
- bottom: h - gap * 2,
84
- r: r,
85
- h: h,
86
- gap: gap
87
- }][pos[1] == "middle" ? 1 : (pos[1] == "top" || pos[1] == "left") * 2];
88
- var dx = 0,
89
- dy = 0,
90
- out = this.path(fill(shapes[pos[0]], mask)).insertBefore(set);
91
- switch (pos[0]) {
92
- case "top":
93
- dx = X - (x + r + mask.left + gap);
94
- dy = Y - (y + r + h + r + gap);
95
- break;
96
- case "bottom":
97
- dx = X - (x + r + mask.left + gap);
98
- dy = Y - (y - gap);
99
- break;
100
- case "left":
101
- dx = X - (x + r + w + r + gap);
102
- dy = Y - (y + r + mask.top + gap);
103
- break;
104
- case "right":
105
- dx = X - (x - gap);
106
- dy = Y - (y + r + mask.top + gap);
107
- break;
108
- }
109
- out.translate(dx, dy);
110
- if (ret) {
111
- ret = out.attr("path");
112
- out.remove();
113
- return {
114
- path: ret,
115
- dx: dx,
116
- dy: dy
117
- };
118
- }
119
- set.translate(dx, dy);
120
- return out;
121
  };
122
 
123
-
124
- function getAnchors(p1x, p1y, p2x, p2y, p3x, p3y) {
125
- var l1 = (p2x - p1x) / 2,
126
- l2 = (p3x - p2x) / 2,
127
- a = Math.atan((p2x - p1x) / Math.abs(p2y - p1y)),
128
- b = Math.atan((p3x - p2x) / Math.abs(p2y - p3y));
129
- a = p1y < p2y ? Math.PI - a : a;
130
- b = p3y < p2y ? Math.PI - b : b;
131
- var alpha = Math.PI / 2 - ((a + b) % (Math.PI * 2)) / 2,
132
- dx1 = l1 * Math.sin(alpha + a),
133
- dy1 = l1 * Math.cos(alpha + a),
134
- dx2 = l2 * Math.sin(alpha + b),
135
- dy2 = l2 * Math.cos(alpha + b);
136
- return {
137
- x1: p2x - dx1,
138
- y1: p2y + dy1,
139
- x2: p2x + dx2,
140
- y2: p2y + dy2
141
- };
142
- }
1
  var tokenRegex = /\{([^\}]+)\}/g,
2
+ objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,
3
+ replacer = function( all, key, obj ) {
4
+ var res = obj;
5
+ key.replace( objNotationRegex, function( all, name, quote, quotedName, isFunc ) {
6
+ name = name || quotedName;
7
+ if ( res ) {
8
+ if ( name in res ) {
9
+ res = res[name];
10
+ }
11
+ typeof res === 'function' && isFunc && ( res = res() );
12
+ }
13
+ } );
14
+ res = ( res == null || res == obj ? all : res ) + '';
15
+ return res;
16
+ },
17
+ fill = function( str, obj ) {
18
+ return String( str ).replace( tokenRegex, function( all, key ) {
19
+ return replacer( all, key, obj );
20
+ } );
21
+ };
22
 
23
+ Raphael.fn.popup = function( X, Y, set, pos, ret ) {
24
+ pos = String( pos || 'top-middle' ).split( '-' );
25
+ pos[1] = pos[1] || 'middle';
26
+ var r = 5,
27
+ bb = set.getBBox(),
28
+ w = Math.round( bb.width ),
29
+ h = Math.round( bb.height ),
30
+ x = Math.round( bb.x ) - r,
31
+ y = Math.round( bb.y ) - r,
32
+ gap = Math.min( h / 2, w / 2, 10 ),
33
+ shapes = {
34
+ top: 'M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}l-{right},0-{gap},{gap}-{gap}-{gap}-{left},0a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z',
35
+ bottom: 'M{x},{y}l{left},0,{gap}-{gap},{gap},{gap},{right},0a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z',
36
+ right: 'M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}l0-{bottom}-{gap}-{gap},{gap}-{gap},0-{top}a{r},{r},0,0,1,{r}-{r}z',
37
+ left: 'M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}l0,{top},{gap},{gap}-{gap},{gap},0,{bottom}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z',
38
+ },
39
+ offset = {
40
+ hx0: X - ( x + r + w - gap * 2 ),
41
+ hx1: X - ( x + r + w / 2 - gap ),
42
+ hx2: X - ( x + r + gap ),
43
+ vhy: Y - ( y + r + h + r + gap ),
44
+ '^hy': Y - ( y - gap ),
45
 
46
+ },
47
+ mask = [ {
48
+ x: x + r,
49
+ y: y,
50
+ w: w,
51
+ w4: w / 4,
52
+ h4: h / 4,
53
+ right: 0,
54
+ left: w - gap * 2,
55
+ bottom: 0,
56
+ top: h - gap * 2,
57
+ r: r,
58
+ h: h,
59
+ gap: gap,
60
+ }, {
61
+ x: x + r,
62
+ y: y,
63
+ w: w,
64
+ w4: w / 4,
65
+ h4: h / 4,
66
+ left: w / 2 - gap,
67
+ right: w / 2 - gap,
68
+ top: h / 2 - gap,
69
+ bottom: h / 2 - gap,
70
+ r: r,
71
+ h: h,
72
+ gap: gap,
73
+ }, {
74
+ x: x + r,
75
+ y: y,
76
+ w: w,
77
+ w4: w / 4,
78
+ h4: h / 4,
79
+ left: 0,
80
+ right: w - gap * 2,
81
+ top: 0,
82
+ bottom: h - gap * 2,
83
+ r: r,
84
+ h: h,
85
+ gap: gap,
86
+ } ][pos[1] == 'middle' ? 1 : ( pos[1] == 'top' || pos[1] == 'left' ) * 2];
87
+ var dx = 0,
88
+ dy = 0,
89
+ out = this.path( fill( shapes[pos[0]], mask ) ).insertBefore( set );
90
+ switch ( pos[0] ) {
91
+ case 'top':
92
+ dx = X - ( x + r + mask.left + gap );
93
+ dy = Y - ( y + r + h + r + gap );
94
+ break;
95
+ case 'bottom':
96
+ dx = X - ( x + r + mask.left + gap );
97
+ dy = Y - ( y - gap );
98
+ break;
99
+ case 'left':
100
+ dx = X - ( x + r + w + r + gap );
101
+ dy = Y - ( y + r + mask.top + gap );
102
+ break;
103
+ case 'right':
104
+ dx = X - ( x - gap );
105
+ dy = Y - ( y + r + mask.top + gap );
106
+ break;
107
+ }
108
+ out.translate( dx, dy );
109
+ if ( ret ) {
110
+ ret = out.attr( 'path' );
111
+ out.remove();
112
+ return {
113
+ path: ret,
114
+ dx: dx,
115
+ dy: dy,
116
+ };
117
+ }
118
+ set.translate( dx, dy );
119
+ return out;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  };
121
 
122
+ function getAnchors( p1x, p1y, p2x, p2y, p3x, p3y ) {
123
+ var l1 = ( p2x - p1x ) / 2,
124
+ l2 = ( p3x - p2x ) / 2,
125
+ a = Math.atan( ( p2x - p1x ) / Math.abs( p2y - p1y ) ),
126
+ b = Math.atan( ( p3x - p2x ) / Math.abs( p2y - p3y ) );
127
+ a = p1y < p2y ? Math.PI - a : a;
128
+ b = p3y < p2y ? Math.PI - b : b;
129
+ var alpha = Math.PI / 2 - ( ( a + b ) % ( Math.PI * 2 ) ) / 2,
130
+ dx1 = l1 * Math.sin( alpha + a ),
131
+ dy1 = l1 * Math.cos( alpha + a ),
132
+ dx2 = l2 * Math.sin( alpha + b ),
133
+ dy2 = l2 * Math.cos( alpha + b );
134
+ return {
135
+ x1: p2x - dx1,
136
+ y1: p2y + dy1,
137
+ x2: p2x + dx2,
138
+ y2: p2y + dy2,
139
+ };
140
+ }
 
js/raphael.helper.min.js CHANGED
@@ -1,5 +1,4 @@
1
- var tokenRegex=/\{([^\}]+)\}/g,objNotationRegex=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,replacer=function(c,h,g){var a=g;h.replace(objNotationRegex,function(c,b,f,d,g){b=b||d;a&&(b in a&&(a=a[b]),"function"==typeof a&&g&&(a=a()))});return a=(null==a||a==g?c:a)+""},fill=function(c,h){return String(c).replace(tokenRegex,function(c,a){return replacer(c,a,h)})};
2
- Raphael.fn.popup=function(c,h,g,a,k){a=String(a||"top-middle").split("-");a[1]=a[1]||"middle";var b=g.getBBox(),f=Math.round(b.width),d=Math.round(b.height),l=Math.round(b.x)-5,b=Math.round(b.y)-5,e=Math.min(d/2,f/2,10),p=[{x:l+5,y:b,w:f,w4:f/4,h4:d/4,right:0,left:f-2*e,bottom:0,top:d-2*e,r:5,h:d,gap:e},{x:l+5,y:b,w:f,w4:f/4,h4:d/4,left:f/2-e,right:f/2-e,top:d/2-e,bottom:d/2-e,r:5,h:d,gap:e},{x:l+5,y:b,w:f,w4:f/4,h4:d/4,left:0,right:f-2*e,top:0,bottom:d-2*e,r:5,h:d,gap:e}]["middle"==a[1]?1:2*("top"==
3
- a[1]||"left"==a[1])],m=0,n=0,q=this.path(fill({top:"M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}l-{right},0-{gap},{gap}-{gap}-{gap}-{left},0a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z",bottom:"M{x},{y}l{left},0,{gap}-{gap},{gap},{gap},{right},0a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z",right:"M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}l0-{bottom}-{gap}-{gap},{gap}-{gap},0-{top}a{r},{r},0,0,1,{r}-{r}z",
4
- left:"M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}l0,{top},{gap},{gap}-{gap},{gap},0,{bottom}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z"}[a[0]],p)).insertBefore(g);switch(a[0]){case "top":m=c-(l+5+p.left+e);n=h-(b+5+d+5+e);break;case "bottom":m=c-(l+5+p.left+e);n=h-(b-e);break;case "left":m=c-(l+5+f+5+e);n=h-(b+5+p.top+e);break;case "right":m=c-(l-e),n=h-(b+5+p.top+e)}q.translate(m,n);if(k)return k=q.attr("path"),q.remove(),
5
- {path:k,dx:m,dy:n};g.translate(m,n);return q};function getAnchors(c,h,g,a,k,b){var f=(g-c)/2,d=(k-g)/2;c=Math.atan((g-c)/Math.abs(a-h));k=Math.atan((k-g)/Math.abs(a-b));c=h<a?Math.PI-c:c;k=b<a?Math.PI-k:k;b=Math.PI/2-(c+k)%(2*Math.PI)/2;h=f*Math.sin(b+c);f*=Math.cos(b+c);c=d*Math.sin(b+k);d*=Math.cos(b+k);return{x1:g-h,y1:a+f,x2:g+c,y2:a+d}};
1
+ var tokenRegex=/\{([^\}]+)\}/g,objNotationRegex=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,replacer=function(all,key,obj){var res=obj;key.replace(objNotationRegex,function(all,name,quote,quotedName,isFunc){name=name||quotedName;if(res){if(name in res){res=res[name]}
2
+ typeof res==='function'&&isFunc&&(res=res())}});res=(res==null||res==obj?all:res)+'';return res},fill=function(str,obj){return String(str).replace(tokenRegex,function(all,key){return replacer(all,key,obj)})};Raphael.fn.popup=function(X,Y,set,pos,ret){pos=String(pos||'top-middle').split('-');pos[1]=pos[1]||'middle';var r=5,bb=set.getBBox(),w=Math.round(bb.width),h=Math.round(bb.height),x=Math.round(bb.x)-r,y=Math.round(bb.y)-r,gap=Math.min(h/2,w/2,10),shapes={top:'M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}l-{right},0-{gap},{gap}-{gap}-{gap}-{left},0a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z',bottom:'M{x},{y}l{left},0,{gap}-{gap},{gap},{gap},{right},0a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z',right:'M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}l0-{bottom}-{gap}-{gap},{gap}-{gap},0-{top}a{r},{r},0,0,1,{r}-{r}z',left:'M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}l0,{top},{gap},{gap}-{gap},{gap},0,{bottom}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z',},offset={hx0:X-(x+r+w-gap*2),hx1:X-(x+r+w/2-gap),hx2:X-(x+r+gap),vhy:Y-(y+r+h+r+gap),'^hy':Y-(y-gap),},mask=[{x:x+r,y:y,w:w,w4:w/4,h4:h/4,right:0,left:w-gap*2,bottom:0,top:h-gap*2,r:r,h:h,gap:gap,},{x:x+r,y:y,w:w,w4:w/4,h4:h/4,left:w/2-gap,right:w/2-gap,top:h/2-gap,bottom:h/2-gap,r:r,h:h,gap:gap,},{x:x+r,y:y,w:w,w4:w/4,h4:h/4,left:0,right:w-gap*2,top:0,bottom:h-gap*2,r:r,h:h,gap:gap,}][pos[1]=='middle'?1:(pos[1]=='top'||pos[1]=='left')*2];var dx=0,dy=0,out=this.path(fill(shapes[pos[0]],mask)).insertBefore(set);switch(pos[0]){case 'top':dx=X-(x+r+mask.left+gap);dy=Y-(y+r+h+r+gap);break;case 'bottom':dx=X-(x+r+mask.left+gap);dy=Y-(y-gap);break;case 'left':dx=X-(x+r+w+r+gap);dy=Y-(y+r+mask.top+gap);break;case 'right':dx=X-(x-gap);dy=Y-(y+r+mask.top+gap);break}
3
+ out.translate(dx,dy);if(ret){ret=out.attr('path');out.remove();return{path:ret,dx:dx,dy:dy,}}
4
+ set.translate(dx,dy);return out};function getAnchors(p1x,p1y,p2x,p2y,p3x,p3y){var l1=(p2x-p1x)/2,l2=(p3x-p2x)/2,a=Math.atan((p2x-p1x)/Math.abs(p2y-p1y)),b=Math.atan((p3x-p2x)/Math.abs(p2y-p3y));a=p1y<p2y?Math.PI-a:a;b=p3y<p2y?Math.PI-b:b;var alpha=Math.PI/2-((a+b)%(Math.PI*2))/2,dx1=l1*Math.sin(alpha+a),dy1=l1*Math.cos(alpha+a),dx2=l2*Math.sin(alpha+b),dy2=l2*Math.cos(alpha+b);return{x1:p2x-dx1,y1:p2y+dy1,x2:p2x+dx2,y2:p2y+dy2,}}
 
js/scripts.js CHANGED
@@ -1,16 +1,16 @@
1
- jQuery(document).ready(
2
- function($) {
3
  function ab_flag_spam() {
4
- var $$ = $('#ab_flag_spam'),
5
- nextAll = $$.parent('li').nextAll( '.ab_flag_spam_child' );
6
 
7
  nextAll.css(
8
  'display',
9
- ( $$.is(':checked') ? 'list-item' : 'none' )
10
  );
11
  }
12
 
13
- $('#ab_flag_spam').on(
14
  'change',
15
  ab_flag_spam
16
  );
1
+ jQuery( document ).ready(
2
+ function( $ ) {
3
  function ab_flag_spam() {
4
+ var $$ = $( '#ab_flag_spam' ),
5
+ nextAll = $$.parent( 'li' ).nextAll( '.ab_flag_spam_child' );
6
 
7
  nextAll.css(
8
  'display',
9
+ ( $$.is( ':checked' ) ? 'list-item' : 'none' )
10
  );
11
  }
12
 
13
+ $( '#ab_flag_spam' ).on(
14
  'change',
15
  ab_flag_spam
16
  );
js/scripts.min.js CHANGED
@@ -1 +1,2 @@
1
- jQuery(document).ready(function(a){function b(){var b=a("#ab_flag_spam"),c=b.parent("li").nextAll(".ab_flag_spam_child");c.css("display",b.is(":checked")?"list-item":"none")}a("#ab_flag_spam").on("change",b),b()});
 
1
+ jQuery(document).ready(function($){function ab_flag_spam(){var $$=$('#ab_flag_spam'),nextAll=$$.parent('li').nextAll('.ab_flag_spam_child');nextAll.css('display',($$.is(':checked')?'list-item':'none'))}
2
+ $('#ab_flag_spam').on('change',ab_flag_spam);ab_flag_spam()})
output.log ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Running php-build composer
2
+ WARNING! Your password will be stored unencrypted in /home/runner/.docker/config.json.
3
+ Configure a credential helper to remove this warning. See
4
+ https://docs.docker.com/engine/reference/commandline/login/#credentials-store
5
+
6
+ Login Succeeded
7
+ Pulling docker.pkg.github.com/pluginkollektiv/antispam-bee/php-actions_composer_antispam-bee:php-latest-build2
8
+ WARNING: ⚠️ Failed to pull manifest by the resolved digest. This registry does not
9
+ appear to conform to the distribution registry specification; falling back to
10
+ pull by tag. This fallback is DEPRECATED, and will be removed in a future
11
+ release. Please contact admins of https://docker.pkg.github.com. ⚠️
12
+
13
+ php-latest-build2: Pulling from pluginkollektiv/antispam-bee/php-actions_composer_antispam-bee
14
+ 9aae54b2144e: Pulling fs layer
15
+ 0552190813ec: Pulling fs layer
16
+ d6abf49ad7ff: Pulling fs layer
17
+ 4a7b791901cf: Pulling fs layer
18
+ 483f3f97a5bc: Pulling fs layer
19
+ e078e9d6b760: Pulling fs layer
20
+ 2c134042be40: Pulling fs layer
21
+ db5fecf1aa2d: Pulling fs layer
22
+ f28c38a75bec: Pulling fs layer
23
+ 159f106d1fc1: Pulling fs layer
24
+ 4a7b791901cf: Waiting
25
+ 483f3f97a5bc: Waiting
26
+ e078e9d6b760: Waiting
27
+ 2c134042be40: Waiting
28
+ db5fecf1aa2d: Waiting
29
+ f28c38a75bec: Waiting
30
+ 159f106d1fc1: Waiting
31
+ d6abf49ad7ff: Verifying Checksum
32
+ d6abf49ad7ff: Download complete
33
+ 0552190813ec: Verifying Checksum
34
+ 0552190813ec: Download complete
35
+ 4a7b791901cf: Verifying Checksum
36
+ 4a7b791901cf: Download complete
37
+ 9aae54b2144e: Verifying Checksum
38
+ 9aae54b2144e: Download complete
39
+ e078e9d6b760: Verifying Checksum
40
+ e078e9d6b760: Download complete
41
+ 9aae54b2144e: Pull complete
42
+ 0552190813ec: Pull complete
43
+ d6abf49ad7ff: Pull complete
44
+ 4a7b791901cf: Pull complete
45
+ 483f3f97a5bc: Verifying Checksum
46
+ 483f3f97a5bc: Download complete
47
+ 483f3f97a5bc: Pull complete
48
+ e078e9d6b760: Pull complete
49
+ f28c38a75bec: Verifying Checksum
50
+ f28c38a75bec: Download complete
51
+ 2c134042be40: Verifying Checksum
52
+ 2c134042be40: Download complete
53
+ db5fecf1aa2d: Verifying Checksum
54
+ db5fecf1aa2d: Download complete
55
+ 159f106d1fc1: Verifying Checksum
56
+ 159f106d1fc1: Download complete
57
+ 2c134042be40: Pull complete
58
+ db5fecf1aa2d: Pull complete
59
+ f28c38a75bec: Pull complete
60
+ 159f106d1fc1: Pull complete
61
+ Digest: sha256:a6b0df3c018b4a3bb89e86996e4f4f3b15381d959d9985a1074cb62e916119df
62
+ Status: Downloaded newer image for docker.pkg.github.com/pluginkollektiv/antispam-bee/php-actions_composer_antispam-bee:php-latest-build2
63
+ docker.pkg.github.com/pluginkollektiv/antispam-bee/php-actions_composer_antispam-bee:php-latest-build2
64
+ Docker tag: docker.pkg.github.com/pluginkollektiv/antispam-bee/php-actions_composer_antispam-bee:php-latest-build2
65
+ No private keys supplied
66
+ Command: composer install --no-progress --no-interaction --ignore-platform-reqs
phpunit.xml.dist DELETED
@@ -1,25 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <phpunit
3
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
- xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.8/phpunit.xsd"
5
- backupGlobals="false"
6
- backupStaticAttributes="false"
7
- bootstrap="vendor/autoload.php"
8
- colors="true"
9
- convertErrorsToExceptions="true"
10
- convertNoticesToExceptions="true"
11
- convertWarningsToExceptions="true"
12
- processIsolation="false"
13
- stopOnFailure="false">
14
- <testsuites>
15
- <testsuite name="unit">
16
- <directory suffix=".php">tests/Unit</directory>
17
- </testsuite>
18
- <testsuite name="integration">
19
- <directory suffix=".php">tests/Integration</directory>
20
- </testsuite>
21
- </testsuites>
22
- <listeners>
23
- <listener class="TestListener" file="tests/TestListener.php"/>
24
- </listeners>
25
- </phpunit>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
readme.txt CHANGED
@@ -1,11 +1,11 @@
1
  # Antispam Bee #
2
- * Contributors: pluginkollektiv, websupporter, schlessera, zodiac1978, swissspidy, krafit, kau-boy, florianbrinkmann
3
  * Tags: anti-spam, antispam, block spam, comment, comments, comment spam, pingback, spam, spam filter, trackback, GDPR
4
  * Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=TD4AMD2D8EMZW
5
  * Requires at least: 4.5
6
- * Tested up to: 5.7
7
  * Requires PHP: 5.2
8
- * Stable tag: 2.9.4
9
  * License: GPLv2 or later
10
  * License URI: https://www.gnu.org/licenses/gpl-2.0.html
11
 
@@ -92,8 +92,19 @@ A complete documentation is available on [pluginkollektiv.org](https://antispamb
92
 
93
  ## Changelog ##
94
 
95
- ### 2.9.4 ###
 
 
 
 
 
 
 
 
 
 
96
 
 
97
  * Enhancement: Add filter to allow ajax calls
98
  * Tweak: Better wording for BBCode feature in plugin description
99
  * Tweak: Better screenshots in the plugin directory
1
  # Antispam Bee #
2
+ * Contributors: pluginkollektiv, websupporter, schlessera, zodiac1978, swissspidy, krafit, kau-boy, florianbrinkmann, pfefferle
3
  * Tags: anti-spam, antispam, block spam, comment, comments, comment spam, pingback, spam, spam filter, trackback, GDPR
4
  * Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=TD4AMD2D8EMZW
5
  * Requires at least: 4.5
6
+ * Tested up to: 5.8
7
  * Requires PHP: 5.2
8
+ * Stable tag: 2.10.0
9
  * License: GPLv2 or later
10
  * License URI: https://www.gnu.org/licenses/gpl-2.0.html
11
 
92
 
93
  ## Changelog ##
94
 
95
+ ### 2.10.0 ###
96
+ * Fix: Switch from ip2country.info to iplocate.io for country check
97
+ * Enhancement: Use filter to add the honeypot field instead of output buffering for new installations and added option to switch between the both ways
98
+ * Tweak: Added comment user agent to regex pattern check
99
+ * Tweak: Make the ping detection filterable to support new comment types
100
+ * Tweak: Updated internal documentation links
101
+ * Tweak: Several updates and optimizations in the testing process
102
+ * Tweak: Adjust color palette to recent WP version
103
+ * Tweak: Adjust wording in variables and option names
104
+ * Readme: Add new contributor and clean up unused code
105
+
106
 
107
+ ### 2.9.4 ###
108
  * Enhancement: Add filter to allow ajax calls
109
  * Tweak: Better wording for BBCode feature in plugin description
110
  * Tweak: Better screenshots in the plugin directory