1- <?php declare ( strict_types=1 );
2-
3- namespace FernleafSystems \Wordpress \Plugin \Shield \Modules \IPs \Lib \Bots ;
4-
5- use FernleafSystems \Utilities \Logic \ExecOnce ;
6- use FernleafSystems \Wordpress \Plugin \Shield \Crons \PluginCronsConsumer ;
7- use FernleafSystems \Wordpress \Plugin \Shield \Modules \IPs \BotTrack ;
8- use FernleafSystems \Wordpress \Plugin \Shield \Modules \IPs \Lib \Bots \Calculator \ScoreLogic ;
9- use FernleafSystems \Wordpress \Plugin \Shield \Modules \IPs \Lib \Bots \NotBot \NotBotHandler ;
10- use FernleafSystems \Wordpress \Plugin \Shield \Modules \PluginControllerConsumer ;
11- use FernleafSystems \Wordpress \Services \Services ;
12-
13- class BotSignalsController {
14-
15- use ExecOnce;
16- use PluginControllerConsumer;
17- use PluginCronsConsumer;
18-
19- private BotEventListener $ eventListener ;
20-
21- private array $ isBots = [];
22-
23- protected function run () {
24-
25- if ( self ::con ()->this_req ->ip_is_public || Services::Request ()->query ( 'force_notbot ' ) ) {
26- $ this ->getEventListener ()->execute ();
27- add_action ( 'init ' , fn () => \array_map ( fn ( $ c ) => ( new $ c () )->execute (), $ this ->enumerateBotTrackers () ) );
28- self ::con ()->comps ->not_bot ->execute ();
29- $ this ->registerFrontPageLoad ();
30- $ this ->registerLoginPageLoad ();
31- }
32-
33- $ this ->setupCronHooks ();
34- }
35-
36- public function runDailyCron () {
37- ( new ScoreLogic () )->getScoringLogic ( true );
38- }
39-
40- public function isBot ( string $ IP = '' , bool $ allowEventFire = true , bool $ forceCheck = false ) :bool {
41-
42- if ( !isset ( $ this ->isBots [ $ IP ] ) || $ forceCheck ) {
43- $ con = self ::con ();
44-
45- $ this ->isBots [ $ IP ] = false ;
46-
47- if ( !$ con ->comps ->opts_lookup ->enabledAntiBotEngine () ) {
48- $ con ->fireEvent ( 'ade_check_option_disabled ' );
49- }
50- else {
51- $ botScoreMinimum = $ con ->comps ->opts_lookup ->getAntiBotMinScore ();
52- if ( $ botScoreMinimum > 0 ) {
53-
54- $ score = ( new Calculator \CalculateVisitorBotScores () )
55- ->setIP ( empty ( $ IP ) ? self ::con ()->this_req ->ip : $ IP )
56- ->probability ();
57-
58- $ this ->isBots [ $ IP ] = $ score < $ botScoreMinimum ;
59-
60- if ( $ allowEventFire ) {
61- $ con ->fireEvent (
62- 'antibot_ ' .( $ this ->isBots [ $ IP ] ? 'fail ' : 'pass ' ),
63- [
64- 'audit_params ' => [
65- 'score ' => $ score ,
66- 'minimum ' => $ botScoreMinimum ,
67- ]
68- ]
69- );
70- }
71- }
72- }
73- }
74-
75- return $ this ->isBots [ $ IP ] ?? false ;
76- }
77-
78- public function getAllowableExt404s () :array {
79- $ def = self ::con ()->cfg ->configuration ->def ( 'bot_signals ' )[ 'allowable_ext_404s ' ] ?? [];
80- return \array_unique ( \array_filter (
81- apply_filters ( 'shield/bot_signals_allowable_extensions_404s ' , $ def ),
82- fn ( $ ext ) => !empty ( $ ext ) && \is_string ( $ ext ) && \preg_match ( '#^[a-z\d]+$#i ' , $ ext )
83- ) );
84- }
85-
86- public function getAllowablePaths404s () :array {
87- $ def = self ::con ()->cfg ->configuration ->def ( 'bot_signals ' )[ 'allowable_paths_404s ' ] ?? [];
88- return \array_unique ( \array_filter (
89- apply_filters ( 'shield/bot_signals_allowable_paths_404s ' , $ def ),
90- function ( $ ext ) {
91- return !empty ( $ ext ) && \is_string ( $ ext );
92- }
93- ) );
94- }
95-
96- public function getAllowableScripts () :array {
97- $ def = self ::con ()->cfg ->configuration ->def ( 'bot_signals ' )[ 'allowable_invalid_scripts ' ] ?? [];
98- return \array_unique ( \array_filter (
99- apply_filters ( 'shield/bot_signals_allowable_invalid_scripts ' , $ def ),
100- function ( $ script ) {
101- return !empty ( $ script ) && \is_string ( $ script ) && \strpos ( $ script , '.php ' );
102- }
103- ) );
104- }
105-
106- public function getEventListener () :BotEventListener {
107- return $ this ->eventListener ??= new BotEventListener ();
108- }
109-
110- /**
111- * @return string[]
112- */
113- private function enumerateBotTrackers () :array {
114- $ con = self ::con ();
115-
116- $ trackers = [
117- BotTrack \TrackCommentSpam::class
118- ];
119-
120- if ( !Services::WpUsers ()->isUserLoggedIn () ) {
121- if ( !$ con ->this_req ->request_bypasses_all_restrictions ) {
122- if ( !$ con ->opts ->optIs ( 'track_loginfailed ' , 'disabled ' ) ) {
123- $ trackers [] = BotTrack \TrackLoginFailed::class;
124- }
125- if ( !$ con ->opts ->optIs ( 'track_logininvalid ' , 'disabled ' ) ) {
126- $ trackers [] = BotTrack \TrackLoginInvalid::class;
127- }
128- }
129- }
130-
131- if ( !$ con ->opts ->optIs ( 'track_linkcheese ' , 'disabled ' ) ) {
132- $ trackers [] = BotTrack \TrackLinkCheese::class;
133- }
134-
135- return $ trackers ;
136- }
137-
138- private function registerFrontPageLoad () {
139- add_action ( self ::con ()->prefix ( 'pre_plugin_shutdown ' ), function () {
140- $ req = Services::Request ();
141- if ( $ req ->isGet () && did_action ( 'wp ' ) && ( is_page () || is_single () || is_front_page () || is_home () ) ) {
142- try {
143- $ record = ( new BotSignalsRecord () )
144- ->setIP ( self ::con ()->this_req ->ip )
145- ->retrieve ();
146- if ( $ req ->ts () - $ record ->frontpage_at > MINUTE_IN_SECONDS *30 ) {
147- $ this ->getEventListener ()->fireEventForIP ( self ::con ()->this_req ->ip , 'frontpage_load ' );
148- }
149- }
150- catch ( \Exception $ e ) {
151- }
152- }
153- } );
154- }
155-
156- private function registerLoginPageLoad () {
157- add_action ( 'login_footer ' , function () {
158- $ req = Services::Request ();
159- if ( $ req ->isGet () ) {
160- try {
161- $ record = ( new BotSignalsRecord () )
162- ->setIP ( self ::con ()->this_req ->ip )
163- ->retrieve ();
164- if ( $ req ->ts () - $ record ->loginpage_at > MINUTE_IN_SECONDS *10 ) {
165- $ this ->getEventListener ()->fireEventForIP ( self ::con ()->this_req ->ip , 'loginpage_load ' );
166- }
167- }
168- catch ( \Exception $ e ) {
169- }
170- }
171- } );
172- }
1+ <?php declare ( strict_types=1 );
2+
3+ namespace FernleafSystems \Wordpress \Plugin \Shield \Modules \IPs \Lib \Bots ;
4+
5+ use FernleafSystems \Utilities \Logic \ExecOnce ;
6+ use FernleafSystems \Wordpress \Plugin \Shield \Crons \PluginCronsConsumer ;
7+ use FernleafSystems \Wordpress \Plugin \Shield \Modules \IPs \BotTrack ;
8+ use FernleafSystems \Wordpress \Plugin \Shield \Modules \IPs \Lib \Bots \Calculator \ScoreLogic ;
9+ use FernleafSystems \Wordpress \Plugin \Shield \Modules \PluginControllerConsumer ;
10+ use FernleafSystems \Wordpress \Services \Services ;
11+
12+ class BotSignalsController {
13+
14+ use ExecOnce;
15+ use PluginControllerConsumer;
16+ use PluginCronsConsumer;
17+
18+ private BotEventListener $ eventListener ;
19+
20+ private array $ isBots = [];
21+
22+ protected function run () {
23+
24+ if ( self ::con ()->this_req ->ip_is_public || Services::Request ()->query ( 'force_notbot ' ) ) {
25+ $ this ->getEventListener ()->execute ();
26+ add_action ( 'init ' , fn () => \array_map ( fn ( $ c ) => ( new $ c () )->execute (), $ this ->enumerateBotTrackers () ) );
27+ self ::con ()->comps ->not_bot ->execute ();
28+ $ this ->registerFrontPageLoad ();
29+ $ this ->registerLoginPageLoad ();
30+ }
31+
32+ $ this ->setupCronHooks ();
33+ }
34+
35+ public function runDailyCron () {
36+ ( new ScoreLogic () )->getScoringLogic ( true );
37+ }
38+
39+ public function isBot ( string $ IP = '' , bool $ allowEventFire = true , bool $ forceCheck = false ) :bool {
40+
41+ if ( !isset ( $ this ->isBots [ $ IP ] ) || $ forceCheck ) {
42+ $ con = self ::con ();
43+
44+ $ this ->isBots [ $ IP ] = false ;
45+
46+ if ( !$ con ->comps ->opts_lookup ->enabledAntiBotEngine () ) {
47+ $ con ->comps ->events ->fireEvent ( 'ade_check_option_disabled ' );
48+ }
49+ else {
50+ $ botScoreMinimum = $ con ->comps ->opts_lookup ->getAntiBotMinScore ();
51+ if ( $ botScoreMinimum > 0 ) {
52+
53+ $ score = ( new Calculator \CalculateVisitorBotScores () )
54+ ->setIP ( empty ( $ IP ) ? self ::con ()->this_req ->ip : $ IP )
55+ ->probability ();
56+
57+ $ this ->isBots [ $ IP ] = $ score < $ botScoreMinimum ;
58+
59+ if ( $ allowEventFire ) {
60+ $ con ->comps ->events ->fireEvent (
61+ 'antibot_ ' .( $ this ->isBots [ $ IP ] ? 'fail ' : 'pass ' ),
62+ [
63+ 'audit_params ' => [
64+ 'score ' => $ score ,
65+ 'minimum ' => $ botScoreMinimum ,
66+ ]
67+ ]
68+ );
69+ }
70+ }
71+ }
72+ }
73+
74+ return $ this ->isBots [ $ IP ] ?? false ;
75+ }
76+
77+ public function getAllowableExt404s () :array {
78+ $ def = self ::con ()->cfg ->configuration ->def ( 'bot_signals ' )[ 'allowable_ext_404s ' ] ?? [];
79+ return \array_unique ( \array_filter (
80+ apply_filters ( 'shield/bot_signals_allowable_extensions_404s ' , $ def ),
81+ fn ( $ ext ) => !empty ( $ ext ) && \is_string ( $ ext ) && \preg_match ( '#^[a-z\d]+$#i ' , $ ext )
82+ ) );
83+ }
84+
85+ public function getAllowablePaths404s () :array {
86+ $ def = self ::con ()->cfg ->configuration ->def ( 'bot_signals ' )[ 'allowable_paths_404s ' ] ?? [];
87+ return \array_unique ( \array_filter (
88+ apply_filters ( 'shield/bot_signals_allowable_paths_404s ' , $ def ),
89+ fn ( $ ext ) => !empty ( $ ext ) && \is_string ( $ ext )
90+ ) );
91+ }
92+
93+ public function getAllowableScripts () :array {
94+ $ def = self ::con ()->cfg ->configuration ->def ( 'bot_signals ' )[ 'allowable_invalid_scripts ' ] ?? [];
95+ return \array_unique ( \array_filter (
96+ apply_filters ( 'shield/bot_signals_allowable_invalid_scripts ' , $ def ),
97+ fn ( $ script ) => !empty ( $ script ) && \is_string ( $ script ) && \strpos ( $ script , '.php ' )
98+ ) );
99+ }
100+
101+ public function getEventListener () :BotEventListener {
102+ return $ this ->eventListener ??= new BotEventListener ();
103+ }
104+
105+ /**
106+ * @return string[]
107+ */
108+ private function enumerateBotTrackers () :array {
109+ $ con = self ::con ();
110+
111+ $ trackers = [
112+ BotTrack \TrackCommentSpam::class
113+ ];
114+
115+ if ( !Services::WpUsers ()->isUserLoggedIn () ) {
116+ if ( !$ con ->this_req ->request_bypasses_all_restrictions ) {
117+ if ( !$ con ->opts ->optIs ( 'track_loginfailed ' , 'disabled ' ) ) {
118+ $ trackers [] = BotTrack \TrackLoginFailed::class;
119+ }
120+ if ( !$ con ->opts ->optIs ( 'track_logininvalid ' , 'disabled ' ) ) {
121+ $ trackers [] = BotTrack \TrackLoginInvalid::class;
122+ }
123+ }
124+ }
125+
126+ if ( !$ con ->opts ->optIs ( 'track_linkcheese ' , 'disabled ' ) ) {
127+ $ trackers [] = BotTrack \TrackLinkCheese::class;
128+ }
129+
130+ return $ trackers ;
131+ }
132+
133+ private function registerFrontPageLoad () {
134+ add_action ( self ::con ()->prefix ( 'pre_plugin_shutdown ' ), function () {
135+ $ req = Services::Request ();
136+ if ( $ req ->isGet () && did_action ( 'wp ' ) && ( is_page () || is_single () || is_front_page () || is_home () ) ) {
137+ try {
138+ $ record = ( new BotSignalsRecord () )
139+ ->setIP ( self ::con ()->this_req ->ip )
140+ ->retrieve ();
141+ if ( $ req ->ts () - $ record ->frontpage_at > MINUTE_IN_SECONDS *30 ) {
142+ $ this ->getEventListener ()->fireEventForIP ( self ::con ()->this_req ->ip , 'frontpage_load ' );
143+ }
144+ }
145+ catch ( \Exception $ e ) {
146+ }
147+ }
148+ } );
149+ }
150+
151+ private function registerLoginPageLoad () {
152+ add_action ( 'login_footer ' , function () {
153+ $ req = Services::Request ();
154+ if ( $ req ->isGet () ) {
155+ try {
156+ $ record = ( new BotSignalsRecord () )
157+ ->setIP ( self ::con ()->this_req ->ip )
158+ ->retrieve ();
159+ if ( $ req ->ts () - $ record ->loginpage_at > MINUTE_IN_SECONDS *10 ) {
160+ $ this ->getEventListener ()->fireEventForIP ( self ::con ()->this_req ->ip , 'loginpage_load ' );
161+ }
162+ }
163+ catch ( \Exception $ e ) {
164+ }
165+ }
166+ } );
167+ }
173168}
0 commit comments