Shield Security for WordPress - Version 14.9.11

Version Description

Download this release

Release Info

Developer paultgoodchild
Plugin Icon 128x128 Shield Security for WordPress
Version 14.9.11
Comparing to
See all releases

Code changes from version 14.1.7 to 14.9.11

Files changed (483) hide show
  1. cl.json +96 -8
  2. config/admin_access_restriction.json +8 -3
  3. config/audit_trail.json +5 -8
  4. config/autoupdates.json +4 -0
  5. config/comments_filter.json +6 -122
  6. config/comms.json +1 -0
  7. config/data.json +7 -0
  8. config/deprecated/admin_access_restriction.php +8 -3
  9. config/deprecated/audit_trail.php +5 -8
  10. config/deprecated/autoupdates.php +4 -0
  11. config/deprecated/comments_filter.php +6 -122
  12. config/deprecated/comms.php +1 -0
  13. config/deprecated/data.php +8 -1
  14. config/deprecated/email.php +1 -0
  15. config/deprecated/events.php +1 -0
  16. config/deprecated/firewall.php +82 -84
  17. config/deprecated/hack_protect.php +35 -52
  18. config/deprecated/headers.php +4 -0
  19. config/deprecated/insights.php +2 -1
  20. config/deprecated/integrations.php +4 -0
  21. config/deprecated/ips.php +14 -16
  22. config/deprecated/license.php +1 -0
  23. config/deprecated/lockdown.php +9 -1
  24. config/deprecated/login_protect.php +38 -25
  25. config/deprecated/plugin.php +26 -110
  26. config/deprecated/reporting.php +1 -11
  27. config/deprecated/sessions.php +1 -0
  28. config/deprecated/traffic.php +5 -12
  29. config/deprecated/user_management.php +4 -0
  30. config/email.json +1 -0
  31. config/events.json +1 -0
  32. config/firewall.json +82 -84
  33. config/hack_protect.json +35 -52
  34. config/headers.json +4 -0
  35. config/insights.json +2 -1
  36. config/integrations.json +4 -0
  37. config/ips.json +14 -16
  38. config/license.json +1 -0
  39. config/lockdown.json +9 -1
  40. config/login_protect.json +38 -25
  41. config/plugin.json +26 -110
  42. config/reporting.json +1 -11
  43. config/sessions.json +1 -0
  44. config/traffic.json +5 -12
  45. config/user_management.json +4 -0
  46. icwp-wpsf.php +1 -1
  47. plugin-spec.php +41 -19
  48. plugin.json +41 -19
  49. resources/css/global-plugin.css +85 -104
  50. resources/css/plugin.css +63 -663
  51. resources/images/bootstrap/clipboard2-data-fill.svg +4 -0
  52. resources/images/bootstrap/hand-thumbs-up.svg +3 -0
  53. resources/images/bootstrap/question-circle.svg +4 -0
  54. resources/images/bootstrap/question-diamond.svg +3 -3
  55. resources/images/bootstrap/question-square.svg +4 -0
  56. resources/js/global-plugin.js +43 -11
  57. resources/js/ip_detect.js +0 -15
  58. resources/js/plugin.js +157 -86
  59. resources/js/shield/comments.js +0 -137
  60. resources/js/shield/ip_detect.js +10 -0
  61. resources/js/shield/ipanalyse.js +8 -9
  62. resources/js/shield/navigation.js +39 -29
  63. resources/js/shield/shuffle.js +0 -194
  64. src/lib/src/Blocks/RenderBlockPages/BaseBlockPage.php +57 -0
  65. src/lib/src/Blocks/RenderBlockPages/RenderBlockAuthorFishing.php +43 -0
  66. src/lib/src/Blocks/RenderBlockPages/RenderBlockFirewall.php +57 -0
  67. src/lib/src/Blocks/RenderBlockPages/RenderBlockIP.php +145 -0
  68. src/lib/src/Controller/Admin/AdminBarMenu.php +1 -1
  69. src/lib/src/Controller/Admin/DashboardWidget.php +3 -2
  70. src/lib/src/Controller/Ajax/Init.php +4 -1
  71. src/lib/src/Controller/Assets/Enqueue.php +1 -0
  72. src/lib/src/Controller/Config/ConfigVO.php +45 -16
  73. src/lib/src/Controller/Config/Ops/LoadConfig.php +0 -1
  74. src/lib/src/Controller/Config/Ops/Read.php +0 -4
  75. src/lib/src/Controller/Controller.php +102 -53
  76. src/lib/src/Databases/Base/Handler.php +1 -0
  77. src/lib/src/Databases/Common/AlignTableWithSchema.php +1 -0
  78. src/lib/src/Databases/Events/Select.php +12 -24
  79. src/lib/src/Databases/IPs/Handler.php +1 -1
  80. src/lib/src/Databases/ReqLogs/QueueReqDbRecordMigrator.php +3 -0
  81. src/lib/src/Databases/ScanQueue/Common.php +0 -59
  82. src/lib/src/Databases/ScanQueue/Delete.php +0 -10
  83. src/lib/src/Databases/ScanQueue/EntryVO.php +0 -66
  84. src/lib/src/Databases/ScanQueue/Handler.php +0 -7
  85. src/lib/src/Databases/ScanQueue/Insert.php +0 -9
  86. src/lib/src/Databases/ScanQueue/Select.php +0 -75
  87. src/lib/src/Databases/ScanQueue/Update.php +0 -34
  88. src/lib/src/Databases/Scanner/Common.php +0 -103
  89. src/lib/src/Databases/Scanner/Delete.php +0 -20
  90. src/lib/src/Databases/Scanner/EntryVO.php +0 -18
  91. src/lib/src/Databases/Scanner/Handler.php +0 -7
  92. src/lib/src/Databases/Scanner/Insert.php +0 -7
  93. src/lib/src/Databases/Scanner/Select.php +0 -27
  94. src/lib/src/Databases/Scanner/Update.php +0 -41
  95. src/lib/src/Databases/Session/Update.php +1 -20
  96. src/lib/src/Helpers/QuickAccess.php +3 -5
  97. src/lib/src/Modules/AuditTrail/AdminNotices.php +1 -51
  98. src/lib/src/Modules/AuditTrail/Insights/OverviewCards.php +0 -40
  99. src/lib/src/Modules/AuditTrail/Lib/LogTable/DelegateAjaxHandler.php +0 -53
  100. src/lib/src/Modules/AuditTrail/Lib/LogTable/LoadRawTableData.php +0 -147
  101. src/lib/src/Modules/AuditTrail/ModCon.php +4 -4
  102. src/lib/src/Modules/AuditTrail/Strings.php +0 -9
  103. src/lib/src/Modules/Autoupdates/Insights/OverviewCards.php +0 -151
  104. src/lib/src/Modules/Base/AdminNotices.php +6 -17
  105. src/lib/src/Modules/Base/AdminPage.php +67 -23
  106. src/lib/src/Modules/Base/AjaxHandler.php +1 -55
  107. src/lib/src/Modules/Base/Config/LoadConfig.php +63 -25
  108. src/lib/src/Modules/Base/Config/ModConfigVO.php +18 -0
  109. src/lib/src/Modules/Base/Insights/OverviewCards.php +0 -59
  110. src/lib/src/Modules/Base/Lib/Request/FormParams.php +0 -2
  111. src/lib/src/Modules/Base/ModCon.php +112 -350
  112. src/lib/src/Modules/Base/Options.php +30 -37
  113. src/lib/src/Modules/Base/Options/HandleOptionsSaveRequest.php +122 -0
  114. src/lib/src/Modules/Base/Options/RenderOptionsForm.php +43 -0
  115. src/lib/src/Modules/Base/Options/WildCardOptions.php +2 -2
  116. src/lib/src/Modules/Base/Processor.php +6 -1
  117. src/lib/src/Modules/Base/Renderer.php +32 -0
  118. src/lib/src/Modules/Base/Strings.php +13 -19
  119. src/lib/src/Modules/Base/UI.php +27 -151
  120. src/lib/src/Modules/BaseShield/AdminPage.php +0 -11
  121. src/lib/src/Modules/BaseShield/AjaxHandler.php +2 -33
  122. src/lib/src/Modules/BaseShield/ModCon.php +33 -80
  123. src/lib/src/Modules/BaseShield/Options.php +9 -5
  124. src/lib/src/Modules/BaseShield/UI.php +11 -6
  125. src/lib/src/Modules/CommentsFilter/AdminNotices.php +6 -10
  126. src/lib/src/Modules/CommentsFilter/AjaxHandler.php +0 -25
  127. src/lib/src/Modules/CommentsFilter/Forms/Gasp.php +0 -117
  128. src/lib/src/Modules/CommentsFilter/Forms/GoogleRecaptcha.php +0 -40
  129. src/lib/src/Modules/CommentsFilter/Insights/OverviewCards.php +0 -48
  130. src/lib/src/Modules/CommentsFilter/ModCon.php +18 -64
  131. src/lib/src/Modules/CommentsFilter/Options.php +35 -42
  132. src/lib/src/Modules/CommentsFilter/Processor.php +9 -25
  133. src/lib/src/Modules/CommentsFilter/Scan/AntiBot.php +0 -24
  134. src/lib/src/Modules/CommentsFilter/Scan/Bot.php +0 -78
  135. src/lib/src/Modules/CommentsFilter/Scan/Human.php +6 -6
  136. src/lib/src/Modules/CommentsFilter/Scan/Scanner.php +51 -76
  137. src/lib/src/Modules/CommentsFilter/Strings.php +15 -89
  138. src/lib/src/Modules/CommentsFilter/Token/Create.php +0 -44
  139. src/lib/src/Modules/CommentsFilter/Upgrade.php +1 -1
  140. src/lib/src/Modules/Comms/ModCon.php +4 -0
  141. src/lib/src/Modules/Data/DB/UserMeta/Ops/Common.php +4 -0
  142. src/lib/src/Modules/Data/DB/UserMeta/Ops/Record.php +5 -6
  143. src/lib/src/Modules/Data/Lib/GeoIP/Lookup.php +3 -2
  144. src/lib/src/Modules/Data/Lib/UpgradeReqLogsTable.php +0 -119
  145. src/lib/src/Modules/Data/ModCon.php +0 -20
  146. src/lib/src/Modules/Events/Lib/EventsService.php +8 -7
  147. src/lib/src/Modules/Events/Lib/UI/BuildDataForStats.php +1 -1
  148. src/lib/src/Modules/Events/ModCon.php +0 -1
  149. src/lib/src/Modules/Events/Processor.php +1 -56
  150. src/lib/src/Modules/Firewall/Insights/OverviewCards.php +0 -56
  151. src/lib/src/Modules/Firewall/Lib/Scan/FirewallHandler.php +30 -34
  152. src/lib/src/Modules/Firewall/Lib/Scan/Handlers/SqlQueries.php +1 -1
  153. src/lib/src/Modules/Firewall/ModCon.php +17 -34
  154. src/lib/src/Modules/Firewall/Options.php +1 -1
  155. src/lib/src/Modules/Firewall/Processor.php +1 -25
  156. src/lib/src/Modules/Firewall/Rules/Build/BuildFirewallBase.php +189 -0
  157. src/lib/src/Modules/Firewall/Rules/Build/FirewallAggressive.php +11 -0
  158. src/lib/src/Modules/Firewall/Rules/Build/FirewallDirTraversal.php +11 -0
  159. src/lib/src/Modules/Firewall/Rules/Build/FirewallExeFileUploads.php +56 -0
  160. src/lib/src/Modules/Firewall/Rules/Build/FirewallFieldTruncation.php +11 -0
  161. src/lib/src/Modules/Firewall/Rules/Build/FirewallLeadingSchema.php +11 -0
  162. src/lib/src/Modules/Firewall/Rules/Build/FirewallPhpCode.php +11 -0
  163. src/lib/src/Modules/Firewall/Rules/Build/FirewallSqlQueries.php +11 -0
  164. src/lib/src/Modules/Firewall/Rules/Build/FirewallWordpressTerms.php +11 -0
  165. src/lib/src/Modules/Firewall/Strings.php +24 -13
  166. src/lib/src/Modules/HackGuard/DB/Scans/Ops/Common.php +0 -2
  167. src/lib/src/Modules/HackGuard/DB/Utility/Clean.php +55 -0
  168. src/lib/src/Modules/HackGuard/Insights/OverviewCards.php +0 -239
  169. src/lib/src/Modules/HackGuard/ModCon.php +8 -2
  170. src/lib/src/Modules/HackGuard/Options.php +2 -4
  171. src/lib/src/Modules/HackGuard/Render/ScanResults/SectionPluginThemesBase.php +0 -2
  172. src/lib/src/Modules/HackGuard/Render/ScanResults/SectionPlugins.php +23 -18
  173. src/lib/src/Modules/HackGuard/Render/ScanResults/SectionThemes.php +55 -48
  174. src/lib/src/Modules/HackGuard/Scan/Controller/Afs.php +0 -7
  175. src/lib/src/Modules/HackGuard/Scan/Controller/Apc.php +0 -7
  176. src/lib/src/Modules/HackGuard/Scan/Controller/Base.php +0 -7
  177. src/lib/src/Modules/HackGuard/Scan/Controller/Wpv.php +0 -7
  178. src/lib/src/Modules/HackGuard/Strings.php +2 -23
  179. src/lib/src/Modules/HackGuard/UI.php +15 -16
  180. src/lib/src/Modules/Headers/Insights/OverviewCards.php +0 -50
  181. src/lib/src/Modules/IPs/AdminNotices.php +1 -4
  182. src/lib/src/Modules/IPs/AjaxHandler.php +28 -7
  183. src/lib/src/Modules/IPs/BotTrack/Track404.php +0 -74
  184. src/lib/src/Modules/IPs/BotTrack/TrackFakeWebCrawler.php +0 -44
  185. src/lib/src/Modules/IPs/BotTrack/TrackInvalidScriptLoad.php +0 -58
  186. src/lib/src/Modules/IPs/BotTrack/TrackLinkCheese.php +1 -1
  187. src/lib/src/Modules/IPs/BotTrack/TrackXmlRpc.php +0 -17
  188. src/lib/src/Modules/IPs/Components/ImportIpsFromFile.php +4 -0
  189. src/lib/src/Modules/IPs/Components/ProcessOffense.php +4 -3
  190. src/lib/src/Modules/IPs/Components/QueryIpBlock.php +12 -12
  191. src/lib/src/Modules/IPs/Components/UnblockIpByFlag.php +19 -3
  192. src/lib/src/Modules/IPs/DB/BotSignal/Ops/Record.php +10 -0
  193. src/lib/src/Modules/IPs/Insights/OverviewCards.php +0 -9
  194. src/lib/src/Modules/IPs/Lib/AutoUnblock.php +62 -57
  195. src/lib/src/Modules/IPs/Lib/BlacklistHandler.php +28 -30
  196. src/lib/src/Modules/IPs/Lib/BlockRequest.php +8 -167
  197. src/lib/src/Modules/IPs/Lib/Bots/BotEventListener.php +2 -2
  198. src/lib/src/Modules/IPs/Lib/Bots/BotSignalsController.php +8 -21
  199. src/lib/src/Modules/IPs/Lib/Bots/BotSignalsRecord.php +31 -16
  200. src/lib/src/Modules/IPs/Lib/Bots/Calculator/BuildScores.php +2 -2
  201. src/lib/src/Modules/IPs/Lib/Bots/NotBot/NotBotHandler.php +5 -5
  202. src/lib/src/Modules/IPs/Lib/IpAnalyse/BuildDisplay.php +35 -36
  203. src/lib/src/Modules/IPs/Lib/IpAnalyse/FindAllPluginIps.php +1 -8
  204. src/lib/src/Modules/IPs/Lib/IsHighReputationIP.php +22 -0
  205. src/lib/src/Modules/IPs/Lib/OffenseTracker.php +17 -6
  206. src/lib/src/Modules/IPs/Lib/Ops/LookupIpOnList.php +2 -4
  207. src/lib/src/Modules/IPs/Lib/ProcessOffenses.php +1 -27
  208. src/lib/src/Modules/IPs/ModCon.php +45 -30
  209. src/lib/src/Modules/IPs/Options.php +14 -1
  210. src/lib/src/Modules/IPs/Processor.php +4 -19
  211. src/lib/src/Modules/IPs/Rules/Build/BotTrack404.php +86 -0
  212. src/lib/src/Modules/IPs/Rules/Build/BotTrackFakeWebCrawler.php +70 -0
  213. src/lib/src/Modules/IPs/Rules/Build/BotTrackInvalidScript.php +79 -0
  214. src/lib/src/Modules/IPs/Rules/Build/BotTrackXmlrpc.php +66 -0
  215. src/lib/src/Modules/IPs/Rules/Build/IpBlocked.php +51 -0
  216. src/lib/src/Modules/IPs/Rules/Build/IpWhitelisted.php +42 -0
  217. src/lib/src/Modules/IPs/Rules/Build/IsPathWhitelisted.php +55 -0
  218. src/lib/src/Modules/IPs/Strings.php +16 -14
  219. src/lib/src/Modules/Insights/AdminPage.php +0 -5
  220. src/lib/src/Modules/Insights/AjaxHandler.php +20 -1
  221. src/lib/src/Modules/Insights/Lib/MeterAnalysis/Components.php +1191 -0
  222. src/lib/src/Modules/Insights/Lib/MeterAnalysis/Handler.php +102 -0
  223. src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterAll.php +30 -0
  224. src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterAssets.php +36 -0
  225. src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterBase.php +160 -0
  226. src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterFirewall.php +49 -0
  227. src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterIntegrity.php +49 -0
  228. src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterIpBlocking.php +45 -0
  229. src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterLockdown.php +38 -0
  230. src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterLoginProtection.php +46 -0
  231. src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterScans.php +50 -0
  232. src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterSpam.php +42 -0
  233. src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterUsers.php +51 -0
  234. src/lib/src/Modules/Insights/Lib/{SideMenuBuilder.php → NavMenuBuilder.php} +38 -20
  235. src/lib/src/Modules/Insights/Lib/OverviewCards.php +0 -91
  236. src/lib/src/Modules/Insights/Lib/Requests/DynamicPageLoader.php +11 -6
  237. src/lib/src/Modules/Insights/Lib/SummaryCards.php +0 -182
  238. src/lib/src/Modules/Insights/ModCon.php +29 -29
  239. src/lib/src/Modules/Insights/Strings.php +0 -1
  240. src/lib/src/Modules/Insights/UI.php +28 -30
  241. src/lib/src/Modules/Integrations/Lib/Bots/Common/BaseBotDetectionController.php +3 -5
  242. src/lib/src/Modules/Integrations/Lib/Bots/Common/BaseHandler.php +23 -8
  243. src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/Base.php +12 -3
  244. src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/ContactForm7.php +1 -1
  245. src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/ElementorPro.php +1 -1
  246. src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/FluentForms.php +1 -1
  247. src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/FormidableForms.php +1 -1
  248. src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/Forminator.php +1 -1
  249. src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/GravityForms.php +1 -1
  250. src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/Groundhogg.php +1 -1
  251. src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/Helpers/NinjaForms_ShieldSpamAction.php +1 -1
  252. src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/KaliForms.php +1 -1
  253. src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/SuperForms.php +1 -1
  254. src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/SupportCandy.php +1 -1
  255. src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/WPForms.php +1 -1
  256. src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/WpForo.php +1 -1
  257. src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/Base.php +16 -18
  258. src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/Buddyboss.php +1 -1
  259. src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/Buddypress.php +1 -1
  260. src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/EasyDigitalDownloads.php +1 -1
  261. src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/LearnPress.php +2 -2
  262. src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/LifterLMS.php +2 -2
  263. src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/MemberPress.php +3 -3
  264. src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/PaidMemberSubscriptions.php +1 -1
  265. src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/ProfileBuilder.php +1 -1
  266. src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/UltimateMember.php +3 -3
  267. src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/WPMembers.php +2 -2
  268. src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/WooCommerce.php +3 -3
  269. src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/WordPress.php +3 -3
  270. src/lib/src/Modules/Integrations/Lib/Bots/UserForms/UserFormsController.php +3 -6
  271. src/lib/src/Modules/Integrations/Processor.php +1 -1
  272. src/lib/src/Modules/License/AjaxHandler.php +1 -2
  273. src/lib/src/Modules/License/ModCon.php +0 -1
  274. src/lib/src/Modules/License/UI.php +124 -19
  275. src/lib/src/Modules/Lockdown/Insights/OverviewCards.php +0 -74
  276. src/lib/src/Modules/Lockdown/ModCon.php +13 -1
  277. src/lib/src/Modules/Lockdown/Options.php +5 -1
  278. src/lib/src/Modules/Lockdown/Processor.php +0 -57
  279. src/lib/src/Modules/Lockdown/Rules/Build/DisableFileEditing.php +42 -0
  280. src/lib/src/Modules/Lockdown/Rules/Build/DisableXmlrpc.php +46 -0
  281. src/lib/src/Modules/Lockdown/Rules/Build/ForceSslAdmin.php +30 -0
  282. src/lib/src/Modules/Lockdown/Rules/Build/HideGeneratorTag.php +46 -0
  283. src/lib/src/Modules/Lockdown/Rules/Build/IsRequestAuthorDiscovery.php +63 -0
  284. src/lib/src/Modules/Lockdown/Strings.php +8 -2
  285. src/lib/src/Modules/LoginGuard/AdminNotices.php +1 -1
  286. src/lib/src/Modules/LoginGuard/AjaxHandler.php +3 -3
  287. src/lib/src/Modules/LoginGuard/Insights/OverviewCards.php +0 -72
  288. src/lib/src/Modules/LoginGuard/Lib/AntiBot/AntibotSetup.php +1 -3
  289. src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/AntiBot.php +1 -1
  290. src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/BaseProtectionProvider.php +2 -2
  291. src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/CoolDown.php +7 -7
  292. src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/GaspJs.php +2 -7
  293. src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/GoogleRecaptcha.php +1 -1
  294. src/lib/src/Modules/LoginGuard/Lib/CooldownFlagFile.php +4 -11
  295. src/lib/src/Modules/LoginGuard/Lib/Rename/RenameLogin.php +4 -8
  296. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/LoginIntentRequestCapture.php +1 -1
  297. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/MfaController.php +2 -3
  298. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/GoogleAuth.php +2 -2
  299. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/Sms.php +1 -1
  300. src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Render/RenderLoginIntentPage.php +1 -1
  301. src/lib/src/Modules/LoginGuard/ModCon.php +36 -61
  302. src/lib/src/Modules/LoginGuard/Options.php +0 -1
  303. src/lib/src/Modules/LoginGuard/Processor.php +1 -2
  304. src/lib/src/Modules/LoginGuard/Strings.php +13 -10
  305. src/lib/src/Modules/Plugin/AdminNotices.php +34 -8
  306. src/lib/src/Modules/Plugin/AjaxHandler.php +38 -20
  307. src/lib/src/Modules/Plugin/Components/DashboardWidget.php +181 -0
  308. src/lib/src/Modules/Plugin/Components/PluginBadge.php +25 -26
  309. src/lib/src/Modules/Plugin/Components/SiteGroundPluginCompatibility.php +5 -11
  310. src/lib/src/Modules/Plugin/Components/TestForCloudflareAPO.php +5 -5
  311. src/lib/src/Modules/Plugin/Debug.php +19 -1
  312. src/lib/src/Modules/Plugin/Insights/DashboardCards.php +0 -433
  313. src/lib/src/Modules/Plugin/Insights/OverviewCards.php +0 -181
  314. src/lib/src/Modules/Plugin/Lib/AllowBetaUpgrades.php +59 -0
  315. src/lib/src/Modules/Plugin/Lib/Debug/Collate.php +0 -5
  316. src/lib/src/Modules/Plugin/Lib/ImportExport/Export.php +33 -40
  317. src/lib/src/Modules/Plugin/Lib/ImportExport/Import.php +34 -41
  318. src/lib/src/Modules/Plugin/Lib/ImportExport/ImportExportController.php +59 -13
  319. src/lib/src/Modules/Plugin/Lib/ImportExport/NotifyWhitelist.php +13 -18
  320. src/lib/src/Modules/Plugin/Lib/PluginTelemetry.php +0 -1
  321. src/lib/src/Modules/Plugin/Lib/TourManager.php +0 -1
  322. src/lib/src/Modules/Plugin/ModCon.php +106 -155
  323. src/lib/src/Modules/Plugin/Options.php +16 -24
  324. src/lib/src/Modules/Plugin/Processor.php +9 -23
  325. src/lib/src/Modules/Plugin/Rules/Build/IsPublicWebRequest.php +41 -0
  326. src/lib/src/Modules/Plugin/Rules/Build/IsServerLoopback.php +37 -0
  327. src/lib/src/Modules/Plugin/Rules/Build/IsTrustedBot.php +54 -0
  328. src/lib/src/Modules/Plugin/Rules/Build/RequestBypassesAllRestrictions.php +59 -0
  329. src/lib/src/Modules/Plugin/Rules/Build/RequestStatusBase.php +12 -0
  330. src/lib/src/Modules/Plugin/Rules/Build/RequestStatusIsAdmin.php +28 -0
  331. src/lib/src/Modules/Plugin/Rules/Build/RequestStatusIsAjax.php +28 -0
  332. src/lib/src/Modules/Plugin/Rules/Build/RequestStatusIsWpCli.php +28 -0
  333. src/lib/src/Modules/Plugin/Rules/Build/RequestStatusIsXmlRpc.php +28 -0
  334. src/lib/src/Modules/Plugin/Strings.php +6 -9
  335. src/lib/src/Modules/Plugin/UI.php +2 -22
  336. src/lib/src/Modules/Plugin/Upgrade.php +3 -9
  337. src/lib/src/Modules/Plugin/WpCli/Export.php +0 -2
  338. src/lib/src/Modules/Plugin/WpCli/ForceOff.php +1 -1
  339. src/lib/src/Modules/Plugin/WpCli/Import.php +2 -4
  340. src/lib/src/Modules/PluginControllerConsumer.php +3 -3
  341. src/lib/src/Modules/Reporting/Insights/OverviewCards.php +0 -9
  342. src/lib/src/Modules/Reporting/UI.php +0 -6
  343. src/lib/src/Modules/SecurityAdmin/AdminNotices.php +4 -4
  344. src/lib/src/Modules/SecurityAdmin/AjaxHandler.php +1 -1
  345. src/lib/src/Modules/SecurityAdmin/Insights/OverviewCards.php +0 -66
  346. src/lib/src/Modules/SecurityAdmin/Lib/SecurityAdmin/Ops/ToggleSecAdminStatus.php +10 -30
  347. src/lib/src/Modules/SecurityAdmin/Lib/SecurityAdmin/Restrictions/WpOptions.php +1 -2
  348. src/lib/src/Modules/SecurityAdmin/Lib/SecurityAdmin/SecurityAdminController.php +22 -28
  349. src/lib/src/Modules/SecurityAdmin/Lib/SecurityAdmin/VerifySecurityAdminList.php +1 -1
  350. src/lib/src/Modules/SecurityAdmin/ModCon.php +7 -2
  351. src/lib/src/Modules/SecurityAdmin/Options.php +8 -2
  352. src/lib/src/Modules/SecurityAdmin/Processor.php +2 -2
  353. src/lib/src/Modules/SecurityAdmin/Rules/Build/IsSecurityAdmin.php +53 -0
  354. src/lib/src/Modules/SecurityAdmin/UI.php +0 -6
  355. src/lib/src/Modules/Sessions/Lib/Ops/Terminate.php +0 -44
  356. src/lib/src/Modules/Sessions/Lib/SessionController.php +157 -67
  357. src/lib/src/Modules/Sessions/Lib/SessionVO.php +20 -0
  358. src/lib/src/Modules/Sessions/ModCon.php +4 -24
  359. src/lib/src/Modules/Sessions/Processor.php +13 -67
  360. src/lib/src/Modules/Traffic/Insights/OverviewCards.php +0 -9
  361. src/lib/src/Modules/Traffic/Lib/Limit/Limiter.php +3 -0
  362. src/lib/src/Modules/Traffic/Lib/Limit/TestIpLimit.php +2 -2
  363. src/lib/src/Modules/Traffic/Lib/LogHandlers/LocalDbWriter.php +1 -1
  364. src/lib/src/Modules/Traffic/Lib/RequestLogger.php +6 -30
  365. src/lib/src/Modules/Traffic/Lib/TrafficTable/DelegateAjaxHandler.php +0 -46
  366. src/lib/src/Modules/Traffic/Lib/TrafficTable/LoadRawTableData.php +0 -233
  367. src/lib/src/Modules/Traffic/ModCon.php +19 -2
  368. src/lib/src/Modules/Traffic/Processor.php +0 -4
  369. src/lib/src/Modules/Traffic/Rules/Build/IsRateLimitExceeded.php +71 -0
  370. src/lib/src/Modules/Traffic/Strings.php +7 -7
  371. src/lib/src/Modules/UserManagement/AjaxHandler.php +28 -37
  372. src/lib/src/Modules/UserManagement/Insights/OverviewCards.php +0 -85
  373. src/lib/src/Modules/UserManagement/Lib/CleanExpired.php +0 -44
  374. src/lib/src/Modules/UserManagement/Lib/Registration/EmailValidate.php +1 -1
  375. src/lib/src/Modules/UserManagement/Lib/RetrieveActive.php +0 -64
  376. src/lib/src/Modules/UserManagement/Lib/Session/FindSessions.php +69 -0
  377. src/lib/src/Modules/UserManagement/Lib/Session/UserSessionHandler.php +5 -33
  378. src/lib/src/Modules/UserManagement/Lib/Suspend/UserSuspendController.php +4 -9
  379. src/lib/src/Modules/UserManagement/ModCon.php +2 -2
  380. src/lib/src/Modules/UserManagement/Options.php +9 -11
  381. src/lib/src/Modules/UserManagement/Processor.php +20 -23
  382. src/lib/src/Modules/UserManagement/Strings.php +28 -21
  383. src/lib/src/Modules/UserManagement/UI.php +32 -9
  384. src/lib/src/Modules/UserManagement/WpCli.php +1 -3
  385. src/lib/src/Modules/UserManagement/WpCli/SessionTerminate.php +3 -45
  386. src/lib/src/Render/LocateTemplateDirs.php +5 -5
  387. src/lib/src/Request/ThisRequest.php +72 -0
  388. src/lib/src/Rules/Build/BuildRuleBase.php +59 -0
  389. src/lib/src/Rules/Build/BuildRuleCoreShieldBase.php +28 -0
  390. src/lib/src/Rules/Build/Builder.php +35 -0
  391. src/lib/src/Rules/Build/SortRulesByDependencies.php +213 -0
  392. src/lib/src/Rules/Conditions/Base.php +121 -0
  393. src/lib/src/Rules/Conditions/IsForceOff.php +34 -0
  394. src/lib/src/Rules/Conditions/IsIpBlacklisted.php +25 -0
  395. src/lib/src/Rules/Conditions/IsIpBlocked.php +28 -0
  396. src/lib/src/Rules/Conditions/IsIpHighReputation.php +20 -0
  397. src/lib/src/Rules/Conditions/IsIpValidPublic.php +18 -0
  398. src/lib/src/Rules/Conditions/IsIpWhitelisted.php +26 -0
  399. src/lib/src/Rules/Conditions/IsLoggedInNormal.php +19 -0
  400. src/lib/src/Rules/Conditions/IsNotLoggedInNormal.php +19 -0
  401. src/lib/src/Rules/Conditions/IsRateLimitExceeded.php +45 -0
  402. src/lib/src/Rules/Conditions/IsRequestToInvalidPlugin.php +27 -0
  403. src/lib/src/Rules/Conditions/IsRequestToInvalidTheme.php +27 -0
  404. src/lib/src/Rules/Conditions/IsRequestToPluginAsset.php +28 -0
  405. src/lib/src/Rules/Conditions/IsRequestToThemeAsset.php +28 -0
  406. src/lib/src/Rules/Conditions/IsRequestToValidPluginAsset.php +37 -0
  407. src/lib/src/Rules/Conditions/IsRequestToValidThemeAsset.php +37 -0
  408. src/lib/src/Rules/Conditions/IsSecurityAdmin.php +18 -0
  409. src/lib/src/Rules/Conditions/IsUserAdminNormal.php +21 -0
  410. src/lib/src/Rules/Conditions/MatchRequestIp.php +28 -0
  411. src/lib/src/Rules/Conditions/MatchRequestIpIdentity.php +52 -0
  412. src/lib/src/Rules/Conditions/MatchRequestParam.php +94 -0
  413. src/lib/src/Rules/Conditions/MatchRequestParamFileUploads.php +17 -0
  414. src/lib/src/Rules/Conditions/MatchRequestParamPost.php +14 -0
  415. src/lib/src/Rules/Conditions/MatchRequestParamQuery.php +14 -0
  416. src/lib/src/Rules/Conditions/MatchRequestPath.php +34 -0
  417. src/lib/src/Rules/Conditions/MatchRequestScriptName.php +42 -0
  418. src/lib/src/Rules/Conditions/MatchRequestStatusCode.php +37 -0
  419. src/lib/src/Rules/Conditions/MatchRequestUseragent.php +48 -0
  420. src/lib/src/Rules/Conditions/NotMatchRequestPath.php +14 -0
  421. src/lib/src/Rules/Conditions/RequestHasParameters.php +15 -0
  422. src/lib/src/Rules/Conditions/RequestParamIs.php +48 -0
  423. src/lib/src/Rules/Conditions/RequestPostParamIs.php +17 -0
  424. src/lib/src/Rules/Conditions/RequestQueryParamIs.php +17 -0
  425. src/lib/src/Rules/Conditions/Traits/RequestIP.php +27 -0
  426. src/lib/src/Rules/Conditions/Traits/RequestPath.php +22 -0
  427. src/lib/src/Rules/Conditions/Traits/RequestScriptName.php +34 -0
  428. src/lib/src/Rules/Conditions/Traits/UserAgent.php +26 -0
  429. src/lib/src/Rules/Conditions/WpIsAdmin.php +17 -0
  430. src/lib/src/Rules/Conditions/WpIsAjax.php +18 -0
  431. src/lib/src/Rules/Conditions/WpIsWpcli.php +18 -0
  432. src/lib/src/Rules/Conditions/WpIsXmlrpc.php +18 -0
  433. src/lib/src/Rules/Exceptions/AttemptToAccessNonExistingRuleException.php +7 -0
  434. src/lib/src/Rules/Exceptions/IpsToMatchUnavailableException.php +7 -0
  435. src/lib/src/Rules/Exceptions/MatchIpIdsUnavailableException.php +7 -0
  436. src/lib/src/Rules/Exceptions/MatchUseragentsUnavailableException.php +7 -0
  437. src/lib/src/Rules/Exceptions/MutuallyDependentRulesException.php +7 -0
  438. src/lib/src/Rules/Exceptions/NoConditionActionDefinedException.php +7 -0
  439. src/lib/src/Rules/Exceptions/NoResponseActionDefinedException.php +7 -0
  440. src/lib/src/Rules/Exceptions/NoStatusProvidedToCheckException.php +7 -0
  441. src/lib/src/Rules/Exceptions/NoSuchConditionHandlerException.php +7 -0
  442. src/lib/src/Rules/Exceptions/NoSuchResponseHandlerException.php +7 -0
  443. src/lib/src/Rules/Exceptions/PathsToMatchUnavailableException.php +7 -0
  444. src/lib/src/Rules/Exceptions/RequestIpUnavailableException.php +7 -0
  445. src/lib/src/Rules/Exceptions/RequestScriptNameUnavailableException.php +7 -0
  446. src/lib/src/Rules/Exceptions/RequestUseragentUnavailableException.php +7 -0
  447. src/lib/src/Rules/Exceptions/RuleNotYetRunException.php +7 -0
  448. src/lib/src/Rules/Exceptions/ScriptNamesToMatchUnavailableException.php +7 -0
  449. src/lib/src/Rules/Exceptions/UnsupportedStatusException.php +7 -0
  450. src/lib/src/Rules/Processors/BaseProcessor.php +24 -0
  451. src/lib/src/Rules/Processors/ConditionsProcessor.php +101 -0
  452. src/lib/src/Rules/Processors/ResponseProcessor.php +49 -0
  453. src/lib/src/Rules/Render/RenderBase.php +15 -0
  454. src/lib/src/Rules/Render/RenderSummary.php +48 -0
  455. src/lib/src/Rules/Responses/Base.php +76 -0
  456. src/lib/src/Rules/Responses/BlockAuthorFishing.php +18 -0
  457. src/lib/src/Rules/Responses/DisableFileEditing.php +33 -0
  458. src/lib/src/Rules/Responses/DisableXmlrpc.php +27 -0
  459. src/lib/src/Rules/Responses/EventFire.php +40 -0
  460. src/lib/src/Rules/Responses/FirewallBlock.php +102 -0
  461. src/lib/src/Rules/Responses/ForceSslAdmin.php +16 -0
  462. src/lib/src/Rules/Responses/HideGeneratorTag.php +13 -0
  463. src/lib/src/Rules/Responses/IsBotProbe404.php +12 -0
  464. src/lib/src/Rules/Responses/SetIpBlocked.php +22 -0
  465. src/lib/src/Rules/Responses/SetIpWhitelisted.php +13 -0
  466. src/lib/src/Rules/Responses/SetIsTrustedBot.php +13 -0
  467. src/lib/src/Rules/Responses/SetRequestBypassesAllRestrictions.php +13 -0
  468. src/lib/src/Rules/Responses/SetSecurityAdmin.php +13 -0
  469. src/lib/src/Rules/Responses/TrafficRateLimitExceeded.php +13 -0
  470. src/lib/src/Rules/RuleVO.php +60 -0
  471. src/lib/src/Rules/RulesController.php +225 -0
  472. src/lib/src/Rules/RulesStorageHandler.php +90 -0
  473. src/lib/src/Rules/Utility/RulesControllerConsumer.php +22 -0
  474. src/lib/src/Rules/WPHooksOrder.php +25 -0
  475. src/lib/src/Rules/rules.json +0 -0
  476. src/lib/src/ShieldNetApi/Common/BaseApi.php +16 -10
  477. src/lib/src/ShieldNetApi/Common/BaseShieldNetApi.php +1 -1
  478. src/lib/src/Tables/Build/BaseBuild.php +9 -4
  479. src/lib/src/Tables/Build/Ip.php +10 -10
  480. src/lib/src/Tables/Build/Sessions.php +110 -32
  481. src/lib/src/Tables/DataTables/LoadData/BaseBuildTableData.php +4 -1
  482. src/lib/src/Tables/DataTables/LoadData/BaseLoadTableData.php +0 -132
  483. src/lib/src/Tables/Render/{WpListTable/Base.php → WpListTa} +0 -0
cl.json CHANGED
@@ -1,4 +1,83 @@
1
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  "14.1": {
3
  "version": "14.1",
4
  "released_at": 1647269718,
@@ -98,20 +177,29 @@
98
  ]
99
  },
100
  {
 
 
101
  "title": "Deprecated: Options For CAPTCHA and GASP Bot Checking On WordPress Comments",
102
  "description": [
103
  "The options to use CAPTCHA and/or GASP Bot Checking for WordPress Comment SPAM has been deprecated.",
104
  "These options are replaced with the AntiBot Detection Engine and will be completely removed in a future release."
105
- ],
106
- "type": "changed",
107
- "pro_only": false
108
  },
109
  {
110
- "type": "fixed",
111
- "pro_only": false,
112
- "title": "Firewall didn't always scan all parameters in some cases",
113
- "description": [
114
- ]
 
 
 
 
 
 
 
 
 
115
  }
116
  ],
117
  "patches": [
1
  {
2
+ "15.0": {
3
+ "version": "15.0",
4
+ "released_at": 1649932802,
5
+ "hrefs": {
6
+ "release": "https://shsec.io/shieldrelease150",
7
+ "upgrade": "https://shsec.io/shieldupgradeguide150"
8
+ },
9
+ "title": "Rules Engine",
10
+ "description": [
11
+ ],
12
+ "items": [
13
+ {
14
+ "type": "new",
15
+ "pro_only": true,
16
+ "title": "Rules Engine",
17
+ "description": [
18
+ "Massive performance and processing optimisations with a brand new core Shield Rules Engine.",
19
+ "All requests are now processed using a unique and customisable (future releases) set of rules."
20
+ ]
21
+ },
22
+ {
23
+ "type": "new",
24
+ "title": "Brand New Shield Block Pages",
25
+ "description": [
26
+ "We now offer more user-friendly block pages to the visitor for all scenarios: firewall, IP block, username fishing."
27
+ ]
28
+ },
29
+ {
30
+ "type": "new",
31
+ "title": "All-New Dashboard Overview",
32
+ "description": [
33
+ "The Shield Dashboard Overview provides detailed and actionable insights into your WordPress security and how to improve it."
34
+ ]
35
+ },
36
+ {
37
+ "type": "new",
38
+ "title": "Removed: Legacy Comment SPAM Detection",
39
+ "description": [
40
+ "We've completely removed the older, less reliable comment spam detection using Javascript and CAPTCHAs.",
41
+ "Please use the newer AntiBot Detection Engine."
42
+ ]
43
+ },
44
+ {
45
+ "type": "improved",
46
+ "title": "Author Discovery/Fishing",
47
+ "description": [
48
+ "This feature is now a Bot Signal which is logged in the Audit Trail and triggers offenses."
49
+ ]
50
+ },
51
+ {
52
+ "type": "improved",
53
+ "title": "Massive Performance Improvements",
54
+ "description": [
55
+ "Shield has undergone major enhancements and performance improvements."
56
+ ],
57
+ "list": [
58
+ "Reduced duplicate and unnecessary DB requests.",
59
+ "Consolidated and removed many excess Transients (fewer DB requests).",
60
+ "Optimised several DB queries."
61
+ ]
62
+ },
63
+ {
64
+ "type": "improved",
65
+ "title": "New Filters: Adjust scanner notices about plugin/theme update/active status",
66
+ "description": [
67
+ "You can now use filters to adjust whether Shield warns about inactive plugins/themes or those with updates."
68
+ ]
69
+ },
70
+ {
71
+ "type": "improved",
72
+ "title": "Option Removed: Auto-Filter Scan Results",
73
+ "description": [
74
+ "Shield will now filter unnecessary scan results automatically. This can be adjusted using a WP filter."
75
+ ]
76
+ }
77
+ ],
78
+ "patches": [
79
+ ]
80
+ },
81
  "14.1": {
82
  "version": "14.1",
83
  "released_at": 1647269718,
177
  ]
178
  },
179
  {
180
+ "type": "changed",
181
+ "pro_only": false,
182
  "title": "Deprecated: Options For CAPTCHA and GASP Bot Checking On WordPress Comments",
183
  "description": [
184
  "The options to use CAPTCHA and/or GASP Bot Checking for WordPress Comment SPAM has been deprecated.",
185
  "These options are replaced with the AntiBot Detection Engine and will be completely removed in a future release."
186
+ ]
 
 
187
  },
188
  {
189
+ "type": "improved",
190
+ "title": "Display of Shield's Admin Menu Bar items can be controlled using a plugin configuration option."
191
+ },
192
+ {
193
+ "type": "fixed",
194
+ "title": "Shield's REST API supports non-permalinks style requests (?rest_route=), regardless of permalinks configuration."
195
+ },
196
+ {
197
+ "type": "fixed",
198
+ "title": "Fix for non-URL-encoding of password reset URL parameters when using Rename Login feature."
199
+ },
200
+ {
201
+ "type": "fixed",
202
+ "title": "Traffic Request Log wasn't correctly indicating a request was an offense in the log viewer."
203
  }
204
  ],
205
  "patches": [
config/admin_access_restriction.json CHANGED
@@ -3,6 +3,8 @@
3
  "properties": {
4
  "slug": "admin_access_restriction",
5
  "name": "Security Admin",
 
 
6
  "sidebar_name": "Security Admin",
7
  "show_module_menu_item": false,
8
  "show_module_options": true,
@@ -16,6 +18,9 @@
16
  "run_if_wpcli": false,
17
  "order": 20
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true,
21
  "cmd_base": "secadmin"
@@ -173,9 +178,9 @@
173
  "link_info": "https://shsec.io/a0",
174
  "link_blog": "https://shsec.io/wpsf32",
175
  "beacon_id": 214,
176
- "name": "Options",
177
- "summary": "Restrict Access To Key WordPress Posts And Pages Actions",
178
- "description": "Careful: This will restrict access to page/post creation, editing and deletion. Note: Selecting 'Edit' will also restrict all other options."
179
  },
180
  {
181
  "key": "admin_access_restrict_admin_users",
3
  "properties": {
4
  "slug": "admin_access_restriction",
5
  "name": "Security Admin",
6
+ "namespace": "SecurityAdmin",
7
+ "load_priority": 11,
8
  "sidebar_name": "Security Admin",
9
  "show_module_menu_item": false,
10
  "show_module_options": true,
18
  "run_if_wpcli": false,
19
  "order": 20
20
  },
21
+ "menus": {
22
+ "config_menu_priority": 15
23
+ },
24
  "wpcli": {
25
  "enabled": true,
26
  "cmd_base": "secadmin"
178
  "link_info": "https://shsec.io/a0",
179
  "link_blog": "https://shsec.io/wpsf32",
180
  "beacon_id": 214,
181
+ "name": "WordPress Options",
182
+ "summary": "Restrict Access To Certain WordPress Admin Options",
183
+ "description": "Careful: This will restrict the ability of WordPress administrators from changing key WordPress settings."
184
  },
185
  {
186
  "key": "admin_access_restrict_admin_users",
config/audit_trail.json CHANGED
@@ -3,6 +3,8 @@
3
  "properties": {
4
  "slug": "audit_trail",
5
  "name": "Audit Trail",
 
 
6
  "sidebar_name": "Audit Trail",
7
  "show_module_menu_item": false,
8
  "show_module_options": true,
@@ -16,6 +18,9 @@
16
  "run_if_wpcli": true,
17
  "order": 110
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
@@ -35,14 +40,6 @@
35
  }
36
  ],
37
  "admin_notices": {
38
- "new-audit-trail": {
39
- "id": "new-audit-trail",
40
- "schedule": "conditions",
41
- "valid_admin": true,
42
- "plugin_page_only": true,
43
- "can_dismiss": true,
44
- "type": "info"
45
- }
46
  },
47
  "sections": [
48
  {
3
  "properties": {
4
  "slug": "audit_trail",
5
  "name": "Audit Trail",
6
+ "load_priority": 11,
7
+ "menu_priority": 40,
8
  "sidebar_name": "Audit Trail",
9
  "show_module_menu_item": false,
10
  "show_module_options": true,
18
  "run_if_wpcli": true,
19
  "order": 110
20
  },
21
+ "menus": {
22
+ "config_menu_priority": 35
23
+ },
24
  "wpcli": {
25
  "enabled": true
26
  },
40
  }
41
  ],
42
  "admin_notices": {
 
 
 
 
 
 
 
 
43
  },
44
  "sections": [
45
  {
config/autoupdates.json CHANGED
@@ -3,6 +3,7 @@
3
  "properties": {
4
  "slug": "autoupdates",
5
  "name": "Automatic Updates",
 
6
  "sidebar_name": "Auto Updates",
7
  "show_module_menu_item": false,
8
  "show_module_options": true,
@@ -16,6 +17,9 @@
16
  "run_if_wpcli": true,
17
  "order": 60
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
3
  "properties": {
4
  "slug": "autoupdates",
5
  "name": "Automatic Updates",
6
+ "load_priority": 100,
7
  "sidebar_name": "Auto Updates",
8
  "show_module_menu_item": false,
9
  "show_module_options": true,
17
  "run_if_wpcli": true,
18
  "order": 60
19
  },
20
+ "menus": {
21
+ "config_menu_priority": 55
22
+ },
23
  "wpcli": {
24
  "enabled": true
25
  },
config/comments_filter.json CHANGED
@@ -2,11 +2,12 @@
2
  "slug": "comments_filter",
3
  "properties": {
4
  "slug": "comments_filter",
 
 
5
  "name": "Comments SPAM",
6
  "sidebar_name": "SPAM",
7
  "show_module_menu_item": false,
8
  "show_module_options": true,
9
- "storage_key": "commentsfilter",
10
  "tagline": "Block comment SPAM and retain your privacy",
11
  "show_central": true,
12
  "access_restricted": true,
@@ -16,6 +17,9 @@
16
  "run_if_wpcli": false,
17
  "order": 50
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
@@ -41,7 +45,7 @@
41
  },
42
  {
43
  "slug": "section_human_spam_filter",
44
- "title": "Human Comment SPAM Protection Filter",
45
  "title_short": "Human SPAM",
46
  "beacon_id": 262,
47
  "summary": [
@@ -79,14 +83,6 @@
79
  "Recommendation - Keep the Comments Filter feature turned on."
80
  ]
81
  },
82
- {
83
- "slug": "section_deprecated",
84
- "title": "Deprecated Options",
85
- "title_short": "Deprecated Options",
86
- "summary": [
87
- "These options will be removed in the future. Please use the ADE option under Bot SPAM section."
88
- ]
89
- },
90
  {
91
  "slug": "section_non_ui",
92
  "hidden": true
@@ -180,52 +176,6 @@
180
  "summary": "How To Categorise Comments When Identified To Be SPAM",
181
  "description": "When a comment is detected as being SPAM from an automatic bot, the comment will be categorised based on this setting."
182
  },
183
- {
184
- "key": "google_recaptcha_style_comments",
185
- "section": "section_deprecated",
186
- "default": "disabled",
187
- "type": "select",
188
- "value_options": [
189
- {
190
- "value_key": "disabled",
191
- "text": "Disabled"
192
- },
193
- {
194
- "value_key": "default",
195
- "text": "Default Style"
196
- },
197
- {
198
- "value_key": "light",
199
- "text": "Light Theme"
200
- },
201
- {
202
- "value_key": "dark",
203
- "text": "Dark Theme"
204
- },
205
- {
206
- "value_key": "invisible",
207
- "text": "Invisible"
208
- }
209
- ],
210
- "link_info": "https://shsec.io/e4",
211
- "link_blog": "",
212
- "beacon_id": 269,
213
- "name": "CAPTCHA",
214
- "summary": "Enable CAPTCHA To Protect Against SPAM Comments",
215
- "description": "You can choose the CAPTCHA display format that best suits your site, including the newer Invisible CAPTCHA."
216
- },
217
- {
218
- "key": "enable_comments_gasp_protection",
219
- "section": "section_deprecated",
220
- "default": "N",
221
- "type": "checkbox",
222
- "link_info": "https://shsec.io/3n",
223
- "link_blog": "https://shsec.io/2n",
224
- "beacon_id": 401,
225
- "name": "GASP Protection",
226
- "summary": "Block Bot Comment SPAM",
227
- "description": "Taking the lead from the original GASP plugin for WordPress, we have extended it to include advanced spam-bot protection."
228
- },
229
  {
230
  "key": "enable_comments_human_spam_filter",
231
  "section": "section_human_spam_filter",
@@ -265,65 +215,6 @@
265
  "summary": "How To Categorise Comments When Identified To Be SPAM'",
266
  "description": "When a comment is detected as being SPAM from a human commenter, the comment will be categorised based on this setting."
267
  },
268
- {
269
- "key": "custom_message_checkbox",
270
- "section": "section_deprecated",
271
- "sensitive": true,
272
- "default": "default",
273
- "type": "text",
274
- "link_info": "https://shsec.io/3p",
275
- "link_blog": "",
276
- "beacon_id": 403,
277
- "name": "Custom Checkbox Message",
278
- "summary": "If you want a custom checkbox message, please provide this here",
279
- "description": "You can customise the message beside the checkbox."
280
- },
281
- {
282
- "key": "custom_message_alert",
283
- "section": "section_deprecated",
284
- "sensitive": true,
285
- "default": "default",
286
- "type": "text",
287
- "link_info": "https://shsec.io/3p",
288
- "link_blog": "",
289
- "beacon_id": 403,
290
- "name": "Custom Alert Message",
291
- "summary": "If you want a custom alert message, please provide this here",
292
- "description": "This alert message is displayed when a visitor attempts to submit a comment without checking the box."
293
- },
294
- {
295
- "key": "custom_message_comment_wait",
296
- "section": "section_deprecated",
297
- "sensitive": true,
298
- "default": "default",
299
- "type": "text",
300
- "link_info": "https://shsec.io/3p",
301
- "link_blog": "",
302
- "beacon_id": 403,
303
- "name": "Custom Wait Message",
304
- "summary": "If you want a custom submit-button wait message, please provide this here.",
305
- "description": "Where you see the '%s' this will be the number of seconds. You must ensure you include 1, and only 1, of these."
306
- },
307
- {
308
- "key": "custom_message_comment_reload",
309
- "section": "section_deprecated",
310
- "sensitive": true,
311
- "default": "default",
312
- "type": "text",
313
- "link_info": "https://shsec.io/3p",
314
- "link_blog": "",
315
- "beacon_id": 403,
316
- "name": "Custom Reload Message",
317
- "summary": "If you want a custom message when the comment token has expired, please provide this here.",
318
- "description": "This message is displayed on the submit-button when the comment token is expired."
319
- },
320
- {
321
- "key": "comments_cooldown",
322
- "section": "section_non_ui",
323
- "type": "integer",
324
- "default": 10,
325
- "min": 0
326
- },
327
  {
328
  "key": "human_spam_items",
329
  "section": "section_non_ui",
@@ -336,16 +227,9 @@
336
  "ip_address",
337
  "user_agent"
338
  ]
339
- },
340
- {
341
- "key": "enable_antibot_check",
342
- "section": "section_non_ui",
343
- "type": "checkbox",
344
- "default": ""
345
  }
346
  ],
347
  "definitions": {
348
- "comments_expire": 1800,
349
  "url_spam_blacklist_terms": "https://raw.githubusercontent.com/splorp/wordpress-comment-blacklist/master/blacklist.txt",
350
  "events": {
351
  "comment_spam_block": {
2
  "slug": "comments_filter",
3
  "properties": {
4
  "slug": "comments_filter",
5
+ "storage_key": "commentsfilter",
6
+ "load_priority": 100,
7
  "name": "Comments SPAM",
8
  "sidebar_name": "SPAM",
9
  "show_module_menu_item": false,
10
  "show_module_options": true,
 
11
  "tagline": "Block comment SPAM and retain your privacy",
12
  "show_central": true,
13
  "access_restricted": true,
17
  "run_if_wpcli": false,
18
  "order": 50
19
  },
20
+ "menus": {
21
+ "config_menu_priority": 40
22
+ },
23
  "wpcli": {
24
  "enabled": true
25
  },
45
  },
46
  {
47
  "slug": "section_human_spam_filter",
48
+ "title": "Human Comment SPAM Protection",
49
  "title_short": "Human SPAM",
50
  "beacon_id": 262,
51
  "summary": [
83
  "Recommendation - Keep the Comments Filter feature turned on."
84
  ]
85
  },
 
 
 
 
 
 
 
 
86
  {
87
  "slug": "section_non_ui",
88
  "hidden": true
176
  "summary": "How To Categorise Comments When Identified To Be SPAM",
177
  "description": "When a comment is detected as being SPAM from an automatic bot, the comment will be categorised based on this setting."
178
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  {
180
  "key": "enable_comments_human_spam_filter",
181
  "section": "section_human_spam_filter",
215
  "summary": "How To Categorise Comments When Identified To Be SPAM'",
216
  "description": "When a comment is detected as being SPAM from a human commenter, the comment will be categorised based on this setting."
217
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  {
219
  "key": "human_spam_items",
220
  "section": "section_non_ui",
227
  "ip_address",
228
  "user_agent"
229
  ]
 
 
 
 
 
 
230
  }
231
  ],
232
  "definitions": {
 
233
  "url_spam_blacklist_terms": "https://raw.githubusercontent.com/splorp/wordpress-comment-blacklist/master/blacklist.txt",
234
  "events": {
235
  "comment_spam_block": {
config/comms.json CHANGED
@@ -3,6 +3,7 @@
3
  "properties": {
4
  "slug": "comms",
5
  "storage_key": "comms",
 
6
  "name": "Comms",
7
  "menu_title": "Comms",
8
  "show_module_options": true,
3
  "properties": {
4
  "slug": "comms",
5
  "storage_key": "comms",
6
+ "load_priority": 100,
7
  "name": "Comms",
8
  "menu_title": "Comms",
9
  "show_module_options": true,
config/data.json CHANGED
@@ -2,6 +2,7 @@
2
  "slug": "data",
3
  "properties": {
4
  "slug": "data",
 
5
  "name": "Data",
6
  "show_module_menu_item": false,
7
  "auto_enabled": true,
@@ -115,6 +116,12 @@
115
  "attr": [
116
  "UNIQUE"
117
  ]
 
 
 
 
 
 
118
  }
119
  },
120
  "cols_timestamps": {
2
  "slug": "data",
3
  "properties": {
4
  "slug": "data",
5
+ "load_priority": 1,
6
  "name": "Data",
7
  "show_module_menu_item": false,
8
  "auto_enabled": true,
116
  "attr": [
117
  "UNIQUE"
118
  ]
119
+ },
120
+ "ip_ref": {
121
+ "macro_type": "foreign_key_id",
122
+ "foreign_key": {
123
+ "ref_table": "icwp_wpsf_ips"
124
+ }
125
  }
126
  },
127
  "cols_timestamps": {
config/deprecated/admin_access_restriction.php CHANGED
@@ -3,6 +3,8 @@
3
  "properties": {
4
  "slug": "admin_access_restriction",
5
  "name": "Security Admin",
 
 
6
  "sidebar_name": "Security Admin",
7
  "show_module_menu_item": false,
8
  "show_module_options": true,
@@ -16,6 +18,9 @@
16
  "run_if_wpcli": false,
17
  "order": 20
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true,
21
  "cmd_base": "secadmin"
@@ -173,9 +178,9 @@
173
  "link_info": "https://shsec.io/a0",
174
  "link_blog": "https://shsec.io/wpsf32",
175
  "beacon_id": 214,
176
- "name": "Options",
177
- "summary": "Restrict Access To Key WordPress Posts And Pages Actions",
178
- "description": "Careful: This will restrict access to page/post creation, editing and deletion. Note: Selecting 'Edit' will also restrict all other options."
179
  },
180
  {
181
  "key": "admin_access_restrict_admin_users",
3
  "properties": {
4
  "slug": "admin_access_restriction",
5
  "name": "Security Admin",
6
+ "namespace": "SecurityAdmin",
7
+ "load_priority": 11,
8
  "sidebar_name": "Security Admin",
9
  "show_module_menu_item": false,
10
  "show_module_options": true,
18
  "run_if_wpcli": false,
19
  "order": 20
20
  },
21
+ "menus": {
22
+ "config_menu_priority": 15
23
+ },
24
  "wpcli": {
25
  "enabled": true,
26
  "cmd_base": "secadmin"
178
  "link_info": "https://shsec.io/a0",
179
  "link_blog": "https://shsec.io/wpsf32",
180
  "beacon_id": 214,
181
+ "name": "WordPress Options",
182
+ "summary": "Restrict Access To Certain WordPress Admin Options",
183
+ "description": "Careful: This will restrict the ability of WordPress administrators from changing key WordPress settings."
184
  },
185
  {
186
  "key": "admin_access_restrict_admin_users",
config/deprecated/audit_trail.php CHANGED
@@ -3,6 +3,8 @@
3
  "properties": {
4
  "slug": "audit_trail",
5
  "name": "Audit Trail",
 
 
6
  "sidebar_name": "Audit Trail",
7
  "show_module_menu_item": false,
8
  "show_module_options": true,
@@ -16,6 +18,9 @@
16
  "run_if_wpcli": true,
17
  "order": 110
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
@@ -35,14 +40,6 @@
35
  }
36
  ],
37
  "admin_notices": {
38
- "new-audit-trail": {
39
- "id": "new-audit-trail",
40
- "schedule": "conditions",
41
- "valid_admin": true,
42
- "plugin_page_only": true,
43
- "can_dismiss": true,
44
- "type": "info"
45
- }
46
  },
47
  "sections": [
48
  {
3
  "properties": {
4
  "slug": "audit_trail",
5
  "name": "Audit Trail",
6
+ "load_priority": 11,
7
+ "menu_priority": 40,
8
  "sidebar_name": "Audit Trail",
9
  "show_module_menu_item": false,
10
  "show_module_options": true,
18
  "run_if_wpcli": true,
19
  "order": 110
20
  },
21
+ "menus": {
22
+ "config_menu_priority": 35
23
+ },
24
  "wpcli": {
25
  "enabled": true
26
  },
40
  }
41
  ],
42
  "admin_notices": {
 
 
 
 
 
 
 
 
43
  },
44
  "sections": [
45
  {
config/deprecated/autoupdates.php CHANGED
@@ -3,6 +3,7 @@
3
  "properties": {
4
  "slug": "autoupdates",
5
  "name": "Automatic Updates",
 
6
  "sidebar_name": "Auto Updates",
7
  "show_module_menu_item": false,
8
  "show_module_options": true,
@@ -16,6 +17,9 @@
16
  "run_if_wpcli": true,
17
  "order": 60
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
3
  "properties": {
4
  "slug": "autoupdates",
5
  "name": "Automatic Updates",
6
+ "load_priority": 100,
7
  "sidebar_name": "Auto Updates",
8
  "show_module_menu_item": false,
9
  "show_module_options": true,
17
  "run_if_wpcli": true,
18
  "order": 60
19
  },
20
+ "menus": {
21
+ "config_menu_priority": 55
22
+ },
23
  "wpcli": {
24
  "enabled": true
25
  },
config/deprecated/comments_filter.php CHANGED
@@ -2,11 +2,12 @@
2
  "slug": "comments_filter",
3
  "properties": {
4
  "slug": "comments_filter",
 
 
5
  "name": "Comments SPAM",
6
  "sidebar_name": "SPAM",
7
  "show_module_menu_item": false,
8
  "show_module_options": true,
9
- "storage_key": "commentsfilter",
10
  "tagline": "Block comment SPAM and retain your privacy",
11
  "show_central": true,
12
  "access_restricted": true,
@@ -16,6 +17,9 @@
16
  "run_if_wpcli": false,
17
  "order": 50
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
@@ -41,7 +45,7 @@
41
  },
42
  {
43
  "slug": "section_human_spam_filter",
44
- "title": "Human Comment SPAM Protection Filter",
45
  "title_short": "Human SPAM",
46
  "beacon_id": 262,
47
  "summary": [
@@ -79,14 +83,6 @@
79
  "Recommendation - Keep the Comments Filter feature turned on."
80
  ]
81
  },
82
- {
83
- "slug": "section_deprecated",
84
- "title": "Deprecated Options",
85
- "title_short": "Deprecated Options",
86
- "summary": [
87
- "These options will be removed in the future. Please use the ADE option under Bot SPAM section."
88
- ]
89
- },
90
  {
91
  "slug": "section_non_ui",
92
  "hidden": true
@@ -180,52 +176,6 @@
180
  "summary": "How To Categorise Comments When Identified To Be SPAM",
181
  "description": "When a comment is detected as being SPAM from an automatic bot, the comment will be categorised based on this setting."
182
  },
183
- {
184
- "key": "google_recaptcha_style_comments",
185
- "section": "section_deprecated",
186
- "default": "disabled",
187
- "type": "select",
188
- "value_options": [
189
- {
190
- "value_key": "disabled",
191
- "text": "Disabled"
192
- },
193
- {
194
- "value_key": "default",
195
- "text": "Default Style"
196
- },
197
- {
198
- "value_key": "light",
199
- "text": "Light Theme"
200
- },
201
- {
202
- "value_key": "dark",
203
- "text": "Dark Theme"
204
- },
205
- {
206
- "value_key": "invisible",
207
- "text": "Invisible"
208
- }
209
- ],
210
- "link_info": "https://shsec.io/e4",
211
- "link_blog": "",
212
- "beacon_id": 269,
213
- "name": "CAPTCHA",
214
- "summary": "Enable CAPTCHA To Protect Against SPAM Comments",
215
- "description": "You can choose the CAPTCHA display format that best suits your site, including the newer Invisible CAPTCHA."
216
- },
217
- {
218
- "key": "enable_comments_gasp_protection",
219
- "section": "section_deprecated",
220
- "default": "N",
221
- "type": "checkbox",
222
- "link_info": "https://shsec.io/3n",
223
- "link_blog": "https://shsec.io/2n",
224
- "beacon_id": 401,
225
- "name": "GASP Protection",
226
- "summary": "Block Bot Comment SPAM",
227
- "description": "Taking the lead from the original GASP plugin for WordPress, we have extended it to include advanced spam-bot protection."
228
- },
229
  {
230
  "key": "enable_comments_human_spam_filter",
231
  "section": "section_human_spam_filter",
@@ -265,65 +215,6 @@
265
  "summary": "How To Categorise Comments When Identified To Be SPAM'",
266
  "description": "When a comment is detected as being SPAM from a human commenter, the comment will be categorised based on this setting."
267
  },
268
- {
269
- "key": "custom_message_checkbox",
270
- "section": "section_deprecated",
271
- "sensitive": true,
272
- "default": "default",
273
- "type": "text",
274
- "link_info": "https://shsec.io/3p",
275
- "link_blog": "",
276
- "beacon_id": 403,
277
- "name": "Custom Checkbox Message",
278
- "summary": "If you want a custom checkbox message, please provide this here",
279
- "description": "You can customise the message beside the checkbox."
280
- },
281
- {
282
- "key": "custom_message_alert",
283
- "section": "section_deprecated",
284
- "sensitive": true,
285
- "default": "default",
286
- "type": "text",
287
- "link_info": "https://shsec.io/3p",
288
- "link_blog": "",
289
- "beacon_id": 403,
290
- "name": "Custom Alert Message",
291
- "summary": "If you want a custom alert message, please provide this here",
292
- "description": "This alert message is displayed when a visitor attempts to submit a comment without checking the box."
293
- },
294
- {
295
- "key": "custom_message_comment_wait",
296
- "section": "section_deprecated",
297
- "sensitive": true,
298
- "default": "default",
299
- "type": "text",
300
- "link_info": "https://shsec.io/3p",
301
- "link_blog": "",
302
- "beacon_id": 403,
303
- "name": "Custom Wait Message",
304
- "summary": "If you want a custom submit-button wait message, please provide this here.",
305
- "description": "Where you see the '%s' this will be the number of seconds. You must ensure you include 1, and only 1, of these."
306
- },
307
- {
308
- "key": "custom_message_comment_reload",
309
- "section": "section_deprecated",
310
- "sensitive": true,
311
- "default": "default",
312
- "type": "text",
313
- "link_info": "https://shsec.io/3p",
314
- "link_blog": "",
315
- "beacon_id": 403,
316
- "name": "Custom Reload Message",
317
- "summary": "If you want a custom message when the comment token has expired, please provide this here.",
318
- "description": "This message is displayed on the submit-button when the comment token is expired."
319
- },
320
- {
321
- "key": "comments_cooldown",
322
- "section": "section_non_ui",
323
- "type": "integer",
324
- "default": 10,
325
- "min": 0
326
- },
327
  {
328
  "key": "human_spam_items",
329
  "section": "section_non_ui",
@@ -336,16 +227,9 @@
336
  "ip_address",
337
  "user_agent"
338
  ]
339
- },
340
- {
341
- "key": "enable_antibot_check",
342
- "section": "section_non_ui",
343
- "type": "checkbox",
344
- "default": ""
345
  }
346
  ],
347
  "definitions": {
348
- "comments_expire": 1800,
349
  "url_spam_blacklist_terms": "https://raw.githubusercontent.com/splorp/wordpress-comment-blacklist/master/blacklist.txt",
350
  "events": {
351
  "comment_spam_block": {
2
  "slug": "comments_filter",
3
  "properties": {
4
  "slug": "comments_filter",
5
+ "storage_key": "commentsfilter",
6
+ "load_priority": 100,
7
  "name": "Comments SPAM",
8
  "sidebar_name": "SPAM",
9
  "show_module_menu_item": false,
10
  "show_module_options": true,
 
11
  "tagline": "Block comment SPAM and retain your privacy",
12
  "show_central": true,
13
  "access_restricted": true,
17
  "run_if_wpcli": false,
18
  "order": 50
19
  },
20
+ "menus": {
21
+ "config_menu_priority": 40
22
+ },
23
  "wpcli": {
24
  "enabled": true
25
  },
45
  },
46
  {
47
  "slug": "section_human_spam_filter",
48
+ "title": "Human Comment SPAM Protection",
49
  "title_short": "Human SPAM",
50
  "beacon_id": 262,
51
  "summary": [
83
  "Recommendation - Keep the Comments Filter feature turned on."
84
  ]
85
  },
 
 
 
 
 
 
 
 
86
  {
87
  "slug": "section_non_ui",
88
  "hidden": true
176
  "summary": "How To Categorise Comments When Identified To Be SPAM",
177
  "description": "When a comment is detected as being SPAM from an automatic bot, the comment will be categorised based on this setting."
178
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  {
180
  "key": "enable_comments_human_spam_filter",
181
  "section": "section_human_spam_filter",
215
  "summary": "How To Categorise Comments When Identified To Be SPAM'",
216
  "description": "When a comment is detected as being SPAM from a human commenter, the comment will be categorised based on this setting."
217
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  {
219
  "key": "human_spam_items",
220
  "section": "section_non_ui",
227
  "ip_address",
228
  "user_agent"
229
  ]
 
 
 
 
 
 
230
  }
231
  ],
232
  "definitions": {
 
233
  "url_spam_blacklist_terms": "https://raw.githubusercontent.com/splorp/wordpress-comment-blacklist/master/blacklist.txt",
234
  "events": {
235
  "comment_spam_block": {
config/deprecated/comms.php CHANGED
@@ -3,6 +3,7 @@
3
  "properties": {
4
  "slug": "comms",
5
  "storage_key": "comms",
 
6
  "name": "Comms",
7
  "menu_title": "Comms",
8
  "show_module_options": true,
3
  "properties": {
4
  "slug": "comms",
5
  "storage_key": "comms",
6
+ "load_priority": 100,
7
  "name": "Comms",
8
  "menu_title": "Comms",
9
  "show_module_options": true,
config/deprecated/data.php CHANGED
@@ -2,6 +2,7 @@
2
  "slug": "data",
3
  "properties": {
4
  "slug": "data",
 
5
  "name": "Data",
6
  "show_module_menu_item": false,
7
  "auto_enabled": true,
@@ -77,7 +78,7 @@
77
  },
78
  "path": {
79
  "macro_type": "varchar",
80
- "length": 256,
81
  "comment": "Request Path"
82
  },
83
  "code": {
@@ -115,6 +116,12 @@
115
  "attr": [
116
  "UNIQUE"
117
  ]
 
 
 
 
 
 
118
  }
119
  },
120
  "cols_timestamps": {
2
  "slug": "data",
3
  "properties": {
4
  "slug": "data",
5
+ "load_priority": 1,
6
  "name": "Data",
7
  "show_module_menu_item": false,
8
  "auto_enabled": true,
78
  },
79
  "path": {
80
  "macro_type": "varchar",
81
+ "length": 512,
82
  "comment": "Request Path"
83
  },
84
  "code": {
116
  "attr": [
117
  "UNIQUE"
118
  ]
119
+ },
120
+ "ip_ref": {
121
+ "macro_type": "foreign_key_id",
122
+ "foreign_key": {
123
+ "ref_table": "icwp_wpsf_ips"
124
+ }
125
  }
126
  },
127
  "cols_timestamps": {
config/deprecated/email.php CHANGED
@@ -2,6 +2,7 @@
2
  "slug": "email",
3
  "properties": {
4
  "slug": "email",
 
5
  "name": "Email",
6
  "show_module_menu_item": false,
7
  "auto_enabled": true,
2
  "slug": "email",
3
  "properties": {
4
  "slug": "email",
5
+ "load_priority": 100,
6
  "name": "Email",
7
  "show_module_menu_item": false,
8
  "auto_enabled": true,
config/deprecated/events.php CHANGED
@@ -1,6 +1,7 @@
1
  {
2
  "properties": {
3
  "slug": "events",
 
4
  "name": "Events",
5
  "show_module_menu_item": false,
6
  "storage_key": "events",
1
  {
2
  "properties": {
3
  "slug": "events",
4
+ "load_priority": 11,
5
  "name": "Events",
6
  "show_module_menu_item": false,
7
  "storage_key": "events",
config/deprecated/firewall.php CHANGED
@@ -2,6 +2,7 @@
2
  "slug": "firewall",
3
  "properties": {
4
  "slug": "firewall",
 
5
  "name": "Firewall",
6
  "sidebar_name": "Firewall",
7
  "show_module_menu_item": false,
@@ -16,6 +17,9 @@
16
  "run_if_wpcli": false,
17
  "order": 30
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
@@ -247,88 +251,90 @@
247
  "name": "Ignore Administrators",
248
  "summary": "Ignore Administrators",
249
  "description": "Authenticated administrator users will not be processed by the firewall rules."
250
- },
251
- {
252
- "key": "text_firewalldie",
253
- "section": "section_user_messages",
254
- "sensitive": true,
255
- "premium": true,
256
- "default": "default",
257
- "type": "text",
258
- "beacon_id": 139,
259
- "link_info": "",
260
- "link_blog": "",
261
- "name": "Firewall Block Message",
262
- "summary": "Message Displayed To Visitor When A Firewall Block Is Triggered",
263
- "description": "When you select the option to display a message to the visitor, this is the message that is displayed."
264
  }
265
  ],
266
  "definitions": {
267
  "default_whitelist": {
268
  "/wp-admin/options-general.php": [],
269
- "/wp-admin/options.php": [
270
- "home",
271
- "siteurl"
272
- ],
273
- "/wp-admin/plugins.php": [
274
- "plugin"
275
- ],
 
 
 
 
276
  "/wp-admin/post-new.php": [],
277
  "/wp-admin/page-new.php": [],
278
  "/wp-admin/link-add.php": [],
279
  "/wp-admin/media-upload.php": [],
280
- "/wp-admin/admin.php": [
281
- "page"
282
- ],
283
- "/wp-admin/post.php": [
284
- "content"
285
- ],
286
- "/wp-admin/plugin-editor.php": [
287
- "newcontent"
288
- ],
 
 
 
 
 
 
289
  "/wp-admin/page.php": [],
290
  "/wp-admin/admin-ajax.php": [],
291
- "/wp-comments-post.php": [
292
- "url",
293
- "comment"
294
- ],
295
- "*": [
296
- "affwp_action",
297
- "ajaxurl",
298
- "g-recaptcha-response",
299
- "verify_sign",
300
- "txn_id",
301
- "wp_http_referer",
302
- "_wp_http_referer",
303
- "_wp_original_http_referer",
304
- "JCS_INENREF",
305
- "pass1",
306
- "pass1-text",
307
- "pwd",
308
- "url",
309
- "referredby",
310
- "redirect_to",
311
- "jetpack_sso_original_request",
312
- "jetpack_sso_redirect_to",
313
- "/^wordpress_logged_in_[0-9a-f]+$/",
314
- "edd_action",
315
- "edd_redirect",
316
- "wpcf7-form",
317
- "yoast_wpseo_metadesc",
318
- "icwp_wpsf_new_u2f_response",
319
- "icwp_wpsf_u2f_otp",
320
- "shield_action",
321
- "appId",
322
- "/^et_.*/",
323
- "ping_sites",
324
- "aioseo-post-settings",
325
- "joe-chnlcustid",
326
- "spd-custhash",
327
- "joe-custinfo"
328
- ]
 
 
 
 
 
 
329
  },
330
  "firewall_patterns": {
331
- "dirtraversal": {
332
  "simple": [
333
  "etc/passwd",
334
  "proc/self/environ",
@@ -340,36 +346,35 @@
340
  "loopback"
341
  ]
342
  },
343
- "wpterms": {
344
  "simple": [
345
  "/**/",
346
  "wp-config.php"
347
  ],
348
  "regex": [
349
- "^wp_",
350
  "^user_login",
351
  "^user_pass"
352
  ]
353
  },
354
- "fieldtruncation": {
355
  "regex": [
356
  "\\s{49,}",
357
  "\\x00"
358
  ]
359
  },
360
- "sqlqueries": {
361
  "regex": [
362
  "concat\\s*\\(",
363
  "group_concat",
364
  "union.*select"
365
  ]
366
  },
367
- "exefile": {
368
  "regex": [
369
  "\\.(dll|rb|py|exe|php[3-6]?|pl|perl|ph[34]|phl|phtml|phtm|sql|ini|jsp|asp|git|svn|tar)$"
370
  ]
371
  },
372
- "schema": {
373
  "simple": [
374
  ".shtml"
375
  ],
@@ -377,13 +382,13 @@
377
  "^(http|https|ftp|file):"
378
  ]
379
  },
380
- "phpcode": {
381
  "simple": null,
382
  "regex": [
383
  "(include|include_once|require|require_once)\\s*\\(.*\\)"
384
  ]
385
  },
386
- "aggressive": {
387
  "simple": [
388
  "eval(",
389
  "(null)",
@@ -407,13 +412,6 @@
407
  }
408
  },
409
  "events": {
410
- "check_skip": {
411
- "audit_params": [
412
- "path"
413
- ],
414
- "level": "debug",
415
- "stat": false
416
- },
417
  "firewall_block": {
418
  "audit_params": [
419
  "name",
2
  "slug": "firewall",
3
  "properties": {
4
  "slug": "firewall",
5
+ "load_priority": 1000,
6
  "name": "Firewall",
7
  "sidebar_name": "Firewall",
8
  "show_module_menu_item": false,
17
  "run_if_wpcli": false,
18
  "order": 30
19
  },
20
+ "menus": {
21
+ "config_menu_priority": 44
22
+ },
23
  "wpcli": {
24
  "enabled": true
25
  },
251
  "name": "Ignore Administrators",
252
  "summary": "Ignore Administrators",
253
  "description": "Authenticated administrator users will not be processed by the firewall rules."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  }
255
  ],
256
  "definitions": {
257
  "default_whitelist": {
258
  "/wp-admin/options-general.php": [],
259
+ "/wp-admin/options.php": {
260
+ "simple": [
261
+ "home",
262
+ "siteurl"
263
+ ]
264
+ },
265
+ "/wp-admin/plugins.php": {
266
+ "simple": [
267
+ "plugin"
268
+ ]
269
+ },
270
  "/wp-admin/post-new.php": [],
271
  "/wp-admin/page-new.php": [],
272
  "/wp-admin/link-add.php": [],
273
  "/wp-admin/media-upload.php": [],
274
+ "/wp-admin/admin.php": {
275
+ "simple": [
276
+ "page"
277
+ ]
278
+ },
279
+ "/wp-admin/post.php": {
280
+ "simple": [
281
+ "content"
282
+ ]
283
+ },
284
+ "/wp-admin/plugin-editor.php": {
285
+ "simple": [
286
+ "newcontent"
287
+ ]
288
+ },
289
  "/wp-admin/page.php": [],
290
  "/wp-admin/admin-ajax.php": [],
291
+ "/wp-comments-post.php": {
292
+ "simple": [
293
+ "url",
294
+ "comment"
295
+ ]
296
+ },
297
+ "*": {
298
+ "regex": [
299
+ "^wordpress_logged_in_[0-9a-f]+$",
300
+ "^et_.*"
301
+ ],
302
+ "simple": [
303
+ "affwp_action",
304
+ "ajaxurl",
305
+ "g-recaptcha-response",
306
+ "verify_sign",
307
+ "txn_id",
308
+ "wp_http_referer",
309
+ "_wp_http_referer",
310
+ "_wp_original_http_referer",
311
+ "JCS_INENREF",
312
+ "pass1",
313
+ "pass1-text",
314
+ "pwd",
315
+ "url",
316
+ "referredby",
317
+ "redirect_to",
318
+ "jetpack_sso_original_request",
319
+ "jetpack_sso_redirect_to",
320
+ "edd_action",
321
+ "edd_redirect",
322
+ "wpcf7-form",
323
+ "yoast_wpseo_metadesc",
324
+ "icwp_wpsf_new_u2f_response",
325
+ "icwp_wpsf_u2f_otp",
326
+ "shield_action",
327
+ "appId",
328
+ "ping_sites",
329
+ "aioseo-post-settings",
330
+ "joe-chnlcustid",
331
+ "spd-custhash",
332
+ "joe-custinfo"
333
+ ]
334
+ }
335
  },
336
  "firewall_patterns": {
337
+ "dir_traversal": {
338
  "simple": [
339
  "etc/passwd",
340
  "proc/self/environ",
346
  "loopback"
347
  ]
348
  },
349
+ "wordpress_terms": {
350
  "simple": [
351
  "/**/",
352
  "wp-config.php"
353
  ],
354
  "regex": [
 
355
  "^user_login",
356
  "^user_pass"
357
  ]
358
  },
359
+ "field_truncation": {
360
  "regex": [
361
  "\\s{49,}",
362
  "\\x00"
363
  ]
364
  },
365
+ "sql_queries": {
366
  "regex": [
367
  "concat\\s*\\(",
368
  "group_concat",
369
  "union.*select"
370
  ]
371
  },
372
+ "exe_file_uploads": {
373
  "regex": [
374
  "\\.(dll|rb|py|exe|php[3-6]?|pl|perl|ph[34]|phl|phtml|phtm|sql|ini|jsp|asp|git|svn|tar)$"
375
  ]
376
  },
377
+ "leading_schema": {
378
  "simple": [
379
  ".shtml"
380
  ],
382
  "^(http|https|ftp|file):"
383
  ]
384
  },
385
+ "php_code": {
386
  "simple": null,
387
  "regex": [
388
  "(include|include_once|require|require_once)\\s*\\(.*\\)"
389
  ]
390
  },
391
+ "aggressive": {
392
  "simple": [
393
  "eval(",
394
  "(null)",
412
  }
413
  },
414
  "events": {
 
 
 
 
 
 
 
415
  "firewall_block": {
416
  "audit_params": [
417
  "name",
config/deprecated/hack_protect.php CHANGED
@@ -2,7 +2,10 @@
2
  "slug": "hack_protect",
3
  "properties": {
4
  "slug": "hack_protect",
 
 
5
  "name": "Hack Guard",
 
6
  "sidebar_name": "Scanners",
7
  "show_module_menu_item": false,
8
  "show_module_options": true,
@@ -16,6 +19,9 @@
16
  "run_if_verified_bot": true,
17
  "run_if_wpcli": true
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true,
21
  "root": "hack_guard"
@@ -57,16 +63,6 @@
57
  "Recommendation - Ensure this is turned on and you will always know if any of your assets have known security vulnerabilities."
58
  ]
59
  },
60
- {
61
- "slug": "section_realtime",
62
- "title": "Realtime Change Detection",
63
- "title_short": "Realtime Change Detection",
64
- "beacon_id": 226,
65
- "summary": [
66
- "Purpose - Monitor Your WordPress Site For Changes To Critical Components In Realtime.",
67
- "Recommendation - Keep The Realtime Change Detection Active."
68
- ]
69
- },
70
  {
71
  "slug": "section_scan_options",
72
  "title": "Scan Options",
@@ -147,17 +143,35 @@
147
  "description": "When a file is modified, or malware is detected, Shield can try to repair files."
148
  },
149
  {
150
- "key": "auto_filter_results",
151
- "section": "section_file_guard",
152
- "premium": false,
153
- "type": "checkbox",
154
- "default": "Y",
155
- "link_info": "",
156
- "link_blog": "",
157
- "beacon_id": 439,
158
- "name": "Auto-Filter Results",
159
- "summary": "Automatically Filter Results Of Irrelevant Items",
160
- "description": "Automatically remove items from results that are irrelevant."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  },
162
  {
163
  "key": "scan_path_exclusions",
@@ -221,37 +235,6 @@
221
  "summary": "Apply Updates Automatically To Vulnerable Plugins",
222
  "description": "When an update becomes available, automatically apply updates to items with known vulnerabilities."
223
  },
224
- {
225
- "key": "file_locker",
226
- "section": "section_realtime",
227
- "premium": true,
228
- "type": "multiple_select",
229
- "default": [],
230
- "value_options": [
231
- {
232
- "value_key": "wpconfig",
233
- "text": "WP Config"
234
- },
235
- {
236
- "value_key": "root_htaccess",
237
- "text": "Root .htaccess"
238
- },
239
- {
240
- "value_key": "root_index",
241
- "text": "Root index.php"
242
- },
243
- {
244
- "value_key": "root_webconfig",
245
- "text": "Root Web.Config"
246
- }
247
- ],
248
- "link_info": "https://shsec.io/h7",
249
- "link_blog": "https://shsec.io/h8",
250
- "beacon_id": 226,
251
- "name": "File Locker",
252
- "summary": "Lock Files Against Tampering and Changes",
253
- "description": "As soon as changes are detected to any selected files, the contents may be examined and reverted."
254
- },
255
  {
256
  "key": "scan_frequency",
257
  "section": "section_scan_options",
2
  "slug": "hack_protect",
3
  "properties": {
4
  "slug": "hack_protect",
5
+ "load_priority": 100,
6
+ "menu_priority": 20,
7
  "name": "Hack Guard",
8
+ "namespace": "HackGuard",
9
  "sidebar_name": "Scanners",
10
  "show_module_menu_item": false,
11
  "show_module_options": true,
19
  "run_if_verified_bot": true,
20
  "run_if_wpcli": true
21
  },
22
+ "menus": {
23
+ "config_menu_priority": 25
24
+ },
25
  "wpcli": {
26
  "enabled": true,
27
  "root": "hack_guard"
63
  "Recommendation - Ensure this is turned on and you will always know if any of your assets have known security vulnerabilities."
64
  ]
65
  },
 
 
 
 
 
 
 
 
 
 
66
  {
67
  "slug": "section_scan_options",
68
  "title": "Scan Options",
143
  "description": "When a file is modified, or malware is detected, Shield can try to repair files."
144
  },
145
  {
146
+ "key": "file_locker",
147
+ "section": "section_file_guard",
148
+ "premium": true,
149
+ "type": "multiple_select",
150
+ "default": [],
151
+ "value_options": [
152
+ {
153
+ "value_key": "wpconfig",
154
+ "text": "WP Config"
155
+ },
156
+ {
157
+ "value_key": "root_htaccess",
158
+ "text": "Root .htaccess"
159
+ },
160
+ {
161
+ "value_key": "root_index",
162
+ "text": "Root index.php"
163
+ },
164
+ {
165
+ "value_key": "root_webconfig",
166
+ "text": "Root Web.Config"
167
+ }
168
+ ],
169
+ "link_info": "https://shsec.io/h7",
170
+ "link_blog": "https://shsec.io/h8",
171
+ "beacon_id": 226,
172
+ "name": "File Locker",
173
+ "summary": "Lock Files Against Tampering and Changes",
174
+ "description": "As soon as changes are detected to any selected files, the contents may be examined and reverted."
175
  },
176
  {
177
  "key": "scan_path_exclusions",
235
  "summary": "Apply Updates Automatically To Vulnerable Plugins",
236
  "description": "When an update becomes available, automatically apply updates to items with known vulnerabilities."
237
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  {
239
  "key": "scan_frequency",
240
  "section": "section_scan_options",
config/deprecated/headers.php CHANGED
@@ -2,6 +2,7 @@
2
  "slug": "headers",
3
  "properties": {
4
  "slug": "headers",
 
5
  "name": "HTTP Headers",
6
  "sidebar_name": "HTTP Headers",
7
  "show_module_menu_item": false,
@@ -16,6 +17,9 @@
16
  "run_if_wpcli": false,
17
  "order": 80
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
2
  "slug": "headers",
3
  "properties": {
4
  "slug": "headers",
5
+ "load_priority": 100,
6
  "name": "HTTP Headers",
7
  "sidebar_name": "HTTP Headers",
8
  "show_module_menu_item": false,
17
  "run_if_wpcli": false,
18
  "order": 80
19
  },
20
+ "menus": {
21
+ "config_menu_priority": 60
22
+ },
23
  "wpcli": {
24
  "enabled": true
25
  },
config/deprecated/insights.php CHANGED
@@ -2,9 +2,10 @@
2
  "slug": "insights",
3
  "properties": {
4
  "slug": "insights",
 
 
5
  "name": "Dashboard",
6
  "menu_title": "Security Dashboard",
7
- "menu_priority": "5",
8
  "show_module_menu_item": true,
9
  "show_module_options": false,
10
  "auto_enabled": true,
2
  "slug": "insights",
3
  "properties": {
4
  "slug": "insights",
5
+ "load_priority": 2,
6
+ "menu_priority": 5,
7
  "name": "Dashboard",
8
  "menu_title": "Security Dashboard",
 
9
  "show_module_menu_item": true,
10
  "show_module_options": false,
11
  "auto_enabled": true,
config/deprecated/integrations.php CHANGED
@@ -3,6 +3,7 @@
3
  "properties": {
4
  "slug": "integrations",
5
  "storage_key": "integrations",
 
6
  "name": "Integrations",
7
  "menu_title": "Integrations",
8
  "sidebar_name": "Integrations",
@@ -18,6 +19,9 @@
18
  "skip_processor": false,
19
  "tracking_exclude": false
20
  },
 
 
 
21
  "wpcli": {
22
  "enabled": true
23
  },
3
  "properties": {
4
  "slug": "integrations",
5
  "storage_key": "integrations",
6
+ "load_priority": 20,
7
  "name": "Integrations",
8
  "menu_title": "Integrations",
9
  "sidebar_name": "Integrations",
19
  "skip_processor": false,
20
  "tracking_exclude": false
21
  },
22
+ "menus": {
23
+ "config_menu_priority": 50
24
+ },
25
  "wpcli": {
26
  "enabled": true
27
  },
config/deprecated/ips.php CHANGED
@@ -2,7 +2,10 @@
2
  "slug": "ips",
3
  "properties": {
4
  "slug": "ips",
 
 
5
  "name": "Block Bad IPs/Visitors",
 
6
  "sidebar_name": "IP Blocking",
7
  "show_module_menu_item": false,
8
  "show_module_options": true,
@@ -16,6 +19,9 @@
16
  "run_if_wpcli": false,
17
  "order": 100
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
@@ -116,6 +122,7 @@
116
  "slug": "section_enable_plugin_feature_ips",
117
  "title": "Enable Module: IP Manager",
118
  "title_short": "Disable Module",
 
119
  "summary": [
120
  "Purpose - The IP Manager allows you to whitelist, blacklist and configure auto-blacklist rules.",
121
  "Recommendation - Keep the IP Manager feature turned on. You should also carefully review the automatic black list settings."
@@ -185,6 +192,7 @@
185
  "section": "section_auto_black_list",
186
  "type": "integer",
187
  "default": 10,
 
188
  "link_info": "https://shsec.io/wpsf24",
189
  "link_blog": "https://shsec.io/wpsf26",
190
  "beacon_id": 207,
@@ -344,7 +352,7 @@
344
  ],
345
  "link_info": "https://shsec.io/fo",
346
  "link_blog": "https://shsec.io/f6",
347
- "beacon_id": 123,
348
  "name": "Link Cheese",
349
  "summary": "Tempt A Bot With A Fake Link To Follow",
350
  "description": "Detect A Bot That Follows A 'no-follow' Link."
@@ -518,7 +526,7 @@
518
  ],
519
  "link_info": "https://shsec.io/f5",
520
  "link_blog": "https://shsec.io/f7",
521
- "beacon_id": 206,
522
  "name": "Fake Web Crawler",
523
  "summary": "Detect Fake Search Engine Crawlers",
524
  "description": "Identify a Bot when it presents as an official web crawler, but analysis shows it's fake."
@@ -558,20 +566,6 @@
558
  "summary": "Detect Requests With Empty User Agents",
559
  "description": "Identify a request as a bot if the user agent is not provided."
560
  },
561
- {
562
- "key": "text_remainingtrans",
563
- "section": "section_user_messages",
564
- "sensitive": true,
565
- "premium": true,
566
- "default": "default",
567
- "type": "text",
568
- "link_info": "https://shsec.io/e9",
569
- "link_blog": "",
570
- "beacon_id": 139,
571
- "name": "Remaining Offenses",
572
- "summary": "Visitor Triggers The IP Offenses System Through A Firewall Block",
573
- "description": "This message is displayed if the visitor triggered the IP Offenses system and reports how many offenses remain before being blocked."
574
- },
575
  {
576
  "key": "autounblock_ips",
577
  "section": "section_non_ui",
@@ -673,6 +667,10 @@
673
  "macro_type": "timestamp",
674
  "comment": "BotTrack InvalidScript"
675
  },
 
 
 
 
676
  "cooldown_at": {
677
  "macro_type": "timestamp",
678
  "comment": "Cooldown Triggered"
2
  "slug": "ips",
3
  "properties": {
4
  "slug": "ips",
5
+ "load_priority": 15,
6
+ "menu_priority": 30,
7
  "name": "Block Bad IPs/Visitors",
8
+ "namespace": "IPs",
9
  "sidebar_name": "IP Blocking",
10
  "show_module_menu_item": false,
11
  "show_module_options": true,
19
  "run_if_wpcli": false,
20
  "order": 100
21
  },
22
+ "menus": {
23
+ "config_menu_priority": 20
24
+ },
25
  "wpcli": {
26
  "enabled": true
27
  },
122
  "slug": "section_enable_plugin_feature_ips",
123
  "title": "Enable Module: IP Manager",
124
  "title_short": "Disable Module",
125
+ "beacon_id": 208,
126
  "summary": [
127
  "Purpose - The IP Manager allows you to whitelist, blacklist and configure auto-blacklist rules.",
128
  "Recommendation - Keep the IP Manager feature turned on. You should also carefully review the automatic black list settings."
192
  "section": "section_auto_black_list",
193
  "type": "integer",
194
  "default": 10,
195
+ "min": 0,
196
  "link_info": "https://shsec.io/wpsf24",
197
  "link_blog": "https://shsec.io/wpsf26",
198
  "beacon_id": 207,
352
  ],
353
  "link_info": "https://shsec.io/fo",
354
  "link_blog": "https://shsec.io/f6",
355
+ "beacon_id": 206,
356
  "name": "Link Cheese",
357
  "summary": "Tempt A Bot With A Fake Link To Follow",
358
  "description": "Detect A Bot That Follows A 'no-follow' Link."
526
  ],
527
  "link_info": "https://shsec.io/f5",
528
  "link_blog": "https://shsec.io/f7",
529
+ "beacon_id": 124,
530
  "name": "Fake Web Crawler",
531
  "summary": "Detect Fake Search Engine Crawlers",
532
  "description": "Identify a Bot when it presents as an official web crawler, but analysis shows it's fake."
566
  "summary": "Detect Requests With Empty User Agents",
567
  "description": "Identify a request as a bot if the user agent is not provided."
568
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
569
  {
570
  "key": "autounblock_ips",
571
  "section": "section_non_ui",
667
  "macro_type": "timestamp",
668
  "comment": "BotTrack InvalidScript"
669
  },
670
+ "btauthorfishing_at": {
671
+ "macro_type": "timestamp",
672
+ "comment": "BotTrack Author Username Fishing"
673
+ },
674
  "cooldown_at": {
675
  "macro_type": "timestamp",
676
  "comment": "Cooldown Triggered"
config/deprecated/license.php CHANGED
@@ -2,6 +2,7 @@
2
  "slug": "license",
3
  "properties": {
4
  "slug": "license",
 
5
  "name": "Pro Security",
6
  "menu_title": "",
7
  "show_module_menu_item": false,
2
  "slug": "license",
3
  "properties": {
4
  "slug": "license",
5
+ "load_priority": 10,
6
  "name": "Pro Security",
7
  "menu_title": "",
8
  "show_module_menu_item": false,
config/deprecated/lockdown.php CHANGED
@@ -2,6 +2,7 @@
2
  "slug": "lockdown",
3
  "properties": {
4
  "slug": "lockdown",
 
5
  "name": "WP Lockdown",
6
  "sidebar_name": "Lockdown",
7
  "show_module_menu_item": false,
@@ -16,6 +17,9 @@
16
  "run_if_wpcli": false,
17
  "order": 90
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
@@ -181,7 +185,7 @@
181
  }
182
  ],
183
  "definitions": {
184
- "events": {
185
  "block_anonymous_restapi": {
186
  "audit_params": [
187
  "namespace"
@@ -189,6 +193,10 @@
189
  "level": "warning",
190
  "recent": true
191
  },
 
 
 
 
192
  "block_xml": {
193
  "level": "notice",
194
  "recent": true,
2
  "slug": "lockdown",
3
  "properties": {
4
  "slug": "lockdown",
5
+ "load_priority": 100,
6
  "name": "WP Lockdown",
7
  "sidebar_name": "Lockdown",
8
  "show_module_menu_item": false,
17
  "run_if_wpcli": false,
18
  "order": 90
19
  },
20
+ "menus": {
21
+ "config_menu_priority": 45
22
+ },
23
  "wpcli": {
24
  "enabled": true
25
  },
185
  }
186
  ],
187
  "definitions": {
188
+ "events": {
189
  "block_anonymous_restapi": {
190
  "audit_params": [
191
  "namespace"
193
  "level": "warning",
194
  "recent": true
195
  },
196
+ "block_author_fishing": {
197
+ "level": "notice",
198
+ "offense": true
199
+ },
200
  "block_xml": {
201
  "level": "notice",
202
  "recent": true,
config/deprecated/login_protect.php CHANGED
@@ -2,7 +2,9 @@
2
  "slug": "login_protect",
3
  "properties": {
4
  "slug": "login_protect",
 
5
  "name": "Login Guard",
 
6
  "sidebar_name": "Login Protection",
7
  "show_module_menu_item": false,
8
  "show_module_options": true,
@@ -16,6 +18,9 @@
16
  "run_if_wpcli": false,
17
  "order": 40
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
@@ -42,6 +47,17 @@
42
  "Recommendation - Use of this feature is highly recommend."
43
  ]
44
  },
 
 
 
 
 
 
 
 
 
 
 
45
  {
46
  "slug": "section_2fa_email",
47
  "title": "Email Two-Factor Authentication",
@@ -74,17 +90,6 @@
74
  "Note: You may combine multiple authentication factors for increased security."
75
  ]
76
  },
77
- {
78
- "slug": "section_multifactor_authentication",
79
- "title": "Multi-Factor Authentication",
80
- "title_short": "2-Factor Auth",
81
- "beacon_id": 326,
82
- "summary": [
83
- "Purpose - Verifies the identity of users who log in to your site - i.e. they are who they say they are.",
84
- "Recommendation - Use of this feature is highly recommend. However, if your host blocks email sending you may lock yourself out.",
85
- "Note: You may combine multiple authentication factors for increased security."
86
- ]
87
- },
88
  {
89
  "slug": "section_rename_wplogin",
90
  "title": "Hide WP Login Page",
@@ -116,6 +121,14 @@
116
  "Recommendation - Keep the Login Guard module turned on."
117
  ]
118
  },
 
 
 
 
 
 
 
 
119
  {
120
  "slug": "section_non_ui",
121
  "hidden": true
@@ -165,17 +178,17 @@
165
  },
166
  {
167
  "key": "mfa_verify_page",
168
- "section": "section_multifactor_authentication",
169
- "default": "custom_shield",
170
  "type": "select",
171
  "value_options": [
172
  {
173
- "value_key": "custom_shield",
174
- "text": "Custom Shield MFA Page"
175
  },
176
  {
177
- "value_key": "wp_login",
178
- "text": "WP Login Page (beta)"
179
  }
180
  ],
181
  "link_info": "",
@@ -187,7 +200,7 @@
187
  },
188
  {
189
  "key": "mfa_user_setup_pages",
190
- "section": "section_multifactor_authentication",
191
  "type": "multiple_select",
192
  "default": [
193
  "profile"
@@ -211,7 +224,7 @@
211
  },
212
  {
213
  "key": "mfa_skip",
214
- "section": "section_multifactor_authentication",
215
  "premium": true,
216
  "type": "integer",
217
  "default": 0,
@@ -225,7 +238,7 @@
225
  },
226
  {
227
  "key": "allow_backupcodes",
228
- "section": "section_multifactor_authentication",
229
  "premium": true,
230
  "default": "N",
231
  "type": "checkbox",
@@ -363,7 +376,7 @@
363
  },
364
  {
365
  "key": "enable_login_gasp_check",
366
- "section": "section_brute_force_login_protection",
367
  "default": "N",
368
  "type": "checkbox",
369
  "link_info": "https://shsec.io/3r",
@@ -375,7 +388,7 @@
375
  },
376
  {
377
  "key": "enable_google_recaptcha_login",
378
- "section": "section_brute_force_login_protection",
379
  "default": "disabled",
380
  "type": "select",
381
  "value_options": [
@@ -409,7 +422,7 @@
409
  },
410
  {
411
  "key": "antibot_form_ids",
412
- "section": "section_brute_force_login_protection",
413
  "advanced": true,
414
  "premium": true,
415
  "type": "array",
@@ -473,7 +486,7 @@
473
  },
474
  {
475
  "key": "text_imahuman",
476
- "section": "section_user_messages",
477
  "sensitive": true,
478
  "premium": true,
479
  "default": "default",
@@ -486,7 +499,7 @@
486
  },
487
  {
488
  "key": "text_pleasecheckbox",
489
- "section": "section_user_messages",
490
  "sensitive": true,
491
  "premium": true,
492
  "default": "default",
2
  "slug": "login_protect",
3
  "properties": {
4
  "slug": "login_protect",
5
+ "load_priority": 100,
6
  "name": "Login Guard",
7
+ "namespace": "LoginGuard",
8
  "sidebar_name": "Login Protection",
9
  "show_module_menu_item": false,
10
  "show_module_options": true,
18
  "run_if_wpcli": false,
19
  "order": 40
20
  },
21
+ "menus": {
22
+ "config_menu_priority": 30
23
+ },
24
  "wpcli": {
25
  "enabled": true
26
  },
47
  "Recommendation - Use of this feature is highly recommend."
48
  ]
49
  },
50
+ {
51
+ "slug": "section_twofactor_auth",
52
+ "title": "Multi-Factor Authentication",
53
+ "title_short": "2-Factor Auth",
54
+ "beacon_id": 326,
55
+ "summary": [
56
+ "Purpose - Verifies the identity of users who log in to your site - i.e. they are who they say they are.",
57
+ "Recommendation - Use of this feature is highly recommend. However, if your host blocks email sending you may lock yourself out.",
58
+ "Note: You may combine multiple authentication factors for increased security."
59
+ ]
60
+ },
61
  {
62
  "slug": "section_2fa_email",
63
  "title": "Email Two-Factor Authentication",
90
  "Note: You may combine multiple authentication factors for increased security."
91
  ]
92
  },
 
 
 
 
 
 
 
 
 
 
 
93
  {
94
  "slug": "section_rename_wplogin",
95
  "title": "Hide WP Login Page",
121
  "Recommendation - Keep the Login Guard module turned on."
122
  ]
123
  },
124
+ {
125
+ "slug": "section_deprecated",
126
+ "title": "Deprecated",
127
+ "title_short": "Deprecated",
128
+ "summary": [
129
+ "These options will be removed in the future. Please use the ADE option under Bot SPAM section."
130
+ ]
131
+ },
132
  {
133
  "slug": "section_non_ui",
134
  "hidden": true
178
  },
179
  {
180
  "key": "mfa_verify_page",
181
+ "section": "section_twofactor_auth",
182
+ "default": "wp_login",
183
  "type": "select",
184
  "value_options": [
185
  {
186
+ "value_key": "wp_login",
187
+ "text": "WP Login Page"
188
  },
189
  {
190
+ "value_key": "custom_shield",
191
+ "text": "Custom Shield MFA Page"
192
  }
193
  ],
194
  "link_info": "",
200
  },
201
  {
202
  "key": "mfa_user_setup_pages",
203
+ "section": "section_twofactor_auth",
204
  "type": "multiple_select",
205
  "default": [
206
  "profile"
224
  },
225
  {
226
  "key": "mfa_skip",
227
+ "section": "section_twofactor_auth",
228
  "premium": true,
229
  "type": "integer",
230
  "default": 0,
238
  },
239
  {
240
  "key": "allow_backupcodes",
241
+ "section": "section_twofactor_auth",
242
  "premium": true,
243
  "default": "N",
244
  "type": "checkbox",
376
  },
377
  {
378
  "key": "enable_login_gasp_check",
379
+ "section": "section_deprecated",
380
  "default": "N",
381
  "type": "checkbox",
382
  "link_info": "https://shsec.io/3r",
388
  },
389
  {
390
  "key": "enable_google_recaptcha_login",
391
+ "section": "section_deprecated",
392
  "default": "disabled",
393
  "type": "select",
394
  "value_options": [
422
  },
423
  {
424
  "key": "antibot_form_ids",
425
+ "section": "section_deprecated",
426
  "advanced": true,
427
  "premium": true,
428
  "type": "array",
486
  },
487
  {
488
  "key": "text_imahuman",
489
+ "section": "section_deprecated",
490
  "sensitive": true,
491
  "premium": true,
492
  "default": "default",
499
  },
500
  {
501
  "key": "text_pleasecheckbox",
502
+ "section": "section_deprecated",
503
  "sensitive": true,
504
  "premium": true,
505
  "default": "default",
config/deprecated/plugin.php CHANGED
@@ -1,6 +1,7 @@
1
  {
2
  "properties": {
3
  "slug": "plugin",
 
4
  "name": "General Settings",
5
  "sidebar_name": "General",
6
  "menu_title": "Configuration",
@@ -17,6 +18,9 @@
17
  "run_if_wpcli": true,
18
  "order": 10
19
  },
 
 
 
20
  "wpcli": {
21
  "enabled": true
22
  },
@@ -29,10 +33,18 @@
29
  "can_dismiss": true,
30
  "type": "error"
31
  },
 
 
 
 
 
 
 
 
32
  "override-forceoff": {
33
  "id": "override-forceoff",
34
  "schedule": "conditions",
35
- "valid_admin": true,
36
  "plugin_page_only": false,
37
  "can_dismiss": false,
38
  "type": "error"
@@ -277,17 +289,6 @@
277
  "summary": "Display Plugin Security Badge On Your Site",
278
  "description": "Enabling this option helps support the plugin by spreading the word about it on your website. The plugin badge also demonstrates to visitors that you take your website security seriously."
279
  },
280
- {
281
- "key": "enable_xmlrpc_compatibility",
282
- "section": "section_defaults",
283
- "default": "N",
284
- "type": "checkbox",
285
- "link_info": "",
286
- "link_blog": "",
287
- "name": "XML-RPC Compatibility",
288
- "summary": "Allow Login Through XML-RPC To By-Pass Login Guard Rules",
289
- "description": "Enable this if you need XML-RPC functionality e.g. if you use the WordPress iPhone/Android App."
290
- },
291
  {
292
  "key": "importexport_enable",
293
  "section": "section_importexport",
@@ -502,13 +503,6 @@
502
  "type": "integer",
503
  "default": 0
504
  },
505
- {
506
- "key": "last_ip_detect_source",
507
- "transferable": false,
508
- "section": "section_non_ui",
509
- "type": "text",
510
- "default": ""
511
- },
512
  {
513
  "key": "openssl_private_key",
514
  "transferable": false,
@@ -538,10 +532,17 @@
538
  "section": "section_non_ui",
539
  "type": "array",
540
  "default": []
 
 
 
 
 
 
 
541
  }
542
  ],
543
  "definitions": {
544
- "rest_api": {
545
  "publish": true,
546
  "pro_only": true,
547
  "route_defs": {
@@ -557,11 +558,11 @@
557
  }
558
  }
559
  },
560
- "href_privacy_policy": "https://shsec.io/wpshieldprivacypolicy",
561
- "db_classes": {
562
  "notes": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\AdminNotes\\Handler"
563
  },
564
- "db_table_notes": {
565
  "slug": "notes",
566
  "has_updated_at": true,
567
  "cols_custom": {
@@ -569,92 +570,7 @@
569
  "note": "TEXT"
570
  }
571
  },
572
- "active_plugin_features": [
573
- {
574
- "slug": "data",
575
- "load_priority": 1
576
- },
577
- {
578
- "slug": "insights",
579
- "load_priority": 2,
580
- "menu_priority": 5
581
- },
582
- {
583
- "slug": "admin_access_restriction",
584
- "namespace": "SecurityAdmin",
585
- "load_priority": 11
586
- },
587
- {
588
- "slug": "ips",
589
- "load_priority": 15,
590
- "namespace": "IPs"
591
- },
592
- {
593
- "slug": "audit_trail",
594
- "load_priority": 11,
595
- "hidden": false
596
- },
597
- {
598
- "slug": "hack_protect",
599
- "namespace": "HackGuard"
600
- },
601
- {
602
- "slug": "traffic",
603
- "load_priority": 12
604
- },
605
- {
606
- "slug": "firewall",
607
- "load_priority": 1000
608
- },
609
- {
610
- "slug": "login_protect",
611
- "storage_key": "loginprotect",
612
- "namespace": "LoginGuard"
613
- },
614
- {
615
- "slug": "user_management"
616
- },
617
- {
618
- "slug": "comments_filter",
619
- "storage_key": "commentsfilter"
620
- },
621
- {
622
- "slug": "events",
623
- "load_priority": 11
624
- },
625
- {
626
- "slug": "reporting",
627
- "load_priority": 12
628
- },
629
- {
630
- "slug": "sessions",
631
- "load_priority": 5
632
- },
633
- {
634
- "slug": "integrations",
635
- "load_priority": 20
636
- },
637
- {
638
- "slug": "license",
639
- "load_priority": 10
640
- },
641
- {
642
- "slug": "autoupdates"
643
- },
644
- {
645
- "slug": "headers"
646
- },
647
- {
648
- "slug": "lockdown"
649
- },
650
- {
651
- "slug": "comms"
652
- },
653
- {
654
- "slug": "email"
655
- }
656
- ],
657
- "events": {
658
  "debug_log": {
659
  "audit_params": [
660
  "message"
@@ -748,7 +664,7 @@
748
  "stat": false
749
  }
750
  },
751
- "wizards": {
752
  "welcome": {
753
  "title": "Getting Started Setup Wizard",
754
  "desc": "An introduction to this security plugin, helping you get setup and started quickly with the core features.",
1
  {
2
  "properties": {
3
  "slug": "plugin",
4
+ "load_priority": 0,
5
  "name": "General Settings",
6
  "sidebar_name": "General",
7
  "menu_title": "Configuration",
18
  "run_if_wpcli": true,
19
  "order": 10
20
  },
21
+ "menus": {
22
+ "config_menu_priority": 10
23
+ },
24
  "wpcli": {
25
  "enabled": true
26
  },
33
  "can_dismiss": true,
34
  "type": "error"
35
  },
36
+ "rules-not-running": {
37
+ "id": "rules-not-running",
38
+ "schedule": "conditions",
39
+ "valid_admin": false,
40
+ "plugin_page_only": true,
41
+ "can_dismiss": false,
42
+ "type": "error"
43
+ },
44
  "override-forceoff": {
45
  "id": "override-forceoff",
46
  "schedule": "conditions",
47
+ "valid_admin": false,
48
  "plugin_page_only": false,
49
  "can_dismiss": false,
50
  "type": "error"
289
  "summary": "Display Plugin Security Badge On Your Site",
290
  "description": "Enabling this option helps support the plugin by spreading the word about it on your website. The plugin badge also demonstrates to visitors that you take your website security seriously."
291
  },
 
 
 
 
 
 
 
 
 
 
 
292
  {
293
  "key": "importexport_enable",
294
  "section": "section_importexport",
503
  "type": "integer",
504
  "default": 0
505
  },
 
 
 
 
 
 
 
506
  {
507
  "key": "openssl_private_key",
508
  "transferable": false,
532
  "section": "section_non_ui",
533
  "type": "array",
534
  "default": []
535
+ },
536
+ {
537
+ "key": "ipdetect_at",
538
+ "transferable": false,
539
+ "section": "section_non_ui",
540
+ "type": "integer",
541
+ "default": 0
542
  }
543
  ],
544
  "definitions": {
545
+ "rest_api": {
546
  "publish": true,
547
  "pro_only": true,
548
  "route_defs": {
558
  }
559
  }
560
  },
561
+ "href_privacy_policy": "https://shsec.io/wpshieldprivacypolicy",
562
+ "db_classes": {
563
  "notes": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\AdminNotes\\Handler"
564
  },
565
+ "db_table_notes": {
566
  "slug": "notes",
567
  "has_updated_at": true,
568
  "cols_custom": {
570
  "note": "TEXT"
571
  }
572
  },
573
+ "events": {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
574
  "debug_log": {
575
  "audit_params": [
576
  "message"
664
  "stat": false
665
  }
666
  },
667
+ "wizards": {
668
  "welcome": {
669
  "title": "Getting Started Setup Wizard",
670
  "desc": "An introduction to this security plugin, helping you get setup and started quickly with the core features.",
config/deprecated/reporting.php CHANGED
@@ -1,6 +1,7 @@
1
  {
2
  "properties": {
3
  "slug": "reporting",
 
4
  "name": "Reporting",
5
  "storage_key": "reporting",
6
  "tagline": "Shield Reporting",
@@ -18,19 +19,8 @@
18
  "enabled": true
19
  },
20
  "menu_items": [
21
- {
22
- "title": "Stats (beta)",
23
- "slug": "stats-redirect"
24
- }
25
  ],
26
  "custom_redirects": [
27
- {
28
- "source_mod_page": "stats-redirect",
29
- "target_mod_page": "insights",
30
- "query_args": {
31
- "inav": "reports"
32
- }
33
- }
34
  ],
35
  "sections": [
36
  {
1
  {
2
  "properties": {
3
  "slug": "reporting",
4
+ "load_priority": 12,
5
  "name": "Reporting",
6
  "storage_key": "reporting",
7
  "tagline": "Shield Reporting",
19
  "enabled": true
20
  },
21
  "menu_items": [
 
 
 
 
22
  ],
23
  "custom_redirects": [
 
 
 
 
 
 
 
24
  ],
25
  "sections": [
26
  {
config/deprecated/sessions.php CHANGED
@@ -1,6 +1,7 @@
1
  {
2
  "properties": {
3
  "slug": "sessions",
 
4
  "name": "Sessions",
5
  "show_module_menu_item": false,
6
  "storage_key": "sessions",
1
  {
2
  "properties": {
3
  "slug": "sessions",
4
+ "load_priority": 5,
5
  "name": "Sessions",
6
  "show_module_menu_item": false,
7
  "storage_key": "sessions",
config/deprecated/traffic.php CHANGED
@@ -2,6 +2,8 @@
2
  "slug": "traffic",
3
  "properties": {
4
  "slug": "traffic",
 
 
5
  "name": "Traffic Watch",
6
  "sidebar_name": "Traffic",
7
  "show_module_menu_item": false,
@@ -16,6 +18,9 @@
16
  "run_if_wpcli": false,
17
  "order": 110
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
@@ -110,18 +115,6 @@
110
  "uptime"
111
  ],
112
  "value_options": [
113
- {
114
- "value_key": "simple",
115
- "text": "Simple Requests"
116
- },
117
- {
118
- "value_key": "api",
119
- "text": "REST API"
120
- },
121
- {
122
- "value_key": "ajax",
123
- "text": "AJAX"
124
- },
125
  {
126
  "value_key": "logged_in",
127
  "text": "Logged-In Users"
2
  "slug": "traffic",
3
  "properties": {
4
  "slug": "traffic",
5
+ "load_priority": 12,
6
+ "menu_priority": 50,
7
  "name": "Traffic Watch",
8
  "sidebar_name": "Traffic",
9
  "show_module_menu_item": false,
18
  "run_if_wpcli": false,
19
  "order": 110
20
  },
21
+ "menus": {
22
+ "config_menu_priority": 35
23
+ },
24
  "wpcli": {
25
  "enabled": true
26
  },
115
  "uptime"
116
  ],
117
  "value_options": [
 
 
 
 
 
 
 
 
 
 
 
 
118
  {
119
  "value_key": "logged_in",
120
  "text": "Logged-In Users"
config/deprecated/user_management.php CHANGED
@@ -2,6 +2,7 @@
2
  "slug": "user_management",
3
  "properties": {
4
  "slug": "user_management",
 
5
  "name": "User Management",
6
  "sidebar_name": "Users",
7
  "show_module_menu_item": false,
@@ -16,6 +17,9 @@
16
  "run_if_wpcli": false,
17
  "order": 40
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
2
  "slug": "user_management",
3
  "properties": {
4
  "slug": "user_management",
5
+ "load_priority": 100,
6
  "name": "User Management",
7
  "sidebar_name": "Users",
8
  "show_module_menu_item": false,
17
  "run_if_wpcli": false,
18
  "order": 40
19
  },
20
+ "menus": {
21
+ "config_menu_priority": 31
22
+ },
23
  "wpcli": {
24
  "enabled": true
25
  },
config/email.json CHANGED
@@ -2,6 +2,7 @@
2
  "slug": "email",
3
  "properties": {
4
  "slug": "email",
 
5
  "name": "Email",
6
  "show_module_menu_item": false,
7
  "auto_enabled": true,
2
  "slug": "email",
3
  "properties": {
4
  "slug": "email",
5
+ "load_priority": 100,
6
  "name": "Email",
7
  "show_module_menu_item": false,
8
  "auto_enabled": true,
config/events.json CHANGED
@@ -1,6 +1,7 @@
1
  {
2
  "properties": {
3
  "slug": "events",
 
4
  "name": "Events",
5
  "show_module_menu_item": false,
6
  "storage_key": "events",
1
  {
2
  "properties": {
3
  "slug": "events",
4
+ "load_priority": 11,
5
  "name": "Events",
6
  "show_module_menu_item": false,
7
  "storage_key": "events",
config/firewall.json CHANGED
@@ -2,6 +2,7 @@
2
  "slug": "firewall",
3
  "properties": {
4
  "slug": "firewall",
 
5
  "name": "Firewall",
6
  "sidebar_name": "Firewall",
7
  "show_module_menu_item": false,
@@ -16,6 +17,9 @@
16
  "run_if_wpcli": false,
17
  "order": 30
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
@@ -247,88 +251,90 @@
247
  "name": "Ignore Administrators",
248
  "summary": "Ignore Administrators",
249
  "description": "Authenticated administrator users will not be processed by the firewall rules."
250
- },
251
- {
252
- "key": "text_firewalldie",
253
- "section": "section_user_messages",
254
- "sensitive": true,
255
- "premium": true,
256
- "default": "default",
257
- "type": "text",
258
- "beacon_id": 139,
259
- "link_info": "",
260
- "link_blog": "",
261
- "name": "Firewall Block Message",
262
- "summary": "Message Displayed To Visitor When A Firewall Block Is Triggered",
263
- "description": "When you select the option to display a message to the visitor, this is the message that is displayed."
264
  }
265
  ],
266
  "definitions": {
267
  "default_whitelist": {
268
  "/wp-admin/options-general.php": [],
269
- "/wp-admin/options.php": [
270
- "home",
271
- "siteurl"
272
- ],
273
- "/wp-admin/plugins.php": [
274
- "plugin"
275
- ],
 
 
 
 
276
  "/wp-admin/post-new.php": [],
277
  "/wp-admin/page-new.php": [],
278
  "/wp-admin/link-add.php": [],
279
  "/wp-admin/media-upload.php": [],
280
- "/wp-admin/admin.php": [
281
- "page"
282
- ],
283
- "/wp-admin/post.php": [
284
- "content"
285
- ],
286
- "/wp-admin/plugin-editor.php": [
287
- "newcontent"
288
- ],
 
 
 
 
 
 
289
  "/wp-admin/page.php": [],
290
  "/wp-admin/admin-ajax.php": [],
291
- "/wp-comments-post.php": [
292
- "url",
293
- "comment"
294
- ],
295
- "*": [
296
- "affwp_action",
297
- "ajaxurl",
298
- "g-recaptcha-response",
299
- "verify_sign",
300
- "txn_id",
301
- "wp_http_referer",
302
- "_wp_http_referer",
303
- "_wp_original_http_referer",
304
- "JCS_INENREF",
305
- "pass1",
306
- "pass1-text",
307
- "pwd",
308
- "url",
309
- "referredby",
310
- "redirect_to",
311
- "jetpack_sso_original_request",
312
- "jetpack_sso_redirect_to",
313
- "/^wordpress_logged_in_[0-9a-f]+$/",
314
- "edd_action",
315
- "edd_redirect",
316
- "wpcf7-form",
317
- "yoast_wpseo_metadesc",
318
- "icwp_wpsf_new_u2f_response",
319
- "icwp_wpsf_u2f_otp",
320
- "shield_action",
321
- "appId",
322
- "/^et_.*/",
323
- "ping_sites",
324
- "aioseo-post-settings",
325
- "joe-chnlcustid",
326
- "spd-custhash",
327
- "joe-custinfo"
328
- ]
 
 
 
 
 
 
329
  },
330
  "firewall_patterns": {
331
- "dirtraversal": {
332
  "simple": [
333
  "etc/passwd",
334
  "proc/self/environ",
@@ -340,36 +346,35 @@
340
  "loopback"
341
  ]
342
  },
343
- "wpterms": {
344
  "simple": [
345
  "/**/",
346
  "wp-config.php"
347
  ],
348
  "regex": [
349
- "^wp_",
350
  "^user_login",
351
  "^user_pass"
352
  ]
353
  },
354
- "fieldtruncation": {
355
  "regex": [
356
  "\\s{49,}",
357
  "\\x00"
358
  ]
359
  },
360
- "sqlqueries": {
361
  "regex": [
362
  "concat\\s*\\(",
363
  "group_concat",
364
  "union.*select"
365
  ]
366
  },
367
- "exefile": {
368
  "regex": [
369
  "\\.(dll|rb|py|exe|php[3-6]?|pl|perl|ph[34]|phl|phtml|phtm|sql|ini|jsp|asp|git|svn|tar)$"
370
  ]
371
  },
372
- "schema": {
373
  "simple": [
374
  ".shtml"
375
  ],
@@ -377,13 +382,13 @@
377
  "^(http|https|ftp|file):"
378
  ]
379
  },
380
- "phpcode": {
381
  "simple": null,
382
  "regex": [
383
  "(include|include_once|require|require_once)\\s*\\(.*\\)"
384
  ]
385
  },
386
- "aggressive": {
387
  "simple": [
388
  "eval(",
389
  "(null)",
@@ -407,13 +412,6 @@
407
  }
408
  },
409
  "events": {
410
- "check_skip": {
411
- "audit_params": [
412
- "path"
413
- ],
414
- "level": "debug",
415
- "stat": false
416
- },
417
  "firewall_block": {
418
  "audit_params": [
419
  "name",
2
  "slug": "firewall",
3
  "properties": {
4
  "slug": "firewall",
5
+ "load_priority": 1000,
6
  "name": "Firewall",
7
  "sidebar_name": "Firewall",
8
  "show_module_menu_item": false,
17
  "run_if_wpcli": false,
18
  "order": 30
19
  },
20
+ "menus": {
21
+ "config_menu_priority": 44
22
+ },
23
  "wpcli": {
24
  "enabled": true
25
  },
251
  "name": "Ignore Administrators",
252
  "summary": "Ignore Administrators",
253
  "description": "Authenticated administrator users will not be processed by the firewall rules."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  }
255
  ],
256
  "definitions": {
257
  "default_whitelist": {
258
  "/wp-admin/options-general.php": [],
259
+ "/wp-admin/options.php": {
260
+ "simple": [
261
+ "home",
262
+ "siteurl"
263
+ ]
264
+ },
265
+ "/wp-admin/plugins.php": {
266
+ "simple": [
267
+ "plugin"
268
+ ]
269
+ },
270
  "/wp-admin/post-new.php": [],
271
  "/wp-admin/page-new.php": [],
272
  "/wp-admin/link-add.php": [],
273
  "/wp-admin/media-upload.php": [],
274
+ "/wp-admin/admin.php": {
275
+ "simple": [
276
+ "page"
277
+ ]
278
+ },
279
+ "/wp-admin/post.php": {
280
+ "simple": [
281
+ "content"
282
+ ]
283
+ },
284
+ "/wp-admin/plugin-editor.php": {
285
+ "simple": [
286
+ "newcontent"
287
+ ]
288
+ },
289
  "/wp-admin/page.php": [],
290
  "/wp-admin/admin-ajax.php": [],
291
+ "/wp-comments-post.php": {
292
+ "simple": [
293
+ "url",
294
+ "comment"
295
+ ]
296
+ },
297
+ "*": {
298
+ "regex": [
299
+ "^wordpress_logged_in_[0-9a-f]+$",
300
+ "^et_.*"
301
+ ],
302
+ "simple": [
303
+ "affwp_action",
304
+ "ajaxurl",
305
+ "g-recaptcha-response",
306
+ "verify_sign",
307
+ "txn_id",
308
+ "wp_http_referer",
309
+ "_wp_http_referer",
310
+ "_wp_original_http_referer",
311
+ "JCS_INENREF",
312
+ "pass1",
313
+ "pass1-text",
314
+ "pwd",
315
+ "url",
316
+ "referredby",
317
+ "redirect_to",
318
+ "jetpack_sso_original_request",
319
+ "jetpack_sso_redirect_to",
320
+ "edd_action",
321
+ "edd_redirect",
322
+ "wpcf7-form",
323
+ "yoast_wpseo_metadesc",
324
+ "icwp_wpsf_new_u2f_response",
325
+ "icwp_wpsf_u2f_otp",
326
+ "shield_action",
327
+ "appId",
328
+ "ping_sites",
329
+ "aioseo-post-settings",
330
+ "joe-chnlcustid",
331
+ "spd-custhash",
332
+ "joe-custinfo"
333
+ ]
334
+ }
335
  },
336
  "firewall_patterns": {
337
+ "dir_traversal": {
338
  "simple": [
339
  "etc/passwd",
340
  "proc/self/environ",
346
  "loopback"
347
  ]
348
  },
349
+ "wordpress_terms": {
350
  "simple": [
351
  "/**/",
352
  "wp-config.php"
353
  ],
354
  "regex": [
 
355
  "^user_login",
356
  "^user_pass"
357
  ]
358
  },
359
+ "field_truncation": {
360
  "regex": [
361
  "\\s{49,}",
362
  "\\x00"
363
  ]
364
  },
365
+ "sql_queries": {
366
  "regex": [
367
  "concat\\s*\\(",
368
  "group_concat",
369
  "union.*select"
370
  ]
371
  },
372
+ "exe_file_uploads": {
373
  "regex": [
374
  "\\.(dll|rb|py|exe|php[3-6]?|pl|perl|ph[34]|phl|phtml|phtm|sql|ini|jsp|asp|git|svn|tar)$"
375
  ]
376
  },
377
+ "leading_schema": {
378
  "simple": [
379
  ".shtml"
380
  ],
382
  "^(http|https|ftp|file):"
383
  ]
384
  },
385
+ "php_code": {
386
  "simple": null,
387
  "regex": [
388
  "(include|include_once|require|require_once)\\s*\\(.*\\)"
389
  ]
390
  },
391
+ "aggressive": {
392
  "simple": [
393
  "eval(",
394
  "(null)",
412
  }
413
  },
414
  "events": {
 
 
 
 
 
 
 
415
  "firewall_block": {
416
  "audit_params": [
417
  "name",
config/hack_protect.json CHANGED
@@ -2,7 +2,10 @@
2
  "slug": "hack_protect",
3
  "properties": {
4
  "slug": "hack_protect",
 
 
5
  "name": "Hack Guard",
 
6
  "sidebar_name": "Scanners",
7
  "show_module_menu_item": false,
8
  "show_module_options": true,
@@ -16,6 +19,9 @@
16
  "run_if_verified_bot": true,
17
  "run_if_wpcli": true
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true,
21
  "root": "hack_guard"
@@ -57,16 +63,6 @@
57
  "Recommendation - Ensure this is turned on and you will always know if any of your assets have known security vulnerabilities."
58
  ]
59
  },
60
- {
61
- "slug": "section_realtime",
62
- "title": "Realtime Change Detection",
63
- "title_short": "Realtime Change Detection",
64
- "beacon_id": 226,
65
- "summary": [
66
- "Purpose - Monitor Your WordPress Site For Changes To Critical Components In Realtime.",
67
- "Recommendation - Keep The Realtime Change Detection Active."
68
- ]
69
- },
70
  {
71
  "slug": "section_scan_options",
72
  "title": "Scan Options",
@@ -147,17 +143,35 @@
147
  "description": "When a file is modified, or malware is detected, Shield can try to repair files."
148
  },
149
  {
150
- "key": "auto_filter_results",
151
- "section": "section_file_guard",
152
- "premium": false,
153
- "type": "checkbox",
154
- "default": "Y",
155
- "link_info": "",
156
- "link_blog": "",
157
- "beacon_id": 439,
158
- "name": "Auto-Filter Results",
159
- "summary": "Automatically Filter Results Of Irrelevant Items",
160
- "description": "Automatically remove items from results that are irrelevant."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  },
162
  {
163
  "key": "scan_path_exclusions",
@@ -221,37 +235,6 @@
221
  "summary": "Apply Updates Automatically To Vulnerable Plugins",
222
  "description": "When an update becomes available, automatically apply updates to items with known vulnerabilities."
223
  },
224
- {
225
- "key": "file_locker",
226
- "section": "section_realtime",
227
- "premium": true,
228
- "type": "multiple_select",
229
- "default": [],
230
- "value_options": [
231
- {
232
- "value_key": "wpconfig",
233
- "text": "WP Config"
234
- },
235
- {
236
- "value_key": "root_htaccess",
237
- "text": "Root .htaccess"
238
- },
239
- {
240
- "value_key": "root_index",
241
- "text": "Root index.php"
242
- },
243
- {
244
- "value_key": "root_webconfig",
245
- "text": "Root Web.Config"
246
- }
247
- ],
248
- "link_info": "https://shsec.io/h7",
249
- "link_blog": "https://shsec.io/h8",
250
- "beacon_id": 226,
251
- "name": "File Locker",
252
- "summary": "Lock Files Against Tampering and Changes",
253
- "description": "As soon as changes are detected to any selected files, the contents may be examined and reverted."
254
- },
255
  {
256
  "key": "scan_frequency",
257
  "section": "section_scan_options",
2
  "slug": "hack_protect",
3
  "properties": {
4
  "slug": "hack_protect",
5
+ "load_priority": 100,
6
+ "menu_priority": 20,
7
  "name": "Hack Guard",
8
+ "namespace": "HackGuard",
9
  "sidebar_name": "Scanners",
10
  "show_module_menu_item": false,
11
  "show_module_options": true,
19
  "run_if_verified_bot": true,
20
  "run_if_wpcli": true
21
  },
22
+ "menus": {
23
+ "config_menu_priority": 25
24
+ },
25
  "wpcli": {
26
  "enabled": true,
27
  "root": "hack_guard"
63
  "Recommendation - Ensure this is turned on and you will always know if any of your assets have known security vulnerabilities."
64
  ]
65
  },
 
 
 
 
 
 
 
 
 
 
66
  {
67
  "slug": "section_scan_options",
68
  "title": "Scan Options",
143
  "description": "When a file is modified, or malware is detected, Shield can try to repair files."
144
  },
145
  {
146
+ "key": "file_locker",
147
+ "section": "section_file_guard",
148
+ "premium": true,
149
+ "type": "multiple_select",
150
+ "default": [],
151
+ "value_options": [
152
+ {
153
+ "value_key": "wpconfig",
154
+ "text": "WP Config"
155
+ },
156
+ {
157
+ "value_key": "root_htaccess",
158
+ "text": "Root .htaccess"
159
+ },
160
+ {
161
+ "value_key": "root_index",
162
+ "text": "Root index.php"
163
+ },
164
+ {
165
+ "value_key": "root_webconfig",
166
+ "text": "Root Web.Config"
167
+ }
168
+ ],
169
+ "link_info": "https://shsec.io/h7",
170
+ "link_blog": "https://shsec.io/h8",
171
+ "beacon_id": 226,
172
+ "name": "File Locker",
173
+ "summary": "Lock Files Against Tampering and Changes",
174
+ "description": "As soon as changes are detected to any selected files, the contents may be examined and reverted."
175
  },
176
  {
177
  "key": "scan_path_exclusions",
235
  "summary": "Apply Updates Automatically To Vulnerable Plugins",
236
  "description": "When an update becomes available, automatically apply updates to items with known vulnerabilities."
237
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  {
239
  "key": "scan_frequency",
240
  "section": "section_scan_options",
config/headers.json CHANGED
@@ -2,6 +2,7 @@
2
  "slug": "headers",
3
  "properties": {
4
  "slug": "headers",
 
5
  "name": "HTTP Headers",
6
  "sidebar_name": "HTTP Headers",
7
  "show_module_menu_item": false,
@@ -16,6 +17,9 @@
16
  "run_if_wpcli": false,
17
  "order": 80
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
2
  "slug": "headers",
3
  "properties": {
4
  "slug": "headers",
5
+ "load_priority": 100,
6
  "name": "HTTP Headers",
7
  "sidebar_name": "HTTP Headers",
8
  "show_module_menu_item": false,
17
  "run_if_wpcli": false,
18
  "order": 80
19
  },
20
+ "menus": {
21
+ "config_menu_priority": 60
22
+ },
23
  "wpcli": {
24
  "enabled": true
25
  },
config/insights.json CHANGED
@@ -2,9 +2,10 @@
2
  "slug": "insights",
3
  "properties": {
4
  "slug": "insights",
 
 
5
  "name": "Dashboard",
6
  "menu_title": "Security Dashboard",
7
- "menu_priority": "5",
8
  "show_module_menu_item": true,
9
  "show_module_options": false,
10
  "auto_enabled": true,
2
  "slug": "insights",
3
  "properties": {
4
  "slug": "insights",
5
+ "load_priority": 2,
6
+ "menu_priority": 5,
7
  "name": "Dashboard",
8
  "menu_title": "Security Dashboard",
 
9
  "show_module_menu_item": true,
10
  "show_module_options": false,
11
  "auto_enabled": true,
config/integrations.json CHANGED
@@ -3,6 +3,7 @@
3
  "properties": {
4
  "slug": "integrations",
5
  "storage_key": "integrations",
 
6
  "name": "Integrations",
7
  "menu_title": "Integrations",
8
  "sidebar_name": "Integrations",
@@ -18,6 +19,9 @@
18
  "skip_processor": false,
19
  "tracking_exclude": false
20
  },
 
 
 
21
  "wpcli": {
22
  "enabled": true
23
  },
3
  "properties": {
4
  "slug": "integrations",
5
  "storage_key": "integrations",
6
+ "load_priority": 20,
7
  "name": "Integrations",
8
  "menu_title": "Integrations",
9
  "sidebar_name": "Integrations",
19
  "skip_processor": false,
20
  "tracking_exclude": false
21
  },
22
+ "menus": {
23
+ "config_menu_priority": 50
24
+ },
25
  "wpcli": {
26
  "enabled": true
27
  },
config/ips.json CHANGED
@@ -2,7 +2,10 @@
2
  "slug": "ips",
3
  "properties": {
4
  "slug": "ips",
 
 
5
  "name": "Block Bad IPs/Visitors",
 
6
  "sidebar_name": "IP Blocking",
7
  "show_module_menu_item": false,
8
  "show_module_options": true,
@@ -16,6 +19,9 @@
16
  "run_if_wpcli": false,
17
  "order": 100
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
@@ -116,6 +122,7 @@
116
  "slug": "section_enable_plugin_feature_ips",
117
  "title": "Enable Module: IP Manager",
118
  "title_short": "Disable Module",
 
119
  "summary": [
120
  "Purpose - The IP Manager allows you to whitelist, blacklist and configure auto-blacklist rules.",
121
  "Recommendation - Keep the IP Manager feature turned on. You should also carefully review the automatic black list settings."
@@ -185,6 +192,7 @@
185
  "section": "section_auto_black_list",
186
  "type": "integer",
187
  "default": 10,
 
188
  "link_info": "https://shsec.io/wpsf24",
189
  "link_blog": "https://shsec.io/wpsf26",
190
  "beacon_id": 207,
@@ -344,7 +352,7 @@
344
  ],
345
  "link_info": "https://shsec.io/fo",
346
  "link_blog": "https://shsec.io/f6",
347
- "beacon_id": 123,
348
  "name": "Link Cheese",
349
  "summary": "Tempt A Bot With A Fake Link To Follow",
350
  "description": "Detect A Bot That Follows A 'no-follow' Link."
@@ -518,7 +526,7 @@
518
  ],
519
  "link_info": "https://shsec.io/f5",
520
  "link_blog": "https://shsec.io/f7",
521
- "beacon_id": 206,
522
  "name": "Fake Web Crawler",
523
  "summary": "Detect Fake Search Engine Crawlers",
524
  "description": "Identify a Bot when it presents as an official web crawler, but analysis shows it's fake."
@@ -558,20 +566,6 @@
558
  "summary": "Detect Requests With Empty User Agents",
559
  "description": "Identify a request as a bot if the user agent is not provided."
560
  },
561
- {
562
- "key": "text_remainingtrans",
563
- "section": "section_user_messages",
564
- "sensitive": true,
565
- "premium": true,
566
- "default": "default",
567
- "type": "text",
568
- "link_info": "https://shsec.io/e9",
569
- "link_blog": "",
570
- "beacon_id": 139,
571
- "name": "Remaining Offenses",
572
- "summary": "Visitor Triggers The IP Offenses System Through A Firewall Block",
573
- "description": "This message is displayed if the visitor triggered the IP Offenses system and reports how many offenses remain before being blocked."
574
- },
575
  {
576
  "key": "autounblock_ips",
577
  "section": "section_non_ui",
@@ -673,6 +667,10 @@
673
  "macro_type": "timestamp",
674
  "comment": "BotTrack InvalidScript"
675
  },
 
 
 
 
676
  "cooldown_at": {
677
  "macro_type": "timestamp",
678
  "comment": "Cooldown Triggered"
2
  "slug": "ips",
3
  "properties": {
4
  "slug": "ips",
5
+ "load_priority": 15,
6
+ "menu_priority": 30,
7
  "name": "Block Bad IPs/Visitors",
8
+ "namespace": "IPs",
9
  "sidebar_name": "IP Blocking",
10
  "show_module_menu_item": false,
11
  "show_module_options": true,
19
  "run_if_wpcli": false,
20
  "order": 100
21
  },
22
+ "menus": {
23
+ "config_menu_priority": 20
24
+ },
25
  "wpcli": {
26
  "enabled": true
27
  },
122
  "slug": "section_enable_plugin_feature_ips",
123
  "title": "Enable Module: IP Manager",
124
  "title_short": "Disable Module",
125
+ "beacon_id": 208,
126
  "summary": [
127
  "Purpose - The IP Manager allows you to whitelist, blacklist and configure auto-blacklist rules.",
128
  "Recommendation - Keep the IP Manager feature turned on. You should also carefully review the automatic black list settings."
192
  "section": "section_auto_black_list",
193
  "type": "integer",
194
  "default": 10,
195
+ "min": 0,
196
  "link_info": "https://shsec.io/wpsf24",
197
  "link_blog": "https://shsec.io/wpsf26",
198
  "beacon_id": 207,
352
  ],
353
  "link_info": "https://shsec.io/fo",
354
  "link_blog": "https://shsec.io/f6",
355
+ "beacon_id": 206,
356
  "name": "Link Cheese",
357
  "summary": "Tempt A Bot With A Fake Link To Follow",
358
  "description": "Detect A Bot That Follows A 'no-follow' Link."
526
  ],
527
  "link_info": "https://shsec.io/f5",
528
  "link_blog": "https://shsec.io/f7",
529
+ "beacon_id": 124,
530
  "name": "Fake Web Crawler",
531
  "summary": "Detect Fake Search Engine Crawlers",
532
  "description": "Identify a Bot when it presents as an official web crawler, but analysis shows it's fake."
566
  "summary": "Detect Requests With Empty User Agents",
567
  "description": "Identify a request as a bot if the user agent is not provided."
568
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
569
  {
570
  "key": "autounblock_ips",
571
  "section": "section_non_ui",
667
  "macro_type": "timestamp",
668
  "comment": "BotTrack InvalidScript"
669
  },
670
+ "btauthorfishing_at": {
671
+ "macro_type": "timestamp",
672
+ "comment": "BotTrack Author Username Fishing"
673
+ },
674
  "cooldown_at": {
675
  "macro_type": "timestamp",
676
  "comment": "Cooldown Triggered"
config/license.json CHANGED
@@ -2,6 +2,7 @@
2
  "slug": "license",
3
  "properties": {
4
  "slug": "license",
 
5
  "name": "Pro Security",
6
  "menu_title": "",
7
  "show_module_menu_item": false,
2
  "slug": "license",
3
  "properties": {
4
  "slug": "license",
5
+ "load_priority": 10,
6
  "name": "Pro Security",
7
  "menu_title": "",
8
  "show_module_menu_item": false,
config/lockdown.json CHANGED
@@ -2,6 +2,7 @@
2
  "slug": "lockdown",
3
  "properties": {
4
  "slug": "lockdown",
 
5
  "name": "WP Lockdown",
6
  "sidebar_name": "Lockdown",
7
  "show_module_menu_item": false,
@@ -16,6 +17,9 @@
16
  "run_if_wpcli": false,
17
  "order": 90
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
@@ -181,7 +185,7 @@
181
  }
182
  ],
183
  "definitions": {
184
- "events": {
185
  "block_anonymous_restapi": {
186
  "audit_params": [
187
  "namespace"
@@ -189,6 +193,10 @@
189
  "level": "warning",
190
  "recent": true
191
  },
 
 
 
 
192
  "block_xml": {
193
  "level": "notice",
194
  "recent": true,
2
  "slug": "lockdown",
3
  "properties": {
4
  "slug": "lockdown",
5
+ "load_priority": 100,
6
  "name": "WP Lockdown",
7
  "sidebar_name": "Lockdown",
8
  "show_module_menu_item": false,
17
  "run_if_wpcli": false,
18
  "order": 90
19
  },
20
+ "menus": {
21
+ "config_menu_priority": 45
22
+ },
23
  "wpcli": {
24
  "enabled": true
25
  },
185
  }
186
  ],
187
  "definitions": {
188
+ "events": {
189
  "block_anonymous_restapi": {
190
  "audit_params": [
191
  "namespace"
193
  "level": "warning",
194
  "recent": true
195
  },
196
+ "block_author_fishing": {
197
+ "level": "notice",
198
+ "offense": true
199
+ },
200
  "block_xml": {
201
  "level": "notice",
202
  "recent": true,
config/login_protect.json CHANGED
@@ -2,7 +2,9 @@
2
  "slug": "login_protect",
3
  "properties": {
4
  "slug": "login_protect",
 
5
  "name": "Login Guard",
 
6
  "sidebar_name": "Login Protection",
7
  "show_module_menu_item": false,
8
  "show_module_options": true,
@@ -16,6 +18,9 @@
16
  "run_if_wpcli": false,
17
  "order": 40
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
@@ -42,6 +47,17 @@
42
  "Recommendation - Use of this feature is highly recommend."
43
  ]
44
  },
 
 
 
 
 
 
 
 
 
 
 
45
  {
46
  "slug": "section_2fa_email",
47
  "title": "Email Two-Factor Authentication",
@@ -74,17 +90,6 @@
74
  "Note: You may combine multiple authentication factors for increased security."
75
  ]
76
  },
77
- {
78
- "slug": "section_multifactor_authentication",
79
- "title": "Multi-Factor Authentication",
80
- "title_short": "2-Factor Auth",
81
- "beacon_id": 326,
82
- "summary": [
83
- "Purpose - Verifies the identity of users who log in to your site - i.e. they are who they say they are.",
84
- "Recommendation - Use of this feature is highly recommend. However, if your host blocks email sending you may lock yourself out.",
85
- "Note: You may combine multiple authentication factors for increased security."
86
- ]
87
- },
88
  {
89
  "slug": "section_rename_wplogin",
90
  "title": "Hide WP Login Page",
@@ -116,6 +121,14 @@
116
  "Recommendation - Keep the Login Guard module turned on."
117
  ]
118
  },
 
 
 
 
 
 
 
 
119
  {
120
  "slug": "section_non_ui",
121
  "hidden": true
@@ -165,17 +178,17 @@
165
  },
166
  {
167
  "key": "mfa_verify_page",
168
- "section": "section_multifactor_authentication",
169
- "default": "custom_shield",
170
  "type": "select",
171
  "value_options": [
172
  {
173
- "value_key": "custom_shield",
174
- "text": "Custom Shield MFA Page"
175
  },
176
  {
177
- "value_key": "wp_login",
178
- "text": "WP Login Page (beta)"
179
  }
180
  ],
181
  "link_info": "",
@@ -187,7 +200,7 @@
187
  },
188
  {
189
  "key": "mfa_user_setup_pages",
190
- "section": "section_multifactor_authentication",
191
  "type": "multiple_select",
192
  "default": [
193
  "profile"
@@ -211,7 +224,7 @@
211
  },
212
  {
213
  "key": "mfa_skip",
214
- "section": "section_multifactor_authentication",
215
  "premium": true,
216
  "type": "integer",
217
  "default": 0,
@@ -225,7 +238,7 @@
225
  },
226
  {
227
  "key": "allow_backupcodes",
228
- "section": "section_multifactor_authentication",
229
  "premium": true,
230
  "default": "N",
231
  "type": "checkbox",
@@ -363,7 +376,7 @@
363
  },
364
  {
365
  "key": "enable_login_gasp_check",
366
- "section": "section_brute_force_login_protection",
367
  "default": "N",
368
  "type": "checkbox",
369
  "link_info": "https://shsec.io/3r",
@@ -375,7 +388,7 @@
375
  },
376
  {
377
  "key": "enable_google_recaptcha_login",
378
- "section": "section_brute_force_login_protection",
379
  "default": "disabled",
380
  "type": "select",
381
  "value_options": [
@@ -409,7 +422,7 @@
409
  },
410
  {
411
  "key": "antibot_form_ids",
412
- "section": "section_brute_force_login_protection",
413
  "advanced": true,
414
  "premium": true,
415
  "type": "array",
@@ -473,7 +486,7 @@
473
  },
474
  {
475
  "key": "text_imahuman",
476
- "section": "section_user_messages",
477
  "sensitive": true,
478
  "premium": true,
479
  "default": "default",
@@ -486,7 +499,7 @@
486
  },
487
  {
488
  "key": "text_pleasecheckbox",
489
- "section": "section_user_messages",
490
  "sensitive": true,
491
  "premium": true,
492
  "default": "default",
2
  "slug": "login_protect",
3
  "properties": {
4
  "slug": "login_protect",
5
+ "load_priority": 100,
6
  "name": "Login Guard",
7
+ "namespace": "LoginGuard",
8
  "sidebar_name": "Login Protection",
9
  "show_module_menu_item": false,
10
  "show_module_options": true,
18
  "run_if_wpcli": false,
19
  "order": 40
20
  },
21
+ "menus": {
22
+ "config_menu_priority": 30
23
+ },
24
  "wpcli": {
25
  "enabled": true
26
  },
47
  "Recommendation - Use of this feature is highly recommend."
48
  ]
49
  },
50
+ {
51
+ "slug": "section_twofactor_auth",
52
+ "title": "Multi-Factor Authentication",
53
+ "title_short": "2-Factor Auth",
54
+ "beacon_id": 326,
55
+ "summary": [
56
+ "Purpose - Verifies the identity of users who log in to your site - i.e. they are who they say they are.",
57
+ "Recommendation - Use of this feature is highly recommend. However, if your host blocks email sending you may lock yourself out.",
58
+ "Note: You may combine multiple authentication factors for increased security."
59
+ ]
60
+ },
61
  {
62
  "slug": "section_2fa_email",
63
  "title": "Email Two-Factor Authentication",
90
  "Note: You may combine multiple authentication factors for increased security."
91
  ]
92
  },
 
 
 
 
 
 
 
 
 
 
 
93
  {
94
  "slug": "section_rename_wplogin",
95
  "title": "Hide WP Login Page",
121
  "Recommendation - Keep the Login Guard module turned on."
122
  ]
123
  },
124
+ {
125
+ "slug": "section_deprecated",
126
+ "title": "Deprecated",
127
+ "title_short": "Deprecated",
128
+ "summary": [
129
+ "These options will be removed in the future. Please use the ADE option under Bot SPAM section."
130
+ ]
131
+ },
132
  {
133
  "slug": "section_non_ui",
134
  "hidden": true
178
  },
179
  {
180
  "key": "mfa_verify_page",
181
+ "section": "section_twofactor_auth",
182
+ "default": "wp_login",
183
  "type": "select",
184
  "value_options": [
185
  {
186
+ "value_key": "wp_login",
187
+ "text": "WP Login Page"
188
  },
189
  {
190
+ "value_key": "custom_shield",
191
+ "text": "Custom Shield MFA Page"
192
  }
193
  ],
194
  "link_info": "",
200
  },
201
  {
202
  "key": "mfa_user_setup_pages",
203
+ "section": "section_twofactor_auth",
204
  "type": "multiple_select",
205
  "default": [
206
  "profile"
224
  },
225
  {
226
  "key": "mfa_skip",
227
+ "section": "section_twofactor_auth",
228
  "premium": true,
229
  "type": "integer",
230
  "default": 0,
238
  },
239
  {
240
  "key": "allow_backupcodes",
241
+ "section": "section_twofactor_auth",
242
  "premium": true,
243
  "default": "N",
244
  "type": "checkbox",
376
  },
377
  {
378
  "key": "enable_login_gasp_check",
379
+ "section": "section_deprecated",
380
  "default": "N",
381
  "type": "checkbox",
382
  "link_info": "https://shsec.io/3r",
388
  },
389
  {
390
  "key": "enable_google_recaptcha_login",
391
+ "section": "section_deprecated",
392
  "default": "disabled",
393
  "type": "select",
394
  "value_options": [
422
  },
423
  {
424
  "key": "antibot_form_ids",
425
+ "section": "section_deprecated",
426
  "advanced": true,
427
  "premium": true,
428
  "type": "array",
486
  },
487
  {
488
  "key": "text_imahuman",
489
+ "section": "section_deprecated",
490
  "sensitive": true,
491
  "premium": true,
492
  "default": "default",
499
  },
500
  {
501
  "key": "text_pleasecheckbox",
502
+ "section": "section_deprecated",
503
  "sensitive": true,
504
  "premium": true,
505
  "default": "default",
config/plugin.json CHANGED
@@ -1,6 +1,7 @@
1
  {
2
  "properties": {
3
  "slug": "plugin",
 
4
  "name": "General Settings",
5
  "sidebar_name": "General",
6
  "menu_title": "Configuration",
@@ -17,6 +18,9 @@
17
  "run_if_wpcli": true,
18
  "order": 10
19
  },
 
 
 
20
  "wpcli": {
21
  "enabled": true
22
  },
@@ -29,10 +33,18 @@
29
  "can_dismiss": true,
30
  "type": "error"
31
  },
 
 
 
 
 
 
 
 
32
  "override-forceoff": {
33
  "id": "override-forceoff",
34
  "schedule": "conditions",
35
- "valid_admin": true,
36
  "plugin_page_only": false,
37
  "can_dismiss": false,
38
  "type": "error"
@@ -277,17 +289,6 @@
277
  "summary": "Display Plugin Security Badge On Your Site",
278
  "description": "Enabling this option helps support the plugin by spreading the word about it on your website. The plugin badge also demonstrates to visitors that you take your website security seriously."
279
  },
280
- {
281
- "key": "enable_xmlrpc_compatibility",
282
- "section": "section_defaults",
283
- "default": "N",
284
- "type": "checkbox",
285
- "link_info": "",
286
- "link_blog": "",
287
- "name": "XML-RPC Compatibility",
288
- "summary": "Allow Login Through XML-RPC To By-Pass Login Guard Rules",
289
- "description": "Enable this if you need XML-RPC functionality e.g. if you use the WordPress iPhone/Android App."
290
- },
291
  {
292
  "key": "importexport_enable",
293
  "section": "section_importexport",
@@ -502,13 +503,6 @@
502
  "type": "integer",
503
  "default": 0
504
  },
505
- {
506
- "key": "last_ip_detect_source",
507
- "transferable": false,
508
- "section": "section_non_ui",
509
- "type": "text",
510
- "default": ""
511
- },
512
  {
513
  "key": "openssl_private_key",
514
  "transferable": false,
@@ -538,10 +532,17 @@
538
  "section": "section_non_ui",
539
  "type": "array",
540
  "default": []
 
 
 
 
 
 
 
541
  }
542
  ],
543
  "definitions": {
544
- "rest_api": {
545
  "publish": true,
546
  "pro_only": true,
547
  "route_defs": {
@@ -557,11 +558,11 @@
557
  }
558
  }
559
  },
560
- "href_privacy_policy": "https://shsec.io/wpshieldprivacypolicy",
561
- "db_classes": {
562
  "notes": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\AdminNotes\\Handler"
563
  },
564
- "db_table_notes": {
565
  "slug": "notes",
566
  "has_updated_at": true,
567
  "cols_custom": {
@@ -569,92 +570,7 @@
569
  "note": "TEXT"
570
  }
571
  },
572
- "active_plugin_features": [
573
- {
574
- "slug": "data",
575
- "load_priority": 1
576
- },
577
- {
578
- "slug": "insights",
579
- "load_priority": 2,
580
- "menu_priority": 5
581
- },
582
- {
583
- "slug": "admin_access_restriction",
584
- "namespace": "SecurityAdmin",
585
- "load_priority": 11
586
- },
587
- {
588
- "slug": "ips",
589
- "load_priority": 15,
590
- "namespace": "IPs"
591
- },
592
- {
593
- "slug": "audit_trail",
594
- "load_priority": 11,
595
- "hidden": false
596
- },
597
- {
598
- "slug": "hack_protect",
599
- "namespace": "HackGuard"
600
- },
601
- {
602
- "slug": "traffic",
603
- "load_priority": 12
604
- },
605
- {
606
- "slug": "firewall",
607
- "load_priority": 1000
608
- },
609
- {
610
- "slug": "login_protect",
611
- "storage_key": "loginprotect",
612
- "namespace": "LoginGuard"
613
- },
614
- {
615
- "slug": "user_management"
616
- },
617
- {
618
- "slug": "comments_filter",
619
- "storage_key": "commentsfilter"
620
- },
621
- {
622
- "slug": "events",
623
- "load_priority": 11
624
- },
625
- {
626
- "slug": "reporting",
627
- "load_priority": 12
628
- },
629
- {
630
- "slug": "sessions",
631
- "load_priority": 5
632
- },
633
- {
634
- "slug": "integrations",
635
- "load_priority": 20
636
- },
637
- {
638
- "slug": "license",
639
- "load_priority": 10
640
- },
641
- {
642
- "slug": "autoupdates"
643
- },
644
- {
645
- "slug": "headers"
646
- },
647
- {
648
- "slug": "lockdown"
649
- },
650
- {
651
- "slug": "comms"
652
- },
653
- {
654
- "slug": "email"
655
- }
656
- ],
657
- "events": {
658
  "debug_log": {
659
  "audit_params": [
660
  "message"
@@ -748,7 +664,7 @@
748
  "stat": false
749
  }
750
  },
751
- "wizards": {
752
  "welcome": {
753
  "title": "Getting Started Setup Wizard",
754
  "desc": "An introduction to this security plugin, helping you get setup and started quickly with the core features.",
1
  {
2
  "properties": {
3
  "slug": "plugin",
4
+ "load_priority": 0,
5
  "name": "General Settings",
6
  "sidebar_name": "General",
7
  "menu_title": "Configuration",
18
  "run_if_wpcli": true,
19
  "order": 10
20
  },
21
+ "menus": {
22
+ "config_menu_priority": 10
23
+ },
24
  "wpcli": {
25
  "enabled": true
26
  },
33
  "can_dismiss": true,
34
  "type": "error"
35
  },
36
+ "rules-not-running": {
37
+ "id": "rules-not-running",
38
+ "schedule": "conditions",
39
+ "valid_admin": false,
40
+ "plugin_page_only": true,
41
+ "can_dismiss": false,
42
+ "type": "error"
43
+ },
44
  "override-forceoff": {
45
  "id": "override-forceoff",
46
  "schedule": "conditions",
47
+ "valid_admin": false,
48
  "plugin_page_only": false,
49
  "can_dismiss": false,
50
  "type": "error"
289
  "summary": "Display Plugin Security Badge On Your Site",
290
  "description": "Enabling this option helps support the plugin by spreading the word about it on your website. The plugin badge also demonstrates to visitors that you take your website security seriously."
291
  },
 
 
 
 
 
 
 
 
 
 
 
292
  {
293
  "key": "importexport_enable",
294
  "section": "section_importexport",
503
  "type": "integer",
504
  "default": 0
505
  },
 
 
 
 
 
 
 
506
  {
507
  "key": "openssl_private_key",
508
  "transferable": false,
532
  "section": "section_non_ui",
533
  "type": "array",
534
  "default": []
535
+ },
536
+ {
537
+ "key": "ipdetect_at",
538
+ "transferable": false,
539
+ "section": "section_non_ui",
540
+ "type": "integer",
541
+ "default": 0
542
  }
543
  ],
544
  "definitions": {
545
+ "rest_api": {
546
  "publish": true,
547
  "pro_only": true,
548
  "route_defs": {
558
  }
559
  }
560
  },
561
+ "href_privacy_policy": "https://shsec.io/wpshieldprivacypolicy",
562
+ "db_classes": {
563
  "notes": "\\FernleafSystems\\Wordpress\\Plugin\\Shield\\Databases\\AdminNotes\\Handler"
564
  },
565
+ "db_table_notes": {
566
  "slug": "notes",
567
  "has_updated_at": true,
568
  "cols_custom": {
570
  "note": "TEXT"
571
  }
572
  },
573
+ "events": {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
574
  "debug_log": {
575
  "audit_params": [
576
  "message"
664
  "stat": false
665
  }
666
  },
667
+ "wizards": {
668
  "welcome": {
669
  "title": "Getting Started Setup Wizard",
670
  "desc": "An introduction to this security plugin, helping you get setup and started quickly with the core features.",
config/reporting.json CHANGED
@@ -1,6 +1,7 @@
1
  {
2
  "properties": {
3
  "slug": "reporting",
 
4
  "name": "Reporting",
5
  "storage_key": "reporting",
6
  "tagline": "Shield Reporting",
@@ -18,19 +19,8 @@
18
  "enabled": true
19
  },
20
  "menu_items": [
21
- {
22
- "title": "Stats (beta)",
23
- "slug": "stats-redirect"
24
- }
25
  ],
26
  "custom_redirects": [
27
- {
28
- "source_mod_page": "stats-redirect",
29
- "target_mod_page": "insights",
30
- "query_args": {
31
- "inav": "reports"
32
- }
33
- }
34
  ],
35
  "sections": [
36
  {
1
  {
2
  "properties": {
3
  "slug": "reporting",
4
+ "load_priority": 12,
5
  "name": "Reporting",
6
  "storage_key": "reporting",
7
  "tagline": "Shield Reporting",
19
  "enabled": true
20
  },
21
  "menu_items": [
 
 
 
 
22
  ],
23
  "custom_redirects": [
 
 
 
 
 
 
 
24
  ],
25
  "sections": [
26
  {
config/sessions.json CHANGED
@@ -1,6 +1,7 @@
1
  {
2
  "properties": {
3
  "slug": "sessions",
 
4
  "name": "Sessions",
5
  "show_module_menu_item": false,
6
  "storage_key": "sessions",
1
  {
2
  "properties": {
3
  "slug": "sessions",
4
+ "load_priority": 5,
5
  "name": "Sessions",
6
  "show_module_menu_item": false,
7
  "storage_key": "sessions",
config/traffic.json CHANGED
@@ -2,6 +2,8 @@
2
  "slug": "traffic",
3
  "properties": {
4
  "slug": "traffic",
 
 
5
  "name": "Traffic Watch",
6
  "sidebar_name": "Traffic",
7
  "show_module_menu_item": false,
@@ -16,6 +18,9 @@
16
  "run_if_wpcli": false,
17
  "order": 110
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
@@ -110,18 +115,6 @@
110
  "uptime"
111
  ],
112
  "value_options": [
113
- {
114
- "value_key": "simple",
115
- "text": "Simple Requests"
116
- },
117
- {
118
- "value_key": "api",
119
- "text": "REST API"
120
- },
121
- {
122
- "value_key": "ajax",
123
- "text": "AJAX"
124
- },
125
  {
126
  "value_key": "logged_in",
127
  "text": "Logged-In Users"
2
  "slug": "traffic",
3
  "properties": {
4
  "slug": "traffic",
5
+ "load_priority": 12,
6
+ "menu_priority": 50,
7
  "name": "Traffic Watch",
8
  "sidebar_name": "Traffic",
9
  "show_module_menu_item": false,
18
  "run_if_wpcli": false,
19
  "order": 110
20
  },
21
+ "menus": {
22
+ "config_menu_priority": 35
23
+ },
24
  "wpcli": {
25
  "enabled": true
26
  },
115
  "uptime"
116
  ],
117
  "value_options": [
 
 
 
 
 
 
 
 
 
 
 
 
118
  {
119
  "value_key": "logged_in",
120
  "text": "Logged-In Users"
config/user_management.json CHANGED
@@ -2,6 +2,7 @@
2
  "slug": "user_management",
3
  "properties": {
4
  "slug": "user_management",
 
5
  "name": "User Management",
6
  "sidebar_name": "Users",
7
  "show_module_menu_item": false,
@@ -16,6 +17,9 @@
16
  "run_if_wpcli": false,
17
  "order": 40
18
  },
 
 
 
19
  "wpcli": {
20
  "enabled": true
21
  },
2
  "slug": "user_management",
3
  "properties": {
4
  "slug": "user_management",
5
+ "load_priority": 100,
6
  "name": "User Management",
7
  "sidebar_name": "Users",
8
  "show_module_menu_item": false,
17
  "run_if_wpcli": false,
18
  "order": 40
19
  },
20
+ "menus": {
21
+ "config_menu_priority": 31
22
+ },
23
  "wpcli": {
24
  "enabled": true
25
  },
icwp-wpsf.php CHANGED
@@ -3,7 +3,7 @@
3
  * Plugin Name: Shield Security
4
  * Plugin URI: https://shsec.io/2f
5
  * Description: Powerful, Easy-To-Use #1 Rated WordPress Security System
6
- * Version: 14.1.7
7
  * Text Domain: wp-simple-firewall
8
  * Domain Path: /languages
9
  * Author: Shield Security
3
  * Plugin Name: Shield Security
4
  * Plugin URI: https://shsec.io/2f
5
  * Description: Powerful, Easy-To-Use #1 Rated WordPress Security System
6
+ * Version: 14.9.11
7
  * Text Domain: wp-simple-firewall
8
  * Domain Path: /languages
9
  * Author: Shield Security
plugin-spec.php CHANGED
@@ -1,8 +1,8 @@
1
  {
2
  "properties": {
3
- "version": "14.1.7",
4
- "release_timestamp": 1648194288,
5
- "build": "202203.2501",
6
  "slug_parent": "icwp",
7
  "slug_plugin": "wpsf",
8
  "human_name": "Shield Security",
@@ -77,7 +77,9 @@
77
  "shield/datatables",
78
  "shield/traffic",
79
  "shield/audit_trail",
80
- "shield/scanners"
 
 
81
  ]
82
  },
83
  "frontend": {
@@ -198,6 +200,11 @@
198
  "wp-jquery"
199
  ]
200
  },
 
 
 
 
 
201
  "select2": {
202
  "url": "https://cdnjs.cloudflare.com/ajax/libs/select2/4.1.0-rc.0/js/select2.min.js",
203
  "deps": [
@@ -285,25 +292,11 @@
285
  "plugin"
286
  ]
287
  },
288
- "shuffle": {
289
- "url": "https://cdnjs.cloudflare.com/ajax/libs/Shuffle/5.3.0/shuffle.min.js"
290
- },
291
- "shield/shuffle": {
292
- "deps": [
293
- "shuffle"
294
- ]
295
- },
296
  "shield/dialog": {
297
  "deps": [
298
  "wp-jquery-ui-dialog"
299
  ]
300
  },
301
- "shield/comments": {
302
- "deps": [
303
- "wp-jquery"
304
- ],
305
- "footer": true
306
- },
307
  "shield/loginbot": {
308
  "deps": [
309
  "wp-jquery"
@@ -325,6 +318,11 @@
325
  "shield/datatables"
326
  ]
327
  },
 
 
 
 
 
328
  "shield/traffic": {
329
  "deps": [
330
  "shield/datatables"
@@ -480,5 +478,29 @@
480
  "show": "free"
481
  }
482
  ]
483
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
  }
1
  {
2
  "properties": {
3
+ "version": "14.9.11",
4
+ "release_timestamp": 1651672680,
5
+ "build": "202205.0402",
6
  "slug_parent": "icwp",
7
  "slug_plugin": "wpsf",
8
  "human_name": "Shield Security",
77
  "shield/datatables",
78
  "shield/traffic",
79
  "shield/audit_trail",
80
+ "shield/scanners",
81
+ "shield/ip_detect",
82
+ "tp/circular-progress"
83
  ]
84
  },
85
  "frontend": {
200
  "wp-jquery"
201
  ]
202
  },
203
+ "tp/circular-progress": {
204
+ "url": "https://cdn.jsdelivr.net/gh/tomik23/circular-progress-bar@1.1.9/dist/circularProgressBar.min.js",
205
+ "deps": [
206
+ ]
207
+ },
208
  "select2": {
209
  "url": "https://cdnjs.cloudflare.com/ajax/libs/select2/4.1.0-rc.0/js/select2.min.js",
210
  "deps": [
292
  "plugin"
293
  ]
294
  },
 
 
 
 
 
 
 
 
295
  "shield/dialog": {
296
  "deps": [
297
  "wp-jquery-ui-dialog"
298
  ]
299
  },
 
 
 
 
 
 
300
  "shield/loginbot": {
301
  "deps": [
302
  "wp-jquery"
318
  "shield/datatables"
319
  ]
320
  },
321
+ "shield/ip_detect": {
322
+ "deps": [
323
+ "wp-jquery"
324
+ ]
325
+ },
326
  "shield/traffic": {
327
  "deps": [
328
  "shield/datatables"
478
  "show": "free"
479
  }
480
  ]
481
+ },
482
+ "modules": [
483
+ "plugin",
484
+ "data",
485
+ "admin_access_restriction",
486
+ "audit_trail",
487
+ "autoupdates",
488
+ "comments_filter",
489
+ "comms",
490
+ "email",
491
+ "events",
492
+ "firewall",
493
+ "hack_protect",
494
+ "headers",
495
+ "insights",
496
+ "integrations",
497
+ "ips",
498
+ "license",
499
+ "lockdown",
500
+ "login_protect",
501
+ "reporting",
502
+ "sessions",
503
+ "traffic",
504
+ "user_management"
505
+ ]
506
  }
plugin.json CHANGED
@@ -1,8 +1,8 @@
1
  {
2
  "properties": {
3
- "version": "14.1.7",
4
- "release_timestamp": 1648194288,
5
- "build": "202203.2501",
6
  "slug_parent": "icwp",
7
  "slug_plugin": "wpsf",
8
  "human_name": "Shield Security",
@@ -77,7 +77,9 @@
77
  "shield/datatables",
78
  "shield/traffic",
79
  "shield/audit_trail",
80
- "shield/scanners"
 
 
81
  ]
82
  },
83
  "frontend": {
@@ -198,6 +200,11 @@
198
  "wp-jquery"
199
  ]
200
  },
 
 
 
 
 
201
  "select2": {
202
  "url": "https://cdnjs.cloudflare.com/ajax/libs/select2/4.1.0-rc.0/js/select2.min.js",
203
  "deps": [
@@ -285,25 +292,11 @@
285
  "plugin"
286
  ]
287
  },
288
- "shuffle": {
289
- "url": "https://cdnjs.cloudflare.com/ajax/libs/Shuffle/5.3.0/shuffle.min.js"
290
- },
291
- "shield/shuffle": {
292
- "deps": [
293
- "shuffle"
294
- ]
295
- },
296
  "shield/dialog": {
297
  "deps": [
298
  "wp-jquery-ui-dialog"
299
  ]
300
  },
301
- "shield/comments": {
302
- "deps": [
303
- "wp-jquery"
304
- ],
305
- "footer": true
306
- },
307
  "shield/loginbot": {
308
  "deps": [
309
  "wp-jquery"
@@ -325,6 +318,11 @@
325
  "shield/datatables"
326
  ]
327
  },
 
 
 
 
 
328
  "shield/traffic": {
329
  "deps": [
330
  "shield/datatables"
@@ -480,5 +478,29 @@
480
  "show": "free"
481
  }
482
  ]
483
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
  }
1
  {
2
  "properties": {
3
+ "version": "14.9.11",
4
+ "release_timestamp": 1651672680,
5
+ "build": "202205.0402",
6
  "slug_parent": "icwp",
7
  "slug_plugin": "wpsf",
8
  "human_name": "Shield Security",
77
  "shield/datatables",
78
  "shield/traffic",
79
  "shield/audit_trail",
80
+ "shield/scanners",
81
+ "shield/ip_detect",
82
+ "tp/circular-progress"
83
  ]
84
  },
85
  "frontend": {
200
  "wp-jquery"
201
  ]
202
  },
203
+ "tp/circular-progress": {
204
+ "url": "https://cdn.jsdelivr.net/gh/tomik23/circular-progress-bar@1.1.9/dist/circularProgressBar.min.js",
205
+ "deps": [
206
+ ]
207
+ },
208
  "select2": {
209
  "url": "https://cdnjs.cloudflare.com/ajax/libs/select2/4.1.0-rc.0/js/select2.min.js",
210
  "deps": [
292
  "plugin"
293
  ]
294
  },
 
 
 
 
 
 
 
 
295
  "shield/dialog": {
296
  "deps": [
297
  "wp-jquery-ui-dialog"
298
  ]
299
  },
 
 
 
 
 
 
300
  "shield/loginbot": {
301
  "deps": [
302
  "wp-jquery"
318
  "shield/datatables"
319
  ]
320
  },
321
+ "shield/ip_detect": {
322
+ "deps": [
323
+ "wp-jquery"
324
+ ]
325
+ },
326
  "shield/traffic": {
327
  "deps": [
328
  "shield/datatables"
478
  "show": "free"
479
  }
480
  ]
481
+ },
482
+ "modules": [
483
+ "plugin",
484
+ "data",
485
+ "admin_access_restriction",
486
+ "audit_trail",
487
+ "autoupdates",
488
+ "comments_filter",
489
+ "comms",
490
+ "email",
491
+ "events",
492
+ "firewall",
493
+ "hack_protect",
494
+ "headers",
495
+ "insights",
496
+ "integrations",
497
+ "ips",
498
+ "license",
499
+ "lockdown",
500
+ "login_protect",
501
+ "reporting",
502
+ "sessions",
503
+ "traffic",
504
+ "user_management"
505
+ ]
506
  }
resources/css/global-plugin.css CHANGED
@@ -1,43 +1,8 @@
1
  @CHARSET "ISO-8859-1";
2
-
3
- #wpbody-content .odp-admin-notice.notice {
4
- margin: 5px 0 15px;
5
- }
6
-
7
- #wpbody-content .icwp-admin-notice,
8
- #wpbody-content .icwp-admin-notice p {
9
- text-shadow: -1px -1px 0 rgba(255, 255, 255, 0.4);
10
- }
11
- #wpbody-content .icwp-admin-notice.updated,
12
- #wpbody-content .icwp-admin-notice.updated p {
13
- color: rgba(26, 111, 0, 1);
14
- }
15
- #wpbody-content .icwp-admin-notice form {
16
- margin: 0;
17
- }
18
- #wpbody-content .icwp-admin-notice .button:hover {
19
- text-decoration: none;
20
- }
21
- #wpbody-content .icwp-admin-notice .notice-dismiss::before {
22
- color: rgba(0, 0, 0, 0.5);
23
- font-size: 24px;
24
- line-height: 21px;
25
- padding-right: 4px;
26
- }
27
- #wpbody-content .icwp-admin-notice .dismiss-p {
28
- margin: 10px;
29
- }
30
- #wpbody-content .icwp-admin-notice .dismiss-p,
31
- #wpbody-content .icwp-admin-notice .dismiss-p a {
32
- color: rgb(103, 21, 0);
33
- text-align: right;
34
- }
35
-
36
  .plugin_update_message {
37
  color: #dd3333;
38
  font-weight: bolder;
39
  }
40
-
41
  .notice-icon {
42
  display: none;
43
  float: left;
@@ -92,26 +57,6 @@ ul.nav-tabs li a {
92
  ul.nav-tabs li.active a {
93
  color: #000000;
94
  }
95
- .bootstrap-wpadmin .form-horizontal .form-actions {
96
- background-color: rgba(0, 0, 0, 0.07);
97
- border: 1px solid #dddddd;
98
- border-top: 1px solid transparent;
99
- }
100
- .bootstrap-wpadmin .icwpTopLevelRow {
101
- }
102
- .bootstrap-wpadmin .icwpTopLevelRow .icwpTopLevelSpan {
103
- margin-left: 0;
104
- }
105
- th.column-icwp_autoupdate, td.icwp_autoupdate, th#icwp_autoupdate {
106
- text-align: center;
107
- vertical-align: middle;
108
- }
109
- td.icwp_autoupdate span.icwp-pluginautoupdateicon.dashicons-update {
110
- color: #559c6a;
111
- }
112
- td.icwp_autoupdate span.icwp-pluginautoupdateicon.dashicons-hammer {
113
- color: #87492e;
114
- }
115
  body.plugins-php .wrap > ul li.vulnerable a {
116
  color: darkred;
117
  }
@@ -179,55 +124,6 @@ tr.icwp-plugin-vulnerability dd {
179
  padding: 5px 10px;
180
  width: 100%;
181
  }
182
- .icwp-toggle-switch {
183
- position: relative;
184
- display: inline-block;
185
- width: 60px;
186
- height: 34px;
187
- }
188
- /* Hide default HTML checkbox */
189
- .icwp-toggle-switch input {
190
- display: none;
191
- }
192
- /* The slider */
193
- .icwp-toggle-switch {
194
- cursor: pointer;
195
- }
196
- .icwp-toggle-switch .icwp-slider {
197
- position: absolute;
198
- top: 0;
199
- left: 0;
200
- right: 0;
201
- bottom: 0;
202
- background-color: #c1bfa9;
203
- -webkit-transition: .4s;
204
- transition: .4s;
205
- }
206
- .icwp-toggle-switch .icwp-slider:before {
207
- position: absolute;
208
- content: "";
209
- height: 26px;
210
- width: 26px;
211
- left: 4px;
212
- bottom: 4px;
213
- background-color: white;
214
- -webkit-transition: .4s;
215
- transition: .4s;
216
- }
217
- .icwp-toggle-switch input:checked + .icwp-slider {
218
- background-color: #65bd84;
219
- }
220
- .icwp-toggle-switch.disabled input + .icwp-slider {
221
- background-color: #bd7429;
222
- }
223
- .icwp-toggle-switch input:focus + .icwp-slider {
224
- box-shadow: 0 0 1px #65bd84;
225
- }
226
- .icwp-toggle-switch input:checked + .icwp-slider:before {
227
- -webkit-transform: translateX(26px);
228
- -ms-transform: translateX(26px);
229
- transform: translateX(26px);
230
- }
231
  .icwp-growl-notice {
232
  -webkit-transition: width 1.5s;
233
  transition: width 1.5s;
@@ -358,4 +254,89 @@ tr.icwp-plugin-vulnerability dd {
358
  padding: 0 0 5px;
359
  }
360
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361
  /* override boostrap */
1
  @CHARSET "ISO-8859-1";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  .plugin_update_message {
3
  color: #dd3333;
4
  font-weight: bolder;
5
  }
 
6
  .notice-icon {
7
  display: none;
8
  float: left;
57
  ul.nav-tabs li.active a {
58
  color: #000000;
59
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  body.plugins-php .wrap > ul li.vulnerable a {
61
  color: darkred;
62
  }
124
  padding: 5px 10px;
125
  width: 100%;
126
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  .icwp-growl-notice {
128
  -webkit-transition: width 1.5s;
129
  transition: width 1.5s;
254
  padding: 0 0 5px;
255
  }
256
 
257
+ #ShieldDashboardWidget a {
258
+ color: #008000 !important;
259
+ }
260
+ #ShieldDashboardWidget p.no-entries {
261
+ margin: 3px 0 0 0;
262
+ }
263
+
264
+ #ShieldDashboardWidget .subheader {
265
+ font-weight: bolder;
266
+ margin: 1rem 0 0;
267
+ }
268
+ #ShieldDashboardWidget table thead th {
269
+ background-color: #97b797;
270
+ color: white;
271
+ font-weight: bolder;
272
+ text-shadow: 0 0 1px rgba(0, 0, 0, 0.5);
273
+ }
274
+
275
+ #ShieldDashboardWidget table.table-recent {
276
+ border-collapse: collapse;
277
+ box-shadow: 3px 3px 2px rgb(0 0 0 / 10%);
278
+ }
279
+ #ShieldDashboardWidget .mini-scrolling-table {
280
+ margin-top: 3px;
281
+ overflow: auto;
282
+ min-height: 60px;
283
+ max-height: 150px;
284
+ }
285
+ #ShieldDashboardWidget .mini-scrolling-table th {
286
+ padding: 2px 10px;
287
+
288
+ position: sticky;
289
+ top: 0;
290
+ z-index: 1;
291
+ }
292
+ #ShieldDashboardWidget .jump-buttons {
293
+ margin: 10px auto;
294
+
295
+ display: flex;
296
+ flex-direction: row;
297
+ justify-content: center;
298
+ gap: 7px;
299
+ }
300
+ #ShieldDashboardWidget .shield-progress-bar {
301
+ height: 20px;
302
+ margin: 15px 0 20px;
303
+ position: relative;
304
+ background: #e9e9e9;
305
+ border-radius: 25px;
306
+ padding: 6px;
307
+ box-shadow: inset 0 -1px 1px rgba(255, 255, 255, 0.3);
308
+ }
309
+ #ShieldDashboardWidget .shield-progress-bar > .thebar {
310
+ display: block;
311
+ height: 100%;
312
+ border-top-right-radius: 8px;
313
+ border-bottom-right-radius: 8px;
314
+ border-top-left-radius: 20px;
315
+ border-bottom-left-radius: 20px;
316
+ background-color: rgb(43, 194, 83);
317
+ background-image: linear-gradient(
318
+ center bottom,
319
+ rgb(43, 194, 83) 37%,
320
+ rgb(84, 240, 84) 69%
321
+ );
322
+ box-shadow: inset 0 2px 9px rgba(255, 255, 255, 0.3),
323
+ inset 0 -2px 6px rgba(0, 0, 0, 0.4);
324
+ position: relative;
325
+ overflow: hidden;
326
+ }
327
+ #ShieldDashboardWidget .shield-progress-bar > .thebar > .thepercent {
328
+ text-shadow: 0 0 1px rgba(0, 0, 0, 1);
329
+ font-weight: bolder;
330
+ color: white;
331
+ text-align: center;
332
+ }
333
+ #ShieldDashboardWidget .shield-progress-bar.orange > .thebar {
334
+ background-color: #f1a165;
335
+ background-image: linear-gradient(to bottom, #f1a165, #f36d0a);
336
+ }
337
+ #ShieldDashboardWidget .shield-progress-bar.red > .thebar {
338
+ background-color: #f0a3a3;
339
+ background-image: linear-gradient(to bottom, #f0a3a3, #f42323);
340
+ }
341
+
342
  /* override boostrap */
resources/css/plugin.css CHANGED
@@ -16,39 +16,9 @@ body.rtl {
16
  margin: 0;
17
  padding: 0;
18
  }
19
- /** Hide WordPress update nag **/
20
- .update-nag {
21
- display: none;
22
- }
23
- #wpbody-content .odp-admin-notice.notice {
24
- margin: 5px 15px;
25
- }
26
  .pluginlogo_16 {
27
  background: url("../images/pluginlogo_16x16.png?ver=1.0.0") no-repeat 0 3px transparent;
28
  }
29
- .page-header .header-icon32 {
30
- display: inline-block;
31
- float: left;
32
- height: 40px;
33
- vertical-align: middle;
34
- width: 42px;
35
- }
36
- .plugin_update_message {
37
- color: #dd3333;
38
- }
39
- .wrap .icon32 {
40
- width: 32px;
41
- }
42
- .bootstrap-wpadmin .page-header {
43
- margin: 5px 0 15px;
44
- padding-bottom: 0;
45
- border-bottom: 1px solid transparent;
46
- }
47
- .bootstrap-wpadmin .page-header h2 small.feature-tagline {
48
- font-size: 14px;
49
- line-height: 10px;
50
- letter-spacing: -0.5px;
51
- }
52
  .footer-form-actions {
53
  background: rgba(0, 0, 0, 0.1);
54
  position: fixed;
@@ -77,44 +47,6 @@ body.rtl {
77
  #ColumnOptions form {
78
  margin-bottom: 50px;
79
  }
80
- /* Form elements */
81
- .bootstrap-wpadmin form fieldset {
82
- width: 80%;
83
- }
84
- .bootstrap-wpadmin .label {
85
- vertical-align: baseline !important;
86
- }
87
- .shortcode-usage {
88
- border-bottom: 1px solid #bbbbbb;
89
- margin-bottom: 10px;
90
- }
91
- p.code-description {
92
- font-style: italic;
93
- }
94
- /* Worpit promo */
95
- #worpit_button {
96
- background: url("../images/bright_squares.png") #efefef;
97
- }
98
- #worpit_button {
99
- border: 1px solid #cccccc;
100
- border-radius: 4px;
101
- height: 190px;
102
- }
103
- #worpit_button:hover {
104
- box-shadow: 0 0 6px rgba(0, 0, 0, 0.4);
105
- border: 1px solid transparent;
106
- }
107
- .row#developer_channel_promo #developer_channel_description .well {
108
- height: 275px;
109
- }
110
- .row#developer_channel_promo #developer_channel_form {
111
- border: 1px solid #eeeeee;
112
- border-radius: 6px;
113
- height: 315px; /* 275px + 20px-padding-x2 */
114
- }
115
- .row#developer_channel_promo #developer_channel_form h3 {
116
- padding: 10px 20px;
117
- }
118
  .row.option_section_row {
119
  margin-left: 0;
120
  padding-bottom: 24px;
@@ -124,105 +56,10 @@ p.code-description {
124
  box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.2);
125
  padding-bottom: 30px;
126
  }
127
- /*
128
- .row.option_section_row.primary_section > div {
129
- background-color: #f9f9f9;
130
- border: 1px solid #ddd;
131
- margin-bottom: 30px;
132
- margin-top: 10px;
133
- padding: 5px 30px 30px;
134
- }
135
- .row.option_section_row.non_primary_section > div {
136
- margin-left: 0;
137
- }*/
138
- .well.admin_access_restriction_form {
139
- background-color: #fbfbfb;
140
- border: 1px solid #cccccc;
141
- box-shadow: none;
142
- }
143
- .well.admin_access_restriction_form .form-actions {
144
- background-color: #fbfbfb;
145
- }
146
- /** LESS PAGE **/
147
- .enabled_section {
148
- opacity: 1.0;
149
- }
150
- .disabled_section {
151
- opacity: 0.5;
152
- }
153
- /* BOOSTRAP SETTINGS PAGE STYLES */
154
- .bootstrap-wpadmin .form-horizontal .form-actions {
155
- padding-left: 200px;
156
- }
157
- .wrap .icon32 {
158
- width: 32px;
159
- }
160
  .submit span {
161
  font-size: smaller;
162
  font-style: italic;
163
  }
164
- .options-body legend {
165
- border-bottom: 1px dashed transparent;
166
- margin-bottom: 0;
167
- padding: 16px 20px 0;
168
- }
169
- .option_row .item_group {
170
- }
171
- .option_row .item_group .control-group {
172
- margin: 22px 20px;
173
- }
174
- .option_row .item_group .control-group .controls {
175
- margin-left: 180px;
176
- }
177
- .option_row .item_group .control-group .control-label {
178
- float: left;
179
- width: 150px;
180
- margin-top: 2px;
181
- text-align: right;
182
- }
183
- .optlinks {
184
- display: block;
185
- margin-top: 5px;
186
- font-size: smaller;
187
- }
188
- .option_row .item_group.selected_item_group {
189
- }
190
- .row.option_row {
191
- margin-bottom: 15px;
192
- }
193
- .overlay_container.disabled {
194
- position: relative;
195
- }
196
- .option_overlay {
197
- position: absolute;
198
- background-color: rgba(0, 0, 0, 0.2);
199
- height: 100%;
200
- width: 100%;
201
- transition: background-color 0.5s ease;
202
- z-index: 5;
203
- }
204
- .option_overlay:hover {
205
- background-color: rgba(0, 0, 0, 0.5);
206
- }
207
- .option_overlay .overlay_message {
208
- display: none;
209
- }
210
- .option_overlay .overlay_message:hover,
211
- .option_overlay:hover .overlay_message {
212
- display: block;
213
- color: #333333;
214
- font-size: 18px;
215
- margin: 15px auto auto auto;
216
- padding: 0 10px;
217
- width: auto;
218
- background: rgba(255, 255, 255, 0.8);
219
- line-height: 40px;
220
- text-align: center;
221
- user-select: none;
222
- }
223
- .option_overlay:hover .overlay_message a:hover {
224
- text-decoration: none;
225
- }
226
  .option_section {
227
  background-color: transparent;
228
  border: 1px dashed transparent;
@@ -234,15 +71,10 @@ p.code-description {
234
  .option_section textarea {
235
  width: 100%;
236
  }
237
- .option_section input[type=checkbox] {
238
- }
239
  .option_section label {
240
  background-color: transparent;
241
  width: 100%;
242
  }
243
- .selected_item label {
244
- font-weight: bold;
245
- }
246
  .nonselected_item {
247
  background-color: transparent;
248
  }
@@ -253,205 +85,10 @@ p.code-description {
253
  background-color: #ffffff;
254
  border: 1px dashed #bbbbbb;
255
  }
256
- .option_section.active {
257
- }
258
- table.table th {
259
- }
260
- table.tbl_tbs_options {
261
- width: 100%;
262
- border: 1px solid transparent;
263
- }
264
- table.tbl_tbs_options tr td {
265
- width: 49%;
266
- border: 1px solid transparent;
267
- }
268
- select#hlt_bootstrap_option {
269
- padding: 5px;
270
- border-radius: 6px 6px 6px 6px;
271
- height: 30px;
272
- width: 200px;
273
- }
274
- table#tbl_tbs_options_javascript td {
275
- vertical-align: top;
276
- width: 50%;
277
- }
278
  /** OPTIONS PAGES **/
279
- .bootstrap-wpadmin > .row {
280
- width: 960px;
281
- }
282
- .bootstrap-wpadmin .row.row_number_1 {
283
- }
284
  label input[type=checkbox] {
285
  height: 16px !important;
286
  }
287
- /** FEature summaries **/
288
- .bootstrap-wpadmin .feature-summary-blocks {
289
- text-align: center;
290
- width: 100%;
291
- margin-top: 20px;
292
- box-shadow: 1px 1px 2px 0 rgba(0, 0, 0, 0.2) inset;
293
- border: 1px solid #cccccc;
294
- background: #f9f9f9;
295
- }
296
- .nav-link.module .module-icon {
297
- position: relative;
298
- top: 3px;
299
- }
300
- .dropdown-menu .module-icon {
301
- position: relative;
302
- top: 7px;
303
- }
304
- .module-icon:before {
305
- -webkit-font-smoothing: antialiased;
306
- font: 16px/1 'dashicons';
307
- }
308
- .module-icon-insights:before {
309
- content: "\f179";
310
- }
311
- .module-icon-plugin:before {
312
- content: "\f111";
313
- }
314
- .module-icon-admin_access_restriction:before {
315
- content: "\f155";
316
- }
317
- .module-icon-firewall:before {
318
- content: "\f479";
319
- }
320
- .module-icon-user_management:before {
321
- content: "\f110";
322
- }
323
- .module-icon-hack_protect:before {
324
- content: "\f332";
325
- }
326
- .module-icon-headers:before {
327
- content: "\f163";
328
- }
329
- .module-icon-login_protect:before {
330
- content: "\f112";
331
- }
332
- .module-icon-reporting:before {
333
- content: "\f488";
334
- }
335
- .module-icon-comments_filter:before {
336
- content: "\f117";
337
- }
338
- .module-icon-autoupdates:before {
339
- content: "\f463";
340
- }
341
- .module-icon-lockdown:before {
342
- content: "\f160";
343
- }
344
- .module-icon-audit_trail:before {
345
- content: "\f115";
346
- }
347
- .module-icon-ips:before {
348
- content: "\f534";
349
- }
350
- .module-icon-traffic:before {
351
- content: "\f319";
352
- }
353
- .module-icon-comms:before {
354
- content: "\f466";
355
- }
356
- .module-icon-support:before {
357
- content: "\f525";
358
- }
359
- #SearchOptionsLaunch > a {
360
- text-decoration: none !important;
361
- }
362
- #SearchOptionsLaunch .dashicons {
363
- width: 24px;
364
- content: "\f179";
365
- font-size: 24px;
366
- color: #008000 !important;
367
- line-height: 40px;
368
- }
369
- .dropdown-item {
370
- font-size: 1.05rem;
371
- }
372
- .dropdown-item .module-icon:before {
373
- font-size: 1.6rem;
374
- }
375
- .dropdown-item .module-name {
376
- margin-left: 10px;
377
- }
378
- .dropdown-item.active {
379
- background-color: rgba(69, 119, 0, 1);
380
- }
381
- .bootstrap-wpadmin .row .shield-free-block {
382
- background-color: rgba(255, 255, 255, 0.7);
383
- border: 1px solid rgba(0, 0, 0, 0.1);
384
- padding: 20px 50px;
385
- }
386
- .bootstrap-wpadmin .row .shield-free-block p {
387
- margin-bottom: 18px;
388
- }
389
- #icwpOptionsTopPill {
390
- }
391
- #icwpOptionsTopPill > .nav-pills {
392
- border-bottom: 1px dashed #999999;
393
- padding-bottom: 10px;
394
- }
395
- #icwpOptionsTopPill > .nav-pills li {
396
- width: 30.5%;
397
- }
398
- #icwpOptionsTopPill > .nav-pills li#icwpWizardPill {
399
- width: 8%;
400
- }
401
- #icwpOptionsTopPill > .nav-pills li#icwpWizardPill .tooltip {
402
- opacity: 1.0;
403
- }
404
- #icwpOptionsTopPill > .nav-pills li#icwpWizardPill .tooltip .tooltip-inner {
405
- max-width: none;
406
- font-size: 16px;
407
- padding: 8px 12px;
408
- }
409
- #icwpOptionsTopPill > .nav-pills li#icwpWizardPill a {
410
- background-size: 100%;
411
- background-color: #1cbaf3;
412
- background-repeat: no-repeat;
413
- background-origin: content-box;
414
- background-position: 0 4px;
415
- }
416
- #icwpOptionsTopPill > .nav-pills li a {
417
- background-color: rgba(0, 0, 0, 0.2);
418
- text-shadow: 1px 0 0 rgba(255, 255, 255, 0.4);
419
- color: #666666;
420
- height: 50px;
421
- }
422
- #icwpOptionsTopPill > .nav-pills li a:hover {
423
- background-color: rgba(38, 66, 1, 0.3);
424
- }
425
- #icwpOptionsTopPill > .nav-pills li a:focus {
426
- box-shadow: none;
427
- }
428
- #icwpOptionsTopPill > .nav-pills li span.dashicons {
429
- float: left;
430
- height: 100%;
431
- font-size: 48px;
432
- display: block;
433
- width: 62px;
434
- }
435
- #icwpOptionsTopPill > .nav-pills li.active a {
436
- background-color: rgba(69, 119, 0, 1);
437
- color: #ffffff;
438
- text-shadow: 1px 0 0 rgba(0, 0, 0, 0.5);
439
- }
440
- #icwpOptionsTopPill > .nav-pills li a .title {
441
- font-weight: bolder;
442
- font-size: 18px;
443
- line-height: 24px;
444
- }
445
- #icwpOptionsTopPill > .nav-pills li a .summary {
446
- font-size: 16px;
447
- line-height: 28px;
448
- padding-left: 10px;
449
- }
450
- #icwpOptionsTopPill > .nav-pills li a.active {
451
- }
452
- #icwpOptionsTopPill > .tab-content {
453
- background-color: transparent;
454
- }
455
  .content-wizards .wizard_slot {
456
  border: 1px solid #dddddd;
457
  border-radius: 3px;
@@ -465,78 +102,6 @@ label input[type=checkbox] {
465
  pointer-events: auto;
466
  }
467
  .content-wizards,
468
- .content-help {
469
- background-color: #f1f1f1; /** == #ColumnOptions */
470
- padding: 20px;
471
- }
472
- .content-help h2 {
473
- margin-bottom: 8px;
474
- }
475
- .content-help p,
476
- .content-help li {
477
- font-size: 14px;
478
- }
479
- .content-help dt {
480
- border-bottom: 1px dotted rgba(0, 0, 0, 0.2);
481
- font-size: 16px;
482
- margin-bottom: 7px;
483
- padding-bottom: 3px;
484
- }
485
- .content-help dd {
486
- margin-bottom: 15px;
487
- margin-left: 12px;
488
- }
489
- .bootstrap-wpadmin .option_section_row .options-body {
490
- }
491
- .bootstrap-wpadmin .option_row .item_group {
492
- }
493
- /* The switch - the box around the slider */
494
- label.forcheckbox .summary {
495
- vertical-align: top;
496
- line-height: 20px;
497
- margin-left: 7px;
498
- }
499
- /* The slider */
500
- .icwp-slider {
501
- position: absolute;
502
- cursor: pointer;
503
- top: 0;
504
- left: 0;
505
- right: 0;
506
- bottom: 0;
507
- background-color: #cccccc;
508
- -webkit-transition: .1s;
509
- transition: .1s;
510
- }
511
- .icwp-slider:before {
512
- position: absolute;
513
- content: "";
514
- height: 16px;
515
- width: 16px;
516
- left: 2px;
517
- bottom: 2px;
518
- background-color: white;
519
- -webkit-transition: .1s;
520
- transition: .1s;
521
- }
522
- input:checked + .icwp-slider {
523
- background-color: rgba(69, 119, 0, 1);
524
- }
525
- input:focus + .icwp-slider {
526
- box-shadow: 0 0 1px rgba(69, 119, 0, 1);
527
- }
528
- input:checked + .icwp-slider:before {
529
- -webkit-transform: translateX(20px);
530
- -ms-transform: translateX(20px);
531
- transform: translateX(20px);
532
- }
533
- /* Rounded sliders */
534
- .icwp-slider.round {
535
- border-radius: 3px;
536
- }
537
- .icwp-slider.round:before {
538
- border-radius: 10%;
539
- }
540
  /** NEW FORMS **/
541
  #wpwrap {
542
  background-color: #f1f1f1;
@@ -545,25 +110,9 @@ input:checked + .icwp-slider:before {
545
  #wpbody-content {
546
  background-color: #f1f1f1;
547
  }
548
- #wpfooter {
549
- }
550
- .icwp-options-page {
551
- /*margin-top: 7px;*/
552
- /*margin-right: 7px;*/
553
- }
554
  #ModulePageTopRow {
555
  min-width: 760px; /** prevents col breaking **/
556
  }
557
- #TopPluginIcon {
558
- background: url("../images/pluginlogo_128x128.png?ver=2.0") no-repeat 0 0 #f1f1f1;
559
- border-bottom: 1px solid #cccccc;
560
- height: 76px;
561
- width: 76px;
562
- background-size: 76px 76px;
563
- margin-bottom: 7px;
564
- position: relative;
565
- left: 1px;
566
- }
567
  #ColumnModules {
568
  width: 200px;
569
  margin-left: 15px;
@@ -611,23 +160,9 @@ input:checked + .icwp-slider:before {
611
  }
612
  .modules a.module.active > div {
613
  }
614
- .modules a.module .module-icon::before {
615
- color: #222222;
616
- }
617
- .modules a.module.active .module-icon::before {
618
- color: white;
619
- }
620
  .modules a#tab-license {
621
  color: #1d00ff;
622
  }
623
- .modules a.module .module-name {
624
- }
625
- .modules .module-name .dashicons {
626
- position: absolute;
627
- left: -20px;
628
- top: 1px;
629
- color: #a56300;
630
- }
631
  .modules a.module .dashicons-warning {
632
  color: #a56300;
633
  }
@@ -637,17 +172,6 @@ input:checked + .icwp-slider:before {
637
  .modules a.module .dashicons-warning:before {
638
  font-size: 16px !important;
639
  }
640
- .modules a.module.active .module-icon {
641
- }
642
- .icwp-options-page .tab-content {
643
- background-color: #f1f1f1;
644
- border: 1px solid #dddddd;
645
- box-shadow: -1px 1px 3px rgba(0, 0, 0, 0.05);
646
- min-height: 450px;
647
- }
648
- .smoothwidth {
649
- transition: width 4.5s;
650
- }
651
  .form-label {
652
  font-size: 14px;
653
  }
@@ -660,10 +184,6 @@ input:checked + .icwp-slider:before {
660
  transition: -webkit-transform 0.3s ease;
661
  transition: transform 0.2s ease, -webkit-transform 0.2s ease;
662
  }
663
- #AuditTrailTabs .tab-content {
664
- border: 0 none;
665
- box-shadow: none;
666
- }
667
  #IcwpWpsfSecurityAdmin {
668
  background-color: white;
669
  padding: 40px;
@@ -675,27 +195,12 @@ input:checked + .icwp-slider:before {
675
  padding: 0; /* fix to override for bootstrap4 cuz WP has a .card style*/
676
  }
677
  @media (max-width: 848px) {
678
- #ColumnModules {
679
- max-width: 81px;
680
- }
681
-
682
- #TopPluginIcon {
683
- height: 76px;
684
- width: 76px;
685
- background-size: 76px 76px;
686
- }
687
-
688
  .modules a.module {
689
  width: 81px;
690
  font-size: 0.7rem;
691
  font-weight: normal;
692
  letter-spacing: -1px;
693
  }
694
-
695
- .modules .module-name .dashicons {
696
- font-size: 16px;
697
- left: -17px;
698
- }
699
  }
700
  #FooterWizardBanner .text-start {
701
  margin-bottom: 0;
@@ -724,14 +229,6 @@ td.column-message textarea {
724
  position: sticky;
725
  top: 126px;
726
  }
727
- #SectionNotices tr.title_row th {
728
- padding: 0.25rem 1.0rem;
729
- border-bottom: 0 none;
730
- letter-spacing: 0.1px;
731
- }
732
- #SectionNotices tr.title_row th h6 {
733
- font-size: 0.85rem;
734
- }
735
  /** TABLE: TRAFFIC **/
736
  th.column-code {
737
  width: 84px;
@@ -739,14 +236,8 @@ th.column-code {
739
  th.column-trans {
740
  width: 114px;
741
  }
742
- th.column-request_info {
743
- width: 120px;
744
- }
745
  /** INSIGHTS **/
746
  /* TABLES */
747
- .tablenav-pages-navspan {
748
- box-sizing: content-box;
749
- }
750
  #odp-PageContainer .card {
751
  box-shadow: none;
752
  margin-top: 0;
@@ -769,28 +260,9 @@ th.column-request_info {
769
  font-weight: bold;
770
  color: #008000;
771
  }
772
- .nav-link.module.notenabled span.module-name {
773
- text-decoration: line-through;
774
- }
775
- .nav-link.module.notenabled .module-icon:before {
776
- color: rgba(0, 0, 0, 0.4);
777
- }
778
- .nav-link.module.enabled .module-icon:before {
779
- color: rgba(50, 50, 50, 1);
780
- }
781
- .is-not-pro #NavItem-license {
782
- color: #09b740;
783
- text-shadow: 0 0 0 rgba(0, 0, 0, 0.2);
784
- font-weight: bolder;
785
- }
786
  #odp-PageMain {
787
  font-size: 13px;
788
  }
789
- .odp-outercontainer.icwp-wpsf-insights.settings #odp-PageMain {
790
- }
791
- .insights_section {
792
- /*margin-top: 20px;*/
793
- }
794
  .insights_widget .card-body {
795
  padding: 0 0 0 0;
796
  }
@@ -806,28 +278,11 @@ th.column-request_info {
806
  .overflow_inner dl {
807
  margin-bottom: 1px;
808
  }
809
- .title_row th,
810
- .title_row td {
811
- background-color: #fbfbfb;
812
- }
813
- .message_row th,
814
- .message_row td {
815
- border-top: 1px solid #e9edf1; /** make bootstrap border lighter */
816
- }
817
- td.cell_delete_note {
818
- max-width: 20px;
819
- }
820
- .icon-sign {
821
- font-size: 14px;
822
- }
823
  #SectionStats {
824
  overflow: auto;
825
  }
826
- .stat.card {
827
- }
828
  .stat.card .card-header {
829
  font-size: smaller;
830
- /*white-space: nowrap;*/
831
  }
832
  .stat.card .card-body {
833
  font-size: x-large;
@@ -842,8 +297,6 @@ td.cell_delete_note {
842
  #SectionModuleSummaries table tr.row-key_opt {
843
  }
844
  /** TABLES **/
845
- .icwpAjaxTableContainer {
846
- }
847
  .icwpAjaxTableContainer .tablenav {
848
  height: auto;
849
  }
@@ -890,21 +343,6 @@ form.add-ip button.add .dashicons {
890
  padding-left: 0;
891
  padding-right: 0;
892
  }
893
- /** unused
894
- th.column-actions,
895
- td.column-actions {
896
- text-align: center;
897
- width: 100px;
898
- }
899
- td.column-actions .actions-block button.btn {
900
- margin: 0 3px;
901
- height: 32px;
902
- width: 32px;
903
- padding: 3px 2px 3px 1px;
904
- }
905
- td.column-actions button > span {
906
- font-size: 20px;
907
- } */
908
  /** END IPS Insights **/
909
  .scan-select .form-text {
910
  white-space: normal;
@@ -921,20 +359,22 @@ table.scan-table.wp-list-table button.toggle-row {
921
  #ScanResultsTabs {
922
  }
923
  .insights-sub-nav ul.nav.nav-tabs a.nav-link {
924
- color: #666666;
925
  font-size: 0.9rem;
926
- margin-right: 1rem;
927
  background-color: rgba(0, 0, 0, 0.05);
928
  border-color: rgba(0, 0, 0, 0.1);
929
  border-bottom-color: transparent;
930
  box-shadow: 0 -2px 3px rgba(0, 0, 0, 0.03) inset;
931
  opacity: 0.9;
 
 
 
932
  }
933
  .insights-sub-nav ul.nav.nav-tabs a.nav-link:hover {
934
  opacity: 1;
935
  }
936
  .insights-sub-nav ul.nav.nav-tabs a.nav-link.active {
937
- color: #333333;
938
  background-color: transparent;
939
  border-bottom-color: #f1f1f1;
940
  opacity: 1;
@@ -1086,7 +526,9 @@ table.odp-table.scan-table td.column-path code {
1086
  /*display: block;*/
1087
  }
1088
 
1089
- .option_label_name .beacon-article > .dashicons,
 
 
1090
  .icwpOptionsForm .option_link_info > .dashicons {
1091
  font-size: 16px;
1092
  text-decoration: none;
@@ -1141,20 +583,6 @@ a:focus .gravatar, a:focus, a:focus .media-icon img {
1141
  .col-form-label {
1142
  font-weight: bold;
1143
  }
1144
- #SectionScanResultsAggregate .card-footer a:hover {
1145
- color: #ffffff;
1146
- background-color: #008000;
1147
- border-color: #008000;
1148
- }
1149
- #ScanPageTabsNav > li a {
1150
- color: #ffffff !important;
1151
- background-color: #008000;
1152
- border-color: #008000 !important;
1153
- }
1154
- #ScanPageTabsNav > li a:hover {
1155
- opacity: 1.0;
1156
- color: #ffffff;
1157
- }
1158
  #SessionsFilter .btn-info {
1159
  color: #ffffff;
1160
  background-color: #008000;
@@ -1199,7 +627,7 @@ a:focus .gravatar, a:focus, a:focus .media-icon img {
1199
  #SectionIpsWhite .form-inline {
1200
  background-color: transparent !important;
1201
  }
1202
- a[target="_blank"]:not(.option_link_info):not(.card-link):not(.table-link)::after {
1203
  content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAQElEQVR42qXKwQkAIAxDUUdxtO6/RBQkQZvSi8I/pL4BoGw/XPkh4XigPmsUgh0626AjRsgxHTkUThsG2T/sIlzdTsp52kSS1wAAAABJRU5ErkJggg==);
1204
  margin: 0 3px;
1205
  }
@@ -1226,10 +654,10 @@ dl.pro-features dd {
1226
  .form-check .form-check-input:checked,
1227
  .option-checkbox.custom-switch .form-check-input:checked {
1228
  background-color: rgba(69, 119, 0, 1) !important;
1229
- border-color: rgba(69, 119, 0, 1)!important;
1230
  }
1231
- .form-check .form-check-input:checked::before ,
1232
- .form-check .form-check-input:checked::before ,
1233
  .option-checkbox.custom-switch .form-check-input:checked::before {
1234
  content: none;
1235
  }
@@ -1487,11 +915,11 @@ a.card_help {
1487
  word-break: keep-all;
1488
  white-space: nowrap;
1489
  box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.2);
1490
- position: absolute;
1491
  bottom: 0;
1492
  padding-left: 160px;
1493
  transition: 0.5s ease;
1494
- z-index: 1;
1495
  }
1496
  #adminmenuback {
1497
  /** To account for the promo bar at the bottom **/
@@ -1525,12 +953,10 @@ body.folded #FooterBannerGoPro {
1525
  #odp-PageHead {
1526
  }
1527
  #apto-PageMainSide {
1528
- background-color: rgb(230 239 230);
1529
- border-color: rgb(177 216 178);
1530
- border-width: 1px 1px 1px 0;
1531
- border-style: solid;
1532
- min-width: 140px;
1533
- width: 140px;
1534
  }
1535
  #NavSideBar a.nav-link:hover {
1536
  }
@@ -1584,39 +1010,25 @@ body.folded #FooterBannerGoPro {
1584
  border-left: 3px solid rgba(0, 128, 0, 0.4);
1585
  padding-left: 6px !important;
1586
  }
1587
- .subnav-menu {
1588
- visibility: hidden;
1589
- background-color: rgba(238, 246, 238, 0.99);
1590
- border: 1px solid rgba(177, 216, 178, 0.99);
1591
- position: absolute;
1592
- left: 130px;
1593
- width: 200px;
1594
- z-index: 1000;
1595
- height: auto;
1596
- box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.4);
1597
- margin-top: -40px;
1598
- opacity: 0;
1599
- transition: visibility 0.15s 0s, opacity 0.15s ease-in;
1600
- }
1601
- .with-submenu.activesub .subnav-menu {
1602
- visibility: visible;
1603
- opacity: 1;
1604
- transition: visibility 0s, opacity 0.15s ease-in;
1605
- z-index: 1001;
1606
- }
1607
  .primary_side_sub_menu {
1608
  }
1609
  .primary_side_sub_menu a {
 
 
1610
  text-decoration: none;
1611
  color: #111111;
1612
  }
1613
  .primary_side_sub_menu a:hover {
 
 
 
1614
  color: #008000;
1615
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
1616
  }
1617
  .primary_side_sub_menu a.active {
1618
- border-left: 3px solid rgba(0, 128, 0, 0.4);
1619
- padding-left: 6px !important;
 
1620
  }
1621
  #SearchDialog .modal-body {
1622
  min-height: 450px;
@@ -1644,59 +1056,25 @@ body.folded #FooterBannerGoPro {
1644
  font-size: 3rem;
1645
  line-height: 3.5rem;
1646
  }
1647
- #odp-PageContainer .summary-card.card {
1648
- border-color: transparent;
1649
- }
1650
- .summary-card.card,
1651
- .summary-card .card-body {
1652
- background-color: transparent;
1653
- }
1654
- .summary-card .card-body {
1655
- padding: 0.1rem;
1656
  }
1657
- .summary-card .card-footer {
1658
- border: 1px solid transparent;
1659
- background-color: transparent;
1660
- }
1661
- .summary-card .card-footer h6 {
1662
- font-size: 0.8rem;
1663
- margin: 0;
1664
  text-align: center;
 
 
 
1665
  }
1666
- .summary-card.enabled .icon,
1667
- .summary-card.enabled .card-footer a {
1668
- color: #008000;
1669
- }
1670
- .summary-card.disabled .icon,
1671
- .summary-card.disabled .card-footer a {
1672
- color: #c23e0c;
1673
- }
1674
- .summary-card a[target="_blank"]::after {
1675
- content: '' !important;
1676
- margin: 0 !important;
1677
- }
1678
- .summary-card .icon > svg {
1679
- width: 64px;
1680
- height: 64px;
1681
- padding: 12px;
1682
- border: 1px solid #008000;
1683
- border-radius: 32px;
1684
- /* Permalink - use to edit and share this gradient: https://colorzilla.com/gradient-editor/#f8ffe8+0,e3f5ab+33,b7df2d+100;Green+3D+%234 */
1685
- background: #f8ffe8; /* Old browsers */
1686
- background: -moz-linear-gradient(top, #f8ffe8 0%, #e3f5ab 33%, #b7df2d 100%); /* FF3.6-15 */
1687
- background: -webkit-linear-gradient(top, #f8ffe8 0%, #e3f5ab 33%, #b7df2d 100%); /* Chrome10-25,Safari5.1-6 */
1688
- background: linear-gradient(to bottom, #f8ffe8 0%, #e3f5ab 33%, #b7df2d 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
1689
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f8ffe8', endColorstr='#b7df2d', GradientType=0); /* IE6-9 */
1690
- }
1691
- .summary-card.disabled .icon > svg {
1692
- border-color: #c23e0c;
1693
- /* Permalink - use to edit and share this gradient: https://colorzilla.com/gradient-editor/#ffeded+0,fcd6d6+12,f9acac+88 */
1694
- background: #ffeded; /* Old browsers */
1695
- background: -moz-linear-gradient(top, #ffeded 0%, #fcd6d6 12%, #f9acac 88%); /* FF3.6-15 */
1696
- background: -webkit-linear-gradient(top, #ffeded 0%, #fcd6d6 12%, #f9acac 88%); /* Chrome10-25,Safari5.1-6 */
1697
- background: linear-gradient(to bottom, #ffeded 0%, #fcd6d6 12%, #f9acac 88%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
1698
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffeded', endColorstr='#f9acac', GradientType=0); /* IE6-9 */
1699
  }
 
1700
  #ScanResultsPlugins li.nav-item a.nav-link {
1701
  /*background-color: white;*/
1702
  border: 1px solid #c2c2c2;
@@ -1744,4 +1122,26 @@ button.btn.action {
1744
  }
1745
  button.bs-placeholder {
1746
  display: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1747
  }
16
  margin: 0;
17
  padding: 0;
18
  }
 
 
 
 
 
 
 
19
  .pluginlogo_16 {
20
  background: url("../images/pluginlogo_16x16.png?ver=1.0.0") no-repeat 0 3px transparent;
21
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  .footer-form-actions {
23
  background: rgba(0, 0, 0, 0.1);
24
  position: fixed;
47
  #ColumnOptions form {
48
  margin-bottom: 50px;
49
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  .row.option_section_row {
51
  margin-left: 0;
52
  padding-bottom: 24px;
56
  box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.2);
57
  padding-bottom: 30px;
58
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  .submit span {
60
  font-size: smaller;
61
  font-style: italic;
62
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  .option_section {
64
  background-color: transparent;
65
  border: 1px dashed transparent;
71
  .option_section textarea {
72
  width: 100%;
73
  }
 
 
74
  .option_section label {
75
  background-color: transparent;
76
  width: 100%;
77
  }
 
 
 
78
  .nonselected_item {
79
  background-color: transparent;
80
  }
85
  background-color: #ffffff;
86
  border: 1px dashed #bbbbbb;
87
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  /** OPTIONS PAGES **/
 
 
 
 
 
89
  label input[type=checkbox] {
90
  height: 16px !important;
91
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  .content-wizards .wizard_slot {
93
  border: 1px solid #dddddd;
94
  border-radius: 3px;
102
  pointer-events: auto;
103
  }
104
  .content-wizards,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  /** NEW FORMS **/
106
  #wpwrap {
107
  background-color: #f1f1f1;
110
  #wpbody-content {
111
  background-color: #f1f1f1;
112
  }
 
 
 
 
 
 
113
  #ModulePageTopRow {
114
  min-width: 760px; /** prevents col breaking **/
115
  }
 
 
 
 
 
 
 
 
 
 
116
  #ColumnModules {
117
  width: 200px;
118
  margin-left: 15px;
160
  }
161
  .modules a.module.active > div {
162
  }
 
 
 
 
 
 
163
  .modules a#tab-license {
164
  color: #1d00ff;
165
  }
 
 
 
 
 
 
 
 
166
  .modules a.module .dashicons-warning {
167
  color: #a56300;
168
  }
172
  .modules a.module .dashicons-warning:before {
173
  font-size: 16px !important;
174
  }
 
 
 
 
 
 
 
 
 
 
 
175
  .form-label {
176
  font-size: 14px;
177
  }
184
  transition: -webkit-transform 0.3s ease;
185
  transition: transform 0.2s ease, -webkit-transform 0.2s ease;
186
  }
 
 
 
 
187
  #IcwpWpsfSecurityAdmin {
188
  background-color: white;
189
  padding: 40px;
195
  padding: 0; /* fix to override for bootstrap4 cuz WP has a .card style*/
196
  }
197
  @media (max-width: 848px) {
 
 
 
 
 
 
 
 
 
 
198
  .modules a.module {
199
  width: 81px;
200
  font-size: 0.7rem;
201
  font-weight: normal;
202
  letter-spacing: -1px;
203
  }
 
 
 
 
 
204
  }
205
  #FooterWizardBanner .text-start {
206
  margin-bottom: 0;
229
  position: sticky;
230
  top: 126px;
231
  }
 
 
 
 
 
 
 
 
232
  /** TABLE: TRAFFIC **/
233
  th.column-code {
234
  width: 84px;
236
  th.column-trans {
237
  width: 114px;
238
  }
 
 
 
239
  /** INSIGHTS **/
240
  /* TABLES */
 
 
 
241
  #odp-PageContainer .card {
242
  box-shadow: none;
243
  margin-top: 0;
260
  font-weight: bold;
261
  color: #008000;
262
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  #odp-PageMain {
264
  font-size: 13px;
265
  }
 
 
 
 
 
266
  .insights_widget .card-body {
267
  padding: 0 0 0 0;
268
  }
278
  .overflow_inner dl {
279
  margin-bottom: 1px;
280
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  #SectionStats {
282
  overflow: auto;
283
  }
 
 
284
  .stat.card .card-header {
285
  font-size: smaller;
 
286
  }
287
  .stat.card .card-body {
288
  font-size: x-large;
297
  #SectionModuleSummaries table tr.row-key_opt {
298
  }
299
  /** TABLES **/
 
 
300
  .icwpAjaxTableContainer .tablenav {
301
  height: auto;
302
  }
343
  padding-left: 0;
344
  padding-right: 0;
345
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
  /** END IPS Insights **/
347
  .scan-select .form-text {
348
  white-space: normal;
359
  #ScanResultsTabs {
360
  }
361
  .insights-sub-nav ul.nav.nav-tabs a.nav-link {
362
+ color: #2b2b2b;
363
  font-size: 0.9rem;
 
364
  background-color: rgba(0, 0, 0, 0.05);
365
  border-color: rgba(0, 0, 0, 0.1);
366
  border-bottom-color: transparent;
367
  box-shadow: 0 -2px 3px rgba(0, 0, 0, 0.03) inset;
368
  opacity: 0.9;
369
+ border-radius: 0;
370
+ padding: 4px 12px;
371
+ margin: 0 0 -1px -1px;
372
  }
373
  .insights-sub-nav ul.nav.nav-tabs a.nav-link:hover {
374
  opacity: 1;
375
  }
376
  .insights-sub-nav ul.nav.nav-tabs a.nav-link.active {
377
+ color: #008000;
378
  background-color: transparent;
379
  border-bottom-color: #f1f1f1;
380
  opacity: 1;
526
  /*display: block;*/
527
  }
528
 
529
+ .option_label_name .beacon-article > sup svg {
530
+ width: 14px;
531
+ }
532
  .icwpOptionsForm .option_link_info > .dashicons {
533
  font-size: 16px;
534
  text-decoration: none;
583
  .col-form-label {
584
  font-weight: bold;
585
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
586
  #SessionsFilter .btn-info {
587
  color: #ffffff;
588
  background-color: #008000;
627
  #SectionIpsWhite .form-inline {
628
  background-color: transparent !important;
629
  }
630
+ a[target="_blank"]:not(.option_link_info):not(.card-link):not(.table-link):not(.meter-analysis-link)::after {
631
  content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAQElEQVR42qXKwQkAIAxDUUdxtO6/RBQkQZvSi8I/pL4BoGw/XPkh4XigPmsUgh0626AjRsgxHTkUThsG2T/sIlzdTsp52kSS1wAAAABJRU5ErkJggg==);
632
  margin: 0 3px;
633
  }
654
  .form-check .form-check-input:checked,
655
  .option-checkbox.custom-switch .form-check-input:checked {
656
  background-color: rgba(69, 119, 0, 1) !important;
657
+ border-color: rgba(69, 119, 0, 1) !important;
658
  }
659
+ .form-check .form-check-input:checked::before,
660
+ .form-check .form-check-input:checked::before,
661
  .option-checkbox.custom-switch .form-check-input:checked::before {
662
  content: none;
663
  }
915
  word-break: keep-all;
916
  white-space: nowrap;
917
  box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.2);
918
+ position: fixed;
919
  bottom: 0;
920
  padding-left: 160px;
921
  transition: 0.5s ease;
922
+ z-index: 100;
923
  }
924
  #adminmenuback {
925
  /** To account for the promo bar at the bottom **/
953
  #odp-PageHead {
954
  }
955
  #apto-PageMainSide {
956
+ background-color: transparent;
957
+ border: 0 none;
958
+ min-width: 160px;
959
+ width: 160px;
 
 
960
  }
961
  #NavSideBar a.nav-link:hover {
962
  }
1010
  border-left: 3px solid rgba(0, 128, 0, 0.4);
1011
  padding-left: 6px !important;
1012
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1013
  .primary_side_sub_menu {
1014
  }
1015
  .primary_side_sub_menu a {
1016
+ border-left: 1px solid rgba(0, 0, 0, 0.2);
1017
+ padding-left: 24px;
1018
  text-decoration: none;
1019
  color: #111111;
1020
  }
1021
  .primary_side_sub_menu a:hover {
1022
+ border-left: 3px solid rgba(0, 128, 0, 0.3);
1023
+ padding-left: 22px;
1024
+
1025
  color: #008000;
1026
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
1027
  }
1028
  .primary_side_sub_menu a.active {
1029
+ color: #008000;
1030
+ border-left: 3px solid rgba(0, 128, 0, 0.7);
1031
+ padding-left: 20px;
1032
  }
1033
  #SearchDialog .modal-body {
1034
  min-height: 450px;
1056
  font-size: 3rem;
1057
  line-height: 3.5rem;
1058
  }
1059
+ #odp-PageContainer .card.progress-meter {
1060
+ box-shadow: 1px 1px 2px rgb(0 0 0 / 20%);
1061
+ background: rgb(253 253 253 / 45%);
1062
+ border-radius: 24px;
1063
+ border: 1px solid rgb(177 216 178);
 
 
 
 
1064
  }
1065
+ .card.progress-meter .card-header {
 
 
 
 
 
 
1066
  text-align: center;
1067
+ padding: 1rem 1rem 0.5rem;
1068
+ background: transparent;
1069
+ border: 0 none;
1070
  }
1071
+ .card.progress-meter .card-footer {
1072
+ text-align: right;
1073
+ border: 0 none;
1074
+ padding: 0.5rem 1rem 1rem;
1075
+ background-color: transparent;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1076
  }
1077
+
1078
  #ScanResultsPlugins li.nav-item a.nav-link {
1079
  /*background-color: white;*/
1080
  border: 1px solid #c2c2c2;
1122
  }
1123
  button.bs-placeholder {
1124
  display: none;
1125
+ }
1126
+
1127
+ #ShieldProgressMeterOffcanvas {
1128
+ width: 600px;
1129
+ }
1130
+ .svg-container > .bi {
1131
+ vertical-align: -0.125em;
1132
+ }
1133
+ #tabEvents .card-body > ul {
1134
+ padding: 0;
1135
+ }
1136
+ #ShieldGeneralPurposeDialog {
1137
+ z-index: 99999; /** Above WP admin bar **/
1138
+ }
1139
+ #ShieldGeneralPurposeDialog .card {
1140
+ max-width: none;
1141
+ padding: 0;
1142
+ margin: 0;
1143
+ border: 0 none;
1144
+ }
1145
+ #ShieldGeneralPurposeDialog .card-header {
1146
+ background: transparent;
1147
  }
resources/images/bootstrap/clipboard2-data-fill.svg ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-clipboard2-data-fill" viewBox="0 0 16 16">
2
+ <path d="M10 .5a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5.5.5 0 0 1-.5.5.5.5 0 0 0-.5.5V2a.5.5 0 0 0 .5.5h5A.5.5 0 0 0 11 2v-.5a.5.5 0 0 0-.5-.5.5.5 0 0 1-.5-.5Z"/>
3
+ <path d="M4.085 1H3.5A1.5 1.5 0 0 0 2 2.5v12A1.5 1.5 0 0 0 3.5 16h9a1.5 1.5 0 0 0 1.5-1.5v-12A1.5 1.5 0 0 0 12.5 1h-.585c.055.156.085.325.085.5V2a1.5 1.5 0 0 1-1.5 1.5h-5A1.5 1.5 0 0 1 4 2v-.5c0-.175.03-.344.085-.5ZM10 7a1 1 0 1 1 2 0v5a1 1 0 1 1-2 0V7Zm-6 4a1 1 0 1 1 2 0v1a1 1 0 1 1-2 0v-1Zm4-3a1 1 0 0 1 1 1v3a1 1 0 1 1-2 0V9a1 1 0 0 1 1-1Z"/>
4
+ </svg>
resources/images/bootstrap/hand-thumbs-up.svg ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-hand-thumbs-up" viewBox="0 0 16 16">
2
+ <path d="M8.864.046C7.908-.193 7.02.53 6.956 1.466c-.072 1.051-.23 2.016-.428 2.59-.125.36-.479 1.013-1.04 1.639-.557.623-1.282 1.178-2.131 1.41C2.685 7.288 2 7.87 2 8.72v4.001c0 .845.682 1.464 1.448 1.545 1.07.114 1.564.415 2.068.723l.048.03c.272.165.578.348.97.484.397.136.861.217 1.466.217h3.5c.937 0 1.599-.477 1.934-1.064a1.86 1.86 0 0 0 .254-.912c0-.152-.023-.312-.077-.464.201-.263.38-.578.488-.901.11-.33.172-.762.004-1.149.069-.13.12-.269.159-.403.077-.27.113-.568.113-.857 0-.288-.036-.585-.113-.856a2.144 2.144 0 0 0-.138-.362 1.9 1.9 0 0 0 .234-1.734c-.206-.592-.682-1.1-1.2-1.272-.847-.282-1.803-.276-2.516-.211a9.84 9.84 0 0 0-.443.05 9.365 9.365 0 0 0-.062-4.509A1.38 1.38 0 0 0 9.125.111L8.864.046zM11.5 14.721H8c-.51 0-.863-.069-1.14-.164-.281-.097-.506-.228-.776-.393l-.04-.024c-.555-.339-1.198-.731-2.49-.868-.333-.036-.554-.29-.554-.55V8.72c0-.254.226-.543.62-.65 1.095-.3 1.977-.996 2.614-1.708.635-.71 1.064-1.475 1.238-1.978.243-.7.407-1.768.482-2.85.025-.362.36-.594.667-.518l.262.066c.16.04.258.143.288.255a8.34 8.34 0 0 1-.145 4.725.5.5 0 0 0 .595.644l.003-.001.014-.003.058-.014a8.908 8.908 0 0 1 1.036-.157c.663-.06 1.457-.054 2.11.164.175.058.45.3.57.65.107.308.087.67-.266 1.022l-.353.353.353.354c.043.043.105.141.154.315.048.167.075.37.075.581 0 .212-.027.414-.075.582-.05.174-.111.272-.154.315l-.353.353.353.354c.047.047.109.177.005.488a2.224 2.224 0 0 1-.505.805l-.353.353.353.354c.006.005.041.05.041.17a.866.866 0 0 1-.121.416c-.165.288-.503.56-1.066.56z"/>
3
+ </svg>
resources/images/bootstrap/question-circle.svg ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-question-circle" viewBox="0 0 16 16">
2
+ <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
3
+ <path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z"/>
4
+ </svg>
resources/images/bootstrap/question-diamond.svg CHANGED
@@ -1,4 +1,4 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-question-diamond" viewBox="0 0 16 16">
2
- <path d="M6.95.435c.58-.58 1.52-.58 2.1 0l6.515 6.516c.58.58.58 1.519 0 2.098L9.05 15.565c-.58.58-1.519.58-2.098 0L.435 9.05a1.482 1.482 0 0 1 0-2.098L6.95.435zm1.4.7a.495.495 0 0 0-.7 0L1.134 7.65a.495.495 0 0 0 0 .7l6.516 6.516a.495.495 0 0 0 .7 0l6.516-6.516a.495.495 0 0 0 0-.7L8.35 1.134z"/>
3
- <path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z"/>
4
  </svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-question-diamond" viewBox="0 0 16 16">
2
+ <path d="M6.95.435c.58-.58 1.52-.58 2.1 0l6.515 6.516c.58.58.58 1.519 0 2.098L9.05 15.565c-.58.58-1.519.58-2.098 0L.435 9.05a1.482 1.482 0 0 1 0-2.098L6.95.435zm1.4.7a.495.495 0 0 0-.7 0L1.134 7.65a.495.495 0 0 0 0 .7l6.516 6.516a.495.495 0 0 0 .7 0l6.516-6.516a.495.495 0 0 0 0-.7L8.35 1.134z"/>
3
+ <path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z"/>
4
  </svg>
resources/images/bootstrap/question-square.svg ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-question-square" viewBox="0 0 16 16">
2
+ <path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
3
+ <path d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z"/>
4
+ </svg>
resources/js/global-plugin.js CHANGED
@@ -13,15 +13,15 @@ iCWP_WPSF_JSErrorTrack.initialise();
13
 
14
  var iCWP_WPSF_ParseAjaxResponse = new function () {
15
  this.parseIt = function ( raw ) {
16
- var parsed = {};
17
  try {
18
  parsed = JSON.parse( raw );
19
  }
20
  catch ( e ) {
21
- var openJsonTag = '##APTO_OPEN##';
22
- var closeJsonTag = '##APTO_CLOSE##';
23
- var start = 0;
24
- var end = 0;
25
 
26
  if ( raw.indexOf( openJsonTag ) >= 0 ) {
27
  start = raw.indexOf( openJsonTag ) + openJsonTag.length;
@@ -56,7 +56,7 @@ var iCWP_WPSF_StandardAjax = new function () {
56
  data: reqData,
57
  dataType: "text",
58
  success: function ( raw ) {
59
- var resp = iCWP_WPSF_ParseAjaxResponse.parseIt( raw );
60
 
61
  if ( typeof resp.data.show_toast === typeof undefined || resp.data.show_toast ) {
62
 
@@ -76,7 +76,7 @@ var iCWP_WPSF_StandardAjax = new function () {
76
  }
77
 
78
  if ( triggerEvent.length > 0 ) {
79
- jQuery( document ).trigger( 'shield-'+triggerEvent, resp );
80
  }
81
  else if ( resp.data.page_reload ) {
82
  setTimeout( function () {
@@ -240,14 +240,42 @@ var iCWP_WPSF_Growl = new function () {
240
  };
241
 
242
  var createDynDiv = function ( sClass ) {
243
- var $oDiv = jQuery( '<div />' ).appendTo( 'body' );
244
- $oDiv.attr( 'id', 'icwp-growl-notice' + Math.floor( (Math.random() * 100) + 1 ) );
245
- $oDiv.addClass( sClass ).addClass( 'icwp-growl-notice' );
246
- return $oDiv;
247
  };
248
 
249
  }();
250
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  var iCWP_WPSF_BodyOverlay = new function () {
252
 
253
  var nOverlays = 0;
@@ -278,4 +306,8 @@ var iCWP_WPSF_BodyOverlay = new function () {
278
 
279
  jQuery( document ).ready( function () {
280
  iCWP_WPSF_BodyOverlay.initialise();
 
 
 
 
281
  } );
13
 
14
  var iCWP_WPSF_ParseAjaxResponse = new function () {
15
  this.parseIt = function ( raw ) {
16
+ let parsed = {};
17
  try {
18
  parsed = JSON.parse( raw );
19
  }
20
  catch ( e ) {
21
+ let openJsonTag = '##APTO_OPEN##';
22
+ let closeJsonTag = '##APTO_CLOSE##';
23
+ let start = 0;
24
+ let end = 0;
25
 
26
  if ( raw.indexOf( openJsonTag ) >= 0 ) {
27
  start = raw.indexOf( openJsonTag ) + openJsonTag.length;
56
  data: reqData,
57
  dataType: "text",
58
  success: function ( raw ) {
59
+ let resp = iCWP_WPSF_ParseAjaxResponse.parseIt( raw );
60
 
61
  if ( typeof resp.data.show_toast === typeof undefined || resp.data.show_toast ) {
62
 
76
  }
77
 
78
  if ( triggerEvent.length > 0 ) {
79
+ jQuery( document ).trigger( 'shield-' + triggerEvent, resp );
80
  }
81
  else if ( resp.data.page_reload ) {
82
  setTimeout( function () {
240
  };
241
 
242
  var createDynDiv = function ( sClass ) {
243
+ let div = jQuery( '<div />' ).appendTo( 'body' );
244
+ div.attr( 'id', 'icwp-growl-notice' + Math.floor( (Math.random() * 100) + 1 ) );
245
+ div.addClass( sClass ).addClass( 'icwp-growl-notice' );
246
+ return div;
247
  };
248
 
249
  }();
250
 
251
+ let Shield_WP_Dashboard_Widget = new function () {
252
+ let data;
253
+ this.render = function ( refresh = 0 ) {
254
+
255
+ let widgetContainer = jQuery( '#ShieldDashboardWidget' );
256
+
257
+ if ( widgetContainer.length === 1 ) {
258
+ widgetContainer.text( 'loading ...' );
259
+ data.ajax.render_dashboard_widget.refresh = refresh;
260
+ jQuery.ajax( {
261
+ type: "POST",
262
+ url: ajaxurl,
263
+ data: data.ajax.render_dashboard_widget,
264
+ dataType: "json",
265
+ success: function ( raw ) {
266
+ widgetContainer.html( raw.data.html );
267
+ }
268
+ } ).fail( function () {
269
+ widgetContainer.text( 'There was a problem loading the content.' )
270
+ } );
271
+ }
272
+ };
273
+ this.initialise = function ( workingData ) {
274
+ data = workingData;
275
+ this.render();
276
+ };
277
+ }();
278
+
279
  var iCWP_WPSF_BodyOverlay = new function () {
280
 
281
  var nOverlays = 0;
306
 
307
  jQuery( document ).ready( function () {
308
  iCWP_WPSF_BodyOverlay.initialise();
309
+
310
+ if ( typeof icwp_wpsf_vars_globalplugin.vars.dashboard_widget !== 'undefined' ) {
311
+ Shield_WP_Dashboard_Widget.initialise( icwp_wpsf_vars_globalplugin.vars.dashboard_widget );
312
+ }
313
  } );
resources/js/ip_detect.js DELETED
@@ -1,15 +0,0 @@
1
- if ( typeof icwp_wpsf_vars_ipdetect !== 'undefined' ) {
2
- let iCWP_WPSF_IP_Detect = new function () {
3
- this.runIpDetect = function () {
4
- jQuery.get( "https://ipinfo.io", function ( response ) {
5
- if ( typeof response !== 'undefined' && typeof response[ 'ip' ] !== 'undefined' ) {
6
- icwp_wpsf_vars_ipdetect.ajax[ 'ip' ] = response[ 'ip' ];
7
- jQuery.post( ajaxurl, icwp_wpsf_vars_ipdetect.ajax ).always();
8
- }
9
- }, "jsonp" );
10
- };
11
- }();
12
- jQuery( document ).ready( function () {
13
- iCWP_WPSF_IP_Detect.runIpDetect();
14
- } );
15
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
resources/js/plugin.js CHANGED
@@ -17,57 +17,51 @@ var iCWP_WPSF_OptionsPages = new function () {
17
  jQuery( document ).on( 'shown.bs.tab', '#ModuleOptionsNav a.nav-link', function ( e ) {
18
  window.location.hash = jQuery( e.target ).attr( "href" ).substr( 1 );
19
  } );
20
-
21
- jQuery( document ).on( 'odp-optsrender', onOptsTabRender );
22
- } );
23
- };
24
-
25
- var onOptsTabRender = function ( evt ) {
26
- var sActiveTabHash = window.location.hash;
27
- if ( typeof sActiveTabHash !== 'undefined' ) {
28
- jQuery( '#ModuleOptionsNav a[href="' + sActiveTabHash + '"]' ).tab( 'show' );
29
- jQuery( 'html,body' ).scrollTop( 0 );
30
- }
31
-
32
- jQuery( function () {
33
- // jQuery( 'a.section_title_info' ).popover( {
34
- // placement: 'bottom',
35
- // trigger: 'click',
36
- // delay: 50,
37
- // html: true
38
- // } );
39
-
40
- // jQuery( '[data-bs-toggle="tooltip"]' ).tooltip( {
41
- // placement: 'left',
42
- // trigger: 'hover focus',
43
- // delay: 150,
44
- // html: false
45
- // } );
46
  } );
47
  };
48
  }();
49
 
50
- let iCWP_WPSF_OptsPageRender = new function () {
51
- this.renderForm = function ( reqData ) {
 
 
52
  iCWP_WPSF_BodyOverlay.show();
 
 
53
  jQuery.ajax(
54
  {
55
  type: "POST",
56
  url: ajaxurl,
57
  data: reqData,
58
- dataType: "text",
59
- success: function ( rawResponse ) {
60
- let response = iCWP_WPSF_ParseAjaxResponse.parseIt( rawResponse );
61
- jQuery( '#ColumnOptions .content-options' )
62
- .html( response.data.html )
63
- .trigger( 'odp-optsrender' );
64
- }
65
- }
66
- ).always(
67
- function () {
68
- iCWP_WPSF_BodyOverlay.hide();
69
  }
70
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  };
72
  }();
73
 
@@ -105,22 +99,18 @@ iCWP_WPSF_Toaster.initialise();
105
 
106
  var iCWP_WPSF_OptionsFormSubmit = new function () {
107
 
108
- let bRequestCurrentlyRunning = false;
109
- var reqParams = icwp_wpsf_vars_base.ajax.mod_options;
110
 
111
  this.submit = function ( msg, success ) {
112
- let $oDiv = createDynDiv( success ? 'success' : 'failed' );
113
- $oDiv.fadeIn().html( msg );
114
  setTimeout( function () {
115
- $oDiv.fadeOut( 5000 );
116
- $oDiv.remove();
117
  }, 4000 );
118
  };
119
 
120
- this.updateAjaxReqParams = function ( params ) {
121
- reqParams = params;
122
- };
123
-
124
  /**
125
  * First try with base64 and failover to lz-string upon abject failure.
126
  * This works around mod_security rules that even unpack b64 encoded params and look
@@ -138,9 +128,9 @@ var iCWP_WPSF_OptionsFormSubmit = new function () {
138
  alert( 'Missing form data' );
139
  return false;
140
  }
141
- reqParams.mod_slug = $form.data( 'mod_slug' );
142
  let reqs = jQuery.extend(
143
- reqParams,
144
  {
145
  'form_params': Base64.encode( formData ),
146
  'enc_params': useCompression ? 'lz-string' : 'b64',
@@ -168,15 +158,11 @@ var iCWP_WPSF_OptionsFormSubmit = new function () {
168
  }
169
 
170
  } ).always( function () {
171
- bRequestCurrentlyRunning = false;
172
- setTimeout( function () {
173
- location.reload();
174
- }, 1000 );
175
  } );
176
-
177
  };
178
 
179
- var handleResponse = function ( raw ) {
180
  let response = iCWP_WPSF_ParseAjaxResponse.parseIt( raw );
181
  let msg;
182
  if ( response === null || typeof response.data === 'undefined'
@@ -187,26 +173,31 @@ var iCWP_WPSF_OptionsFormSubmit = new function () {
187
  msg = response.data.message;
188
  }
189
  iCWP_WPSF_Toaster.showMessage( msg, response.success );
 
 
 
 
 
190
  };
191
 
192
- var submitOptionsForm = function ( event ) {
193
  iCWP_WPSF_BodyOverlay.show();
194
 
195
- if ( bRequestCurrentlyRunning ) {
196
  return false;
197
  }
198
- bRequestCurrentlyRunning = true;
199
  event.preventDefault();
200
 
201
- var $form = jQuery( this );
202
 
203
  var $passwordsReady = true;
204
  jQuery( 'input[type=password]', $form ).each( function () {
205
- var $oPass = jQuery( this );
206
- var $oConfirm = jQuery( '#' + $oPass.attr( 'id' ) + '_confirm', $form );
207
- if ( typeof $oConfirm.attr( 'id' ) !== 'undefined' ) {
208
- if ( $oPass.val() && !$oConfirm.val() ) {
209
- $oConfirm.addClass( 'is-invalid' );
210
  alert( 'Form not submitted due to error: password confirmation field not provided.' );
211
  $passwordsReady = false;
212
  }
@@ -218,15 +209,13 @@ var iCWP_WPSF_OptionsFormSubmit = new function () {
218
  }
219
  };
220
 
221
- this.initialise = function () {
222
- jQuery( document ).ready( function () {
223
- jQuery( document ).on( "submit", 'form.icwpOptionsForm', submitOptionsForm );
224
- } );
225
  };
226
  }();
227
 
228
  iCWP_WPSF_OptionsPages.initialise();
229
- iCWP_WPSF_OptionsFormSubmit.initialise();
230
 
231
  jQuery.fn.icwpWpsfAjaxTable = function ( aOptions ) {
232
 
@@ -350,8 +339,104 @@ if ( typeof icwp_wpsf_vars_plugin !== 'undefined' ) {
350
  } );
351
  }
352
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
  jQuery( document ).ready( function () {
354
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
355
  jQuery( document ).ajaxComplete( function () {
356
  let popoverTriggerList = [].slice.call( document.querySelectorAll( '[data-bs-toggle="popover"]' ) )
357
  popoverTriggerList.map( function ( popoverTriggerEl ) {
@@ -389,18 +474,4 @@ jQuery( document ).ready( function () {
389
  }
390
  }
391
  } );
392
-
393
- jQuery( document ).on( 'click', 'a.beacon-article', function ( evt ) {
394
- evt.preventDefault();
395
- let link = jQuery( evt.currentTarget );
396
- let id = link.data( 'beacon-article-id' );
397
- if ( id ) {
398
- let format = '';
399
- if ( link.data( 'beacon-article-format' ) ) {
400
- format = link.data( 'beacon-article-format' );
401
- }
402
- Beacon( 'article', String( id ), { type: format } );
403
- }
404
- return false;
405
- } );
406
  } );
17
  jQuery( document ).on( 'shown.bs.tab', '#ModuleOptionsNav a.nav-link', function ( e ) {
18
  window.location.hash = jQuery( e.target ).attr( "href" ).substr( 1 );
19
  } );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  } );
21
  };
22
  }();
23
 
24
+ let iCWP_WPSF_Modals = new function () {
25
+ let workingData = {};
26
+
27
+ let renderModalIpAnalysis = function ( ip ) {
28
  iCWP_WPSF_BodyOverlay.show();
29
+ let reqData = workingData.modal_ip_analysis.ajax.render_ip_analysis;
30
+ reqData.ip = ip;
31
  jQuery.ajax(
32
  {
33
  type: "POST",
34
  url: ajaxurl,
35
  data: reqData,
36
+ dataType: "json",
37
+ success: function ( raw ) {
38
+ iCWP_WPSF_Modals.display( raw.data );
39
+ },
 
 
 
 
 
 
 
40
  }
41
+ ).fail( function () {
42
+ } ).always( function () {
43
+ iCWP_WPSF_BodyOverlay.hide();
44
+ } );
45
+ };
46
+
47
+ this.display = function ( params ) {
48
+ let modal = document.getElementById( 'ShieldGeneralPurposeDialog' );
49
+ jQuery( '.modal-dialog', modal ).addClass( 'modal-xl' );
50
+ jQuery( '.modal-title', modal ).html( params.title );
51
+ jQuery( '.modal-body .col', modal ).html( params.body );
52
+ (new bootstrap.Modal( modal )).show();
53
+ };
54
+
55
+ this.setData = function ( key, data ) {
56
+ workingData[ key ] = data;
57
+ };
58
+
59
+ this.initialise = function () {
60
+ jQuery( document ).on( 'click', '.modal_ip_analysis', function ( evt ) {
61
+ evt.preventDefault();
62
+ renderModalIpAnalysis( jQuery( evt.currentTarget ).data( 'ip' ) );
63
+ return false;
64
+ } );
65
  };
66
  }();
67
 
99
 
100
  var iCWP_WPSF_OptionsFormSubmit = new function () {
101
 
102
+ let workingData;
103
+ let requestRunning = false;
104
 
105
  this.submit = function ( msg, success ) {
106
+ let theDiv = createDynDiv( success ? 'success' : 'failed' );
107
+ theDiv.fadeIn().html( msg );
108
  setTimeout( function () {
109
+ theDiv.fadeOut( 5000 );
110
+ theDiv.remove();
111
  }, 4000 );
112
  };
113
 
 
 
 
 
114
  /**
115
  * First try with base64 and failover to lz-string upon abject failure.
116
  * This works around mod_security rules that even unpack b64 encoded params and look
128
  alert( 'Missing form data' );
129
  return false;
130
  }
131
+
132
  let reqs = jQuery.extend(
133
+ workingData.ajax.mod_options_save,
134
  {
135
  'form_params': Base64.encode( formData ),
136
  'enc_params': useCompression ? 'lz-string' : 'b64',
158
  }
159
 
160
  } ).always( function () {
161
+ requestRunning = false;
 
 
 
162
  } );
 
163
  };
164
 
165
+ let handleResponse = function ( raw ) {
166
  let response = iCWP_WPSF_ParseAjaxResponse.parseIt( raw );
167
  let msg;
168
  if ( response === null || typeof response.data === 'undefined'
173
  msg = response.data.message;
174
  }
175
  iCWP_WPSF_Toaster.showMessage( msg, response.success );
176
+
177
+ setTimeout( function () {
178
+ // window.location.replace( response.data.redirect_to );
179
+ window.location.reload();
180
+ }, 1000 );
181
  };
182
 
183
+ let submitOptionsForm = function ( event ) {
184
  iCWP_WPSF_BodyOverlay.show();
185
 
186
+ if ( requestRunning ) {
187
  return false;
188
  }
189
+ requestRunning = true;
190
  event.preventDefault();
191
 
192
+ let $form = jQuery( this );
193
 
194
  var $passwordsReady = true;
195
  jQuery( 'input[type=password]', $form ).each( function () {
196
+ let $pass = jQuery( this );
197
+ let $confirm = jQuery( '#' + $pass.attr( 'id' ) + '_confirm', $form );
198
+ if ( typeof $confirm.attr( 'id' ) !== 'undefined' ) {
199
+ if ( $pass.val() && !$confirm.val() ) {
200
+ $confirm.addClass( 'is-invalid' );
201
  alert( 'Form not submitted due to error: password confirmation field not provided.' );
202
  $passwordsReady = false;
203
  }
209
  }
210
  };
211
 
212
+ this.initialise = function ( data ) {
213
+ workingData = data;
214
+ jQuery( document ).on( "submit", 'form.icwpOptionsForm', submitOptionsForm );
 
215
  };
216
  }();
217
 
218
  iCWP_WPSF_OptionsPages.initialise();
 
219
 
220
  jQuery.fn.icwpWpsfAjaxTable = function ( aOptions ) {
221
 
339
  } );
340
  }
341
 
342
+ let iCWP_WPSF_ProgressMeters = new function () {
343
+
344
+ let data;
345
+ let $canvas;
346
+ let analysisContainer;
347
+
348
+ this.renderAnalysis = function ( meter ) {
349
+ iCWP_WPSF_BodyOverlay.show();
350
+ let reqData = data.ajax.render_meter_analysis;
351
+ reqData.meter = meter;
352
+
353
+ $canvas.html( '<div class="d-flex justify-content-center align-items-center"><div class="spinner-border text-success m-5" role="status"><span class="visually-hidden">Loading...</span></div></div>' );
354
+ analysisContainer.show();
355
+
356
+ jQuery.ajax(
357
+ {
358
+ type: "POST",
359
+ url: ajaxurl,
360
+ data: reqData,
361
+ dataType: "text",
362
+ success: function ( raw ) {
363
+ let response = iCWP_WPSF_ParseAjaxResponse.parseIt( raw );
364
+ $canvas.html( response.data.html );
365
+ }
366
+ }
367
+ ).always(
368
+ function () {
369
+ iCWP_WPSF_BodyOverlay.hide();
370
+ }
371
+ );
372
+ };
373
+
374
+ this.initialise = function ( workingData ) {
375
+ data = workingData;
376
+ $canvas = jQuery( '#ShieldProgressMeterOffcanvas' );
377
+ analysisContainer = new bootstrap.Offcanvas( document.getElementById( 'ShieldProgressMeterOffcanvas' ) );
378
+
379
+ const circle = new CircularProgressBar( 'pie' );
380
+ circle.initial();
381
+ };
382
+ }();
383
+
384
+ let iCWP_WPSF_Helpscout = new function () {
385
+ this.initialise = function ( workingData ) {
386
+ beaconInit();
387
+ window.Beacon( 'init', workingData.beacon_id );
388
+ Beacon( 'navigate', '/' );
389
+
390
+ jQuery( document ).on( 'click', 'a.beacon-article', function ( evt ) {
391
+ evt.preventDefault();
392
+ let link = jQuery( evt.currentTarget );
393
+ let id = link.data( 'beacon-article-id' );
394
+ if ( id ) {
395
+ let format = '';
396
+ if ( link.data( 'beacon-article-format' ) ) {
397
+ format = link.data( 'beacon-article-format' );
398
+ }
399
+ Beacon( 'article', String( id ), { type: format } );
400
+ }
401
+ return false;
402
+ } );
403
+ };
404
+
405
+ let beaconInit = function () {
406
+ !function ( e, t, n ) {
407
+ function a() {
408
+ var e = t.getElementsByTagName( "script" )[ 0 ], n = t.createElement( "script" );
409
+ n.type = "text/javascript", n.async = !0, n.src = "https://beacon-v2.helpscout.net", e.parentNode.insertBefore( n, e )
410
+ }
411
+
412
+ if ( e.Beacon = n = function ( t, n, a ) {
413
+ e.Beacon.readyQueue.push( { method: t, options: n, data: a } )
414
+ }, n.readyQueue = [], "complete" === t.readyState ) return a();
415
+ e.attachEvent ? e.attachEvent( "onload", a ) : e.addEventListener( "load", a, !1 )
416
+ }( window, document, window.Beacon || function () {
417
+ } );
418
+ };
419
+ }();
420
+
421
  jQuery( document ).ready( function () {
422
 
423
+ if ( typeof icwp_wpsf_vars_insights.vars.meters !== 'undefined' ) {
424
+ iCWP_WPSF_ProgressMeters.initialise( icwp_wpsf_vars_insights.vars.meters );
425
+ }
426
+
427
+ if ( typeof icwp_wpsf_vars_plugin.components.mod_options !== 'undefined' ) {
428
+ iCWP_WPSF_OptionsFormSubmit.initialise( icwp_wpsf_vars_plugin.components.mod_options );
429
+ }
430
+
431
+ if ( typeof icwp_wpsf_vars_plugin.components.helpscout !== 'undefined' ) {
432
+ iCWP_WPSF_Helpscout.initialise( icwp_wpsf_vars_plugin.components.helpscout );
433
+ }
434
+
435
+ iCWP_WPSF_Modals.initialise();
436
+ if ( typeof icwp_wpsf_vars_ips.components.modal_ip_analysis !== 'undefined' ) {
437
+ iCWP_WPSF_Modals.setData( 'modal_ip_analysis', icwp_wpsf_vars_ips.components.modal_ip_analysis );
438
+ }
439
+
440
  jQuery( document ).ajaxComplete( function () {
441
  let popoverTriggerList = [].slice.call( document.querySelectorAll( '[data-bs-toggle="popover"]' ) )
442
  popoverTriggerList.map( function ( popoverTriggerEl ) {
474
  }
475
  }
476
  } );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
  } );
resources/js/shield/comments.js DELETED
@@ -1,137 +0,0 @@
1
- /** @var shield_comments object */
2
- var iCWP_WPSF_ShieldCommentGuard = new function () {
3
-
4
- var submitButton;
5
- var origButtonValue;
6
- var nTimerCounter;
7
- var sCountdownTimer;
8
-
9
- this.initialise = function () {
10
- jQuery( document ).ready( function () {
11
- if ( typeof shield_comments !== 'undefined' ) {
12
- insertPlaceHolder_Gasp( this );
13
- }
14
- } );
15
- };
16
-
17
- var reEnableButton = function () {
18
- let nRemaining = shield_comments.vars.cooldown - nTimerCounter;
19
- submitButton.value = shield_comments.strings.js_comment_wait.replace( "%s", nRemaining );
20
- if ( nTimerCounter >= shield_comments.vars.cooldown ) {
21
- submitButton.value = origButtonValue;
22
- submitButton.disabled = false;
23
- clearInterval( sCountdownTimer );
24
- }
25
- nTimerCounter++;
26
- };
27
-
28
- var assignElements = function ( shiep ) {
29
- var maybecheckbox = document.getElementById( '_shieldcb_nombre' );
30
- if ( typeof (maybecheckbox) === "undefined" || maybecheckbox === null ) {
31
- var cbnombre = document.createElement( "input" );
32
- cbnombre.type = "hidden";
33
- cbnombre.id = "_shieldcb_nombre";
34
- cbnombre.name = "cb_nombre";
35
- cbnombre.value = shield_comments.vars.cbname;
36
- shiep.appendChild( cbnombre );
37
-
38
- document.body.style.cursor = 'wait';
39
- submitButton.disabled = true;
40
-
41
- var aAjaxVars = shield_comments.ajax.comment_token;
42
- jQuery.post( aAjaxVars.ajaxurl, aAjaxVars,
43
- function ( oResponse ) {
44
- if ( typeof (oResponse) !== "undefined" && oResponse !== null ) {
45
- if ( oResponse.success ) {
46
- var inputBotts = document.createElement( "input" );
47
- inputBotts.type = "hidden";
48
- inputBotts.name = "botts";
49
- inputBotts.value = aAjaxVars.ts;
50
- var inputToken = document.createElement( "input" );
51
- inputToken.type = "hidden";
52
- inputToken.name = "comment_token";
53
- inputToken.value = oResponse.data.token;
54
-
55
- shiep.appendChild( inputBotts );
56
- shiep.appendChild( inputToken );
57
- }
58
- }
59
- }
60
- ).fail(
61
- function () {
62
- alert( 'There was a problem with the request. Please try reloading the page.' );
63
- }
64
- ).always( function () {
65
- submitButton.disabled = false;
66
- document.body.style.cursor = 'default';
67
- }
68
- );
69
- }
70
- };
71
-
72
- var reDisableButton = function () {
73
- submitButton.value = shield_comments.strings.comment_reload;
74
- submitButton.disabled = true;
75
- };
76
-
77
- var insertPlaceHolder_Gasp = function ( form ) {
78
- var shiep = document.getElementById( shield_comments.vars.uniq );
79
- if ( typeof (shiep) === "undefined" || shiep === null ) {
80
- return;
81
- }
82
-
83
- var shieThe_cb = document.createElement( "input" );
84
- shieThe_cb.type = "checkbox";
85
- shieThe_cb.value = "Y";
86
- shieThe_cb.name = shield_comments.vars.cbname;
87
- shieThe_cb.id = '_' + shieThe_cb.name;
88
- shieThe_cb.onchange = function () {
89
- assignElements( shiep );
90
- };
91
-
92
- var shieThe_lab = document.createElement( "label" );
93
- var shieThe_labspan = document.createElement( "span" );
94
- shieThe_labspan.innerHTML = '&nbsp;' + shield_comments.strings.label;
95
-
96
- shieThe_lab.appendChild( shieThe_cb );
97
- shieThe_lab.appendChild( shieThe_labspan );
98
-
99
- var shishoney = document.createElement( "input" );
100
- shishoney.type = "hidden";
101
- shishoney.name = "sugar_sweet_email";
102
-
103
- shiep.appendChild( shishoney );
104
- shiep.appendChild( shieThe_lab );
105
-
106
- var comForm = shieThe_cb.form;
107
- var subbuttonList = comForm.querySelectorAll( 'input[type="submit"]' );
108
-
109
- if ( typeof (subbuttonList) !== "undefined" ) {
110
-
111
- submitButton = subbuttonList[ 0 ];
112
-
113
- if ( typeof (submitButton) !== "undefined" ) {
114
-
115
- if ( shield_comments.vars.cooldown > 0 ) {
116
- submitButton.disabled = true;
117
- origButtonValue = submitButton.value;
118
- nTimerCounter = 0;
119
- reEnableButton();
120
- sCountdownTimer = setInterval( reEnableButton, 1000 );
121
- }
122
- if ( shield_comments.vars.expires > 0 ) {
123
- setTimeout( reDisableButton, (1000 * shield_comments.vars.expires - 1000) );
124
- }
125
- }
126
- }
127
-
128
- shieThe_cb.form.onsubmit = function () {
129
- if ( shieThe_cb.checked !== true ) {
130
- alert( shield_comments.strings.alert );
131
- return false;
132
- }
133
- return true;
134
- };
135
- };
136
- }();
137
- iCWP_WPSF_ShieldCommentGuard.initialise();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
resources/js/shield/ip_detect.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ if ( typeof icwp_wpsf_vars_ipdetect !== 'undefined' ) {
2
+ jQuery( document ).ready( function () {
3
+ jQuery.getJSON( icwp_wpsf_vars_ipdetect.url, function ( response ) {
4
+ if ( typeof response !== 'undefined' && typeof response[ 'ip' ] !== 'undefined' ) {
5
+ icwp_wpsf_vars_ipdetect.ajax[ 'ip' ] = response[ 'ip' ];
6
+ jQuery.post( ajaxurl, icwp_wpsf_vars_ipdetect.ajax ).always();
7
+ }
8
+ } );
9
+ } );
10
+ }
resources/js/shield/ipanalyse.js CHANGED
@@ -27,22 +27,22 @@ jQuery.fn.icwpWpsfIpAnalyse = function ( options ) {
27
 
28
  jQuery( '#IpReviewContent' ).html( 'loading IP info ...' );
29
 
30
- var aReqData = aOpts[ 'ajax_ip_analyse_build' ];
31
- jQuery.post( ajaxurl, jQuery.extend( aReqData, params ),
32
- function ( oResponse ) {
33
 
34
- if ( oResponse.success ) {
35
  jQuery( '#IpSelectContent' ).addClass( "d-none" );
36
  jQuery( '#IpReviewContent' ).removeClass( "d-none" )
37
- .html( oResponse.data.html );
38
- if ( oResponse.page_reload ) {
39
  location.reload();
40
  }
41
  }
42
  else {
43
  var msg = 'Communications error with site.';
44
- if ( oResponse.data.message !== undefined ) {
45
- msg = oResponse.data.message;
46
  }
47
  jQuery( '#IpSelectContent' ).removeClass( "d-none" );
48
  jQuery( '#IpReviewContent' ).addClass( "d-none" );
@@ -91,7 +91,6 @@ jQuery.fn.icwpWpsfIpAnalyse = function ( options ) {
91
  $ipSelect.append( new Option( theIP, theIP, true, true ) ).trigger( 'change' );
92
  }
93
  $ipSelect.val( theIP );
94
- runAnalysis();
95
  }
96
  else {
97
  let activeTab = localStorage.getItem( 'ipsActiveTab' );
27
 
28
  jQuery( '#IpReviewContent' ).html( 'loading IP info ...' );
29
 
30
+ let reqData = aOpts[ 'ajax_ip_analyse_build' ];
31
+ jQuery.post( ajaxurl, jQuery.extend( reqData, params ),
32
+ function ( response ) {
33
 
34
+ if ( response.success ) {
35
  jQuery( '#IpSelectContent' ).addClass( "d-none" );
36
  jQuery( '#IpReviewContent' ).removeClass( "d-none" )
37
+ .html( response.data.html );
38
+ if ( response.page_reload ) {
39
  location.reload();
40
  }
41
  }
42
  else {
43
  var msg = 'Communications error with site.';
44
+ if ( response.data.message !== undefined ) {
45
+ msg = response.data.message;
46
  }
47
  jQuery( '#IpSelectContent' ).removeClass( "d-none" );
48
  jQuery( '#IpReviewContent' ).addClass( "d-none" );
91
  $ipSelect.append( new Option( theIP, theIP, true, true ) ).trigger( 'change' );
92
  }
93
  $ipSelect.val( theIP );
 
94
  }
95
  else {
96
  let activeTab = localStorage.getItem( 'ipsActiveTab' );
resources/js/shield/navigation.js CHANGED
@@ -1,25 +1,46 @@
1
  jQuery.fn.icwpWpsfPluginNavigation = function ( options ) {
2
 
3
- let currentMenuClickTarget;
4
 
5
  var handleDynamicLoad = function ( evt, response ) {
6
  document.querySelector( '#apto-PageMainBody' ).innerHTML = response.data.html;
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  window.history.replaceState(
9
  {},
10
  response.data.page_title,
11
- response.data.page_url
12
  );
13
 
14
  document.getElementById( 'PageTitle' ).innerHTML = response.data.page_title;
15
 
16
  let activeLinks = document.querySelectorAll( '#NavSideBar a.nav-link.active' );
17
- for ( var i = 0; i < activeLinks.length; i++ ) {
18
  activeLinks[ i ].classList.remove( 'active' );
19
  }
20
- currentMenuClickTarget.classList.add( 'active' );
21
 
22
- let parentNav = currentMenuClickTarget.closest( 'ul' ).closest( 'li.nav-item' );
23
  if ( parentNav !== null ) {
24
  parentNav.querySelector( 'li.nav-item > a.nav-link' ).classList.add( 'active' );
25
  }
@@ -27,12 +48,12 @@ jQuery.fn.icwpWpsfPluginNavigation = function ( options ) {
27
  iCWP_WPSF_BodyOverlay.hide();
28
  };
29
 
30
- var renderDynamicPageLoad = function ( params ) {
31
  sendReq( params );
32
  };
33
 
34
- var sendReq = function ( params ) {
35
- document.querySelector( '#apto-PageMainBody' ).innerHTML = 'Loading ...';
36
  shield_vars_navigation.ajax.dynamic_load.load_params = params;
37
  iCWP_WPSF_StandardAjax.send_ajax_req(
38
  shield_vars_navigation.ajax.dynamic_load, false, 'dynamic_load'
@@ -41,37 +62,26 @@ jQuery.fn.icwpWpsfPluginNavigation = function ( options ) {
41
 
42
  var initialise = function () {
43
 
 
 
44
  jQuery( document ).ready( function () {
45
 
46
  jQuery( document ).on( 'click', 'a.dynamic_body_load', function ( evt ) {
47
  evt.preventDefault();
48
- currentMenuClickTarget = evt.currentTarget;
49
- renderDynamicPageLoad( jQuery( currentMenuClickTarget ).data() );
50
  return false;
51
  } );
52
 
53
- jQuery( document ).on( 'shield-dynamic_load', handleDynamicLoad );
54
-
55
- let navBar = jQuery( '#NavSideBar' );
56
- navBar.on( 'click', 'li.nav-item.with-submenu', function ( evt ) {
57
- let $theLink = jQuery( evt.currentTarget );
58
- if ( $theLink.hasClass( 'activesub' ) ) {
59
- $theLink.removeClass( 'activesub' )
60
- }
61
- else {
62
- jQuery( 'li.nav-item.with-submenu.activesub', navBar ).removeClass( 'activesub' );
63
- $theLink.addClass( 'activesub' )
64
- }
65
- } );
66
- jQuery( document ).on( 'click', function ( evt ) {
67
- if ( !jQuery( evt.target ).closest( navBar ).length && jQuery( navBar ).is( ":visible" ) ) {
68
- jQuery( 'li.nav-item.with-submenu.activesub', navBar ).removeClass( 'activesub' );
69
- }
70
- } );
71
  } );
72
  };
73
 
74
- var opts = jQuery.extend( {}, options );
75
  initialise();
76
 
77
  return this;
1
  jQuery.fn.icwpWpsfPluginNavigation = function ( options ) {
2
 
3
+ let currentMenuLoadItem;
4
 
5
  var handleDynamicLoad = function ( evt, response ) {
6
  document.querySelector( '#apto-PageMainBody' ).innerHTML = response.data.html;
7
 
8
+ let urlHash = window.location.hash ? window.location.hash : '';
9
+ // Using links to specific config sections, we extract the section and trigger the tab show()
10
+ if ( urlHash ) {
11
+ let theTabToShow = document.querySelector( '#tab-navlink-' + urlHash.split( '-' )[ 1 ] );
12
+ if ( theTabToShow ) {
13
+ (new bootstrap.Tab( theTabToShow )).show();
14
+ }
15
+ }
16
+ jQuery( 'html,body' ).scrollTop( 0 );
17
+
18
+ /**
19
+ * we then update the window URL (only after triggering tabs)
20
+ * We need to take into account the window's hash link. We do this by checking
21
+ * for its existence on-page and only add it back to the URL if it's applicable.
22
+ */
23
+ let currentTargetHref = jQuery( currentMenuLoadItem ).data( 'target_href' );
24
+ let replaceStateUrl = currentTargetHref ? currentTargetHref : response.data.page_url;
25
+ let hashOnPageElement = document.getElementById( urlHash.replace( /#/, '' ) );
26
+ if ( hashOnPageElement ) {
27
+ replaceStateUrl += urlHash;
28
+ }
29
  window.history.replaceState(
30
  {},
31
  response.data.page_title,
32
+ replaceStateUrl
33
  );
34
 
35
  document.getElementById( 'PageTitle' ).innerHTML = response.data.page_title;
36
 
37
  let activeLinks = document.querySelectorAll( '#NavSideBar a.nav-link.active' );
38
+ for ( let i = 0; i < activeLinks.length; i++ ) {
39
  activeLinks[ i ].classList.remove( 'active' );
40
  }
41
+ currentMenuLoadItem.classList.add( 'active' );
42
 
43
+ let parentNav = currentMenuLoadItem.closest( 'ul' ).closest( 'li.nav-item' );
44
  if ( parentNav !== null ) {
45
  parentNav.querySelector( 'li.nav-item > a.nav-link' ).classList.add( 'active' );
46
  }
48
  iCWP_WPSF_BodyOverlay.hide();
49
  };
50
 
51
+ let renderDynamicPageLoad = function ( params ) {
52
  sendReq( params );
53
  };
54
 
55
+ let sendReq = function ( params ) {
56
+ document.querySelector( '#apto-PageMainBody' ).innerHTML = '<div class="d-flex justify-content-center align-items-center h-100"><div class="spinner-border text-success m-5" role="status"><span class="visually-hidden">Loading...</span></div></div>';
57
  shield_vars_navigation.ajax.dynamic_load.load_params = params;
58
  iCWP_WPSF_StandardAjax.send_ajax_req(
59
  shield_vars_navigation.ajax.dynamic_load, false, 'dynamic_load'
62
 
63
  var initialise = function () {
64
 
65
+ jQuery( document ).on( 'shield-dynamic_load', handleDynamicLoad );
66
+
67
  jQuery( document ).ready( function () {
68
 
69
  jQuery( document ).on( 'click', 'a.dynamic_body_load', function ( evt ) {
70
  evt.preventDefault();
71
+ currentMenuLoadItem = evt.currentTarget;
72
+ renderDynamicPageLoad( jQuery( currentMenuLoadItem ).data() );
73
  return false;
74
  } );
75
 
76
+ let activePageLink = jQuery( '#NavSideBar a.active.body_content_link.dynamic_body_load' );
77
+ if ( activePageLink.length === 1 ) {
78
+ currentMenuLoadItem = activePageLink[ 0 ];
79
+ renderDynamicPageLoad( jQuery( activePageLink ).data() );
80
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  } );
82
  };
83
 
84
+ let opts = jQuery.extend( {}, options );
85
  initialise();
86
 
87
  return this;
resources/js/shield/shuffle.js DELETED
@@ -1,194 +0,0 @@
1
- var Shuffle = window.Shuffle;
2
-
3
- var ShieldCardShuffle = function ( element, itemSelector ) {
4
- this.element = element;
5
-
6
- this.shuffle = new Shuffle( element, {
7
- itemSelector: itemSelector,
8
- filterMode: Shuffle.FilterMode.ANY,
9
- } );
10
-
11
- // Log events.
12
- // this.addShuffleEventListeners();
13
-
14
- this._activeModFilters = [];
15
- this._activeStateFilters = [];
16
- this._activeFilters = [];
17
-
18
- this.addFilterButtons();
19
- // this.addSorting();
20
- // this.addSearchFilter();
21
-
22
- this.mode = 'additive';
23
- };
24
-
25
- ShieldCardShuffle.prototype.addFilterButtons = function () {
26
- var filterByMod = document.querySelector( '.filter-groups' );
27
- if ( filterByMod ) {
28
- var filterButtons = Array.from( filterByMod.children );
29
- filterButtons.forEach( function ( button ) {
30
- button.addEventListener( 'click', this._handleFilterClick.bind( this ), false );
31
- }, this );
32
- }
33
-
34
- var filterByStates = document.querySelector( '.filter-states' );
35
- if ( filterByStates ) {
36
- var filterStatesButtons = Array.from( filterByStates.children );
37
- filterStatesButtons.forEach( function ( button ) {
38
- button.addEventListener( 'click', this._handleFilterClick.bind( this ), false );
39
- }, this );
40
- }
41
- };
42
-
43
- ShieldCardShuffle.prototype._handleFilterClick = function ( evt ) {
44
- var btn = evt.currentTarget;
45
- var isActive = btn.classList.contains( 'active' );
46
- var btnGroup = btn.getAttribute( 'data-filter' );
47
- var btnCategory = btn.getAttribute( 'data-category' );
48
- // You don't need _both_ of these modes. This is only for the demo.
49
-
50
- // For this custom 'additive' mode in the demo, clicking on filter buttons
51
- // doesn't remove any other filters.
52
- if ( this.mode === 'additive' ) {
53
-
54
- var workingFilters;
55
- if ( btnCategory === 'mod' ) {
56
- workingFilters = this._activeModFilters;
57
- }
58
- else { //'state'
59
- workingFilters = this._activeStateFilters;
60
- }
61
-
62
- // If this button is already active, remove it from the list of filters.
63
- if ( isActive ) {
64
- workingFilters.splice( workingFilters.indexOf( btnGroup ), 1 );
65
- }
66
- else {
67
- workingFilters.push( btnGroup );
68
- }
69
-
70
- btn.classList.toggle( 'active' );
71
-
72
- // Filter elements
73
- // this.shuffle.filter( workingFilter );
74
-
75
- var modFilters = this._activeModFilters;
76
- var stateFilters = this._activeStateFilters;
77
-
78
- this.shuffle.filter( function ( element, shuffle ) {
79
- // If there is a current filter applied, ignore elements that don't match it.
80
- // if ( shuffle.group !== Shuffle.ALL_ITEMS ) {
81
- // Get the item's groups.
82
- // Get the item's groups.
83
- var elemGroups = JSON.parse( element.getAttribute( 'data-groups' ) );
84
- return (modFilters.length === 0 || elemGroups.filter( x => modFilters.includes( x ) ).length > 0)
85
- && (stateFilters.length === 0 || elemGroups.filter( x => stateFilters.includes( x ) ).length > 0);
86
- } );
87
-
88
- // 'exclusive' mode lets only one filter button be active at a time.
89
- }
90
- else {
91
- this._removeActiveClassFromChildren( btn.parentNode );
92
-
93
- var filterGroup;
94
- if ( isActive ) {
95
- btn.classList.remove( 'active' );
96
- filterGroup = Shuffle.ALL_ITEMS;
97
- }
98
- else {
99
- btn.classList.add( 'active' );
100
- filterGroup = btnGroup;
101
- }
102
-
103
- this.shuffle.filter( filterGroup );
104
- }
105
- };
106
-
107
- ShieldCardShuffle.prototype._removeActiveClassFromChildren = function ( parent ) {
108
- var children = parent.children;
109
- for ( var i = children.length - 1; i >= 0; i-- ) {
110
- children[ i ].classList.remove( 'active' );
111
- }
112
- };
113
-
114
- ShieldCardShuffle.prototype.addSorting = function () {
115
- var buttonGroup = document.querySelector( '.sort-options' );
116
- if ( buttonGroup ) {
117
- buttonGroup.addEventListener( 'change', this._handleSortChange.bind( this ) );
118
- }
119
- };
120
-
121
- ShieldCardShuffle.prototype._handleSortChange = function ( evt ) {
122
- // Add and remove `active` class from buttons.
123
- var buttons = Array.from( evt.currentTarget.children );
124
- buttons.forEach( function ( button ) {
125
- if ( button.querySelector( 'input' ).value === evt.target.value ) {
126
- button.classList.add( 'active' );
127
- }
128
- else {
129
- button.classList.remove( 'active' );
130
- }
131
- } );
132
-
133
- // Create the sort options to give to Shuffle.
134
- var value = evt.target.value;
135
- var options = {};
136
-
137
- function sortByDate( element ) {
138
- return Date.parse( element.getAttribute( 'data-date-created' ) );
139
- }
140
-
141
- function sortByTitle( element ) {
142
- return element.getAttribute( 'data-title' ).toLowerCase();
143
- }
144
-
145
- if ( value === 'date-created' ) {
146
- options = {
147
- reverse: true,
148
- by: sortByDate,
149
- };
150
- }
151
- else if ( value === 'title' ) {
152
- options = {
153
- by: sortByTitle,
154
- };
155
- }
156
-
157
- this.shuffle.sort( options );
158
- };
159
-
160
- // Advanced filtering
161
- ShieldCardShuffle.prototype.addSearchFilter = function () {
162
- var searchInput = document.querySelector( '.js-shuffle-search' );
163
- if ( searchInput ) {
164
- searchInput.addEventListener( 'input', this._handleSearchKeyup.bind( this ) );
165
- }
166
- };
167
-
168
- /**
169
- * Filter the shuffle instance by items with a title that matches the search input.
170
- * @param {Event} evt Event object.
171
- */
172
- ShieldCardShuffle.prototype._handleSearchKeyup = function ( evt ) {
173
- var searchText = evt.target.value.toLowerCase();
174
-
175
- this.shuffle.filter( function ( element, shuffle ) {
176
-
177
- // If there is a current filter applied, ignore elements that don't match it.
178
- if ( shuffle.group !== Shuffle.ALL_ITEMS ) {
179
- // Get the item's groups.
180
- var groups = JSON.parse( element.getAttribute( 'data-groups' ) );
181
- var isElementInCurrentGroup = groups.indexOf( shuffle.group ) !== -1;
182
-
183
- // Only search elements in the current group
184
- if ( !isElementInCurrentGroup ) {
185
- return false;
186
- }
187
- }
188
-
189
- var titleElement = element.querySelector( '.picture-item__title' );
190
- var titleText = titleElement.textContent.toLowerCase().trim();
191
-
192
- return titleText.indexOf( searchText ) !== -1;
193
- } );
194
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Blocks/RenderBlockPages/BaseBlockPage.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Blocks\RenderBlockPages;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Render\BasePageDisplay;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
+
9
+ abstract class BaseBlockPage extends BasePageDisplay {
10
+
11
+ use ModConsumer;
12
+
13
+ protected function getResponseCode() :int {
14
+ return 503;
15
+ }
16
+
17
+ protected function getRenderData() :array {
18
+ return Services::DataManipulation()->mergeArraysRecursive(
19
+ $this->getMod()->getUIHandler()->getBaseDisplayData(),
20
+ parent::getRenderData(),
21
+ [
22
+ 'strings' => [
23
+ 'restriction_details' => __( 'Restriction Details', 'wp-simple-firewall' ),
24
+ 'restriction_details_blurb' => $this->getRestrictionDetailsBlurb(),
25
+ ],
26
+ 'vars' => [
27
+ 'restriction_details_points' => $this->getRestrictionDetailsPoints(),
28
+ ],
29
+ ],
30
+ $this->getPageSpecificData()
31
+ );
32
+ }
33
+
34
+ protected function getPageSpecificData() :array {
35
+ return [];
36
+ }
37
+
38
+ protected function getRestrictionDetailsBlurb() :array {
39
+ return [
40
+ 'this_website' => __( "This website uses a security service to monitor requests to check for activity that is malicious, abnormal or unexpected.", 'wp-simple-firewall' ),
41
+ 'activity_recorded' => __( "This activity will have been recorded against your IP address and you may be completely blocked from further site access if similar activity is repeated.", 'wp-simple-firewall' ),
42
+ ];
43
+ }
44
+
45
+ protected function getRestrictionDetailsPoints() :array {
46
+ $WP = Services::WpGeneral();
47
+ return [
48
+ __( 'Your IP Address', 'wp-simple-firewall' ) => Services::IP()->getRequestIp(),
49
+ __( 'Time Now', 'wp-simple-firewall' ) => $WP->getTimeStringForDisplay(),
50
+ __( 'Homepage', 'wp-simple-firewall' ) => $WP->getHomeUrl(),
51
+ ];
52
+ }
53
+
54
+ protected function getTemplateBaseDir() :string {
55
+ return '/pages/block/';
56
+ }
57
+ }
src/lib/src/Blocks/RenderBlockPages/RenderBlockAuthorFishing.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Blocks\RenderBlockPages;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Components\QueryRemainingOffenses;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
+
9
+ class RenderBlockAuthorFishing extends BaseBlockPage {
10
+
11
+ protected function getPageSpecificData() :array {
12
+ $con = $this->getCon();
13
+ return [
14
+ 'strings' => [
15
+ 'page_title' => sprintf( '%s | %s', __( 'Block Username Fishing', 'wp-simple-firewall' ), $con->getHumanName() ),
16
+ 'title' => __( 'Username Fishing Blocked', 'wp-simple-firewall' ),
17
+ 'subtitle' => __( 'Username/Author Fishing is disabled on this site.', 'wp-simple-firewall' ),
18
+ ],
19
+ ];
20
+ }
21
+
22
+ protected function getRestrictionDetailsBlurb() :array {
23
+ $additional = [
24
+ sprintf(
25
+ __( 'The %s query parameter has been blocked to protect against username / author fishing.', 'wp-simple-firewall' ),
26
+ '<code>author</code>'
27
+ )
28
+ ];
29
+
30
+ if ( !$this->getCon()->getModule_SecAdmin()->getWhiteLabelController()->isEnabled() ) {
31
+ $additional[] = sprintf( '<a href="%s" target="_blank">%s</a>',
32
+ 'https://shsec.io/7l',
33
+ __( 'Learn More', 'wp-simple-firewall' )
34
+ );
35
+ }
36
+
37
+ return array_merge( $additional, parent::getRestrictionDetailsBlurb() );
38
+ }
39
+
40
+ protected function getTemplateStub() :string {
41
+ return 'block_page_standard';
42
+ }
43
+ }
src/lib/src/Blocks/RenderBlockPages/RenderBlockFirewall.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Blocks\RenderBlockPages;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Components\QueryRemainingOffenses;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
+
9
+ class RenderBlockFirewall extends BaseBlockPage {
10
+
11
+ protected function getPageSpecificData() :array {
12
+ $con = $this->getCon();
13
+ return [
14
+ 'strings' => [
15
+ 'page_title' => sprintf( '%s | %s', __( 'Request Blocked by Firewall', 'wp-simple-firewall' ), $con->getHumanName() ),
16
+ 'title' => __( 'Request Blocked', 'wp-simple-firewall' ),
17
+ 'subtitle' => __( 'Firewall terminated the request because it triggered a firewall rule.', 'wp-simple-firewall' ),
18
+ ],
19
+ ];
20
+ }
21
+
22
+ protected function getRestrictionDetailsBlurb() :array {
23
+ $messages = apply_filters( 'shield/firewall_die_message', [
24
+ __( "Data scanned in this request matched at least 1 firewall rule and is considered potentially dangerous.", 'wp-simple-firewall' )
25
+ ] );
26
+ return array_merge(
27
+ is_array( $messages ) ? $messages : [],
28
+ parent::getRestrictionDetailsBlurb()
29
+ );
30
+ }
31
+
32
+ protected function getRestrictionDetailsPoints() :array {
33
+ $aux = $this->getAuxData();
34
+ /** @var Firewall\Strings $str */
35
+ $str = $this->getMod()->getStrings();
36
+
37
+ $remainingOffenses = max( 0, ( new QueryRemainingOffenses() )
38
+ ->setMod( $this->getCon()->getModule_IPs() )
39
+ ->setIP( Services::IP()->getRequestIp() )
40
+ ->run() );
41
+
42
+ return array_merge(
43
+ [
44
+ __( 'Remaining Offenses Allowed', 'wp-simple-firewall' ) => $remainingOffenses,
45
+ __( 'Firewall Rule Category', 'wp-simple-firewall' ) => $str->getFirewallCategoryName( $aux[ 'match_category' ] ),
46
+ __( 'Request Parameter', 'wp-simple-firewall' ) => $aux[ 'match_request_param' ],
47
+ __( 'Request Parameter Value', 'wp-simple-firewall' ) => $aux[ 'match_request_value' ],
48
+ __( 'Firewall Pattern', 'wp-simple-firewall' ) => $aux[ 'match_pattern' ],
49
+ ],
50
+ parent::getRestrictionDetailsPoints()
51
+ );
52
+ }
53
+
54
+ protected function getTemplateStub() :string {
55
+ return 'block_page_standard';
56
+ }
57
+ }
src/lib/src/Blocks/RenderBlockPages/RenderBlockIP.php ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Blocks\RenderBlockPages;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+ use FernleafSystems\Wordpress\Services\Utilities\Obfuscate;
8
+
9
+ class RenderBlockIP extends BaseBlockPage {
10
+
11
+ protected function getPageSpecificData() :array {
12
+ $con = $this->getCon();
13
+
14
+ $autoUnblock = $this->renderAutoUnblock();
15
+ $magicLink = $this->renderEmailMagicLinkContent();
16
+
17
+ return [
18
+ 'content' => [
19
+ 'auto_unblock' => $autoUnblock,
20
+ 'email_unblock' => $magicLink,
21
+ ],
22
+ 'flags' => [
23
+ 'has_magiclink' => !empty( $magicLink ),
24
+ 'has_autorecover' => !empty( $autoUnblock ),
25
+ ],
26
+ 'hrefs' => [
27
+ 'how_to_unblock' => 'https://shsec.io/shieldhowtounblock',
28
+ ],
29
+ 'strings' => [
30
+ 'page_title' => sprintf( '%s | %s', __( 'Access Restricted', 'wp-simple-firewall' ), $con->getHumanName() ),
31
+ 'title' => __( 'Access Restricted', 'wp-simple-firewall' ),
32
+ 'subtitle' => __( 'Access from your IP address has been temporarily restricted.', 'wp-simple-firewall' ),
33
+ ],
34
+ ];
35
+ }
36
+
37
+ protected function getTemplateStub() :string {
38
+ return 'block_page_ip';
39
+ }
40
+
41
+ private function renderAutoUnblock() :string {
42
+ /** @var IPs\ModCon $mod */
43
+ $mod = $this->getMod();
44
+ /** @var IPs\Options $opts */
45
+ $opts = $this->getOptions();
46
+
47
+ $ip = Services::IP()->getRequestIp();
48
+ $canAutoRecover = $opts->isEnabledAutoVisitorRecover()
49
+ && $opts->getCanIpRequestAutoUnblock( $ip );
50
+
51
+ $content = '';
52
+
53
+ if ( $canAutoRecover ) {
54
+ $content = $mod->renderTemplate( '/pages/block/autorecover.twig', [
55
+ 'hrefs' => [
56
+ 'home' => Services::WpGeneral()->getHomeUrl( '/' )
57
+ ],
58
+ 'vars' => [
59
+ 'nonce' => $mod->getNonceActionData( 'uau-'.$ip ),
60
+ ],
61
+ 'strings' => [
62
+ 'title' => __( 'Auto-Unblock Your IP', 'wp-simple-firewall' ),
63
+ 'you_can' => __( 'You can automatically unblock your IP address by clicking the button below.', 'wp-simple-firewall' ),
64
+ 'button' => __( 'Unblock My IP Address', 'wp-simple-firewall' ),
65
+ ],
66
+ ] );
67
+ }
68
+
69
+ return $content;
70
+ }
71
+
72
+ protected function getRestrictionDetailsBlurb() :array {
73
+ $blurb = array_merge(
74
+ [
75
+ __( "Too many requests from your IP address have triggered the site's automated defenses.", 'wp-simple-firewall' ),
76
+ ],
77
+ parent::getRestrictionDetailsBlurb()
78
+ );
79
+ unset( $blurb[ 'activity_recorded' ] );
80
+ return $blurb;
81
+ }
82
+
83
+ protected function getRestrictionDetailsPoints() :array {
84
+ /** @var IPs\Options $opts */
85
+ $opts = $this->getOptions();
86
+ return array_merge(
87
+ [
88
+ __( 'Restrictions Lifted', 'wp-simple-firewall' ) => Services::Request()
89
+ ->carbon()
90
+ ->addSeconds( $opts->getAutoExpireTime() )
91
+ ->diffForHumans(),
92
+ ],
93
+ parent::getRestrictionDetailsPoints()
94
+ );
95
+ }
96
+
97
+ private function renderEmailMagicLinkContent() :string {
98
+ $con = $this->getCon();
99
+ /** @var IPs\ModCon $mod */
100
+ $mod = $this->getMod();
101
+ /** @var IPs\Options $opts */
102
+ $opts = $this->getOptions();
103
+
104
+ $content = '';
105
+
106
+ $user = Services::WpUsers()->getCurrentWpUser();
107
+ if ( $user instanceof \WP_User &&
108
+ $opts->isEnabledMagicEmailLinkRecover()
109
+ && $opts->getCanRequestAutoUnblockEmailLink( $user )
110
+ ) {
111
+
112
+ if ( apply_filters( $con->prefix( 'can_user_magic_link' ), true ) ) {
113
+ $content = $mod->renderTemplate( '/pages/block/magic_link.twig', [
114
+ 'flags' => [
115
+ ],
116
+ 'hrefs' => [
117
+ 'unblock' => add_query_arg(
118
+ array_merge(
119
+ $mod->getNonceActionData( 'uaum-init-'.substr( sha1( $user->user_login ), 0, 6 ) ),
120
+ [
121
+ 'ip' => Services::IP()->getRequestIp()
122
+ ]
123
+ ),
124
+ Services::WpGeneral()->getHomeUrl()
125
+ )
126
+ ],
127
+ 'vars' => [
128
+ 'email' => Obfuscate::Email( $user->user_email )
129
+ ],
130
+ 'strings' => [
131
+ 'you_may' => __( 'You can automatically unblock your IP address by clicking the link below.', 'wp-simple-firewall' ),
132
+ 'this_will_send' => __( 'Clicking the button will send you an email letting you unblock your IP address.', 'wp-simple-firewall' ),
133
+ 'assumes_email' => __( 'This assumes that your WordPress site has been properly configured to send email - many are not.', 'wp-simple-firewall' ),
134
+ 'dont_receive' => __( "If you don't receive the email, check your spam and contact your site admin.", 'wp-simple-firewall' ),
135
+ 'limit_60' => __( "You may only use this link once every 60 minutes. If you're repeatedly blocked, ask your site admin to review the audit trail to determine the cause.", 'wp-simple-firewall' ),
136
+ 'same_browser' => __( "When you click the link from your email, it must open up in this web browser.", 'wp-simple-firewall' ),
137
+ 'click_to_send' => __( 'Send Auto-Unblock Link To My Email', 'wp-simple-firewall' )
138
+ ],
139
+ ] );
140
+ }
141
+ }
142
+
143
+ return $content;
144
+ }
145
+ }
src/lib/src/Controller/Admin/AdminBarMenu.php CHANGED
@@ -13,7 +13,7 @@ class AdminBarMenu {
13
  protected function canRun() :bool {
14
  $con = $this->getCon();
15
  return $con->getMeetsBasePermissions() &&
16
- apply_filters( $con->prefix( 'shield/show_admin_bar_menu' ), $con->cfg->properties[ 'show_admin_bar_menu' ] );
17
  }
18
 
19
  protected function run() {
13
  protected function canRun() :bool {
14
  $con = $this->getCon();
15
  return $con->getMeetsBasePermissions() &&
16
+ apply_filters( 'shield/show_admin_bar_menu', $con->cfg->properties[ 'show_admin_bar_menu' ] );
17
  }
18
 
19
  protected function run() {
src/lib/src/Controller/Admin/DashboardWidget.php CHANGED
@@ -26,9 +26,10 @@ class DashboardWidget {
26
  $con = $this->getCon();
27
  wp_add_dashboard_widget(
28
  $con->prefix( 'dashboard_widget' ),
29
- apply_filters( $con->prefix( 'dashboard_widget_title' ), $con->getHumanName() ),
 
30
  function () {
31
- do_action( $this->getCon()->prefix( 'dashboard_widget_content' ) );
32
  }
33
  );
34
  }
26
  $con = $this->getCon();
27
  wp_add_dashboard_widget(
28
  $con->prefix( 'dashboard_widget' ),
29
+ apply_filters( 'shield/dashboard_widget_title',
30
+ sprintf( '%s: %s', $con->getHumanName(), __( 'Overview', 'wp-simple-firewall' ) ) ),
31
  function () {
32
+ echo '<div id="ShieldDashboardWidget"><div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div></div>';
33
  }
34
  );
35
  }
src/lib/src/Controller/Ajax/Init.php CHANGED
@@ -43,7 +43,10 @@ class Init {
43
  $response = $this->normaliseAjaxResponse( $response );
44
  }
45
  else {
46
- $response = [];
 
 
 
47
  $noise = [];
48
  }
49
 
43
  $response = $this->normaliseAjaxResponse( $response );
44
  }
45
  else {
46
+ $response = [
47
+ 'success' => false,
48
+ 'error' => 'There was no AJAX handler available for '.$nonceAction
49
+ ];
50
  $noise = [];
51
  }
52
 
src/lib/src/Controller/Assets/Enqueue.php CHANGED
@@ -63,6 +63,7 @@ class Enqueue {
63
  $toDequeue = [];
64
  $prefix = $this->getCon()->prefix();
65
  $conflictHandles = array_map( 'preg_quote', [
 
66
  'bootstrap',
67
  'wp-notes',
68
  ] );
63
  $toDequeue = [];
64
  $prefix = $this->getCon()->prefix();
65
  $conflictHandles = array_map( 'preg_quote', [
66
+ 'cerber_css', // Really? on every WP admin page?
67
  'bootstrap',
68
  'wp-notes',
69
  ] );
src/lib/src/Controller/Config/ConfigVO.php CHANGED
@@ -3,24 +3,27 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Controller\Config;
4
 
5
  use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
 
6
 
7
  /**
8
- * @property array $properties
9
- * @property array $requirements
10
- * @property array $paths
11
- * @property array $includes
12
- * @property array $menu
13
- * @property array $labels
14
- * @property array $action_links
15
- * @property array $meta
16
- * @property array $plugin_meta
17
- * @property array $upgrade_reqs
18
- * @property array $version_upgrades
19
- * @property array $reqs_rest
 
20
  * -- not part of config file --
21
- * @property string $hash
22
- * @property string $previous_version
23
- * @property array $update_first_detected
 
24
  */
25
  class ConfigVO extends DynPropertiesClass {
26
 
@@ -30,7 +33,6 @@ class ConfigVO extends DynPropertiesClass {
30
  public $rebuilt = false;
31
 
32
  /**
33
- * @param string $key
34
  * @return mixed
35
  */
36
  public function __get( string $key ) {
@@ -53,6 +55,7 @@ class ConfigVO extends DynPropertiesClass {
53
  );
54
  break;
55
 
 
56
  case 'update_first_detected':
57
  if ( empty( $val ) ) {
58
  $val = [];
@@ -67,10 +70,36 @@ class ConfigVO extends DynPropertiesClass {
67
  }
68
  break;
69
 
 
 
 
 
 
 
 
 
 
70
  default:
71
  break;
72
  }
73
 
74
  return $val;
75
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Controller\Config;
4
 
5
  use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Config\ModConfigVO;
7
 
8
  /**
9
+ * @property array $properties
10
+ * @property array $modules
11
+ * @property array $requirements
12
+ * @property array $paths
13
+ * @property array $includes
14
+ * @property array $menu
15
+ * @property array $labels
16
+ * @property array $action_links
17
+ * @property array $meta
18
+ * @property array $plugin_meta
19
+ * @property array $upgrade_reqs
20
+ * @property array $version_upgrades
21
+ * @property array $reqs_rest
22
  * -- not part of config file --
23
+ * @property string $hash
24
+ * @property string $previous_version
25
+ * @property array $update_first_detected
26
+ * @property ModConfigVO[] $mods_cfg
27
  */
28
  class ConfigVO extends DynPropertiesClass {
29
 
33
  public $rebuilt = false;
34
 
35
  /**
 
36
  * @return mixed
37
  */
38
  public function __get( string $key ) {
55
  );
56
  break;
57
 
58
+ case 'modules':
59
  case 'update_first_detected':
60
  if ( empty( $val ) ) {
61
  $val = [];
70
  }
71
  break;
72
 
73
+ case 'mods_cfg':
74
+ $val = array_filter( array_map(
75
+ function ( $cfg ) {
76
+ return is_array( $cfg ) ? ( new ModConfigVO() )->applyFromArray( $cfg ) : null;
77
+ },
78
+ is_array( $val ) ? $val : []
79
+ ) );
80
+ break;
81
+
82
  default:
83
  break;
84
  }
85
 
86
  return $val;
87
  }
88
+
89
+ public function __set( string $key, $value ) {
90
+ switch ( $key ) {
91
+ case 'mods_cfg':
92
+ $value = array_filter( array_map(
93
+ function ( $cfg ) {
94
+ return $cfg instanceof ModConfigVO ? $cfg->getRawData() : null;
95
+ },
96
+ is_array( $value ) ? $value : []
97
+ ) );
98
+ break;
99
+
100
+ default:
101
+ break;
102
+ }
103
+ parent::__set( $key, $value );
104
+ }
105
  }
src/lib/src/Controller/Config/Ops/LoadConfig.php CHANGED
@@ -27,7 +27,6 @@ class LoadConfig {
27
  }
28
 
29
  /**
30
- * @return ConfigVO
31
  * @throws \Exception
32
  */
33
  public function run() :ConfigVO {
27
  }
28
 
29
  /**
 
30
  * @throws \Exception
31
  */
32
  public function run() :ConfigVO {
src/lib/src/Controller/Config/Ops/Read.php CHANGED
@@ -7,8 +7,6 @@ use FernleafSystems\Wordpress\Services\Services;
7
  class Read {
8
 
9
  /**
10
- * @param string $path
11
- * @return array
12
  * @throws \Exception
13
  */
14
  public static function FromFile( string $path ) :array {
@@ -27,8 +25,6 @@ class Read {
27
  }
28
 
29
  /**
30
- * @param string $def
31
- * @return array
32
  * @throws \Exception
33
  */
34
  public static function FromString( string $def ) :array {
7
  class Read {
8
 
9
  /**
 
 
10
  * @throws \Exception
11
  */
12
  public static function FromFile( string $path ) :array {
25
  }
26
 
27
  /**
 
 
28
  * @throws \Exception
29
  */
30
  public static function FromString( string $def ) :array {
src/lib/src/Controller/Controller.php CHANGED
@@ -5,6 +5,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Controller;
5
  use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
  use FernleafSystems\Wordpress\Plugin\Shield;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Controller\Plugin\PluginDeactivate;
 
8
  use FernleafSystems\Wordpress\Services\Services;
9
  use FernleafSystems\Wordpress\Services\Utilities\Options\Transient;
10
 
@@ -13,6 +14,7 @@ use FernleafSystems\Wordpress\Services\Utilities\Options\Transient;
13
  * @property Shield\Controller\Assets\Urls $urls
14
  * @property Shield\Controller\Assets\Paths $paths
15
  * @property Shield\Controller\Assets\Svgs $svgs
 
16
  * @property bool $is_activating
17
  * @property bool $is_mode_debug
18
  * @property bool $is_mode_staging
@@ -30,6 +32,7 @@ use FernleafSystems\Wordpress\Services\Utilities\Options\Transient;
30
  * @property string $base_file
31
  * @property string $root_file
32
  * @property Shield\Modules\Integrations\Lib\MainWP\Common\MainWPVO $mwpVO
 
33
  * @property Shield\Utilities\MU\MUHandler $mu_handler
34
  * @property Shield\Utilities\Nonce\Handler $nonce_handler
35
  * @property Shield\Modules\Events\Lib\EventsService $service_events
@@ -66,10 +69,7 @@ class Controller extends DynPropertiesClass {
66
  return $this;
67
  }
68
 
69
- /**
70
- * @return Shield\Modules\Events\Lib\EventsService
71
- */
72
- public function loadEventsService() {
73
  if ( !isset( $this->service_events ) ) {
74
  $this->service_events = ( new Shield\Modules\Events\Lib\EventsService() )
75
  ->setCon( $this );
@@ -123,6 +123,20 @@ class Controller extends DynPropertiesClass {
123
 
124
  switch ( $key ) {
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  case 'is_activating':
127
  case 'is_my_upgrade':
128
  case 'modules_loaded':
@@ -280,17 +294,24 @@ class Controller extends DynPropertiesClass {
280
  }
281
  }
282
 
 
 
 
 
 
 
283
  private function isMysqlVersionSupported( string $versionToSupport ) :bool {
284
  $mysqlInfo = Services::WpDb()->getMysqlServerInfo();
285
  $supported = empty( $versionToSupport )
286
  || empty( $mysqlInfo )
287
- || ( stripos( $mysqlInfo, 'MariaDB' ) !== false )
288
- || version_compare( preg_replace( '/[^0-9.].*/', '', $mysqlInfo ), $versionToSupport, '>=' );
 
289
  if ( !$supported ) {
290
  $miscFunctions = Services::WpDb()->selectCustom( "HELP miscellaneous_functions" );
291
- if ( !empty( $miscFunctions ) && is_array( $miscFunctions ) ) {
292
- foreach ( $miscFunctions as $func ) {
293
- if ( strtoupper( $func[ 'name' ] ?? '' ) === 'INET6_ATON' ) {
294
  $supported = true;
295
  break;
296
  }
@@ -807,10 +828,10 @@ class Controller extends DynPropertiesClass {
807
  apply_filters( $this->prefix( 'plugin_labels' ), $this->cfg->labels )
808
  );
809
 
810
- $oDP = Services::Data();
811
  foreach ( [ '16x16', '32x32', '128x128' ] as $dimension ) {
812
  $key = 'icon_url_'.$dimension;
813
- if ( !empty( $labels[ $key ] ) && !$oDP->isValidWebUrl( $labels[ $key ] ) ) {
814
  $labels[ $key ] = $this->urls->forImage( $labels[ $key ] );
815
  }
816
  }
@@ -927,8 +948,8 @@ class Controller extends DynPropertiesClass {
927
  return $this->getCfgProperty( 'base_permissions' );
928
  }
929
 
930
- public function isValidAdminArea( bool $bCheckUserPerms = false ) :bool {
931
- if ( $bCheckUserPerms && did_action( 'init' ) && !$this->isPluginAdmin() ) {
932
  return false;
933
  }
934
 
@@ -1106,24 +1127,24 @@ class Controller extends DynPropertiesClass {
1106
  self::$sSessionId = null;
1107
  }
1108
 
1109
- /**
1110
- * @return $this
1111
- */
1112
  public function deleteForceOffFile() {
1113
- if ( $this->getIfForceOffActive() ) {
1114
- Services::WpFs()->deleteFile( $this->getForceOffFilePath() );
1115
- unset( $this->file_forceoff );
1116
  clearstatcache();
1117
  }
1118
- return $this;
1119
  }
1120
 
 
 
 
1121
  public function getIfForceOffActive() :bool {
1122
- return $this->getForceOffFilePath() !== false;
1123
  }
1124
 
1125
  /**
1126
  * @return false|string
 
1127
  */
1128
  protected function getForceOffFilePath() {
1129
  if ( !isset( $this->file_forceoff ) ) {
@@ -1172,17 +1193,12 @@ class Controller extends DynPropertiesClass {
1172
  * We let the \Exception from the core plugin feature to bubble up because it's critical.
1173
  * @return Shield\Modules\Plugin\ModCon
1174
  * @throws \Exception from loadFeatureHandler()
 
1175
  */
1176
  public function loadCorePluginFeatureHandler() {
1177
  $plugin = $this->modules[ 'plugin' ] ?? null;
1178
  if ( !$plugin instanceof Shield\Modules\Plugin\ModCon ) {
1179
- $this->loadFeatureHandler(
1180
- [
1181
- 'slug' => 'plugin',
1182
- 'storage_key' => 'plugin',
1183
- 'load_priority' => 10
1184
- ]
1185
- );
1186
  }
1187
  return $this->modules[ 'plugin' ];
1188
  }
@@ -1191,9 +1207,30 @@ class Controller extends DynPropertiesClass {
1191
  * @throws \Exception
1192
  */
1193
  public function loadAllFeatures() :bool {
1194
- foreach ( array_keys( $this->loadCorePluginFeatureHandler()->getActivePluginFeatures() ) as $slug ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1195
  try {
1196
- $this->getModule( $slug );
1197
  }
1198
  catch ( \Exception $e ) {
1199
  if ( $this->isValidAdminArea() && $this->isPluginAdmin() ) {
@@ -1212,7 +1249,20 @@ class Controller extends DynPropertiesClass {
1212
  ->execute();
1213
 
1214
  do_action( $this->prefix( 'modules_loaded' ) );
1215
- do_action( $this->prefix( 'run_processors' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
1216
  return true;
1217
  }
1218
 
@@ -1223,12 +1273,11 @@ class Controller extends DynPropertiesClass {
1223
  $mod = $this->modules[ $slug ] ?? null;
1224
  if ( !$mod instanceof Shield\Modules\Base\ModCon ) {
1225
  try {
1226
- $mods = $this->loadCorePluginFeatureHandler()->getActivePluginFeatures();
1227
- if ( isset( $mods[ $slug ] ) ) {
1228
- $mod = $this->loadFeatureHandler( $mods[ $slug ] );
1229
- }
1230
  }
1231
  catch ( \Exception $e ) {
 
 
1232
  }
1233
  }
1234
  return $mod;
@@ -1238,6 +1287,10 @@ class Controller extends DynPropertiesClass {
1238
  return $this->getModule( 'audit_trail' );
1239
  }
1240
 
 
 
 
 
1241
  public function getModule_Comments() :Shield\Modules\CommentsFilter\ModCon {
1242
  return $this->getModule( 'comments_filter' );
1243
  }
@@ -1262,10 +1315,18 @@ class Controller extends DynPropertiesClass {
1262
  return $this->getModule( 'firewall' );
1263
  }
1264
 
 
 
 
 
1265
  public function getModule_HackGuard() :Shield\Modules\HackGuard\ModCon {
1266
  return $this->getModule( 'hack_protect' );
1267
  }
1268
 
 
 
 
 
1269
  public function getModule_Insights() :Shield\Modules\Insights\ModCon {
1270
  return $this->getModule( 'insights' );
1271
  }
@@ -1287,7 +1348,7 @@ class Controller extends DynPropertiesClass {
1287
  }
1288
 
1289
  public function getModule_Plugin() :Shield\Modules\Plugin\ModCon {
1290
- return $this->loadCorePluginFeatureHandler();
1291
  }
1292
 
1293
  public function getModule_Reporting() :Shield\Modules\Reporting\ModCon {
@@ -1318,30 +1379,18 @@ class Controller extends DynPropertiesClass {
1318
  * @return Shield\Modules\Base\ModCon|mixed
1319
  * @throws \Exception
1320
  */
1321
- public function loadFeatureHandler( array $modProps ) {
1322
- $modSlug = $modProps[ 'slug' ];
1323
- $mod = $this->modules[ $modSlug ] ?? null;
1324
- if ( $mod instanceof Shield\Modules\Base\ModCon ) {
1325
- return $mod;
1326
- }
1327
-
1328
- if ( empty( $modProps[ 'storage_key' ] ) ) {
1329
- $modProps[ 'storage_key' ] = $modSlug;
1330
- }
1331
- if ( empty( $modProps[ 'namespace' ] ) ) {
1332
- $modProps[ 'namespace' ] = str_replace( ' ', '', ucwords( str_replace( '_', ' ', $modSlug ) ) );
1333
- }
1334
-
1335
- $modName = $modProps[ 'namespace' ];
1336
 
1337
- $className = $this->getModulesNamespace().sprintf( '\\%s\\ModCon', $modName );
 
1338
  if ( !class_exists( $className ) ) {
1339
  // All this to prevent fatal errors if the plugin doesn't install/upgrade correctly
1340
  throw new \Exception( sprintf( 'Class "%s" is missing', $className ) );
1341
  }
1342
 
1343
  $modules = $this->modules;
1344
- $modules[ $modSlug ] = new $className( $this, $modProps );
1345
  $this->modules = $modules;
1346
 
1347
  return $this->modules[ $modSlug ];
5
  use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
  use FernleafSystems\Wordpress\Plugin\Shield;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Controller\Plugin\PluginDeactivate;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Config\LoadConfig;
9
  use FernleafSystems\Wordpress\Services\Services;
10
  use FernleafSystems\Wordpress\Services\Utilities\Options\Transient;
11
 
14
  * @property Shield\Controller\Assets\Urls $urls
15
  * @property Shield\Controller\Assets\Paths $paths
16
  * @property Shield\Controller\Assets\Svgs $svgs
17
+ * @property Shield\Request\ThisRequest $this_req
18
  * @property bool $is_activating
19
  * @property bool $is_mode_debug
20
  * @property bool $is_mode_staging
32
  * @property string $base_file
33
  * @property string $root_file
34
  * @property Shield\Modules\Integrations\Lib\MainWP\Common\MainWPVO $mwpVO
35
+ * @property Shield\Rules\RulesController $rules
36
  * @property Shield\Utilities\MU\MUHandler $mu_handler
37
  * @property Shield\Utilities\Nonce\Handler $nonce_handler
38
  * @property Shield\Modules\Events\Lib\EventsService $service_events
69
  return $this;
70
  }
71
 
72
+ public function loadEventsService() :Shield\Modules\Events\Lib\EventsService {
 
 
 
73
  if ( !isset( $this->service_events ) ) {
74
  $this->service_events = ( new Shield\Modules\Events\Lib\EventsService() )
75
  ->setCon( $this );
123
 
124
  switch ( $key ) {
125
 
126
+ case 'this_req':
127
+ if ( is_null( $val ) ) {
128
+ $val = new Shield\Request\ThisRequest( $this );
129
+ $this->this_req = $val;
130
+ }
131
+ break;
132
+
133
+ case 'rules':
134
+ if ( is_null( $val ) ) {
135
+ $val = ( new Shield\Rules\RulesController() )->setCon( $this );
136
+ $this->rules = $val;
137
+ }
138
+ break;
139
+
140
  case 'is_activating':
141
  case 'is_my_upgrade':
142
  case 'modules_loaded':
294
  }
295
  }
296
 
297
+ /**
298
+ * Supported if:
299
+ * - the mysql version is at least the minimum version
300
+ * - OR: it's mariaDB and it doesn't match the pattern: 5.5.xx-MariaDB
301
+ * - OR: we can find the function 'INET6_ATON'
302
+ */
303
  private function isMysqlVersionSupported( string $versionToSupport ) :bool {
304
  $mysqlInfo = Services::WpDb()->getMysqlServerInfo();
305
  $supported = empty( $versionToSupport )
306
  || empty( $mysqlInfo )
307
+ || version_compare( preg_replace( '/[^\d.].*/', '', $mysqlInfo ), $versionToSupport, '>=' )
308
+ || ( stripos( $mysqlInfo, 'MariaDB' ) !== false && !preg_match( '#5.5.\d+-MariaDB#i', $mysqlInfo ) );
309
+
310
  if ( !$supported ) {
311
  $miscFunctions = Services::WpDb()->selectCustom( "HELP miscellaneous_functions" );
312
+ if ( is_array( $miscFunctions ) ) {
313
+ foreach ( $miscFunctions as $fn ) {
314
+ if ( is_array( $fn ) && strtoupper( $fn[ 'name' ] ?? '' ) === 'INET6_ATON' ) {
315
  $supported = true;
316
  break;
317
  }
828
  apply_filters( $this->prefix( 'plugin_labels' ), $this->cfg->labels )
829
  );
830
 
831
+ $D = Services::Data();
832
  foreach ( [ '16x16', '32x32', '128x128' ] as $dimension ) {
833
  $key = 'icon_url_'.$dimension;
834
+ if ( !empty( $labels[ $key ] ) && !$D->isValidWebUrl( $labels[ $key ] ) ) {
835
  $labels[ $key ] = $this->urls->forImage( $labels[ $key ] );
836
  }
837
  }
948
  return $this->getCfgProperty( 'base_permissions' );
949
  }
950
 
951
+ public function isValidAdminArea( bool $checkUserPerms = false ) :bool {
952
+ if ( $checkUserPerms && did_action( 'init' ) && !$this->isPluginAdmin() ) {
953
  return false;
954
  }
955
 
1127
  self::$sSessionId = null;
1128
  }
1129
 
 
 
 
1130
  public function deleteForceOffFile() {
1131
+ if ( $this->this_req->is_force_off && !empty( $this->file_forceoff ) ) {
1132
+ Services::WpFs()->deleteFile( $this->file_forceoff );
1133
+ $this->this_req->is_force_off = false;
1134
  clearstatcache();
1135
  }
 
1136
  }
1137
 
1138
+ /**
1139
+ * @deprecated 15.0
1140
+ */
1141
  public function getIfForceOffActive() :bool {
1142
+ return (bool)$this->this_req->is_force_off;
1143
  }
1144
 
1145
  /**
1146
  * @return false|string
1147
+ * @deprecated 15.0
1148
  */
1149
  protected function getForceOffFilePath() {
1150
  if ( !isset( $this->file_forceoff ) ) {
1193
  * We let the \Exception from the core plugin feature to bubble up because it's critical.
1194
  * @return Shield\Modules\Plugin\ModCon
1195
  * @throws \Exception from loadFeatureHandler()
1196
+ * @deprecated 15.0
1197
  */
1198
  public function loadCorePluginFeatureHandler() {
1199
  $plugin = $this->modules[ 'plugin' ] ?? null;
1200
  if ( !$plugin instanceof Shield\Modules\Plugin\ModCon ) {
1201
+ $this->getModule_Plugin();
 
 
 
 
 
 
1202
  }
1203
  return $this->modules[ 'plugin' ];
1204
  }
1207
  * @throws \Exception
1208
  */
1209
  public function loadAllFeatures() :bool {
1210
+ $modsCfg = empty( $this->cfg->mods_cfg ) ? [] : $this->cfg->mods_cfg;
1211
+
1212
+ // First load all module Configs
1213
+ foreach ( $this->cfg->modules as $slug ) {
1214
+ $modsCfg[ $slug ] = ( new LoadConfig( $slug, $modsCfg[ $slug ] ?? null ) )
1215
+ ->setCon( $this )
1216
+ ->run();
1217
+ }
1218
+
1219
+ // Order Modules
1220
+ uasort( $modsCfg, function ( $a, $b ) {
1221
+ /** @var Shield\Modules\Base\Config\ModConfigVO $a */
1222
+ /** @var Shield\Modules\Base\Config\ModConfigVO $b */
1223
+ if ( $a->properties[ 'load_priority' ] == $b->properties[ 'load_priority' ] ) {
1224
+ return 0;
1225
+ }
1226
+ return ( $a->properties[ 'load_priority' ] < $b->properties[ 'load_priority' ] ) ? -1 : 1;
1227
+ } );
1228
+
1229
+ $this->cfg->mods_cfg = $modsCfg;
1230
+
1231
+ foreach ( $this->cfg->mods_cfg as $cfg ) {
1232
  try {
1233
+ $this->getModule( $cfg->slug );
1234
  }
1235
  catch ( \Exception $e ) {
1236
  if ( $this->isValidAdminArea() && $this->isPluginAdmin() ) {
1249
  ->execute();
1250
 
1251
  do_action( $this->prefix( 'modules_loaded' ) );
1252
+
1253
+ $this->rules->execute();
1254
+ if ( !$this->cfg->rebuilt && $this->rules->isRulesEngineReady() ) {
1255
+
1256
+ $this->rules->processRules();
1257
+
1258
+ foreach ( $this->modules as $module ) {
1259
+ $module->onRunProcessors();
1260
+ }
1261
+
1262
+ // This is where any rules responses will execute (i.e. after processors are run):
1263
+ do_action( $this->prefix( 'after_run_processors' ) );
1264
+ }
1265
+
1266
  return true;
1267
  }
1268
 
1273
  $mod = $this->modules[ $slug ] ?? null;
1274
  if ( !$mod instanceof Shield\Modules\Base\ModCon ) {
1275
  try {
1276
+ $mod = $this->loadFeatureHandler( $this->cfg->mods_cfg[ $slug ] );
 
 
 
1277
  }
1278
  catch ( \Exception $e ) {
1279
+ error_log( $e->getMessage() );
1280
+ die();
1281
  }
1282
  }
1283
  return $mod;
1287
  return $this->getModule( 'audit_trail' );
1288
  }
1289
 
1290
+ public function getModule_Autoupdates() :Shield\Modules\Autoupdates\ModCon {
1291
+ return $this->getModule( 'autoupdates' );
1292
+ }
1293
+
1294
  public function getModule_Comments() :Shield\Modules\CommentsFilter\ModCon {
1295
  return $this->getModule( 'comments_filter' );
1296
  }
1315
  return $this->getModule( 'firewall' );
1316
  }
1317
 
1318
+ public function getModule_Lockdown() :Shield\Modules\Lockdown\ModCon {
1319
+ return $this->getModule( 'lockdown' );
1320
+ }
1321
+
1322
  public function getModule_HackGuard() :Shield\Modules\HackGuard\ModCon {
1323
  return $this->getModule( 'hack_protect' );
1324
  }
1325
 
1326
+ public function getModule_Headers() :Shield\Modules\Headers\ModCon {
1327
+ return $this->getModule( 'headers' );
1328
+ }
1329
+
1330
  public function getModule_Insights() :Shield\Modules\Insights\ModCon {
1331
  return $this->getModule( 'insights' );
1332
  }
1348
  }
1349
 
1350
  public function getModule_Plugin() :Shield\Modules\Plugin\ModCon {
1351
+ return $this->getModule( 'plugin' );
1352
  }
1353
 
1354
  public function getModule_Reporting() :Shield\Modules\Reporting\ModCon {
1379
  * @return Shield\Modules\Base\ModCon|mixed
1380
  * @throws \Exception
1381
  */
1382
+ private function loadFeatureHandler( Shield\Modules\Base\Config\ModConfigVO $cfg ) {
1383
+ $modSlug = $cfg->properties[ 'slug' ];
 
 
 
 
 
 
 
 
 
 
 
 
 
1384
 
1385
+ $className = $this->getModulesNamespace().sprintf( '\\%s\\ModCon',
1386
+ $cfg->properties[ 'namespace' ] ?? str_replace( ' ', '', ucwords( str_replace( '_', ' ', $modSlug ) ) ) );
1387
  if ( !class_exists( $className ) ) {
1388
  // All this to prevent fatal errors if the plugin doesn't install/upgrade correctly
1389
  throw new \Exception( sprintf( 'Class "%s" is missing', $className ) );
1390
  }
1391
 
1392
  $modules = $this->modules;
1393
+ $modules[ $modSlug ] = new $className( $this, $cfg );
1394
  $this->modules = $modules;
1395
 
1396
  return $this->modules[ $modSlug ];
src/lib/src/Databases/Base/Handler.php CHANGED
@@ -173,6 +173,7 @@ abstract class Handler extends ExecOnceModConsumer {
173
  $sch = $this->getTableSchema();
174
  if ( !$DB->getIfTableExists( $sch->table ) ) {
175
  $DB->doSql( $sch->buildCreate() );
 
176
  }
177
  return $this;
178
  }
173
  $sch = $this->getTableSchema();
174
  if ( !$DB->getIfTableExists( $sch->table ) ) {
175
  $DB->doSql( $sch->buildCreate() );
176
+ $DB->clearResultShowTables();
177
  }
178
  return $this;
179
  }
src/lib/src/Databases/Common/AlignTableWithSchema.php CHANGED
@@ -32,6 +32,7 @@ class AlignTableWithSchema {
32
  $DB = Services::WpDb();
33
  if ( !$DB->getIfTableExists( $this->schema->table ) ) {
34
  $DB->doSql( $this->schema->buildCreate() );
 
35
  }
36
  else {
37
  $this->alignColumns();
32
  $DB = Services::WpDb();
33
  if ( !$DB->getIfTableExists( $this->schema->table ) ) {
34
  $DB->doSql( $this->schema->buildCreate() );
35
+ $DB->clearResultShowTables();
36
  }
37
  else {
38
  $this->alignColumns();
src/lib/src/Databases/Events/Select.php CHANGED
@@ -10,7 +10,6 @@ class Select extends Base\Select {
10
 
11
  /**
12
  * @param string $event
13
- * @return int
14
  */
15
  public function sumEvent( $event ) :int {
16
  return $this->sumEvents( [ $event ] );
@@ -18,7 +17,6 @@ class Select extends Base\Select {
18
 
19
  /**
20
  * @param string[] $events
21
- * @return int
22
  */
23
  public function sumEvents( array $events ) :int {
24
  return (int)$this->filterByEvents( $events )
@@ -35,7 +33,7 @@ class Select extends Base\Select {
35
  /**
36
  * @return int[]
37
  */
38
- public function sumAllEvents() {
39
  $sums = [];
40
 
41
  $allEvents = ( clone $this )->reset()->getAllEvents();
@@ -48,7 +46,6 @@ class Select extends Base\Select {
48
  }
49
 
50
  /**
51
- * @param string $event
52
  * @return EntryVO|null
53
  */
54
  public function getLatestForEvent( string $event ) {
@@ -59,11 +56,11 @@ class Select extends Base\Select {
59
  }
60
 
61
  /**
62
- * @param string $sEvent
63
  * @return EntryVO|null
64
  */
65
- public function getOldestForEvent( $sEvent ) {
66
- return $this->filterByEvent( $sEvent )
67
  ->setOrderBy( 'created_at', 'ASC' )
68
  ->setResultsAsVo( true )
69
  ->first();
@@ -80,7 +77,7 @@ class Select extends Base\Select {
80
  * https://stackoverflow.com/questions/5554075/get-last-distinct-set-of-records
81
  * @return EntryVO[] - keys are event names
82
  */
83
- public function getLatestForAllEvents() {
84
  $latest = [];
85
  $this->setGroupBy( 'event' )
86
  ->setOrderBy( 'created_at', 'DESC' )
@@ -97,24 +94,15 @@ class Select extends Base\Select {
97
  * @return int[]
98
  */
99
  private function getMaxIds() {
100
- $aIds = $this->setCustomSelect( 'MAX(id)' )
101
- ->setGroupBy( 'event' )
102
- ->setResultsAsVo( false )
103
- ->setSelectResultsFormat( ARRAY_A )
104
- ->query();
105
  return array_map(
106
- function ( $aId ) {
107
- return (int)$aId[ 'MAX(id)' ];
108
  },
109
- $aIds
 
 
 
 
110
  );
111
  }
112
-
113
- /**
114
- * @param int $nGreaterThan
115
- * @return $this
116
- */
117
- public function filterByCountGreaterThan( $nGreaterThan ) {
118
- return $this->addWhere( 'count', (int)$nGreaterThan, '>' );
119
- }
120
  }
10
 
11
  /**
12
  * @param string $event
 
13
  */
14
  public function sumEvent( $event ) :int {
15
  return $this->sumEvents( [ $event ] );
17
 
18
  /**
19
  * @param string[] $events
 
20
  */
21
  public function sumEvents( array $events ) :int {
22
  return (int)$this->filterByEvents( $events )
33
  /**
34
  * @return int[]
35
  */
36
+ public function sumAllEvents() :array {
37
  $sums = [];
38
 
39
  $allEvents = ( clone $this )->reset()->getAllEvents();
46
  }
47
 
48
  /**
 
49
  * @return EntryVO|null
50
  */
51
  public function getLatestForEvent( string $event ) {
56
  }
57
 
58
  /**
59
+ * @param string $event
60
  * @return EntryVO|null
61
  */
62
+ public function getOldestForEvent( $event ) {
63
+ return $this->filterByEvent( $event )
64
  ->setOrderBy( 'created_at', 'ASC' )
65
  ->setResultsAsVo( true )
66
  ->first();
77
  * https://stackoverflow.com/questions/5554075/get-last-distinct-set-of-records
78
  * @return EntryVO[] - keys are event names
79
  */
80
+ public function getLatestForAllEvents() :array {
81
  $latest = [];
82
  $this->setGroupBy( 'event' )
83
  ->setOrderBy( 'created_at', 'DESC' )
94
  * @return int[]
95
  */
96
  private function getMaxIds() {
 
 
 
 
 
97
  return array_map(
98
+ function ( $id ) {
99
+ return (int)$id[ 'MAX(id)' ];
100
  },
101
+ $this->setCustomSelect( 'MAX(id)' )
102
+ ->setGroupBy( 'event' )
103
+ ->setResultsAsVo( false )
104
+ ->setSelectResultsFormat( ARRAY_A )
105
+ ->query()
106
  );
107
  }
 
 
 
 
 
 
 
 
108
  }
src/lib/src/Databases/IPs/Handler.php CHANGED
@@ -20,7 +20,7 @@ class Handler extends Base\Handler {
20
  }
21
 
22
  public function cleanLabel( string $label ) :string {
23
- return trim( empty( $label ) ? '' : preg_replace( '#[^\sa-z0-9_-]#i', '', $label ) );
24
  }
25
 
26
  /**
20
  }
21
 
22
  public function cleanLabel( string $label ) :string {
23
+ return trim( empty( $label ) ? '' : preg_replace( '#[^\s\da-z_-]#i', '', $label ) );
24
  }
25
 
26
  /**
src/lib/src/Databases/ReqLogs/QueueReqDbRecordMigrator.php CHANGED
@@ -5,6 +5,9 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\ReqLogs;
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\ReqLogs\Ops as ReqDB;
7
 
 
 
 
8
  class QueueReqDbRecordMigrator extends Shield\Databases\Utility\QueueDbRecordsMigrator {
9
 
10
  public function __construct() {
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\ReqLogs\Ops as ReqDB;
7
 
8
+ /**
9
+ * Used in version 14.1.5 to migrate the request logs to the newer format.
10
+ */
11
  class QueueReqDbRecordMigrator extends Shield\Databases\Utility\QueueDbRecordsMigrator {
12
 
13
  public function __construct() {
src/lib/src/Databases/ScanQueue/Common.php DELETED
@@ -1,59 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\ScanQueue;
4
-
5
- /**
6
- * Trait Filters
7
- * @package FernleafSystems\Wordpress\Plugin\Shield\Databases\ScanQueue
8
- */
9
- trait Common {
10
-
11
- /**
12
- * @param string $sScan
13
- * @return $this
14
- */
15
- public function filterByScan( $sScan ) {
16
- if ( !empty( $sScan ) ) {
17
- $this->addWhereEquals( 'scan', $sScan );
18
- }
19
- return $this;
20
- }
21
-
22
- /**
23
- * @return $this
24
- */
25
- public function filterByNotFinished() {
26
- return $this->addWhereEquals( 'finished_at', 0 );
27
- }
28
-
29
- /**
30
- * @return $this
31
- */
32
- public function filterByNotStarted() {
33
- return $this->addWhereEquals( 'started_at', 0 );
34
- }
35
-
36
- /**
37
- * @return $this
38
- */
39
- public function filterByFinished() {
40
- return $this->addWhereNewerThan( 0, 'finished_at' );
41
- }
42
-
43
- /**
44
- * @return $this
45
- */
46
- public function filterByStarted() {
47
- return $this->addWhereNewerThan( 0, 'started_at' );
48
- }
49
-
50
- /**
51
- * @param string $scan
52
- * @return bool
53
- */
54
- public function forScan( $scan ) {
55
- $this->reset();
56
- return $this->filterByScan( $scan )
57
- ->query();
58
- }
59
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Databases/ScanQueue/Delete.php DELETED
@@ -1,10 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\ScanQueue;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
-
7
- class Delete extends Base\Delete {
8
-
9
- use Common;
10
- }
 
 
 
 
 
 
 
 
 
 
src/lib/src/Databases/ScanQueue/EntryVO.php DELETED
@@ -1,66 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\ScanQueue;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
-
7
- /**
8
- * @property string $scan
9
- * @property array $items
10
- * @property array $results
11
- * @property int $started_at
12
- * @property int $finished_at
13
- */
14
- class EntryVO extends Base\EntryVO {
15
-
16
- /**
17
- * @inheritDoc
18
- */
19
- public function __get( string $key ) {
20
-
21
- $value = parent::__get( $key );
22
-
23
- switch ( $key ) {
24
-
25
- case 'items':
26
- case 'results':
27
- if ( is_string( $value ) && !empty( $value ) ) {
28
- $value = base64_decode( $value );
29
- if ( !empty( $value ) ) {
30
- $value = @json_decode( $value, true );
31
- }
32
- }
33
-
34
- if ( !is_array( $value ) ) {
35
- $value = [];
36
- }
37
- break;
38
-
39
- default:
40
- break;
41
- }
42
- return $value;
43
- }
44
-
45
- /**
46
- * @inheritDoc
47
- */
48
- public function __set( string $key, $value ) {
49
-
50
- switch ( $key ) {
51
-
52
- case 'items':
53
- case 'results':
54
- if ( !is_array( $value ) ) {
55
- $value = [];
56
- }
57
- $value = base64_encode( json_encode( $value ) );
58
- break;
59
-
60
- default:
61
- break;
62
- }
63
-
64
- parent::__set( $key, $value );
65
- }
66
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Databases/ScanQueue/Handler.php DELETED
@@ -1,7 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\ScanQueue;
4
-
5
- class Handler extends \FernleafSystems\Wordpress\Plugin\Shield\Databases\Base\Handler {
6
-
7
- }
 
 
 
 
 
 
 
src/lib/src/Databases/ScanQueue/Insert.php DELETED
@@ -1,9 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\ScanQueue;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
-
7
- class Insert extends Base\Insert {
8
-
9
- }
 
 
 
 
 
 
 
 
 
src/lib/src/Databases/ScanQueue/Select.php DELETED
@@ -1,75 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\ScanQueue;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
-
7
- class Select extends Base\Select {
8
-
9
- use Common;
10
-
11
- public function countAllForEachScan() :array {
12
- /** @var array[] $res */
13
- $res = $this->setCustomSelect( '`scan`,COUNT(*) as count' )
14
- ->setGroupBy( 'scan' )
15
- ->setResultsAsVo( false )
16
- ->setSelectResultsFormat( ARRAY_A )
17
- ->query();
18
- $counts = [];
19
- if ( is_array( $res ) ) {
20
- foreach ( $res as $entry ) {
21
- $counts[ $entry[ 'scan' ] ] = $entry[ 'count' ];
22
- }
23
- }
24
- return $counts;
25
- }
26
-
27
- public function countUnfinishedForEachScan() :array {
28
- /** @var array[] $res */
29
- $res = $this->setCustomSelect( '`scan`,COUNT(*) as count' )
30
- ->filterByNotFinished()
31
- ->setGroupBy( 'scan' )
32
- ->setResultsAsVo( false )
33
- ->setSelectResultsFormat( ARRAY_A )
34
- ->query();
35
- $counts = [];
36
- if ( is_array( $res ) ) {
37
- foreach ( $res as $entry ) {
38
- $counts[ $entry[ 'scan' ] ] = $entry[ 'count' ];
39
- }
40
- }
41
- return $counts;
42
- }
43
-
44
- /**
45
- * Not quite right. it'll only get the latest finished_at, not the currently processing item
46
- */
47
- public function getCurrentScan() :string {
48
- return (string)$this->reset()
49
- ->setColumnsToSelect( [ 'scan' ] )
50
- ->setOrderBy( 'finished_at', 'desc' )
51
- ->setLimit( 1 )
52
- ->queryVar();
53
- }
54
-
55
- /**
56
- * @return string[]
57
- */
58
- public function getInitiatedScans() {
59
- return $this->getDistinctForColumn( 'scan' );
60
- }
61
-
62
- public function getUnfinishedScans() :array {
63
- return $this->reset()
64
- ->filterByNotFinished()
65
- ->addColumnToSelect( 'scan' )
66
- ->setIsDistinct( true )
67
- ->query();
68
- }
69
-
70
- public function countForScan( string $scan ) :int {
71
- return $this->reset()
72
- ->filterByScan( $scan )
73
- ->count();
74
- }
75
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Databases/ScanQueue/Update.php DELETED
@@ -1,34 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\ScanQueue;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
- use FernleafSystems\Wordpress\Services\Services;
7
-
8
- class Update extends Base\Update {
9
-
10
- /**
11
- * @param EntryVO $entry
12
- * @return bool
13
- */
14
- public function storeResults( $entry ) {
15
- return isset( $entry->results ) &&
16
- $this->updateEntry( $entry, [ 'results' => gzcompress( $entry->getRawData()[ 'results' ] ) ] );
17
- }
18
-
19
- /**
20
- * @param EntryVO $entry
21
- * @return bool
22
- */
23
- public function setFinished( $entry ) {
24
- return $this->updateEntry( $entry, [ 'finished_at' => Services::Request()->ts() ] );
25
- }
26
-
27
- /**
28
- * @param EntryVO $entry
29
- * @return bool
30
- */
31
- public function setStarted( $entry ) {
32
- return $this->updateEntry( $entry, [ 'started_at' => Services::Request()->ts() ] );
33
- }
34
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Databases/Scanner/Common.php DELETED
@@ -1,103 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Scanner;
4
-
5
- use FernleafSystems\Wordpress\Services\Services;
6
-
7
- trait Common {
8
-
9
- /**
10
- * @param string $hash
11
- * @return $this
12
- */
13
- public function filterByHash( string $hash ) {
14
- if ( !empty( $hash ) ) {
15
- $this->addWhereEquals( 'hash', $hash );
16
- }
17
- return $this;
18
- }
19
-
20
- /**
21
- * @param string[] $hashes
22
- * @return $this
23
- */
24
- public function filterByHashes( $hashes ) {
25
- return $this->addWhereIn( 'hash', $hashes );
26
- }
27
-
28
- /**
29
- * @return $this
30
- */
31
- public function filterByIgnored() {
32
- return $this->addWhereNewerThan( 0, 'ignored_at' );
33
- }
34
-
35
- /**
36
- * @return $this
37
- */
38
- public function filterByNotIgnored() {
39
- return $this->addWhereEquals( 'ignored_at', 0 );
40
- }
41
-
42
- /**
43
- * @return $this
44
- */
45
- public function filterByNoRepairAttempted() {
46
- return $this->addWhereEquals( 'attempt_repair_at', 0 );
47
- }
48
-
49
- /**
50
- * @return $this
51
- */
52
- public function filterByNotified() {
53
- return $this->addWhereOlderThan( 0, 'notified_at' );
54
- }
55
-
56
- /**
57
- * @return $this
58
- */
59
- public function filterByNotNotified() {
60
- return $this->addWhereEquals( 'notified_at', 0 );
61
- }
62
-
63
- /**
64
- * @param int $nInterval
65
- * @return $this
66
- */
67
- public function filterByNotRecentlyNotified( $nInterval = null ) {
68
- if ( is_null( $nInterval ) ) {
69
- $nInterval = WEEK_IN_SECONDS;
70
- }
71
- return $this->addWhereOlderThan( Services::Request()->ts() - $nInterval, 'notified_at' );
72
- }
73
-
74
- /**
75
- * @param int $nInterval
76
- * @return $this
77
- */
78
- public function filterByIsRecentlyNotified( $nInterval = null ) {
79
- if ( is_null( $nInterval ) ) {
80
- $nInterval = WEEK_IN_SECONDS;
81
- }
82
- return $this->addWhereNewerThan( Services::Request()->ts() - $nInterval, 'notified_at' );
83
- }
84
-
85
- /**
86
- * @param string $sScan
87
- * @return $this
88
- */
89
- public function filterByScan( $sScan ) {
90
- if ( !empty( $sScan ) ) {
91
- $this->filterByScans( [ $sScan ] );
92
- }
93
- return $this;
94
- }
95
-
96
- /**
97
- * @param string[] $aScans
98
- * @return $this
99
- */
100
- public function filterByScans( $aScans ) {
101
- return $this->addWhereIn( 'scan', array_map( 'strtolower', $aScans ) );
102
- }
103
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Databases/Scanner/Delete.php DELETED
@@ -1,20 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Scanner;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
-
7
- class Delete extends Base\Delete {
8
-
9
- use Common;
10
-
11
- /**
12
- * @param string $sScan
13
- * @return bool
14
- */
15
- public function forScan( $sScan ) {
16
- return $this->reset()
17
- ->filterByScan( $sScan )
18
- ->query();
19
- }
20
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Databases/Scanner/EntryVO.php DELETED
@@ -1,18 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Scanner;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
-
7
- /**
8
- * @property string $hash
9
- * @property array $meta
10
- * @property string $scan
11
- * @property int $severity
12
- * @property int $ignored_at
13
- * @property int $notified_at
14
- * @property int $attempt_repair_at
15
- */
16
- class EntryVO extends Base\EntryVO {
17
-
18
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Databases/Scanner/Handler.php DELETED
@@ -1,7 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Scanner;
4
-
5
- class Handler extends \FernleafSystems\Wordpress\Plugin\Shield\Databases\Base\Handler {
6
-
7
- }
 
 
 
 
 
 
 
src/lib/src/Databases/Scanner/Insert.php DELETED
@@ -1,7 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Scanner;
4
-
5
- class Insert extends \FernleafSystems\Wordpress\Plugin\Shield\Databases\Base\Insert {
6
-
7
- }
 
 
 
 
 
 
 
src/lib/src/Databases/Scanner/Select.php DELETED
@@ -1,27 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Scanner;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
-
7
- class Select extends Base\Select {
8
-
9
- use Common;
10
-
11
- public function countForEachScan() :array {
12
- /** @var array[] $res */
13
- $res = $this->setCustomSelect( '`scan`,COUNT(*) as count' )
14
- ->setGroupBy( 'scan' )
15
- ->setResultsAsVo( false )
16
- ->setSelectResultsFormat( ARRAY_A )
17
- ->filterByNotIgnored()
18
- ->query();
19
- $counts = [];
20
- if ( is_array( $res ) ) {
21
- foreach ( $res as $entry ) {
22
- $counts[ $entry[ 'scan' ] ] = $entry[ 'count' ];
23
- }
24
- }
25
- return $counts;
26
- }
27
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Databases/Scanner/Update.php DELETED
@@ -1,41 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Databases\Scanner;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Base;
6
- use FernleafSystems\Wordpress\Services\Services;
7
-
8
- class Update extends Base\Update {
9
-
10
- /**
11
- * @param EntryVO $entry
12
- * @return bool
13
- */
14
- public function setIgnored( $entry ) {
15
- return $this->updateEntry( $entry, [ 'ignored_at' => Services::Request()->ts() ] );
16
- }
17
-
18
- /**
19
- * @param EntryVO $entry
20
- * @return bool
21
- */
22
- public function setNotified( $entry ) {
23
- return $this->updateEntry( $entry, [ 'notified_at' => Services::Request()->ts() ] );
24
- }
25
-
26
- /**
27
- * @param EntryVO $entry
28
- * @return bool
29
- */
30
- public function setNotIgnored( $entry ) {
31
- return $this->updateEntry( $entry, [ 'ignored_at' => 0 ] );
32
- }
33
-
34
- /**
35
- * @param EntryVO $entry
36
- * @return bool
37
- */
38
- public function setNotNotified( $entry ) {
39
- return $this->updateEntry( $entry, [ 'notified_at' => 0 ] );
40
- }
41
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Databases/Session/Update.php CHANGED
@@ -28,26 +28,7 @@ class Update extends Base\Update {
28
  * @return bool
29
  */
30
  public function updateLastActivity( $session ) {
31
- $oR = Services::Request();
32
- return $this->updateSession(
33
- $session,
34
- [
35
- 'last_activity_at' => $oR->ts(),
36
- 'last_activity_uri' => $oR->server( 'REQUEST_URI' )
37
- ]
38
- );
39
- }
40
-
41
- /**
42
- * @param EntryVO $session
43
- * @param int $nExpiresAt
44
- * @return bool
45
- */
46
- public function updateLoginIntentExpiresAt( $session, $nExpiresAt ) {
47
- return $this->updateSession(
48
- $session,
49
- [ 'login_intent_expires_at' => (int)$nExpiresAt ]
50
- );
51
  }
52
 
53
  /**
28
  * @return bool
29
  */
30
  public function updateLastActivity( $session ) {
31
+ return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  }
33
 
34
  /**
src/lib/src/Helpers/QuickAccess.php CHANGED
@@ -1,4 +1,4 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Helpers;
4
 
@@ -6,11 +6,9 @@ use FernleafSystems\Wordpress\Plugin\Shield\Controller\Controller;
6
 
7
  class QuickAccess {
8
 
9
- public static function IsRequestWhiteListed() {
10
  try {
11
- return Controller::GetInstance()
12
- ->getModule_IPs()
13
- ->isVisitorWhitelisted();
14
  }
15
  catch ( \Exception $e ) {
16
  return false;
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Helpers;
4
 
6
 
7
  class QuickAccess {
8
 
9
+ public static function IsRequestWhiteListed() :bool {
10
  try {
11
+ return Controller::GetInstance()->this_req->is_ip_whitelisted;
 
 
12
  }
13
  catch ( \Exception $e ) {
14
  return false;
src/lib/src/Modules/AuditTrail/AdminNotices.php CHANGED
@@ -1,59 +1,9 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Utilities\AdminNotices\NoticeVO;
7
- use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class AdminNotices extends Shield\Modules\Base\AdminNotices {
10
 
11
- /**
12
- * @inheritDoc
13
- */
14
- protected function processNotice( NoticeVO $notice ) {
15
-
16
- switch ( $notice->id ) {
17
-
18
- case 'new-audit-trail':
19
- $this->buildNotice_NewAuditTrail( $notice );
20
- break;
21
-
22
- default:
23
- parent::processNotice( $notice );
24
- break;
25
- }
26
- }
27
-
28
- protected function isDisplayNeeded( NoticeVO $notice ) :bool {
29
- switch ( $notice->id ) {
30
-
31
- case 'new-audit-trail':
32
- $needed = $this->getMod()->isPage_InsightsThisModule();
33
- break;
34
-
35
- default:
36
- $needed = parent::isDisplayNeeded( $notice );
37
- break;
38
- }
39
- return $needed;
40
- }
41
-
42
- private function buildNotice_NewAuditTrail( NoticeVO $notice ) {
43
- $notice->render_data = [
44
- 'notice_attributes' => [],
45
- 'strings' => [
46
- 'title' => __( "Your Security Audit Log Is Brand New", 'wp-simple-firewall' ),
47
- 'lines' => [
48
- __( "We've completely completely rewritten the security audit log making it more detailed, searchable, faster, and more.", 'wp-simple-firewall' ),
49
- __( "Every effort has been made to convert old data to the new format, but it wasn't 100% possible.", 'wp-simple-firewall' )
50
- .' '.__( "Some older events may have missing data, but all new events will populate properly.", 'wp-simple-firewall' ),
51
- ],
52
- 'read_more' => __( 'Click here to read more about the changes', 'wp-simple-firewall' )
53
- ],
54
- 'hrefs' => [
55
- 'read_more' => 'https://shsec.io/kf'
56
- ]
57
- ];
58
- }
59
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
 
 
6
 
7
  class AdminNotices extends Shield\Modules\Base\AdminNotices {
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  }
src/lib/src/Modules/AuditTrail/Insights/OverviewCards.php DELETED
@@ -1,40 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail\Insights;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail;
7
-
8
- class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
9
-
10
- protected function buildModCards() :array {
11
- /** @var AuditTrail\ModCon $mod */
12
- $mod = $this->getMod();
13
- /** @var AuditTrail\Options $opts */
14
- $opts = $this->getOptions();
15
-
16
- $cards = [];
17
-
18
- if ( $mod->isModOptEnabled() ) {
19
- $cards[ 'audit' ] = [
20
- 'name' => __( 'Audit Areas', 'wp-simple-firewall' ),
21
- 'state' => 1,
22
- 'summary' => __( 'All important events on your site are being logged', 'wp-simple-firewall' ),
23
- 'href' => $mod->getUrl_DirectLinkToOption( 'section_enable_audit_contexts' ),
24
- ];
25
-
26
- $cards[ 'audit_length' ] = [
27
- 'name' => __( 'Audit Trail', 'wp-simple-firewall' ),
28
- 'state' => 0,
29
- 'summary' => sprintf( __( 'Audit Trail entries limited to maximum %s days', 'wp-simple-firewall' ), $opts->getAutoCleanDays() ),
30
- 'href' => $mod->getUrl_DirectLinkToOption( 'audit_trail_max_entries' ),
31
- ];
32
- }
33
-
34
- return $cards;
35
- }
36
-
37
- protected function getSectionTitle() :string {
38
- return __( 'Activity Audit Log', 'wp-simple-firewall' );
39
- }
40
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/AuditTrail/Lib/LogTable/DelegateAjaxHandler.php DELETED
@@ -1,53 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail\Lib\LogTable;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Services\Services;
7
-
8
- class DelegateAjaxHandler {
9
-
10
- use Shield\Modules\ModConsumer;
11
-
12
- /**
13
- * @throws \Exception
14
- */
15
- public function processAjaxAction() :array {
16
- $action = Services::Request()->post( 'sub_action' );
17
- switch ( $action ) {
18
-
19
- case 'retrieve_table_data':
20
- $response = $this->retrieveTableData();
21
- break;
22
-
23
- case 'get_request_meta':
24
- $response = $this->getRequestMeta();
25
- break;
26
-
27
- default:
28
- throw new \Exception( 'Not a supported Audit Trail table sub_action: '.$action );
29
- }
30
- return $response;
31
- }
32
-
33
- /**
34
- * @throws \Exception
35
- */
36
- private function retrieveTableData() :array {
37
- $builder = ( new BuildAuditTableData() )->setMod( $this->getMod() );
38
- $builder->table_data = (array)Services::Request()->post( 'table_data', [] );
39
- return [
40
- 'success' => true,
41
- 'datatable_data' => $builder->build(),
42
- ];
43
- }
44
-
45
- private function getRequestMeta() :array {
46
- return [
47
- 'success' => true,
48
- 'html' => ( new Shield\Modules\Data\DB\ReqLogs\GetRequestMeta() )
49
- ->setMod( $this->getCon()->getModule_Data() )
50
- ->retrieve( Services::Request()->post( 'rid' ) )
51
- ];
52
- }
53
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/AuditTrail/Lib/LogTable/LoadRawTableData.php DELETED
@@ -1,147 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail\Lib\LogTable;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail\DB\LoadLogs;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail\DB\LogRecord;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\AuditTrail\Lib\AuditMessageBuilder;
8
- use FernleafSystems\Wordpress\Plugin\Shield\Tables\DataTables\Build\AuditTrail\ForAuditTrail;
9
- use FernleafSystems\Wordpress\Plugin\Shield\Tables\DataTables\LoadData\BaseLoadTableData;
10
- use FernleafSystems\Wordpress\Services\Services;
11
-
12
- class LoadRawTableData extends BaseLoadTableData {
13
-
14
- /**
15
- * @var LogRecord
16
- */
17
- private $log;
18
-
19
- /**
20
- * @param LogRecord[] $records
21
- */
22
- protected function buildTableRowsFromRawLogs( array $records ) :array {
23
- return array_values( array_map(
24
- function ( $log ) {
25
- $this->log = $log;
26
- $data = $this->log->getRawData();
27
- $data[ 'ip' ] = $this->log->ip;
28
- $data[ 'rid' ] = $this->log->rid ?? __( 'Unknown', 'wp-simple-firewall' );
29
- $data[ 'ip_linked' ] = $this->getColumnContent_RequestDetails();
30
- $data[ 'event' ] = $this->getCon()->loadEventsService()->getEventName( $this->log->event_slug );
31
- $data[ 'created_since' ] = $this->getColumnContent_Date( $this->log->created_at );
32
- $data[ 'message' ] = $this->getColumnContent_Message();
33
- $data[ 'user' ] = $this->getColumnContent_User();
34
- $data[ 'user_id' ] = $this->getColumnContent_UserID();
35
- $data[ 'level' ] = $this->getColumnContent_Level();
36
- $data[ 'severity' ] = $this->getColumnContent_SeverityIcon();
37
- $data[ 'meta' ] = $this->getColumnContent_Meta();
38
- return $data;
39
- },
40
- $records
41
- ) );
42
- }
43
-
44
- protected function getSearchableColumns() :array {
45
- // Use the DataTables definition builder to locate searchable columns
46
- return array_filter( array_map(
47
- function ( $column ) {
48
- return ( $column[ 'searchable' ] ?? false ) ? $column[ 'data' ] : '';
49
- },
50
- ( new ForAuditTrail() )
51
- ->setMod( $this->getMod() )
52
- ->buildRaw()[ 'columns' ]
53
- ) );
54
- }
55
-
56
- /**
57
- * @return LogRecord[]
58
- */
59
- protected function getRecords( int $offset = 0, int $limit = 0 ) :array {
60
- $loader = ( new LoadLogs() )->setMod( $this->getCon()->getModule_AuditTrail() );
61
- $loader->limit = $limit;
62
- $loader->offset = $offset;
63
- $loader->order_dir = $this->getOrderDirection();
64
- return array_filter(
65
- $loader->run(),
66
- function ( $logRecord ) {
67
- return $this->getCon()->loadEventsService()->eventExists( $logRecord->event_slug );
68
- }
69
- );
70
- }
71
-
72
- private function getColumnContent_RequestDetails() :string {
73
- return sprintf( '<h6>%s</h6>', $this->getIpAnalysisLink( (string)$this->log->ip ) );
74
- }
75
-
76
- private function getColumnContent_UserID() :string {
77
- return $this->log->meta_data[ 'uid' ] ?? '-';
78
- }
79
-
80
- private function getColumnContent_User() :string {
81
- $content = '-';
82
- $uid = $this->log->meta_data[ 'uid' ] ?? '';
83
- if ( !empty( $uid ) ) {
84
- if ( is_numeric( $uid ) ) {
85
- $user = Services::WpUsers()->getUserById( $uid );
86
- if ( !empty( $user ) ) {
87
- $content = sprintf( '<a href="%s" target="_blank">%s</a>',
88
- Services::WpUsers()->getAdminUrl_ProfileEdit( $user ),
89
- $user->user_login );
90
- }
91
- else {
92
- $content = sprintf( 'Unavailable (ID:%s)', $uid );
93
- }
94
- }
95
- else {
96
- $content = $uid === 'cron' ? 'WP Cron' : 'WP-CLI';
97
- }
98
- }
99
- return $content;
100
- }
101
-
102
- private function getColumnContent_Message() :string {
103
- $msg = AuditMessageBuilder::BuildFromLogRecord( $this->log );
104
- return sprintf( '<span class="message-header">%s</span><textarea readonly rows="%s">%s</textarea>',
105
- $this->getCon()->loadEventsService()->getEventName( $this->log->event_slug ),
106
- count( $msg ) + 1, sanitize_textarea_field( implode( "\n", $msg ) ) );
107
- }
108
-
109
- private function getColumnContent_Meta() :string {
110
- return sprintf(
111
- '<button type="button" class="btn btn-link"'.
112
- ' data-toggle="popover"'.
113
- ' data-rid="%s">%s</button>', $this->log->rid,
114
- sprintf( '<span class="meta-icon">%s</span>',
115
- $this->getCon()->svgs->raw( 'bootstrap/tags.svg' )
116
- )
117
- );
118
- }
119
-
120
- private function getColumnContent_Level() :string {
121
- return $this->getCon()->loadEventsService()->getEventDef( $this->log->event_slug )[ 'level' ];
122
- }
123
-
124
- private function getColumnContent_SeverityIcon() :string {
125
- $level = $this->getColumnContent_Level();
126
- $levelDetails = [
127
- 'alert' => [
128
- 'icon' => 'x-octagon',
129
- ],
130
- 'warning' => [
131
- 'icon' => 'exclamation-triangle',
132
- ],
133
- 'notice' => [
134
- 'icon' => 'info-square',
135
- ],
136
- 'info' => [
137
- 'icon' => 'info-circle',
138
- ],
139
- 'debug' => [
140
- 'icon' => 'question-diamond',
141
- ],
142
- ][ $level ];
143
- return sprintf( '<span class="severity-%s severity-icon">%s</span>', $level,
144
- $this->getCon()->svgs->raw( sprintf( 'bootstrap/%s.svg', $levelDetails[ 'icon' ] ) )
145
- );
146
- }
147
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/AuditTrail/ModCon.php CHANGED
@@ -68,7 +68,7 @@ class ModCon extends BaseShield\ModCon {
68
 
69
  $user = Services::WpUsers()->getUserByEmail( $email );
70
  if ( !empty( $user ) ) {
71
-
72
  $WP = Services::WpGeneral();
73
  $exportData = array_map(
74
  function ( $log ) use ( $WP ) {
@@ -96,10 +96,10 @@ class ModCon extends BaseShield\ModCon {
96
  $exportItems[] = [
97
  'group_id' => $this->getModSlug(),
98
  'group_label' => sprintf( __( '[%s] Audit Trail Entries', 'wp-simple-firewall' ),
99
- $this->getCon()->getHumanName() ),
100
  'group_description' => sprintf( __( '[%s] Audit Trail Entries referencing the given user.', 'wp-simple-firewall' ),
101
- $this->getCon()->getHumanName() ),
102
- 'item_id' => $this->prefix( 'audit-trail' ),
103
  'data' => $exportData,
104
  ];
105
  }
68
 
69
  $user = Services::WpUsers()->getUserByEmail( $email );
70
  if ( !empty( $user ) ) {
71
+ $con = $this->getCon();
72
  $WP = Services::WpGeneral();
73
  $exportData = array_map(
74
  function ( $log ) use ( $WP ) {
96
  $exportItems[] = [
97
  'group_id' => $this->getModSlug(),
98
  'group_label' => sprintf( __( '[%s] Audit Trail Entries', 'wp-simple-firewall' ),
99
+ $con->getHumanName() ),
100
  'group_description' => sprintf( __( '[%s] Audit Trail Entries referencing the given user.', 'wp-simple-firewall' ),
101
+ $con->getHumanName() ),
102
+ 'item_id' => $con->prefix( 'audit-trail' ),
103
  'data' => $exportData,
104
  ];
105
  }
src/lib/src/Modules/AuditTrail/Strings.php CHANGED
@@ -241,15 +241,6 @@ class Strings extends Base\Strings {
241
  $titleShort = __( 'Log To DB', 'wp-simple-firewall' );
242
  break;
243
 
244
- case 'section_enable_audit_contexts' :
245
- $title = __( 'Enable Audit Areas', 'wp-simple-firewall' );
246
- $summary = [
247
- sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Specify which types of actions on your site are logged.', 'wp-simple-firewall' ) ),
248
- sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), __( 'These settings are dependent on your requirements.', 'wp-simple-firewall' ) )
249
- ];
250
- $titleShort = __( 'Audit Areas', 'wp-simple-firewall' );
251
- break;
252
-
253
  default:
254
  return parent::getSectionStrings( $section );
255
  }
241
  $titleShort = __( 'Log To DB', 'wp-simple-firewall' );
242
  break;
243
 
 
 
 
 
 
 
 
 
 
244
  default:
245
  return parent::getSectionStrings( $section );
246
  }
src/lib/src/Modules/Autoupdates/Insights/OverviewCards.php DELETED
@@ -1,151 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Autoupdates\Insights;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Autoupdates;
7
- use FernleafSystems\Wordpress\Services\Services;
8
-
9
- class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
10
-
11
- protected function buildModCards() :array {
12
- /** @var Autoupdates\ModCon $mod */
13
- $mod = $this->getMod();
14
- /** @var Autoupdates\Options $opts */
15
- $opts = $this->getOptions();
16
- $WP = Services::WpGeneral();
17
-
18
- $cards = [];
19
-
20
- if ( $mod->isModOptEnabled() ) {
21
- $hasUpdate = $WP->hasCoreUpdate();
22
- $cards[ 'core_update' ] = [
23
- 'name' => __( 'Core Update', 'wp-simple-firewall' ),
24
- 'state' => $hasUpdate ? -1 : 1,
25
- 'summary' => $hasUpdate ?
26
- __( 'WordPress Core is up-to-date', 'wp-simple-firewall' )
27
- : __( "No WordPress Core upgrades waiting to be applied", 'wp-simple-firewall' ),
28
- 'href' => $WP->getAdminUrl_Updates( true ),
29
- 'help' => __( 'Core upgrades should be applied as early as possible.', 'wp-simple-firewall' )
30
- ];
31
-
32
- $canCore = Services::WpGeneral()->canCoreUpdateAutomatically();
33
- $cards[ 'core_minor' ] = [
34
- 'name' => __( 'Auto Core Updates', 'wp-simple-firewall' ),
35
- 'state' => $canCore ? 1 : -1,
36
- 'summary' => $canCore ?
37
- __( 'Minor WP Core updates will be installed automatically', 'wp-simple-firewall' )
38
- : __( 'Minor WP Core updates will not be installed automatically', 'wp-simple-firewall' ),
39
- 'href' => $mod->getUrl_DirectLinkToOption( 'autoupdate_core' ),
40
- ];
41
-
42
- $hasDelay = $mod->isModOptEnabled() && $opts->getDelayUpdatesPeriod();
43
- $cards[ 'delay' ] = [
44
- 'name' => __( 'Update Delay', 'wp-simple-firewall' ),
45
- 'state' => $hasDelay ? 1 : -1,
46
- 'summary' => $hasDelay ?
47
- __( 'Automatic updates are applied after a short delay', 'wp-simple-firewall' )
48
- : __( 'Automatic updates are applied immediately', 'wp-simple-firewall' ),
49
- 'href' => $mod->getUrl_DirectLinkToOption( 'update_delay' ),
50
- ];
51
-
52
- $sName = $this->getCon()->getHumanName();
53
- $bSelfAuto = $mod->isModOptEnabled()
54
- && in_array( $opts->getSelfAutoUpdateOpt(), [ 'auto', 'immediate' ] );
55
- $cards[ 'self' ] = [
56
- 'name' => __( 'Self Auto-Update', 'wp-simple-firewall' ),
57
- 'state' => $bSelfAuto ? 1 : -1,
58
- 'summary' => $bSelfAuto ?
59
- sprintf( __( '%s upgrades are installed automatically', 'wp-simple-firewall' ), $sName )
60
- : sprintf( __( "%s upgrades aren't installed automatically", 'wp-simple-firewall' ), $sName ),
61
- 'href' => $mod->getUrl_DirectLinkToOption( 'autoupdate_plugin_self' ),
62
- ];
63
- }
64
-
65
- //really disabled?
66
- if ( $mod->isModOptEnabled()
67
- && $opts->isDisableAllAutoUpdates() && !$WP->getWpAutomaticUpdater()->is_disabled() ) {
68
- $cards[ 'messages' ][ 'disabled_auto' ] = [
69
- 'name' => 'Auto Updates Not Really Disabled',
70
- 'summary' => __( 'Automatic Updates Are Not Disabled As Expected.', 'wp-simple-firewall' ),
71
- 'href' => $mod->getUrl_DirectLinkToOption( 'enable_autoupdate_disable_all' ),
72
- 'action' => sprintf( __( 'Go To %s', 'wp-simple-firewall' ), __( 'Options', 'wp-simple-firewall' ) ),
73
- 'help' => sprintf( __( 'A plugin/theme other than %s is affecting your automatic update settings.', 'wp-simple-firewall' ), $this->getCon()
74
- ->getHumanName() ),
75
- 'state' => -2
76
- ];
77
- }
78
-
79
- return array_merge(
80
- $cards,
81
- $this->getCardsForPlugins(),
82
- $this->getCardsForThemes()
83
- );
84
- }
85
-
86
- protected function getSectionTitle() :string {
87
- return __( 'Automatic Updates', 'wp-simple-firewall' );
88
- }
89
-
90
- protected function getSectionSubTitle() :string {
91
- return __( 'Controlling WordPress Automatic Updates', 'wp-simple-firewall' );
92
- }
93
-
94
- private function getCardsForPlugins() :array {
95
- $cards = [];
96
-
97
- $WPP = Services::WpPlugins();
98
- $nCount = count( $WPP->getPlugins() ) - count( $WPP->getActivePlugins() );
99
- $cards[ 'plugins_inactive' ] = [
100
- 'name' => __( 'Inactive Plugins', 'wp-simple-firewall' ),
101
- 'state' => $nCount > 0 ? -1 : 1,
102
- 'summary' => $nCount > 0 ?
103
- sprintf( __( 'There are %s inactive and unused plugins', 'wp-simple-firewall' ), $nCount )
104
- : __( "There appears to be no unused plugins", 'wp-simple-firewall' ),
105
- 'href' => Services::WpGeneral()->getAdminUrl_Plugins( true ),
106
- 'help' => __( 'Unused plugins should be removed.', 'wp-simple-firewall' )
107
- ];
108
-
109
- $nCount = count( $WPP->getUpdates() );
110
- $cards[ 'plugin_updates' ] = [
111
- 'name' => __( 'Plugin Updates', 'wp-simple-firewall' ),
112
- 'state' => $nCount > 0 ? -1 : 1,
113
- 'summary' => $nCount > 0 ?
114
- sprintf( __( 'There are %s plugin updates available to be applied', 'wp-simple-firewall' ), $nCount )
115
- : __( "All available plugin updates have been applied", 'wp-simple-firewall' ),
116
- 'href' => Services::WpGeneral()->getAdminUrl_Updates( true ),
117
- 'help' => __( 'Updates should be applied as early as possible.', 'wp-simple-firewall' ),
118
- ];
119
-
120
- return $cards;
121
- }
122
-
123
- private function getCardsForThemes() :array {
124
- $cards = [];
125
-
126
- $oWpT = Services::WpThemes();
127
- $nCount = count( $oWpT->getThemes() ) - ( $oWpT->isActiveThemeAChild() ? 2 : 1 );
128
- $cards[ 'themes_inactive' ] = [
129
- 'name' => __( 'Inactive Themes', 'wp-simple-firewall' ),
130
- 'state' => $nCount > 0 ? -1 : 1,
131
- 'summary' => $nCount > 0 ?
132
- sprintf( __( 'There are %s inactive and unused themes', 'wp-simple-firewall' ), $nCount )
133
- : __( "There appears to be no unused themes", 'wp-simple-firewall' ),
134
- 'href' => Services::WpGeneral()->getAdminUrl_Themes( true ),
135
- 'help' => __( 'Unused themes should be removed.', 'wp-simple-firewall' )
136
- ];
137
-
138
- $nCount = count( $oWpT->getUpdates() );
139
- $cards[ 'theme_updates' ] = [
140
- 'name' => __( 'Theme Updates', 'wp-simple-firewall' ),
141
- 'state' => $nCount > 0 ? -1 : 1,
142
- 'summary' => $nCount > 0 ?
143
- sprintf( __( 'There are %s theme updates available to be applied', 'wp-simple-firewall' ), $nCount )
144
- : __( "All available theme updates have been applied", 'wp-simple-firewall' ),
145
- 'href' => Services::WpGeneral()->getAdminUrl_Updates( true ),
146
- 'help' => __( 'Updates should be applied as early as possible.', 'wp-simple-firewall' ),
147
- ];
148
-
149
- return $cards;
150
- }
151
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/Base/AdminNotices.php CHANGED
@@ -5,7 +5,6 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Utilities\AdminNotices\NoticeVO;
7
  use FernleafSystems\Wordpress\Services\Services;
8
- use FernleafSystems\Wordpress\Services\Utilities\PluginUserMeta;
9
 
10
  class AdminNotices extends Shield\Modules\Base\Common\ExecOnceModConsumer {
11
 
@@ -15,13 +14,14 @@ class AdminNotices extends Shield\Modules\Base\Common\ExecOnceModConsumer {
15
  return Services::WpUsers()->isUserLoggedIn();
16
  }
17
 
18
- protected function run() {
19
- add_filter( $this->getCon()->prefix( 'collectNotices' ), [ $this, 'addNotices' ] );
20
  }
21
 
22
  /**
23
  * @param Shield\Utilities\AdminNotices\NoticeVO[] $notices
24
  * @return Shield\Utilities\AdminNotices\NoticeVO[]
 
25
  */
26
  public function addNotices( $notices ) {
27
  return array_merge( $notices, $this->buildNotices() );
@@ -132,10 +132,6 @@ class AdminNotices extends Shield\Modules\Base\Common\ExecOnceModConsumer {
132
  return $dismissedUser || $dismissedMod;
133
  }
134
 
135
- /**
136
- * @param NoticeVO $notice
137
- * @return bool
138
- */
139
  protected function isDisplayNeeded( NoticeVO $notice ) :bool {
140
  return true;
141
  }
@@ -144,7 +140,7 @@ class AdminNotices extends Shield\Modules\Base\Common\ExecOnceModConsumer {
144
  $dismissed = false;
145
 
146
  $meta = $this->getCon()->getCurrentUserMeta();
147
- if ( $meta instanceof PluginUserMeta ) {
148
  $noticeMetaKey = $this->getNoticeMetaKey( $notice );
149
 
150
  if ( isset( $meta->{$noticeMetaKey} ) ) {
@@ -174,7 +170,7 @@ class AdminNotices extends Shield\Modules\Base\Common\ExecOnceModConsumer {
174
  $noticeMetaKey = $this->getNoticeMetaKey( $notice );
175
 
176
  if ( $notice->per_user ) {
177
- if ( $meta instanceof PluginUserMeta ) {
178
  $meta->{$noticeMetaKey} = $ts;
179
  }
180
  }
@@ -185,7 +181,7 @@ class AdminNotices extends Shield\Modules\Base\Common\ExecOnceModConsumer {
185
  $mod->setDismissedNotices( $allDismissed );
186
 
187
  // Clear out any old
188
- if ( $meta instanceof PluginUserMeta ) {
189
  unset( $meta->{$noticeMetaKey} );
190
  }
191
  }
@@ -194,11 +190,4 @@ class AdminNotices extends Shield\Modules\Base\Common\ExecOnceModConsumer {
194
  private function getNoticeMetaKey( NoticeVO $notice ) :string {
195
  return 'notice_'.str_replace( [ '-', '_' ], '', $notice->id );
196
  }
197
-
198
- /**
199
- * @deprecated 14.1
200
- */
201
- public function handleAuthAjax( array $ajaxResponse ) :array {
202
- return $ajaxResponse;
203
- }
204
  }
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Utilities\AdminNotices\NoticeVO;
7
  use FernleafSystems\Wordpress\Services\Services;
 
8
 
9
  class AdminNotices extends Shield\Modules\Base\Common\ExecOnceModConsumer {
10
 
14
  return Services::WpUsers()->isUserLoggedIn();
15
  }
16
 
17
+ public function getNotices() :array {
18
+ return $this->buildNotices();
19
  }
20
 
21
  /**
22
  * @param Shield\Utilities\AdminNotices\NoticeVO[] $notices
23
  * @return Shield\Utilities\AdminNotices\NoticeVO[]
24
+ * @deprecated 15.0
25
  */
26
  public function addNotices( $notices ) {
27
  return array_merge( $notices, $this->buildNotices() );
132
  return $dismissedUser || $dismissedMod;
133
  }
134
 
 
 
 
 
135
  protected function isDisplayNeeded( NoticeVO $notice ) :bool {
136
  return true;
137
  }
140
  $dismissed = false;
141
 
142
  $meta = $this->getCon()->getCurrentUserMeta();
143
+ if ( !empty( $meta ) ) {
144
  $noticeMetaKey = $this->getNoticeMetaKey( $notice );
145
 
146
  if ( isset( $meta->{$noticeMetaKey} ) ) {
170
  $noticeMetaKey = $this->getNoticeMetaKey( $notice );
171
 
172
  if ( $notice->per_user ) {
173
+ if ( !empty( $meta ) ) {
174
  $meta->{$noticeMetaKey} = $ts;
175
  }
176
  }
181
  $mod->setDismissedNotices( $allDismissed );
182
 
183
  // Clear out any old
184
+ if ( !empty( $meta ) ) {
185
  unset( $meta->{$noticeMetaKey} );
186
  }
187
  }
190
  private function getNoticeMetaKey( NoticeVO $notice ) :string {
191
  return 'notice_'.str_replace( [ '-', '_' ], '', $notice->id );
192
  }
 
 
 
 
 
 
 
193
  }
src/lib/src/Modules/Base/AdminPage.php CHANGED
@@ -3,7 +3,9 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Common\ExecOnceModConsumer;
 
6
  use FernleafSystems\Wordpress\Services\Services;
 
7
 
8
  class AdminPage extends ExecOnceModConsumer {
9
 
@@ -17,18 +19,20 @@ class AdminPage extends ExecOnceModConsumer {
17
  $con = $this->getCon();
18
  add_action( $con->prefix( 'admin_submenu' ), function () {
19
  $this->addSubMenuItem();
20
- }, $this->getMenuPriority() );
21
  }
22
 
23
  protected function addSubMenuItem() {
24
- $this->screenID = add_submenu_page(
25
- $this->isShowMenu() ? $this->getCon()->prefix() : null,
26
- $this->getPageTitle(),
27
- $this->getMenuTitle(),
28
- $this->getCap(),
29
- $this->getMod()->getModSlug(),
30
- [ $this, 'displayModuleAdminPage' ]
31
- );
 
 
32
 
33
  foreach ( $this->getAdditionalMenuItems() as $additionalMenuItem ) {
34
  list( $itemText, $itemID, $itemCallback, $showItem ) = $additionalMenuItem;
@@ -47,7 +51,7 @@ class AdminPage extends ExecOnceModConsumer {
47
  * @uses echo()
48
  */
49
  public function displayModuleAdminPage() {
50
- echo $this->renderModulePage();
51
  }
52
 
53
  public function getScreenID() :string {
@@ -56,28 +60,67 @@ class AdminPage extends ExecOnceModConsumer {
56
 
57
  /**
58
  * Override this to customize anything with the display of the page
59
- * @param array $data
60
  * @return string
61
  */
62
- protected function renderModulePage( array $data = [] ) :string {
63
- return $this->getMod()->renderTemplate(
64
- 'index.php',
65
- Services::DataManipulation()->mergeArraysRecursive( $this->getMod()->getUIHandler()
66
- ->getBaseDisplayData(), $data )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  );
68
  }
69
 
 
 
 
70
  protected function getMenuPriority() :int {
71
- $pri = $this->getOptions()->getFeatureProperty( 'menu_priority' );
72
- return is_null( $pri ) ? 100 : (int)$pri;
73
  }
74
 
75
  public function getCap() :string {
76
  return $this->getCon()->getBasePermissions();
77
  }
78
 
 
 
 
79
  public function isShowMenu() :bool {
80
- return (bool)$this->getOptions()->getFeatureProperty( 'show_module_menu_item' );
81
  }
82
 
83
  public function isCurrentPage() :bool {
@@ -88,9 +131,10 @@ class AdminPage extends ExecOnceModConsumer {
88
 
89
  public function getMenuTitle( bool $markup = true ) :string {
90
  $mod = $this->getMod();
91
- $title = $this->getOptions()->getFeatureProperty( 'menu_title' );
92
- $title = empty( $title ) ? $mod->getMainFeatureName() : __( $title, 'wp-simple-firewall' );
93
- if ( $markup && $this->getOptions()->getFeatureProperty( 'highlight_menu_item' ) ) {
 
94
  $title = sprintf( '<span class="shield_highlighted_menu">%s</span>', $title );
95
  }
96
  return $title;
@@ -115,7 +159,7 @@ class AdminPage extends ExecOnceModConsumer {
115
  $isHighlighted = $menuItem[ 'highlight' ] ?? false;
116
  $items[ $menuPageTitle ] = [
117
  $isHighlighted ? sprintf( '<span class="shield_highlighted_menu">%s</span>', $title ) : $title,
118
- $mod->prefix( $menuItem[ 'slug' ] ),
119
  [ $this, $menuItem[ 'callback' ] ?? '' ],
120
  true
121
  ];
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Common\ExecOnceModConsumer;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin;
7
  use FernleafSystems\Wordpress\Services\Services;
8
+ use FernleafSystems\Wordpress\Services\Utilities\Obfuscate;
9
 
10
  class AdminPage extends ExecOnceModConsumer {
11
 
19
  $con = $this->getCon();
20
  add_action( $con->prefix( 'admin_submenu' ), function () {
21
  $this->addSubMenuItem();
22
+ }, $this->getMod()->cfg->properties[ 'menu_priority' ] );
23
  }
24
 
25
  protected function addSubMenuItem() {
26
+ if ( $this->getMod()->cfg->properties[ 'show_module_menu_item' ] ) {
27
+ $this->screenID = add_submenu_page(
28
+ $this->getCon()->prefix(),
29
+ $this->getPageTitle(),
30
+ $this->getMenuTitle(),
31
+ $this->getCap(),
32
+ $this->getMod()->getModSlug(),
33
+ [ $this, 'displayModuleAdminPage' ]
34
+ );
35
+ }
36
 
37
  foreach ( $this->getAdditionalMenuItems() as $additionalMenuItem ) {
38
  list( $itemText, $itemID, $itemCallback, $showItem ) = $additionalMenuItem;
51
  * @uses echo()
52
  */
53
  public function displayModuleAdminPage() {
54
+ echo $this->getMod()->isAccessRestricted() ? $this->renderRestrictedPage() : $this->renderModulePage();
55
  }
56
 
57
  public function getScreenID() :string {
60
 
61
  /**
62
  * Override this to customize anything with the display of the page
 
63
  * @return string
64
  */
65
+ protected function renderModulePage() :string {
66
+ /** @var \FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\UI $uiHandler */
67
+ $uiHandler = $this->getCon()
68
+ ->getModule_Insights()
69
+ ->getUIHandler();
70
+ return $uiHandler->renderPages();
71
+ }
72
+
73
+ public function renderRestrictedPage() :string {
74
+ $mod = $this->getMod();
75
+ $modSecAdmin = $this->getCon()->getModule_SecAdmin();
76
+ /** @var SecurityAdmin\Options $secOpts */
77
+ $secOpts = $modSecAdmin->getOptions();
78
+
79
+ $reportEmail = $mod->getPluginReportEmail();
80
+
81
+ return $mod->renderTemplate(
82
+ '/wpadmin_pages/security_admin/index.twig',
83
+ Services::DataManipulation()
84
+ ->mergeArraysRecursive(
85
+ $mod->getUIHandler()->getBaseDisplayData(),
86
+ [
87
+ 'ajax' => [
88
+ 'restricted_access' => $mod->getAjaxActionData( 'restricted_access' ),
89
+ ],
90
+ 'flags' => [
91
+ 'allow_email_override' => $secOpts->isEmailOverridePermitted()
92
+ ],
93
+ 'hrefs' => [
94
+ 'form_action' => $modSecAdmin->getUrl_AdminPage()
95
+ ],
96
+ 'strings' => [
97
+ 'force_remove_email' => __( "If you've forgotten your PIN, a link can be sent to the plugin administrator email address to remove this restriction.", 'wp-simple-firewall' ),
98
+ 'click_email' => __( "Click here to send the verification email.", 'wp-simple-firewall' ),
99
+ 'send_to_email' => sprintf( __( "Email will be sent to %s", 'wp-simple-firewall' ),
100
+ Obfuscate::Email( $reportEmail ) ),
101
+ 'no_email_override' => __( "The Security Administrator has restricted the use of the email override feature.", 'wp-simple-firewall' ),
102
+ ],
103
+ ]
104
+ )
105
  );
106
  }
107
 
108
+ /**
109
+ * @deprecated 15.0
110
+ */
111
  protected function getMenuPriority() :int {
112
+ return $this->getMod()->cfg->properties[ 'menu_priority' ] ?? 100;
 
113
  }
114
 
115
  public function getCap() :string {
116
  return $this->getCon()->getBasePermissions();
117
  }
118
 
119
+ /**
120
+ * @deprecated 15.0
121
+ */
122
  public function isShowMenu() :bool {
123
+ return $this->getMod()->cfg->properties[ 'show_module_menu_item' ] ?? false;
124
  }
125
 
126
  public function isCurrentPage() :bool {
131
 
132
  public function getMenuTitle( bool $markup = true ) :string {
133
  $mod = $this->getMod();
134
+
135
+ $title = __( $mod->cfg->properties[ 'menu_title' ], 'wp-simple-firewall' );
136
+
137
+ if ( $markup && $mod->cfg->properties[ 'highlight_menu_item' ] ) {
138
  $title = sprintf( '<span class="shield_highlighted_menu">%s</span>', $title );
139
  }
140
  return $title;
159
  $isHighlighted = $menuItem[ 'highlight' ] ?? false;
160
  $items[ $menuPageTitle ] = [
161
  $isHighlighted ? sprintf( '<span class="shield_highlighted_menu">%s</span>', $title ) : $title,
162
+ $con->prefix( $menuItem[ 'slug' ] ),
163
  [ $this, $menuItem[ 'callback' ] ?? '' ],
164
  true
165
  ];
src/lib/src/Modules/Base/AjaxHandler.php CHANGED
@@ -14,7 +14,7 @@ abstract class AjaxHandler {
14
  */
15
  public function __construct( $mod ) {
16
  $this->setMod( $mod );
17
- add_filter( $mod->prefix( 'ajax_handlers' ),
18
  function ( array $ajaxHandlers, bool $isAuth ) {
19
  return \array_merge( $ajaxHandlers, $this->getAjaxActionCallbackMap( $isAuth ) );
20
  },
@@ -53,58 +53,4 @@ abstract class AjaxHandler {
53
  // leave response empty if it doesn't apply here, so other modules can process it.
54
  return $ajaxResponse;
55
  }
56
-
57
- /**
58
- * @deprecated 14.1
59
- */
60
- public function init() {
61
- }
62
-
63
- /**
64
- * We check for empty since if it's empty, there's nothing to normalize. It's a filter,
65
- * so if we send something back non-empty, it'll be treated like a "handled" response and
66
- * processing will finish
67
- * @deprecated 14.1
68
- */
69
- protected function normaliseAjaxResponse( array $ajaxResponse ) :array {
70
- return $ajaxResponse;
71
- }
72
-
73
- /**
74
- * @throws \Exception
75
- * @deprecated 14.1
76
- */
77
- protected function processAjaxAction() :array {
78
- return [];
79
- }
80
-
81
- /**
82
- * @deprecated 14.1
83
- */
84
- public function handleAjaxAuth( array $ajaxResponse, string $ajaxAction ) :array {
85
- return [];
86
- }
87
-
88
- /**
89
- * @deprecated 14.1
90
- */
91
- public function handleAjaxNonAuth( array $ajaxResponse, string $ajaxAction ) :array {
92
- return [];
93
- }
94
-
95
- /**
96
- * @throws \Exception
97
- * @deprecated 14.1
98
- */
99
- protected function processAuthAjaxAction() :array {
100
- return [];
101
- }
102
-
103
- /**
104
- * @throws \Exception
105
- * @deprecated 14.1
106
- */
107
- protected function processNonAuthAjaxAction() :array {
108
- return [];
109
- }
110
  }
14
  */
15
  public function __construct( $mod ) {
16
  $this->setMod( $mod );
17
+ add_filter( $mod->getCon()->prefix( 'ajax_handlers' ),
18
  function ( array $ajaxHandlers, bool $isAuth ) {
19
  return \array_merge( $ajaxHandlers, $this->getAjaxActionCallbackMap( $isAuth ) );
20
  },
53
  // leave response empty if it doesn't apply here, so other modules can process it.
54
  return $ajaxResponse;
55
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  }
src/lib/src/Modules/Base/Config/LoadConfig.php CHANGED
@@ -2,13 +2,25 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Config;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
  use FernleafSystems\Wordpress\Services\Services;
7
  use FernleafSystems\Wordpress\Services\Utilities\Options\Transient;
8
 
9
  class LoadConfig {
10
 
11
- use ModConsumer;
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  private $configSourceFile = '';
14
 
@@ -18,32 +30,29 @@ class LoadConfig {
18
  return empty( $this->configSourceFile ) ? $this->getPathCfg() : $this->configSourceFile;
19
  }
20
 
 
 
 
21
  public function isBuiltFromFile() :bool {
22
  return $this->isBuiltFromFile;
23
  }
24
 
25
  /**
26
- * @return array
27
  * @throws \Exception
28
  */
29
- public function run() :array {
30
- try {
31
- if ( $this->getCon()->cfg->rebuilt ) {
32
- throw new \Exception( 'Force rebuild from file' );
33
- }
34
- $cfg = $this->fromWP();
35
- $this->isBuiltFromFile = false;
36
- }
37
- catch ( \Exception $e ) {
38
- $cfg = $this->fromFile();
39
- $this->isBuiltFromFile = true;
40
  }
41
- return $cfg;
42
  }
43
 
44
  /**
45
- * @return array
46
  * @throws \Exception
 
47
  */
48
  public function fromWP() :array {
49
  $FS = Services::WpFs();
@@ -56,11 +65,10 @@ class LoadConfig {
56
  }
57
 
58
  public function storeKey() :string {
59
- return 'shield_mod_config_'.$this->getMod()->getSlug();
60
  }
61
 
62
  /**
63
- * @return array
64
  * @throws \Exception
65
  */
66
  public function fromFile() :array {
@@ -70,18 +78,18 @@ class LoadConfig {
70
  $this->configSourceFile = $path;
71
  }
72
  catch ( \Exception $e ) {
73
- $path = $this->getCon()->paths->forModuleConfig( $this->getMod()->getSlug(), false );
74
  $raw = $this->loadRawFromFile( $path );
75
  $this->configSourceFile = $path;
76
  }
77
 
78
  $cfg = json_decode( $raw, true );
79
  if ( empty( $cfg ) || !is_array( $cfg ) ) {
80
- throw new \Exception( sprintf( "Couldn't part JSON from (%s) file '%s'.", $this->configSourceFile, $path ) );
81
  }
82
 
83
  $keyedOptions = [];
84
- foreach ( $cfg[ 'options' ] as $option ) {
85
  if ( !empty( $option[ 'key' ] ) ) {
86
  $keyedOptions[ $option[ 'key' ] ] = $option;
87
  }
@@ -92,17 +100,47 @@ class LoadConfig {
92
  'ts_mod' => Services::WpFs()->getModifiedTime( $this->getConfigSourceFile() ),
93
  ];
94
 
95
- Transient::Set( $this->storeKey(), $cfg, WEEK_IN_SECONDS );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  return $cfg;
97
  }
98
 
99
  private function getPathCfg() :string {
100
- return $this->getCon()->paths->forModuleConfig( $this->getMod()->getSlug(), true );
101
  }
102
 
103
  /**
104
- * @param string $file
105
- * @return string
106
  * @throws \Exception
107
  */
108
  private function loadRawFromFile( string $file ) :string {
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Config;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\PluginControllerConsumer;
6
  use FernleafSystems\Wordpress\Services\Services;
7
  use FernleafSystems\Wordpress\Services\Utilities\Options\Transient;
8
 
9
  class LoadConfig {
10
 
11
+ use PluginControllerConsumer;
12
+
13
+ private $slug;
14
+
15
+ /**
16
+ * @var ModConfigVO|null
17
+ */
18
+ private $cfg;
19
+
20
+ public function __construct( string $slug, $cfg = null ) {
21
+ $this->slug = $slug;
22
+ $this->cfg = $cfg;
23
+ }
24
 
25
  private $configSourceFile = '';
26
 
30
  return empty( $this->configSourceFile ) ? $this->getPathCfg() : $this->configSourceFile;
31
  }
32
 
33
+ /**
34
+ * @deprecated 15.0
35
+ */
36
  public function isBuiltFromFile() :bool {
37
  return $this->isBuiltFromFile;
38
  }
39
 
40
  /**
 
41
  * @throws \Exception
42
  */
43
+ public function run() :ModConfigVO {
44
+ $rebuild = $this->getCon()->cfg->rebuilt
45
+ || !$this->cfg instanceof ModConfigVO
46
+ || ( Services::WpFs()->getModifiedTime( $this->getPathCfg() ) > $this->cfg->meta[ 'ts_mod' ] );
47
+ if ( $rebuild ) {
48
+ $this->getCon()->cfg->rebuilt = true;
 
 
 
 
 
49
  }
50
+ return $rebuild ? ( new ModConfigVO() )->applyFromArray( $this->fromFile() ) : $this->cfg;
51
  }
52
 
53
  /**
 
54
  * @throws \Exception
55
+ * @deprecated 15.0
56
  */
57
  public function fromWP() :array {
58
  $FS = Services::WpFs();
65
  }
66
 
67
  public function storeKey() :string {
68
+ return 'shield_mod_config_'.$this->slug;
69
  }
70
 
71
  /**
 
72
  * @throws \Exception
73
  */
74
  public function fromFile() :array {
78
  $this->configSourceFile = $path;
79
  }
80
  catch ( \Exception $e ) {
81
+ $path = $this->getCon()->paths->forModuleConfig( $this->slug, false );
82
  $raw = $this->loadRawFromFile( $path );
83
  $this->configSourceFile = $path;
84
  }
85
 
86
  $cfg = json_decode( $raw, true );
87
  if ( empty( $cfg ) || !is_array( $cfg ) ) {
88
+ throw new \Exception( sprintf( "Couldn't parse JSON from (%s) file '%s'.", $this->configSourceFile, $path ) );
89
  }
90
 
91
  $keyedOptions = [];
92
+ foreach ( $cfg[ 'options' ] ?? [] as $option ) {
93
  if ( !empty( $option[ 'key' ] ) ) {
94
  $keyedOptions[ $option[ 'key' ] ] = $option;
95
  }
100
  'ts_mod' => Services::WpFs()->getModifiedTime( $this->getConfigSourceFile() ),
101
  ];
102
 
103
+ if ( empty( $cfg[ 'slug' ] ) ) {
104
+ $cfg[ 'slug' ] = $cfg[ 'properties' ][ 'slug' ];
105
+ }
106
+
107
+ $cfg[ 'properties' ] = array_merge( [
108
+ 'namespace' => str_replace( ' ', '', ucwords( str_replace( '_', ' ', $cfg[ 'slug' ] ) ) ),
109
+ 'storage_key' => $cfg[ 'slug' ],
110
+ 'tagline' => '',
111
+ 'premium' => false,
112
+ 'access_restricted' => true,
113
+ 'auto_enabled' => false,
114
+ 'auto_load_processor' => false,
115
+ 'skip_processor' => false,
116
+ 'show_module_options' => false,
117
+ 'run_if_whitelisted' => true,
118
+ 'run_if_verified_bot' => true,
119
+ 'run_if_wpcli' => true,
120
+ 'tracking_exclude' => false,
121
+ 'sidebar_name' => $cfg[ 'properties' ][ 'name' ],
122
+ 'menu_title' => $cfg[ 'properties' ][ 'name' ],
123
+ 'menu_priority' => 100,
124
+ 'highlight_menu_item' => false,
125
+ 'show_module_menu_item' => false,
126
+ ], $cfg[ 'properties' ] );
127
+
128
+ $cfg[ 'menus' ] = array_merge( [
129
+ 'config_menu_priority' => 100,
130
+ ], $cfg[ 'menus' ] ?? [] );
131
+
132
+ if ( empty( $cfg[ 'properties' ][ 'storage_key' ] ) ) {
133
+ $cfg[ 'properties' ][ 'storage_key' ] = $cfg[ 'properties' ][ 'slug' ];
134
+ }
135
+
136
  return $cfg;
137
  }
138
 
139
  private function getPathCfg() :string {
140
+ return $this->getCon()->paths->forModuleConfig( $this->slug, true );
141
  }
142
 
143
  /**
 
 
144
  * @throws \Exception
145
  */
146
  private function loadRawFromFile( string $file ) :string {
src/lib/src/Modules/Base/Config/ModConfigVO.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Config;
4
+
5
+ use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
+
7
+ /**
8
+ * @property array $properties
9
+ * @property array $menus
10
+ * @property array $meta
11
+ * @property array $options
12
+ * @property array $sections
13
+ * -- not part of config file --
14
+ * @property string $slug
15
+ */
16
+ class ModConfigVO extends DynPropertiesClass {
17
+
18
+ }
src/lib/src/Modules/Base/Insights/OverviewCards.php DELETED
@@ -1,59 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Insights;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
-
7
- class OverviewCards {
8
-
9
- use Shield\Modules\ModConsumer;
10
-
11
- public function build() :array {
12
- $mod = $this->getMod();
13
- return [
14
- $this->getMod()->getModSlug( false ) => [
15
- 'title' => $this->getSectionTitle(),
16
- 'subtitle' => $mod->getStrings()->getModTagLine(),
17
- 'href_options' => $mod->getUrl_AdminPage(),
18
- 'cards' => $this->buildCards()
19
- ]
20
- ];
21
- }
22
-
23
- protected function buildCards() :array {
24
- return array_filter( array_merge( $this->buildCommonCards(), $this->buildModCards() ) );
25
- }
26
-
27
- protected function buildModCards() :array {
28
- return [];
29
- }
30
-
31
- protected function buildCommonCards() :array {
32
- return [
33
- 'mod' => $this->getModDisabledCard()
34
- ];
35
- }
36
-
37
- protected function getSectionTitle() :string {
38
- return $this->getMod()->getMainFeatureName();
39
- }
40
-
41
- protected function getSectionSubTitle() :string {
42
- return $this->getMod()->getStrings()->getModTagLine();
43
- }
44
-
45
- protected function getModDisabledCard() :array {
46
- $mod = $this->getMod();
47
- $card = [];
48
- if ( $mod->getOptions()->optExists( $mod->getEnableModOptKey() ) && !$mod->isModOptEnabled() ) {
49
- $card = [
50
- 'name' => sprintf( '%s: %s',
51
- $this->getMod()->getMainFeatureName(), __( 'Disabled', 'wp-simple-firewall' ) ),
52
- 'summary' => __( 'All features of this module are completely disabled', 'wp-simple-firewall' ),
53
- 'state' => -2,
54
- 'href' => $mod->getUrl_DirectLinkToOption( $mod->getEnableModOptKey() ),
55
- ];
56
- }
57
- return $card;
58
- }
59
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/Base/Lib/Request/FormParams.php CHANGED
@@ -2,7 +2,6 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Lib\Request;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  class FormParams {
@@ -10,7 +9,6 @@ class FormParams {
10
  const ENC_NONE = 'none';
11
  const ENC_LZ = 'lz-string';
12
  const ENC_BASE64 = 'b64';
13
- use ModConsumer;
14
 
15
  public static function Retrieve( string $encoding = self::ENC_NONE ) :array {
16
  $req = Services::Request();
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Lib\Request;
4
 
 
5
  use FernleafSystems\Wordpress\Services\Services;
6
 
7
  class FormParams {
9
  const ENC_NONE = 'none';
10
  const ENC_LZ = 'lz-string';
11
  const ENC_BASE64 = 'b64';
 
12
 
13
  public static function Retrieve( string $encoding = self::ENC_NONE ) :array {
14
  $req = Services::Request();
src/lib/src/Modules/Base/ModCon.php CHANGED
@@ -5,6 +5,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Lib\Request\FormParams;
 
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
10
  abstract class ModCon {
@@ -13,14 +14,9 @@ abstract class ModCon {
13
  use Shield\Crons\PluginCronsConsumer;
14
 
15
  /**
16
- * @var string
17
  */
18
- private $sOptionsStoreKey;
19
-
20
- /**
21
- * @var string
22
- */
23
- protected $sModSlug;
24
 
25
  /**
26
  * @var bool
@@ -40,7 +36,7 @@ abstract class ModCon {
40
  /**
41
  * @var Shield\Modules\Base\Reporting
42
  */
43
- private $oReporting;
44
 
45
  /**
46
  * @var Shield\Modules\Base\UI
@@ -52,15 +48,10 @@ abstract class ModCon {
52
  */
53
  private $opts;
54
 
55
- /**
56
- * @var Shield\Modules\Base\Options
57
- */
58
- private $oOpts;
59
-
60
  /**
61
  * @var Shield\Modules\Base\WpCli
62
  */
63
- private $oWpCli;
64
 
65
  /**
66
  * @var Shield\Modules\Base\AdminPage
@@ -84,39 +75,25 @@ abstract class ModCon {
84
 
85
  /**
86
  * @param Shield\Controller\Controller $pluginCon
87
- * @param array $mod
88
  * @throws \Exception
89
  */
90
- public function __construct( $pluginCon, $mod = [] ) {
91
- if ( !$pluginCon instanceof Shield\Controller\Controller ) {
92
- throw new \Exception( 'Plugin controller not supplied to Module' );
93
- }
94
  $this->setCon( $pluginCon );
95
-
96
- if ( empty( $mod[ 'storage_key' ] ) && empty( $mod[ 'slug' ] ) ) {
97
- throw new \Exception( 'Module storage key AND slug are undefined' );
98
- }
99
-
100
- $this->sOptionsStoreKey = empty( $mod[ 'storage_key' ] ) ? $mod[ 'slug' ] : $mod[ 'storage_key' ];
101
- if ( isset( $mod[ 'slug' ] ) ) {
102
- $this->sModSlug = $mod[ 'slug' ];
103
- }
104
-
105
  if ( $this->verifyModuleMeetRequirements() ) {
106
  $this->handleAutoPageRedirects();
107
- $this->setupHooks( $mod );
108
  $this->doPostConstruction();
109
  }
110
  }
111
 
112
- protected function setupHooks( array $modProps ) {
113
  $con = $this->getCon();
114
- $nRunPriority = $modProps[ 'load_priority' ] ?? 100;
115
 
116
  add_action( $con->prefix( 'modules_loaded' ), function () {
117
  $this->onModulesLoaded();
118
- }, $nRunPriority );
119
- add_action( $con->prefix( 'run_processors' ), [ $this, 'onRunProcessors' ], $nRunPriority );
120
 
121
  add_action( 'init', [ $this, 'onWpInit' ], 1 );
122
  add_action( 'init', [ $this, 'onWpLoaded' ] );
@@ -128,10 +105,29 @@ abstract class ModCon {
128
  // if ( $this->isAdminOptionsPage() ) {
129
  // add_action( 'current_screen', array( $this, 'onSetCurrentScreen' ) );
130
  // }
 
131
  $this->setupCronHooks();
132
  $this->setupCustomHooks();
133
  }
134
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  protected function setupCustomHooks() {
136
  }
137
 
@@ -231,8 +227,8 @@ abstract class ModCon {
231
  }
232
  }
233
  if ( !empty( $reqPHP[ 'constants' ] ) && is_array( $reqPHP[ 'constants' ] ) ) {
234
- foreach ( $reqPHP[ 'constants' ] as $sConstant ) {
235
- $meetReqs = $meetReqs && defined( $sConstant );
236
  }
237
  }
238
  }
@@ -244,13 +240,11 @@ abstract class ModCon {
244
  }
245
 
246
  public function onRunProcessors() {
247
- $opts = $this->getOptions();
248
- if ( $opts->getFeatureProperty( 'auto_load_processor' ) ) {
249
  $this->loadProcessor();
250
  }
251
  try {
252
- $skip = (bool)$opts->getFeatureProperty( 'skip_processor' );
253
- if ( !$skip && !$this->isUpgrading() && $this->isModuleEnabled() && $this->isReadyToExecute() ) {
254
  $this->doExecuteProcessor();
255
  }
256
  }
@@ -297,10 +291,12 @@ abstract class ModCon {
297
  }
298
 
299
  public function onWpInit() {
 
300
 
301
- $shieldAction = $this->getCon()->getShieldAction();
302
  if ( !empty( $shieldAction ) ) {
303
- do_action( $this->getCon()->prefix( 'shield_action' ), $shieldAction );
 
304
  }
305
 
306
  add_action( 'cli_init', function () {
@@ -332,8 +328,8 @@ abstract class ModCon {
332
 
333
  // GDPR
334
  if ( $this->isPremium() ) {
335
- add_filter( $this->prefix( 'wpPrivacyExport' ), [ $this, 'onWpPrivacyExport' ], 10, 3 );
336
- add_filter( $this->prefix( 'wpPrivacyErase' ), [ $this, 'onWpPrivacyErase' ], 10, 3 );
337
  }
338
 
339
  if ( is_admin() || is_network_admin() ) {
@@ -343,6 +339,9 @@ abstract class ModCon {
343
  $this->loadDebug();
344
  }
345
 
 
 
 
346
  /**
347
  * We have to do it this way as the "page hook" is built upon the top-level plugin
348
  * menu name. But what if we white label? So we need to dynamically grab the page hook
@@ -389,8 +388,11 @@ abstract class ModCon {
389
  );
390
  }
391
 
 
 
 
392
  public function isUpgrading() :bool {
393
- return $this->getCon()->cfg->rebuilt || $this->getOptions()->getConfigLoader()->isBuiltFromFile();
394
  }
395
 
396
  /**
@@ -406,7 +408,8 @@ abstract class ModCon {
406
  }
407
 
408
  public function getOptionsStorageKey() :string {
409
- return $this->getCon()->prefixOption( $this->sOptionsStoreKey ).'_options';
 
410
  }
411
 
412
  /**
@@ -416,6 +419,10 @@ abstract class ModCon {
416
  return $this->loadProcessor();
417
  }
418
 
 
 
 
 
419
  public function getUrl_AdminPage() :string {
420
  return Services::WpGeneral()
421
  ->getUrl_AdminPage(
@@ -456,7 +463,6 @@ abstract class ModCon {
456
  $exec = $req->request( 'exec' );
457
  if ( !empty( $exec ) && $req->request( 'action' ) == $con->prefix() ) {
458
 
459
-
460
  if ( wp_verify_nonce( $req->request( 'exec_nonce' ), $exec ) && $con->getMeetsBasePermissions() ) {
461
  $valid = true;
462
  }
@@ -473,19 +479,17 @@ abstract class ModCon {
473
  }
474
 
475
  public function getUrl_DirectLinkToOption( string $key ) :string {
476
- $url = $this->getUrl_AdminPage();
477
  $def = $this->getOptions()->getOptDefinition( $key );
478
- if ( !empty( $def[ 'section' ] ) ) {
479
- $url = $this->getUrl_DirectLinkToSection( $def[ 'section' ] );
480
- }
481
- return $url;
482
  }
483
 
484
  public function getUrl_DirectLinkToSection( string $section ) :string {
485
  if ( $section == 'primary' ) {
486
  $section = $this->getOptions()->getPrimarySection()[ 'slug' ];
487
  }
488
- return $this->getUrl_AdminPage().'#tab-'.$section;
489
  }
490
 
491
  /**
@@ -518,17 +522,17 @@ abstract class ModCon {
518
  /** @var Shield\Modules\Plugin\Options $pluginOpts */
519
  $pluginOpts = $this->getCon()->getModule_Plugin()->getOptions();
520
 
521
- if ( $this->getOptions()->getFeatureProperty( 'auto_enabled' ) === true ) {
522
  // Auto enabled modules always run regardless
523
  $enabled = true;
524
  }
525
  elseif ( $pluginOpts->isPluginGloballyDisabled() ) {
526
  $enabled = false;
527
  }
528
- elseif ( $this->getCon()->getIfForceOffActive() ) {
529
  $enabled = false;
530
  }
531
- elseif ( $this->getOptions()->getFeatureProperty( 'premium' ) === true && !$this->isPremium() ) {
532
  $enabled = false;
533
  }
534
  else {
@@ -549,21 +553,15 @@ abstract class ModCon {
549
  }
550
 
551
  public function getMainFeatureName() :string {
552
- return __( $this->getOptions()->getFeatureProperty( 'name' ), 'wp-simple-firewall' );
553
  }
554
 
555
  public function getModSlug( bool $prefix = true ) :string {
556
- return $prefix ? $this->prefix( $this->getSlug() ) : $this->getSlug();
557
  }
558
 
559
- /**
560
- * @return string
561
- */
562
- public function getSlug() {
563
- if ( !isset( $this->sModSlug ) ) {
564
- $this->sModSlug = $this->getOptions()->getFeatureProperty( 'slug' );
565
- }
566
- return $this->sModSlug;
567
  }
568
 
569
  /**
@@ -575,7 +573,8 @@ abstract class ModCon {
575
  $cfg = $this->getOptions()->getRawData_FullFeatureConfig();
576
  if ( !empty( $cfg[ 'custom_redirects' ] ) && $this->getCon()->isValidAdminArea() ) {
577
  foreach ( $cfg[ 'custom_redirects' ] as $redirect ) {
578
- if ( Services::Request()->query( 'page' ) == $this->prefix( $redirect[ 'source_mod_page' ] ) ) {
 
579
  Services::Response()->redirect(
580
  $this->getCon()->getModule( $redirect[ 'target_mod_page' ] )->getUrl_AdminPage(),
581
  $redirect[ 'query_args' ],
@@ -587,72 +586,15 @@ abstract class ModCon {
587
  }
588
  }
589
 
590
- /**
591
- * TODO: not the place for this method.
592
- * @return array[]
593
- */
594
- public function getModulesSummaryData() {
595
- return array_map(
596
- function ( $mod ) {
597
- return $mod->buildSummaryData();
598
- },
599
- $this->getCon()->modules
600
- );
601
- }
602
-
603
- public function buildSummaryData() :array {
604
- $opts = $this->getOptions();
605
- $menuTitle = $opts->getFeatureProperty( 'menu_title' );
606
-
607
- $sections = $opts->getSections();
608
- foreach ( $sections as $slug => $section ) {
609
- try {
610
- $strings = $this->getStrings()->getSectionStrings( $section[ 'slug' ] );
611
- foreach ( $strings as $key => $val ) {
612
- unset( $section[ $key ] );
613
- $section[ $key ] = $val;
614
- }
615
- }
616
- catch ( \Exception $e ) {
617
- }
618
- }
619
-
620
- $summary = [
621
- 'slug' => $this->getSlug(),
622
- 'enabled' => $this->getUIHandler()->isEnabledForUiSummary(),
623
- 'active' => $this->isThisModulePage() || $this->isPage_InsightsThisModule(),
624
- 'name' => $this->getMainFeatureName(),
625
- 'sidebar_name' => $opts->getFeatureProperty( 'sidebar_name' ),
626
- 'menu_title' => empty( $menuTitle ) ? $this->getMainFeatureName() : __( $menuTitle, 'wp-simple-firewall' ),
627
- 'href' => network_admin_url( 'admin.php?page='.$this->getModSlug() ),
628
- 'sections' => $sections,
629
- 'options' => [],
630
- 'show_mod_opts' => $this->getIfShowModuleOpts(),
631
- ];
632
-
633
- foreach ( $opts->getVisibleOptionsKeys() as $optKey ) {
634
- try {
635
- $optData = $this->getStrings()->getOptionStrings( $optKey );
636
- $optData[ 'href' ] = $this->getUrl_DirectLinkToOption( $optKey );
637
- $summary[ 'options' ][ $optKey ] = $optData;
638
- }
639
- catch ( \Exception $e ) {
640
- }
641
- }
642
-
643
- $summary[ 'tooltip' ] = sprintf(
644
- '%s',
645
- empty( $summary[ 'sidebar_name' ] ) ? $summary[ 'name' ] : __( $summary[ 'sidebar_name' ], 'wp-simple-firewall' )
646
- );
647
- return $summary;
648
- }
649
-
650
  public function getIfShowModuleMenuItem() :bool {
651
- return (bool)$this->getOptions()->getFeatureProperty( 'show_module_menu_item' );
652
  }
653
 
 
 
 
654
  public function getIfShowModuleOpts() :bool {
655
- return (bool)$this->getOptions()->getFeatureProperty( 'show_module_options' );
656
  }
657
 
658
  /**
@@ -663,16 +605,14 @@ abstract class ModCon {
663
  }
664
 
665
  /**
666
- * @param bool $bAsString
667
- * @param string $sGlue
668
  * @return string|array
669
  */
670
- public function getLastErrors( $bAsString = false, $sGlue = " " ) {
671
  $errors = $this->getOptions()->getOpt( 'last_errors' );
672
  if ( !is_array( $errors ) ) {
673
  $errors = [];
674
  }
675
- return $bAsString ? implode( $sGlue, $errors ) : $errors;
676
  }
677
 
678
  public function hasLastErrors() :bool {
@@ -775,13 +715,16 @@ abstract class ModCon {
775
  }
776
 
777
  $this->doPrePluginOptionsSave();
778
- if ( apply_filters( $this->prefix( 'force_options_resave' ), false ) ) {
779
  $this->getOptions()
780
  ->setNeedSave( true );
781
  }
782
 
783
  // we set the flag that options have been updated. (only use this flag if it's a MANUAL options update)
784
- $this->bImportExportWhitelistNotify = $this->getOptions()->getNeedSave();
 
 
 
785
  $this->store();
786
  return $this;
787
  }
@@ -790,10 +733,11 @@ abstract class ModCon {
790
  }
791
 
792
  private function store() {
793
- add_filter( $this->prefix( 'bypass_is_plugin_admin' ), '__return_true', 1000 );
 
794
  $this->getOptions()
795
- ->doOptionsSave( $this->getCon()->getIsResetPlugin(), $this->isPremium() );
796
- remove_filter( $this->prefix( 'bypass_is_plugin_admin' ), '__return_true', 1000 );
797
  }
798
 
799
  /**
@@ -834,26 +778,6 @@ abstract class ModCon {
834
  );
835
  }
836
 
837
- /**
838
- * @throws \Exception
839
- */
840
- public function saveOptionsSubmit() {
841
- if ( !$this->getCon()->isPluginAdmin() ) {
842
- throw new \Exception( __( "You don't currently have permission to save settings.", 'wp-simple-firewall' ) );
843
- }
844
-
845
- $this->doSaveStandardOptions();
846
-
847
- $this->saveModOptions( true );
848
-
849
- // only use this flag when the options are being updated with a MANUAL save.
850
- if ( isset( $this->bImportExportWhitelistNotify ) && $this->bImportExportWhitelistNotify ) {
851
- if ( !wp_next_scheduled( $this->prefix( 'importexport_notify' ) ) ) {
852
- wp_schedule_single_event( Services::Request()->ts() + 15, $this->prefix( 'importexport_notify' ) );
853
- }
854
- }
855
- }
856
-
857
  /**
858
  * @param string $msg
859
  * @param \WP_User|null $user
@@ -877,81 +801,6 @@ abstract class ModCon {
877
  return $this->getCon()->isPremiumActive();
878
  }
879
 
880
- /**
881
- * @throws \Exception
882
- */
883
- private function doSaveStandardOptions() {
884
- // standard options use b64 and fail-over to lz-string
885
- $form = FormParams::Retrieve( FormParams::ENC_BASE64 );
886
-
887
- $optsAndTypes = array_map(
888
- function ( $optDef ) {
889
- return $optDef[ 'type' ];
890
- },
891
- $this->getOptions()->getVisibleOptions()
892
- );
893
- foreach ( $optsAndTypes as $optKey => $optType ) {
894
-
895
- $optValue = $form[ $optKey ] ?? null;
896
- if ( is_null( $optValue ) ) {
897
-
898
- if ( in_array( $optType, [ 'text', 'email' ] ) ) { //text box, and it's null, don't update
899
- continue;
900
- }
901
- elseif ( $optType == 'checkbox' ) { //if it was a checkbox, and it's null, it means 'N'
902
- $optValue = 'N';
903
- }
904
- elseif ( $optType == 'integer' ) { //if it was a integer, and it's null, it means '0'
905
- $optValue = 0;
906
- }
907
- elseif ( $optType == 'multiple_select' ) {
908
- $optValue = [];
909
- }
910
- }
911
- else { //handle any pre-processing we need to.
912
-
913
- if ( $optType == 'text' || $optType == 'email' ) {
914
- $optValue = trim( $optValue );
915
- }
916
- if ( $optType == 'integer' ) {
917
- $optValue = intval( $optValue );
918
- }
919
- elseif ( $optType == 'password' ) {
920
- $sTempValue = trim( $optValue );
921
- if ( empty( $sTempValue ) ) {
922
- continue;
923
- }
924
-
925
- $confirm = $form[ $optKey.'_confirm' ] ?? null;
926
- if ( $sTempValue !== $confirm ) {
927
- throw new \Exception( __( 'Password values do not match.', 'wp-simple-firewall' ) );
928
- }
929
-
930
- $optValue = md5( $sTempValue );
931
- }
932
- elseif ( $optType == 'array' ) { //arrays are textareas, where each is separated by newline
933
- $optValue = array_filter( explode( "\n", esc_textarea( $optValue ) ), 'trim' );
934
- }
935
- elseif ( $optType == 'comma_separated_lists' ) {
936
- $optValue = Services::Data()->extractCommaSeparatedList( $optValue );
937
- }
938
- /* elseif ( $optType == 'multiple_select' ) { } */
939
- }
940
-
941
- // Prevent overwriting of non-editable fields
942
- if ( !in_array( $optType, [ 'noneditable_text' ] ) ) {
943
- $this->getOptions()->setOpt( $optKey, $optValue );
944
- }
945
- }
946
-
947
- // Handle Import/Export exclusions
948
- if ( $this->isPremium() ) {
949
- ( new Shield\Modules\Plugin\Lib\ImportExport\Options\SaveExcludedOptions() )
950
- ->setMod( $this )
951
- ->save( $form );
952
- }
953
- }
954
-
955
  protected function runWizards() {
956
  if ( $this->isWizardPage() && $this->hasWizard() ) {
957
  $wiz = $this->getWizardHandler();
@@ -975,8 +824,11 @@ abstract class ModCon {
975
  && Services::Request()->query( 'inav' ) == $this->getSlug();
976
  }
977
 
 
 
 
978
  protected function isModuleOptionsRequest() :bool {
979
- return Services::Request()->post( 'mod_slug' ) === $this->getModSlug();
980
  }
981
 
982
  protected function isWizardPage() :bool {
@@ -988,41 +840,12 @@ abstract class ModCon {
988
  * @param string $suffix
989
  * @param string $glue
990
  * @return string
 
991
  */
992
  public function prefix( $suffix = '', $glue = '-' ) {
993
  return $this->getCon()->prefix( $suffix, $glue );
994
  }
995
 
996
- /**
997
- * @uses echo()
998
- */
999
- public function displayModuleAdminPage() {
1000
- echo $this->renderModulePage();
1001
- }
1002
-
1003
- /**
1004
- * Override this to customize anything with the display of the page
1005
- * @param array $data
1006
- * @return string
1007
- */
1008
- protected function renderModulePage( array $data = [] ) :string {
1009
- return $this->renderTemplate(
1010
- 'index.php',
1011
- Services::DataManipulation()->mergeArraysRecursive( $this->getUIHandler()->getBaseDisplayData(), $data )
1012
- );
1013
- }
1014
-
1015
- /**
1016
- * @return string
1017
- */
1018
- protected function getContentWizardLanding() {
1019
- $aData = $this->getUIHandler()->getBaseDisplayData();
1020
- if ( $this->hasWizard() ) {
1021
- $aData[ 'content' ][ 'wizard_landing' ] = $this->getWizardHandler()->renderWizardLandingSnippet();
1022
- }
1023
- return $this->renderTemplate( 'snippets/module-wizard-template.php', $aData );
1024
- }
1025
-
1026
  protected function buildContextualHelp() {
1027
  if ( !function_exists( 'get_current_screen' ) ) {
1028
  require_once( ABSPATH.'wp-admin/includes/screen.php' );
@@ -1044,8 +867,6 @@ abstract class ModCon {
1044
  }
1045
 
1046
  /**
1047
- * @param string $wizardSlug
1048
- * @return string
1049
  * @uses nonce
1050
  */
1051
  public function getUrl_Wizard( string $wizardSlug ) :string {
@@ -1102,90 +923,30 @@ abstract class ModCon {
1102
  }
1103
 
1104
  public function getIsShowMarketing() :bool {
1105
- return (bool)apply_filters( $this->prefix( 'show_marketing' ), !$this->isPremium() );
1106
  }
1107
 
1108
- /**
1109
- * @return string
1110
- */
1111
- public function renderOptionsForm() {
1112
-
1113
- if ( $this->canDisplayOptionsForm() ) {
1114
- $template = 'components/options_form/main.twig';
1115
- }
1116
- else {
1117
- $template = 'subfeature-access_restricted';
1118
- }
1119
-
1120
- try {
1121
- return $this->getCon()
1122
- ->getRenderer()
1123
- ->setTemplate( $template )
1124
- ->setRenderVars( $this->getUIHandler()->getBaseDisplayData() )
1125
- ->setTemplateEngineTwig()
1126
- ->render();
1127
- }
1128
- catch ( \Exception $e ) {
1129
- return 'Error rendering options form: '.$e->getMessage();
1130
- }
1131
  }
1132
 
1133
  public function canDisplayOptionsForm() :bool {
1134
- return $this->getOptions()->isAccessRestricted() ? $this->getCon()->isPluginAdmin() : true;
1135
  }
1136
 
1137
  public function getScriptLocalisations() :array {
1138
- return [
1139
- [
1140
- 'plugin',
1141
- 'icwp_wpsf_vars_base',
1142
- [
1143
- 'ajax' => [
1144
- 'mod_options' => $this->getAjaxActionData( 'mod_options' ),
1145
- 'mod_opts_form_render' => $this->getAjaxActionData( 'mod_opts_form_render' ),
1146
- ]
1147
- ]
1148
- ]
1149
- ];
1150
  }
1151
 
1152
  public function getCustomScriptEnqueues() :array {
1153
  return [];
1154
  }
1155
 
1156
- /**
1157
- * @param array $aData
1158
- * @param string $sSubView
1159
- */
1160
- protected function display( $aData = [], $sSubView = '' ) {
1161
- }
1162
-
1163
- public function renderTemplate( string $template, array $data = [], bool $isTwig = false ) :string {
1164
- if ( empty( $data[ 'unique_render_id' ] ) ) {
1165
- $data[ 'unique_render_id' ] = 'noticeid-'.substr( md5( mt_rand() ), 0, 5 );
1166
- }
1167
- try {
1168
- $rndr = $this->getCon()->getRenderer();
1169
- if ( $isTwig || preg_match( '#^.*\.twig$#i', $template ) ) {
1170
- $rndr->setTemplateEngineTwig();
1171
- }
1172
-
1173
- $data[ 'strings' ] = Services::DataManipulation()
1174
- ->mergeArraysRecursive(
1175
- $this->getStrings()->getDisplayStrings(),
1176
- $data[ 'strings' ] ?? []
1177
- );
1178
-
1179
- $render = $rndr->setTemplate( $template )
1180
- ->setRenderVars( $data )
1181
- ->render();
1182
- }
1183
- catch ( \Exception $e ) {
1184
- $render = $e->getMessage();
1185
- error_log( $e->getMessage() );
1186
- }
1187
-
1188
- return (string)$render;
1189
  }
1190
 
1191
  public function getMainWpData() :array {
@@ -1226,14 +987,6 @@ abstract class ModCon {
1226
  return $this->opts;
1227
  }
1228
 
1229
- /**
1230
- * @return Rest|mixed
1231
- * @deprecated 14.1
1232
- */
1233
- public function getRestHandler() {
1234
- return $this->loadModElement( 'RestHandler' );
1235
- }
1236
-
1237
  /**
1238
  * @return AdminPage
1239
  */
@@ -1248,10 +1001,10 @@ abstract class ModCon {
1248
  * @return Shield\Modules\Base\WpCli
1249
  */
1250
  public function getWpCli() {
1251
- if ( !isset( $this->oWpCli ) ) {
1252
- $this->oWpCli = $this->loadModElement( 'WpCli' );
1253
  }
1254
- return $this->oWpCli;
1255
  }
1256
 
1257
  /**
@@ -1261,6 +1014,15 @@ abstract class ModCon {
1261
  return $this->loadStrings()->setMod( $this );
1262
  }
1263
 
 
 
 
 
 
 
 
 
 
1264
  /**
1265
  * @return Shield\Modules\Base\UI
1266
  */
@@ -1275,10 +1037,10 @@ abstract class ModCon {
1275
  * @return Shield\Modules\Base\Reporting|mixed|false
1276
  */
1277
  public function getReportingHandler() {
1278
- if ( !isset( $this->oReporting ) ) {
1279
- $this->oReporting = $this->loadModElement( 'Reporting' );
1280
  }
1281
- return $this->oReporting;
1282
  }
1283
 
1284
  public function getAdminNotices() {
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Lib\Request\FormParams;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Options\RenderOptionsForm;
9
  use FernleafSystems\Wordpress\Services\Services;
10
 
11
  abstract class ModCon {
14
  use Shield\Crons\PluginCronsConsumer;
15
 
16
  /**
17
+ * @var Config\ModConfigVO
18
  */
19
+ public $cfg;
 
 
 
 
 
20
 
21
  /**
22
  * @var bool
36
  /**
37
  * @var Shield\Modules\Base\Reporting
38
  */
39
+ private $reporting;
40
 
41
  /**
42
  * @var Shield\Modules\Base\UI
48
  */
49
  private $opts;
50
 
 
 
 
 
 
51
  /**
52
  * @var Shield\Modules\Base\WpCli
53
  */
54
+ private $wpCli;
55
 
56
  /**
57
  * @var Shield\Modules\Base\AdminPage
75
 
76
  /**
77
  * @param Shield\Controller\Controller $pluginCon
78
+ * @param Config\ModConfigVO $cfg
79
  * @throws \Exception
80
  */
81
+ public function __construct( Shield\Controller\Controller $pluginCon, Config\ModConfigVO $cfg ) {
 
 
 
82
  $this->setCon( $pluginCon );
83
+ $this->cfg = $cfg;
 
 
 
 
 
 
 
 
 
84
  if ( $this->verifyModuleMeetRequirements() ) {
85
  $this->handleAutoPageRedirects();
86
+ $this->setupHooks();
87
  $this->doPostConstruction();
88
  }
89
  }
90
 
91
+ protected function setupHooks() {
92
  $con = $this->getCon();
 
93
 
94
  add_action( $con->prefix( 'modules_loaded' ), function () {
95
  $this->onModulesLoaded();
96
+ } );
 
97
 
98
  add_action( 'init', [ $this, 'onWpInit' ], 1 );
99
  add_action( 'init', [ $this, 'onWpLoaded' ] );
105
  // if ( $this->isAdminOptionsPage() ) {
106
  // add_action( 'current_screen', array( $this, 'onSetCurrentScreen' ) );
107
  // }
108
+ $this->collateRuleBuilders();
109
  $this->setupCronHooks();
110
  $this->setupCustomHooks();
111
  }
112
 
113
+ protected function collateRuleBuilders() {
114
+ add_filter( 'shield/collate_rule_builders', function ( array $builders ) {
115
+ return array_merge( $builders, array_map(
116
+ function ( $class ) {
117
+ /** @var Shield\Rules\Build\BuildRuleBase $theClass */
118
+ $theClass = new $class();
119
+ $theClass->setMod( $this );
120
+ return $theClass;
121
+ },
122
+ array_filter( $this->enumRuleBuilders() )
123
+ ) );
124
+ } );
125
+ }
126
+
127
+ protected function enumRuleBuilders() :array {
128
+ return [];
129
+ }
130
+
131
  protected function setupCustomHooks() {
132
  }
133
 
227
  }
228
  }
229
  if ( !empty( $reqPHP[ 'constants' ] ) && is_array( $reqPHP[ 'constants' ] ) ) {
230
+ foreach ( $reqPHP[ 'constants' ] as $constant ) {
231
+ $meetReqs = $meetReqs && defined( $constant );
232
  }
233
  }
234
  }
240
  }
241
 
242
  public function onRunProcessors() {
243
+ if ( $this->cfg->properties[ 'auto_load_processor' ] ) {
 
244
  $this->loadProcessor();
245
  }
246
  try {
247
+ if ( !$this->cfg->properties[ 'skip_processor' ] && $this->isModuleEnabled() && $this->isReadyToExecute() ) {
 
248
  $this->doExecuteProcessor();
249
  }
250
  }
291
  }
292
 
293
  public function onWpInit() {
294
+ $con = $this->getCon();
295
 
296
+ $shieldAction = $con->getShieldAction();
297
  if ( !empty( $shieldAction ) ) {
298
+ do_action( $con->prefix( 'shield_action' ), $shieldAction );
299
+ $this->handleShieldAction( $shieldAction );
300
  }
301
 
302
  add_action( 'cli_init', function () {
328
 
329
  // GDPR
330
  if ( $this->isPremium() ) {
331
+ add_filter( $con->prefix( 'wpPrivacyExport' ), [ $this, 'onWpPrivacyExport' ], 10, 3 );
332
+ add_filter( $con->prefix( 'wpPrivacyErase' ), [ $this, 'onWpPrivacyErase' ], 10, 3 );
333
  }
334
 
335
  if ( is_admin() || is_network_admin() ) {
339
  $this->loadDebug();
340
  }
341
 
342
+ protected function handleShieldAction( string $action ) {
343
+ }
344
+
345
  /**
346
  * We have to do it this way as the "page hook" is built upon the top-level plugin
347
  * menu name. But what if we white label? So we need to dynamically grab the page hook
388
  );
389
  }
390
 
391
+ /**
392
+ * @deprecated 15.0
393
+ */
394
  public function isUpgrading() :bool {
395
+ return $this->getCon()->cfg->rebuilt;
396
  }
397
 
398
  /**
408
  }
409
 
410
  public function getOptionsStorageKey() :string {
411
+ return $this->getCon()->prefixOption( $this->sOptionsStoreKey ?? $this->cfg->properties[ 'storage_key' ] )
412
+ .'_options';
413
  }
414
 
415
  /**
419
  return $this->loadProcessor();
420
  }
421
 
422
+ public function getUrl_OptionsConfigPage() :string {
423
+ return $this->getCon()->getModule_Insights()->getUrl_SubInsightsPage( 'settings', $this->getSlug() );
424
+ }
425
+
426
  public function getUrl_AdminPage() :string {
427
  return Services::WpGeneral()
428
  ->getUrl_AdminPage(
463
  $exec = $req->request( 'exec' );
464
  if ( !empty( $exec ) && $req->request( 'action' ) == $con->prefix() ) {
465
 
 
466
  if ( wp_verify_nonce( $req->request( 'exec_nonce' ), $exec ) && $con->getMeetsBasePermissions() ) {
467
  $valid = true;
468
  }
479
  }
480
 
481
  public function getUrl_DirectLinkToOption( string $key ) :string {
 
482
  $def = $this->getOptions()->getOptDefinition( $key );
483
+ return empty( $def[ 'section' ] ) ?
484
+ $this->getUrl_OptionsConfigPage()
485
+ : $this->getUrl_DirectLinkToSection( $def[ 'section' ] );
 
486
  }
487
 
488
  public function getUrl_DirectLinkToSection( string $section ) :string {
489
  if ( $section == 'primary' ) {
490
  $section = $this->getOptions()->getPrimarySection()[ 'slug' ];
491
  }
492
+ return $this->getUrl_OptionsConfigPage().'#tab-'.$section;
493
  }
494
 
495
  /**
522
  /** @var Shield\Modules\Plugin\Options $pluginOpts */
523
  $pluginOpts = $this->getCon()->getModule_Plugin()->getOptions();
524
 
525
+ if ( $this->cfg->properties[ 'auto_enabled' ] ) {
526
  // Auto enabled modules always run regardless
527
  $enabled = true;
528
  }
529
  elseif ( $pluginOpts->isPluginGloballyDisabled() ) {
530
  $enabled = false;
531
  }
532
+ elseif ( $this->getCon()->this_req->is_force_off ) {
533
  $enabled = false;
534
  }
535
+ elseif ( $this->cfg->properties[ 'premium' ] && !$this->isPremium() ) {
536
  $enabled = false;
537
  }
538
  else {
553
  }
554
 
555
  public function getMainFeatureName() :string {
556
+ return __( $this->cfg->properties[ 'name' ], 'wp-simple-firewall' );
557
  }
558
 
559
  public function getModSlug( bool $prefix = true ) :string {
560
+ return $prefix ? $this->getCon()->prefix( $this->getSlug() ) : $this->getSlug();
561
  }
562
 
563
+ public function getSlug() :string {
564
+ return $this->sModSlug ?? $this->cfg->slug;
 
 
 
 
 
 
565
  }
566
 
567
  /**
573
  $cfg = $this->getOptions()->getRawData_FullFeatureConfig();
574
  if ( !empty( $cfg[ 'custom_redirects' ] ) && $this->getCon()->isValidAdminArea() ) {
575
  foreach ( $cfg[ 'custom_redirects' ] as $redirect ) {
576
+ if ( Services::Request()->query( 'page' ) == $this->getCon()
577
+ ->prefix( $redirect[ 'source_mod_page' ] ) ) {
578
  Services::Response()->redirect(
579
  $this->getCon()->getModule( $redirect[ 'target_mod_page' ] )->getUrl_AdminPage(),
580
  $redirect[ 'query_args' ],
586
  }
587
  }
588
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
589
  public function getIfShowModuleMenuItem() :bool {
590
+ return $this->cfg->properties[ 'show_module_menu_item' ];
591
  }
592
 
593
+ /**
594
+ * @deprecated 15.0
595
+ */
596
  public function getIfShowModuleOpts() :bool {
597
+ return $this->cfg->properties[ 'show_module_options' ];
598
  }
599
 
600
  /**
605
  }
606
 
607
  /**
 
 
608
  * @return string|array
609
  */
610
+ public function getLastErrors( bool $asString = false, string $glue = " " ) {
611
  $errors = $this->getOptions()->getOpt( 'last_errors' );
612
  if ( !is_array( $errors ) ) {
613
  $errors = [];
614
  }
615
+ return $asString ? implode( $glue, $errors ) : $errors;
616
  }
617
 
618
  public function hasLastErrors() :bool {
715
  }
716
 
717
  $this->doPrePluginOptionsSave();
718
+ if ( apply_filters( $this->getCon()->prefix( 'force_options_resave' ), false ) ) {
719
  $this->getOptions()
720
  ->setNeedSave( true );
721
  }
722
 
723
  // we set the flag that options have been updated. (only use this flag if it's a MANUAL options update)
724
+ if ( $this->getOptions()->getNeedSave() ) {
725
+ $this->bImportExportWhitelistNotify = true;
726
+ do_action( $this->getCon()->prefix( 'pre_options_store' ), $this );
727
+ }
728
  $this->store();
729
  return $this;
730
  }
733
  }
734
 
735
  private function store() {
736
+ $con = $this->getCon();
737
+ add_filter( $con->prefix( 'bypass_is_plugin_admin' ), '__return_true', 1000 );
738
  $this->getOptions()
739
+ ->doOptionsSave( $con->getIsResetPlugin(), $this->isPremium() );
740
+ remove_filter( $con->prefix( 'bypass_is_plugin_admin' ), '__return_true', 1000 );
741
  }
742
 
743
  /**
778
  );
779
  }
780
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
781
  /**
782
  * @param string $msg
783
  * @param \WP_User|null $user
801
  return $this->getCon()->isPremiumActive();
802
  }
803
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
804
  protected function runWizards() {
805
  if ( $this->isWizardPage() && $this->hasWizard() ) {
806
  $wiz = $this->getWizardHandler();
824
  && Services::Request()->query( 'inav' ) == $this->getSlug();
825
  }
826
 
827
+ /**
828
+ * @deprecated 15.0
829
+ */
830
  protected function isModuleOptionsRequest() :bool {
831
+ return false;
832
  }
833
 
834
  protected function isWizardPage() :bool {
840
  * @param string $suffix
841
  * @param string $glue
842
  * @return string
843
+ * @deprecated 15.0
844
  */
845
  public function prefix( $suffix = '', $glue = '-' ) {
846
  return $this->getCon()->prefix( $suffix, $glue );
847
  }
848
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
849
  protected function buildContextualHelp() {
850
  if ( !function_exists( 'get_current_screen' ) ) {
851
  require_once( ABSPATH.'wp-admin/includes/screen.php' );
867
  }
868
 
869
  /**
 
 
870
  * @uses nonce
871
  */
872
  public function getUrl_Wizard( string $wizardSlug ) :string {
923
  }
924
 
925
  public function getIsShowMarketing() :bool {
926
+ return (bool)apply_filters( 'shield/show_marketing', !$this->isPremium() );
927
  }
928
 
929
+ public function isAccessRestricted() :bool {
930
+ return $this->cfg->properties[ 'access_restricted' ] && !$this->getCon()->isPluginAdmin();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
931
  }
932
 
933
  public function canDisplayOptionsForm() :bool {
934
+ return !$this->cfg->properties[ 'access_restricted' ] || $this->getCon()->isPluginAdmin();
935
  }
936
 
937
  public function getScriptLocalisations() :array {
938
+ return [];
 
 
 
 
 
 
 
 
 
 
 
939
  }
940
 
941
  public function getCustomScriptEnqueues() :array {
942
  return [];
943
  }
944
 
945
+ public function renderTemplate( string $template, array $data = [] ) :string {
946
+ return $this->getRenderer()
947
+ ->setTemplate( $template )
948
+ ->setRenderData( $data )
949
+ ->render();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
950
  }
951
 
952
  public function getMainWpData() :array {
987
  return $this->opts;
988
  }
989
 
 
 
 
 
 
 
 
 
990
  /**
991
  * @return AdminPage
992
  */
1001
  * @return Shield\Modules\Base\WpCli
1002
  */
1003
  public function getWpCli() {
1004
+ if ( !isset( $this->wpCli ) ) {
1005
+ $this->wpCli = $this->loadModElement( 'WpCli' );
1006
  }
1007
+ return $this->wpCli;
1008
  }
1009
 
1010
  /**
1014
  return $this->loadStrings()->setMod( $this );
1015
  }
1016
 
1017
+ /**
1018
+ * @return mixed|Shield\Modules\Base\Renderer
1019
+ */
1020
+ public function getRenderer() {
1021
+ /** @var Renderer $r */
1022
+ $r = $this->loadModElement( 'Renderer' );
1023
+ return $r->setMod( $this );
1024
+ }
1025
+
1026
  /**
1027
  * @return Shield\Modules\Base\UI
1028
  */
1037
  * @return Shield\Modules\Base\Reporting|mixed|false
1038
  */
1039
  public function getReportingHandler() {
1040
+ if ( !isset( $this->reporting ) ) {
1041
+ $this->reporting = $this->loadModElement( 'Reporting' );
1042
  }
1043
+ return $this->reporting;
1044
  }
1045
 
1046
  public function getAdminNotices() {
src/lib/src/Modules/Base/Options.php CHANGED
@@ -2,6 +2,7 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Options\OptValueSanitize;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Services\Services;
@@ -24,11 +25,6 @@ class Options {
24
  */
25
  protected $aOld;
26
 
27
- /**
28
- * @var array
29
- */
30
- protected $aRawOptionsConfigData;
31
-
32
  /**
33
  * @var bool
34
  */
@@ -77,10 +73,6 @@ class Options {
77
  return $this->aOptionsValues;
78
  }
79
 
80
- public function getSlug() :string {
81
- return (string)$this->getFeatureProperty( 'slug' );
82
- }
83
-
84
  /**
85
  * Returns an array of all the transferable options and their values
86
  */
@@ -133,7 +125,7 @@ class Options {
133
  */
134
  public function getOptionsForTracking() :array {
135
  $opts = [];
136
- if ( (bool)$this->getFeatureProperty( 'tracking_exclude' ) === false ) {
137
 
138
  $options = $this->getAllOptionsValues();
139
  foreach ( $this->getOptionsKeys() as $key ) {
@@ -152,7 +144,6 @@ class Options {
152
  }
153
 
154
  /**
155
- * @param string $property
156
  * @return mixed|null
157
  */
158
  public function getFeatureProperty( string $property ) {
@@ -179,7 +170,7 @@ class Options {
179
  }
180
 
181
  /**
182
- * @return string
183
  */
184
  public function getFeatureTagline() {
185
  return $this->getFeatureProperty( 'tagline' );
@@ -410,24 +401,19 @@ class Options {
410
  }
411
 
412
  /**
413
- * @param string $key
414
- * @param string $prop
415
  * @return mixed|null
416
  */
417
  public function getOptProperty( string $key, string $prop ) {
418
  return $this->getOptDefinition( $key )[ $prop ] ?? null;
419
  }
420
 
 
 
 
 
421
  public function getRawData_FullFeatureConfig() :array {
422
- if ( empty( $this->aRawOptionsConfigData ) ) {
423
- try {
424
- $this->aRawOptionsConfigData = $this->getConfigLoader()->run();
425
- }
426
- catch ( \Exception $e ) {
427
- $this->aRawOptionsConfigData = [];
428
- }
429
- }
430
- return $this->aRawOptionsConfigData;
431
  }
432
 
433
  protected function getRawData_AllOptions() :array {
@@ -453,21 +439,33 @@ class Options {
453
  return $text;
454
  }
455
 
 
 
 
456
  public function isAccessRestricted() :bool {
457
  $state = $this->getFeatureProperty( 'access_restricted' );
458
  return is_null( $state ) || $state;
459
  }
460
 
 
 
 
461
  public function isModuleRunIfWhitelisted() :bool {
462
  $state = $this->getFeatureProperty( 'run_if_whitelisted' );
463
  return is_null( $state ) || $state;
464
  }
465
 
 
 
 
466
  public function isModuleRunUnderWpCli() :bool {
467
  $state = $this->getFeatureProperty( 'run_if_wpcli' );
468
  return is_null( $state ) || $state;
469
  }
470
 
 
 
 
471
  public function isModuleRunIfVerifiedBot() :bool {
472
  return (bool)$this->getFeatureProperty( 'run_if_verified_bot' );
473
  }
@@ -560,7 +558,6 @@ class Options {
560
  }
561
 
562
  /**
563
- * @param string $key
564
  * @return $this
565
  */
566
  public function setOptAt( string $key ) {
@@ -569,8 +566,7 @@ class Options {
569
 
570
  /**
571
  * Use this to directly set the option value without the risk of any recursion.
572
- * @param string $key
573
- * @param mixed $value
574
  * @return $this
575
  */
576
  protected function setOptValue( string $key, $value ) {
@@ -581,9 +577,7 @@ class Options {
581
  }
582
 
583
  /**
584
- * @param string $key
585
- * @param mixed $mPotentialValue
586
- * @return bool
587
  */
588
  private function verifyCanSet( string $key, $mPotentialValue ) :bool {
589
  $valid = true;
@@ -591,14 +585,14 @@ class Options {
591
  switch ( $this->getOptionType( $key ) ) {
592
 
593
  case 'integer':
594
- $nMin = $this->getOptProperty( $key, 'min' );
595
- if ( !is_null( $nMin ) ) {
596
- $valid = $mPotentialValue >= $nMin;
597
  }
598
  if ( $valid ) {
599
- $nMax = $this->getOptProperty( $key, 'max' );
600
- if ( !is_null( $nMax ) ) {
601
- $valid = $mPotentialValue <= $nMax;
602
  }
603
  }
604
  break;
@@ -662,7 +656,6 @@ class Options {
662
  return [
663
  'dismissed_notices',
664
  'ui_track',
665
- 'help_video_options',
666
  'xfer_excluded',
667
  'cfg_version'
668
  ];
@@ -689,7 +682,7 @@ class Options {
689
 
690
  public function getConfigLoader() :Config\LoadConfig {
691
  if ( empty( $this->cfgLoader ) ) {
692
- $this->cfgLoader = ( new Config\LoadConfig() )->setMod( $this->getMod() );
693
  }
694
  return $this->cfgLoader;
695
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Config\ModConfigVO;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Options\OptValueSanitize;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
  use FernleafSystems\Wordpress\Services\Services;
25
  */
26
  protected $aOld;
27
 
 
 
 
 
 
28
  /**
29
  * @var bool
30
  */
73
  return $this->aOptionsValues;
74
  }
75
 
 
 
 
 
76
  /**
77
  * Returns an array of all the transferable options and their values
78
  */
125
  */
126
  public function getOptionsForTracking() :array {
127
  $opts = [];
128
+ if ( !$this->getMod()->cfg->properties[ 'tracking_exclude' ] ) {
129
 
130
  $options = $this->getAllOptionsValues();
131
  foreach ( $this->getOptionsKeys() as $key ) {
144
  }
145
 
146
  /**
 
147
  * @return mixed|null
148
  */
149
  public function getFeatureProperty( string $property ) {
170
  }
171
 
172
  /**
173
+ * @deprecated 15.0
174
  */
175
  public function getFeatureTagline() {
176
  return $this->getFeatureProperty( 'tagline' );
401
  }
402
 
403
  /**
 
 
404
  * @return mixed|null
405
  */
406
  public function getOptProperty( string $key, string $prop ) {
407
  return $this->getOptDefinition( $key )[ $prop ] ?? null;
408
  }
409
 
410
+ public function cfg() :ModConfigVO {
411
+ return $this->getMod()->cfg;
412
+ }
413
+
414
  public function getRawData_FullFeatureConfig() :array {
415
+ // TODO: use the cfg directly throughout instead of via array
416
+ return empty( $this->aRawOptionsConfigData ) ? $this->cfg()->getRawData() : $this->aRawOptionsConfigData;
 
 
 
 
 
 
 
417
  }
418
 
419
  protected function getRawData_AllOptions() :array {
439
  return $text;
440
  }
441
 
442
+ /**
443
+ * @deprecated 15.0
444
+ */
445
  public function isAccessRestricted() :bool {
446
  $state = $this->getFeatureProperty( 'access_restricted' );
447
  return is_null( $state ) || $state;
448
  }
449
 
450
+ /**
451
+ * @deprecated 15.0
452
+ */
453
  public function isModuleRunIfWhitelisted() :bool {
454
  $state = $this->getFeatureProperty( 'run_if_whitelisted' );
455
  return is_null( $state ) || $state;
456
  }
457
 
458
+ /**
459
+ * @deprecated 15.0
460
+ */
461
  public function isModuleRunUnderWpCli() :bool {
462
  $state = $this->getFeatureProperty( 'run_if_wpcli' );
463
  return is_null( $state ) || $state;
464
  }
465
 
466
+ /**
467
+ * @deprecated 15.0
468
+ */
469
  public function isModuleRunIfVerifiedBot() :bool {
470
  return (bool)$this->getFeatureProperty( 'run_if_verified_bot' );
471
  }
558
  }
559
 
560
  /**
 
561
  * @return $this
562
  */
563
  public function setOptAt( string $key ) {
566
 
567
  /**
568
  * Use this to directly set the option value without the risk of any recursion.
569
+ * @param mixed $value
 
570
  * @return $this
571
  */
572
  protected function setOptValue( string $key, $value ) {
577
  }
578
 
579
  /**
580
+ * @param mixed $mPotentialValue
 
 
581
  */
582
  private function verifyCanSet( string $key, $mPotentialValue ) :bool {
583
  $valid = true;
585
  switch ( $this->getOptionType( $key ) ) {
586
 
587
  case 'integer':
588
+ $min = $this->getOptProperty( $key, 'min' );
589
+ if ( !is_null( $min ) ) {
590
+ $valid = $mPotentialValue >= $min;
591
  }
592
  if ( $valid ) {
593
+ $max = $this->getOptProperty( $key, 'max' );
594
+ if ( !is_null( $max ) ) {
595
+ $valid = $mPotentialValue <= $max;
596
  }
597
  }
598
  break;
656
  return [
657
  'dismissed_notices',
658
  'ui_track',
 
659
  'xfer_excluded',
660
  'cfg_version'
661
  ];
682
 
683
  public function getConfigLoader() :Config\LoadConfig {
684
  if ( empty( $this->cfgLoader ) ) {
685
+ $this->cfgLoader = new Config\LoadConfig( $this->getMod()->getSlug() );
686
  }
687
  return $this->cfgLoader;
688
  }
src/lib/src/Modules/Base/Options/HandleOptionsSaveRequest.php ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Options;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Lib\Request\FormParams;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\ImportExport\Options\SaveExcludedOptions;
8
+ use FernleafSystems\Wordpress\Services\Services;
9
+
10
+ class HandleOptionsSaveRequest {
11
+
12
+ use ModConsumer;
13
+
14
+ private $form;
15
+
16
+ public function handleSave() :bool {
17
+ $form = $this->getForm();
18
+ $this->setMod( $this->getCon()->getModule( $form[ 'working_mod' ] ) );
19
+
20
+ try {
21
+ $con = $this->getCon();
22
+ if ( !$con->isPluginAdmin() ) {
23
+ throw new \Exception( __( "You don't currently have permission to save settings.", 'wp-simple-firewall' ) );
24
+ }
25
+ $this->doSaveStandardOptions();
26
+ $this->getMod()->saveModOptions( true );
27
+
28
+ // auto-import notify: ONLY when the options are being updated with a MANUAL save.
29
+ if ( !wp_next_scheduled( $con->prefix( 'importexport_notify' ) ) ) {
30
+ wp_schedule_single_event( Services::Request()->ts() + 30, $con->prefix( 'importexport_notify' ) );
31
+ }
32
+
33
+ $success = true;
34
+ }
35
+ catch ( \Exception $e ) {
36
+ $success = false;
37
+ }
38
+ return $success;
39
+ }
40
+
41
+ private function getForm() :array {
42
+ if ( !isset( $this->form ) ) {
43
+ $this->form = FormParams::Retrieve( FormParams::ENC_BASE64 );
44
+ }
45
+ return $this->form;
46
+ }
47
+
48
+ /**
49
+ * @throws \Exception
50
+ */
51
+ private function doSaveStandardOptions() {
52
+ // standard options use b64 and fail-over to lz-string
53
+ $form = $this->getForm();
54
+
55
+ $optsAndTypes = array_map(
56
+ function ( $optDef ) {
57
+ return $optDef[ 'type' ];
58
+ },
59
+ $this->getOptions()->getVisibleOptions()
60
+ );
61
+ foreach ( $optsAndTypes as $optKey => $optType ) {
62
+
63
+ $optValue = $form[ $optKey ] ?? null;
64
+ if ( is_null( $optValue ) ) {
65
+
66
+ if ( in_array( $optType, [ 'text', 'email' ] ) ) { //text box, and it's null, don't update
67
+ continue;
68
+ }
69
+ elseif ( $optType == 'checkbox' ) { //if it was a checkbox, and it's null, it means 'N'
70
+ $optValue = 'N';
71
+ }
72
+ elseif ( $optType == 'integer' ) { //if it was a integer, and it's null, it means '0'
73
+ $optValue = 0;
74
+ }
75
+ elseif ( $optType == 'multiple_select' ) {
76
+ $optValue = [];
77
+ }
78
+ }
79
+ else { //handle any pre-processing we need to.
80
+
81
+ if ( $optType == 'text' || $optType == 'email' ) {
82
+ $optValue = trim( $optValue );
83
+ }
84
+ if ( $optType == 'integer' ) {
85
+ $optValue = intval( $optValue );
86
+ }
87
+ elseif ( $optType == 'password' ) {
88
+ $sTempValue = trim( $optValue );
89
+ if ( empty( $sTempValue ) ) {
90
+ continue;
91
+ }
92
+
93
+ $confirm = $form[ $optKey.'_confirm' ] ?? null;
94
+ if ( $sTempValue !== $confirm ) {
95
+ throw new \Exception( __( 'Password values do not match.', 'wp-simple-firewall' ) );
96
+ }
97
+
98
+ $optValue = md5( $sTempValue );
99
+ }
100
+ elseif ( $optType == 'array' ) { //arrays are textareas, where each is separated by newline
101
+ $optValue = array_filter( explode( "\n", esc_textarea( $optValue ) ), 'trim' );
102
+ }
103
+ elseif ( $optType == 'comma_separated_lists' ) {
104
+ $optValue = Services::Data()->extractCommaSeparatedList( $optValue );
105
+ }
106
+ /* elseif ( $optType == 'multiple_select' ) { } */
107
+ }
108
+
109
+ // Prevent overwriting of non-editable fields
110
+ if ( !in_array( $optType, [ 'noneditable_text' ] ) ) {
111
+ $this->getOptions()->setOpt( $optKey, $optValue );
112
+ }
113
+ }
114
+
115
+ // Handle Import/Export exclusions
116
+ if ( $this->getCon()->isPremiumActive() ) {
117
+ ( new SaveExcludedOptions() )
118
+ ->setMod( $this->getMod() )
119
+ ->save( $form );
120
+ }
121
+ }
122
+ }
src/lib/src/Modules/Base/Options/RenderOptionsForm.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Options;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\ImportExport\Options\BuildTransferableOptions;
7
+
8
+ class RenderOptionsForm {
9
+
10
+ use ModConsumer;
11
+
12
+ public function render() :string {
13
+ $mod = $this->getMod();
14
+
15
+ try {
16
+ return $mod->getRenderer()
17
+ ->setTemplate( '/components/options_form/main.twig' )
18
+ ->setRenderData( [
19
+ 'hrefs' => [
20
+ 'form_action' => 'admin.php?page='.$mod->getModSlug(),
21
+ ],
22
+ 'vars' => [
23
+ 'working_mod' => $mod->getSlug(),
24
+ 'all_options' => $this->buildOptionsForStandardUI(),
25
+ 'xferable_opts' => ( new BuildTransferableOptions() )
26
+ ->setMod( $mod )
27
+ ->build(),
28
+ ],
29
+ ] )
30
+ ->render();
31
+ }
32
+ catch ( \Exception $e ) {
33
+ return 'Error rendering options form: '.$e->getMessage();
34
+ }
35
+ }
36
+
37
+ public function buildOptionsForStandardUI() :array {
38
+ return ( new BuildForDisplay() )
39
+ ->setMod( $this->getMod() )
40
+ ->setIsWhitelabelled( $this->getCon()->getModule_SecAdmin()->getWhiteLabelController()->isEnabled() )
41
+ ->standard();
42
+ }
43
+ }
src/lib/src/Modules/Base/Options/WildCardOptions.php CHANGED
@@ -88,7 +88,7 @@ class WildCardOptions {
88
  return array_unique( $optValues );
89
  }
90
 
91
- public function buildFullRegexValue( string $value, int $type ) :string {
92
  $valueRegEx = $this->convertValueToRegEx( $value, $type );
93
 
94
  switch ( $type ) {
@@ -102,7 +102,7 @@ class WildCardOptions {
102
  break;
103
  }
104
 
105
- return sprintf( '#^%s$#i', $fullValue );
106
  }
107
 
108
  protected function convertValueToRegEx( string $value, int $type ) :string {
88
  return array_unique( $optValues );
89
  }
90
 
91
+ public function buildFullRegexValue( string $value, int $type, bool $regexWrap = true ) :string {
92
  $valueRegEx = $this->convertValueToRegEx( $value, $type );
93
 
94
  switch ( $type ) {
102
  break;
103
  }
104
 
105
+ return $regexWrap ? sprintf( '#^%s$#i', $fullValue ) : $fullValue;
106
  }
107
 
108
  protected function convertValueToRegEx( string $value, int $type ) :string {
src/lib/src/Modules/Base/Processor.php CHANGED
@@ -29,7 +29,12 @@ abstract class Processor {
29
  }
30
 
31
  public function onWpAdminInit() {
32
- add_filter( $this->getCon()->prefix( 'admin_bar_menu_groups' ), [ $this, 'addAdminBarMenuGroup' ] );
 
 
 
 
 
33
  }
34
 
35
  public function addAdminBarMenuGroup( array $groups ) :array {
29
  }
30
 
31
  public function onWpAdminInit() {
32
+ /** @var Shield\Modules\Plugin\Options $optsPlugin */
33
+ $optsPlugin = $this->getCon()->getModule_Plugin()->getOptions();
34
+ // @deprecated 14.1.8
35
+ if ( method_exists( $optsPlugin, 'isShowPluginNotices' ) && $optsPlugin->isShowPluginNotices() ) {
36
+ add_filter( $this->getCon()->prefix( 'admin_bar_menu_groups' ), [ $this, 'addAdminBarMenuGroup' ] );
37
+ }
38
  }
39
 
40
  public function addAdminBarMenuGroup( array $groups ) :array {
src/lib/src/Modules/Base/Renderer.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Render\BaseTemplateRenderer;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
+
9
+ class Renderer extends BaseTemplateRenderer {
10
+
11
+ use ModConsumer;
12
+
13
+ public function getRenderData() :array {
14
+ $data = parent::getRenderData();
15
+
16
+ if ( empty( $data[ 'unique_render_id' ] ) ) {
17
+ $data[ 'unique_render_id' ] = 'noticeid-'.uniqid();
18
+ }
19
+
20
+ $data = Services::DataManipulation()->mergeArraysRecursive(
21
+ $this->getMod()->getUIHandler()->getBaseDisplayData(),
22
+ $data
23
+ );
24
+
25
+ $data[ 'strings' ] = Services::DataManipulation()->mergeArraysRecursive(
26
+ $this->getMod()->getStrings()->getDisplayStrings(),
27
+ $data[ 'strings' ] ?? []
28
+ );
29
+
30
+ return $data;
31
+ }
32
+ }
src/lib/src/Modules/Base/Strings.php CHANGED
@@ -10,7 +10,7 @@ class Strings {
10
  use ModConsumer;
11
 
12
  public function getModTagLine() :string {
13
- return (string)__( $this->getOptions()->getFeatureProperty( 'tagline' ), 'wp-simple-firewall' );
14
  }
15
 
16
  /**
@@ -19,7 +19,7 @@ class Strings {
19
  public function getDisplayStrings() :array {
20
  $con = $this->getCon();
21
 
22
- $aProFeatures = [
23
  __( 'More Scans', 'wp-simple-firewall' ),
24
  __( 'Malware Scanner', 'wp-simple-firewall' ),
25
  __( 'Scan Every Hour', 'wp-simple-firewall' ),
@@ -30,14 +30,13 @@ class Strings {
30
  __( 'WooCommerce Support', 'wp-simple-firewall' ),
31
  __( 'MainWP Integration', 'wp-simple-firewall' ),
32
  ];
33
- $aProFeaturesDisplay = array_intersect_key( $aProFeatures, array_flip( array_rand( $aProFeatures, 6 ) ) );
34
- $aProFeaturesDisplay[] = __( 'and much more!' );
35
 
36
  $bIsAdvanced = $this->getCon()->getModule_Plugin()->isShowAdvanced();
37
 
38
  return Services::DataManipulation()->mergeArraysRecursive(
39
  [
40
- 'see_help_video' => __( 'Watch Help Video' ),
41
  'btn_save' => __( 'Save Options' ),
42
  'btn_options' => __( 'Options' ),
43
  'btn_help' => __( 'Help' ),
@@ -51,6 +50,7 @@ class Strings {
51
  'time_until' => __( 'Until', 'wp-simple-firewall' ),
52
  'time_since' => __( 'Since', 'wp-simple-firewall' ),
53
  'more_info' => __( 'Info', 'wp-simple-firewall' ),
 
54
  'opt_info_helpdesk' => __( 'Read the HelpDesk article for this option', 'wp-simple-firewall' ),
55
  'opt_info_blog' => __( 'Read our Blog article for this option', 'wp-simple-firewall' ),
56
  'logged_in' => __( 'Logged-In', 'wp-simple-firewall' ),
@@ -114,20 +114,15 @@ class Strings {
114
 
115
  'pro_features' => __( 'Pro features include', 'wp-simple-firewall' ),
116
  'join_thousands_H' => __( "Join The 1,000s Who've Already Upgraded Their WordPress Security To Better Protect Their Sites.", 'wp-simple-firewall' ),
117
- 'join_thousands_P' => implode( ', ', $aProFeaturesDisplay ),
118
  'get_pro_protection' => __( 'Get Pro Protection', 'wp-simple-firewall' ),
119
 
120
- 'recommendation' => ucfirst( __( 'recommendation', 'wp-simple-firewall' ) ),
121
- 'suggestion' => ucfirst( __( 'suggestion', 'wp-simple-firewall' ) ),
122
- 'box_welcome_title' => sprintf( __( 'Welcome To %s Security Insights Dashboard', 'wp-simple-firewall' ), $con->getHumanName() ),
123
- 'options' => __( 'Options', 'wp-simple-firewall' ),
124
- 'not_available' => __( 'Sorry, this feature is included with Pro subscriptions.', 'wp-simple-firewall' ),
125
- 'not_enabled' => __( "This feature isn't currently enabled.", 'wp-simple-firewall' ),
126
- 'please_upgrade' => __( 'You can get this feature (along with loads more) by going Pro.', 'wp-simple-firewall' ),
127
- 'please_enable' => __( 'Please turn on this feature in the options.', 'wp-simple-firewall' ),
128
- 'no_security_notices' => __( 'There are no important security notices at this time.', 'wp-simple-firewall' ),
129
- 'this_is_wonderful' => __( 'This is wonderful!', 'wp-simple-firewall' ),
130
- 'yyyymmdd' => __( 'YYYY-MM-DD', 'wp-simple-firewall' ),
131
 
132
  'wphashes_token' => 'WPHashes.com API Token',
133
  'is_opt_importexport' => __( 'Is this option included with import/export?', 'wp-simple-firewall' ),
@@ -166,7 +161,7 @@ class Strings {
166
  */
167
  public function getOptionStrings( string $key ) :array {
168
  $opt = $this->getOptions()->getOptDefinition( $key );
169
- if ( is_array( $opt ) && !empty( $opt[ 'name' ] ) && !empty( $opt[ 'summary' ] ) && !empty( $opt[ 'description' ] ) ) {
170
  return [
171
  'name' => __( $opt[ 'name' ], 'wp-simple-firewall' ),
172
  'summary' => __( $opt[ 'summary' ], 'wp-simple-firewall' ),
@@ -182,7 +177,6 @@ class Strings {
182
  public function getSectionStrings( string $section ) :array {
183
 
184
  switch ( $section ) {
185
-
186
  default:
187
  $section = $this->getOptions()->getSection( $section );
188
  if ( is_array( $section ) && !empty( $section[ 'title' ] ) && !empty( $section[ 'title_short' ] ) ) {
10
  use ModConsumer;
11
 
12
  public function getModTagLine() :string {
13
+ return __( $this->getMod()->cfg->properties[ 'tagline' ], 'wp-simple-firewall' );
14
  }
15
 
16
  /**
19
  public function getDisplayStrings() :array {
20
  $con = $this->getCon();
21
 
22
+ $proFeatures = [
23
  __( 'More Scans', 'wp-simple-firewall' ),
24
  __( 'Malware Scanner', 'wp-simple-firewall' ),
25
  __( 'Scan Every Hour', 'wp-simple-firewall' ),
30
  __( 'WooCommerce Support', 'wp-simple-firewall' ),
31
  __( 'MainWP Integration', 'wp-simple-firewall' ),
32
  ];
33
+ $proFeaturesDisplay = array_intersect_key( $proFeatures, array_flip( array_rand( $proFeatures, 6 ) ) );
34
+ $proFeaturesDisplay[] = __( 'and much more!' );
35
 
36
  $bIsAdvanced = $this->getCon()->getModule_Plugin()->isShowAdvanced();
37
 
38
  return Services::DataManipulation()->mergeArraysRecursive(
39
  [
 
40
  'btn_save' => __( 'Save Options' ),
41
  'btn_options' => __( 'Options' ),
42
  'btn_help' => __( 'Help' ),
50
  'time_until' => __( 'Until', 'wp-simple-firewall' ),
51
  'time_since' => __( 'Since', 'wp-simple-firewall' ),
52
  'more_info' => __( 'Info', 'wp-simple-firewall' ),
53
+ 'view_details' => __( 'View Details', 'wp-simple-firewall' ),
54
  'opt_info_helpdesk' => __( 'Read the HelpDesk article for this option', 'wp-simple-firewall' ),
55
  'opt_info_blog' => __( 'Read our Blog article for this option', 'wp-simple-firewall' ),
56
  'logged_in' => __( 'Logged-In', 'wp-simple-firewall' ),
114
 
115
  'pro_features' => __( 'Pro features include', 'wp-simple-firewall' ),
116
  'join_thousands_H' => __( "Join The 1,000s Who've Already Upgraded Their WordPress Security To Better Protect Their Sites.", 'wp-simple-firewall' ),
117
+ 'join_thousands_P' => implode( ', ', $proFeaturesDisplay ),
118
  'get_pro_protection' => __( 'Get Pro Protection', 'wp-simple-firewall' ),
119
 
120
+ 'options' => __( 'Options', 'wp-simple-firewall' ),
121
+ 'not_available' => __( 'Sorry, this feature is included with Pro subscriptions.', 'wp-simple-firewall' ),
122
+ 'not_enabled' => __( "This feature isn't currently enabled.", 'wp-simple-firewall' ),
123
+ 'please_upgrade' => __( 'You can get this feature (along with loads more) by going Pro.', 'wp-simple-firewall' ),
124
+ 'please_enable' => __( 'Please turn on this feature in the options.', 'wp-simple-firewall' ),
125
+ 'yyyymmdd' => __( 'YYYY-MM-DD', 'wp-simple-firewall' ),
 
 
 
 
 
126
 
127
  'wphashes_token' => 'WPHashes.com API Token',
128
  'is_opt_importexport' => __( 'Is this option included with import/export?', 'wp-simple-firewall' ),
161
  */
162
  public function getOptionStrings( string $key ) :array {
163
  $opt = $this->getOptions()->getOptDefinition( $key );
164
+ if ( !empty( $opt[ 'name' ] ) && !empty( $opt[ 'summary' ] ) && !empty( $opt[ 'description' ] ) ) {
165
  return [
166
  'name' => __( $opt[ 'name' ], 'wp-simple-firewall' ),
167
  'summary' => __( $opt[ 'summary' ], 'wp-simple-firewall' ),
177
  public function getSectionStrings( string $section ) :array {
178
 
179
  switch ( $section ) {
 
180
  default:
181
  $section = $this->getOptions()->getSection( $section );
182
  if ( is_array( $section ) && !empty( $section[ 'title' ] ) && !empty( $section[ 'title_short' ] ) ) {
src/lib/src/Modules/Base/UI.php CHANGED
@@ -17,20 +17,26 @@ class UI {
17
  ->standard();
18
  }
19
 
20
- public function buildSelectData_ModuleSettings() :array {
21
- return $this->getMod()->getModulesSummaryData();
22
- }
23
-
24
  public function buildSelectData_OptionsSearch() :array {
25
- $modsToSearch = array_filter(
26
- $this->getMod()->getModulesSummaryData(),
27
- function ( $modSummary ) {
28
- return !empty( $modSummary[ 'show_mod_opts' ] );
29
- }
30
- );
31
  $searchSelect = [];
32
- foreach ( $modsToSearch as $slug => $summary ) {
33
- $searchSelect[ $summary[ 'name' ] ] = $summary[ 'options' ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  }
35
  return $searchSelect;
36
  }
@@ -44,51 +50,15 @@ class UI {
44
  $pluginOptions = $con->getModule_Plugin()->getOptions();
45
 
46
  return [
47
- 'sPluginName' => $con->getHumanName(),
48
- 'sTagline' => $this->getOptions()->getFeatureTagline(),
49
- 'nonce_field' => wp_nonce_field( $con->getPluginPrefix(), '_wpnonce', true, false ), //don't echo!
50
- 'form_action' => 'admin.php?page='.$mod->getModSlug(),
51
- 'aPluginLabels' => $con->getLabels(),
52
- 'help_video' => [
53
- 'auto_show' => $this->getIfAutoShowHelpVideo(),
54
- 'display_id' => 'ShieldHelpVideo'.$mod->getSlug(),
55
- 'options' => $this->getHelpVideoOptions(),
56
- 'displayable' => $this->isHelpVideoDisplayable(),
57
- 'show' => $this->isHelpVideoDisplayable() && !$this->getHelpVideoHasBeenClosed(),
58
- 'width' => 772,
59
- 'height' => 454,
60
- ],
61
-
62
- 'aSummaryData' => array_filter(
63
- $mod->getModulesSummaryData(),
64
- function ( $summary ) {
65
- return $summary[ 'show_mod_opts' ];
66
- }
67
- ),
68
 
69
  'sPageTitle' => $mod->getMainFeatureName(),
70
- 'data' => [
71
- 'mod_slug' => $mod->getModSlug( true ),
72
- 'mod_slug_short' => $mod->getModSlug( false ),
73
- 'all_options' => $this->buildOptionsForStandardUI(),
74
- 'xferable_opts' => ( new Shield\Modules\Plugin\Lib\ImportExport\Options\BuildTransferableOptions() )
75
- ->setMod( $mod )
76
- ->build(),
77
- 'hidden_options' => $this->getOptions()->getHiddenOptions()
78
- ],
79
- 'vars' => [
80
- 'mod_slug' => $mod->getModSlug( true ),
81
- ],
82
  'ajax' => [
83
- 'mod_options' => $mod->getAjaxActionData( 'mod_options', true ),
84
- 'mod_opts_form_render' => $mod->getAjaxActionData( 'mod_opts_form_render', true ),
85
- ],
86
- 'vendors' => [
87
- 'widget_freshdesk' => '3000000081' /* TODO: plugin spec config */
88
  ],
89
  'strings' => $mod->getStrings()->getDisplayStrings(),
90
  'flags' => [
91
- 'access_restricted' => !$mod->canDisplayOptionsForm(),
92
  'show_ads' => $mod->getIsShowMarketing(),
93
  'wrap_page_content' => true,
94
  'show_standard_options' => true,
@@ -120,6 +90,7 @@ class UI {
120
  ],
121
  'imgs' => [
122
  'svgs' => [
 
123
  'ignore' => $con->svgs->raw( 'bootstrap/eye-slash-fill.svg' ),
124
  'triangle' => $con->svgs->raw( 'bootstrap/triangle-fill.svg' ),
125
  ],
@@ -133,86 +104,18 @@ class UI {
133
  'actions' => '',
134
  'help' => '',
135
  'wizard_landing' => ''
136
- ]
137
- ];
138
- }
139
-
140
- public function getInsightsOverviewCards() :array {
141
- /** @var Insights\OverviewCards $oc */
142
- $oc = $this->loadInsightsHelperClass( 'OverviewCards' );
143
- return $oc->build();
144
- }
145
-
146
- protected function getModDisabledCard() :array {
147
- $mod = $this->getMod();
148
- return [
149
- 'name' => __( 'Module Disabled', 'wp-simple-firewall' ),
150
- 'summary' => __( 'All features of this module are completely disabled', 'wp-simple-firewall' ),
151
- 'state' => -1,
152
- 'href' => $mod->getUrl_DirectLinkToOption( $mod->getEnableModOptKey() ),
153
- ];
154
- }
155
-
156
- protected function getModDisabledInsight() :array {
157
- $mod = $this->getMod();
158
- return [
159
- 'name' => __( 'Module Disabled', 'wp-simple-firewall' ),
160
- 'enabled' => false,
161
- 'summary' => __( 'All features of this module are completely disabled', 'wp-simple-firewall' ),
162
- 'weight' => 2,
163
- 'href' => $mod->getUrl_DirectLinkToOption( $mod->getEnableModOptKey() ),
164
  ];
165
  }
166
 
167
- protected function getHelpVideoOptions() :array {
168
- $aOptions = $this->getOptions()->getOpt( 'help_video_options', [] );
169
- if ( is_null( $aOptions ) || !is_array( $aOptions ) ) {
170
- $aOptions = [
171
- 'closed' => false,
172
- 'displayed' => false,
173
- 'played' => false,
174
- ];
175
- $this->getOptions()->setOpt( 'help_video_options', $aOptions );
176
- }
177
- return $aOptions;
178
- }
179
-
180
  protected function getHelpVideoUrl( string $id ) :string {
181
  return sprintf( 'https://player.vimeo.com/video/%s', $id );
182
  }
183
 
184
- protected function getIfAutoShowHelpVideo() :bool {
185
- return !$this->getHelpVideoHasBeenClosed();
186
- }
187
-
188
- protected function getHelpVideoHasBeenDisplayed() :bool {
189
- return (bool)$this->getHelpVideoOption( 'displayed' );
190
- }
191
-
192
- protected function getVideoHasBeenPlayed() :bool {
193
- return (bool)$this->getHelpVideoOption( 'played' );
194
- }
195
-
196
- /**
197
- * @param string $key
198
- * @return mixed|null
199
- */
200
- protected function getHelpVideoOption( $key ) {
201
- $opts = $this->getHelpVideoOptions();
202
- return $opts[ $key ] ?? null;
203
- }
204
-
205
- protected function getHelpVideoHasBeenClosed() :bool {
206
- return (bool)$this->getHelpVideoOption( 'closed' );
207
- }
208
-
209
- /**
210
- * @return bool
211
- */
212
- protected function isHelpVideoDisplayable() {
213
- return false;
214
- }
215
-
216
  public function getSectionNotices( string $section ) :array {
217
  return [];
218
  }
@@ -220,31 +123,4 @@ class UI {
220
  public function getSectionWarnings( string $section ) :array {
221
  return [];
222
  }
223
-
224
- /**
225
- * @return bool
226
- * @deprecated 10.0
227
- */
228
- public function isEnabledForUiSummary() :bool {
229
- return $this->getMod()->isModuleEnabled();
230
- }
231
-
232
- protected function loadInsightsHelperClass( string $classToLoad ) {
233
- try {
234
- $NS = ( new \ReflectionClass( $this ) )->getNamespaceName();
235
- }
236
- catch ( \Exception $e ) {
237
- $NS = __NAMESPACE__;
238
- }
239
-
240
- $fullClass = rtrim( $NS, '\\' ).'\\Insights\\'.$classToLoad;
241
- if ( !@class_exists( $fullClass ) ) {
242
- $fullClass = __NAMESPACE__.'\\Insights\\'.$classToLoad;
243
- }
244
-
245
- /** @var ModConsumer $class */
246
- $class = new $fullClass();
247
- $class->setMod( $this->getMod() );
248
- return $class;
249
- }
250
  }
17
  ->standard();
18
  }
19
 
 
 
 
 
20
  public function buildSelectData_OptionsSearch() :array {
 
 
 
 
 
 
21
  $searchSelect = [];
22
+ foreach ( $this->getCon()->modules as $module ) {
23
+ $cfg = $module->cfg;
24
+ if ( $cfg->properties[ 'show_module_options' ] ) {
25
+ $options = [];
26
+ foreach ( $module->getOptions()->getVisibleOptionsKeys() as $optKey ) {
27
+ try {
28
+ $options[ $optKey ] = array_merge(
29
+ $module->getStrings()->getOptionStrings( $optKey ),
30
+ [
31
+ 'href' => $module->getUrl_DirectLinkToOption( $optKey )
32
+ ]
33
+ );
34
+ }
35
+ catch ( \Exception $e ) {
36
+ }
37
+ }
38
+ $searchSelect[ $module->getMainFeatureName() ] = $options;
39
+ }
40
  }
41
  return $searchSelect;
42
  }
50
  $pluginOptions = $con->getModule_Plugin()->getOptions();
51
 
52
  return [
53
+ 'sTagline' => $mod->cfg->properties[ 'tagline' ],
54
+ 'nonce_field' => wp_nonce_field( $con->getPluginPrefix(), '_wpnonce', true, false ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
  'sPageTitle' => $mod->getMainFeatureName(),
 
 
 
 
 
 
 
 
 
 
 
 
57
  'ajax' => [
 
 
 
 
 
58
  ],
59
  'strings' => $mod->getStrings()->getDisplayStrings(),
60
  'flags' => [
61
+ 'access_restricted' => method_exists( $mod, 'isAccessRestricted' ) && $mod->isAccessRestricted(),
62
  'show_ads' => $mod->getIsShowMarketing(),
63
  'wrap_page_content' => true,
64
  'show_standard_options' => true,
90
  ],
91
  'imgs' => [
92
  'svgs' => [
93
+ 'help' => $con->svgs->raw( 'bootstrap/question-circle.svg' ),
94
  'ignore' => $con->svgs->raw( 'bootstrap/eye-slash-fill.svg' ),
95
  'triangle' => $con->svgs->raw( 'bootstrap/triangle-fill.svg' ),
96
  ],
104
  'actions' => '',
105
  'help' => '',
106
  'wizard_landing' => ''
107
+ ],
108
+ 'vars' => [
109
+ 'mod_slug' => $mod->getModSlug(),
110
+ 'unique_render_id' => uniqid(),
111
+ ],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  ];
113
  }
114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  protected function getHelpVideoUrl( string $id ) :string {
116
  return sprintf( 'https://player.vimeo.com/video/%s', $id );
117
  }
118
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  public function getSectionNotices( string $section ) :array {
120
  return [];
121
  }
123
  public function getSectionWarnings( string $section ) :array {
124
  return [];
125
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  }
src/lib/src/Modules/BaseShield/AdminPage.php CHANGED
@@ -4,15 +4,4 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
4
 
5
  class AdminPage extends \FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\AdminPage {
6
 
7
- /**
8
- * @uses echo()
9
- */
10
- public function displayModuleAdminPage() {
11
- if ( $this->getMod()->canDisplayOptionsForm() ) {
12
- parent::displayModuleAdminPage();
13
- }
14
- else {
15
- echo $this->getMod()->renderRestrictedPage();
16
- }
17
- }
18
  }
4
 
5
  class AdminPage extends \FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\AdminPage {
6
 
 
 
 
 
 
 
 
 
 
 
 
7
  }
src/lib/src/Modules/BaseShield/AjaxHandler.php CHANGED
@@ -10,41 +10,10 @@ class AjaxHandler extends Base\AjaxHandler {
10
  $map = parent::getAjaxActionCallbackMap( $isAuth );
11
  if ( $isAuth ) {
12
  $map = array_merge( $map, [
13
- 'mod_opts_form_render' => [ $this, 'ajaxExec_ModOptionsFormRender' ],
14
- 'mod_options' => [ $this, 'ajaxExec_ModOptions' ],
15
- 'wiz_process_step' => [ $this->getMod()->getWizardHandler(), 'ajaxExec_WizProcessStep' ],
16
- 'wiz_render_step' => [ $this->getMod()->getWizardHandler(), 'ajaxExec_WizRenderStep' ],
17
  ] );
18
  }
19
  return $map;
20
  }
21
-
22
- public function ajaxExec_ModOptions() :array {
23
- $name = $this->getCon()->getHumanName();
24
-
25
- try {
26
- $this->getMod()->saveOptionsSubmit();
27
- $success = true;
28
- $msg = sprintf( __( '%s Plugin options updated successfully.', 'wp-simple-firewall' ), $name );
29
- }
30
- catch ( \Exception $e ) {
31
- $success = false;
32
- $msg = sprintf( __( 'Failed to update %s plugin options.', 'wp-simple-firewall' ), $name )
33
- .' '.$e->getMessage();
34
- }
35
-
36
- return [
37
- 'success' => $success,
38
- 'html' => '', //we reload the page
39
- 'message' => $msg
40
- ];
41
- }
42
-
43
- public function ajaxExec_ModOptionsFormRender() :array {
44
- return [
45
- 'success' => true,
46
- 'html' => $this->getMod()->renderOptionsForm(),
47
- 'message' => 'loaded'
48
- ];
49
- }
50
  }
10
  $map = parent::getAjaxActionCallbackMap( $isAuth );
11
  if ( $isAuth ) {
12
  $map = array_merge( $map, [
13
+ 'wiz_process_step' => [ $this->getMod()->getWizardHandler(), 'ajaxExec_WizProcessStep' ],
14
+ 'wiz_render_step' => [ $this->getMod()->getWizardHandler(), 'ajaxExec_WizRenderStep' ],
 
 
15
  ] );
16
  }
17
  return $map;
18
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  }
src/lib/src/Modules/BaseShield/ModCon.php CHANGED
@@ -12,24 +12,37 @@ class ModCon extends Base\ModCon {
12
 
13
  /**
14
  * @var bool
 
15
  */
16
  protected static $bIsVerifiedBot;
17
 
18
  /**
19
  * @var bool
 
20
  */
21
  private static $bVisitorIsWhitelisted;
22
 
 
 
 
23
  public function getDbHandler_Sessions() :Shield\Databases\Session\Handler {
24
  return $this->getCon()
25
  ->getModule_Sessions()
26
  ->getDbHandler_Sessions();
27
  }
28
 
 
 
 
 
 
 
 
29
  /**
30
  * @return Shield\Databases\Session\EntryVO|null
 
31
  */
32
- public function getSession() {
33
  return $this->getCon()
34
  ->getModule_Sessions()
35
  ->getSessionCon()
@@ -82,108 +95,48 @@ class ModCon extends Base\ModCon {
82
  ->getPluginReportEmail();
83
  }
84
 
85
- /**
86
- * @uses echo()
87
- */
88
- public function displayModuleAdminPage() {
89
- if ( $this->canDisplayOptionsForm() ) {
90
- parent::displayModuleAdminPage();
91
- }
92
- else {
93
- echo $this->renderRestrictedPage();
94
- }
95
- }
96
-
97
- public function renderRestrictedPage() :string {
98
- /** @var Shield\Modules\SecurityAdmin\Options $secOpts */
99
- $secOpts = $this->getCon()
100
- ->getModule_SecAdmin()
101
- ->getOptions();
102
-
103
- return $this->renderTemplate(
104
- '/wpadmin_pages/security_admin/index.twig',
105
- Services::DataManipulation()
106
- ->mergeArraysRecursive(
107
- $this->getUIHandler()->getBaseDisplayData(),
108
- [
109
- 'ajax' => [
110
- 'restricted_access' => $this->getAjaxActionData( 'restricted_access' ),
111
- ],
112
- 'strings' => [
113
- 'force_remove_email' => __( "If you've forgotten your PIN, a link can be sent to the plugin administrator email address to remove this restriction.", 'wp-simple-firewall' ),
114
- 'click_email' => __( "Click here to send the verification email.", 'wp-simple-firewall' ),
115
- 'send_to_email' => sprintf( __( "Email will be sent to %s", 'wp-simple-firewall' ),
116
- Utilities\Obfuscate::Email( $this->getPluginReportEmail() ) ),
117
- 'no_email_override' => __( "The Security Administrator has restricted the use of the email override feature.", 'wp-simple-firewall' ),
118
- ],
119
- 'flags' => [
120
- 'allow_email_override' => $secOpts->isEmailOverridePermitted()
121
- ]
122
- ]
123
- ),
124
- true );
125
- }
126
-
127
  public function getIfSupport3rdParty() :bool {
128
  return $this->isPremium();
129
  }
130
 
131
  /**
132
- * @return bool
133
  * @throws \Exception
134
  */
135
  protected function isReadyToExecute() :bool {
136
- $opts = $this->getOptions();
137
- return ( $opts->isModuleRunIfWhitelisted() || !$this->isVisitorWhitelisted() )
138
- && ( $opts->isModuleRunIfVerifiedBot() || !$this->isVerifiedBot() )
139
- && ( $opts->isModuleRunUnderWpCli() || !Services::WpGeneral()->isWpCli() )
140
  && parent::isReadyToExecute();
141
  }
142
 
 
 
 
143
  public function isVisitorWhitelisted() :bool {
144
- if ( !isset( self::$bVisitorIsWhitelisted ) ) {
145
-
146
- $ipID = Services::IP()->getIpDetector()->getIPIdentity();
147
-
148
- if ( in_array( $ipID, $this->getUntrustedProviders() ) ) {
149
- self::$bVisitorIsWhitelisted = false;
150
- }
151
- elseif ( in_array( $ipID, Services::ServiceProviders()->getWpSiteManagementProviders() ) ) {
152
- self::$bVisitorIsWhitelisted = true; // iControlWP / ManageWP
153
- }
154
- else {
155
- self::$bVisitorIsWhitelisted =
156
- ( new Shield\Modules\IPs\Lib\Ops\LookupIpOnList() )
157
- ->setDbHandler( $this->getCon()->getModule_IPs()->getDbHandler_IPs() )
158
- ->setIP( Services::IP()->getRequestIp() )
159
- ->setListTypeBypass()
160
- ->lookup() instanceof Shield\Databases\IPs\EntryVO;
161
- }
162
- }
163
- return self::$bVisitorIsWhitelisted;
164
  }
165
 
 
 
 
166
  public function isTrustedVerifiedBot() :bool {
167
- return $this->isVerifiedBot()
168
- && !in_array( Services::IP()->getIpDetector()->getIPIdentity(), $this->getUntrustedProviders() );
169
  }
170
 
 
 
 
171
  protected function getUntrustedProviders() :array {
172
  $untrustedProviders = apply_filters( 'shield/untrusted_service_providers', [] );
173
  return is_array( $untrustedProviders ) ? $untrustedProviders : [];
174
  }
175
 
 
 
 
176
  public function isVerifiedBot() :bool {
177
- if ( !isset( self::$bIsVerifiedBot ) ) {
178
- $ipID = Services::IP()->getIpDetector()->getIPIdentity();
179
- self::$bIsVerifiedBot = !Services::IP()->isLoopback() &&
180
- !in_array( $ipID, [
181
- Utilities\Net\IpID::UNKNOWN,
182
- Utilities\Net\IpID::THIS_SERVER,
183
- Utilities\Net\IpID::VISITOR,
184
- ] );
185
- }
186
- return self::$bIsVerifiedBot;
187
  }
188
 
189
  public function isXmlrpcBypass() :bool {
12
 
13
  /**
14
  * @var bool
15
+ * @deprecated 15.0
16
  */
17
  protected static $bIsVerifiedBot;
18
 
19
  /**
20
  * @var bool
21
+ * @deprecated 15.0
22
  */
23
  private static $bVisitorIsWhitelisted;
24
 
25
+ /**
26
+ * @deprecated 15.0
27
+ */
28
  public function getDbHandler_Sessions() :Shield\Databases\Session\Handler {
29
  return $this->getCon()
30
  ->getModule_Sessions()
31
  ->getDbHandler_Sessions();
32
  }
33
 
34
+ public function getSessionWP() :Shield\Modules\Sessions\Lib\SessionVO {
35
+ return $this->getCon()
36
+ ->getModule_Sessions()
37
+ ->getSessionCon()
38
+ ->getCurrentWP();
39
+ }
40
+
41
  /**
42
  * @return Shield\Databases\Session\EntryVO|null
43
+ * @deprecated 15.0
44
  */
45
+ public function getSession() :array {
46
  return $this->getCon()
47
  ->getModule_Sessions()
48
  ->getSessionCon()
95
  ->getPluginReportEmail();
96
  }
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  public function getIfSupport3rdParty() :bool {
99
  return $this->isPremium();
100
  }
101
 
102
  /**
 
103
  * @throws \Exception
104
  */
105
  protected function isReadyToExecute() :bool {
106
+ $req = $this->getCon()->this_req;
107
+ return ( !$req->request_bypasses_all_restrictions || $this->cfg->properties[ 'run_if_whitelisted' ] )
108
+ && ( !$req->is_trusted_bot || $this->cfg->properties[ 'run_if_verified_bot' ] )
109
+ && ( !$req->wp_is_wpcli || $this->cfg->properties[ 'run_if_wpcli' ] )
110
  && parent::isReadyToExecute();
111
  }
112
 
113
+ /**
114
+ * @deprecated 15.0
115
+ */
116
  public function isVisitorWhitelisted() :bool {
117
+ return $this->getCon()->this_req->is_ip_whitelisted;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  }
119
 
120
+ /**
121
+ * @deprecated 15.0
122
+ */
123
  public function isTrustedVerifiedBot() :bool {
124
+ return $this->getCon()->this_req->is_trusted_bot;
 
125
  }
126
 
127
+ /**
128
+ * @deprecated 15.0
129
+ */
130
  protected function getUntrustedProviders() :array {
131
  $untrustedProviders = apply_filters( 'shield/untrusted_service_providers', [] );
132
  return is_array( $untrustedProviders ) ? $untrustedProviders : [];
133
  }
134
 
135
+ /**
136
+ * @deprecated 15.0
137
+ */
138
  public function isVerifiedBot() :bool {
139
+ return $this->isTrustedVerifiedBot();
 
 
 
 
 
 
 
 
 
140
  }
141
 
142
  public function isXmlrpcBypass() :bool {
src/lib/src/Modules/BaseShield/Options.php CHANGED
@@ -8,19 +8,23 @@ use FernleafSystems\Wordpress\Services\Services;
8
  class Options extends Base\Options {
9
 
10
  public function getInstallationDays() :int {
11
- $nTimeInstalled = $this->getCon()
12
- ->getModule_Plugin()
13
- ->getInstallDate();
14
- if ( empty( $nTimeInstalled ) ) {
15
  return 0;
16
  }
17
- return (int)round( ( Services::Request()->ts() - $nTimeInstalled )/DAY_IN_SECONDS );
18
  }
19
 
20
  public function isPremium() :bool {
21
  return $this->getCon()->isPremiumActive();
22
  }
23
 
 
 
 
 
24
  public function isShowPromoAdminNotices() :bool {
25
  return $this->getCon()
26
  ->getModule_Plugin()
8
  class Options extends Base\Options {
9
 
10
  public function getInstallationDays() :int {
11
+ $installedAt = $this->getCon()
12
+ ->getModule_Plugin()
13
+ ->getInstallDate();
14
+ if ( empty( $installedAt ) ) {
15
  return 0;
16
  }
17
+ return (int)round( ( Services::Request()->ts() - $installedAt )/DAY_IN_SECONDS );
18
  }
19
 
20
  public function isPremium() :bool {
21
  return $this->getCon()->isPremiumActive();
22
  }
23
 
24
+ public function isShowPluginNotices() :bool {
25
+ return $this->isShowPromoAdminNotices();
26
+ }
27
+
28
  public function isShowPromoAdminNotices() :bool {
29
  return $this->getCon()
30
  ->getModule_Plugin()
src/lib/src/Modules/BaseShield/UI.php CHANGED
@@ -30,11 +30,21 @@ class UI extends Base\UI {
30
  'type_type' => 'Cache-Control',
31
  'content' => 'no-store, no-cache',
32
  ],
 
 
 
 
 
33
  [
34
  'type' => 'http-equiv',
35
  'type_type' => 'Expires',
36
  'content' => '0',
37
  ],
 
 
 
 
 
38
  ],
39
  'scripts' => []
40
  ],
@@ -42,9 +52,7 @@ class UI extends Base\UI {
42
  'sec_admin_login' => $con->getModule_SecAdmin()->getSecAdminLoginAjaxData(),
43
  ],
44
  'flags' => [
45
- 'has_session' => $con->getModule_Sessions()
46
- ->getSessionCon()
47
- ->hasSession(),
48
  'display_helpdesk_widget' => !$isWhitelabelled,
49
  'is_whitelabelled' => $isWhitelabelled
50
  ],
@@ -53,9 +61,6 @@ class UI extends Base\UI {
53
  $this->getCon()->getLabels()[ 'AuthorURI' ] : 'https://shsec.io/gc'
54
  ],
55
  'vars' => [
56
- 'helpscout_beacon_id' => $isPremium ?
57
- 'db2ff886-2329-4029-9452-44587df92c8c'
58
- : 'aded6929-af83-452d-993f-a60c03b46568'
59
  ],
60
  'classes' => [
61
  'top_container' => implode( ' ', array_filter( [
30
  'type_type' => 'Cache-Control',
31
  'content' => 'no-store, no-cache',
32
  ],
33
+ [
34
+ 'type' => 'http-equiv',
35
+ 'type_type' => 'Cache-Control',
36
+ 'content' => 'max-age=0',
37
+ ],
38
  [
39
  'type' => 'http-equiv',
40
  'type_type' => 'Expires',
41
  'content' => '0',
42
  ],
43
+ [
44
+ 'type' => 'name',
45
+ 'type_type' => 'robots',
46
+ 'content' => implode( ',', [ 'noindex', 'nofollow', 'noarchve', 'noimageindex' ] ),
47
+ ],
48
  ],
49
  'scripts' => []
50
  ],
52
  'sec_admin_login' => $con->getModule_SecAdmin()->getSecAdminLoginAjaxData(),
53
  ],
54
  'flags' => [
55
+ 'has_session' => $mod->getSessionWP()->valid,
 
 
56
  'display_helpdesk_widget' => !$isWhitelabelled,
57
  'is_whitelabelled' => $isWhitelabelled
58
  ],
61
  $this->getCon()->getLabels()[ 'AuthorURI' ] : 'https://shsec.io/gc'
62
  ],
63
  'vars' => [
 
 
 
64
  ],
65
  'classes' => [
66
  'top_container' => implode( ' ', array_filter( [
src/lib/src/Modules/CommentsFilter/AdminNotices.php CHANGED
@@ -26,7 +26,7 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
26
  }
27
 
28
  private function buildNotice_AkismetRunning( NoticeVO $notice ) {
29
- $oWpPlugins = Services::WpPlugins();
30
 
31
  $notice->render_data = [
32
  'notice_attributes' => [],
@@ -37,15 +37,11 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
37
  'click_to_deactivate' => __( 'Click to deactivate Akismet now.', 'wp-simple-firewall' ),
38
  ],
39
  'hrefs' => [
40
- 'deactivate' => $oWpPlugins->getUrl_Deactivate( $oWpPlugins->findPluginFileFromDirName( 'akismet' ) )
41
  ]
42
  ];
43
  }
44
 
45
- /**
46
- * @param Shield\Utilities\AdminNotices\NoticeVO $notice
47
- * @return bool
48
- */
49
  protected function isDisplayNeeded( Shield\Utilities\AdminNotices\NoticeVO $notice ) :bool {
50
  /** @var Options $opts */
51
  $opts = $this->getOptions();
@@ -53,11 +49,11 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
53
  switch ( $notice->id ) {
54
 
55
  case 'akismet-running':
56
- $oWpPlugins = Services::WpPlugins();
57
- $sPluginFile = $oWpPlugins->findPluginFileFromDirName( 'akismet' );
58
  $needed = $this->getMod()->isModuleEnabled()
59
- && !empty( $sPluginFile )
60
- && $oWpPlugins->isActive( $sPluginFile )
61
  && $opts->isEnabledHumanCheck();
62
  break;
63
 
26
  }
27
 
28
  private function buildNotice_AkismetRunning( NoticeVO $notice ) {
29
+ $WPP = Services::WpPlugins();
30
 
31
  $notice->render_data = [
32
  'notice_attributes' => [],
37
  'click_to_deactivate' => __( 'Click to deactivate Akismet now.', 'wp-simple-firewall' ),
38
  ],
39
  'hrefs' => [
40
+ 'deactivate' => $WPP->getUrl_Deactivate( $WPP->findPluginFileFromDirName( 'akismet' ) )
41
  ]
42
  ];
43
  }
44
 
 
 
 
 
45
  protected function isDisplayNeeded( Shield\Utilities\AdminNotices\NoticeVO $notice ) :bool {
46
  /** @var Options $opts */
47
  $opts = $this->getOptions();
49
  switch ( $notice->id ) {
50
 
51
  case 'akismet-running':
52
+ $WPP = Services::WpPlugins();
53
+ $file = $WPP->findPluginFileFromDirName( 'akismet' );
54
  $needed = $this->getMod()->isModuleEnabled()
55
+ && !empty( $file )
56
+ && $WPP->isActive( $file )
57
  && $opts->isEnabledHumanCheck();
58
  break;
59
 
src/lib/src/Modules/CommentsFilter/AjaxHandler.php DELETED
@@ -1,25 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Services\Services;
7
-
8
- class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
9
-
10
- protected function getAjaxActionCallbackMap( bool $isAuth ) :array {
11
- return array_merge( parent::getAjaxActionCallbackMap( $isAuth ), [
12
- 'comment_token'.Services::IP()->getRequestIp() => [ $this, 'ajaxExec_GenCommentToken' ],
13
- ] );
14
- }
15
-
16
- public function ajaxExec_GenCommentToken() :array {
17
- $req = Services::Request();
18
- return [
19
- 'success' => true,
20
- 'token' => ( new Shield\Modules\CommentsFilter\Token\Create() )
21
- ->setMod( $this->getMod() )
22
- ->run( $req->post( 'ts' ), $req->post( 'post_id' ) ),
23
- ];
24
- }
25
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/CommentsFilter/Forms/Gasp.php DELETED
@@ -1,117 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter\Forms;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Controller\Assets\Enqueue;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Common\ExecOnceModConsumer;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
8
- use FernleafSystems\Wordpress\Services\Services;
9
-
10
- class Gasp extends ExecOnceModConsumer {
11
-
12
- /**
13
- * The unique comment token assigned to this page
14
- * @var string
15
- */
16
- private $formID;
17
-
18
- /**
19
- * @var bool
20
- */
21
- private $formItemsPrinted = false;
22
-
23
- protected function canRun() :bool {
24
- /** @var CommentsFilter\Options $opts */
25
- $opts = $this->getOptions();
26
- return !Services::Request()->isPost() && $opts->isEnabledGaspCheck() && !Services::WpUsers()->isUserLoggedIn();
27
- }
28
-
29
- protected function run() {
30
- add_action( 'wp', [ $this, 'onWP' ] );
31
- add_action( 'wp_footer', [ $this, 'maybeDequeueScript' ] );
32
- }
33
-
34
- public function onWP() {
35
- $this->enqueueJS();
36
- add_action( 'comment_form', [ $this, 'printGaspFormItems' ], 1 );
37
- }
38
-
39
- protected function enqueueJS() {
40
- add_filter( 'shield/custom_enqueues', function ( array $enqueues ) {
41
- $enqueues[ Enqueue::JS ][] = 'shield/comments';
42
-
43
- add_filter( 'shield/custom_localisations', function ( array $localz ) {
44
- /** @var CommentsFilter\ModCon $mod */
45
- $mod = $this->getMod();
46
- /** @var CommentsFilter\Options $opts */
47
- $opts = $this->getOptions();
48
-
49
- $ts = Services::Request()->ts();
50
- $nonce = $mod->getAjaxActionData( 'comment_token'.Services::IP()->getRequestIp() );
51
- $nonce[ 'ts' ] = $ts;
52
- $nonce[ 'post_id' ] = Services::WpPost()->getCurrentPostId();
53
-
54
- $localz[] = [
55
- 'shield/comments',
56
- 'shield_comments',
57
- [
58
- 'ajax' => [
59
- 'comment_token' => $nonce,
60
- ],
61
- 'vars' => [
62
- 'cbname' => 'cb_nombre'.rand(),
63
- 'botts' => $ts,
64
- 'token' => 'not created',
65
- 'uniq' => $this->getUniqueFormId(),
66
- 'cooldown' => $opts->getTokenCooldown(),
67
- 'expires' => $opts->getTokenExpireInterval(),
68
- ],
69
- 'strings' => [
70
- 'label' => $mod->getTextOpt( 'custom_message_checkbox' ),
71
- 'alert' => $mod->getTextOpt( 'custom_message_alert' ),
72
- 'comment_reload' => $mod->getTextOpt( 'custom_message_comment_reload' ),
73
- 'js_comment_wait' => $mod->getTextOpt( 'custom_message_comment_wait' ),
74
- ],
75
- 'flags' => [
76
- 'gasp' => true,
77
- 'recap' => $opts->isEnabledCaptcha() && $mod->getCaptchaCfg()->ready,
78
- ]
79
- ]
80
- ];
81
- return $localz;
82
- } );
83
-
84
- return $enqueues;
85
- } );
86
- }
87
-
88
- /**
89
- * If the comment form component hasn't been printed, there's no comment form to protect.
90
- */
91
- public function maybeDequeueScript() {
92
- if ( empty( $this->formItemsPrinted ) ) {
93
- wp_dequeue_script( $this->getCon()->prefix( 'shield/comments' ) );
94
- }
95
- }
96
-
97
- public function printGaspFormItems() {
98
- $this->formItemsPrinted = true;
99
- echo $this->getMod()
100
- ->renderTemplate(
101
- 'snippets/comment_form_botbox.twig',
102
- [ 'uniq' => $this->getUniqueFormId() ],
103
- true
104
- );
105
- }
106
-
107
- private function getUniqueFormId() :string {
108
- if ( !isset( $this->formID ) ) {
109
- $DP = Services::Data();
110
- $id = $DP->generateRandomLetter().$DP->generateRandomString( rand( 7, 23 ), 7 );
111
- $this->formID = preg_replace(
112
- '#[^a-zA-Z0-9]#', '',
113
- apply_filters( 'icwp_shield_cf_gasp_uniqid', $id ) );
114
- }
115
- return $this->formID;
116
- }
117
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/CommentsFilter/Forms/GoogleRecaptcha.php DELETED
@@ -1,40 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter\Forms;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Common\ExecOnceModConsumer;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
7
- use FernleafSystems\Wordpress\Services\Services;
8
-
9
- class GoogleRecaptcha extends ExecOnceModConsumer {
10
-
11
- protected function canRun() :bool {
12
- /** @var CommentsFilter\ModCon $mod */
13
- $mod = $this->getMod();
14
- /** @var CommentsFilter\Options $opts */
15
- $opts = $this->getOptions();
16
- return $opts->isEnabledCaptcha() && $mod->getCaptchaCfg()->ready;
17
- }
18
-
19
- protected function run() {
20
- add_action( 'wp', [ $this, 'setup' ] );
21
- }
22
-
23
- public function setup() {
24
- if ( Services::WpComments()->isCommentsOpen() ) {
25
- $this->getCon()
26
- ->getModule_Plugin()
27
- ->getCaptchaEnqueue()
28
- ->setMod( $this->getMod() )
29
- ->setToEnqueue();
30
- add_action( 'comment_form_after_fields', [ $this, 'printGoogleRecaptchaCheck' ] );
31
- }
32
- }
33
-
34
- public function printGoogleRecaptchaCheck() {
35
- echo $this->getCon()
36
- ->getModule_Plugin()
37
- ->getCaptchaEnqueue()
38
- ->getCaptchaHtml();
39
- }
40
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/CommentsFilter/Insights/OverviewCards.php DELETED
@@ -1,48 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter\Insights;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
7
-
8
- class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
9
-
10
- protected function buildModCards() :array {
11
- /** @var CommentsFilter\ModCon $mod */
12
- $mod = $this->getMod();
13
- /** @var CommentsFilter\Options $opts */
14
- $opts = $this->getOptions();
15
-
16
- $cards = [];
17
-
18
- if ( $mod->isModOptEnabled() ) {
19
- $botSpamOn = $opts->isEnabledAntiBot() || $opts->isEnabledGaspCheck() || $mod->isEnabledCaptcha();
20
- $cards[ 'bot' ] = [
21
- 'name' => __( 'Bot SPAM', 'wp-simple-firewall' ),
22
- 'state' => $botSpamOn ? 1 : -1,
23
- 'summary' => $botSpamOn ?
24
- __( 'Bot SPAM comments are blocked', 'wp-simple-firewall' )
25
- : __( 'There is no protection against Bot SPAM comments', 'wp-simple-firewall' ),
26
- 'href' => $mod->getUrl_DirectLinkToSection( 'section_bot_comment_spam_protection_filter' ),
27
- ];
28
- $cards[ 'human' ] = [
29
- 'name' => __( 'Human SPAM', 'wp-simple-firewall' ),
30
- 'state' => $opts->isEnabledHumanCheck() ? 1 : -1,
31
- 'summary' => $opts->isEnabledHumanCheck() ?
32
- __( 'Comments posted by humans are checked for SPAM', 'wp-simple-firewall' )
33
- : __( "Comments posted by humans aren't checked for SPAM", 'wp-simple-firewall' ),
34
- 'href' => $mod->getUrl_DirectLinkToSection( 'section_human_spam_filter' ),
35
- ];
36
- }
37
-
38
- return $cards;
39
- }
40
-
41
- protected function getSectionTitle() :string {
42
- return __( 'SPAM Blocking', 'wp-simple-firewall' );
43
- }
44
-
45
- protected function getSectionSubTitle() :string {
46
- return __( 'Block Bot & Human Comment SPAM', 'wp-simple-firewall' );
47
- }
48
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/CommentsFilter/ModCon.php CHANGED
@@ -2,62 +2,10 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\Captcha\CaptchaConfigVO;
8
 
9
  class ModCon extends BaseShield\ModCon {
10
 
11
- public function getCaptchaCfg() :CaptchaConfigVO {
12
- $cfg = parent::getCaptchaCfg();
13
- $sStyle = $this->getOptions()->getOpt( 'google_recaptcha_style_comments' );
14
- if ( $sStyle !== 'default' && $this->isPremium() ) {
15
- $cfg->theme = $sStyle;
16
- $cfg->invisible = $cfg->theme == 'invisible';
17
- }
18
- return $cfg;
19
- }
20
-
21
- public function ensureCorrectCaptchaConfig() {
22
- /** @var Options $opts */
23
- $opts = $this->getOptions();
24
-
25
- $style = $opts->getOpt( 'google_recaptcha_style_comments' );
26
- if ( $this->isPremium() ) {
27
- $cfg = $this->getCaptchaCfg();
28
- if ( $cfg->provider == $cfg::PROV_GOOGLE_RECAP2 ) {
29
- if ( !$cfg->invisible && $style == 'invisible' ) {
30
- $opts->setOpt( 'google_recaptcha_style_comments', 'default' );
31
- }
32
- }
33
- }
34
- elseif ( !in_array( $style, [ 'disabled', 'default' ] ) ) {
35
- $opts->setOpt( 'google_recaptcha_style_comments', 'default' );
36
- }
37
- }
38
-
39
- public function getTextOptDefault( string $key ) :string {
40
-
41
- switch ( $key ) {
42
- case 'custom_message_checkbox':
43
- $text = __( "I'm not a spammer.", 'wp-simple-firewall' );
44
- break;
45
- case 'custom_message_alert':
46
- $text = __( "Please check the box to confirm you're not a spammer.", 'wp-simple-firewall' );
47
- break;
48
- case 'custom_message_comment_wait':
49
- $text = __( "Please wait %s seconds before posting your comment.", 'wp-simple-firewall' );
50
- break;
51
- case 'custom_message_comment_reload':
52
- $text = __( "Please reload this page to post a comment.", 'wp-simple-firewall' );
53
- break;
54
- default:
55
- $text = parent::getTextOptDefault( $key );
56
- break;
57
- }
58
- return $text;
59
- }
60
-
61
  protected function preProcessOptions() {
62
  /** @var Options $opts */
63
  $opts = $this->getOptions();
@@ -71,23 +19,29 @@ class ModCon extends BaseShield\ModCon {
71
  $opts->getTrustedRoles()
72
  ) ) )
73
  );
 
74
 
75
- $this->ensureCorrectCaptchaConfig();
76
-
77
- if ( $opts->isEnabledAntiBot() ) {
78
- $opts->setOpt( 'google_recaptcha_style_comments', 'disabled' );
79
- $opts->setOpt( 'enable_comments_gasp_protection', 'N' );
80
- }
81
  }
82
 
 
 
 
83
  public function isEnabledCaptcha() :bool {
84
- /** @var Options $opts */
85
- $opts = $this->getOptions();
86
- return $this->isModOptEnabled() && $opts->isEnabledCaptcha()
87
- && $this->getCaptchaCfg()->ready;
88
  }
89
 
90
- public function getSpamBlacklistFile() :string {
91
- return $this->getCon()->paths->forCacheItem( 'spamblacklist.txt' );
 
 
 
 
 
 
 
 
 
92
  }
93
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
 
6
 
7
  class ModCon extends BaseShield\ModCon {
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  protected function preProcessOptions() {
10
  /** @var Options $opts */
11
  $opts = $this->getOptions();
19
  $opts->getTrustedRoles()
20
  ) ) )
21
  );
22
+ }
23
 
24
+ public function getSpamBlacklistFile() :string {
25
+ return $this->getCon()->paths->forCacheItem( 'spamblacklist.txt' );
 
 
 
 
26
  }
27
 
28
+ /**
29
+ * @deprecated 15.0
30
+ */
31
  public function isEnabledCaptcha() :bool {
32
+ return false;
 
 
 
33
  }
34
 
35
+ /**
36
+ * @deprecated 15.0
37
+ */
38
+ public function ensureCorrectCaptchaConfig() {
39
+ }
40
+
41
+ /**
42
+ * @deprecated 15.0
43
+ */
44
+ public function getCaptchaCfg() :\FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\Captcha\CaptchaConfigVO {
45
+ return parent::getCaptchaCfg();
46
  }
47
  }
src/lib/src/Modules/CommentsFilter/Options.php CHANGED
@@ -11,64 +11,29 @@ class Options extends BaseShield\Options {
11
  }
12
 
13
  public function getHumanSpamFilterItems() :array {
14
- $aDefault = $this->getOptDefault( 'human_spam_items' );
15
- $aItems = apply_filters(
16
  $this->getCon()->prefix( 'human_spam_items' ),
17
  $this->getOpt( 'human_spam_items', [] )
18
  );
19
- return is_array( $aItems ) ? array_intersect( $aDefault, $aItems ) : $aDefault;
20
- }
21
-
22
- public function getTokenCooldown() :int {
23
- if ( (int)$this->getOpt( 'comments_cooldown', 10 ) < 1 ) {
24
- $this->resetOptToDefault( 'comments_cooldown' );
25
- }
26
- return (int)max( 0,
27
- apply_filters(
28
- $this->getCon()->prefix( 'comments_cooldown' ),
29
- $this->getOpt( 'comments_cooldown', 10 )
30
- )
31
- );
32
- }
33
-
34
- public function getTokenExpireInterval() :int {
35
- return (int)max( 0,
36
- apply_filters(
37
- $this->getCon()->prefix( 'comments_expire' ),
38
- $this->getDef( 'comments_expire' )
39
- )
40
- );
41
  }
42
 
43
  /**
44
  * @return string[]
45
  */
46
- public function getTrustedRoles() {
47
- $aRoles = [];
48
  if ( $this->isPremium() ) {
49
- $aRoles = $this->getOpt( 'trusted_user_roles', [] );
50
  }
51
- return is_array( $aRoles ) ? $aRoles : [];
52
- }
53
-
54
- public function isEnabledGaspCheck() :bool {
55
- return $this->isOpt( 'enable_comments_gasp_protection', 'Y' )
56
- && ( $this->getTokenExpireInterval() > $this->getTokenCooldown() )
57
- && !$this->isEnabledAntiBot();
58
  }
59
 
60
  public function isEnabledAntiBot() :bool {
61
- if ( $this->isOpt( 'enable_antibot_check', 'Y' ) ) {
62
- /** @deprecated 14.1 - remove the older option */
63
- $this->setOpt( 'enable_antibot_comments', 'Y' );
64
- }
65
  return $this->isOpt( 'enable_antibot_comments', 'Y' );
66
  }
67
 
68
- public function isEnabledCaptcha() :bool {
69
- return !$this->isOpt( 'google_recaptcha_style_comments', 'disabled' ) && !$this->isEnabledAntiBot();
70
- }
71
-
72
  public function isEnabledHumanCheck() :bool {
73
  return $this->isOpt( 'enable_comments_human_spam_filter', 'Y' )
74
  && count( $this->getHumanSpamFilterItems() ) > 0;
@@ -77,4 +42,32 @@ class Options extends BaseShield\Options {
77
  public function setEnabledAntiBot( bool $enabled = true ) {
78
  $this->setOpt( 'enable_antibot_comments', $enabled ? 'Y' : 'N' );
79
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  }
11
  }
12
 
13
  public function getHumanSpamFilterItems() :array {
14
+ $default = $this->getOptDefault( 'human_spam_items' );
15
+ $items = apply_filters(
16
  $this->getCon()->prefix( 'human_spam_items' ),
17
  $this->getOpt( 'human_spam_items', [] )
18
  );
19
+ return is_array( $items ) ? array_intersect( $default, $items ) : $default;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  }
21
 
22
  /**
23
  * @return string[]
24
  */
25
+ public function getTrustedRoles() :array {
26
+ $roles = [];
27
  if ( $this->isPremium() ) {
28
+ $roles = $this->getOpt( 'trusted_user_roles', [] );
29
  }
30
+ return is_array( $roles ) ? $roles : [];
 
 
 
 
 
 
31
  }
32
 
33
  public function isEnabledAntiBot() :bool {
 
 
 
 
34
  return $this->isOpt( 'enable_antibot_comments', 'Y' );
35
  }
36
 
 
 
 
 
37
  public function isEnabledHumanCheck() :bool {
38
  return $this->isOpt( 'enable_comments_human_spam_filter', 'Y' )
39
  && count( $this->getHumanSpamFilterItems() ) > 0;
42
  public function setEnabledAntiBot( bool $enabled = true ) {
43
  $this->setOpt( 'enable_antibot_comments', $enabled ? 'Y' : 'N' );
44
  }
45
+
46
+ /**
47
+ * @deprecated 15.0
48
+ */
49
+ public function isEnabledGaspCheck() :bool {
50
+ return false;
51
+ }
52
+
53
+ /**
54
+ * @deprecated 15.0
55
+ */
56
+ public function isEnabledCaptcha() :bool {
57
+ return false;
58
+ }
59
+
60
+ /**
61
+ * @deprecated 15.0
62
+ */
63
+ public function getTokenExpireInterval() :int {
64
+ return 0;
65
+ }
66
+
67
+ /**
68
+ * @deprecated 15.0
69
+ */
70
+ public function getTokenCooldown() :int {
71
+ return 0;
72
+ }
73
  }
src/lib/src/Modules/CommentsFilter/Processor.php CHANGED
@@ -12,20 +12,17 @@ class Processor extends BaseShield\Processor {
12
  $opts = $this->getOptions();
13
  $WPU = Services::WpUsers();
14
 
15
- $loadCommentFilter = !$WPU->isUserLoggedIn() ||
16
- !( new Scan\IsEmailTrusted() )->trusted(
17
- $WPU->getCurrentWpUser()->user_email,
18
- $opts->getApprovedMinimum(),
19
- $opts->getTrustedRoles()
20
- );
 
21
 
22
- ( new Scan\CommentAdditiveCleaner() )
23
- ->setMod( $this->getMod() )
24
- ->execute();
25
 
26
- if ( $loadCommentFilter ) {
27
-
28
- ( new Forms\GoogleRecaptcha() )
29
  ->setMod( $this->getMod() )
30
  ->execute();
31
 
@@ -35,19 +32,6 @@ class Processor extends BaseShield\Processor {
35
  ->execute();
36
  add_filter( 'comment_notification_recipients', [ $this, 'clearCommentNotificationEmail' ], 100, 1 );
37
  }
38
- else {
39
- ( new Forms\Gasp() )
40
- ->setMod( $this->getMod() )
41
- ->execute();
42
- }
43
- }
44
- }
45
-
46
- public function runHourlyCron() {
47
- /** @var Options $opts */
48
- $opts = $this->getOptions();
49
- if ( $opts->isEnabledGaspCheck() && function_exists( 'delete_expired_transients' ) ) {
50
- delete_expired_transients(); // cleanup unused comment tokens
51
  }
52
  }
53
 
12
  $opts = $this->getOptions();
13
  $WPU = Services::WpUsers();
14
 
15
+ $commentsFilterBypass = $this->getCon()->this_req->request_bypasses_all_restrictions ||
16
+ ( $WPU->isUserLoggedIn() &&
17
+ ( new Scan\IsEmailTrusted() )->trusted(
18
+ $WPU->getCurrentWpUser()->user_email,
19
+ $opts->getApprovedMinimum(),
20
+ $opts->getTrustedRoles()
21
+ ) );
22
 
23
+ if ( !$commentsFilterBypass ) {
 
 
24
 
25
+ ( new Scan\CommentAdditiveCleaner() )
 
 
26
  ->setMod( $this->getMod() )
27
  ->execute();
28
 
32
  ->execute();
33
  add_filter( 'comment_notification_recipients', [ $this, 'clearCommentNotificationEmail' ], 100, 1 );
34
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  }
36
  }
37
 
src/lib/src/Modules/CommentsFilter/Scan/AntiBot.php DELETED
@@ -1,24 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter\Scan;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
-
7
- class AntiBot {
8
-
9
- use ModConsumer;
10
-
11
- /**
12
- * @throws \Exception
13
- */
14
- public function scan() :bool {
15
- $isBot = $this->getCon()
16
- ->getModule_IPs()
17
- ->getBotSignalsController()
18
- ->isBot();
19
- if ( $isBot ) {
20
- throw new \Exception( __( 'Failed AntiBot Verification', 'wp-simple-firewall' ) );
21
- }
22
- return true;
23
- }
24
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/CommentsFilter/Scan/Bot.php DELETED
@@ -1,78 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter\Scan;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
- use FernleafSystems\Wordpress\Services\Services;
8
-
9
- class Bot {
10
-
11
- use ModConsumer;
12
-
13
- /**
14
- * @param int $nPostId
15
- * @return true|\WP_Error
16
- */
17
- public function scan( $nPostId ) {
18
- /** @var CommentsFilter\Options $opts */
19
- $opts = $this->getOptions();
20
- $req = Services::Request();
21
-
22
- $sFieldCheckboxName = $req->post( 'cb_nombre' );
23
- $sFieldHoney = $req->post( 'sugar_sweet_email' );
24
- $nCommentTs = (int)$req->post( 'botts' );
25
- $sCommentToken = $req->post( 'comment_token' );
26
-
27
- $cooldown = $opts->getTokenCooldown();
28
- $expire = $opts->getTokenExpireInterval();
29
-
30
- $key = null;
31
- $explanation = null;
32
- if ( !$sFieldCheckboxName || !$req->post( $sFieldCheckboxName ) ) {
33
- $explanation = sprintf( __( 'Failed Bot Test (%s)', 'wp-simple-firewall' ), __( 'checkbox', 'wp-simple-firewall' ) );
34
- $key = 'checkbox';
35
- }
36
- // honeypot check
37
- elseif ( !empty( $sFieldHoney ) ) {
38
- $explanation = sprintf( __( 'Failed Bot Test (%s)', 'wp-simple-firewall' ), __( 'honeypot', 'wp-simple-firewall' ) );
39
- $key = 'honeypot';
40
- }
41
- elseif ( $cooldown > 0 || $expire > 0 ) {
42
-
43
- if ( $cooldown > 0 && $req->ts() < ( $nCommentTs + $cooldown ) ) {
44
- $explanation = sprintf( __( 'Failed Bot Test (%s)', 'wp-simple-firewall' ), __( 'cooldown', 'wp-simple-firewall' ) );
45
- $key = 'cooldown';
46
- }
47
- elseif ( $expire > 0 && $req->ts() > ( $nCommentTs + $expire ) ) {
48
- $explanation = sprintf( __( 'Failed Bot Test (%s)', 'wp-simple-firewall' ), __( 'expired', 'wp-simple-firewall' ) );
49
- $key = 'expired';
50
- }
51
- elseif ( !$this->checkTokenHash( $sCommentToken, $nCommentTs, $nPostId ) ) {
52
- $explanation = sprintf( __( 'Failed Bot Test (%s)', 'wp-simple-firewall' ), __( 'token', 'wp-simple-firewall' ) );
53
- $key = 'token';
54
- }
55
- }
56
-
57
- return empty( $key ) ? true : new \WP_Error( 'bot', $explanation, [ 'type' => $key ] );
58
- }
59
-
60
- /**
61
- * @param $sToken
62
- * @param $nTs
63
- * @param $nPostId
64
- * @return bool
65
- */
66
- private function checkTokenHash( $sToken, $nTs, $nPostId ) {
67
- $WP = Services::WpGeneral();
68
- $key = $this->getCon()->prefix(
69
- 'comtok-'.md5( sprintf( '%s-%s-%s', $nPostId, $nTs, Services::IP()->getRequestIp() ) )
70
- );
71
- $sStoredToken = Services::WpGeneral()->getTransient( $key );
72
- $WP->deleteTransient( $key ); // single use hashes & clean as we go
73
- return hash_equals(
74
- (string)$sStoredToken,
75
- $sToken
76
- );
77
- }
78
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/CommentsFilter/Scan/Human.php CHANGED
@@ -15,10 +15,10 @@ class Human {
15
  * Does the same as the WordPress blacklist filter, but more intelligently and with a nod towards much higher
16
  * performance. It also uses defined options for which fields are checked for SPAM instead of just checking
17
  * EVERYTHING!
18
- * @param array $aCommData
19
  * @return \WP_Error|true
20
  */
21
- public function scan( $aCommData ) {
22
  /** @var CommentsFilter\Options $opts */
23
  $opts = $this->getOptions();
24
 
@@ -26,10 +26,10 @@ class Human {
26
 
27
  $items = array_intersect_key(
28
  [
29
- 'comment_content' => $aCommData[ 'comment_content' ],
30
- 'url' => $aCommData[ 'comment_author_url' ],
31
- 'author_name' => $aCommData[ 'comment_author' ],
32
- 'author_email' => $aCommData[ 'comment_author_email' ],
33
  'ip_address' => Services::IP()->getRequestIp(),
34
  'user_agent' => substr( Services::Request()->getUserAgent(), 0, 254 )
35
  ],
15
  * Does the same as the WordPress blacklist filter, but more intelligently and with a nod towards much higher
16
  * performance. It also uses defined options for which fields are checked for SPAM instead of just checking
17
  * EVERYTHING!
18
+ * @param array $commData
19
  * @return \WP_Error|true
20
  */
21
+ public function scan( $commData ) {
22
  /** @var CommentsFilter\Options $opts */
23
  $opts = $this->getOptions();
24
 
26
 
27
  $items = array_intersect_key(
28
  [
29
+ 'comment_content' => $commData[ 'comment_content' ],
30
+ 'url' => $commData[ 'comment_author_url' ],
31
+ 'author_name' => $commData[ 'comment_author' ],
32
+ 'author_email' => $commData[ 'comment_author_email' ],
33
  'ip_address' => Services::IP()->getRequestIp(),
34
  'user_agent' => substr( Services::Request()->getUserAgent(), 0, 254 )
35
  ],
src/lib/src/Modules/CommentsFilter/Scan/Scanner.php CHANGED
@@ -4,7 +4,6 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter\Scan;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Common\ExecOnceModConsumer;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Utilities;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
10
  class Scanner extends ExecOnceModConsumer {
@@ -81,113 +80,89 @@ class Scanner extends ExecOnceModConsumer {
81
  }
82
 
83
  /**
84
- * @param array $aCommData
85
  * @return array
86
  */
87
- public function checkComment( $aCommData ) {
 
88
  $opts = $this->getOptions();
89
 
90
  if ( Services::WpComments()->isCommentSubmission()
91
- && $this->getIfDoCommentsCheck( $aCommData[ 'comment_post_ID' ], $aCommData[ 'comment_author_email' ] ) ) {
 
 
 
92
 
93
- $mResult = $this->runScans( $aCommData );
94
- if ( is_wp_error( $mResult ) ) {
 
 
 
 
 
 
 
 
95
 
96
- $this->getCon()
97
- ->fireEvent(
98
- 'spam_block_'.$mResult->get_error_code(),
99
- [ 'audit_params' => $mResult->get_error_data() ]
100
- );
101
  $this->getCon()->fireEvent( 'comment_spam_block' );
102
 
103
- if ( $mResult->get_error_code() == 'human' ) {
 
 
 
 
104
  $status = $opts->getOpt( 'comments_default_action_human_spam' );
105
  }
106
  else {
107
- $status = $opts->getOpt( 'comments_default_action_spam_bot' );
108
  }
109
 
110
- if ( $status == 'reject' ) {
111
- Services::Response()->redirectToHome();
112
- }
 
113
 
114
- $this->spamStatus = $status;
115
- $this->spamReason = $mResult->get_error_message();
 
116
  }
117
  }
118
 
119
- return $aCommData;
120
  }
121
 
122
- /**
123
- * @param array $aCommData
124
- * @return true|\WP_Error|null
125
- */
126
- private function runScans( $aCommData ) {
127
- /** @var CommentsFilter\ModCon $mod */
128
- $mod = $this->getMod();
129
- /** @var CommentsFilter\Options $opts */
130
- $opts = $this->getOptions();
131
-
132
- $mResult = true;
133
 
134
- if ( $opts->isEnabledAntiBot() ) {
135
- try {
136
- ( new AntiBot() )
137
- ->setMod( $this->getMod() )
138
- ->scan();
139
- }
140
- catch ( \Exception $e ) {
141
- $mResult = new \WP_Error( 'antibot', $e->getMessage() );
142
- }
143
- }
144
- else {
145
-
146
- if ( $opts->isEnabledGaspCheck() ) {
147
- $mResult = ( new Bot() )
148
- ->setMod( $this->getMod() )
149
- ->scan( $aCommData[ 'comment_post_ID' ] );
150
- }
151
-
152
- if ( !is_wp_error( $mResult ) && $opts->isEnabledCaptcha() && $mod->getCaptchaCfg()->ready ) {
153
- try {
154
- if ( $mod->getCaptchaCfg()->provider === 'hcaptcha' ) {
155
- ( new Utilities\HCaptcha\TestRequest() )
156
- ->setMod( $this->getMod() )
157
- ->test();
158
- }
159
- else {
160
- ( new Utilities\ReCaptcha\TestRequest() )
161
- ->setMod( $this->getMod() )
162
- ->test();
163
- }
164
- }
165
- catch ( \Exception $e ) {
166
- $mResult = new \WP_Error( 'recaptcha', $e->getMessage(), [] );
167
- }
168
- }
169
  }
170
 
171
- if ( !is_wp_error( $mResult ) && $opts->isEnabledHumanCheck() ) {
172
- $mResult = ( new Human() )
173
- ->setMod( $this->getMod() )
174
- ->scan( $aCommData );
 
 
175
  }
176
 
177
- return $mResult;
178
  }
179
 
180
  /**
181
- * @param int $nPostId
182
- * @param string $sCommentEmail
183
- * @return bool
184
  */
185
- public function getIfDoCommentsCheck( $nPostId, $sCommentEmail ) {
186
  /** @var CommentsFilter\Options $opts */
187
  $opts = $this->getOptions();
188
- $post = Services::WpPost()->getById( $nPostId );
189
  return $post instanceof \WP_Post
190
  && Services::WpComments()->isCommentsOpen( $post )
191
- && !( new IsEmailTrusted() )->trusted( $sCommentEmail, $opts->getApprovedMinimum(), $opts->getTrustedRoles() );
192
  }
193
  }
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Common\ExecOnceModConsumer;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
 
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class Scanner extends ExecOnceModConsumer {
80
  }
81
 
82
  /**
83
+ * @param array $commData
84
  * @return array
85
  */
86
+ public function checkComment( $commData ) {
87
+ /** @var CommentsFilter\Options $opts */
88
  $opts = $this->getOptions();
89
 
90
  if ( Services::WpComments()->isCommentSubmission()
91
+ && is_array( $commData )
92
+ && $this->getIfDoCommentsCheck( $commData[ 'comment_post_ID' ], $commData[ 'comment_author_email' ] ) ) {
93
+
94
+ $spamErrors = $this->runScans( $commData );
95
 
96
+ $errorCodes = $spamErrors->get_error_codes();
97
+ if ( count( $errorCodes ) > 0 ) {
98
+
99
+ foreach ( $errorCodes as $errorCode ) {
100
+ $this->getCon()
101
+ ->fireEvent(
102
+ 'spam_block_'.$errorCode,
103
+ [ 'audit_params' => $spamErrors->get_error_data( $errorCode ) ]
104
+ );
105
+ }
106
 
 
 
 
 
 
107
  $this->getCon()->fireEvent( 'comment_spam_block' );
108
 
109
+ // if we're configured to actually block...
110
+ if ( $opts->isEnabledAntiBot() && in_array( 'antibot', $errorCodes ) ) {
111
+ $status = $opts->getOpt( 'comments_default_action_spam_bot' );
112
+ }
113
+ elseif ( $opts->isEnabledHumanCheck() && in_array( 'human', $errorCodes ) ) {
114
  $status = $opts->getOpt( 'comments_default_action_human_spam' );
115
  }
116
  else {
117
+ $status = null;
118
  }
119
 
120
+ if ( !is_null( $status ) ) {
121
+ if ( $status == 'reject' ) {
122
+ Services::Response()->redirectToHome();
123
+ }
124
 
125
+ $this->spamStatus = $status;
126
+ $this->spamReason = $spamErrors->get_error_message();
127
+ }
128
  }
129
  }
130
 
131
+ return $commData;
132
  }
133
 
134
+ private function runScans( array $commData ) :\WP_Error {
135
+ $errors = new \WP_Error();
 
 
 
 
 
 
 
 
 
136
 
137
+ $isBot = $this->getCon()
138
+ ->getModule_IPs()
139
+ ->getBotSignalsController()
140
+ ->isBot();
141
+ if ( $isBot ) {
142
+ $errors->add( 'antibot', __( 'Failed AntiBot Verification', 'wp-simple-firewall' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  }
144
 
145
+ $humanError = ( new Human() )
146
+ ->setMod( $this->getMod() )
147
+ ->scan( $commData );
148
+ if ( is_wp_error( $humanError ) ) {
149
+ $code = $humanError->get_error_code();
150
+ $errors->add( $code, $humanError->get_error_message( $code ), $humanError->get_error_data( $code ) );
151
  }
152
 
153
+ return $errors;
154
  }
155
 
156
  /**
157
+ * @param int $postID
158
+ * @param string $commentEmail
 
159
  */
160
+ public function getIfDoCommentsCheck( $postID, $commentEmail ) :bool {
161
  /** @var CommentsFilter\Options $opts */
162
  $opts = $this->getOptions();
163
+ $post = Services::WpPost()->getById( $postID );
164
  return $post instanceof \WP_Post
165
  && Services::WpComments()->isCommentsOpen( $post )
166
+ && !( new IsEmailTrusted() )->trusted( $commentEmail, $opts->getApprovedMinimum(), $opts->getTrustedRoles() );
167
  }
168
  }
src/lib/src/Modules/CommentsFilter/Strings.php CHANGED
@@ -60,39 +60,39 @@ class Strings extends Base\Strings {
60
  switch ( $section ) {
61
 
62
  case 'section_enable_plugin_feature_spam_comments_protection_filter' :
63
- $sTitleShort = sprintf( '%s/%s', __( 'On', 'wp-simple-firewall' ), __( 'Off', 'wp-simple-firewall' ) );
64
- $sTitle = sprintf( __( 'Enable Module: %s', 'wp-simple-firewall' ), __( 'Comments SPAM Protection', 'wp-simple-firewall' ) );
65
- $aSummary = [
66
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'The Comments Filter can block 100% of automated spam bots and also offer the option to analyse human-generated spam.', 'wp-simple-firewall' ) ),
67
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), sprintf( __( 'Keep the %s feature turned on.', 'wp-simple-firewall' ), __( 'Comments Filter', 'wp-simple-firewall' ) ) )
68
  ];
69
  break;
70
 
71
  case 'section_bot_comment_spam_common' :
72
- $sTitleShort = __( 'Common Settings', 'wp-simple-firewall' );
73
- $sTitle = __( 'Common Settings For All SPAM Scanning', 'wp-simple-firewall' );
74
- $aSummary = [
75
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Settings that apply to all comment SPAM scanning.', 'wp-simple-firewall' ) ),
76
  ];
77
  break;
78
 
79
  case 'section_bot_comment_spam_protection_filter' :
80
- $sTitle = sprintf( __( '%s Comment SPAM Protection', 'wp-simple-firewall' ), __( 'Automatic Bot', 'wp-simple-firewall' ) );
81
- $aSummary = [
82
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Blocks 100% of all automated bot-generated comment SPAM.', 'wp-simple-firewall' ) ),
83
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), __( 'Use of this feature is highly recommend.', 'wp-simple-firewall' ) )
84
  ];
85
- $sTitleShort = __( 'Bot SPAM', 'wp-simple-firewall' );
86
  break;
87
 
88
  case 'section_human_spam_filter' :
89
- $sTitle = sprintf( __( '%s Comment SPAM Protection Filter', 'wp-simple-firewall' ), __( 'Human', 'wp-simple-firewall' ) );
90
- $aSummary = [
91
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Uses a 3rd party SPAM dictionary to detect human-based comment SPAM.', 'wp-simple-firewall' ) ),
92
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), __( 'Use of this feature is highly recommend.', 'wp-simple-firewall' ) ),
93
  __( 'This tool, unlike other SPAM tools such as Akismet, will not send your comment data to 3rd party services for analysis.', 'wp-simple-firewall' )
94
  ];
95
- $sTitleShort = __( 'Human SPAM', 'wp-simple-firewall' );
96
  break;
97
 
98
  default:
@@ -100,15 +100,13 @@ class Strings extends Base\Strings {
100
  }
101
 
102
  return [
103
- 'title' => $sTitle,
104
- 'title_short' => $sTitleShort,
105
- 'summary' => ( isset( $aSummary ) && is_array( $aSummary ) ) ? $aSummary : [],
106
  ];
107
  }
108
 
109
  public function getOptionStrings( string $key ) :array {
110
- /** @var ModCon $mod */
111
- $mod = $this->getMod();
112
  $modName = $this->getMod()->getMainFeatureName();
113
 
114
  switch ( $key ) {
@@ -163,21 +161,6 @@ class Strings extends Base\Strings {
163
  $desc = sprintf( __( 'When a comment is detected as being SPAM from %s, the comment will be categorised based on this setting.', 'wp-simple-firewall' ), '<span style"text-decoration:underline;">'.__( 'a human commenter', 'wp-simple-firewall' ).'</span>' );
164
  break;
165
 
166
- case 'enable_comments_gasp_protection' :
167
- $name = __( 'SPAM Bot Protection', 'wp-simple-firewall' );
168
- $summary = sprintf( '[DEPRECATED - %s] %s',
169
- 'Please use the newer AntiBot setting above',
170
- __( 'Block 100% Comment SPAM From Automated Bots', 'wp-simple-firewall' )
171
- );
172
- $desc = [
173
- sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ),
174
- __( "Use the newer AntiBot Detection Engine to detect SPAM instead of CAPTCHAs.", 'wp-simple-firewall' ) ),
175
-
176
- __( 'Highly effective detection for the most common types of comment SPAM.', 'wp-simple-firewall' ),
177
- sprintf( '%s: %s', __( 'Bonus', 'wp-simple-firewall' ), __( "Unlike Akismet, your data is never sent off-site to 3rd party processing servers.", 'wp-simple-firewall' ) )
178
- ];
179
- break;
180
-
181
  case 'comments_default_action_spam_bot' :
182
  $name = __( 'SPAM Action', 'wp-simple-firewall' );
183
  $summary = __( 'Where To Put SPAM Comments', 'wp-simple-firewall' );
@@ -185,63 +168,6 @@ class Strings extends Base\Strings {
185
  $this->getCon()->getHumanName() );
186
  break;
187
 
188
- case 'custom_message_checkbox' :
189
- $name = __( 'GASP Checkbox Message', 'wp-simple-firewall' );
190
- $summary = __( 'If you want a custom checkbox message, please provide this here', 'wp-simple-firewall' );
191
- $desc = [
192
- __( "You can customise the message beside the checkbox.", 'wp-simple-firewall' ),
193
- sprintf( __( 'Default Message: %s', 'wp-simple-firewall' ), __( "Please check the box to confirm you're not a spammer", 'wp-simple-firewall' ) )
194
- ];
195
- break;
196
-
197
- case 'google_recaptcha_style_comments' :
198
- $name = __( 'CAPTCHA', 'wp-simple-firewall' );
199
- $summary = sprintf( '[DEPRECATED - %s] %s',
200
- 'Please use the newer AntiBot setting above',
201
- __( 'Enable CAPTCHA To Protect Against SPAM Comments', 'wp-simple-firewall' )
202
- );
203
- $desc = [
204
- sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ),
205
- __( "Use the newer AntiBot Detection Engine to detect SPAM instead of CAPTCHAs.", 'wp-simple-firewall' ) ),
206
- __( 'You can choose the CAPTCHA display format that best suits your site, including the newer Invisible CAPTCHA, when you upgrade to PRO.', 'wp-simple-firewall' )
207
- ];
208
- if ( !$mod->getCaptchaCfg()->ready ) {
209
- $desc[] = sprintf( '<a href="%s">%s</a>',
210
- $this->getCon()
211
- ->getModule_Plugin()
212
- ->getUrl_DirectLinkToSection( 'section_third_party_captcha' ),
213
- __( 'Please remember to provide your CAPTCHA keys.', 'wp-simple-firewall' )
214
- );
215
- }
216
- break;
217
-
218
- case 'custom_message_alert' :
219
- $name = __( 'GASP Alert Message', 'wp-simple-firewall' );
220
- $summary = __( 'If you want a custom alert message, please provide this here', 'wp-simple-firewall' );
221
- $desc = [
222
- __( "This alert message is displayed when a visitor attempts to submit a comment without checking the box.", 'wp-simple-firewall' ),
223
- sprintf( __( 'Default Message: %s', 'wp-simple-firewall' ), __( "Please check the box to confirm you're not a spammer", 'wp-simple-firewall' ) )
224
- ];
225
- break;
226
-
227
- case 'custom_message_comment_wait' :
228
- $name = __( 'GASP Wait Message', 'wp-simple-firewall' );
229
- $summary = __( 'If you want a custom submit-button wait message, please provide this here.', 'wp-simple-firewall' );
230
- $desc = [
231
- __( "Where you see the '%s' this will be the number of seconds. You must ensure you include 1, and only 1, of these.", 'wp-simple-firewall' ),
232
- sprintf( __( 'Default Message: %s', 'wp-simple-firewall' ), __( 'Please wait %s seconds before posting your comment', 'wp-simple-firewall' ) )
233
- ];
234
- break;
235
-
236
- case 'custom_message_comment_reload' :
237
- $name = __( 'GASP Reload Message', 'wp-simple-firewall' );
238
- $summary = __( 'If you want a custom message when the comment token has expired, please provide this here.', 'wp-simple-firewall' );
239
- $desc = [
240
- __( 'This message is displayed on the submit-button when the comment token is expired', 'wp-simple-firewall' ),
241
- sprintf( __( 'Default Message: %s', 'wp-simple-firewall' ), __( "Please reload this page to post a comment", 'wp-simple-firewall' ) )
242
- ];
243
- break;
244
-
245
  default:
246
  return parent::getOptionStrings( $key );
247
  }
60
  switch ( $section ) {
61
 
62
  case 'section_enable_plugin_feature_spam_comments_protection_filter' :
63
+ $titleShort = sprintf( '%s/%s', __( 'On', 'wp-simple-firewall' ), __( 'Off', 'wp-simple-firewall' ) );
64
+ $title = sprintf( __( 'Enable Module: %s', 'wp-simple-firewall' ), __( 'Comments SPAM Protection', 'wp-simple-firewall' ) );
65
+ $summary = [
66
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'The Comments Filter can block 100% of automated spam bots and also offer the option to analyse human-generated spam.', 'wp-simple-firewall' ) ),
67
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), sprintf( __( 'Keep the %s feature turned on.', 'wp-simple-firewall' ), __( 'Comments Filter', 'wp-simple-firewall' ) ) )
68
  ];
69
  break;
70
 
71
  case 'section_bot_comment_spam_common' :
72
+ $titleShort = __( 'Common Settings', 'wp-simple-firewall' );
73
+ $title = __( 'Common Settings For All SPAM Scanning', 'wp-simple-firewall' );
74
+ $summary = [
75
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Settings that apply to all comment SPAM scanning.', 'wp-simple-firewall' ) ),
76
  ];
77
  break;
78
 
79
  case 'section_bot_comment_spam_protection_filter' :
80
+ $title = sprintf( __( '%s Comment SPAM Protection', 'wp-simple-firewall' ), __( 'Automatic Bot', 'wp-simple-firewall' ) );
81
+ $summary = [
82
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Blocks 100% of all automated bot-generated comment SPAM.', 'wp-simple-firewall' ) ),
83
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), __( 'Use of this feature is highly recommend.', 'wp-simple-firewall' ) )
84
  ];
85
+ $titleShort = __( 'Bot SPAM', 'wp-simple-firewall' );
86
  break;
87
 
88
  case 'section_human_spam_filter' :
89
+ $title = sprintf( __( '%s Comment SPAM Protection', 'wp-simple-firewall' ), __( 'Human', 'wp-simple-firewall' ) );
90
+ $summary = [
91
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Uses a 3rd party SPAM dictionary to detect human-based comment SPAM.', 'wp-simple-firewall' ) ),
92
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), __( 'Use of this feature is highly recommend.', 'wp-simple-firewall' ) ),
93
  __( 'This tool, unlike other SPAM tools such as Akismet, will not send your comment data to 3rd party services for analysis.', 'wp-simple-firewall' )
94
  ];
95
+ $titleShort = __( 'Human SPAM', 'wp-simple-firewall' );
96
  break;
97
 
98
  default:
100
  }
101
 
102
  return [
103
+ 'title' => $title,
104
+ 'title_short' => $titleShort,
105
+ 'summary' => $summary ?? [],
106
  ];
107
  }
108
 
109
  public function getOptionStrings( string $key ) :array {
 
 
110
  $modName = $this->getMod()->getMainFeatureName();
111
 
112
  switch ( $key ) {
161
  $desc = sprintf( __( 'When a comment is detected as being SPAM from %s, the comment will be categorised based on this setting.', 'wp-simple-firewall' ), '<span style"text-decoration:underline;">'.__( 'a human commenter', 'wp-simple-firewall' ).'</span>' );
162
  break;
163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  case 'comments_default_action_spam_bot' :
165
  $name = __( 'SPAM Action', 'wp-simple-firewall' );
166
  $summary = __( 'Where To Put SPAM Comments', 'wp-simple-firewall' );
168
  $this->getCon()->getHumanName() );
169
  break;
170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  default:
172
  return parent::getOptionStrings( $key );
173
  }
src/lib/src/Modules/CommentsFilter/Token/Create.php DELETED
@@ -1,44 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter\Token;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\CommentsFilter;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
- use FernleafSystems\Wordpress\Services\Services;
8
-
9
- class Create {
10
-
11
- use ModConsumer;
12
-
13
- /**
14
- * @param int $nTs
15
- * @param int $nPostId
16
- * @return string
17
- */
18
- public function run( $nTs, $nPostId ) {
19
- /** @var CommentsFilter\Options $oOpts */
20
- $oOpts = $this->getOptions();
21
-
22
- $sToken = $this->generateNewToken( $nTs, $nPostId );
23
-
24
- Services::WpGeneral()->setTransient(
25
- $this->getCon()->prefix( 'comtok-'.md5( sprintf( '%s-%s-%s', $nPostId, $nTs, Services::IP()
26
- ->getRequestIp() ) ) ),
27
- $sToken,
28
- $oOpts->getTokenExpireInterval()
29
- );
30
-
31
- return $sToken;
32
- }
33
-
34
- /**
35
- * @param int $nTs
36
- * @param string $nPostId
37
- * @return string
38
- */
39
- private function generateNewToken( $nTs, $nPostId ) {
40
- return hash_hmac( 'sha1',
41
- $nPostId.Services::IP()->getRequestIp().$nTs, $this->getCon()->getSiteInstallationId()
42
- );
43
- }
44
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/CommentsFilter/Upgrade.php CHANGED
@@ -15,7 +15,7 @@ class Upgrade extends Base\Upgrade {
15
  protected function upgrade_1411() {
16
  /** @var Options $opts */
17
  $opts = $this->getOptions();
18
- if ( $opts->isOpt( 'enable_antibot_check', 'Y' ) || $opts->isEnabledCaptcha() || $opts->isEnabledGaspCheck() ) {
19
  $opts->setEnabledAntiBot();
20
  }
21
  }
15
  protected function upgrade_1411() {
16
  /** @var Options $opts */
17
  $opts = $this->getOptions();
18
+ if ( $opts->isOpt( 'enable_antibot_check', 'Y' ) ) {
19
  $opts->setEnabledAntiBot();
20
  }
21
  }
src/lib/src/Modules/Comms/ModCon.php CHANGED
@@ -10,4 +10,8 @@ class ModCon extends BaseShield\ModCon {
10
  return ( new Lib\SureSend\SureSendController() )
11
  ->setMod( $this );
12
  }
 
 
 
 
13
  }
10
  return ( new Lib\SureSend\SureSendController() )
11
  ->setMod( $this );
12
  }
13
+
14
+ public function isModOptEnabled() :bool {
15
+ return true;
16
+ }
17
  }
src/lib/src/Modules/Data/DB/UserMeta/Ops/Common.php CHANGED
@@ -4,6 +4,10 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\UserMeta\Ops;
4
 
5
  trait Common {
6
 
 
 
 
 
7
  public function filterByUser( int $userID ) {
8
  return $this->addWhereEquals( 'user_id', $userID );
9
  }
4
 
5
  trait Common {
6
 
7
+ public function filterByIPRef( int $ipRef ) {
8
+ return $this->addWhereEquals( 'ip_ref', $ipRef );
9
+ }
10
+
11
  public function filterByUser( int $userID ) {
12
  return $this->addWhereEquals( 'user_id', $userID );
13
  }
src/lib/src/Modules/Data/DB/UserMeta/Ops/Record.php CHANGED
@@ -4,6 +4,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\UserMeta\Ops;
4
 
5
  /**
6
  * @property int $user_id
 
7
  * @property int $first_seen_at
8
  * @property int $last_login_at
9
  * @property int $last_2fa_verified_at
@@ -13,16 +14,14 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\UserMeta\Ops;
13
  class Record extends \FernleafSystems\Wordpress\Plugin\Core\Databases\Base\Record {
14
 
15
  /**
16
- * @param string $key
17
- * @param mixed $value
18
  */
19
  public function __set( string $key, $value ) {
20
  $dbh = $this->getDbHandler();
21
  if ( isset( $this->id ) && !empty( $dbh ) ) {
22
- $dbh->getQueryUpdater()
23
- ->updateRecord( $this, [
24
- $key => $value
25
- ] );
26
  }
27
  parent::__set( $key, $value );
28
  }
4
 
5
  /**
6
  * @property int $user_id
7
+ * @property int $ip_ref
8
  * @property int $first_seen_at
9
  * @property int $last_login_at
10
  * @property int $last_2fa_verified_at
14
  class Record extends \FernleafSystems\Wordpress\Plugin\Core\Databases\Base\Record {
15
 
16
  /**
17
+ * @param mixed $value
 
18
  */
19
  public function __set( string $key, $value ) {
20
  $dbh = $this->getDbHandler();
21
  if ( isset( $this->id ) && !empty( $dbh ) ) {
22
+ $dbh->getQueryUpdater()->updateRecord( $this, [
23
+ $key => $value
24
+ ] );
 
25
  }
26
  parent::__set( $key, $value );
27
  }
src/lib/src/Modules/Data/Lib/GeoIP/Lookup.php CHANGED
@@ -13,10 +13,11 @@ use FernleafSystems\Wordpress\Services\Services;
13
 
14
  class Lookup {
15
 
16
- const URL_REDIRECTLI = 'https://api.redirect.li/v1/ip/';
17
  use PluginControllerConsumer;
18
  use IpAddressConsumer;
19
 
 
 
20
  private $ips = [];
21
 
22
  private $reqCount = 0;
@@ -38,7 +39,7 @@ class Lookup {
38
  ->loadIP( $this->getIP(), true );
39
 
40
  if ( is_null( $ipRecord->geo )
41
- || Services::Request()->carbon()->subMonth()->timestamp > @$ipRecord->geo[ 'ts' ] ) {
42
 
43
  if ( $this->reqCount++ > 30 ) {
44
  throw new \Exception( 'Lookup limit reached.' );
13
 
14
  class Lookup {
15
 
 
16
  use PluginControllerConsumer;
17
  use IpAddressConsumer;
18
 
19
+ const URL_REDIRECTLI = 'https://api.redirect.li/v1/ip/';
20
+
21
  private $ips = [];
22
 
23
  private $reqCount = 0;
39
  ->loadIP( $this->getIP(), true );
40
 
41
  if ( is_null( $ipRecord->geo )
42
+ || Services::Request()->carbon()->subMonth()->timestamp > ( $ipRecord->geo[ 'ts' ] ?? 0 ) ) {
43
 
44
  if ( $this->reqCount++ > 30 ) {
45
  throw new \Exception( 'Lookup limit reached.' );
src/lib/src/Modules/Data/Lib/UpgradeReqLogsTable.php DELETED
@@ -1,119 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\Lib;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Common\ExecOnceModConsumer;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\ReqLogs\Ops\Handler;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\ReqLogs\Ops\Record;
8
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\ReqLogs\Ops\Select;
9
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\ModCon;
10
-
11
- class UpgradeReqLogsTable extends ExecOnceModConsumer {
12
-
13
- protected function run() {
14
- /** @var ModCon $mod */
15
- $mod = $this->getMod();
16
- /** @var Select $select */
17
- $select = $mod->getDbH_ReqLogs()->getQuerySelector();
18
-
19
- $page = 1;
20
- $pageSize = 100;
21
- do {
22
- /** @var Record[] $records */
23
- $records = $select->setLimit( $pageSize )
24
- ->setPage( $page )
25
- ->addWhere( 'type', '' )
26
- ->queryWithResult();
27
- foreach ( $records as $record ) {
28
- try {
29
- $this->upgradeLogEntry( $record );
30
- }
31
- catch ( \Exception $e ) {
32
- break( 2 );
33
- }
34
- }
35
-
36
- $page++;
37
- if ( $page > 5 ) {
38
- break;
39
- }
40
- } while ( !empty( $records ) );
41
- }
42
-
43
- /**
44
- * @throws \Exception
45
- */
46
- private function upgradeLogEntry( Record $record ) {
47
- /** @var ModCon $mod */
48
- $mod = $this->getMod();
49
-
50
- $upgradeData = [
51
- 'type' => Handler::TYPE_HTTP,
52
- 'path' => $record->path,
53
- ];
54
-
55
- $meta = $record->meta;
56
-
57
- if ( $meta[ 'ua' ] === 'wpcli' ) {
58
- $isWpCli = true;
59
- $upgradeData[ 'type' ] = Handler::TYPE_WPCLI;
60
- unset( $meta[ 'ua' ] );
61
- }
62
- else {
63
- $isWpCli = false;
64
- }
65
-
66
- if ( isset( $meta[ 'code' ] ) && is_numeric( $meta[ 'code' ] ) ) {
67
- $upgradeData[ 'code' ] = (int)$meta[ 'code' ];
68
- unset( $meta[ 'code' ] );
69
- }
70
-
71
- if ( isset( $meta[ 'offense' ] ) ) {
72
- $upgradeData[ 'offense' ] = true;
73
- unset( $meta[ 'offense' ] );
74
- }
75
-
76
- if ( !empty( $meta[ 'path' ] ) ) {
77
- $parts = explode( $isWpCli ? ' ' : '?', (string)$meta[ 'path' ], 2 );
78
- $upgradeData[ 'path' ] = $parts[ 0 ];
79
- if ( !empty( $parts[ 1 ] ) ) {
80
- $meta[ 'query' ] = $parts[ 1 ];
81
- }
82
- unset( $meta[ 'path' ] );
83
- }
84
-
85
- if ( !empty( $meta[ 'verb' ] ) ) {
86
- $upgradeData[ 'verb' ] = strtoupper( (string)$meta[ 'verb' ] );
87
- unset( $meta[ 'verb' ] );
88
- }
89
-
90
- if ( !empty( $meta[ 'uid' ] ) ) {
91
- $upgradeData[ 'uid' ] = (int)$meta[ 'uid' ];
92
- unset( $meta[ 'uid' ] );
93
- }
94
-
95
- if ( ( $meta[ 'ua' ] ?? '' ) === 'wpcli' ) {
96
- $upgradeData[ 'type' ] = 'WPCLI';
97
- unset( $meta[ 'ua' ] );
98
- }
99
- elseif ( wp_parse_url( admin_url( 'admin-ajax.php' ), PHP_URL_PATH ) === $upgradeData[ 'path' ] ) {
100
- $upgradeData[ 'type' ] = Handler::TYPE_AJAX;
101
- }
102
- elseif ( wp_parse_url( home_url( 'wp-cron.php' ), PHP_URL_PATH ) === $upgradeData[ 'path' ] ) {
103
- $upgradeData[ 'type' ] = Handler::TYPE_CRON;
104
- }
105
- elseif ( wp_parse_url( home_url( 'xmlrpc.php' ), PHP_URL_PATH ) === $upgradeData[ 'path' ] ) {
106
- $upgradeData[ 'type' ] = Handler::TYPE_XMLRPC;
107
- }
108
-
109
- $record->meta = $meta;
110
- $upgradeData[ 'meta' ] = $record->getRawData()[ 'meta' ];
111
-
112
- $success = $mod->getDbH_ReqLogs()
113
- ->getQueryUpdater()
114
- ->updateById( $record->id, $upgradeData );
115
- if ( !$success ) {
116
- throw new \Exception( 'failed to update' );
117
- }
118
- }
119
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/Data/ModCon.php CHANGED
@@ -2,7 +2,6 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Data;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\ReqLogs\QueueReqDbRecordMigrator;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\{
7
  AuditTrail,
8
  Traffic
@@ -11,20 +10,6 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
11
 
12
  class ModCon extends BaseShield\ModCon {
13
 
14
- private $reqDbMigrator;
15
-
16
- public function onWpLoaded() {
17
- parent::onWpLoaded();
18
- $this->getReqDbMigrator();
19
- }
20
-
21
- public function getReqDbMigrator() :QueueReqDbRecordMigrator {
22
- if ( !isset( $this->reqDbMigrator ) ) {
23
- $this->reqDbMigrator = ( new QueueReqDbRecordMigrator() )->setMod( $this );
24
- }
25
- return $this->reqDbMigrator;
26
- }
27
-
28
  public function getDbH_IPs() :DB\IPs\Ops\Handler {
29
  return $this->getDbHandler()->loadDbH( 'ips' );
30
  }
@@ -38,11 +23,6 @@ class ModCon extends BaseShield\ModCon {
38
  return $this->getDbHandler()->loadDbH( 'req_logs' );
39
  }
40
 
41
- public function runDailyCron() {
42
- parent::runDailyCron();
43
- $this->getReqDbMigrator()->dispatch();
44
- }
45
-
46
  protected function cleanupDatabases() {
47
  $con = $this->getCon();
48
 
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Data;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\{
6
  AuditTrail,
7
  Traffic
10
 
11
  class ModCon extends BaseShield\ModCon {
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  public function getDbH_IPs() :DB\IPs\Ops\Handler {
14
  return $this->getDbHandler()->loadDbH( 'ips' );
15
  }
23
  return $this->getDbHandler()->loadDbH( 'req_logs' );
24
  }
25
 
 
 
 
 
 
26
  protected function cleanupDatabases() {
27
  $con = $this->getCon();
28
 
src/lib/src/Modules/Events/Lib/EventsService.php CHANGED
@@ -20,11 +20,10 @@ class EventsService {
20
  public function fireEvent( string $event, array $meta = [] ) {
21
  if ( $this->eventExists( $event ) ) {
22
  try {
23
- $this->verifyAuditParams( $event, $meta );
24
  do_action(
25
  'shield/event',
26
  $event,
27
- $meta,
28
  $this->getEventDef( $event )
29
  );
30
  }
@@ -37,7 +36,7 @@ class EventsService {
37
  /**
38
  * @throws \Exception
39
  */
40
- private function verifyAuditParams( string $event, array $meta ) {
41
  $def = $this->getEventDef( $event )[ 'audit_params' ] ?? [];
42
  $metaParams = array_keys( $meta[ 'audit_params' ] ?? [] );
43
 
@@ -45,13 +44,16 @@ class EventsService {
45
  error_log( sprintf( 'WARNING: Event (%s) receives params but none are defined.', $event ) );
46
  }
47
  elseif ( !empty( $def ) ) {
48
- if ( array_diff( $def, $metaParams ) ) {
49
- throw new \Exception( sprintf( "Event (%s) def has audit params that aren't present: %s", $event, implode( ', ', $def ) ) );
 
50
  }
51
  if ( array_diff( $metaParams, $def ) ) {
52
- throw new \Exception( sprintf( "Event (%s) has audit params that aren't present in def: %s", $event, implode( ', ', $metaParams ) ) );
 
53
  }
54
  }
 
55
  }
56
 
57
  /**
@@ -84,7 +86,6 @@ class EventsService {
84
  }
85
 
86
  /**
87
- * @param string $eventKey
88
  * @return array|null
89
  */
90
  public function getEventDef( string $eventKey ) {
20
  public function fireEvent( string $event, array $meta = [] ) {
21
  if ( $this->eventExists( $event ) ) {
22
  try {
 
23
  do_action(
24
  'shield/event',
25
  $event,
26
+ $this->verifyAuditParams( $event, $meta ),
27
  $this->getEventDef( $event )
28
  );
29
  }
36
  /**
37
  * @throws \Exception
38
  */
39
+ private function verifyAuditParams( string $event, array $meta ) :array {
40
  $def = $this->getEventDef( $event )[ 'audit_params' ] ?? [];
41
  $metaParams = array_keys( $meta[ 'audit_params' ] ?? [] );
42
 
44
  error_log( sprintf( 'WARNING: Event (%s) receives params but none are defined.', $event ) );
45
  }
46
  elseif ( !empty( $def ) ) {
47
+ $missingParams = array_diff( $def, $metaParams );
48
+ if ( !empty( $missingParams ) ) {
49
+ throw new \Exception( sprintf( "Event (%s) def has audit params that aren't present: %s", $event, implode( ', ', $missingParams ) ) );
50
  }
51
  if ( array_diff( $metaParams, $def ) ) {
52
+ // Previously we threw an exception. Now we just clean out the unwanted params.
53
+ $meta[ 'audit_params' ] = array_intersect_key( $meta[ 'audit_params' ], array_flip( $def ) );
54
  }
55
  }
56
+ return $meta;
57
  }
58
 
59
  /**
86
  }
87
 
88
  /**
 
89
  * @return array|null
90
  */
91
  public function getEventDef( string $eventKey ) {
src/lib/src/Modules/Events/Lib/UI/BuildDataForStats.php CHANGED
@@ -84,7 +84,7 @@ class BuildDataForStats {
84
  ]
85
  ],
86
  [
87
- 'title' => __( 'Bot Tracking' ),
88
  'events' => [
89
  'antibot_fail',
90
  'antibot_pass',
84
  ]
85
  ],
86
  [
87
+ 'title' => __( 'Bot Tracking', 'wp-simple-firewall' ),
88
  'events' => [
89
  'antibot_fail',
90
  'antibot_pass',
src/lib/src/Modules/Events/ModCon.php CHANGED
@@ -12,7 +12,6 @@ class ModCon extends BaseShield\ModCon {
12
  }
13
 
14
  /**
15
- * @return bool
16
  * @throws \Exception
17
  */
18
  protected function isReadyToExecute() :bool {
12
  }
13
 
14
  /**
 
15
  * @throws \Exception
16
  */
17
  protected function isReadyToExecute() :bool {
src/lib/src/Modules/Events/Processor.php CHANGED
@@ -15,13 +15,9 @@ class Processor extends BaseShield\Processor {
15
 
16
  protected function run() {
17
  $this->loadStatsWriter()->setIfCommit( true );
18
- add_action( $this->getCon()->prefix( 'dashboard_widget_content' ), [ $this, 'statsWidget' ], 10 );
19
  }
20
 
21
- /**
22
- * @return Events\Lib\StatsWriter
23
- */
24
- public function loadStatsWriter() {
25
  if ( !isset( $this->oStatsWriter ) ) {
26
  /** @var ModCon $mod */
27
  $mod = $this->getMod();
@@ -31,57 +27,6 @@ class Processor extends BaseShield\Processor {
31
  return $this->oStatsWriter;
32
  }
33
 
34
- public function statsWidget() {
35
- /** @var Databases\Events\Select $selector */
36
- $selector = $this->getCon()
37
- ->getModule_Events()
38
- ->getDbHandler_Events()
39
- ->getQuerySelector();
40
-
41
- $aKeyStats = [
42
- 'comments' => [
43
- __( 'Comment Blocks', 'wp-simple-firewall' ),
44
- $selector->clearWheres()->sumEvents( [
45
- 'spam_block_bot',
46
- 'spam_block_human',
47
- 'spam_block_recaptcha'
48
- ] )
49
- ],
50
- 'firewall' => [
51
- __( 'Firewall Blocks', 'wp-simple-firewall' ),
52
- $selector->clearWheres()->sumEvent( 'firewall_block' )
53
- ],
54
- 'login_fail' => [
55
- __( 'Login Blocks', 'wp-simple-firewall' ),
56
- $selector->clearWheres()->sumEvent( 'login_block' )
57
- ],
58
- 'login_verified' => [
59
- __( 'Login Verified', 'wp-simple-firewall' ),
60
- $selector->clearWheres()->sumEvent( '2fa_success' )
61
- ],
62
- 'session_start' => [
63
- __( 'User Sessions', 'wp-simple-firewall' ),
64
- $selector->clearWheres()->sumEvent( 'session_start' )
65
- ],
66
- 'ip_killed' => [
67
- __( 'IP Blocks', 'wp-simple-firewall' ),
68
- $selector->clearWheres()->sumEvent( 'conn_kill' )
69
- ],
70
- 'ip_transgressions' => [
71
- __( 'Total Offenses', 'wp-simple-firewall' ),
72
- $selector->clearWheres()->sumEvent( 'ip_offense' )
73
- ],
74
- ];
75
-
76
- echo $this->getMod()->renderTemplate(
77
- 'snippets/widget_dashboard_statistics.php',
78
- [
79
- 'sHeading' => sprintf( __( '%s Statistics', 'wp-simple-firewall' ), $this->getCon()->getHumanName() ),
80
- 'aKeyStats' => $aKeyStats,
81
- ]
82
- );
83
- }
84
-
85
  public function runDailyCron() {
86
  ( new Events\Consolidate\ConsolidateAllEvents() )
87
  ->setMod( $this->getMod() )
15
 
16
  protected function run() {
17
  $this->loadStatsWriter()->setIfCommit( true );
 
18
  }
19
 
20
+ public function loadStatsWriter() :Events\Lib\StatsWriter {
 
 
 
21
  if ( !isset( $this->oStatsWriter ) ) {
22
  /** @var ModCon $mod */
23
  $mod = $this->getMod();
27
  return $this->oStatsWriter;
28
  }
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  public function runDailyCron() {
31
  ( new Events\Consolidate\ConsolidateAllEvents() )
32
  ->setMod( $this->getMod() )
src/lib/src/Modules/Firewall/Insights/OverviewCards.php DELETED
@@ -1,56 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\Insights;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\ModCon;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\Options;
8
-
9
- class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
10
-
11
- protected function buildModCards() :array {
12
- /** @var ModCon $mod */
13
- $mod = $this->getMod();
14
- /** @var Options $opts */
15
- $opts = $this->getOptions();
16
-
17
- $cards = [];
18
-
19
- if ( $mod->isModOptEnabled() ) {
20
- //ignoring admin isn't a good idea
21
- $includeAdmin = !$opts->isIgnoreAdmin();
22
- $cards[ 'admin' ] = [
23
- 'name' => $includeAdmin ?
24
- __( "Include Admins", 'wp-simple-firewall' )
25
- : __( "Ignore Admins", 'wp-simple-firewall' ),
26
- 'state' => $includeAdmin ? 1 : 0,
27
- 'summary' => $includeAdmin ?
28
- __( "Firewall rules are also applied to admins", 'wp-simple-firewall' )
29
- : __( "Firewall rules aren't applied to admins", 'wp-simple-firewall' ),
30
- 'href' => $mod->getUrl_DirectLinkToOption( 'whitelist_admins' ),
31
- ];
32
- }
33
-
34
- return $cards;
35
- }
36
-
37
- protected function getSectionTitle() :string {
38
- return __( 'Firewall', 'wp-simple-firewall' );
39
- }
40
-
41
- protected function getSectionSubTitle() :string {
42
- return __( 'Block Malicious Requests', 'wp-simple-firewall' );
43
- }
44
-
45
- protected function getModDisabledCard() :array {
46
- $mod = $this->getMod();
47
- return [
48
- 'name' => __( 'Firewall', 'wp-simple-firewall' ),
49
- 'state' => $mod->isModOptEnabled() ? 1 : -2,
50
- 'summary' => $mod->isModOptEnabled() ?
51
- __( "The Firewall is protecting your site against malicious requests", 'wp-simple-firewall' )
52
- : __( "The Firewall is disabled so your site isn't protected against malicious requests", 'wp-simple-firewall' ),
53
- 'href' => $mod->getUrl_DirectLinkToOption( $mod->getEnableModOptKey() ),
54
- ];
55
- }
56
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/Firewall/Lib/Scan/FirewallHandler.php CHANGED
@@ -10,6 +10,9 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\{
10
  };
11
  use FernleafSystems\Wordpress\Services\Services;
12
 
 
 
 
13
  class FirewallHandler extends ExecOnceModConsumer {
14
 
15
  /**
@@ -94,16 +97,9 @@ class FirewallHandler extends ExecOnceModConsumer {
94
  }
95
 
96
  protected function getFirewallDieMessage() :array {
97
- $default = __( "Something in the request URL or Form data triggered the firewall.", 'wp-simple-firewall' );
98
- $customMessage = $this->getMod()->getTextOpt( 'text_firewalldie' );
99
-
100
- $messages = apply_filters(
101
- 'shield/firewall_die_message',
102
- [
103
- empty( $customMessage ) ? $default : $customMessage,
104
- ]
105
- );
106
- return is_array( $messages ) ? $messages : [ $default ];
107
  }
108
 
109
  private function sendBlockEmail() :bool {
@@ -148,30 +144,30 @@ class FirewallHandler extends ExecOnceModConsumer {
148
  */
149
  private function enumHandlers() :array {
150
  return [
151
- 'dir_traversal' => function () {
152
- return new Handlers\DirTraversal();
153
- },
154
- 'sql_queries' => function () {
155
- return new Handlers\SqlQueries();
156
- },
157
- 'wordpress_terms' => function () {
158
- return new Handlers\WpTerms();
159
- },
160
- 'field_truncation' => function () {
161
- return new Handlers\FieldTruncation();
162
- },
163
- 'php_code' => function () {
164
- return new Handlers\PhpCode();
165
- },
166
- 'leading_schema' => function () {
167
- return new Handlers\LeadingSchema();
168
- },
169
- 'aggressive' => function () {
170
- return new Handlers\Aggressive();
171
- },
172
- 'exe_file_uploads' => function () {
173
- return new Handlers\ExeFiles();
174
- },
175
  ];
176
  }
177
  }
10
  };
11
  use FernleafSystems\Wordpress\Services\Services;
12
 
13
+ /**
14
+ * @deprecated 15.0
15
+ */
16
  class FirewallHandler extends ExecOnceModConsumer {
17
 
18
  /**
97
  }
98
 
99
  protected function getFirewallDieMessage() :array {
100
+ return [
101
+ __( "Something in the request URL or Form data triggered the firewall.", 'wp-simple-firewall' )
102
+ ];
 
 
 
 
 
 
 
103
  }
104
 
105
  private function sendBlockEmail() :bool {
144
  */
145
  private function enumHandlers() :array {
146
  return [
147
+ // 'dir_traversal' => function () {
148
+ // return new Handlers\DirTraversal();
149
+ // },
150
+ // 'sql_queries' => function () {
151
+ // return new Handlers\SqlQueries();
152
+ // },
153
+ // 'wordpress_terms' => function () {
154
+ // return new Handlers\WpTerms();
155
+ // },
156
+ // 'field_truncation' => function () {
157
+ // return new Handlers\FieldTruncation();
158
+ // },
159
+ // 'php_code' => function () {
160
+ // return new Handlers\PhpCode();
161
+ // },
162
+ // 'leading_schema' => function () {
163
+ // return new Handlers\LeadingSchema();
164
+ // },
165
+ // 'aggressive' => function () {
166
+ // return new Handlers\Aggressive();
167
+ // },
168
+ 'exe_file_uploads' => function () {
169
+ return new Handlers\ExeFiles();
170
+ },
171
  ];
172
  }
173
  }
src/lib/src/Modules/Firewall/Lib/Scan/Handlers/SqlQueries.php CHANGED
@@ -4,7 +4,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\Lib\Scan\Hand
4
 
5
  class SqlQueries extends BaseRequestParams {
6
 
7
- const SLUG = 'sqlqueries';
8
 
9
  protected function getScanName() :string {
10
  return __( 'SQL Queries', 'wp-simple-firewall' );
4
 
5
  class SqlQueries extends BaseRequestParams {
6
 
7
+ const SLUG = 'sql_queries';
8
 
9
  protected function getScanName() :string {
10
  return __( 'SQL Queries', 'wp-simple-firewall' );
src/lib/src/Modules/Firewall/ModCon.php CHANGED
@@ -7,47 +7,30 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
 
8
  class ModCon extends BaseShield\ModCon {
9
 
10
- /**
11
- * @param string $sParam
12
- * @param string $sPage
13
- */
14
- public function addParamToWhitelist( $sParam, $sPage = '*' ) {
15
  /** @var Options $opts */
16
  $opts = $this->getOptions();
17
 
18
- if ( empty( $sPage ) ) {
19
- $sPage = '*';
20
- }
21
-
22
- $aW = $opts->getCustomWhitelist();
23
- $aParams = isset( $aW[ $sPage ] ) ? $aW[ $sPage ] : [];
24
- $aParams[] = $sParam;
25
- natsort( $aParams );
26
- $aW[ $sPage ] = array_unique( $aParams );
27
-
28
- $opts->setOpt( 'page_params_whitelist', $aW );
 
 
 
 
 
29
  }
30
 
31
  public function getBlockResponse() :string {
32
  $response = $this->getOptions()->getOpt( 'block_response', '' );
33
  return !empty( $response ) ? $response : 'redirect_die_message'; // TODO: use default
34
  }
35
-
36
- public function getTextOptDefault( string $key ) :string {
37
-
38
- switch ( $key ) {
39
- case 'text_firewalldie':
40
- $text = sprintf(
41
- __( "You were blocked by the %s Firewall.", 'wp-simple-firewall' ),
42
- '<a href="https://wordpress.org/plugins/wp-simple-firewall/" target="_blank">'.$this->getCon()
43
- ->getHumanName().'</a>'
44
- );
45
- break;
46
-
47
- default:
48
- $text = parent::getTextOptDefault( $key );
49
- break;
50
- }
51
- return $text;
52
- }
53
  }
7
 
8
  class ModCon extends BaseShield\ModCon {
9
 
10
+ protected function enumRuleBuilders() :array {
 
 
 
 
11
  /** @var Options $opts */
12
  $opts = $this->getOptions();
13
 
14
+ return array_filter(
15
+ [
16
+ Rules\Build\FirewallSqlQueries::class,
17
+ Rules\Build\FirewallDirTraversal::class,
18
+ Rules\Build\FirewallFieldTruncation::class,
19
+ Rules\Build\FirewallWordpressTerms::class,
20
+ Rules\Build\FirewallPhpCode::class,
21
+ Rules\Build\FirewallLeadingSchema::class,
22
+ Rules\Build\FirewallAggressive::class,
23
+ Rules\Build\FirewallExeFileUploads::class,
24
+ ],
25
+ function ( $blockTypeClass ) use ( $opts ) {
26
+ /** @var Rules\Build\BuildFirewallBase $blockTypeClass */
27
+ return $opts->isOpt( 'block_'.$blockTypeClass::SCAN_CATEGORY, 'Y' );
28
+ }
29
+ );
30
  }
31
 
32
  public function getBlockResponse() :string {
33
  $response = $this->getOptions()->getOpt( 'block_response', '' );
34
  return !empty( $response ) ? $response : 'redirect_die_message'; // TODO: use default
35
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  }
src/lib/src/Modules/Firewall/Options.php CHANGED
@@ -1,4 +1,4 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall;
4
 
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall;
4
 
src/lib/src/Modules/Firewall/Processor.php CHANGED
@@ -1,33 +1,9 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\Lib\Scan\FirewallHandler;
7
 
8
  class Processor extends BaseShield\Processor {
9
 
10
- private $firewallHandler;
11
-
12
- public function onWpInit() {
13
- $this->getFirewallHandler()->execute();
14
- }
15
-
16
- private function getFirewallHandler() :FirewallHandler {
17
- if ( !isset( $this->firewallHandler ) ) {
18
- $this->firewallHandler = ( new FirewallHandler() )->setMod( $this->getMod() );
19
- }
20
- return $this->firewallHandler;
21
- }
22
-
23
- protected function getWpHookPriority( string $hook ) :int {
24
- switch ( $hook ) {
25
- case 'init':
26
- $pri = 0;
27
- break;
28
- default:
29
- $pri = parent::getWpHookPriority( $hook );
30
- }
31
- return $pri;
32
- }
33
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
 
6
 
7
  class Processor extends BaseShield\Processor {
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  }
src/lib/src/Modules/Firewall/Rules/Build/BuildFirewallBase.php ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\Options;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
9
+ Build\BuildRuleCoreShieldBase,
10
+ Conditions,
11
+ Responses
12
+ };
13
+
14
+ abstract class BuildFirewallBase extends BuildRuleCoreShieldBase {
15
+
16
+ const SCAN_CATEGORY = '';
17
+
18
+ protected function getName() :string {
19
+ return sprintf( '%s: %s', __( 'Firewall', 'wp-simple-firewall' ),
20
+ $this->getMod()
21
+ ->getStrings()
22
+ ->getOptionStrings( 'block_'.static::SCAN_CATEGORY )[ 'name' ] );
23
+ }
24
+
25
+ protected function getCommonAuditParamsMapping() :array {
26
+ return array_merge( parent::getCommonAuditParamsMapping(), [
27
+ 'term' => 'match_pattern',
28
+ 'param' => 'match_request_param',
29
+ 'value' => 'match_request_value',
30
+ 'scan' => 'match_category',
31
+ 'type' => 'match_type',
32
+ ] );
33
+ }
34
+
35
+ protected function getDescription() :string {
36
+ return sprintf( __( 'Check request parameters that trigger "%s" patterns.', 'wp-simple-firewall' ),
37
+ $this->getName() );
38
+ }
39
+
40
+ protected function getConditions() :array {
41
+ /** @var Options $opts */
42
+ $opts = $this->getOptions();
43
+
44
+ $conditions = [
45
+ 'logic' => static::LOGIC_AND,
46
+ 'group' => [
47
+ [
48
+ 'rule' => Plugin\Rules\Build\RequestBypassesAllRestrictions::SLUG,
49
+ 'invert_match' => true
50
+ ],
51
+ [
52
+ 'rule' => Shield\Modules\IPs\Rules\Build\IpBlocked::SLUG,
53
+ 'invert_match' => true
54
+ ],
55
+ [
56
+ 'condition' => Conditions\RequestHasParameters::SLUG,
57
+ ],
58
+ ]
59
+ ];
60
+
61
+ $excludedPaths = $this->getExcludedPaths();
62
+ if ( !empty( $excludedPaths ) ) {
63
+ $conditions[ 'group' ][] = [
64
+ 'condition' => Conditions\NotMatchRequestPath::SLUG,
65
+ 'params' => [
66
+ 'is_match_regex' => false,
67
+ 'match_paths' => $excludedPaths,
68
+ ],
69
+ ];
70
+ }
71
+
72
+ if ( $opts->isIgnoreAdmin() ) {
73
+ $conditions[ 'group' ][] = [
74
+ 'condition' => Conditions\IsUserAdminNormal::SLUG,
75
+ 'invert_match' => true,
76
+ ];
77
+ }
78
+
79
+ $firewallRulesGroup = [
80
+ 'logic' => static::LOGIC_OR,
81
+ 'group' => [],
82
+ ];
83
+
84
+ $simple = $this->getFirewallPatterns_Simple();
85
+ if ( !empty( $simple ) ) {
86
+ $firewallRulesGroup[ 'group' ][] = [
87
+ 'condition' => Conditions\MatchRequestParamQuery::SLUG,
88
+ 'params' => [
89
+ 'is_match_regex' => false,
90
+ 'match_patterns' => $simple,
91
+ 'match_category' => static::SCAN_CATEGORY,
92
+ 'excluded_params' => $this->getExclusions(),
93
+ ],
94
+ ];
95
+ $firewallRulesGroup[ 'group' ][] = [
96
+ 'condition' => Conditions\MatchRequestParamPost::SLUG,
97
+ 'params' => [
98
+ 'is_match_regex' => false,
99
+ 'match_patterns' => $simple,
100
+ 'match_category' => static::SCAN_CATEGORY,
101
+ 'excluded_params' => $this->getExclusions(),
102
+ ],
103
+ ];
104
+ }
105
+
106
+ $regex = $this->getFirewallPatterns_Regex();
107
+ if ( !empty( $regex ) ) {
108
+ $firewallRulesGroup[ 'group' ][] = [
109
+ 'condition' => Conditions\MatchRequestParamQuery::SLUG,
110
+ 'params' => [
111
+ 'is_match_regex' => true,
112
+ 'match_patterns' => $regex,
113
+ 'match_category' => static::SCAN_CATEGORY,
114
+ 'excluded_params' => $this->getExclusions(),
115
+ ],
116
+ ];
117
+ $firewallRulesGroup[ 'group' ][] = [
118
+ 'condition' => Conditions\MatchRequestParamPost::SLUG,
119
+ 'params' => [
120
+ 'is_match_regex' => true,
121
+ 'match_patterns' => $regex,
122
+ 'match_category' => static::SCAN_CATEGORY,
123
+ 'excluded_params' => $this->getExclusions(),
124
+ ],
125
+ ];
126
+ }
127
+
128
+ $conditions[ 'group' ][] = $firewallRulesGroup;
129
+
130
+ return $conditions;
131
+ }
132
+
133
+ protected function getExcludedPaths() :array {
134
+ return array_keys( array_filter( $this->getExclusions(), function ( $excl ) {
135
+ return empty( $excl );
136
+ } ) );
137
+ }
138
+
139
+ protected function getExclusions() :array {
140
+ /** @var Options $opts */
141
+ $opts = $this->getOptions();
142
+ $exclusions = $opts->getDef( 'default_whitelist' );
143
+ foreach ( $opts->getCustomWhitelist() as $page => $params ) {
144
+ if ( empty( $params ) || !is_array( $params ) ) {
145
+ continue;
146
+ }
147
+ if ( !isset( $exclusions[ $page ] ) ) {
148
+ $exclusions[ $page ] = [
149
+ 'simple' => [],
150
+ ];
151
+ }
152
+ $exclusions[ $page ][ 'simple' ] = array_merge( $exclusions[ $page ][ 'simple' ], $params );
153
+ }
154
+ return $exclusions;
155
+ }
156
+
157
+ protected function getResponses() :array {
158
+ return [
159
+ [
160
+ 'response' => Responses\EventFire::SLUG,
161
+ 'params' => [
162
+ 'event' => 'firewall_block',
163
+ 'offense_count' => 1,
164
+ 'block' => false,
165
+ 'audit_params' => [
166
+ 'name' => $this->getName()
167
+ ],
168
+ 'audit_params_map' => $this->getCommonAuditParamsMapping(),
169
+ ],
170
+ ],
171
+ [
172
+ 'response' => Responses\FirewallBlock::SLUG,
173
+ 'params' => [],
174
+ ],
175
+ ];
176
+ }
177
+
178
+ protected function getFirewallPatterns() :array {
179
+ return $this->getOptions()->getDef( 'firewall_patterns' )[ static::SCAN_CATEGORY ] ?? [];
180
+ }
181
+
182
+ protected function getFirewallPatterns_Regex() :array {
183
+ return $this->getFirewallPatterns()[ 'regex' ] ?? [];
184
+ }
185
+
186
+ protected function getFirewallPatterns_Simple() :array {
187
+ return $this->getFirewallPatterns()[ 'simple' ] ?? [];
188
+ }
189
+ }
src/lib/src/Modules/Firewall/Rules/Build/FirewallAggressive.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+
7
+ class FirewallAggressive extends BuildFirewallBase {
8
+
9
+ const SLUG = 'shield/firewall_aggressive';
10
+ const SCAN_CATEGORY = 'aggressive';
11
+ }
src/lib/src/Modules/Firewall/Rules/Build/FirewallDirTraversal.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+
7
+ class FirewallDirTraversal extends BuildFirewallBase {
8
+
9
+ const SLUG = 'shield/firewall_dir_traversal';
10
+ const SCAN_CATEGORY = 'dir_traversal';
11
+ }
src/lib/src/Modules/Firewall/Rules/Build/FirewallExeFileUploads.php ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+
7
+ class FirewallExeFileUploads extends BuildFirewallBase {
8
+
9
+ const SLUG = 'shield/firewall_exe_file_uploads';
10
+ const SCAN_CATEGORY = 'exe_file_uploads';
11
+
12
+ protected function getConditions() :array {
13
+ $conditions = [
14
+ 'logic' => static::LOGIC_AND,
15
+ 'group' => [
16
+ [
17
+ 'rule' => Shield\Modules\Plugin\Rules\Build\RequestBypassesAllRestrictions::SLUG,
18
+ 'invert_match' => true
19
+ ],
20
+ ]
21
+ ];
22
+
23
+ $matchGroup = [
24
+ 'logic' => static::LOGIC_OR,
25
+ 'group' => [],
26
+ ];
27
+
28
+ $simple = $this->getFirewallPatterns_Simple();
29
+ if ( !empty( $simple ) ) {
30
+ $matchGroup[ 'group' ][] = [
31
+ 'condition' => Shield\Rules\Conditions\MatchRequestParamFileUploads::SLUG,
32
+ 'params' => [
33
+ 'is_match_regex' => false,
34
+ 'match_patterns' => $simple,
35
+ 'match_category' => static::SCAN_CATEGORY,
36
+ ],
37
+ ];
38
+ }
39
+
40
+ $regex = $this->getFirewallPatterns_Regex();
41
+ if ( !empty( $regex ) ) {
42
+ $matchGroup[ 'group' ][] = [
43
+ 'condition' => Shield\Rules\Conditions\MatchRequestParamFileUploads::SLUG,
44
+ 'params' => [
45
+ 'is_match_regex' => true,
46
+ 'match_patterns' => $regex,
47
+ 'match_category' => static::SCAN_CATEGORY,
48
+ ],
49
+ ];
50
+ }
51
+
52
+ $conditions[ 'group' ][] = $matchGroup;
53
+
54
+ return $conditions;
55
+ }
56
+ }
src/lib/src/Modules/Firewall/Rules/Build/FirewallFieldTruncation.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+
7
+ class FirewallFieldTruncation extends BuildFirewallBase {
8
+
9
+ const SLUG = 'shield/firewall_field_truncation';
10
+ const SCAN_CATEGORY = 'field_truncation';
11
+ }
src/lib/src/Modules/Firewall/Rules/Build/FirewallLeadingSchema.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+
7
+ class FirewallLeadingSchema extends BuildFirewallBase {
8
+
9
+ const SLUG = 'shield/firewall_leading_schema';
10
+ const SCAN_CATEGORY = 'leading_schema';
11
+ }
src/lib/src/Modules/Firewall/Rules/Build/FirewallPhpCode.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+
7
+ class FirewallPhpCode extends BuildFirewallBase {
8
+
9
+ const SLUG = 'shield/firewall_php_code';
10
+ const SCAN_CATEGORY = 'php_code';
11
+ }
src/lib/src/Modules/Firewall/Rules/Build/FirewallSqlQueries.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+
7
+ class FirewallSqlQueries extends BuildFirewallBase {
8
+
9
+ const SLUG = 'shield/firewall_sql_queries';
10
+ const SCAN_CATEGORY = 'sql_queries';
11
+ }
src/lib/src/Modules/Firewall/Rules/Build/FirewallWordpressTerms.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+
7
+ class FirewallWordpressTerms extends BuildFirewallBase {
8
+
9
+ const SLUG = 'shield/firewall_wordpress_terms';
10
+ const SCAN_CATEGORY = 'wordpress_terms';
11
+ }
src/lib/src/Modules/Firewall/Strings.php CHANGED
@@ -1,4 +1,4 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall;
4
 
@@ -105,8 +105,10 @@ class Strings extends Base\Strings {
105
  case 'block_php_code' :
106
  $name = __( 'PHP Code', 'wp-simple-firewall' );
107
  $summary = sprintf( __( 'Block %s', 'wp-simple-firewall' ), __( 'PHP Code Includes', 'wp-simple-firewall' ) );
108
- $desc = __( 'This will block any data that appears to try and include PHP files.', 'wp-simple-firewall' )
109
- .'<br />'.__( 'Will probably block saving within the Plugin/Theme file editors.', 'wp-simple-firewall' );
 
 
110
  break;
111
 
112
  case 'block_exe_file_uploads' :
@@ -124,8 +126,10 @@ class Strings extends Base\Strings {
124
  case 'block_aggressive' :
125
  $name = __( 'Aggressive Scan', 'wp-simple-firewall' );
126
  $summary = __( 'Aggressively Block Data', 'wp-simple-firewall' );
127
- $desc = __( 'Employs a set of aggressive rules to detect and block malicious data submitted to your site.', 'wp-simple-firewall' )
128
- .'<br />'.sprintf( '%s - %s', __( 'Warning', 'wp-simple-firewall' ), __( 'May cause an increase in false-positive firewall blocks.', 'wp-simple-firewall' ) );
 
 
129
  break;
130
 
131
  case 'block_response' :
@@ -182,12 +186,6 @@ class Strings extends Base\Strings {
182
  __( 'The offending request parameter was "{{param}}" with a value of "{{value}}".', 'wp-simple-firewall' ),
183
  ],
184
  ],
185
- 'check_skip' => [
186
- 'name' => __( 'Firewall Skip Checking', 'wp-simple-firewall' ),
187
- 'audit' => [
188
- __( 'Skipping firewall checking for this visit: {{path}}.', 'wp-simple-firewall' )
189
- ],
190
- ],
191
  'fw_email_success' => [
192
  'name' => __( 'Firewall Block Email Success', 'wp-simple-firewall' ),
193
  'audit' => [
@@ -203,6 +201,19 @@ class Strings extends Base\Strings {
203
  ];
204
  }
205
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  /**
207
  * @inheritDoc
208
  */
@@ -218,10 +229,10 @@ class Strings extends Base\Strings {
218
  'blockparam_fieldtruncation' => [
219
  sprintf( __( 'Firewall Trigger: %s.', 'wp-simple-firewall' ), __( 'Field Truncation', 'wp-simple-firewall' ) )
220
  ],
221
- 'blockparam_sqlqueries' => [
222
  sprintf( __( 'Firewall Trigger: %s.', 'wp-simple-firewall' ), __( 'SQL Queries', 'wp-simple-firewall' ) )
223
  ],
224
- 'blockparam_schema' => [
225
  sprintf( __( 'Firewall Trigger: %s.', 'wp-simple-firewall' ), __( 'Leading Schema', 'wp-simple-firewall' ) )
226
  ],
227
  'blockparam_aggressive' => [
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall;
4
 
105
  case 'block_php_code' :
106
  $name = __( 'PHP Code', 'wp-simple-firewall' );
107
  $summary = sprintf( __( 'Block %s', 'wp-simple-firewall' ), __( 'PHP Code Includes', 'wp-simple-firewall' ) );
108
+ $desc = [
109
+ __( 'This will block any data that appears to try and include PHP files.', 'wp-simple-firewall' ),
110
+ __( 'Will probably block saving within the Plugin/Theme file editors.', 'wp-simple-firewall' )
111
+ ];
112
  break;
113
 
114
  case 'block_exe_file_uploads' :
126
  case 'block_aggressive' :
127
  $name = __( 'Aggressive Scan', 'wp-simple-firewall' );
128
  $summary = __( 'Aggressively Block Data', 'wp-simple-firewall' );
129
+ $desc = [
130
+ __( 'Employs a set of aggressive rules to detect and block malicious data submitted to your site.', 'wp-simple-firewall' ),
131
+ sprintf( '<strong>%s</strong> - %s', __( 'Warning', 'wp-simple-firewall' ), __( 'May cause an increase in false-positive firewall blocks.', 'wp-simple-firewall' ) )
132
+ ];
133
  break;
134
 
135
  case 'block_response' :
186
  __( 'The offending request parameter was "{{param}}" with a value of "{{value}}".', 'wp-simple-firewall' ),
187
  ],
188
  ],
 
 
 
 
 
 
189
  'fw_email_success' => [
190
  'name' => __( 'Firewall Block Email Success', 'wp-simple-firewall' ),
191
  'audit' => [
201
  ];
202
  }
203
 
204
+ public function getFirewallCategoryName( string $category ) :string {
205
+ return [
206
+ 'dir_traversal' => __( 'Directory Traversal', 'wp-simple-firewall' ),
207
+ 'wordpress_terms' => __( 'WordPress Terms', 'wp-simple-firewall' ),
208
+ 'sql_queries' => __( 'SQL Queries', 'wp-simple-firewall' ),
209
+ 'field_truncation' => __( 'Field Truncation', 'wp-simple-firewall' ),
210
+ 'aggressive' => __( 'Aggressive Rules', 'wp-simple-firewall' ),
211
+ 'leading_schema' => __( 'Leading Schema', 'wp-simple-firewall' ),
212
+ 'php_code' => __( 'PHP Code', 'wp-simple-firewall' ),
213
+ 'exe_file_uploads' => __( 'EXE File Uploads', 'wp-simple-firewall' ),
214
+ ][ $category ] ?? 'Unknown';
215
+ }
216
+
217
  /**
218
  * @inheritDoc
219
  */
229
  'blockparam_fieldtruncation' => [
230
  sprintf( __( 'Firewall Trigger: %s.', 'wp-simple-firewall' ), __( 'Field Truncation', 'wp-simple-firewall' ) )
231
  ],
232
+ 'blockparam_sql_queries' => [
233
  sprintf( __( 'Firewall Trigger: %s.', 'wp-simple-firewall' ), __( 'SQL Queries', 'wp-simple-firewall' ) )
234
  ],
235
+ 'blockparam_leading_schema' => [
236
  sprintf( __( 'Firewall Trigger: %s.', 'wp-simple-firewall' ), __( 'Leading Schema', 'wp-simple-firewall' ) )
237
  ],
238
  'blockparam_aggressive' => [
src/lib/src/Modules/HackGuard/DB/Scans/Ops/Common.php CHANGED
@@ -2,8 +2,6 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\DB\Scans\Ops;
4
 
5
- use FernleafSystems\Wordpress\Services\Services;
6
-
7
  trait Common {
8
 
9
  public function filterByScan( string $scan ) {
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\DB\Scans\Ops;
4
 
 
 
5
  trait Common {
6
 
7
  public function filterByScan( string $scan ) {
src/lib/src/Modules/HackGuard/DB/Utility/Clean.php ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\DB\Utility;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Common\ExecOnceModConsumer;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\{
8
+ DB\Scans\Ops as ScansDB,
9
+ ModCon,
10
+ Options
11
+ };
12
+
13
+ class Clean extends ExecOnceModConsumer {
14
+
15
+ protected function run() {
16
+ error_log( 'clean' );
17
+ $this->deleteScansThatNeverCompleted();
18
+ $this->deleteEarlierScans();
19
+ }
20
+
21
+ private function deleteScansThatNeverCompleted() {
22
+ /** @var ModCon $mod */
23
+ $mod = $this->getMod();
24
+
25
+ /** @var ScansDB\Delete $deleter */
26
+ $deleter = $mod->getDbH_Scans()->getQueryDeleter();
27
+ $deleter->filterByNotFinished()
28
+ ->filterByCreatedAt( Services::Request()->carbon()->subDay()->timestamp, '<' )
29
+ ->query();
30
+ }
31
+
32
+ private function deleteEarlierScans() {
33
+ /** @var ModCon $mod */
34
+ $mod = $this->getMod();
35
+ /** @var Options $opts */
36
+ $opts = $this->getOptions();
37
+ $dbhScan = $mod->getDbH_Scans();
38
+
39
+ $scanIDsToKeep = [];
40
+ foreach ( $opts->getScanSlugs() as $scanSlug ) {
41
+ /** @var ScansDB\Select $select */
42
+ $select = $dbhScan->getQuerySelector();
43
+ $scanRecord = $select->filterByFinished()
44
+ ->filterByScan( $scanSlug )
45
+ ->setOrderBy( 'finished_at', 'DESC' )
46
+ ->setLimit( 1 )
47
+ ->queryWithResult();
48
+ $scanIDsToKeep[] = $scanRecord[ 0 ]->id;
49
+ }
50
+
51
+ Services::WpDb()->doSql( sprintf( 'DELETE FROM %s WHERE `id` NOT IN (%s) AND `finished_at`>0',
52
+ $mod->getDbH_Scans()->getTableSchema()->table,
53
+ implode( ', ', $scanIDsToKeep ) ) );
54
+ }
55
+ }
src/lib/src/Modules/HackGuard/Insights/OverviewCards.php DELETED
@@ -1,239 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Insights;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan\Controller\{
7
- Afs,
8
- Apc,
9
- Wpv
10
- };
11
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard;
12
-
13
- class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
14
-
15
- protected function buildModCards() :array {
16
- /** @var HackGuard\ModCon $mod */
17
- $mod = $this->getMod();
18
- /** @var HackGuard\Options $opts */
19
- $opts = $this->getOptions();
20
-
21
- $cards = [];
22
-
23
- if ( $mod->isModOptEnabled() ) {
24
- $goodFrequency = $opts->getScanFrequency() > 1;
25
- $cards[ 'frequency' ] = [
26
- 'name' => __( 'Scan Frequency', 'wp-simple-firewall' ),
27
- 'state' => $goodFrequency ? 1 : 0,
28
- 'summary' => $goodFrequency ?
29
- __( 'Automatic scanners run more than once per day', 'wp-simple-firewall' )
30
- : __( "Automatic scanners only run once per day", 'wp-simple-firewall' ),
31
- 'href' => $mod->getUrl_DirectLinkToSection( 'section_scan_options' ),
32
- ];
33
-
34
- $cards = array_merge(
35
- $cards,
36
- $this->getCardsForWcf(),
37
- $this->getCardsForMal(),
38
- $this->getCardsForWpv(),
39
- $this->getCardsForPtg(),
40
- $this->getCardsForApc()
41
- );
42
- }
43
-
44
- return $cards;
45
- }
46
-
47
- protected function getSectionTitle() :string {
48
- return __( 'Hack Guard', 'wp-simple-firewall' );
49
- }
50
-
51
- protected function getSectionSubTitle() :string {
52
- return __( 'Threats/Intrusions Detection & Repair', 'wp-simple-firewall' );
53
- }
54
-
55
- private function getCardsForWcf() :array {
56
- /** @var HackGuard\ModCon $mod */
57
- $mod = $this->getMod();
58
- /** @var HackGuard\Options $opts */
59
- $opts = $this->getOptions();
60
- /** @var Afs $scanCon */
61
- $scanCon = $mod->getScanCon( Afs::SCAN_SLUG );
62
-
63
- $cards = [];
64
-
65
- $scanCore = $scanCon->isEnabled();
66
- $cards[ $scanCon::SCAN_SLUG ] = [
67
- 'name' => sprintf( '%s: %s', __( 'Scanner', 'wp-simple-firewall' ), $scanCon->getScanName() ),
68
- 'state' => $scanCore ? 1 : -2,
69
- 'summary' => $scanCore ?
70
- __( 'WP Core files are scanned automatically', 'wp-simple-firewall' )
71
- : __( "WP Core files aren't automatically scanned!", 'wp-simple-firewall' ),
72
- 'href' => $mod->getUrl_DirectLinkToOption( 'enable_core_file_integrity_scan' ),
73
- 'help' => __( 'Automatic WordPress Core File scanner should be turned-on.', 'wp-simple-firewall' )
74
- ];
75
- if ( $scanCore ) {
76
- if ( !$opts->isRepairFileWP() ) {
77
- $cards[ 'wcf_repair' ] = [
78
- 'name' => __( 'WP Core File Repair', 'wp-simple-firewall' ),
79
- 'state' => $opts->isRepairFileWP() ? 1 : -1,
80
- 'summary' => $opts->isRepairFileWP() ?
81
- __( 'Core files are automatically repaired', 'wp-simple-firewall' )
82
- : __( "Core files aren't automatically repaired!", 'wp-simple-firewall' ),
83
- 'href' => $mod->getUrl_DirectLinkToOption( 'file_repair_areas' ),
84
- ];
85
- }
86
- }
87
-
88
- if ( $scanCore && $scanCon->getScansController()->getScanResultsCount()->countWPFiles() ) {
89
- $cards[ 'wcf_problem' ] = [
90
- 'name' => sprintf( '%s: %s', __( 'Modified', 'wp-simple-firewall' ), __( 'WordPress Core Files', 'wp-simple-firewall' ) ),
91
- 'summary' => __( 'WordPress core files have been modified.', 'wp-simple-firewall' ),
92
- 'href' => $this->getUrlForScanResults(),
93
- 'state' => -2,
94
- 'help' => __( 'Scan WP core files and repair any files that are flagged as modified.', 'wp-simple-firewall' )
95
- ];
96
- }
97
-
98
- return $cards;
99
- }
100
-
101
- private function getCardsForPtg() :array {
102
- /** @var HackGuard\ModCon $mod */
103
- $mod = $this->getMod();
104
- /** @var Afs $scanCon */
105
- $scanCon = $mod->getScanCon( Afs::SCAN_SLUG );
106
-
107
- $cards = [];
108
-
109
- $isPTG = $scanCon->isEnabledPluginThemeScan();
110
- $cards[ $scanCon::SCAN_SLUG ] = [
111
- 'name' => sprintf( '%s: %s', __( 'Scanner', 'wp-simple-firewall' ), __( 'Plugins & Themes', 'wp-simple-firewall' ) ),
112
- 'summary' => $isPTG ?
113
- __( 'Plugins and Themes are guarded against tampering', 'wp-simple-firewall' )
114
- : __( "Plugins and Themes are never scanned for tampering!", 'wp-simple-firewall' ),
115
- 'state' => $isPTG ? 1 : -2,
116
- 'href' => $mod->getUrl_DirectLinkToOption( 'enable_core_file_integrity_scan' ),
117
- 'help' => __( 'Automatic detection of plugin/theme modifications is recommended.', 'wp-simple-firewall' ),
118
- ];
119
-
120
- $status = $scanCon->getScansController()->getScanResultsCount();
121
- if ( $isPTG && ( $status->countPluginFiles() + $status->countPluginFiles() ) > 0 ) {
122
- $cards[ 'ptg_problem' ] = [
123
- 'name' => sprintf( '%s: %s', __( 'Modified', 'wp-simple-firewall' ), __( 'Plugins & Themes', 'wp-simple-firewall' ) ),
124
- 'summary' => __( 'A plugin/theme was found to have been modified.', 'wp-simple-firewall' ),
125
- 'state' => -2,
126
- 'href' => $this->getUrlForScanResults(),
127
- 'help' => __( 'Reviewing modifications to your plugins/themes is recommended.', 'wp-simple-firewall' ),
128
- ];
129
- }
130
-
131
- return $cards;
132
- }
133
-
134
- private function getCardsForMal() :array {
135
- /** @var HackGuard\ModCon $mod */
136
- $mod = $this->getMod();
137
- /** @var Afs $scanCon */
138
- $scanCon = $mod->getScanCon( Afs::SCAN_SLUG );
139
-
140
- $cards = [];
141
-
142
- $malEnabled = $scanCon->isEnabledMalwareScan();
143
- $cards[ $scanCon::SCAN_SLUG ] = [
144
- 'name' => sprintf( '%s: %s', __( 'Scanner', 'wp-simple-firewall' ), $scanCon->getScanName() ),
145
- 'summary' => $malEnabled ?
146
- sprintf( __( '%s Scanner runs automatically.' ), $scanCon->getScanName() )
147
- : sprintf( __( "%s Scanner isn't set to run automatically." ), $scanCon->getScanName() ),
148
- 'state' => $malEnabled ? 1 : -2,
149
- 'href' => $mod->getUrl_DirectLinkToSection( 'section_file_guard' ),
150
- 'help' => __( 'Automatic detection of Malware is recommended.', 'wp-simple-firewall' )
151
- ];
152
- if ( $malEnabled && $scanCon->getScansController()->getScanResultsCount()->countMalware() ) {
153
- $cards[ 'mal_problem' ] = [
154
- 'name' => __( 'Potential Malware Detected', 'wp-simple-firewall' ),
155
- 'summary' => __( 'Potential Malware files have been discovered.', 'wp-simple-firewall' ),
156
- 'state' => -2,
157
- 'href' => $this->getUrlForScanResults(),
158
- 'help' => __( 'Files identified as potential malware should be examined as soon as possible.', 'wp-simple-firewall' ),
159
- ];
160
- }
161
-
162
- return $cards;
163
- }
164
-
165
- private function getCardsForApc() :array {
166
- /** @var HackGuard\ModCon $mod */
167
- $mod = $this->getMod();
168
- $scanCon = $mod->getScanCon( Apc::SCAN_SLUG );
169
-
170
- $cards = [];
171
-
172
- $isAPC = $scanCon->isEnabled();
173
- $cards[ $scanCon::SCAN_SLUG ] = [
174
- 'name' => sprintf( '%s: %s', __( 'Scanner', 'wp-simple-firewall' ), $scanCon->getScanName() ),
175
- 'state' => $isAPC ? 1 : -1,
176
- 'summary' => $isAPC ?
177
- sprintf( __( '%s Scanner is enabled.' ), $scanCon->getScanName() )
178
- : sprintf( __( '%s Scanner is not enabled.' ), $scanCon->getScanName() ),
179
- 'href' => $mod->getUrl_DirectLinkToSection( 'section_scan_apc' ),
180
- ];
181
- if ( $isAPC && $scanCon->getScansController()->getScanResultsCount()->countAbandoned() > 0 ) {
182
- $cards[ 'apc_problem' ] = [
183
- 'name' => __( 'Plugin Abandoned' ),
184
- 'summary' => __( 'At least 1 plugin on your site is abandoned.', 'wp-simple-firewall' ),
185
- 'state' => -1,
186
- 'href' => $this->getUrlForScanResults(),
187
- 'help' => __( 'Plugins that have been abandoned represent a potential risk to your site.', 'wp-simple-firewall' )
188
- ];
189
- }
190
-
191
- return $cards;
192
- }
193
-
194
- private function getCardsForWpv() :array {
195
- /** @var HackGuard\ModCon $mod */
196
- $mod = $this->getMod();
197
- $scanCon = $mod->getScanCon( Wpv::SCAN_SLUG );
198
-
199
- $cards = [];
200
-
201
- $enabledWpv = $scanCon->isEnabled();
202
- $cards[ $scanCon::SCAN_SLUG ] = [
203
- 'name' => __( 'Vulnerability Scan', 'wp-simple-firewall' ),
204
- 'state' => $enabledWpv ? 1 : -2,
205
- 'summary' => $enabledWpv ?
206
- __( 'Regularly scanning for known vulnerabilities', 'wp-simple-firewall' )
207
- : __( "Plugins/Themes never scanned for vulnerabilities!", 'wp-simple-firewall' ),
208
- 'href' => $mod->getUrl_DirectLinkToSection( 'section_scan_wpv' ),
209
- 'help' => __( 'Automatic detection of vulnerabilities is recommended.', 'wp-simple-firewall' )
210
- ];
211
-
212
- $bWpvAutoUpdates = $scanCon->isCronAutoRepair();
213
- if ( $enabledWpv ) {
214
- $cards[ 'wpv_repair' ] = [
215
- 'name' => __( 'Auto Update', 'wp-simple-firewall' ),
216
- 'summary' => $bWpvAutoUpdates ?
217
- __( 'Vulnerable items are automatically updated', 'wp-simple-firewall' )
218
- : __( "Vulnerable items aren't automatically updated!", 'wp-simple-firewall' ),
219
- 'state' => $bWpvAutoUpdates ? 1 : -1,
220
- 'href' => $mod->getUrl_DirectLinkToSection( 'section_scan_wpv' ),
221
- ];
222
- }
223
- if ( $enabledWpv && $scanCon->getScansController()->getScanResultsCount()->countVulnerableAssets() > 0 ) {
224
- $cards[ 'wpv_problem' ] = [
225
- 'name' => __( 'Vulnerable Plugin', 'wp-simple-firewall' ),
226
- 'summary' => __( 'Plugin with vulnerabilities found on site.', 'wp-simple-firewall' ),
227
- 'state' => -2,
228
- 'href' => $this->getUrlForScanResults(),
229
- 'help' => __( 'Items with known vulnerabilities should be updated, removed, or replaced.', 'wp-simple-firewall' )
230
- ];
231
- }
232
-
233
- return $cards;
234
- }
235
-
236
- private function getUrlForScanResults() :string {
237
- return $this->getCon()->getModule_Insights()->getUrl_ScansResults();
238
- }
239
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/HackGuard/ModCon.php CHANGED
@@ -173,7 +173,7 @@ class ModCon extends BaseShield\ModCon {
173
  $freq = $opts->getScanFrequency();
174
  Services::WpCron()
175
  ->addNewSchedule(
176
- $this->prefix( sprintf( 'per-day-%s', $freq ) ),
177
  [
178
  'interval' => DAY_IN_SECONDS/$freq,
179
  'display' => sprintf( __( '%s per day', 'wp-simple-firewall' ), $freq )
@@ -197,7 +197,7 @@ class ModCon extends BaseShield\ModCon {
197
  else {
198
  $exclusion = wp_normalize_path( trim( $exclusion ) );
199
  if ( strpos( $exclusion, '/' ) === false ) { // filename only
200
- $exclusion = trim( preg_replace( '#[^.0-9a-z_-]#i', '', $exclusion ) );
201
  }
202
  }
203
 
@@ -224,6 +224,12 @@ class ModCon extends BaseShield\ModCon {
224
  return $this->getDbH( 'filelocker' );
225
  }
226
 
 
 
 
 
 
 
227
  /**
228
  * @throws \Exception
229
  */
173
  $freq = $opts->getScanFrequency();
174
  Services::WpCron()
175
  ->addNewSchedule(
176
+ $this->getCon()->prefix( sprintf( 'per-day-%s', $freq ) ),
177
  [
178
  'interval' => DAY_IN_SECONDS/$freq,
179
  'display' => sprintf( __( '%s per day', 'wp-simple-firewall' ), $freq )
197
  else {
198
  $exclusion = wp_normalize_path( trim( $exclusion ) );
199
  if ( strpos( $exclusion, '/' ) === false ) { // filename only
200
+ $exclusion = trim( preg_replace( '#[^.\da-z_-]#i', '', $exclusion ) );
201
  }
202
  }
203
 
224
  return $this->getDbH( 'filelocker' );
225
  }
226
 
227
+ protected function cleanupDatabases() {
228
+ ( new Shield\Modules\HackGuard\DB\Utility\Clean() )
229
+ ->setMod( $this )
230
+ ->execute();
231
+ }
232
+
233
  /**
234
  * @throws \Exception
235
  */
src/lib/src/Modules/HackGuard/Options.php CHANGED
@@ -47,7 +47,7 @@ class Options extends BaseShield\Options {
47
  function ( $value ) {
48
  return ( new WildCardOptions() )->buildFullRegexValue( $value, WildCardOptions::FILE_PATH_REL );
49
  },
50
- is_array( $paths ) ? $paths : []
51
  );
52
  }
53
 
@@ -70,8 +70,6 @@ class Options extends BaseShield\Options {
70
  }
71
 
72
  /**
73
- * @param string $fileName
74
- * @param string $url
75
  * @return string[]
76
  */
77
  private function getMalSignatures( string $fileName, string $url ) :array {
@@ -107,7 +105,7 @@ class Options extends BaseShield\Options {
107
  }
108
 
109
  public function isAutoFilterResults() :bool {
110
- return $this->isOpt( 'auto_filter_results', 'Y' );
111
  }
112
 
113
  public function isPtgReinstallLinks() :bool {
47
  function ( $value ) {
48
  return ( new WildCardOptions() )->buildFullRegexValue( $value, WildCardOptions::FILE_PATH_REL );
49
  },
50
+ $paths
51
  );
52
  }
53
 
70
  }
71
 
72
  /**
 
 
73
  * @return string[]
74
  */
75
  private function getMalSignatures( string $fileName, string $url ) :array {
105
  }
106
 
107
  public function isAutoFilterResults() :bool {
108
+ return (bool)apply_filters( 'shield/scan_auto_filter_results', true );
109
  }
110
 
111
  public function isPtgReinstallLinks() :bool {
src/lib/src/Modules/HackGuard/Render/ScanResults/SectionPluginThemesBase.php CHANGED
@@ -12,8 +12,6 @@ use FernleafSystems\Wordpress\Services\Services;
12
  class SectionPluginThemesBase extends SectionBase {
13
 
14
  protected function getCommonRenderData() :array {
15
- /** @var ModCon $mod */
16
- $mod = $this->getMod();
17
  return Services::DataManipulation()
18
  ->mergeArraysRecursive( parent::getCommonRenderData(), [
19
  'strings' => [
12
  class SectionPluginThemesBase extends SectionBase {
13
 
14
  protected function getCommonRenderData() :array {
 
 
15
  return Services::DataManipulation()
16
  ->mergeArraysRecursive( parent::getCommonRenderData(), [
17
  'strings' => [
src/lib/src/Modules/HackGuard/Render/ScanResults/SectionPlugins.php CHANGED
@@ -91,7 +91,28 @@ class SectionPlugins extends SectionPluginThemesBase {
91
 
92
  $vulnerabilities = $this->getVulnerabilities()->getItemsForSlug( $plugin->file );
93
 
94
- $data = [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  'info' => [
96
  'type' => 'plugin',
97
  'name' => $plugin->Title,
@@ -117,28 +138,12 @@ class SectionPlugins extends SectionPluginThemesBase {
117
  'https://shsec.io/shieldvulnerabilitylookup'
118
  ),
119
  ],
120
- 'flags' => [
121
- 'has_update' => $plugin->hasUpdate(),
122
- 'has_guard_files' => !empty( $guardFilesData ),
123
- 'is_abandoned' => !empty( $abandoned ),
124
- 'is_active' => $plugin->active,
125
- 'is_vulnerable' => !empty( $vulnerabilities ),
126
- 'is_wporg' => $plugin->isWpOrg(),
127
- ],
128
  'vars' => [
129
  'abandoned_rid' => empty( $abandoned ) ? -1 : $abandoned->VO->scanresult_id,
130
  'count_items' => count( $guardFilesData ) + count( $vulnerabilities )
131
  + ( empty( $abandoned ) ? 0 : 1 )
132
  ],
133
  ];
134
- $data[ 'flags' ][ 'has_issue' ] = $data[ 'flags' ][ 'is_abandoned' ]
135
- || $data[ 'flags' ][ 'has_guard_files' ]
136
- || $data[ 'flags' ][ 'is_vulnerable' ];
137
- $data[ 'flags' ][ 'has_warning' ] = !$data[ 'flags' ][ 'has_issue' ]
138
- && (
139
- !$data[ 'flags' ][ 'is_active' ]
140
- || $data[ 'flags' ][ 'has_update' ]
141
- );
142
- return $data;
143
  }
144
  }
91
 
92
  $vulnerabilities = $this->getVulnerabilities()->getItemsForSlug( $plugin->file );
93
 
94
+ $isCheckActive = apply_filters( 'shield/scans_check_plugin_active', true );
95
+ $isCheckUpdates = apply_filters( 'shield/scans_check_plugin_update', true );
96
+
97
+ $flags = [
98
+ 'has_update' => $plugin->hasUpdate(),
99
+ 'has_guard_files' => !empty( $guardFilesData ),
100
+ 'is_abandoned' => !empty( $abandoned ),
101
+ 'is_active' => $plugin->active,
102
+ 'is_vulnerable' => !empty( $vulnerabilities ),
103
+ 'is_wporg' => $plugin->isWpOrg(),
104
+ ];
105
+ $flags[ 'has_issue' ] = $flags[ 'is_abandoned' ]
106
+ || $flags[ 'has_guard_files' ]
107
+ || $flags[ 'is_vulnerable' ];
108
+ $flags[ 'has_warning' ] = !$flags[ 'has_issue' ]
109
+ && (
110
+ ( $isCheckActive && !$flags[ 'is_active' ] )
111
+ ||
112
+ ( $isCheckUpdates && $flags[ 'has_update' ] )
113
+ );
114
+
115
+ return [
116
  'info' => [
117
  'type' => 'plugin',
118
  'name' => $plugin->Title,
138
  'https://shsec.io/shieldvulnerabilitylookup'
139
  ),
140
  ],
141
+ 'flags' => $flags,
 
 
 
 
 
 
 
142
  'vars' => [
143
  'abandoned_rid' => empty( $abandoned ) ? -1 : $abandoned->VO->scanresult_id,
144
  'count_items' => count( $guardFilesData ) + count( $vulnerabilities )
145
  + ( empty( $abandoned ) ? 0 : 1 )
146
  ],
147
  ];
 
 
 
 
 
 
 
 
 
148
  }
149
  }
src/lib/src/Modules/HackGuard/Render/ScanResults/SectionThemes.php CHANGED
@@ -93,7 +93,60 @@ class SectionThemes extends SectionPluginThemesBase {
93
 
94
  $vulnerabilities = $this->getVulnerabilities()->getItemsForSlug( $theme->stylesheet );
95
 
96
- $data = [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  'info' => [
98
  'type' => 'theme',
99
  'name' => $theme->Name,
@@ -121,57 +174,11 @@ class SectionThemes extends SectionPluginThemesBase {
121
  'https://shsec.io/shieldvulnerabilitylookup'
122
  ),
123
  ],
124
- 'flags' => [
125
- 'has_update' => $theme->hasUpdate(),
126
- 'is_abandoned' => !empty( $abandoned ),
127
- 'has_guard_files' => !empty( $guardFilesData ),
128
- 'is_active' => $theme->active || $theme->is_parent,
129
- 'is_ignored' => $theme->active || $theme->is_parent,
130
- 'is_vulnerable' => !empty( $vulnerabilities ),
131
- 'is_wporg' => $theme->isWpOrg(),
132
- 'is_child' => $theme->is_child,
133
- 'is_parent' => $theme->is_parent,
134
- ],
135
  'vars' => [
136
  'count_items' => count( $guardFilesData ) + count( $vulnerabilities )
137
  + ( empty( $abandoned ) ? 0 : 1 )
138
  ],
139
  ];
140
- $data[ 'flags' ][ 'has_issue' ] = $data[ 'flags' ][ 'is_abandoned' ]
141
- || $data[ 'flags' ][ 'has_guard_files' ]
142
- || $data[ 'flags' ][ 'is_vulnerable' ];
143
- $data[ 'flags' ][ 'has_warning' ] = !$data[ 'flags' ][ 'has_issue' ]
144
- && (
145
- !$data[ 'flags' ][ 'is_active' ]
146
- || $data[ 'flags' ][ 'has_update' ]
147
- );
148
- if ( $theme->isWpOrg() && $data[ 'flags' ][ 'has_warning' ] && !$data[ 'flags' ][ 'has_update' ] ) {
149
- $wpOrgThemes = implode( '|', array_map( function ( $ver ) {
150
- return 'twenty'.$ver;
151
- }, [
152
- 'twentyseven',
153
- 'twentysix',
154
- 'twentyfive',
155
- 'twentyfour',
156
- 'twentythree',
157
- 'twentytwo',
158
- 'twentyone',
159
- 'twenty',
160
- 'nineteen',
161
- 'seventeen',
162
- 'sixteen',
163
- 'fifteen',
164
- 'fourteen',
165
- 'thirteen',
166
- 'twelve',
167
- 'eleven',
168
- 'ten',
169
- ] ) );
170
- if ( preg_match( sprintf( '#^%s$#', $wpOrgThemes ), strtolower( (string)$theme->slug ) ) ) {
171
- $data[ 'flags' ][ 'has_warning' ] = false;
172
- }
173
- }
174
-
175
- return $data;
176
  }
177
  }
93
 
94
  $vulnerabilities = $this->getVulnerabilities()->getItemsForSlug( $theme->stylesheet );
95
 
96
+ $flags = [
97
+ 'has_update' => $theme->hasUpdate(),
98
+ 'is_abandoned' => !empty( $abandoned ),
99
+ 'has_guard_files' => !empty( $guardFilesData ),
100
+ 'is_active' => $theme->active || $theme->is_parent,
101
+ 'is_ignored' => $theme->active || $theme->is_parent,
102
+ 'is_vulnerable' => !empty( $vulnerabilities ),
103
+ 'is_wporg' => $theme->isWpOrg(),
104
+ 'is_child' => $theme->is_child,
105
+ 'is_parent' => $theme->is_parent,
106
+ ];
107
+
108
+ $isCheckActive = apply_filters( 'shield/scans_check_theme_active', true );
109
+ $isCheckUpdates = apply_filters( 'shield/scans_check_theme_update', true );
110
+
111
+ $flags[ 'has_issue' ] = $flags[ 'is_abandoned' ]
112
+ || $flags[ 'has_guard_files' ]
113
+ || $flags[ 'is_vulnerable' ];
114
+ $flags[ 'has_warning' ] = !$flags[ 'has_issue' ]
115
+ && (
116
+ ( $isCheckActive && !$flags[ 'is_active' ] )
117
+ ||
118
+ ( $isCheckUpdates && $flags[ 'has_update' ] )
119
+ );
120
+
121
+
122
+ if ( $theme->isWpOrg() && $flags[ 'has_warning' ] && !$flags[ 'has_update' ] ) {
123
+ $wpOrgThemes = implode( '|', array_map( function ( $ver ) {
124
+ return 'twenty'.$ver;
125
+ }, [
126
+ 'twentyseven',
127
+ 'twentysix',
128
+ 'twentyfive',
129
+ 'twentyfour',
130
+ 'twentythree',
131
+ 'twentytwo',
132
+ 'twentyone',
133
+ 'twenty',
134
+ 'nineteen',
135
+ 'seventeen',
136
+ 'sixteen',
137
+ 'fifteen',
138
+ 'fourteen',
139
+ 'thirteen',
140
+ 'twelve',
141
+ 'eleven',
142
+ 'ten',
143
+ ] ) );
144
+ if ( preg_match( sprintf( '#^%s$#', $wpOrgThemes ), strtolower( (string)$theme->slug ) ) ) {
145
+ $flags[ 'has_warning' ] = false;
146
+ }
147
+ }
148
+
149
+ return [
150
  'info' => [
151
  'type' => 'theme',
152
  'name' => $theme->Name,
174
  'https://shsec.io/shieldvulnerabilitylookup'
175
  ),
176
  ],
177
+ 'flags' => $flags,
 
 
 
 
 
 
 
 
 
 
178
  'vars' => [
179
  'count_items' => count( $guardFilesData ) + count( $vulnerabilities )
180
  + ( empty( $abandoned ) ? 0 : 1 )
181
  ],
182
  ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  }
184
  }
src/lib/src/Modules/HackGuard/Scan/Controller/Afs.php CHANGED
@@ -77,13 +77,6 @@ class Afs extends BaseForFiles {
77
  return $items;
78
  }
79
 
80
- /**
81
- * @deprecated 14.1
82
- */
83
- public function addAdminMenuBarItem( array $items ) :array {
84
- return $items;
85
- }
86
-
87
  public function onWpLoaded() {
88
  ( new Lib\Snapshots\StoreAction\ScheduleBuildAll() )
89
  ->setMod( $this->getMod() )
77
  return $items;
78
  }
79
 
 
 
 
 
 
 
 
80
  public function onWpLoaded() {
81
  ( new Lib\Snapshots\StoreAction\ScheduleBuildAll() )
82
  ->setMod( $this->getMod() )
src/lib/src/Modules/HackGuard/Scan/Controller/Apc.php CHANGED
@@ -53,11 +53,4 @@ class Apc extends BaseForAssets {
53
  ->build()
54
  ->getScanActionVO();
55
  }
56
-
57
- /**
58
- * @deprecated 14.1
59
- */
60
- public function addAdminMenuBarItem( array $items ) :array {
61
- return $items;
62
- }
63
  }
53
  ->build()
54
  ->getScanActionVO();
55
  }
 
 
 
 
 
 
 
56
  }
src/lib/src/Modules/HackGuard/Scan/Controller/Base.php CHANGED
@@ -47,13 +47,6 @@ abstract class Base extends ExecOnceModConsumer {
47
  );
48
  }
49
 
50
- /**
51
- * @deprecated 14.1
52
- */
53
- public function addAdminMenuBarItem( array $items ) :array {
54
- return [];
55
- }
56
-
57
  public function getAdminMenuItems() :array {
58
  return [];
59
  }
47
  );
48
  }
49
 
 
 
 
 
 
 
 
50
  public function getAdminMenuItems() :array {
51
  return [];
52
  }
src/lib/src/Modules/HackGuard/Scan/Controller/Wpv.php CHANGED
@@ -33,13 +33,6 @@ class Wpv extends BaseForAssets {
33
  }
34
  }
35
 
36
- /**
37
- * @deprecated 14.1
38
- */
39
- public function addAdminMenuBarItem( array $items ) :array {
40
- return $items;
41
- }
42
-
43
  public function getAdminMenuItems() :array {
44
  $items = [];
45
  $status = $this->getScansController()->getScanResultsCount();
33
  }
34
  }
35
 
 
 
 
 
 
 
 
36
  public function getAdminMenuItems() :array {
37
  $items = [];
38
  $status = $this->getScansController()->getScanResultsCount();
src/lib/src/Modules/HackGuard/Strings.php CHANGED
@@ -120,7 +120,8 @@ class Strings extends Base\Strings {
120
  break;
121
 
122
  case 'section_scan_wpv' :
123
- $shortTitle = __( 'Vulnerabilities', 'wp-simple-firewall' );
 
124
  $title = __( 'Vulnerabilities Scanner', 'wp-simple-firewall' );
125
  $summary = [
126
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Regularly scan your WordPress plugins and themes for known security vulnerabilities.', 'wp-simple-firewall' ) ),
@@ -140,17 +141,6 @@ class Strings extends Base\Strings {
140
  ];
141
  break;
142
 
143
- case 'section_realtime' :
144
- $shortTitle = __( 'Realtime Change Detection', 'wp-simple-firewall' );
145
- $title = __( 'Realtime Change Detection', 'wp-simple-firewall' );
146
- $summary = [
147
- sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ),
148
- __( 'Monitor Your WordPress Site For Changes To Critical Components In Realtime.', 'wp-simple-firewall' ) ),
149
- sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ),
150
- sprintf( __( 'Enable The Realtime Change Detection Features.', 'wp-simple-firewall' ), $title ) )
151
- ];
152
- break;
153
-
154
  case 'section_scan_apc' :
155
  $title = __( 'Enable The Abandoned Plugin Scanner', 'wp-simple-firewall' );
156
  $shortTitle = __( 'Abandoned Plugins', 'wp-simple-firewall' );
@@ -318,15 +308,6 @@ class Strings extends Base\Strings {
318
  $desc = __( "Show links to re-install plugins and offer re-install when activating plugins.", 'wp-simple-firewall' );
319
  break;
320
 
321
- case 'auto_filter_results' :
322
- $name = __( 'Auto-Filter Results', 'wp-simple-firewall' );
323
- $summary = __( 'Automatically Filter Results Of Irrelevant Items', 'wp-simple-firewall' );
324
- $desc = [
325
- __( 'Automatically remove items from results that are irrelevant.', 'wp-simple-firewall' ),
326
- __( "An example of this is filtering out results for PHP files that don't have any executable code.", 'wp-simple-firewall' ),
327
- ];
328
- break;
329
-
330
  case 'scan_path_exclusions' :
331
  $name = __( 'Scan Exclusions', 'wp-simple-firewall' );
332
  $summary = __( 'Scan File And Folder Exclusions', 'wp-simple-firewall' );
@@ -370,8 +351,6 @@ class Strings extends Base\Strings {
370
 
371
  private function deprecated_strings() {
372
  // scan
373
- __( "Detect changes to core WordPress files when compared to the official distribution", 'wp-simple-firewall' );
374
- __( "Detect files which aren't part of the official WordPress.org distribution", 'wp-simple-firewall' );
375
  __( "Detect files that may be infected with malware", 'wp-simple-firewall' );
376
  __( '%s has detected abandoned plugins installed on your site.', 'wp-simple-firewall' );
377
  __( "Running code that hasn't seen any updates for over 2 years is far from ideal.", 'wp-simple-firewall' );
120
  break;
121
 
122
  case 'section_scan_wpv' :
123
+ $shortTitle = sprintf( '%s, %s, %s', __( 'Vulnerabilities', 'wp-simple-firewall' ),
124
+ __( 'Plugins', 'wp-simple-firewall' ), __( 'Themes', 'wp-simple-firewall' ) );
125
  $title = __( 'Vulnerabilities Scanner', 'wp-simple-firewall' );
126
  $summary = [
127
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Regularly scan your WordPress plugins and themes for known security vulnerabilities.', 'wp-simple-firewall' ) ),
141
  ];
142
  break;
143
 
 
 
 
 
 
 
 
 
 
 
 
144
  case 'section_scan_apc' :
145
  $title = __( 'Enable The Abandoned Plugin Scanner', 'wp-simple-firewall' );
146
  $shortTitle = __( 'Abandoned Plugins', 'wp-simple-firewall' );
308
  $desc = __( "Show links to re-install plugins and offer re-install when activating plugins.", 'wp-simple-firewall' );
309
  break;
310
 
 
 
 
 
 
 
 
 
 
311
  case 'scan_path_exclusions' :
312
  $name = __( 'Scan Exclusions', 'wp-simple-firewall' );
313
  $summary = __( 'Scan File And Folder Exclusions', 'wp-simple-firewall' );
351
 
352
  private function deprecated_strings() {
353
  // scan
 
 
354
  __( "Detect files that may be infected with malware", 'wp-simple-firewall' );
355
  __( '%s has detected abandoned plugins installed on your site.', 'wp-simple-firewall' );
356
  __( "Running code that hasn't seen any updates for over 2 years is far from ideal.", 'wp-simple-firewall' );
src/lib/src/Modules/HackGuard/UI.php CHANGED
@@ -40,7 +40,6 @@ class UI extends BaseShield\UI {
40
  ],
41
  'strings' => [
42
  'never' => __( 'Never', 'wp-simple-firewall' ),
43
- 'not_available' => __( 'Sorry, this scan is not available.', 'wp-simple-firewall' ),
44
  'not_enabled' => __( 'This scan is not currently enabled.', 'wp-simple-firewall' ),
45
  'please_enable' => __( 'Please turn on this scan in the options.', 'wp-simple-firewall' ),
46
  'title_scan_now' => __( 'Scan Your Site Now', 'wp-simple-firewall' ),
@@ -208,8 +207,8 @@ class UI extends BaseShield\UI {
208
  'is_restricted' => !$this->getCon()->isPremiumActive(),
209
  ],
210
  'hrefs' => [
211
- 'options' => $mod->getUrl_DirectLinkToSection( 'section_realtime' ),
212
- 'please_enable' => $mod->getUrl_DirectLinkToSection( 'section_realtime' ),
213
  ],
214
  'vars' => [
215
  'file_locks' => [
@@ -228,17 +227,23 @@ class UI extends BaseShield\UI {
228
  }
229
 
230
  public function getSectionWarnings( string $section ) :array {
 
231
  $warnings = [];
232
 
233
  switch ( $section ) {
234
 
235
- case 'section_realtime':
236
- $canHandshake = $this->getCon()
237
- ->getModule_Plugin()
238
- ->getShieldNetApiController()
239
- ->canHandshake();
240
- if ( !$canHandshake ) {
241
- $warnings[] = sprintf( __( 'Not available as your site cannot handshake with ShieldNET API.', 'wp-simple-firewall' ), 'OpenSSL' );
 
 
 
 
 
242
  }
243
  // if ( !Services::Encrypt()->isSupportedOpenSslDataEncryption() ) {
244
  // $warnings[] = sprintf( __( 'Not available because the %s extension is not available.', 'wp-simple-firewall' ), 'OpenSSL' );
@@ -247,12 +252,6 @@ class UI extends BaseShield\UI {
247
  // $warnings[] = sprintf( __( "Not available because PHP/WordPress doesn't have direct filesystem access.", 'wp-simple-firewall' ), 'OpenSSL' );
248
  // }
249
  break;
250
-
251
- case 'section_file_guard':
252
- if ( !$this->getCon()->cache_dir_handler->dirExists() ) {
253
- $warnings[] = __( "Plugin/Theme file scanners are unavailable because we couldn't create a temporary directory to store files.", 'wp-simple-firewall' );
254
- }
255
- break;
256
  }
257
 
258
  return $warnings;
40
  ],
41
  'strings' => [
42
  'never' => __( 'Never', 'wp-simple-firewall' ),
 
43
  'not_enabled' => __( 'This scan is not currently enabled.', 'wp-simple-firewall' ),
44
  'please_enable' => __( 'Please turn on this scan in the options.', 'wp-simple-firewall' ),
45
  'title_scan_now' => __( 'Scan Your Site Now', 'wp-simple-firewall' ),
207
  'is_restricted' => !$this->getCon()->isPremiumActive(),
208
  ],
209
  'hrefs' => [
210
+ 'options' => $mod->getUrl_DirectLinkToOption( 'file_locker' ),
211
+ 'please_enable' => $mod->getUrl_DirectLinkToOption( 'file_locker' ),
212
  ],
213
  'vars' => [
214
  'file_locks' => [
227
  }
228
 
229
  public function getSectionWarnings( string $section ) :array {
230
+ $con = $this->getCon();
231
  $warnings = [];
232
 
233
  switch ( $section ) {
234
 
235
+ case 'section_file_guard':
236
+ if ( !$this->getCon()->cache_dir_handler->dirExists() ) {
237
+ $warnings[] = __( "Plugin/Theme file scanners are unavailable because we couldn't create a temporary directory to store files.", 'wp-simple-firewall' );
238
+ }
239
+
240
+ if ( $con->isPremiumActive() ) {
241
+ $canHandshake = $con->getModule_Plugin()
242
+ ->getShieldNetApiController()
243
+ ->canHandshake();
244
+ if ( !$canHandshake ) {
245
+ $warnings[] = sprintf( __( 'Not available as your site cannot handshake with ShieldNET API.', 'wp-simple-firewall' ), 'OpenSSL' );
246
+ }
247
  }
248
  // if ( !Services::Encrypt()->isSupportedOpenSslDataEncryption() ) {
249
  // $warnings[] = sprintf( __( 'Not available because the %s extension is not available.', 'wp-simple-firewall' ), 'OpenSSL' );
252
  // $warnings[] = sprintf( __( "Not available because PHP/WordPress doesn't have direct filesystem access.", 'wp-simple-firewall' ), 'OpenSSL' );
253
  // }
254
  break;
 
 
 
 
 
 
255
  }
256
 
257
  return $warnings;
src/lib/src/Modules/Headers/Insights/OverviewCards.php DELETED
@@ -1,50 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Headers\Insights;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Headers\Options;
7
-
8
- class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
9
-
10
- protected function buildModCards() :array {
11
- /** @var Shield\Modules\Headers\ModCon $mod */
12
- $mod = $this->getMod();
13
- /** @var Options $opts */
14
- $opts = $this->getOptions();
15
-
16
- $cards = [];
17
-
18
- if ( $mod->isModOptEnabled() ) {
19
- $bAllEnabled = $opts->isEnabledXFrame() && $opts->isEnabledXssProtection()
20
- && $opts->isEnabledContentTypeHeader() && $opts->isReferrerPolicyEnabled();
21
- $cards[ 'all' ] = [
22
- 'name' => __( 'HTTP Headers', 'wp-simple-firewall' ),
23
- 'state' => $bAllEnabled ? 1 : -1,
24
- 'summary' => $bAllEnabled ?
25
- __( 'All important security Headers have been set', 'wp-simple-firewall' )
26
- : __( "At least one of the HTTP Headers hasn't been set", 'wp-simple-firewall' ),
27
- 'href' => $mod->getUrl_DirectLinkToSection( 'section_security_headers' ),
28
- ];
29
-
30
- $cards[ 'csp' ] = [
31
- 'name' => __( 'Content Security Policies', 'wp-simple-firewall' ),
32
- 'state' => $opts->isEnabledContentSecurityPolicy() ? 1 : 0,
33
- 'summary' => $opts->isEnabledContentSecurityPolicy() ?
34
- __( 'Content Security Policy is turned on', 'wp-simple-firewall' )
35
- : __( "Content Security Policies aren't active or there are no rules provided", 'wp-simple-firewall' ),
36
- 'href' => $mod->getUrl_DirectLinkToSection( 'section_content_security_policy' ),
37
- ];
38
- }
39
-
40
- return $cards;
41
- }
42
-
43
- protected function getSectionTitle() :string {
44
- return __( 'HTTP Security Headers', 'wp-simple-firewall' );
45
- }
46
-
47
- protected function getSectionSubTitle() :string {
48
- return __( 'Protect Visitors With Powerful HTTP Headers', 'wp-simple-firewall' );
49
- }
50
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/IPs/AdminNotices.php CHANGED
@@ -44,13 +44,10 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
44
  }
45
 
46
  protected function isDisplayNeeded( NoticeVO $notice ) :bool {
47
- /** @var ModCon $mod */
48
- $mod = $this->getMod();
49
-
50
  switch ( $notice->id ) {
51
 
52
  case 'visitor-whitelisted':
53
- $needed = $mod->isVisitorWhitelisted();
54
  break;
55
 
56
  default:
44
  }
45
 
46
  protected function isDisplayNeeded( NoticeVO $notice ) :bool {
 
 
 
47
  switch ( $notice->id ) {
48
 
49
  case 'visitor-whitelisted':
50
+ $needed = $this->getCon()->this_req->is_ip_whitelisted;
51
  break;
52
 
53
  default:
src/lib/src/Modules/IPs/AjaxHandler.php CHANGED
@@ -17,12 +17,13 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
17
  ] );
18
  if ( $isAuth ) {
19
  $map = array_merge( $map, [
20
- 'ip_insert' => [ $this, 'ajaxExec_AddIp' ],
21
- 'ip_delete' => [ $this, 'ajaxExec_IpDelete' ],
22
- 'render_table_ip' => [ $this, 'ajaxExec_BuildTableIps' ],
23
- 'ip_analyse_build' => [ $this, 'ajaxExec_BuildIpAnalyse' ],
24
- 'ip_analyse_action' => [ $this, 'ajaxExec_IpAnalyseAction' ],
25
- 'ip_review_select' => [ $this, 'ajaxExec_IpReviewSelect' ],
 
26
  ] );
27
  }
28
  return $map;
@@ -168,7 +169,7 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
168
  public function ajaxExec_IpReviewSelect() :array {
169
  $req = Services::Request();
170
 
171
- $filter = preg_replace( '#[^0-9a-f:.]#', '', strtolower( (string)$req->post( 'search' ) ) );
172
  $ips = ( new FindAllPluginIps() )
173
  ->setCon( $this->getCon() )
174
  ->run( $filter );
@@ -186,6 +187,26 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
186
  ];
187
  }
188
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  public function ajaxExec_IpAnalyseAction() :array {
190
  $req = Services::Request();
191
 
17
  ] );
18
  if ( $isAuth ) {
19
  $map = array_merge( $map, [
20
+ 'ip_insert' => [ $this, 'ajaxExec_AddIp' ],
21
+ 'ip_delete' => [ $this, 'ajaxExec_IpDelete' ],
22
+ 'render_table_ip' => [ $this, 'ajaxExec_BuildTableIps' ],
23
+ 'ip_analyse_build' => [ $this, 'ajaxExec_BuildIpAnalyse' ],
24
+ 'ip_analyse_action' => [ $this, 'ajaxExec_IpAnalyseAction' ],
25
+ 'ip_review_select' => [ $this, 'ajaxExec_IpReviewSelect' ],
26
+ 'render_ip_analysis' => [ $this, 'ajaxExec_RenderIpAnalysis' ],
27
  ] );
28
  }
29
  return $map;
169
  public function ajaxExec_IpReviewSelect() :array {
170
  $req = Services::Request();
171
 
172
+ $filter = preg_replace( '#[^\da-f:.]#', '', strtolower( (string)$req->post( 'search' ) ) );
173
  $ips = ( new FindAllPluginIps() )
174
  ->setCon( $this->getCon() )
175
  ->run( $filter );
187
  ];
188
  }
189
 
190
+ public function ajaxExec_RenderIpAnalysis() :array {
191
+ $data = [
192
+ 'success' => false,
193
+ 'title' => __( "Couldn't Build IP Analysis", 'wp-simple-firewall' ),
194
+ 'body' => __( "Couldn't Build IP Analysis", 'wp-simple-firewall' ),
195
+ ];
196
+ try {
197
+ $ip = Services::Request()->post( 'ip', '' );
198
+ $data[ 'title' ] = sprintf( '%s: %s', __( 'IP Analysis', 'wp-simple-firewall' ), $ip );
199
+ $data[ 'body' ] = ( new Shield\Modules\IPs\Lib\IpAnalyse\BuildDisplay() )
200
+ ->setMod( $this->getMod() )
201
+ ->setIP( $ip )
202
+ ->run();
203
+ }
204
+ catch ( \Exception $e ) {
205
+ $data[ 'body' ] = $e->getMessage();
206
+ }
207
+ return $data;
208
+ }
209
+
210
  public function ajaxExec_IpAnalyseAction() :array {
211
  $req = Services::Request();
212
 
src/lib/src/Modules/IPs/BotTrack/Track404.php DELETED
@@ -1,74 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\BotTrack;
4
-
5
- use FernleafSystems\Wordpress\Services\Services;
6
-
7
- class Track404 extends Base {
8
-
9
- const OPT_KEY = 'track_404';
10
-
11
- protected function process() {
12
- add_action( 'template_redirect', function () {
13
- if ( is_404() ) {
14
- $reqPath = $this->getRequestPath();
15
- $extensions = implode( '|', $this->getAllowableExtensions() );
16
-
17
- // if the request's file extension is allowed to trigger 404s, we fire only the event, without transgression.
18
- // However, if the requested asset is within for a plugin or theme that doesn't exists, it's not allowed.
19
- $allowed = preg_match( sprintf( '#\.(%s)$#i', $extensions ), $reqPath ) === 1
20
- && !$this->isRequestToInvalidPlugin()
21
- && !$this->isRequestToInvalidTheme();
22
-
23
- $this->doTransgression( $allowed );
24
- }
25
- } );
26
- }
27
-
28
- private function isRequestToInvalidPlugin() :bool {
29
- $isInvalid = false;
30
-
31
- $reqPath = $this->getRequestPath();
32
- $pathToPlugins = ltrim( wp_parse_url( plugins_url(), PHP_URL_PATH ), '/' );
33
-
34
- if ( strpos( $reqPath, $pathToPlugins ) === 0 ) {
35
- $assetStub = trim( str_replace( $pathToPlugins, '', $reqPath ), '/' );
36
- if ( substr_count( $assetStub, '/' ) > 0 ) {
37
- $dir = explode( '/', $assetStub, 2 )[ 0 ];
38
- $file = Services::WpPlugins()->findPluginFileFromDirName( $dir );
39
- if ( empty( $file ) ) {
40
- $isInvalid = true;
41
- }
42
- }
43
- }
44
-
45
- return $isInvalid;
46
- }
47
-
48
- private function isRequestToInvalidTheme() :bool {
49
- $isInvalid = false;
50
-
51
- $reqPath = $this->getRequestPath();
52
- $pathsToThemes = ltrim( dirname( wp_parse_url( get_stylesheet_directory_uri(), PHP_URL_PATH ) ), '/' );
53
-
54
- if ( strpos( $reqPath, $pathsToThemes ) === 0 ) {
55
- $assetStub = trim( str_replace( $pathsToThemes, '', $reqPath ), '/' );
56
- if ( substr_count( $assetStub, '/' ) > 0 ) {
57
- $dir = explode( '/', $assetStub, 2 )[ 0 ];
58
- $isInvalid = !Services::WpThemes()->getExists( $dir );
59
- }
60
- }
61
-
62
- return $isInvalid;
63
- }
64
-
65
- private function getAllowableExtensions() :array {
66
- $defExts = $this->getOptions()->getDef( 'allowable_ext_404s' );
67
- $extensions = apply_filters( 'shield/allowable_extensions_404s', $defExts );
68
- return is_array( $extensions ) ? $extensions : $defExts;
69
- }
70
-
71
- private function getRequestPath() :string {
72
- return ltrim( Services::Request()->getPath(), '/' );
73
- }
74
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/IPs/BotTrack/TrackFakeWebCrawler.php DELETED
@@ -1,44 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\BotTrack;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\ModCon;
6
- use FernleafSystems\Wordpress\Services\Services;
7
-
8
- class TrackFakeWebCrawler extends Base {
9
-
10
- const OPT_KEY = 'track_fakewebcrawler';
11
-
12
- private $crawlerUsed = '';
13
-
14
- protected function process() {
15
- /** @var ModCon $mod */
16
- $mod = $this->getMod();
17
- if ( $this->identifiesAsCrawler() && !$mod->isTrustedVerifiedBot() ) {
18
- $this->doTransgression();
19
- }
20
- }
21
-
22
- private function identifiesAsCrawler() :bool {
23
- $identifiesAsCrawler = false;
24
-
25
- $userAgent = Services::Request()->getUserAgent();
26
- if ( !empty( $userAgent ) ) {
27
- foreach ( Services::ServiceProviders()->getAllCrawlerUseragents() as $possibleAgent ) {
28
- if ( stripos( $userAgent, $possibleAgent ) !== false ) {
29
- $identifiesAsCrawler = true;
30
- $this->crawlerUsed = $possibleAgent;
31
- break;
32
- }
33
- }
34
- }
35
-
36
- return $identifiesAsCrawler;
37
- }
38
-
39
- protected function getAuditData() :array {
40
- return array_merge( parent::getAuditData(), [
41
- 'crawler' => $this->crawlerUsed
42
- ] );
43
- }
44
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/IPs/BotTrack/TrackInvalidScriptLoad.php DELETED
@@ -1,58 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\BotTrack;
4
-
5
- use FernleafSystems\Wordpress\Services\Services;
6
-
7
- class TrackInvalidScriptLoad extends Base {
8
-
9
- const OPT_KEY = 'track_invalidscript';
10
-
11
- private $script = null;
12
-
13
- protected function process() {
14
- $this->testScript();
15
- }
16
-
17
- private function testScript() {
18
- $req = Services::Request();
19
-
20
- // For the moment we only handle the actual file name itself.
21
- $scripts = array_unique( array_map( 'basename', array_filter( [
22
- $req->server( 'SCRIPT_NAME' ),
23
- $req->server( 'SCRIPT_FILENAME' ),
24
- $req->server( 'PHP_SELF' )
25
- ] ) ) );
26
- // There should only ever be 1. More than 1 means a strange configuration which we wont touch.
27
- if ( count( $scripts ) === 1 ) {
28
- $script = array_shift( $scripts );
29
- if ( !in_array( $script, $this->getAllowedScripts() ) ) {
30
- $this->script = $script;
31
- $this->doTransgression();
32
- }
33
- }
34
- }
35
-
36
- protected function getAllowedScripts() :array {
37
- return [
38
- 'index.php',
39
- 'admin-ajax.php',
40
- 'wp-activate.php',
41
- 'wp-links-opml.php',
42
- 'wp-cron.php',
43
- 'wp-login.php',
44
- 'wp-mail.php',
45
- 'wp-comments-post.php',
46
- 'wp-signup.php',
47
- 'wp-trackback.php',
48
- 'xmlrpc.php',
49
- 'admin.php',
50
- ];
51
- }
52
-
53
- protected function getAuditData() :array {
54
- return [
55
- 'script' => $this->script
56
- ];
57
- }
58
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/IPs/BotTrack/TrackLinkCheese.php CHANGED
@@ -53,7 +53,7 @@ class TrackLinkCheese extends Base {
53
  $reqPath = trim( (string)Services::Request()->getPath(), '/' );
54
  $isCheese = ( $reqPath ===
55
  trim( (string)parse_url( $WP->getHomeUrl( $this->getCheeseWord() ), PHP_URL_PATH ), '/' ) )
56
- || preg_match( '#icwp-wpsf-[a-z]+-[a-z0-9]{7,9}#', $reqPath ) > 0;
57
  /** TODO: 10.3 legacy remove */
58
  }
59
  else {
53
  $reqPath = trim( (string)Services::Request()->getPath(), '/' );
54
  $isCheese = ( $reqPath ===
55
  trim( (string)parse_url( $WP->getHomeUrl( $this->getCheeseWord() ), PHP_URL_PATH ), '/' ) )
56
+ || preg_match( '#icwp-wpsf-[a-z]+-[a-z\d]{7,9}#', $reqPath ) > 0;
57
  /** TODO: 10.3 legacy remove */
58
  }
59
  else {
src/lib/src/Modules/IPs/BotTrack/TrackXmlRpc.php DELETED
@@ -1,17 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\BotTrack;
4
-
5
- use FernleafSystems\Wordpress\Services\Services;
6
-
7
- class TrackXmlRpc extends Base {
8
-
9
- const OPT_KEY = 'track_xmlrpc';
10
-
11
- protected function process() {
12
- if ( Services::WpGeneral()->isXmlrpc()
13
- || preg_match( '#/xmlrpc\.php#', Services::Request()->getPath() ) ) {
14
- $this->doTransgression();
15
- }
16
- }
17
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/IPs/Components/ImportIpsFromFile.php CHANGED
@@ -8,6 +8,10 @@ use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class ImportIpsFromFile extends Shield\Modules\Base\Common\ExecOnceModConsumer {
10
 
 
 
 
 
11
  protected function run() {
12
  foreach ( [ 'black', 'white', 'block', 'bypass' ] as $type ) {
13
  $this->runFileImport( $type );
8
 
9
  class ImportIpsFromFile extends Shield\Modules\Base\Common\ExecOnceModConsumer {
10
 
11
+ protected function canRun() :bool {
12
+ return $this->getCon()->isPremiumActive();
13
+ }
14
+
15
  protected function run() {
16
  foreach ( [ 'black', 'white', 'block', 'bypass' ] as $type ) {
17
  $this->runFileImport( $type );
src/lib/src/Modules/IPs/Components/ProcessOffense.php CHANGED
@@ -6,9 +6,6 @@ use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
8
 
9
- /**
10
- * NOT IMPLEMENTED
11
- */
12
  class ProcessOffense {
13
 
14
  use Shield\Modules\ModConsumer;
@@ -39,6 +36,10 @@ class ProcessOffense {
39
  $toBlock = $offenseTracker->isBlocked() ||
40
  ( $IP->blocked_at == 0 && ( $newCount >= $opts->getOffenseLimit() ) );
41
 
 
 
 
 
42
  /** @var Databases\IPs\Update $updater */
43
  $updater = $mod->getDbHandler_IPs()->getQueryUpdater();
44
  $updater->updateTransgressions( $IP, $newCount );
6
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
8
 
 
 
 
9
  class ProcessOffense {
10
 
11
  use Shield\Modules\ModConsumer;
36
  $toBlock = $offenseTracker->isBlocked() ||
37
  ( $IP->blocked_at == 0 && ( $newCount >= $opts->getOffenseLimit() ) );
38
 
39
+ if ( $toBlock ) {
40
+ $newCount = (int)max( 1, $newCount ); // Ensure there's an offense registered for immediate blocks
41
+ }
42
+
43
  /** @var Databases\IPs\Update $updater */
44
  $updater = $mod->getDbHandler_IPs()->getQueryUpdater();
45
  $updater->updateTransgressions( $IP, $newCount );
src/lib/src/Modules/IPs/Components/QueryIpBlock.php CHANGED
@@ -15,8 +15,8 @@ class QueryIpBlock {
15
  public function run() :bool {
16
  $isBlocked = false;
17
 
18
- $oIP = $this->getBlockedIpRecord();
19
- if ( $oIP instanceof Databases\IPs\EntryVO ) {
20
 
21
  $isBlocked = true;
22
 
@@ -24,7 +24,7 @@ class QueryIpBlock {
24
  $mod = $this->getMod();
25
  /** @var Databases\IPs\Update $upd */
26
  $upd = $mod->getDbHandler_IPs()->getQueryUpdater();
27
- $upd->updateLastAccessAt( $oIP );
28
  }
29
  return $isBlocked;
30
  }
@@ -33,24 +33,24 @@ class QueryIpBlock {
33
  * @return Databases\IPs\EntryVO|null
34
  */
35
  private function getBlockedIpRecord() {
36
- $oBlockIP = null;
37
 
38
  /** @var IPs\ModCon $mod */
39
  $mod = $this->getMod();
40
- $oIP = ( new IPs\Lib\Ops\LookupIpOnList() )
41
  ->setDbHandler( $mod->getDbHandler_IPs() )
42
  ->setIP( $this->getIP() )
43
  ->setListTypeBlock()
44
  ->setIsIpBlocked( true )
45
  ->lookup();
46
 
47
- if ( $oIP instanceof Databases\IPs\EntryVO ) {
48
- /** @var IPs\Options $oOpts */
49
- $oOpts = $this->getOptions();
50
 
51
  // Clean out old IPs as we go so they don't show up in future queries.
52
- if ( $oIP->list == $mod::LIST_AUTO_BLACK
53
- && $oIP->last_access_at < Services::Request()->ts() - $oOpts->getAutoExpireTime() ) {
54
 
55
  ( new IPs\Lib\Ops\DeleteIp() )
56
  ->setMod( $mod )
@@ -58,10 +58,10 @@ class QueryIpBlock {
58
  ->fromBlacklist();
59
  }
60
  else {
61
- $oBlockIP = $oIP;
62
  }
63
  }
64
 
65
- return $oBlockIP;
66
  }
67
  }
15
  public function run() :bool {
16
  $isBlocked = false;
17
 
18
+ $IP = $this->getBlockedIpRecord();
19
+ if ( !empty( $IP ) ) {
20
 
21
  $isBlocked = true;
22
 
24
  $mod = $this->getMod();
25
  /** @var Databases\IPs\Update $upd */
26
  $upd = $mod->getDbHandler_IPs()->getQueryUpdater();
27
+ $upd->updateLastAccessAt( $IP );
28
  }
29
  return $isBlocked;
30
  }
33
  * @return Databases\IPs\EntryVO|null
34
  */
35
  private function getBlockedIpRecord() {
36
+ $blockIP = null;
37
 
38
  /** @var IPs\ModCon $mod */
39
  $mod = $this->getMod();
40
+ $IP = ( new IPs\Lib\Ops\LookupIpOnList() )
41
  ->setDbHandler( $mod->getDbHandler_IPs() )
42
  ->setIP( $this->getIP() )
43
  ->setListTypeBlock()
44
  ->setIsIpBlocked( true )
45
  ->lookup();
46
 
47
+ if ( !empty( $IP ) ) {
48
+ /** @var IPs\Options $opts */
49
+ $opts = $this->getOptions();
50
 
51
  // Clean out old IPs as we go so they don't show up in future queries.
52
+ if ( $IP->list == $mod::LIST_AUTO_BLACK
53
+ && $IP->last_access_at < Services::Request()->ts() - $opts->getAutoExpireTime() ) {
54
 
55
  ( new IPs\Lib\Ops\DeleteIp() )
56
  ->setMod( $mod )
58
  ->fromBlacklist();
59
  }
60
  else {
61
+ $blockIP = $IP;
62
  }
63
  }
64
 
65
+ return $blockIP;
66
  }
67
  }
src/lib/src/Modules/IPs/Components/UnblockIpByFlag.php CHANGED
@@ -6,31 +6,47 @@ use FernleafSystems\Wordpress\Plugin\Shield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
- class UnblockIpByFlag {
10
 
11
  use Shield\Modules\ModConsumer;
12
 
13
- public function run() {
 
 
 
 
14
  /** @var IPs\ModCon $mod */
15
  $mod = $this->getMod();
16
  $FS = Services::WpFs();
 
 
 
17
 
18
  $path = $FS->findFileInDir( 'unblock', $this->getCon()->paths->forFlag() );
19
  if ( !empty( $path ) && $FS->isFile( $path ) ) {
20
  $content = $FS->getFileContent( $path );
21
  if ( !empty( $content ) ) {
22
-
23
  foreach ( array_map( 'trim', explode( "\n", $content ) ) as $ip ) {
24
  $removed = ( new IPs\Lib\Ops\DeleteIp() )
25
  ->setMod( $mod )
26
  ->setIP( $ip )
27
  ->fromBlacklist();
28
  if ( $removed ) {
 
29
  $this->getCon()->fireEvent( 'ip_unblock_flag', [ 'audit_params' => [ 'ip' => $ip ] ] );
30
  }
31
  }
32
  }
33
  $FS->deleteFile( $path );
34
  }
 
 
 
 
 
 
 
 
 
35
  }
36
  }
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
+ class UnblockIpByFlag extends Shield\Modules\Base\Common\ExecOnceModConsumer {
10
 
11
  use Shield\Modules\ModConsumer;
12
 
13
+ protected function canRun() :bool {
14
+ return !empty( Services::WpFs()->findFileInDir( 'unblock', $this->getCon()->paths->forFlag() ) );
15
+ }
16
+
17
+ protected function run() {
18
  /** @var IPs\ModCon $mod */
19
  $mod = $this->getMod();
20
  $FS = Services::WpFs();
21
+ $srvIP = Services::IP();
22
+
23
+ $IPs = [];
24
 
25
  $path = $FS->findFileInDir( 'unblock', $this->getCon()->paths->forFlag() );
26
  if ( !empty( $path ) && $FS->isFile( $path ) ) {
27
  $content = $FS->getFileContent( $path );
28
  if ( !empty( $content ) ) {
 
29
  foreach ( array_map( 'trim', explode( "\n", $content ) ) as $ip ) {
30
  $removed = ( new IPs\Lib\Ops\DeleteIp() )
31
  ->setMod( $mod )
32
  ->setIP( $ip )
33
  ->fromBlacklist();
34
  if ( $removed ) {
35
+ $IPs[] = $ip;
36
  $this->getCon()->fireEvent( 'ip_unblock_flag', [ 'audit_params' => [ 'ip' => $ip ] ] );
37
  }
38
  }
39
  }
40
  $FS->deleteFile( $path );
41
  }
42
+
43
+ try {
44
+ $myIP = $srvIP->getRequestIp();
45
+ if ( !empty( $IPs ) && !empty( $myIP ) && $srvIP->checkIp( $myIP, $IPs ) ) {
46
+ Services::Response()->redirectHere();
47
+ }
48
+ }
49
+ catch ( \Exception $e ) {
50
+ }
51
  }
52
  }
src/lib/src/Modules/IPs/DB/BotSignal/Ops/Record.php CHANGED
@@ -11,6 +11,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\DB\BotSignal\Ops;
11
  * @property int $btcheese_at
12
  * @property int $btfake_at
13
  * @property int $btinvalidscript_at
 
14
  * @property int $btloginfail_at
15
  * @property int $btlogininvalid_at
16
  * @property int $btua_at
@@ -32,4 +33,13 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\DB\BotSignal\Ops;
32
  */
33
  class Record extends \FernleafSystems\Wordpress\Plugin\Core\Databases\Base\Record {
34
 
 
 
 
 
 
 
 
 
 
35
  }
11
  * @property int $btcheese_at
12
  * @property int $btfake_at
13
  * @property int $btinvalidscript_at
14
+ * @property int $btauthorfishing_at
15
  * @property int $btloginfail_at
16
  * @property int $btlogininvalid_at
17
  * @property int $btua_at
33
  */
34
  class Record extends \FernleafSystems\Wordpress\Plugin\Core\Databases\Base\Record {
35
 
36
+ public function __get( string $key ) {
37
+ $value = parent::__get( $key );
38
+
39
+ if ( $key === 'ip_ref' ) {
40
+ $value = (int)$value;
41
+ }
42
+
43
+ return $value;
44
+ }
45
  }
src/lib/src/Modules/IPs/Insights/OverviewCards.php DELETED
@@ -1,9 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Insights;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
-
7
- class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
8
-
9
- }
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/IPs/Lib/AutoUnblock.php CHANGED
@@ -1,17 +1,18 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
- class AutoUnblock {
10
 
11
- use ModConsumer;
 
 
12
 
13
  /**
14
- * This should only be run if the current IP has been verified as being blocked
15
  * @return bool - true if IP is unblocked, false otherwise.
16
  */
17
  public function run() :bool {
@@ -28,11 +29,15 @@ class AutoUnblock {
28
  catch ( \Exception $e ) {
29
  }
30
  }
 
 
 
 
 
31
  return $unblocked;
32
  }
33
 
34
  /**
35
- * @return bool
36
  * @throws \Exception
37
  */
38
  private function processAutoUnblockRequest() :bool {
@@ -44,21 +49,22 @@ class AutoUnblock {
44
 
45
  $unblocked = false;
46
 
47
- if ( $opts->isEnabledAutoVisitorRecover() && $req->isPost()
48
- && $req->request( 'action' ) == $mod->prefix() && $req->request( 'exec' ) == 'uau' ) {
 
 
49
 
50
- if ( check_admin_referer( $req->request( 'exec' ), 'exec_nonce' ) !== 1 ) {
 
 
 
 
51
  throw new \Exception( 'Nonce failed' );
52
  }
53
- if ( strlen( $req->post( 'icwp_wpsf_login_email' ) ) > 0 ) {
54
  throw new \Exception( 'Email should not be provided in honeypot' );
55
  }
56
 
57
- $ip = Services::IP()->getRequestIp();
58
- if ( empty( $ip ) || $req->post( 'ip' ) !== Services::IP()->getRequestIp() ) {
59
- throw new \Exception( 'IP does not match' );
60
- }
61
-
62
  if ( !$opts->getCanIpRequestAutoUnblock( $ip ) ) {
63
  throw new \Exception( 'IP already processed in the last 1hr' );
64
  }
@@ -90,7 +96,6 @@ class AutoUnblock {
90
  }
91
 
92
  /**
93
- * @return bool
94
  * @throws \Exception
95
  */
96
  private function processUserMagicLink() :bool {
@@ -103,7 +108,7 @@ class AutoUnblock {
103
  $unblocked = false;
104
 
105
  if ( $opts->isEnabledMagicEmailLinkRecover()
106
- && $req->query( 'action' ) == $mod->prefix()
107
  && strpos( $req->query( 'exec' ), 'uaum-' ) === 0 ) {
108
 
109
  if ( check_admin_referer( $req->request( 'exec' ), 'exec_nonce' ) !== 1 ) {
@@ -142,7 +147,8 @@ class AutoUnblock {
142
  } )
143
  );
144
  }
145
- wp_die( 'Email sent.' );
 
146
  }
147
  elseif ( $linkParts[ 1 ] === 'go' ) {
148
  ( new IPs\Lib\Ops\DeleteIp() )
@@ -171,45 +177,44 @@ class AutoUnblock {
171
  $mod = $this->getMod();
172
  $user = Services::WpUsers()->getCurrentWpUser();
173
 
174
- $mod->getEmailProcessor()
175
- ->sendEmailWithTemplate(
176
- '/email/uaum_init',
177
- $user->user_email,
178
- __( 'Automatic IP Unblock Request', 'wp-simple-firewall' ),
179
- [
180
- 'flags' => [
181
- 'show_login_link' => !$this->getCon()->isRelabelled()
182
- ],
183
- 'vars' => [
184
- ],
185
- 'hrefs' => [
186
- 'unblock' => add_query_arg(
187
- array_merge(
188
- $mod->getNonceActionData( 'uaum-go-'.substr( sha1( $user->user_login ), 0, 6 ) ),
189
- [
190
- 'ip' => Services::IP()->getRequestIp()
191
- ]
192
- ),
193
- Services::WpGeneral()->getHomeUrl()
194
- )
195
- ],
196
- 'strings' => [
197
- 'looks_like' => __( "It looks like you've been blocked and have clicked to have your IP address removed from the blocklist.", 'wp-simple-firewall' ),
198
- 'please_click' => __( 'Please click the link provided below to do so.', 'wp-simple-firewall' ),
199
- 'details' => __( 'Details', 'wp-simple-firewall' ),
200
- 'unblock_my_ip' => sprintf( '%s: %s',
201
- __( 'Unblock My IP', 'wp-simple-firewall' ), Services::IP()->getRequestIp() ),
202
- 'or_copy' => __( 'Or Copy-Paste', 'wp-simple-firewall' ),
203
- 'details_url' => sprintf( '%s: %s',
204
- __( 'URL', 'wp-simple-firewall' ), Services::WpGeneral()->getHomeUrl() ),
205
- 'details_username' => sprintf( '%s: %s', __( 'Username', 'wp-simple-firewall' ), $user->user_login ),
206
- 'details_ip' => sprintf( '%s: %s', __( 'IP Address', 'wp-simple-firewall' ), Services::IP()
207
- ->getRequestIp() ),
208
- 'important' => __( 'Important', 'wp-simple-firewall' ),
209
- 'imp_limit' => __( "You'll need to wait for a further 60 minutes if your IP address gets blocked again.", 'wp-simple-firewall' ),
210
- 'imp_browser' => __( "This link will ONLY work if it opens in the same web browser that you used to request this email.", 'wp-simple-firewall' ),
211
- ]
212
  ]
213
- );
 
214
  }
215
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Common\ExecOnceModConsumer;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
 
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
+ class AutoUnblock extends ExecOnceModConsumer {
10
 
11
+ protected function canRun() :bool {
12
+ return $this->getCon()->this_req->is_ip_blocked;
13
+ }
14
 
15
  /**
 
16
  * @return bool - true if IP is unblocked, false otherwise.
17
  */
18
  public function run() :bool {
29
  catch ( \Exception $e ) {
30
  }
31
  }
32
+
33
+ if ( $unblocked ) {
34
+ Services::Response()->redirectToHome();
35
+ }
36
+
37
  return $unblocked;
38
  }
39
 
40
  /**
 
41
  * @throws \Exception
42
  */
43
  private function processAutoUnblockRequest() :bool {
49
 
50
  $unblocked = false;
51
 
52
+ $ip = Services::IP()->getRequestIp();
53
+ if ( empty( $ip ) ) {
54
+ throw new \Exception( 'No IP' );
55
+ }
56
 
57
+ if ( $opts->isEnabledAutoVisitorRecover()
58
+ && $req->post( 'action' ) == $mod->getCon()->prefix()
59
+ && $req->post( 'exec' ) == 'uau-'.$ip ) {
60
+
61
+ if ( check_admin_referer( 'uau-'.$ip, 'exec_nonce' ) !== 1 ) {
62
  throw new \Exception( 'Nonce failed' );
63
  }
64
+ if ( strlen( (string)$req->post( 'icwp_wpsf_login_email' ) ) > 0 ) {
65
  throw new \Exception( 'Email should not be provided in honeypot' );
66
  }
67
 
 
 
 
 
 
68
  if ( !$opts->getCanIpRequestAutoUnblock( $ip ) ) {
69
  throw new \Exception( 'IP already processed in the last 1hr' );
70
  }
96
  }
97
 
98
  /**
 
99
  * @throws \Exception
100
  */
101
  private function processUserMagicLink() :bool {
108
  $unblocked = false;
109
 
110
  if ( $opts->isEnabledMagicEmailLinkRecover()
111
+ && $req->query( 'action' ) == $mod->getCon()->prefix()
112
  && strpos( $req->query( 'exec' ), 'uaum-' ) === 0 ) {
113
 
114
  if ( check_admin_referer( $req->request( 'exec' ), 'exec_nonce' ) !== 1 ) {
147
  } )
148
  );
149
  }
150
+ http_response_code( 200 );
151
+ die();
152
  }
153
  elseif ( $linkParts[ 1 ] === 'go' ) {
154
  ( new IPs\Lib\Ops\DeleteIp() )
177
  $mod = $this->getMod();
178
  $user = Services::WpUsers()->getCurrentWpUser();
179
 
180
+ $mod->getEmailProcessor()->sendEmailWithTemplate(
181
+ '/email/uaum_init',
182
+ $user->user_email,
183
+ __( 'Automatic IP Unblock Request', 'wp-simple-firewall' ),
184
+ [
185
+ 'flags' => [
186
+ 'show_login_link' => !$this->getCon()->isRelabelled()
187
+ ],
188
+ 'vars' => [
189
+ ],
190
+ 'hrefs' => [
191
+ 'unblock' => add_query_arg(
192
+ array_merge(
193
+ $mod->getNonceActionData( 'uaum-go-'.substr( sha1( $user->user_login ), 0, 6 ) ),
194
+ [
195
+ 'ip' => Services::IP()->getRequestIp()
196
+ ]
197
+ ),
198
+ Services::WpGeneral()->getHomeUrl()
199
+ )
200
+ ],
201
+ 'strings' => [
202
+ 'looks_like' => __( "It looks like you've been blocked and have clicked to have your IP address removed from the blocklist.", 'wp-simple-firewall' ),
203
+ 'please_click' => __( 'Please click the link provided below to do so.', 'wp-simple-firewall' ),
204
+ 'details' => __( 'Details', 'wp-simple-firewall' ),
205
+ 'unblock_my_ip' => sprintf( '%s: %s',
206
+ __( 'Unblock My IP', 'wp-simple-firewall' ), Services::IP()->getRequestIp() ),
207
+ 'or_copy' => __( 'Or Copy-Paste', 'wp-simple-firewall' ),
208
+ 'details_url' => sprintf( '%s: %s',
209
+ __( 'URL', 'wp-simple-firewall' ), Services::WpGeneral()->getHomeUrl() ),
210
+ 'details_username' => sprintf( '%s: %s', __( 'Username', 'wp-simple-firewall' ), $user->user_login ),
211
+ 'details_ip' => sprintf( '%s: %s', __( 'IP Address', 'wp-simple-firewall' ), Services::IP()
212
+ ->getRequestIp() ),
213
+ 'important' => __( 'Important', 'wp-simple-firewall' ),
214
+ 'imp_limit' => __( "You'll need to wait for a further 60 minutes if your IP address gets blocked again.", 'wp-simple-firewall' ),
215
+ 'imp_browser' => __( "This link will ONLY work if it opens in the same web browser that you used to request this email.", 'wp-simple-firewall' ),
 
 
216
  ]
217
+ ]
218
+ );
219
  }
220
  }
src/lib/src/Modules/IPs/Lib/BlacklistHandler.php CHANGED
@@ -4,6 +4,7 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Crons\PluginCronsConsumer;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
 
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
@@ -18,41 +19,44 @@ class BlacklistHandler extends Modules\Base\Common\ExecOnceModConsumer {
18
  }
19
 
20
  protected function run() {
21
- /** @var IPs\ModCon $mod */
22
- $mod = $this->getMod();
23
-
24
- if ( $this->getCon()->isPremiumActive() ) {
25
- $this->setupCronHooks();
26
- }
27
 
28
  ( new IPs\Components\UnblockIpByFlag() )
29
- ->setMod( $mod )
30
- ->run();
31
-
32
- if ( !$mod->isVisitorWhitelisted() && !$this->isRequestWhitelisted() ) {
33
-
34
- // We setup offenses processing immediately but run the blocks on 'init'
35
- ( new ProcessOffenses() )
36
- ->setMod( $this->getMod() )
37
- ->execute();
38
 
39
- add_action( 'init', function () {
40
- ( new BlockRequest() )
41
- ->setMod( $this->getMod() )
42
- ->execute();
43
- }, -100000 );
44
- }
45
  }
46
 
 
 
 
47
  private function isRequestWhitelisted() :bool {
48
  /** @var IPs\Options $opts */
49
  $opts = $this->getOptions();
50
  $isWhitelisted = false;
51
- $whitelistPaths = $opts->getRequestWhitelistAsRegex();
 
 
 
 
 
 
 
52
  if ( !empty( $whitelistPaths ) ) {
53
- $sPath = strtolower( '/'.ltrim( (string)Services::Request()->getPath(), '/' ) );
54
  foreach ( $whitelistPaths as $rule ) {
55
- if ( preg_match( $rule, $sPath ) ) {
56
  $isWhitelisted = true;
57
  break;
58
  }
@@ -60,10 +64,4 @@ class BlacklistHandler extends Modules\Base\Common\ExecOnceModConsumer {
60
  }
61
  return $isWhitelisted;
62
  }
63
-
64
- public function runHourlyCron() {
65
- ( new IPs\Components\ImportIpsFromFile() )
66
- ->setMod( $this->getMod() )
67
- ->execute();
68
- }
69
  }
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Crons\PluginCronsConsumer;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Options\WildCardOptions;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
9
  use FernleafSystems\Wordpress\Services\Services;
10
 
19
  }
20
 
21
  protected function run() {
22
+ $this->setupCronHooks();
 
 
 
 
 
23
 
24
  ( new IPs\Components\UnblockIpByFlag() )
25
+ ->setMod( $this->getMod() )
26
+ ->execute();
27
+ ( new ProcessOffenses() )
28
+ ->setMod( $this->getMod() )
29
+ ->execute();
30
+ ( new AutoUnblock() )
31
+ ->setMod( $this->getMod() )
32
+ ->execute();
33
+ }
34
 
35
+ public function runHourlyCron() {
36
+ ( new IPs\Components\ImportIpsFromFile() )
37
+ ->setMod( $this->getMod() )
38
+ ->execute();
 
 
39
  }
40
 
41
+ /**
42
+ * @deprecated 15.0
43
+ */
44
  private function isRequestWhitelisted() :bool {
45
  /** @var IPs\Options $opts */
46
  $opts = $this->getOptions();
47
  $isWhitelisted = false;
48
+
49
+ $whitelistPaths = array_map(
50
+ function ( $value ) {
51
+ return ( new WildCardOptions() )->buildFullRegexValue( $value, WildCardOptions::URL_PATH );
52
+ },
53
+ $this->getCon()->isPremiumActive() ? $opts->getOpt( 'request_whitelist', [] ) : []
54
+ );
55
+
56
  if ( !empty( $whitelistPaths ) ) {
57
+ $path = strtolower( '/'.ltrim( Services::Request()->getPath(), '/' ) );
58
  foreach ( $whitelistPaths as $rule ) {
59
+ if ( preg_match( $rule, $path ) ) {
60
  $isWhitelisted = true;
61
  break;
62
  }
64
  }
65
  return $isWhitelisted;
66
  }
 
 
 
 
 
 
67
  }
src/lib/src/Modules/IPs/Lib/BlockRequest.php CHANGED
@@ -2,178 +2,19 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Common\ExecOnceModConsumer;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
7
- use FernleafSystems\Wordpress\Services\Services;
8
- use FernleafSystems\Wordpress\Services\Utilities\Obfuscate;
9
 
10
  class BlockRequest extends ExecOnceModConsumer {
11
 
12
- private $ipBlocked;
13
-
14
- protected function run() {
15
- if ( $this->isAutoUnBlocked() ) {
16
- Services::Response()->redirectToHome();
17
- }
18
- elseif ( $this->isRequestBlocked() ) {
19
- $this->renderKillPage();
20
- }
21
- }
22
-
23
- private function isRequestBlocked() :bool {
24
- return (bool)apply_filters( 'shield/is_request_blocked', $this->isIpBlocked() );
25
- }
26
-
27
- private function isIpBlocked() :bool {
28
- if ( !isset( $this->ipBlocked ) ) {
29
- $this->ipBlocked = ( new IPs\Components\QueryIpBlock() )
30
- ->setMod( $this->getMod() )
31
- ->setIp( Services::IP()->getRequestIp() )
32
- ->run();
33
-
34
- // do not block IPs with high reputation
35
- if ( $this->ipBlocked && $this->isHighReputationIP() ) {
36
- $this->ipBlocked = false;
37
- $this->getCon()->fireEvent( 'not_conn_kill_high_rep' );
38
- }
39
- }
40
- return $this->ipBlocked;
41
- }
42
-
43
- private function isHighReputationIP() :bool {
44
- /** @var IPs\Options $opts */
45
- $opts = $this->getOptions();
46
- return ( new IPs\Lib\Bots\Calculator\CalculateVisitorBotScores() )
47
- ->setMod( $this->getMod() )
48
- ->setIP( Services::IP()->getRequestIp() )
49
- ->total() >
50
- (int)apply_filters( 'shield/high_reputation_ip_minimum', $opts->getAntiBotHighReputationMinimum() );
51
- }
52
-
53
- private function isAutoUnBlocked() :bool {
54
- return $this->isIpBlocked()
55
- && ( new AutoUnblock() )
56
- ->setMod( $this->getMod() )
57
- ->run();
58
- }
59
-
60
- private function renderKillPage() {
61
- $con = $this->getCon();
62
- /** @var IPs\ModCon $mod */
63
- $mod = $this->getMod();
64
- /** @var IPs\Options $opts */
65
- $opts = $this->getOptions();
66
-
67
- $ip = Services::IP()->getRequestIp();
68
- $timeRemaining = max( floor( $opts->getAutoExpireTime()/60 ), 0 );
69
-
70
- $user = Services::WpUsers()->getCurrentWpUser();
71
- $canUauBot = $opts->isEnabledAutoVisitorRecover() && !empty( $ip ) && $opts->getCanIpRequestAutoUnblock( $ip );
72
- $canUauMagic = $opts->isEnabledMagicEmailLinkRecover() &&
73
- $user instanceof \WP_User
74
- && $opts->getCanRequestAutoUnblockEmailLink( $user );
75
- $canAutoRecover = $canUauBot || $canUauMagic;
76
-
77
- if ( !empty( $con->getLabels()[ 'PluginURI' ] ) ) {
78
- $homeURL = $con->getLabels()[ 'PluginURI' ];
79
- }
80
- else {
81
- $homeURL = $con->cfg->meta[ 'url_repo_home' ];
82
- }
83
-
84
- $data = [
85
- 'strings' => [
86
- 'title' => sprintf( __( "You've been blocked by the %s plugin", 'wp-simple-firewall' ),
87
- sprintf( '<a href="%s" target="_blank">%s</a>',
88
- $homeURL,
89
- $con->getHumanName()
90
- )
91
- ),
92
- 'lines' => [
93
- sprintf( __( 'Time remaining on black list: %s', 'wp-simple-firewall' ),
94
- sprintf( _n( '%s minute', '%s minutes', $timeRemaining, 'wp-simple-firewall' ), $timeRemaining )
95
- ),
96
- sprintf( __( 'You tripped the security plugin defenses a total of %s times making you a suspect.', 'wp-simple-firewall' ), $opts->getOffenseLimit() ),
97
- __( 'If you believe this to be in error, please contact the site owner and quote your IP address below.', 'wp-simple-firewall' ),
98
- ],
99
- 'your_ip' => 'Your IP address',
100
- 'unblock' => [
101
- 'title' => __( 'Auto-Unblock Your IP', 'wp-simple-firewall' ),
102
- 'you_can' => __( 'You can automatically unblock your IP address by clicking the button below.', 'wp-simple-firewall' ),
103
- 'button' => __( 'Unblock My IP Address', 'wp-simple-firewall' ),
104
- ],
105
- ],
106
- 'content' => [
107
- 'email_unblock' => $this->renderEmailMagicLinkContent()
108
- ],
109
- 'hrefs' => [
110
- 'home' => Services::WpGeneral()->getHomeUrl()
111
- ],
112
- 'vars' => [
113
- 'nonce' => $mod->getNonceActionData( 'uau' ),
114
- 'ip' => $ip,
115
- ],
116
- 'flags' => [
117
- 'is_autorecover' => $canAutoRecover,
118
- 'is_uaug_permitted' => $canUauBot,
119
- 'is_uaum_permitted' => $canUauMagic,
120
- ],
121
- ];
122
-
123
- if ( $con->isPremiumActive() ) {
124
- $data = apply_filters( 'shield/render_data_block_page', $data );
125
- }
126
-
127
- Services::WpGeneral()->wpDie(
128
- $mod->renderTemplate( '/pages/block/blocklist_die.twig', $data, true )
129
- );
130
  }
131
 
132
- private function renderEmailMagicLinkContent() :string {
133
- $con = $this->getCon();
134
- /** @var IPs\ModCon $mod */
135
- $mod = $this->getMod();
136
- /** @var IPs\Options $opts */
137
- $opts = $this->getOptions();
138
-
139
- $content = '';
140
-
141
- $user = Services::WpUsers()->getCurrentWpUser();
142
- if ( $user instanceof \WP_User &&
143
- $opts->isEnabledMagicEmailLinkRecover()
144
- && $opts->getCanRequestAutoUnblockEmailLink( $user ) ) {
145
-
146
- if ( apply_filters( $con->prefix( 'can_user_magic_link' ), true ) ) {
147
- $content = $mod->renderTemplate( '/pages/block/magic_link.twig', [
148
- 'flags' => [
149
- ],
150
- 'hrefs' => [
151
- 'unblock' => add_query_arg(
152
- array_merge(
153
- $mod->getNonceActionData( 'uaum-init-'.substr( sha1( $user->user_login ), 0, 6 ) ),
154
- [
155
- 'ip' => Services::IP()->getRequestIp()
156
- ]
157
- ),
158
- Services::WpGeneral()->getHomeUrl()
159
- )
160
- ],
161
- 'vars' => [
162
- 'email' => Obfuscate::Email( $user->user_email )
163
- ],
164
- 'strings' => [
165
- 'you_may' => __( 'You can automatically unblock your IP address by clicking the link below.', 'wp-simple-firewall' ),
166
- 'this_will_send' => __( 'Clicking the button will send you an email letting you unblock your IP address.', 'wp-simple-firewall' ),
167
- 'assumes_email' => __( 'This assumes that your WordPress site has been properly configured to send email - many are not.', 'wp-simple-firewall' ),
168
- 'dont_receive' => __( "If you don't receive the email, check your spam and contact your site admin.", 'wp-simple-firewall' ),
169
- 'limit_60' => __( "You may only use this link once every 60 minutes. If you're repeatedly blocked, ask your site admin to review the audit trail to determine the cause.", 'wp-simple-firewall' ),
170
- 'same_browser' => __( "When you click the link from your email, it must open up in this web browser.", 'wp-simple-firewall' ),
171
- 'click_to_send' => __( 'Send Auto-Unblock Link To My Email', 'wp-simple-firewall' )
172
- ],
173
- ] );
174
- }
175
- }
176
-
177
- return $content;
178
  }
179
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Blocks\RenderBlockPages\RenderBlockIP;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Common\ExecOnceModConsumer;
 
 
 
7
 
8
  class BlockRequest extends ExecOnceModConsumer {
9
 
10
+ protected function canRun() :bool {
11
+ return (bool)apply_filters( 'shield/is_request_blocked',
12
+ $this->getCon()->this_req->is_ip_blocked );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  }
14
 
15
+ protected function run() {
16
+ ( new RenderBlockIP() )
17
+ ->setMod( $this->getMod() )
18
+ ->display();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  }
20
  }
src/lib/src/Modules/IPs/Lib/Bots/BotEventListener.php CHANGED
@@ -32,8 +32,7 @@ class BotEventListener extends ExecOnceModConsumer {
32
  protected function canRun() :bool {
33
  /** @var ModCon $mod */
34
  $mod = $this->getMod();
35
- return !$mod->isTrustedVerifiedBot()
36
- && $mod->getDbH_BotSignal()->isReady();
37
  }
38
 
39
  protected function run() {
@@ -62,6 +61,7 @@ class BotEventListener extends ExecOnceModConsumer {
62
  'bottrack_xmlrpc' => 'btxml',
63
  'bottrack_logininvalid' => 'btlogininvalid',
64
  'bottrack_invalidscript' => 'btinvalidscript',
 
65
  'cooldown_fail' => 'cooldown',
66
  'recaptcha_success' => 'captchapass',
67
  'request_limit_exceeded' => 'ratelimit',
32
  protected function canRun() :bool {
33
  /** @var ModCon $mod */
34
  $mod = $this->getMod();
35
+ return !$this->getCon()->this_req->is_trusted_bot && $mod->getDbH_BotSignal()->isReady();
 
36
  }
37
 
38
  protected function run() {
61
  'bottrack_xmlrpc' => 'btxml',
62
  'bottrack_logininvalid' => 'btlogininvalid',
63
  'bottrack_invalidscript' => 'btinvalidscript',
64
+ 'block_author_fishing' => 'btauthorfishing',
65
  'cooldown_fail' => 'cooldown',
66
  'recaptcha_success' => 'captchapass',
67
  'request_limit_exceeded' => 'ratelimit',
src/lib/src/Modules/IPs/Lib/Bots/BotSignalsController.php CHANGED
@@ -71,15 +71,15 @@ class BotSignalsController extends ExecOnceModConsumer {
71
  protected function run() {
72
  $this->getEventListener()->execute();
73
  add_action( 'init', function () {
74
- foreach ( $this->enumerateBotTrackers() as $botTracker ) {
75
- $botTracker->setMod( $this->getMod() )->execute();
76
  }
77
  } );
78
  $this->getHandlerNotBot()->execute();
79
  }
80
 
81
  /**
82
- * @return BotTrack\Base[]
83
  */
84
  private function enumerateBotTrackers() :array {
85
  /** @var ModCon $mod */
@@ -88,35 +88,22 @@ class BotSignalsController extends ExecOnceModConsumer {
88
  $opts = $this->getOptions();
89
 
90
  $trackers = [
91
- new BotTrack\TrackCommentSpam()
92
  ];
93
 
94
  if ( !Services::WpUsers()->isUserLoggedIn() ) {
95
 
96
- if ( !$mod->isTrustedVerifiedBot() ) {
97
-
98
- if ( $opts->isEnabledTrack404() ) {
99
- $trackers[] = new BotTrack\Track404();
100
- }
101
- if ( $opts->isEnabledTrackXmlRpc() ) {
102
- $trackers[] = new BotTrack\TrackXmlRpc();
103
- }
104
  if ( $opts->isEnabledTrackLoginFailed() ) {
105
- $trackers[] = new BotTrack\TrackLoginFailed();
106
  }
107
  if ( $opts->isEnabledTrackLoginInvalid() ) {
108
- $trackers[] = new BotTrack\TrackLoginInvalid();
109
- }
110
- if ( $opts->isEnabledTrackFakeWebCrawler() ) {
111
- $trackers[] = new BotTrack\TrackFakeWebCrawler();
112
- }
113
- if ( $opts->isEnabledTrackInvalidScript() ) {
114
- $trackers[] = new BotTrack\TrackInvalidScriptLoad();
115
  }
116
  }
117
 
118
  if ( $opts->isEnabledTrackLinkCheese() && $mod->canLinkCheese() ) {
119
- $trackers[] = new BotTrack\TrackLinkCheese();
120
  }
121
  }
122
 
71
  protected function run() {
72
  $this->getEventListener()->execute();
73
  add_action( 'init', function () {
74
+ foreach ( $this->enumerateBotTrackers() as $botTrackerClass ) {
75
+ ( new $botTrackerClass() )->setMod( $this->getMod() )->execute();
76
  }
77
  } );
78
  $this->getHandlerNotBot()->execute();
79
  }
80
 
81
  /**
82
+ * @return string[]
83
  */
84
  private function enumerateBotTrackers() :array {
85
  /** @var ModCon $mod */
88
  $opts = $this->getOptions();
89
 
90
  $trackers = [
91
+ BotTrack\TrackCommentSpam::class
92
  ];
93
 
94
  if ( !Services::WpUsers()->isUserLoggedIn() ) {
95
 
96
+ if ( !$this->getCon()->this_req->request_bypasses_all_restrictions ) {
 
 
 
 
 
 
 
97
  if ( $opts->isEnabledTrackLoginFailed() ) {
98
+ $trackers[] = BotTrack\TrackLoginFailed::class;
99
  }
100
  if ( $opts->isEnabledTrackLoginInvalid() ) {
101
+ $trackers[] = BotTrack\TrackLoginInvalid::class;
 
 
 
 
 
 
102
  }
103
  }
104
 
105
  if ( $opts->isEnabledTrackLinkCheese() && $mod->canLinkCheese() ) {
106
+ $trackers[] = BotTrack\TrackLinkCheese::class;
107
  }
108
  }
109
 
src/lib/src/Modules/IPs/Lib/Bots/BotSignalsRecord.php CHANGED
@@ -2,16 +2,16 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Bots;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Session;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\IPs;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Components\IpAddressConsumer;
8
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Ops\LookupIpOnList;
9
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\{
 
10
  DB\BotSignal\BotSignalRecord,
11
  DB\BotSignal\LoadBotSignalRecords,
12
- ModCon,
13
- DB\BotSignal
14
  };
 
 
15
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
16
  use FernleafSystems\Wordpress\Services\Services;
17
 
@@ -23,8 +23,14 @@ class BotSignalsRecord {
23
  public function delete() :bool {
24
  /** @var ModCon $mod */
25
  $mod = $this->getMod();
 
26
  /** @var BotSignal\Ops\Select $select */
27
  $select = $mod->getDbH_BotSignal()->getQueryDeleter();
 
 
 
 
 
28
  return $select->filterByIP( $this->getIPRecord()->id )->query();
29
  }
30
 
@@ -49,6 +55,11 @@ class BotSignalsRecord {
49
  public function retrieve( bool $storeOnLoad = true ) :BotSignalRecord {
50
  /** @var ModCon $mod */
51
  $mod = $this->getMod();
 
 
 
 
 
52
 
53
  $r = $this->dbLoad();
54
  if ( empty( $r ) ) {
@@ -78,15 +89,15 @@ class BotSignalsRecord {
78
  }
79
 
80
  if ( empty( $r->auth_at ) ) {
81
- $dbhSessions = $this->getCon()
82
- ->getModule_Sessions()
83
- ->getDbHandler_Sessions();
84
- /** @var Session\Select $selector */
85
- $selector = $dbhSessions->getQuerySelector();
86
- $session = $selector->setIncludeSoftDeleted( true )
87
- ->filterByIp( $this->getIP() )
88
- ->first();
89
- $r->auth_at = empty( $session ) ? 0 : $session->created_at;
90
  }
91
 
92
  if ( $storeOnLoad ) {
@@ -129,13 +140,17 @@ class BotSignalsRecord {
129
  ->getQueryUpdater()
130
  ->updateById( $record->id, $data );
131
  }
 
 
 
 
 
 
132
  return $success;
133
  }
134
 
135
  /**
136
- * @param string $field
137
  * @param int|null $ts
138
- * @return BotSignalRecord
139
  * @throws \LogicException
140
  */
141
  public function updateSignalField( string $field, $ts = null ) :BotSignalRecord {
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Bots;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\IPs;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\UserMeta\Ops as UserMetaDB;
 
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\{
8
+ DB\BotSignal,
9
  DB\BotSignal\BotSignalRecord,
10
  DB\BotSignal\LoadBotSignalRecords,
11
+ ModCon
 
12
  };
13
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Components\IpAddressConsumer;
14
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Ops\LookupIpOnList;
15
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
16
  use FernleafSystems\Wordpress\Services\Services;
17
 
23
  public function delete() :bool {
24
  /** @var ModCon $mod */
25
  $mod = $this->getMod();
26
+ $thisReq = $this->getCon()->this_req;
27
  /** @var BotSignal\Ops\Select $select */
28
  $select = $mod->getDbH_BotSignal()->getQueryDeleter();
29
+
30
+ if ( $thisReq->ip === $this->getIP() ) {
31
+ unset( $thisReq->botsignal_record );
32
+ }
33
+
34
  return $select->filterByIP( $this->getIPRecord()->id )->query();
35
  }
36
 
55
  public function retrieve( bool $storeOnLoad = true ) :BotSignalRecord {
56
  /** @var ModCon $mod */
57
  $mod = $this->getMod();
58
+ $thisReq = $this->getCon()->this_req;
59
+
60
+ if ( $thisReq->ip === $this->getIP() && !empty( $thisReq->botsignal_record ) ) {
61
+ return $thisReq->botsignal_record;
62
+ }
63
 
64
  $r = $this->dbLoad();
65
  if ( empty( $r ) ) {
89
  }
90
 
91
  if ( empty( $r->auth_at ) ) {
92
+ /** @var UserMetaDB\Select $userMetaSelect */
93
+ $userMetaSelect = $this->getCon()->getModule_Data()->getDbH_UserMeta()->getQuerySelector();
94
+ $lastUserMetaLogin = $userMetaSelect->filterByIPRef( $r->ip_ref )
95
+ ->setColumnsToSelect( [ 'last_login_at' ] )
96
+ ->setOrderBy( 'last_login_at' )
97
+ ->first();
98
+ if ( !empty( $lastUserMetaLogin ) ) {
99
+ $r->auth_at = $lastUserMetaLogin->last_login_at;
100
+ }
101
  }
102
 
103
  if ( $storeOnLoad ) {
140
  ->getQueryUpdater()
141
  ->updateById( $record->id, $data );
142
  }
143
+
144
+ $thisReq = $this->getCon()->this_req;
145
+ if ( $thisReq->ip === $record->ip ) {
146
+ $thisReq->botsignal_record = $record;
147
+ }
148
+
149
  return $success;
150
  }
151
 
152
  /**
 
153
  * @param int|null $ts
 
154
  * @throws \LogicException
155
  */
156
  public function updateSignalField( string $field, $ts = null ) :BotSignalRecord {
src/lib/src/Modules/IPs/Lib/Bots/Calculator/BuildScores.php CHANGED
@@ -24,10 +24,10 @@ class BuildScores extends BaseBuildScores {
24
  $logic = $this->getScoringLogic()->getFieldScoreLogic( $field );
25
 
26
  // -1 represents the default if none of the following boundaries are satisfied
27
- $score = $logic[ -1 ];
28
 
29
  if ( $this->lastAtTs( $field ) === 0 ) {
30
- $score = $logic[ 0 ];
31
  }
32
  else {
33
  unset( $logic[ 0 ] );
24
  $logic = $this->getScoringLogic()->getFieldScoreLogic( $field );
25
 
26
  // -1 represents the default if none of the following boundaries are satisfied
27
+ $score = $logic[ -1 ] ?? 0;
28
 
29
  if ( $this->lastAtTs( $field ) === 0 ) {
30
+ $score = $logic[ 0 ] ?? 0;
31
  }
32
  else {
33
  unset( $logic[ 0 ] );
src/lib/src/Modules/IPs/Lib/Bots/NotBot/NotBotHandler.php CHANGED
@@ -40,8 +40,8 @@ class NotBotHandler extends ExecOnceModConsumer {
40
 
41
  private function registerFrontPageLoad() {
42
  add_action( $this->getCon()->prefix( 'pre_plugin_shutdown' ), function () {
43
- $req = Services::Request();
44
- if ( $req->isGet() && ( is_page() || is_single() || is_front_page() || is_home() ) ) {
45
  /** @var ModCon $mod */
46
  $mod = $this->getMod();
47
  $mod->getBotSignalsController()
@@ -69,7 +69,7 @@ class NotBotHandler extends ExecOnceModConsumer {
69
  $cookieLife = apply_filters( 'shield/notbot_cookie_life', self::LIFETIME );
70
  $ts = Services::Request()->ts() + $cookieLife;
71
  Services::Response()->cookieSet(
72
- $this->getMod()->prefix( self::SLUG ),
73
  sprintf( '%sz%s', $ts, $this->getHashForVisitorTS( $ts ) ),
74
  $cookieLife
75
  );
@@ -80,7 +80,7 @@ class NotBotHandler extends ExecOnceModConsumer {
80
 
81
  public function clearCookie() :bool {
82
  Services::Response()->cookieSet(
83
- $this->getMod()->prefix( self::SLUG ),
84
  '',
85
  -self::LIFETIME
86
  );
@@ -104,7 +104,7 @@ class NotBotHandler extends ExecOnceModConsumer {
104
  private function getCookieParts() :array {
105
  $parts = [];
106
  $req = Services::Request();
107
- $notBot = $req->cookie( $this->getMod()->prefix( self::SLUG ), '' );
108
  if ( !empty( $notBot ) && strpos( $notBot, 'z' ) ) {
109
  list( $ts, $hash ) = explode( 'z', $notBot );
110
  $parts[ 'ts' ] = $ts;
40
 
41
  private function registerFrontPageLoad() {
42
  add_action( $this->getCon()->prefix( 'pre_plugin_shutdown' ), function () {
43
+ if ( Services::Request()->isGet() && did_action( 'wp' )
44
+ && ( is_page() || is_single() || is_front_page() || is_home() ) ) {
45
  /** @var ModCon $mod */
46
  $mod = $this->getMod();
47
  $mod->getBotSignalsController()
69
  $cookieLife = apply_filters( 'shield/notbot_cookie_life', self::LIFETIME );
70
  $ts = Services::Request()->ts() + $cookieLife;
71
  Services::Response()->cookieSet(
72
+ $this->getCon()->prefix( self::SLUG ),
73
  sprintf( '%sz%s', $ts, $this->getHashForVisitorTS( $ts ) ),
74
  $cookieLife
75
  );
80
 
81
  public function clearCookie() :bool {
82
  Services::Response()->cookieSet(
83
+ $this->getCon()->prefix( self::SLUG ),
84
  '',
85
  -self::LIFETIME
86
  );
104
  private function getCookieParts() :array {
105
  $parts = [];
106
  $req = Services::Request();
107
+ $notBot = $req->cookie( $this->getCon()->prefix( self::SLUG ), '' );
108
  if ( !empty( $notBot ) && strpos( $notBot, 'z' ) ) {
109
  list( $ts, $hash ) = explode( 'z', $notBot );
110
  $parts[ 'ts' ] = $ts;
src/lib/src/Modules/IPs/Lib/IpAnalyse/BuildDisplay.php CHANGED
@@ -17,6 +17,7 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Ops\LookupIpOnList;
17
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\ModCon;
18
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Strings;
19
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
 
20
  use FernleafSystems\Wordpress\Plugin\Shield\ShieldNetApi\Reputation\GetIPReputation;
21
  use FernleafSystems\Wordpress\Services\Services;
22
  use FernleafSystems\Wordpress\Services\Utilities\Net\IpID;
@@ -27,7 +28,6 @@ class BuildDisplay {
27
  use ModConsumer;
28
 
29
  /**
30
- * @return string
31
  * @throws \Exception
32
  */
33
  public function run() :string {
@@ -211,43 +211,43 @@ class BuildDisplay {
211
 
212
  private function renderForSessions() :string {
213
  $WP = Services::WpGeneral();
214
- /** @var Databases\Session\Select $sel */
215
- $sel = $this->getCon()
216
- ->getModule_Sessions()
217
- ->getDbHandler_Sessions()
218
- ->getQuerySelector();
219
- /** @var Databases\Session\EntryVO[] $sessions */
220
- $sessions = $sel->filterByIp( $this->getIP() )
221
- ->query();
222
-
223
- foreach ( $sessions as $key => $session ) {
224
- $asArray = $session->getRawData();
225
- $asArray[ 'logged_in_at' ] = $WP->getTimeStringForDisplay( $session->logged_in_at );
226
- $asArray[ 'logged_in_at_ago' ] = $this->getTimeAgo( $session->logged_in_at );
227
- $asArray[ 'last_activity_at' ] = $WP->getTimeStringForDisplay( $session->last_activity_at );
228
- $asArray[ 'last_activity_at_ago' ] = $this->getTimeAgo( $session->last_activity_at );
229
- $asArray[ 'is_sec_admin' ] = $session->secadmin_at > 0;
230
- $sessions[ $key ] = $asArray;
231
  }
232
 
233
- return $this->getMod()->renderTemplate(
234
- '/wpadmin_pages/insights/ips/ip_analyse/ip_sessions.twig',
235
- [
236
- 'strings' => [
237
- 'title' => __( 'User Sessions', 'wp-simple-firewall' ),
238
- 'no_sessions' => __( 'No sessions at this IP', 'wp-simple-firewall' ),
239
- 'username' => __( 'Username', 'wp-simple-firewall' ),
240
- 'sec_admin' => __( 'Security Admin', 'wp-simple-firewall' ),
241
- 'logged_in_at' => __( 'Logged-In At', 'wp-simple-firewall' ),
242
- 'last_activity_at' => __( 'Last Seen At', 'wp-simple-firewall' ),
243
- ],
244
- 'vars' => [
245
- 'sessions' => $sessions,
246
- 'total_sessions' => count( $sessions ),
247
- ],
248
  ],
249
- true
250
- );
 
 
 
251
  }
252
 
253
  private function renderForTraffic() :string {
@@ -334,7 +334,6 @@ class BuildDisplay {
334
  }
335
  catch ( \Exception $e ) {
336
  $record = null;
337
- $signals = [];
338
  }
339
 
340
  foreach ( $scores as $scoreKey => $scoreValue ) {
17
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\ModCon;
18
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Strings;
19
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
20
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement\Lib\Session\FindSessions;
21
  use FernleafSystems\Wordpress\Plugin\Shield\ShieldNetApi\Reputation\GetIPReputation;
22
  use FernleafSystems\Wordpress\Services\Services;
23
  use FernleafSystems\Wordpress\Services\Utilities\Net\IpID;
28
  use ModConsumer;
29
 
30
  /**
 
31
  * @throws \Exception
32
  */
33
  public function run() :string {
211
 
212
  private function renderForSessions() :string {
213
  $WP = Services::WpGeneral();
214
+
215
+ $finder = ( new FindSessions() )->setMod( $this->getMod() );
216
+ $allSessions = [];
217
+ foreach ( $finder->byIP( $this->getIP() ) as $userID => $sessions ) {
218
+ foreach ( $sessions as $session ) {
219
+ $loginAt = $session[ 'login' ];
220
+ $activityAt = $session[ 'shield' ][ 'last_activity_at' ] ?? $loginAt;
221
+ $session[ 'logged_in_at' ] = $WP->getTimeStringForDisplay( $loginAt );
222
+ $session[ 'logged_in_at_ago' ] = $this->getTimeAgo( $loginAt );
223
+ $session[ 'last_activity_at' ] = $WP->getTimeStringForDisplay( $activityAt );
224
+ $session[ 'last_activity_at_ago' ] = $this->getTimeAgo( $activityAt );
225
+ $session[ 'is_sec_admin' ] = (bool)( $session[ 'shield' ][ 'secadmin_at' ] ?? false );
226
+ $allSessions[] = $session;
227
+ }
 
 
 
228
  }
229
 
230
+ uasort( $allSessions, function ( $a, $b ) {
231
+ if ( $a[ 'last_activity_at' ] == $b[ 'last_activity_at' ] ) {
232
+ return 0;
233
+ }
234
+ return ( $a[ 'last_activity_at' ] < $b[ 'last_activity_at' ] ) ? 1 : -1;
235
+ } );
236
+
237
+ return $this->getMod()->renderTemplate( '/wpadmin_pages/insights/ips/ip_analyse/ip_sessions.twig', [
238
+ 'strings' => [
239
+ 'title' => __( 'User Sessions', 'wp-simple-firewall' ),
240
+ 'no_sessions' => __( 'No sessions at this IP', 'wp-simple-firewall' ),
241
+ 'username' => __( 'Username', 'wp-simple-firewall' ),
242
+ 'sec_admin' => __( 'Security Admin', 'wp-simple-firewall' ),
243
+ 'logged_in_at' => __( 'Logged-In At', 'wp-simple-firewall' ),
244
+ 'last_activity_at' => __( 'Last Seen At', 'wp-simple-firewall' ),
245
  ],
246
+ 'vars' => [
247
+ 'sessions' => $allSessions,
248
+ 'total_sessions' => count( $allSessions ),
249
+ ],
250
+ ] );
251
  }
252
 
253
  private function renderForTraffic() :string {
334
  }
335
  catch ( \Exception $e ) {
336
  $record = null;
 
337
  }
338
 
339
  foreach ( $scores as $scoreKey => $scoreValue ) {
src/lib/src/Modules/IPs/Lib/IpAnalyse/FindAllPluginIps.php CHANGED
@@ -14,18 +14,11 @@ class FindAllPluginIps {
14
  public function run( string $ipFilter = '' ) :array {
15
  $con = $this->getCon();
16
 
17
- // User Sessions
18
- /** @var Databases\Session\Select $sel */
19
- $sel = $con->getModule_Sessions()
20
- ->getDbHandler_Sessions()
21
- ->getQuerySelector();
22
- $ips = $sel->getDistinctIps();
23
-
24
  /** @var Select $sel */
25
  $sel = $con->getModule_Data()
26
  ->getDbH_IPs()
27
  ->getQuerySelector();
28
- $ips = array_merge( $ips, $sel->getDistinctIps() );
29
 
30
  // IP Addresses
31
  /** @var Databases\IPs\Select $sel */
14
  public function run( string $ipFilter = '' ) :array {
15
  $con = $this->getCon();
16
 
 
 
 
 
 
 
 
17
  /** @var Select $sel */
18
  $sel = $con->getModule_Data()
19
  ->getDbH_IPs()
20
  ->getQuerySelector();
21
+ $ips = $sel->getDistinctIps();
22
 
23
  // IP Addresses
24
  /** @var Databases\IPs\Select $sel */
src/lib/src/Modules/IPs/Lib/IsHighReputationIP.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
+
8
+ class IsHighReputationIP {
9
+
10
+ use ModConsumer;
11
+ use IPs\Components\IpAddressConsumer;
12
+
13
+ public function query() :bool {
14
+ /** @var IPs\Options $opts */
15
+ $opts = $this->getOptions();
16
+ return ( new IPs\Lib\Bots\Calculator\CalculateVisitorBotScores() )
17
+ ->setMod( $this->getMod() )
18
+ ->setIP( $this->getIP() )
19
+ ->total() >
20
+ (int)apply_filters( 'shield/high_reputation_ip_minimum', $opts->getAntiBotHighReputationMinimum() );
21
+ }
22
+ }
src/lib/src/Modules/IPs/Lib/OffenseTracker.php CHANGED
@@ -9,11 +9,23 @@ class OffenseTracker extends EventsListener {
9
  /**
10
  * @var bool
11
  */
 
 
 
 
 
 
12
  private $bIsBlocked = false;
13
 
14
  /**
15
  * @var int
16
  */
 
 
 
 
 
 
17
  private $nOffenseCount = 0;
18
 
19
  protected function captureEvent( string $evt, array $meta = [], array $def = [] ) {
@@ -30,19 +42,18 @@ class OffenseTracker extends EventsListener {
30
  }
31
 
32
  public function isBlocked() :bool {
33
- return (bool)$this->bIsBlocked;
34
  }
35
 
36
  public function getOffenseCount() :int {
37
- return (int)$this->nOffenseCount;
38
  }
39
 
40
  /**
41
- * @param bool $isBlocked
42
  * @return $this
43
  */
44
  public function setIsBlocked( bool $isBlocked ) {
45
- $this->bIsBlocked = $isBlocked;
46
  return $this;
47
  }
48
 
@@ -50,14 +61,14 @@ class OffenseTracker extends EventsListener {
50
  * @return $this
51
  */
52
  public function incrementCount( int $increment = 1 ) {
53
- return $this->setOffenseCount( $this->getOffenseCount() + (int)$increment );
54
  }
55
 
56
  /**
57
  * @return $this
58
  */
59
  public function setOffenseCount( int $offenseCount ) {
60
- $this->nOffenseCount = max( $offenseCount, (int)$this->nOffenseCount );
61
  return $this;
62
  }
63
  }
9
  /**
10
  * @var bool
11
  */
12
+ private $isBlocked = false;
13
+
14
+ /**
15
+ * @var bool
16
+ * @deprecated 15.0
17
+ */
18
  private $bIsBlocked = false;
19
 
20
  /**
21
  * @var int
22
  */
23
+ private $offenseCount = 0;
24
+
25
+ /**
26
+ * @var int
27
+ * @deprecated 15.0
28
+ */
29
  private $nOffenseCount = 0;
30
 
31
  protected function captureEvent( string $evt, array $meta = [], array $def = [] ) {
42
  }
43
 
44
  public function isBlocked() :bool {
45
+ return (bool)( $this->isBlocked ?? $this->bIsBlocked );
46
  }
47
 
48
  public function getOffenseCount() :int {
49
+ return (int)( $this->offenseCount ?? $this->nOffenseCount );
50
  }
51
 
52
  /**
 
53
  * @return $this
54
  */
55
  public function setIsBlocked( bool $isBlocked ) {
56
+ $this->isBlocked = $isBlocked;
57
  return $this;
58
  }
59
 
61
  * @return $this
62
  */
63
  public function incrementCount( int $increment = 1 ) {
64
+ return $this->setOffenseCount( $this->getOffenseCount() + $increment );
65
  }
66
 
67
  /**
68
  * @return $this
69
  */
70
  public function setOffenseCount( int $offenseCount ) {
71
+ $this->offenseCount = max( $offenseCount, $this->getOffenseCount() );
72
  return $this;
73
  }
74
  }
src/lib/src/Modules/IPs/Lib/Ops/LookupIpOnList.php CHANGED
@@ -22,11 +22,10 @@ class LookupIpOnList {
22
  private $isBlocked;
23
 
24
  /**
25
- * @param bool $includeRanges
26
  * @return Databases\IPs\EntryVO|null
27
  * @version 8.6.0 - switched to lookup ranges first
28
  */
29
- public function lookup( $includeRanges = true ) {
30
  $IP = null;
31
  if ( !empty( $this->getIP() ) ) {
32
  if ( $includeRanges ) {
@@ -41,7 +40,7 @@ class LookupIpOnList {
41
  }
42
  }
43
  }
44
- if ( !$IP instanceof Databases\IPs\EntryVO ) {
45
  $IP = $this->lookupIp();
46
  }
47
  }
@@ -106,7 +105,6 @@ class LookupIpOnList {
106
  }
107
 
108
  /**
109
- * @param bool $blocked
110
  * @return $this
111
  */
112
  public function setIsIpBlocked( bool $blocked ) {
22
  private $isBlocked;
23
 
24
  /**
 
25
  * @return Databases\IPs\EntryVO|null
26
  * @version 8.6.0 - switched to lookup ranges first
27
  */
28
+ public function lookup( bool $includeRanges = true ) {
29
  $IP = null;
30
  if ( !empty( $this->getIP() ) ) {
31
  if ( $includeRanges ) {
40
  }
41
  }
42
  }
43
+ if ( empty( $IP ) ) {
44
  $IP = $this->lookupIp();
45
  }
46
  }
105
  }
106
 
107
  /**
 
108
  * @return $this
109
  */
110
  public function setIsIpBlocked( bool $blocked ) {
src/lib/src/Modules/IPs/Lib/ProcessOffenses.php CHANGED
@@ -8,10 +8,6 @@ use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class ProcessOffenses extends ExecOnceModConsumer {
10
 
11
- protected function canRun() :bool {
12
- return !$this->getMod()->isTrustedVerifiedBot();
13
- }
14
-
15
  protected function run() {
16
  /** @var IPs\ModCon $mod */
17
  $mod = $this->getMod();
@@ -19,7 +15,6 @@ class ProcessOffenses extends ExecOnceModConsumer {
19
  $mod->loadOffenseTracker()->setIfCommit( true );
20
 
21
  $con = $this->getCon();
22
- add_filter( 'shield/firewall_die_message', [ $this, 'augmentFirewallDieMessage' ] );
23
  add_action( $con->prefix( 'pre_plugin_shutdown' ), function () {
24
  $this->processOffense();
25
  } );
@@ -31,8 +26,7 @@ class ProcessOffenses extends ExecOnceModConsumer {
31
  $mod = $this->getMod();
32
 
33
  $tracker = $mod->loadOffenseTracker();
34
- if ( !$this->getCon()->plugin_deleting
35
- && $tracker->hasVisitorOffended() && $tracker->isCommit() ) {
36
  ( new IPs\Components\ProcessOffense() )
37
  ->setMod( $mod )
38
  ->setIp( Services::IP()->getRequestIp() )
@@ -40,26 +34,6 @@ class ProcessOffenses extends ExecOnceModConsumer {
40
  }
41
  }
42
 
43
- /**
44
- * @param array $msg
45
- * @return array
46
- */
47
- public function augmentFirewallDieMessage( $msg ) {
48
- if ( !is_array( $msg ) ) {
49
- $msg = [];
50
- }
51
-
52
- $msg[] = sprintf( '<p>%s</p>', sprintf(
53
- $this->getMod()->getTextOpt( 'text_remainingtrans' ),
54
- max( 0, ( new IPs\Components\QueryRemainingOffenses() )
55
- ->setMod( $this->getMod() )
56
- ->setIP( Services::IP()->getRequestIp() )
57
- ->run() )
58
- ) );
59
-
60
- return $msg;
61
- }
62
-
63
  /**
64
  * Allows 3rd parties to trigger Shield offenses
65
  * @param string $message
8
 
9
  class ProcessOffenses extends ExecOnceModConsumer {
10
 
 
 
 
 
11
  protected function run() {
12
  /** @var IPs\ModCon $mod */
13
  $mod = $this->getMod();
15
  $mod->loadOffenseTracker()->setIfCommit( true );
16
 
17
  $con = $this->getCon();
 
18
  add_action( $con->prefix( 'pre_plugin_shutdown' ), function () {
19
  $this->processOffense();
20
  } );
26
  $mod = $this->getMod();
27
 
28
  $tracker = $mod->loadOffenseTracker();
29
+ if ( !$this->getCon()->plugin_deleting && $tracker->hasVisitorOffended() && $tracker->isCommit() ) {
 
30
  ( new IPs\Components\ProcessOffense() )
31
  ->setMod( $mod )
32
  ->setIp( Services::IP()->getRequestIp() )
34
  }
35
  }
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  /**
38
  * Allows 3rd parties to trigger Shield offenses
39
  * @param string $message
src/lib/src/Modules/IPs/ModCon.php CHANGED
@@ -52,8 +52,21 @@ class ModCon extends BaseShield\ModCon {
52
  return $this->getDbH( 'ip_lists' );
53
  }
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  /**
56
- * @return bool
57
  * @throws \Exception
58
  */
59
  protected function isReadyToExecute() :bool {
@@ -80,34 +93,26 @@ class ModCon extends BaseShield\ModCon {
80
  if ( !defined( strtoupper( $opts->getOpt( 'auto_expire' ).'_IN_SECONDS' ) ) ) {
81
  $opts->resetOptToDefault( 'auto_expire' );
82
  }
83
-
84
- $nLimit = $opts->getOffenseLimit();
85
- if ( !is_int( $nLimit ) || $nLimit < 0 ) {
86
- $opts->resetOptToDefault( 'transgression_limit' );
87
- }
88
-
89
  $this->cleanPathWhitelist();
90
  }
91
 
92
  private function cleanPathWhitelist() {
 
93
  /** @var Options $opts */
94
  $opts = $this->getOptions();
95
-
96
- $specialPaths = array_map(
97
- function ( $url ) {
98
- return (string)parse_url( $url, PHP_URL_PATH );
99
- },
100
- [
101
- Services::WpGeneral()->getHomeUrl(),
102
- Services::WpGeneral()->getWpUrl(),
103
- ]
104
- );
105
-
106
- $values = $opts->getOpt( 'request_whitelist', [] );
107
  $opts->setOpt( 'request_whitelist',
108
  ( new Shield\Modules\Base\Options\WildCardOptions() )->clean(
109
- is_array( $values ) ? $values : [],
110
- $specialPaths,
 
 
 
 
 
 
 
 
 
111
  Shield\Modules\Base\Options\WildCardOptions::URL_PATH
112
  )
113
  );
@@ -129,6 +134,25 @@ class ModCon extends BaseShield\ModCon {
129
  return $this->oOffenseTracker;
130
  }
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  public function getTextOptDefault( string $key ) :string {
133
 
134
  switch ( $key ) {
@@ -140,15 +164,6 @@ class ModCon extends BaseShield\ModCon {
140
  );
141
  break;
142
 
143
- case 'text_remainingtrans':
144
- $text = sprintf( '%s: %s',
145
- __( 'Warning', 'wp-simple-firewall' ),
146
- __( 'You have %s remaining offenses(s) against this site and then your IP address will be completely blocked.', 'wp-simple-firewall' )
147
- .'<br/><strong>'.__( 'Seriously, stop repeating what you are doing or you will be locked out.', 'wp-simple-firewall' ).'</strong>'
148
- .sprintf( ' [<a href="%s" target="_blank">%s</a>]', 'https://shsec.io/shieldcantaccess', __( 'More Info', 'wp-simple-firewall' ) )
149
- );
150
- break;
151
-
152
  default:
153
  $text = parent::getTextOptDefault( $key );
154
  break;
52
  return $this->getDbH( 'ip_lists' );
53
  }
54
 
55
+ protected function enumRuleBuilders() :array {
56
+ /** @var Options $opts */
57
+ $opts = $this->getOptions();
58
+ return [
59
+ Rules\Build\IpWhitelisted::class,
60
+ Rules\Build\IsPathWhitelisted::class,
61
+ $opts->isEnabledAutoBlackList() ? Rules\Build\IpBlocked::class : null,
62
+ $opts->isEnabledTrack404() ? Rules\Build\BotTrack404::class : null,
63
+ $opts->isEnabledTrackXmlRpc() ? Rules\Build\BotTrackXmlrpc::class : null,
64
+ $opts->isEnabledTrackFakeWebCrawler() ? Rules\Build\BotTrackFakeWebCrawler::class : null,
65
+ $opts->isEnabledTrackInvalidScript() ? Rules\Build\BotTrackInvalidScript::class : null,
66
+ ];
67
+ }
68
+
69
  /**
 
70
  * @throws \Exception
71
  */
72
  protected function isReadyToExecute() :bool {
93
  if ( !defined( strtoupper( $opts->getOpt( 'auto_expire' ).'_IN_SECONDS' ) ) ) {
94
  $opts->resetOptToDefault( 'auto_expire' );
95
  }
 
 
 
 
 
 
96
  $this->cleanPathWhitelist();
97
  }
98
 
99
  private function cleanPathWhitelist() {
100
+ $WP = Services::WpGeneral();
101
  /** @var Options $opts */
102
  $opts = $this->getOptions();
 
 
 
 
 
 
 
 
 
 
 
 
103
  $opts->setOpt( 'request_whitelist',
104
  ( new Shield\Modules\Base\Options\WildCardOptions() )->clean(
105
+ $opts->getOpt( 'request_whitelist', [] ),
106
+ array_unique( array_map(
107
+ function ( $url ) {
108
+ return (string)wp_parse_url( $url, PHP_URL_PATH );
109
+ },
110
+ [
111
+ '/',
112
+ $WP->getHomeUrl(),
113
+ $WP->getWpUrl(),
114
+ ]
115
+ ) ),
116
  Shield\Modules\Base\Options\WildCardOptions::URL_PATH
117
  )
118
  );
134
  return $this->oOffenseTracker;
135
  }
136
 
137
+ public function getScriptLocalisations() :array {
138
+ $locals = parent::getScriptLocalisations();
139
+
140
+ $locals[] = [
141
+ 'plugin',
142
+ 'icwp_wpsf_vars_ips',
143
+ [
144
+ 'components' => [
145
+ 'modal_ip_analysis' => [
146
+ 'ajax' => [
147
+ 'render_ip_analysis' => $this->getAjaxActionData( 'render_ip_analysis' )
148
+ ]
149
+ ],
150
+ ],
151
+ ]
152
+ ];
153
+ return $locals;
154
+ }
155
+
156
  public function getTextOptDefault( string $key ) :string {
157
 
158
  switch ( $key ) {
164
  );
165
  break;
166
 
 
 
 
 
 
 
 
 
 
167
  default:
168
  $text = parent::getTextOptDefault( $key );
169
  break;
src/lib/src/Modules/IPs/Options.php CHANGED
@@ -2,8 +2,8 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Options\WildCardOptions;
 
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class Options extends BaseShield\Options {
@@ -106,6 +106,19 @@ class Options extends BaseShield\Options {
106
  return $this->isSelectOptionEnabled( 'track_xmlrpc' );
107
  }
108
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  public function isTrackOptTransgression( string $key ) :bool {
110
  return strpos( $this->getOpt( $key ), 'transgression' ) !== false;
111
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Options\WildCardOptions;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class Options extends BaseShield\Options {
106
  return $this->isSelectOptionEnabled( 'track_xmlrpc' );
107
  }
108
 
109
+ public function getOffenseCountFor( string $key ) :int {
110
+ if ( $this->isTrackOptDoubleTransgression( $key ) ) {
111
+ $count = 2;
112
+ }
113
+ elseif ( $this->isTrackOptTransgression( $key ) ) {
114
+ $count = 1;
115
+ }
116
+ else {
117
+ $count = 0;
118
+ }
119
+ return $count;
120
+ }
121
+
122
  public function isTrackOptTransgression( string $key ) :bool {
123
  return strpos( $this->getOpt( $key ), 'transgression' ) !== false;
124
  }
src/lib/src/Modules/IPs/Processor.php CHANGED
@@ -2,11 +2,8 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\IPs\{
6
- EntryVO,
7
- Select
8
- };
9
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
 
10
 
11
  class Processor extends BaseShield\Processor {
12
 
@@ -18,16 +15,9 @@ class Processor extends BaseShield\Processor {
18
  }
19
 
20
  public function addAdminBarMenuGroup( array $groups ) :array {
21
- /** @var ModCon $mod */
22
- $mod = $this->getMod();
23
  $modInsights = $this->getCon()->getModule_Insights();
24
- /** @var Select $sel */
25
- $sel = $mod->getDbHandler_IPs()->getQuerySelector();
26
- /** @var EntryVO[] $IPs */
27
- $IPs = $sel->filterByBlocked( true )
28
- ->setOrderBy( 'blocked_at' )
29
- ->setLimit( 10 )
30
- ->query();
31
 
32
  if ( !empty( $IPs ) ) {
33
  $groups[] = [
@@ -43,12 +33,7 @@ class Processor extends BaseShield\Processor {
43
  ];
44
  }
45
 
46
- /** @var EntryVO[] $IPs */
47
- $IPs = $sel->filterByBlocked( false )
48
- ->filterByList( ModCon::LIST_AUTO_BLACK )
49
- ->setOrderBy( 'last_access_at' )
50
- ->setLimit( 10 )
51
- ->query();
52
  if ( !empty( $IPs ) ) {
53
  $groups[] = [
54
  'title' => __( 'Recent Offenses', 'wp-simple-firewall' ),
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs;
4
 
 
 
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Collate\RecentStats;
7
 
8
  class Processor extends BaseShield\Processor {
9
 
15
  }
16
 
17
  public function addAdminBarMenuGroup( array $groups ) :array {
 
 
18
  $modInsights = $this->getCon()->getModule_Insights();
19
+ $recentStats = ( new RecentStats() )->setCon( $this->getCon() );
20
+ $IPs = $recentStats->getRecentlyBlockedIPs();
 
 
 
 
 
21
 
22
  if ( !empty( $IPs ) ) {
23
  $groups[] = [
33
  ];
34
  }
35
 
36
+ $IPs = $recentStats->getRecentlyOffendedIPs();
 
 
 
 
 
37
  if ( !empty( $IPs ) ) {
38
  $groups[] = [
39
  'title' => __( 'Recent Offenses', 'wp-simple-firewall' ),
src/lib/src/Modules/IPs/Rules/Build/BotTrack404.php ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
7
+ Build\BuildRuleCoreShieldBase,
8
+ Conditions,
9
+ Responses
10
+ };
11
+
12
+ class BotTrack404 extends BuildRuleCoreShieldBase {
13
+
14
+ const SLUG = 'shield/is_bot_probe_404';
15
+
16
+ protected function getName() :string {
17
+ return 'Bot-Track 404';
18
+ }
19
+
20
+ protected function getDescription() :string {
21
+ return 'Tracking HTTP 404 errors by bots probing a site';
22
+ }
23
+
24
+ protected function getConditions() :array {
25
+ return [
26
+ 'logic' => static::LOGIC_AND,
27
+ 'group' => [
28
+ [
29
+ 'rule' => Shield\Modules\Plugin\Rules\Build\RequestBypassesAllRestrictions::SLUG,
30
+ 'invert_match' => true
31
+ ],
32
+ [
33
+ 'condition' => Conditions\IsNotLoggedInNormal::SLUG
34
+ ],
35
+ [
36
+ 'condition' => Conditions\MatchRequestStatusCode::SLUG,
37
+ 'params' => [
38
+ 'code' => '404',
39
+ ],
40
+ ],
41
+ [
42
+ 'logic' => static::LOGIC_OR,
43
+ 'group' => [
44
+ [
45
+ 'condition' => Conditions\NotMatchRequestPath::SLUG,
46
+ 'params' => [
47
+ 'is_match_regex' => true,
48
+ 'match_paths' => [
49
+ sprintf( "\\.(%s)$", implode( '|', $this->getAllowableExtensions() ) )
50
+ ],
51
+ ],
52
+ ],
53
+ [
54
+ 'condition' => Conditions\IsRequestToInvalidPlugin::SLUG,
55
+ ],
56
+ [
57
+ 'condition' => Conditions\IsRequestToInvalidTheme::SLUG,
58
+ ],
59
+ ]
60
+ ]
61
+ ]
62
+ ];
63
+ }
64
+
65
+ protected function getResponses() :array {
66
+ /** @var Shield\Modules\IPs\Options $opts */
67
+ $opts = $this->getOptions();
68
+ return [
69
+ [
70
+ 'response' => Responses\EventFire::SLUG,
71
+ 'params' => [
72
+ 'event' => 'bottrack_404',
73
+ 'offense_count' => $opts->getOffenseCountFor( 'track_404' ),
74
+ 'block' => $opts->isTrackOptImmediateBlock( 'track_404' ),
75
+ 'audit_params_map' => $this->getCommonAuditParamsMapping(),
76
+ ],
77
+ ],
78
+ ];
79
+ }
80
+
81
+ private function getAllowableExtensions() :array {
82
+ $defExt = $this->getOptions()->getDef( 'allowable_ext_404s' );
83
+ $extensions = apply_filters( 'shield/allowable_extensions_404s', $defExt );
84
+ return is_array( $extensions ) ? array_filter( $extensions ) : $defExt;
85
+ }
86
+ }
src/lib/src/Modules/IPs/Rules/Build/BotTrackFakeWebCrawler.php ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
7
+ Build\BuildRuleCoreShieldBase,
8
+ Conditions,
9
+ Responses
10
+ };
11
+ use FernleafSystems\Wordpress\Services\Services;
12
+
13
+ class BotTrackFakeWebCrawler extends BuildRuleCoreShieldBase {
14
+
15
+ const SLUG = 'shield/is_bot_probe_fakewebcrawler';
16
+
17
+ protected function getName() :string {
18
+ return 'Bot-Track Fake Web Crawler';
19
+ }
20
+
21
+ protected function getDescription() :string {
22
+ return 'Track probing bots that incorrectly identify as official web crawlers.';
23
+ }
24
+
25
+ protected function getConditions() :array {
26
+ return [
27
+ 'logic' => static::LOGIC_AND,
28
+ 'group' => [
29
+ [
30
+ 'rule' => Shield\Modules\Plugin\Rules\Build\RequestBypassesAllRestrictions::SLUG,
31
+ 'invert_match' => true
32
+ ],
33
+ [
34
+ 'condition' => Conditions\IsNotLoggedInNormal::SLUG
35
+ ],
36
+ [
37
+ 'condition' => Conditions\MatchRequestPath::SLUG,
38
+ 'params' => [
39
+ 'is_match_regex' => true,
40
+ 'match_paths' => [
41
+ '.*'
42
+ ],
43
+ ],
44
+ ],
45
+ [
46
+ 'condition' => Conditions\MatchRequestUseragent::SLUG,
47
+ 'params' => [
48
+ 'match_useragents' => Services::ServiceProviders()->getAllCrawlerUseragents(),
49
+ ],
50
+ ],
51
+ ]
52
+ ];
53
+ }
54
+
55
+ protected function getResponses() :array {
56
+ /** @var Shield\Modules\IPs\Options $opts */
57
+ $opts = $this->getOptions();
58
+ return [
59
+ [
60
+ 'response' => Responses\EventFire::SLUG,
61
+ 'params' => [
62
+ 'event' => 'bottrack_fakewebcrawler',
63
+ 'offense_count' => $opts->getOffenseCountFor( 'track_fakewebcrawler' ),
64
+ 'block' => $opts->isTrackOptImmediateBlock( 'track_fakewebcrawler' ),
65
+ 'audit_params_map' => $this->getCommonAuditParamsMapping(),
66
+ ],
67
+ ],
68
+ ];
69
+ }
70
+ }
src/lib/src/Modules/IPs/Rules/Build/BotTrackInvalidScript.php ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
7
+ Build\BuildRuleCoreShieldBase,
8
+ Conditions,
9
+ Responses
10
+ };
11
+
12
+ class BotTrackInvalidScript extends BuildRuleCoreShieldBase {
13
+
14
+ const SLUG = 'shield/is_bot_probe_invalidscript';
15
+
16
+ protected function getName() :string {
17
+ return 'Bot-Track Invalid Script';
18
+ }
19
+
20
+ protected function getDescription() :string {
21
+ return 'Track probing bots that send requests to invalid scripts.';
22
+ }
23
+
24
+ protected function getConditions() :array {
25
+ return [
26
+ 'logic' => static::LOGIC_AND,
27
+ 'group' => [
28
+ [
29
+ 'rule' => Shield\Modules\Plugin\Rules\Build\RequestBypassesAllRestrictions::SLUG,
30
+ 'invert_match' => true
31
+ ],
32
+ [
33
+ 'condition' => Conditions\IsNotLoggedInNormal::SLUG
34
+ ],
35
+ [
36
+ 'condition' => Conditions\MatchRequestScriptName::SLUG,
37
+ 'invert_match' => true,
38
+ 'params' => [
39
+ 'is_match_regex' => false,
40
+ 'match_script_names' => $this->getAllowedScripts(),
41
+ ],
42
+ ],
43
+ ]
44
+ ];
45
+ }
46
+
47
+ protected function getResponses() :array {
48
+ /** @var Shield\Modules\IPs\Options $opts */
49
+ $opts = $this->getOptions();
50
+ return [
51
+ [
52
+ 'response' => Responses\EventFire::SLUG,
53
+ 'params' => [
54
+ 'event' => 'bottrack_invalidscript',
55
+ 'offense_count' => $opts->getOffenseCountFor( 'track_invalidscript' ),
56
+ 'block' => $opts->isTrackOptImmediateBlock( 'track_invalidscript' ),
57
+ 'audit_params_map' => $this->getCommonAuditParamsMapping(),
58
+ ],
59
+ ],
60
+ ];
61
+ }
62
+
63
+ private function getAllowedScripts() :array {
64
+ return [
65
+ 'index.php',
66
+ 'admin-ajax.php',
67
+ 'wp-activate.php',
68
+ 'wp-links-opml.php',
69
+ 'wp-cron.php',
70
+ 'wp-login.php',
71
+ 'wp-mail.php',
72
+ 'wp-comments-post.php',
73
+ 'wp-signup.php',
74
+ 'wp-trackback.php',
75
+ 'xmlrpc.php',
76
+ 'admin.php',
77
+ ];
78
+ }
79
+ }
src/lib/src/Modules/IPs/Rules/Build/BotTrackXmlrpc.php ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
7
+ Build\BuildRuleCoreShieldBase,
8
+ Conditions,
9
+ Responses
10
+ };
11
+
12
+ class BotTrackXmlrpc extends BuildRuleCoreShieldBase {
13
+
14
+ const SLUG = 'shield/is_bot_probe_xmlrpc';
15
+
16
+ protected function getName() :string {
17
+ return 'Bot-Track XML-RPC';
18
+ }
19
+
20
+ protected function getDescription() :string {
21
+ return 'Track probing bots that send requests to XML-RPC.';
22
+ }
23
+
24
+ protected function getConditions() :array {
25
+ return [
26
+ 'logic' => static::LOGIC_AND,
27
+ 'group' => [
28
+ [
29
+ 'rule' => Shield\Modules\Plugin\Rules\Build\RequestBypassesAllRestrictions::SLUG,
30
+ 'invert_match' => true
31
+ ],
32
+ [
33
+ 'condition' => Conditions\IsNotLoggedInNormal::SLUG
34
+ ],
35
+ [
36
+ 'condition' => Conditions\WpIsXmlrpc::SLUG,
37
+ ],
38
+ [
39
+ 'condition' => Conditions\MatchRequestPath::SLUG,
40
+ 'params' => [
41
+ 'is_match_regex' => true,
42
+ 'match_paths' => [
43
+ '/xmlrpc\\.php$'
44
+ ],
45
+ ],
46
+ ],
47
+ ]
48
+ ];
49
+ }
50
+
51
+ protected function getResponses() :array {
52
+ /** @var Shield\Modules\IPs\Options $opts */
53
+ $opts = $this->getOptions();
54
+ return [
55
+ [
56
+ 'response' => Responses\EventFire::SLUG,
57
+ 'params' => [
58
+ 'event' => 'bottrack_xmlrpc',
59
+ 'offense_count' => $opts->getOffenseCountFor( 'track_xmlrpc' ),
60
+ 'block' => $opts->isTrackOptImmediateBlock( 'track_xmlrpc' ),
61
+ 'audit_params_map' => $this->getCommonAuditParamsMapping(),
62
+ ],
63
+ ],
64
+ ];
65
+ }
66
+ }
src/lib/src/Modules/IPs/Rules/Build/IpBlocked.php ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
8
+ Build\BuildRuleCoreShieldBase,
9
+ Conditions,
10
+ Responses
11
+ };
12
+
13
+ class IpBlocked extends BuildRuleCoreShieldBase {
14
+
15
+ const SLUG = 'shield/is_ip_blocked';
16
+
17
+ protected function getName() :string {
18
+ return 'Is IP Blocked';
19
+ }
20
+
21
+ protected function getDescription() :string {
22
+ return 'Test whether the current Request IP is Blocked.';
23
+ }
24
+
25
+ protected function getConditions() :array {
26
+ return [
27
+ 'logic' => static::LOGIC_AND,
28
+ 'group' => [
29
+ [
30
+ 'rule' => Plugin\Rules\Build\RequestBypassesAllRestrictions::SLUG,
31
+ 'invert_match' => true
32
+ ],
33
+ [
34
+ 'condition' => Conditions\IsIpBlocked::SLUG,
35
+ ],
36
+ [
37
+ 'condition' => Conditions\IsIpHighReputation::SLUG,
38
+ 'invert_match' => true
39
+ ],
40
+ ]
41
+ ];
42
+ }
43
+
44
+ protected function getResponses() :array {
45
+ return [
46
+ [
47
+ 'response' => Responses\SetIpBlocked::SLUG,
48
+ ],
49
+ ];
50
+ }
51
+ }
src/lib/src/Modules/IPs/Rules/Build/IpWhitelisted.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
7
+ Build\BuildRuleCoreShieldBase,
8
+ Conditions,
9
+ Responses
10
+ };
11
+
12
+ class IpWhitelisted extends BuildRuleCoreShieldBase {
13
+
14
+ const SLUG = 'shield/is_ip_whitelisted';
15
+
16
+ protected function getName() :string {
17
+ return 'Is IP Whitelisted';
18
+ }
19
+
20
+ protected function getDescription() :string {
21
+ return 'Test whether the current Request IP is whitelisted.';
22
+ }
23
+
24
+ protected function getConditions() :array {
25
+ return [
26
+ 'logic' => static::LOGIC_AND,
27
+ 'group' => [
28
+ [
29
+ 'condition' => Conditions\IsIpWhitelisted::SLUG
30
+ ],
31
+ ]
32
+ ];
33
+ }
34
+
35
+ protected function getResponses() :array {
36
+ return [
37
+ [
38
+ 'response' => Responses\SetIpWhitelisted::SLUG,
39
+ ],
40
+ ];
41
+ }
42
+ }
src/lib/src/Modules/IPs/Rules/Build/IsPathWhitelisted.php ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Options\WildCardOptions;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
8
+ Build\BuildRuleCoreShieldBase,
9
+ Conditions
10
+ };
11
+ use FernleafSystems\Wordpress\Services\Services;
12
+
13
+ class IsPathWhitelisted extends BuildRuleCoreShieldBase {
14
+
15
+ const SLUG = 'shield/is_path_whitelisted';
16
+
17
+ protected function getName() :string {
18
+ return 'Is Path Whitelisted';
19
+ }
20
+
21
+ protected function getDescription() :string {
22
+ return 'Test whether the current Request Path is whitelisted.';
23
+ }
24
+
25
+ protected function getConditions() :array {
26
+ /** @var Shield\Modules\IPs\Options $opts */
27
+ $opts = $this->getOptions();
28
+ return [
29
+ 'logic' => static::LOGIC_AND,
30
+ 'group' => [
31
+ [
32
+ 'condition' => Conditions\MatchRequestPath::SLUG,
33
+ 'params' => [
34
+ 'is_match_regex' => true,
35
+ 'match_paths' => $this->buildPaths(),
36
+ ]
37
+ ],
38
+ ]
39
+ ];
40
+ }
41
+
42
+ private function buildPaths() :array {
43
+ $homeUrlPath = (string)wp_parse_url( Services::WpGeneral()->getHomeUrl(), PHP_URL_PATH );
44
+ return array_map(
45
+ function ( $value ) use ( $homeUrlPath ) {
46
+ $regEx = ( new WildCardOptions() )->buildFullRegexValue( $value, WildCardOptions::URL_PATH, false );
47
+ if ( strpos( $regEx, $homeUrlPath ) !== 0 ) {
48
+ $regEx = '/'.ltrim( rtrim( $homeUrlPath, '/' ).'/'.ltrim( $regEx, '/' ), '/' );
49
+ }
50
+ return '^'.$regEx;
51
+ },
52
+ $this->getCon()->isPremiumActive() ? $this->getOptions()->getOpt( 'request_whitelist', [] ) : []
53
+ );
54
+ }
55
+ }
src/lib/src/Modules/IPs/Strings.php CHANGED
@@ -51,7 +51,8 @@ class Strings extends Base\Strings {
51
  'ip_block_auto' => [
52
  'name' => __( 'IP Block List Add (Auto)', 'wp-simple-firewall' ),
53
  'audit' => [
54
- __( "IP address '{{ip}}' automatically added to block list.", 'wp-simple-firewall' ),
 
55
  ],
56
  ],
57
  'ip_block_manual' => [
@@ -332,12 +333,6 @@ class Strings extends Base\Strings {
332
  $desc = __( 'This message is displayed if the visitor fails a login attempt.', 'wp-simple-firewall' );
333
  break;
334
 
335
- case 'text_remainingtrans' :
336
- $name = __( 'Remaining Offenses', 'wp-simple-firewall' );
337
- $summary = __( 'Visitor Triggers The IP Offenses System Through A Firewall Block', 'wp-simple-firewall' );
338
- $desc = __( 'This message is displayed if the visitor triggered the IP Offense system and reports how many offenses remain before being blocked.', 'wp-simple-firewall' );
339
- break;
340
-
341
  case 'track_404' :
342
  $name = __( '404 Detect', 'wp-simple-firewall' );
343
  $summary = __( 'Identify A Bot When It Hits A 404', 'wp-simple-firewall' );
@@ -354,23 +349,29 @@ class Strings extends Base\Strings {
354
  case 'track_xmlrpc' :
355
  $name = __( 'XML-RPC Access', 'wp-simple-firewall' );
356
  $summary = __( 'Identify A Bot When It Accesses XML-RPC', 'wp-simple-firewall' );
357
- $desc = __( "If you don't use XML-RPC, there's no reason anything should be accessing it.", 'wp-simple-firewall' )
358
- .'<br/>'.__( "Be careful the ensure you don't block legitimate XML-RPC traffic if your site needs it.", 'wp-simple-firewall' )
359
- .'<br/>'.__( "We recommend logging here in-case of blocking valid request unless you're sure.", 'wp-simple-firewall' );
 
 
360
  break;
361
 
362
  case 'track_linkcheese' :
363
  $name = __( 'Link Cheese', 'wp-simple-firewall' );
364
  $summary = __( 'Tempt A Bot With A Fake Link To Follow', 'wp-simple-firewall' );
365
- $desc = __( "Detect a bot when it follows a fake 'no-follow' link.", 'wp-simple-firewall' )
366
- .'<br/>'.__( "This works because legitimate web crawlers respect 'robots.txt' and 'nofollow' directives.", 'wp-simple-firewall' );
 
 
367
  break;
368
 
369
  case 'track_logininvalid' :
370
  $name = __( 'Invalid Usernames', 'wp-simple-firewall' );
371
  $summary = __( "Detect Attempted Logins With Usernames That Don't Exist", 'wp-simple-firewall' );
372
- $desc = __( "Identify a Bot when it tries to login with a non-existent username.", 'wp-simple-firewall' )
373
- .'<br/>'.__( "This includes the default 'admin' if you've removed that account.", 'wp-simple-firewall' );
 
 
374
  break;
375
 
376
  case 'track_loginfailed' :
@@ -437,6 +438,7 @@ class Strings extends Base\Strings {
437
  'frontpage' => __( 'Any Frontend Page Visited', 'wp-simple-firewall' ),
438
  'loginpage' => __( 'Login Page Visited', 'wp-simple-firewall' ),
439
  'bt404' => __( '404 Triggered', 'wp-simple-firewall' ),
 
440
  'btfake' => __( 'Fake Web Crawler', 'wp-simple-firewall' ),
441
  'btcheese' => __( 'Link Cheese', 'wp-simple-firewall' ),
442
  'btloginfail' => __( 'Login Fail', 'wp-simple-firewall' ),
51
  'ip_block_auto' => [
52
  'name' => __( 'IP Block List Add (Auto)', 'wp-simple-firewall' ),
53
  'audit' => [
54
+ __( "IP address '{{ip}}' automatically added to block list as an offender.", 'wp-simple-firewall' )
55
+ .' '.__( "The IP may not be blocked yet.", 'wp-simple-firewall' ),
56
  ],
57
  ],
58
  'ip_block_manual' => [
333
  $desc = __( 'This message is displayed if the visitor fails a login attempt.', 'wp-simple-firewall' );
334
  break;
335
 
 
 
 
 
 
 
336
  case 'track_404' :
337
  $name = __( '404 Detect', 'wp-simple-firewall' );
338
  $summary = __( 'Identify A Bot When It Hits A 404', 'wp-simple-firewall' );
349
  case 'track_xmlrpc' :
350
  $name = __( 'XML-RPC Access', 'wp-simple-firewall' );
351
  $summary = __( 'Identify A Bot When It Accesses XML-RPC', 'wp-simple-firewall' );
352
+ $desc = [
353
+ __( "If you don't use XML-RPC, there's no reason anything should be accessing it.", 'wp-simple-firewall' ),
354
+ __( "Be careful to ensure you don't block legitimate XML-RPC traffic if your site needs it.", 'wp-simple-firewall' ),
355
+ __( "We recommend logging here in-case of blocking valid request unless you're sure.", 'wp-simple-firewall' )
356
+ ];
357
  break;
358
 
359
  case 'track_linkcheese' :
360
  $name = __( 'Link Cheese', 'wp-simple-firewall' );
361
  $summary = __( 'Tempt A Bot With A Fake Link To Follow', 'wp-simple-firewall' );
362
+ $desc = [
363
+ __( "Detect a bot when it follows a fake 'no-follow' link.", 'wp-simple-firewall' ),
364
+ __( "This works because legitimate web crawlers respect 'robots.txt' and 'nofollow' directives.", 'wp-simple-firewall' )
365
+ ];
366
  break;
367
 
368
  case 'track_logininvalid' :
369
  $name = __( 'Invalid Usernames', 'wp-simple-firewall' );
370
  $summary = __( "Detect Attempted Logins With Usernames That Don't Exist", 'wp-simple-firewall' );
371
+ $desc = [
372
+ __( "Identify a Bot when it tries to login with a non-existent username.", 'wp-simple-firewall' ),
373
+ __( "This includes the default 'admin' if you've removed that account.", 'wp-simple-firewall' )
374
+ ];
375
  break;
376
 
377
  case 'track_loginfailed' :
438
  'frontpage' => __( 'Any Frontend Page Visited', 'wp-simple-firewall' ),
439
  'loginpage' => __( 'Login Page Visited', 'wp-simple-firewall' ),
440
  'bt404' => __( '404 Triggered', 'wp-simple-firewall' ),
441
+ 'btauthorfishing' => __( 'Username Fishing', 'wp-simple-firewall' ),
442
  'btfake' => __( 'Fake Web Crawler', 'wp-simple-firewall' ),
443
  'btcheese' => __( 'Link Cheese', 'wp-simple-firewall' ),
444
  'btloginfail' => __( 'Login Fail', 'wp-simple-firewall' ),
src/lib/src/Modules/Insights/AdminPage.php CHANGED
@@ -4,9 +4,4 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights;
4
 
5
  class AdminPage extends \FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield\AdminPage {
6
 
7
- protected function renderModulePage( array $data = [] ) :string {
8
- /** @var UI $UI */
9
- $UI = $this->getMod()->getUIHandler();
10
- return $UI->renderPages();
11
- }
12
  }
4
 
5
  class AdminPage extends \FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield\AdminPage {
6
 
 
 
 
 
 
7
  }
src/lib/src/Modules/Insights/AjaxHandler.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
 
6
 
7
  class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
8
 
@@ -10,12 +11,30 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
10
  $map = parent::getAjaxActionCallbackMap( $isAuth );
11
  if ( $isAuth ) {
12
  $map = array_merge( $map, [
13
- 'dynamic_load' => [ $this, 'ajaxExec_DynamicLoad' ],
 
14
  ] );
15
  }
16
  return $map;
17
  }
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  public function ajaxExec_DynamicLoad() :array {
20
  try {
21
  $pageData = ( new Lib\Requests\DynamicPageLoader() )
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
 
8
  class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
9
 
11
  $map = parent::getAjaxActionCallbackMap( $isAuth );
12
  if ( $isAuth ) {
13
  $map = array_merge( $map, [
14
+ 'dynamic_load' => [ $this, 'ajaxExec_DynamicLoad' ],
15
+ 'render_meter_analysis' => [ $this, 'ajaxExec_RenderMeterAnalysis' ],
16
  ] );
17
  }
18
  return $map;
19
  }
20
 
21
+ public function ajaxExec_RenderMeterAnalysis() :array {
22
+ try {
23
+ $html = ( new Lib\MeterAnalysis\Handler() )
24
+ ->setMod( $this->getMod() )
25
+ ->renderAnalysis( Services::Request()->post( 'meter' ) );
26
+ $success = true;
27
+ }
28
+ catch ( \Exception $e ) {
29
+ $html = $e->getMessage();
30
+ $success = false;
31
+ }
32
+ return [
33
+ 'success' => $success,
34
+ 'html' => $html,
35
+ ];
36
+ }
37
+
38
  public function ajaxExec_DynamicLoad() :array {
39
  try {
40
  $pageData = ( new Lib\Requests\DynamicPageLoader() )
src/lib/src/Modules/Insights/Lib/MeterAnalysis/Components.php ADDED
@@ -0,0 +1,1191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\Lib\MeterAnalysis;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\PluginControllerConsumer;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+ use FernleafSystems\Wordpress\Services\Utilities\Ssl;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\{
9
+ AuditTrail,
10
+ CommentsFilter,
11
+ Firewall,
12
+ HackGuard,
13
+ Headers,
14
+ Integrations,
15
+ IPs,
16
+ Lockdown,
17
+ LoginGuard,
18
+ Plugin,
19
+ SecurityAdmin,
20
+ Traffic,
21
+ UserManagement
22
+ };
23
+ use ZxcvbnPhp\Zxcvbn;
24
+
25
+ class Components {
26
+
27
+ use PluginControllerConsumer;
28
+
29
+ private static $components;
30
+
31
+ /**
32
+ * @throws \Exception
33
+ */
34
+ public function getComponent( string $slug ) :array {
35
+ $c = $this->components();
36
+ if ( !isset( $c[ $slug ] ) ) {
37
+ throw new \Exception( 'Component does not exist: '.$slug );
38
+ }
39
+ if ( is_callable( $c[ $slug ] ) ) {
40
+ self::$components[ $slug ] = $c[ $slug ]();
41
+ }
42
+
43
+ return self::$components[ $slug ];
44
+ }
45
+
46
+ /**
47
+ * @return array[]
48
+ * @throws \Exception
49
+ */
50
+ public function getComponents( array $slugs ) :array {
51
+ $components = [];
52
+ foreach ( $slugs as $slug ) {
53
+ $components[ $slug ] = $this->getComponent( $slug );
54
+ }
55
+ return $components;
56
+ }
57
+
58
+ public function getAllComponentsSlugs() :array {
59
+ return array_keys( $this->build() );
60
+ }
61
+
62
+ /**
63
+ * @return \Closure[]
64
+ */
65
+ private function components() :array {
66
+ if ( !isset( self::$components ) ) {
67
+ self::$components = $this->build();
68
+ }
69
+ return self::$components;
70
+ }
71
+
72
+ /**
73
+ * @return \Closure[]
74
+ */
75
+ private function build() :array {
76
+ $modFW = $this->getCon()->getModule_Firewall();
77
+ /** @var Firewall\Options $optsFW */
78
+ $optsFW = $modFW->getOptions();
79
+ /** @var Firewall\Strings $stringsFW */
80
+ $stringsFW = $modFW->getStrings();
81
+
82
+ $fireModEnabled = $modFW->isModOptEnabled();
83
+ $firewallComponents = [];
84
+ foreach (
85
+ [
86
+ 'dir_traversal',
87
+ 'wordpress_terms',
88
+ 'field_truncation',
89
+ 'php_code',
90
+ 'exe_file_uploads',
91
+ 'leading_schema',
92
+ 'aggressive'
93
+ ] as $firewallBlockKey
94
+ ) {
95
+ $firewallComponents[ 'fwb_'.$firewallBlockKey ] = [
96
+ 'title' => $stringsFW->getFirewallCategoryName( $firewallBlockKey ),
97
+ 'desc_protected' => __( 'Firewall is configured to block this category of requests.', 'wp-simple-firewall' ),
98
+ 'desc_unprotected' => __( "Firewall isn't configured to block this category of requests.", 'wp-simple-firewall' ),
99
+ 'href' => $fireModEnabled ?
100
+ $modFW->getUrl_DirectLinkToOption( 'block_'.$firewallBlockKey )
101
+ : $modFW->getUrl_DirectLinkToOption( 'enable_firewall' ),
102
+ 'protected' => $fireModEnabled && $optsFW->isOpt( 'block_'.$firewallBlockKey, 'Y' ),
103
+ 'weight' => 20,
104
+ ];
105
+ }
106
+
107
+ return array_merge(
108
+ $firewallComponents,
109
+ [
110
+ 'all' => function () {
111
+ $con = $this->getCon();
112
+ $meter = ( new MeterAll() )
113
+ ->setCon( $this->getCon() )
114
+ ->buildMeterComponents();
115
+
116
+ $weight = 200;
117
+ return [
118
+ 'title' => sprintf( __( 'Overall %s Configuration Summary', 'wp-simple-firewall' ), $con->getHumanName() ),
119
+ 'desc_protected' => sprintf( __( 'The cumulative score for your entire %s configuration', 'wp-simple-firewall' ), $con->getHumanName() ),
120
+ 'desc_unprotected' => sprintf( __( 'The cumulative score for your entire %s configuration', 'wp-simple-firewall' ), $con->getHumanName() ),
121
+ 'href' => '',
122
+ 'protected' => $meter[ 'totals' ][ 'percentage' ] > 75,
123
+ 'score' => $meter[ 'totals' ][ 'percentage' ]*$weight/100,
124
+ 'weight' => $weight,
125
+ 'original_score' => $meter[ 'totals' ][ 'percentage' ],
126
+ ];
127
+ },
128
+ 'shieldpro' => function () {
129
+ $con = $this->getCon();
130
+ return [
131
+ 'title' => __( 'ShieldPRO Premium Security', 'wp-simple-firewall' ),
132
+ 'desc_protected' => __( 'Your site benefits from additional security protection provided by ShieldPRO.', 'wp-simple-firewall' ),
133
+ 'desc_unprotected' => __( "Your site doesn't benefit from the additional security protection provided by ShieldPRO.", 'wp-simple-firewall' ),
134
+ 'href' => $con->getModule_Insights()->getUrl_SubInsightsPage( 'license' ),
135
+ 'protected' => $con->isPremiumActive(),
136
+ 'weight' => 35,
137
+ ];
138
+ },
139
+ 'comment_spam_antibot' => function () {
140
+ $modComments = $this->getCon()->getModule_Comments();
141
+ /** @var CommentsFilter\Options $optsComments */
142
+ $optsComments = $modComments->getOptions();
143
+ return [
144
+ 'title' => __( 'Bot Comment SPAM', 'wp-simple-firewall' ),
145
+ 'desc_protected' => __( 'Your site is protected against automated Comment SPAM by Bots.', 'wp-simple-firewall' ),
146
+ 'desc_unprotected' => __( "Your site isn't protected against automated Comment SPAM by Bots.", 'wp-simple-firewall' ),
147
+ 'href' => $modComments->isModOptEnabled() ?
148
+ $modComments->getUrl_DirectLinkToOption( 'enable_antibot_comments' )
149
+ : $modComments->getUrl_DirectLinkToOption( 'enable_comments_filter' ),
150
+ 'protected' => $modComments->isModOptEnabled() && $optsComments->isEnabledAntiBot(),
151
+ 'weight' => 75,
152
+ ];
153
+ },
154
+ 'comment_spam_human' => function () {
155
+ $modComments = $this->getCon()->getModule_Comments();
156
+ /** @var CommentsFilter\Options $optsComments */
157
+ $optsComments = $modComments->getOptions();
158
+ return [
159
+ 'title' => __( 'Human Comment SPAM', 'wp-simple-firewall' ),
160
+ 'desc_protected' => __( 'Your site is protected against Comment SPAM by humans.', 'wp-simple-firewall' ),
161
+ 'desc_unprotected' => __( "Your site isn't protected against Comment SPAM by humans.", 'wp-simple-firewall' ),
162
+ 'href' => $modComments->isModOptEnabled() ?
163
+ $modComments->getUrl_DirectLinkToOption( 'enable_comments_human_spam_filter' )
164
+ : $modComments->getUrl_DirectLinkToOption( 'enable_comments_filter' ),
165
+ 'protected' => $modComments->isModOptEnabled() && $optsComments->isEnabledHumanCheck(),
166
+ 'weight' => 25,
167
+ ];
168
+ },
169
+ 'tp_login_forms' => function () {
170
+ $modIntegrations = $this->getCon()->getModule_Integrations();
171
+ /** @var Integrations\Lib\Bots\Common\BaseHandler[] $installedButNotEnabledProviders */
172
+ $installedButNotEnabledProviders = array_filter(
173
+ array_map(
174
+ function ( $providerClass ) use ( $modIntegrations ) {
175
+ return ( new $providerClass() )->setMod( $modIntegrations );
176
+ },
177
+ $modIntegrations->getController_UserForms()->enumProviders()
178
+ ),
179
+ function ( $provider ) {
180
+ /** @var Integrations\Lib\Bots\Common\BaseHandler $provider */
181
+ return !$provider->isEnabled() && $provider::IsProviderInstalled();
182
+ }
183
+ );
184
+
185
+ $names = empty( $installedButNotEnabledProviders ) ? '' :
186
+ implode( ', ', array_map(
187
+ function ( $provider ) {
188
+ return $provider->getHandlerName();
189
+ }, $installedButNotEnabledProviders
190
+ ) );
191
+
192
+ return [
193
+ 'title' => __( '3rd Party Login Forms', 'wp-simple-firewall' ),
194
+ 'desc_protected' => __( "It appears that any 3rd party login forms you're using are protected against Bots.", 'wp-simple-firewall' ),
195
+ 'desc_unprotected' => sprintf( __( "It appears that certain 3rd party login forms aren't protected against Bots: %s", 'wp-simple-firewall' ), $names ),
196
+ 'href' => $modIntegrations->getUrl_DirectLinkToOption( 'user_form_providers' ),
197
+ 'protected' => empty( $names ),
198
+ 'weight' => 30,
199
+ ];
200
+ },
201
+ 'contact_forms_spam' => function () {
202
+ $modIntegrations = $this->getCon()->getModule_Integrations();
203
+ /** @var Integrations\Lib\Bots\Common\BaseHandler[] $installedButNotEnabledProviders */
204
+ $installedButNotEnabledProviders = array_filter(
205
+ array_map(
206
+ function ( $providerClass ) use ( $modIntegrations ) {
207
+ return ( new $providerClass() )->setMod( $modIntegrations );
208
+ },
209
+ $modIntegrations->getController_SpamForms()->enumProviders()
210
+ ),
211
+ function ( $provider ) {
212
+ /** @var Integrations\Lib\Bots\Common\BaseHandler $provider */
213
+ return !$provider->isEnabled() && $provider::IsProviderInstalled();
214
+ }
215
+ );
216
+
217
+ $names = empty( $installedButNotEnabledProviders ) ? '' :
218
+ implode( ', ', array_map(
219
+ function ( $provider ) {
220
+ return $provider->getHandlerName();
221
+ }, $installedButNotEnabledProviders
222
+ ) );
223
+
224
+ return [
225
+ 'title' => __( '3rd Party Contact Form SPAM', 'wp-simple-firewall' ),
226
+ 'desc_protected' => __( "It appears that any contact forms you're using are protected against Bot SPAM.", 'wp-simple-firewall' ),
227
+ 'desc_unprotected' => sprintf( __( "It appears that certain contact forms aren't protected against Bot SPAM: %s", 'wp-simple-firewall' ), $names ),
228
+ 'href' => $modIntegrations->getUrl_DirectLinkToOption( 'form_spam_providers' ),
229
+ 'protected' => empty( $names ),
230
+ 'weight' => 30,
231
+ ];
232
+ },
233
+ 'comment_approved_minimum' => function () {
234
+ $modComments = $this->getCon()->getModule_Comments();
235
+ /** @var CommentsFilter\Options $optsComments */
236
+ $optsComments = $modComments->getOptions();
237
+ return [
238
+ 'title' => __( 'Minimum Comment Auto-Approval', 'wp-simple-firewall' ),
239
+ 'desc_protected' => __( 'Comments are auto-approved only if they have at least 1 other approved comment.', 'wp-simple-firewall' ),
240
+ 'desc_unprotected' => __( "Comments are auto-approved only if they have at least 1 other approved comment.", 'wp-simple-firewall' ),
241
+ 'href' => $modComments->isModOptEnabled() ?
242
+ $modComments->getUrl_DirectLinkToOption( 'trusted_commenter_minimum' )
243
+ : $modComments->getUrl_DirectLinkToOption( 'enable_comments_filter' ),
244
+ 'protected' => $modComments->isModOptEnabled() && $optsComments->getApprovedMinimum() > 1,
245
+ 'weight' => 10,
246
+ ];
247
+ },
248
+ 'admin_user' => function () {
249
+ $WPUsers = Services::WpUsers();
250
+ $adminUser = $WPUsers->getUserByUsername( 'admin' );
251
+ return [
252
+ 'title' => __( 'Default Admin User', 'wp-simple-firewall' ),
253
+ 'desc_protected' => __( "The default 'admin' user is no longer available.", 'wp-simple-firewall' ),
254
+ 'desc_unprotected' => __( "The default 'admin' user is still available.", 'wp-simple-firewall' ),
255
+ 'href' => $adminUser instanceof \WP_User ? $WPUsers->getAdminUrl_ProfileEdit( $adminUser ) : '',
256
+ 'protected' => !$adminUser instanceof \WP_User || !user_can( $adminUser, 'manage_options' ),
257
+ 'weight' => 5,
258
+ ];
259
+ },
260
+ 'cooldown' => function () {
261
+ $modLG = $this->getCon()->getModule_LoginGuard();
262
+ /** @var LoginGuard\Options $optsLG */
263
+ $optsLG = $modLG->getOptions();
264
+ return [
265
+ 'title' => __( 'Login Cooldown', 'wp-simple-firewall' ),
266
+ 'desc_protected' => __( 'Login Cooldown system is helping prevent brute force attacks by limiting login attempts.', 'wp-simple-firewall' ),
267
+ 'desc_unprotected' => __( "Brute force login attacks are not blocked by the login cooldown system.", 'wp-simple-firewall' ),
268
+ 'href' => $modLG->isModOptEnabled() ?
269
+ $modLG->getUrl_DirectLinkToOption( 'login_limit_interval' )
270
+ : $modLG->getUrl_DirectLinkToOption( 'enable_login_protect' ),
271
+ 'protected' => $modLG->isModOptEnabled() && $optsLG->isEnabledCooldown(),
272
+ 'weight' => 20,
273
+ ];
274
+ },
275
+ 'ade_loginguard' => function () {
276
+ $modLG = $this->getCon()->getModule_LoginGuard();
277
+ /** @var LoginGuard\Options $optsLG */
278
+ $optsLG = $modLG->getOptions();
279
+ return [
280
+ 'title' => __( 'AntiBot Detection Engine For Logins', 'wp-simple-firewall' ),
281
+ 'desc_protected' => __( 'The AntiBot Detection Engine option is enabled.', 'wp-simple-firewall' ),
282
+ 'desc_unprotected' => __( 'The AntiBot Detection Engine option is disabled, removing brute force protection for login, register and lost password forms.', 'wp-simple-firewall' ),
283
+ 'href' => $modLG->isModOptEnabled() ?
284
+ $modLG->getUrl_DirectLinkToOption( 'enable_antibot_check' )
285
+ : $modLG->getUrl_DirectLinkToOption( 'enable_login_protect' ),
286
+ 'protected' => $modLG->isModOptEnabled() && $optsLG->isEnabledAntiBot(),
287
+ 'weight' => 30,
288
+ ];
289
+ },
290
+ 'ade_login' => function () {
291
+ $modLG = $this->getCon()->getModule_LoginGuard();
292
+ /** @var LoginGuard\Options $optsLG */
293
+ $optsLG = $modLG->getOptions();
294
+ return [
295
+ 'title' => __( 'Login Bot Protection', 'wp-simple-firewall' ),
296
+ 'desc_protected' => __( 'Brute force bot attacks against your WordPress login are blocked by the AntiBot Detection Engine.', 'wp-simple-firewall' ),
297
+ 'desc_unprotected' => __( "Brute force login attacks by bots aren't being blocked.", 'wp-simple-firewall' ),
298
+ 'href' => $modLG->isModOptEnabled() ?
299
+ $modLG->getUrl_DirectLinkToOption( 'bot_protection_locations' )
300
+ : $modLG->getUrl_DirectLinkToOption( 'enable_login_protect' ),
301
+ 'protected' => $modLG->isModOptEnabled() && $optsLG->isEnabledAntiBot() && $optsLG->isProtectLogin(),
302
+ 'weight' => 30,
303
+ ];
304
+ },
305
+ 'ade_register' => function () {
306
+ $modLG = $this->getCon()->getModule_LoginGuard();
307
+ /** @var LoginGuard\Options $optsLG */
308
+ $optsLG = $modLG->getOptions();
309
+ return [
310
+ 'title' => __( 'Register Bot Protection', 'wp-simple-firewall' ),
311
+ 'desc_protected' => __( 'SPAM and bulk user registration by bots are blocked by the AntiBot Detection Engine.', 'wp-simple-firewall' ),
312
+ 'desc_unprotected' => __( "SPAM and bulk user registration by bots aren't being blocked.", 'wp-simple-firewall' ),
313
+ 'href' => $modLG->isModOptEnabled() ?
314
+ $modLG->getUrl_DirectLinkToOption( 'bot_protection_locations' )
315
+ : $modLG->getUrl_DirectLinkToOption( 'enable_login_protect' ),
316
+ 'protected' => $modLG->isModOptEnabled() && $optsLG->isEnabledAntiBot() && $optsLG->isProtectRegister(),
317
+ 'weight' => 30,
318
+ ];
319
+ },
320
+ 'ade_lostpassword' => function () {
321
+ $modLG = $this->getCon()->getModule_LoginGuard();
322
+ /** @var LoginGuard\Options $optsLG */
323
+ $optsLG = $modLG->getOptions();
324
+ return [
325
+ 'title' => __( 'Lost Password Bot Protection', 'wp-simple-firewall' ),
326
+ 'desc_protected' => __( 'Lost Password SPAMing by bots are blocked by the AntiBot Detection Engine.', 'wp-simple-firewall' ),
327
+ 'desc_unprotected' => __( "Lost Password SPAMing by bots aren't being blocked.", 'wp-simple-firewall' ),
328
+ 'href' => $modLG->isModOptEnabled() ?
329
+ $modLG->getUrl_DirectLinkToOption( 'bot_protection_locations' )
330
+ : $modLG->getUrl_DirectLinkToOption( 'enable_login_protect' ),
331
+ 'protected' => $modLG->isModOptEnabled() && $optsLG->isEnabledAntiBot() && $optsLG->isProtectLostPassword(),
332
+ 'weight' => 30,
333
+ ];
334
+ },
335
+ '2fa' => function () {
336
+ $modLG = $this->getCon()->getModule_LoginGuard();
337
+ /** @var LoginGuard\Options $optsLG */
338
+ $optsLG = $modLG->getOptions();
339
+ return [
340
+ 'title' => __( '2-Factor Authentication', 'wp-simple-firewall' ),
341
+ 'desc_protected' => __( 'At least 1 2FA option is available to help users protect their accounts.', 'wp-simple-firewall' ),
342
+ 'desc_unprotected' => __( "There are no 2FA options made available to help users protect their accounts.", 'wp-simple-firewall' ),
343
+ 'href' => $modLG->isModOptEnabled() ?
344
+ $modLG->getUrl_DirectLinkToOption( 'enable_email_authentication' )
345
+ : $modLG->getUrl_DirectLinkToOption( 'enable_login_protect' ),
346
+ 'protected' => $modLG->isModOptEnabled()
347
+ && ( $optsLG->isEmailAuthenticationActive()
348
+ || $optsLG->isEnabledGoogleAuthenticator()
349
+ || $optsLG->isEnabledYubikey()
350
+ || $optsLG->isEnabledU2F() ),
351
+ 'weight' => 30,
352
+ ];
353
+ },
354
+ 'pass_policies' => function () {
355
+ $modUM = $this->getCon()->getModule_UserManagement();
356
+ /** @var UserManagement\Options $optsUM */
357
+ $optsUM = $modUM->getOptions();
358
+ return [
359
+ 'title' => __( 'Password Policies', 'wp-simple-firewall' ),
360
+ 'desc_protected' => __( 'Password policies are enabled to help promote good password hygiene.', 'wp-simple-firewall' ),
361
+ 'desc_unprotected' => __( "Password polices aren't enabled which may lead to poor password hygiene.", 'wp-simple-firewall' ),
362
+ 'href' => $modUM->isModOptEnabled() ?
363
+ $modUM->getUrl_DirectLinkToOption( 'enable_password_policies' )
364
+ : $modUM->getUrl_DirectLinkToOption( 'enable_user_management' ),
365
+ 'protected' => $modUM->isModOptEnabled() && $optsUM->isPasswordPoliciesEnabled(),
366
+ 'weight' => 30,
367
+ ];
368
+ },
369
+ 'user_email_validation' => function () {
370
+ $modUM = $this->getCon()->getModule_UserManagement();
371
+ /** @var UserManagement\Options $optsUM */
372
+ $optsUM = $modUM->getOptions();
373
+ return [
374
+ 'title' => __( 'User Registration Email Validation', 'wp-simple-firewall' ),
375
+ 'desc_protected' => __( 'Newly registered users have their email address checked for valid and non-SPAM domain names.', 'wp-simple-firewall' ),
376
+ 'desc_unprotected' => __( "Newly registered users don't have their email address checked for valid and non-SPAM domain names.", 'wp-simple-firewall' ),
377
+ 'href' => $modUM->isModOptEnabled() ?
378
+ $modUM->getUrl_DirectLinkToOption( 'reg_email_validate' )
379
+ : $modUM->getUrl_DirectLinkToOption( 'enable_user_management' ),
380
+ 'protected' => $modUM->isModOptEnabled() && $optsUM->isValidateEmailOnRegistration(),
381
+ 'weight' => 30,
382
+ ];
383
+ },
384
+ 'pass_pwned' => function () {
385
+ $modUM = $this->getCon()->getModule_UserManagement();
386
+ /** @var UserManagement\Options $optsUM */
387
+ $optsUM = $modUM->getOptions();
388
+ return [
389
+ 'title' => __( 'Pwned Passwords', 'wp-simple-firewall' ),
390
+ 'desc_protected' => __( 'Pwned passwords are blocked from being set by any user.', 'wp-simple-firewall' ),
391
+ 'desc_unprotected' => __( "Pwned passwords are allowed to be used.", 'wp-simple-firewall' ),
392
+ 'href' => $modUM->isModOptEnabled() ?
393
+ $modUM->getUrl_DirectLinkToOption( 'pass_prevent_pwned' )
394
+ : $modUM->getUrl_DirectLinkToOption( 'enable_user_management' ),
395
+ 'protected' => $modUM->isModOptEnabled() && $optsUM->isPasswordPoliciesEnabled() && $optsUM->isPassPreventPwned(),
396
+ 'weight' => 30,
397
+ ];
398
+ },
399
+ 'pass_str' => function () {
400
+ $modUM = $this->getCon()->getModule_UserManagement();
401
+ /** @var UserManagement\Options $optsUM */
402
+ $optsUM = $modUM->getOptions();
403
+ return [
404
+ 'title' => __( 'Strong Passwords', 'wp-simple-firewall' ),
405
+ 'desc_protected' => __( 'All new passwords are required to be be of high strength.', 'wp-simple-firewall' ),
406
+ 'desc_unprotected' => __( "There is no requirement for strong user passwords.", 'wp-simple-firewall' ),
407
+ 'href' => $modUM->isModOptEnabled() ?
408
+ $modUM->getUrl_DirectLinkToOption( 'pass_min_strength' )
409
+ : $modUM->getUrl_DirectLinkToOption( 'enable_user_management' ),
410
+ 'protected' => $modUM->isModOptEnabled() && $optsUM->isPasswordPoliciesEnabled() && $optsUM->getPassMinStrength() >= 3,
411
+ 'weight' => 20,
412
+ ];
413
+ },
414
+ 'plugin_badge' => function () {
415
+ $modPlugin = $this->getCon()->getModule_Plugin();
416
+ /** @var Plugin\Options $optsPlugin */
417
+ $optsPlugin = $modPlugin->getOptions();
418
+ return [
419
+ 'title' => __( 'Plugin Security Badge', 'wp-simple-firewall' ),
420
+ 'desc_protected' => __( 'Your customers and visitors are reassured that you take their security seriously.', 'wp-simple-firewall' ),
421
+ 'desc_unprotected' => __( "Your customers and visitors aren't given reassurance that you take their security seriously.", 'wp-simple-firewall' ),
422
+ 'href' => $modPlugin->isModOptEnabled() ?
423
+ $modPlugin->getUrl_DirectLinkToOption( 'display_plugin_badge' )
424
+ : $modPlugin->getUrl_DirectLinkToOption( 'global_enable_plugin_features' ),
425
+ 'protected' => $modPlugin->isModOptEnabled() && $optsPlugin->isOpt( 'display_plugin_badge', 'Y' ),
426
+ 'weight' => 5,
427
+ ];
428
+ },
429
+ 'db_password' => function () {
430
+ return [
431
+ 'title' => __( 'MySQL DB Password', 'wp-simple-firewall' ),
432
+ 'desc_protected' => __( 'WP Database password is very strong.', 'wp-simple-firewall' ),
433
+ 'desc_unprotected' => __( "WP Database password appears to be weak.", 'wp-simple-firewall' ),
434
+ 'href' => '',
435
+ 'protected' => ( ( new Zxcvbn() )->passwordStrength( DB_PASSWORD )[ 'score' ] ?? 0 ) >= 4,
436
+ 'weight' => 25,
437
+ ];
438
+ },
439
+ 'audit_trail_enabled' => function () {
440
+ $modAudit = $this->getCon()->getModule_AuditTrail();
441
+ /** @var AuditTrail\Options $optsAudit */
442
+ $optsAudit = $modAudit->getOptions();
443
+ return [
444
+ 'title' => __( 'Activity Logging', 'wp-simple-firewall' ),
445
+ 'desc_protected' => __( 'Tracking activity with the Audit Trail is enabled making it easier to track issues.', 'wp-simple-firewall' ),
446
+ 'desc_unprotected' => __( "Activity tracking with the Audit Trail is disabled making it harder to track issues.", 'wp-simple-firewall' ),
447
+ 'href' => $modAudit->isModOptEnabled() ?
448
+ $modAudit->getUrl_DirectLinkToOption( 'section_localdb' )
449
+ : $modAudit->getUrl_DirectLinkToOption( 'enable_audit_trail' ),
450
+ 'protected' => $modAudit->isModOptEnabled() && $optsAudit->isLogToDB(),
451
+ 'weight' => 25,
452
+ ];
453
+ },
454
+ 'traffic_log_enabled' => function () {
455
+ $modTraffic = $this->getCon()->getModule_Traffic();
456
+ /** @var Traffic\Options $optsTraffic */
457
+ $optsTraffic = $modTraffic->getOptions();
458
+ return [
459
+ 'title' => __( 'Traffic Logging', 'wp-simple-firewall' ),
460
+ 'desc_protected' => __( 'Traffic requests are being logged, making it easier to track issues.', 'wp-simple-firewall' ),
461
+ 'desc_unprotected' => __( "Traffic requests aren't being logged, making it harder to track issues.", 'wp-simple-firewall' ),
462
+ 'href' => $modTraffic->isModOptEnabled() ?
463
+ $modTraffic->getUrl_DirectLinkToOption( 'enable_logger' )
464
+ : $modTraffic->getUrl_DirectLinkToOption( 'enable_traffic' ),
465
+ 'protected' => $modTraffic->isModOptEnabled() && $optsTraffic->isTrafficLoggerEnabled(),
466
+ 'weight' => 25,
467
+ ];
468
+ },
469
+ 'traffic_rate_limiting' => function () {
470
+ $modTraffic = $this->getCon()->getModule_Traffic();
471
+ /** @var Traffic\Options $optsTraffic */
472
+ $optsTraffic = $modTraffic->getOptions();
473
+ return [
474
+ 'title' => __( 'Traffic Rate Limiting', 'wp-simple-firewall' ),
475
+ 'desc_protected' => __( 'Traffic rate limiting is enabled reducing the likelihood that bots can overwhelm your site.', 'wp-simple-firewall' ),
476
+ 'desc_unprotected' => __( "Traffic is never rate limited meaning abusive bots and crawlers may consume resources without limits and potentially overload your system.", 'wp-simple-firewall' ),
477
+ 'href' => $modTraffic->isModOptEnabled() ?
478
+ $modTraffic->getUrl_DirectLinkToOption( 'enable_limiter' )
479
+ : $modTraffic->getUrl_DirectLinkToOption( 'enable_traffic' ),
480
+ 'protected' => $modTraffic->isModOptEnabled() && $optsTraffic->isTrafficLimitEnabled(),
481
+ 'weight' => 35,
482
+ ];
483
+ },
484
+ 'scanresults_apc' => function () {
485
+ $results = $this->getCon()->getModule_HackGuard()->getScansCon()->getScanResultsCount();
486
+ $hasResults = $results->countAbandoned() > 0;
487
+ return [
488
+ 'title' => __( 'Abandoned Plugins Found', 'wp-simple-firewall' ),
489
+ 'desc_protected' => __( "There doesn't appear to be any abandoned plugins on your site.", 'wp-simple-firewall' ),
490
+ 'desc_unprotected' => __( "There appears to be at least 1 abandoned plugin installed on your site.", 'wp-simple-firewall' ),
491
+ 'href' => $this->getUrlForScanResults(),
492
+ 'protected' => !$hasResults,
493
+ 'weight' => $hasResults ? 45 : 10,
494
+ 'is_critical' => $hasResults
495
+ ];
496
+ },
497
+ 'scanresults_mal' => function () {
498
+ $results = $this->getCon()->getModule_HackGuard()->getScansCon()->getScanResultsCount();
499
+ $hasResults = $results->countMalware() > 0;
500
+ return [
501
+ 'title' => $hasResults ? __( 'Potential Malware Found', 'wp-simple-firewall' ) : __( 'No Potential Malware Found', 'wp-simple-firewall' ),
502
+ 'desc_protected' => __( "There doesn't appear to be any PHP malware files on your site.", 'wp-simple-firewall' ),
503
+ 'desc_unprotected' => __( "There appears to be at least 1 PHP malware file on your site.", 'wp-simple-firewall' ),
504
+ 'href' => $this->getUrlForScanResults(),
505
+ 'protected' => !$hasResults,
506
+ 'weight' => $hasResults ? 55 : 10,
507
+ 'is_critical' => $hasResults
508
+ ];
509
+ },
510
+ 'scanresults_wcf' => function () {
511
+ $results = $this->getCon()->getModule_HackGuard()->getScansCon()->getScanResultsCount();
512
+ $hasResults = $results->countWPFiles() > 0;
513
+ return [
514
+ 'title' => $hasResults ? __( 'WordPress Files Modified', 'wp-simple-firewall' ) : __( 'No Modified WordPress Files Found', 'wp-simple-firewall' ),
515
+ 'desc_protected' => __( "All WordPress Core files appear to be clean and unmodified.", 'wp-simple-firewall' ),
516
+ 'desc_unprotected' => __( "At least 1 WordPress Core file appears to be modified or unrecognised.", 'wp-simple-firewall' ),
517
+ 'href' => $this->getUrlForScanResults(),
518
+ 'protected' => !$hasResults,
519
+ 'weight' => $hasResults ? 55 : 10,
520
+ 'is_critical' => $hasResults
521
+ ];
522
+ },
523
+ 'scanresults_ptg' => function () {
524
+ $results = $this->getCon()->getModule_HackGuard()->getScansCon()->getScanResultsCount();
525
+ $hasResults = ( $results->countPluginFiles() + $results->countThemeFiles() ) > 0;
526
+ return [
527
+ 'title' => $hasResults ? __( 'Modified Plugin/Theme Files Found', 'wp-simple-firewall' ) : __( 'No Modified Plugin/Theme Files Found', 'wp-simple-firewall' ),
528
+ 'desc_protected' => __( "All plugin & theme files appear to be valid.", 'wp-simple-firewall' ),
529
+ 'desc_unprotected' => __( "At least 1 of your plugins or themes appears to be modified.", 'wp-simple-firewall' ),
530
+ 'href' => $this->getUrlForScanResults(),
531
+ 'protected' => !$hasResults,
532
+ 'weight' => $hasResults ? 55 : 10,
533
+ 'is_critical' => $hasResults
534
+ ];
535
+ },
536
+ 'scanresults_wpv' => function () {
537
+ $results = $this->getCon()->getModule_HackGuard()->getScansCon()->getScanResultsCount();
538
+ $hasResults = $results->countVulnerableAssets() > 0;
539
+ return [
540
+ 'title' => $hasResults ? __( 'Vulnerable Assets Found', 'wp-simple-firewall' ) : __( 'No Vulnerable Assets Found', 'wp-simple-firewall' ),
541
+ 'desc_protected' => __( "There doesn't appear to be any plugins or themes with known vulnerabilities.", 'wp-simple-firewall' ),
542
+ 'desc_unprotected' => __( "There appears to be at least 1 vulnerable plugin or theme installed on your site.", 'wp-simple-firewall' ),
543
+ 'href' => $this->getUrlForScanResults(),
544
+ 'protected' => !$hasResults,
545
+ 'weight' => $hasResults ? 55 : 10,
546
+ 'is_critical' => $hasResults
547
+ ];
548
+ },
549
+ 'report_email' => function () {
550
+ $modPlugin = $this->getCon()->getModule_Plugin();
551
+ /** @var Plugin\Options $optsPlugin */
552
+ $optsPlugin = $modPlugin->getOptions();
553
+ return [
554
+ 'title' => __( 'Report Email', 'wp-simple-firewall' ),
555
+ 'desc_protected' => __( 'Email address has been provided for reporting important security notices.', 'wp-simple-firewall' ),
556
+ 'desc_unprotected' => __( "An email address hasn't been provided for reporting important security notices.", 'wp-simple-firewall' )
557
+ .' '.__( 'A default will be used.', 'wp-simple-firewall' ),
558
+ 'href' => $modPlugin->getUrl_DirectLinkToOption( 'block_send_email_address' ),
559
+ 'protected' => Services::Data()
560
+ ->validEmail( $optsPlugin->getOpt( 'block_send_email_address' ) ),
561
+ 'weight' => 10,
562
+ ];
563
+ },
564
+ 'headers' => function () {
565
+ $modHeaders = $this->getCon()->getModule_Headers();
566
+ /** @var Headers\Options $optsHeaders */
567
+ $optsHeaders = $modHeaders->getOptions();
568
+ return [
569
+ 'title' => __( 'HTTP Headers', 'wp-simple-firewall' ),
570
+ 'desc_protected' => __( 'Important HTTP Headers are helping to protect visitors.', 'wp-simple-firewall' ),
571
+ 'desc_unprotected' => __( "Important HTTP Headers aren't being used to help protect visitors.", 'wp-simple-firewall' ),
572
+ 'href' => $modHeaders->isModOptEnabled() ?
573
+ $modHeaders->getUrl_DirectLinkToOption( 'section_security_headers' )
574
+ : $modHeaders->getUrl_DirectLinkToOption( 'enable_headers' ),
575
+ 'protected' => $modHeaders->isModOptEnabled() && $optsHeaders->isEnabledXFrame()
576
+ && $optsHeaders->isEnabledXssProtection() && $optsHeaders->isEnabledContentTypeHeader()
577
+ && $optsHeaders->isReferrerPolicyEnabled(),
578
+ 'weight' => 10,
579
+ ];
580
+ },
581
+ 'file_scanner' => function () {
582
+ $modHG = $this->getCon()->getModule_HackGuard();
583
+ $scansCon = $modHG->getScansCon();
584
+ /** @var HackGuard\Scan\Controller\Afs $afsCon */
585
+ $afsCon = $scansCon->getScanCon( HackGuard\Scan\Controller\Afs::SCAN_SLUG );
586
+ return [
587
+ 'title' => __( 'WordPress File Scanner', 'wp-simple-firewall' ),
588
+ 'desc_protected' => __( 'WordPress file scanner is enabled.', 'wp-simple-firewall' ),
589
+ 'desc_unprotected' => __( "WordPress file scanner isn't enabled.", 'wp-simple-firewall' ),
590
+ 'href' => $modHG->isModOptEnabled() ?
591
+ $modHG->getUrl_DirectLinkToOption( 'enable_core_file_integrity_scan' )
592
+ : $modHG->getUrl_DirectLinkToOption( 'enable_hack_protect' ),
593
+ 'protected' => $modHG->isModOptEnabled() && $afsCon->isEnabled(),
594
+ 'weight' => 40,
595
+ ];
596
+ },
597
+ 'malware_scanner' => function () {
598
+ $modHG = $this->getCon()->getModule_HackGuard();
599
+ $scansCon = $modHG->getScansCon();
600
+ /** @var HackGuard\Scan\Controller\Afs $afsCon */
601
+ $afsCon = $scansCon->getScanCon( HackGuard\Scan\Controller\Afs::SCAN_SLUG );
602
+ return [
603
+ 'title' => __( 'PHP Malware Scanner', 'wp-simple-firewall' ),
604
+ 'desc_protected' => __( 'PHP malware scanner is enabled.', 'wp-simple-firewall' ),
605
+ 'desc_unprotected' => __( "PHP malware scanner isn't enabled.", 'wp-simple-firewall' ),
606
+ 'href' => $modHG->isModOptEnabled() ?
607
+ $modHG->getUrl_DirectLinkToOption( 'enable_core_file_integrity_scan' )
608
+ : $modHG->getUrl_DirectLinkToOption( 'enable_hack_protect' ),
609
+ 'protected' => $modHG->isModOptEnabled() && $afsCon->isEnabledMalwareScan(),
610
+ 'weight' => 30,
611
+ ];
612
+ },
613
+ 'apc_scanner' => function () {
614
+ $modHG = $this->getCon()->getModule_HackGuard();
615
+ $scansCon = $modHG->getScansCon();
616
+ return [
617
+ 'title' => __( 'Abandoned WordPress.org Plugins', 'wp-simple-firewall' ),
618
+ 'desc_protected' => __( 'Detection of abandoned WordPress.org plugins is enabled.', 'wp-simple-firewall' ),
619
+ 'desc_unprotected' => __( "Detection of abandoned WordPress.org plugins isn't enabled.", 'wp-simple-firewall' ),
620
+ 'href' => $modHG->isModOptEnabled() ?
621
+ $modHG->getUrl_DirectLinkToOption( 'enabled_scan_apc' )
622
+ : $modHG->getUrl_DirectLinkToOption( 'enable_hack_protect' ),
623
+ 'protected' => $modHG->isModOptEnabled()
624
+ && $scansCon->getScanCon( HackGuard\Scan\Controller\Apc::SCAN_SLUG )
625
+ ->isEnabled(),
626
+ 'weight' => 30,
627
+ ];
628
+ },
629
+ 'wpv_scanner' => function () {
630
+ $modHG = $this->getCon()->getModule_HackGuard();
631
+ $scansCon = $modHG->getScansCon();
632
+ return [
633
+ 'title' => __( 'Vulnerable Plugins & Themes', 'wp-simple-firewall' ),
634
+ 'desc_protected' => __( 'Plugins and Themes are scanned for known vulnerabilities.', 'wp-simple-firewall' ),
635
+ 'desc_unprotected' => __( "Plugins and Themes aren't scanned for known vulnerabilities.", 'wp-simple-firewall' ),
636
+ 'href' => $modHG->isModOptEnabled() ?
637
+ $modHG->getUrl_DirectLinkToOption( 'enable_wpvuln_scan' )
638
+ : $modHG->getUrl_DirectLinkToOption( 'enable_hack_protect' ),
639
+ 'protected' => $modHG->isModOptEnabled()
640
+ && $scansCon->getScanCon( HackGuard\Scan\Controller\Wpv::SCAN_SLUG )
641
+ ->isEnabled(),
642
+ 'weight' => 40,
643
+ ];
644
+ },
645
+ 'vuln_autoupdate' => function () {
646
+ $modHG = $this->getCon()->getModule_HackGuard();
647
+ $scansCon = $modHG->getScansCon();
648
+ /** @var HackGuard\Options $optsHG */
649
+ $optsHG = $modHG->getOptions();
650
+ return [
651
+ 'title' => __( 'Auto-Update Vulnerable Plugins', 'wp-simple-firewall' ),
652
+ 'desc_protected' => __( 'Plugins with known vulnerabilities are automatically updated to protect your site.', 'wp-simple-firewall' ),
653
+ 'desc_unprotected' => __( "Plugins with known vulnerabilities aren't automatically updated to protect your site.", 'wp-simple-firewall' ),
654
+ 'href' => $modHG->isModOptEnabled() ?
655
+ $modHG->getUrl_DirectLinkToOption( 'wpvuln_scan_autoupdate' )
656
+ : $modHG->getUrl_DirectLinkToOption( 'enable_hack_protect' ),
657
+ 'protected' => $modHG->isModOptEnabled()
658
+ && $scansCon->getScanCon( HackGuard\Scan\Controller\Wpv::SCAN_SLUG )
659
+ ->isEnabled()
660
+ && $optsHG->isWpvulnAutoupdatesEnabled(),
661
+ 'weight' => 30,
662
+ ];
663
+ },
664
+ 'auto_repair_core' => function () {
665
+ $modHG = $this->getCon()->getModule_HackGuard();
666
+ /** @var HackGuard\Options $optsHG */
667
+ $optsHG = $modHG->getOptions();
668
+ $scansCon = $modHG->getScansCon();
669
+ /** @var HackGuard\Scan\Controller\Afs $afsCon */
670
+ $afsCon = $scansCon->getScanCon( HackGuard\Scan\Controller\Afs::SCAN_SLUG );
671
+ return [
672
+ 'title' => __( 'WordPress Core Auto-Repair', 'wp-simple-firewall' ),
673
+ 'desc_protected' => __( 'Auto-repair of modified WordPress core files is enabled.', 'wp-simple-firewall' ),
674
+ 'desc_unprotected' => __( "Auto-repair of modified WordPress core files isn't enabled.", 'wp-simple-firewall' ),
675
+ 'href' => $modHG->isModOptEnabled() ?
676
+ $modHG->getUrl_DirectLinkToOption( 'file_repair_areas' )
677
+ : $modHG->getUrl_DirectLinkToOption( 'enable_hack_protect' ),
678
+ 'protected' => $modHG->isModOptEnabled() && $afsCon->isEnabled() && $optsHG->isRepairFileWP(),
679
+ 'weight' => 30,
680
+ ];
681
+ },
682
+ 'auto_repair_plugin' => function () {
683
+ $modHG = $this->getCon()->getModule_HackGuard();
684
+ /** @var HackGuard\Options $optsHG */
685
+ $optsHG = $modHG->getOptions();
686
+ $scansCon = $modHG->getScansCon();
687
+ /** @var HackGuard\Scan\Controller\Afs $afsCon */
688
+ $afsCon = $scansCon->getScanCon( HackGuard\Scan\Controller\Afs::SCAN_SLUG );
689
+ return [
690
+ 'title' => __( 'WordPress.org Plugin Auto-Repair', 'wp-simple-firewall' ),
691
+ 'desc_protected' => __( 'Auto-repair of files from WordPress.org plugins is enabled.', 'wp-simple-firewall' ),
692
+ 'desc_unprotected' => __( "Auto-repair of files from WordPress.org plugins isn't enabled.", 'wp-simple-firewall' ),
693
+ 'href' => $modHG->isModOptEnabled() ?
694
+ $modHG->getUrl_DirectLinkToOption( 'file_repair_areas' )
695
+ : $modHG->getUrl_DirectLinkToOption( 'enable_hack_protect' ),
696
+ 'protected' => $modHG->isModOptEnabled() && $afsCon->isEnabledPluginThemeScan() && $optsHG->isRepairFilePlugin(),
697
+ 'weight' => 30,
698
+ ];
699
+ },
700
+ 'auto_repair_theme' => function () {
701
+ $modHG = $this->getCon()->getModule_HackGuard();
702
+ /** @var HackGuard\Options $optsHG */
703
+ $optsHG = $modHG->getOptions();
704
+ $scansCon = $modHG->getScansCon();
705
+ /** @var HackGuard\Scan\Controller\Afs $afsCon */
706
+ $afsCon = $scansCon->getScanCon( HackGuard\Scan\Controller\Afs::SCAN_SLUG );
707
+ return [
708
+ 'title' => __( 'WordPress.org Theme Auto-Repair', 'wp-simple-firewall' ),
709
+ 'desc_protected' => __( 'Auto-repair of files from WordPress.org themes is enabled.', 'wp-simple-firewall' ),
710
+ 'desc_unprotected' => __( "Auto-repair of files from WordPress.org themes isn't enabled.", 'wp-simple-firewall' ),
711
+ 'href' => $modHG->isModOptEnabled() ?
712
+ $modHG->getUrl_DirectLinkToOption( 'file_repair_areas' )
713
+ : $modHG->getUrl_DirectLinkToOption( 'enable_hack_protect' ),
714
+ 'protected' => $modHG->isModOptEnabled() && $afsCon->isEnabledPluginThemeScan() && $optsHG->isRepairFileTheme(),
715
+ 'weight' => 20,
716
+ ];
717
+ },
718
+ 'scan_freq' => function () {
719
+ $modHG = $this->getCon()->getModule_HackGuard();
720
+ /** @var HackGuard\Options $optsHG */
721
+ $optsHG = $modHG->getOptions();
722
+ return [
723
+ 'title' => __( 'Scanning Frequency', 'wp-simple-firewall' ),
724
+ 'desc_protected' => __( 'Scans are run against your site at least twice per day.', 'wp-simple-firewall' ),
725
+ 'desc_unprotected' => __( "Scans are run against your site once per day at most.", 'wp-simple-firewall' ),
726
+ 'href' => $modHG->isModOptEnabled() ?
727
+ $modHG->getUrl_DirectLinkToOption( 'scan_frequency' )
728
+ : $modHG->getUrl_DirectLinkToOption( 'enable_hack_protect' ),
729
+ 'protected' => $modHG->isModOptEnabled() && $optsHG->getScanFrequency() > 1,
730
+ 'weight' => 10,
731
+ ];
732
+ },
733
+ 'filelocker_wpconfig' => function () {
734
+ $modHG = $this->getCon()->getModule_HackGuard();
735
+ /** @var HackGuard\Options $optsHG */
736
+ $optsHG = $modHG->getOptions();
737
+ $fileLocker = $modHG->getFileLocker();
738
+ return [
739
+ 'title' => sprintf( '%s - %s', 'wp-config.php', __( 'Protection', 'wp-simple-firewall' ) ),
740
+ 'desc_protected' => sprintf( __( '%s is protected against tampering.', 'wp-simple-firewall' ), 'wp-config.php' ),
741
+ 'desc_unprotected' => sprintf( __( "%s isn't protected against tampering.", 'wp-simple-firewall' ), 'wp-config.php' ),
742
+ 'href' => $modHG->isModOptEnabled() ?
743
+ $modHG->getUrl_DirectLinkToOption( 'file_locker' )
744
+ : $modHG->getUrl_DirectLinkToOption( 'enable_hack_protect' ),
745
+ 'protected' => $modHG->isModOptEnabled() && $fileLocker->isEnabled() && in_array( 'wpconfig', $optsHG->getFilesToLock() ),
746
+ 'weight' => 30,
747
+ ];
748
+ },
749
+ 'filelocker_htaccess' => function () {
750
+ $modHG = $this->getCon()->getModule_HackGuard();
751
+ /** @var HackGuard\Options $optsHG */
752
+ $optsHG = $modHG->getOptions();
753
+ $fileLocker = $modHG->getFileLocker();
754
+ return [
755
+ 'title' => sprintf( '%s - %s', '.htaccess', __( 'Protection', 'wp-simple-firewall' ) ),
756
+ 'desc_protected' => sprintf( __( '%s is protected against tampering.', 'wp-simple-firewall' ), '.htaccess' ),
757
+ 'desc_unprotected' => sprintf( __( "%s isn't protected against tampering.", 'wp-simple-firewall' ), '.htaccess' ),
758
+ 'href' => $modHG->isModOptEnabled() ?
759
+ $modHG->getUrl_DirectLinkToOption( 'file_locker' )
760
+ : $modHG->getUrl_DirectLinkToOption( 'enable_hack_protect' ),
761
+ 'protected' => $modHG->isModOptEnabled() && $fileLocker->isEnabled() && in_array( 'root_htaccess', $optsHG->getFilesToLock() ),
762
+ 'weight' => 30,
763
+ ];
764
+ },
765
+ 'secadmin' => function () {
766
+ $modSecAdmin = $this->getCon()->getModule_SecAdmin();
767
+ $secAdminCon = $modSecAdmin->getSecurityAdminController();
768
+ return [
769
+ 'title' => __( 'Security Admin Protection', 'wp-simple-firewall' ),
770
+ 'desc_protected' => __( 'The security plugin is protected against tampering through use of a Security Admin PIN.', 'wp-simple-firewall' ),
771
+ 'desc_unprotected' => __( "The security plugin isn't protected against tampering through use of a Security Admin PIN.", 'wp-simple-firewall' ),
772
+ 'href' => $modSecAdmin->isModOptEnabled() ?
773
+ $modSecAdmin->getUrl_DirectLinkToOption( 'admin_access_key' )
774
+ : $modSecAdmin->getUrl_DirectLinkToOption( 'enable_admin_access_restriction' ),
775
+ 'protected' => $modSecAdmin->isModOptEnabled() && $secAdminCon->isEnabledSecAdmin(),
776
+ 'weight' => 40,
777
+ ];
778
+ },
779
+ 'secadmin_admins' => function () {
780
+ $modSecAdmin = $this->getCon()->getModule_SecAdmin();
781
+ /** @var SecurityAdmin\Options $optsSecAdmin */
782
+ $optsSecAdmin = $modSecAdmin->getOptions();
783
+ $secAdminCon = $modSecAdmin->getSecurityAdminController();
784
+ return [
785
+ 'title' => __( 'WordPress Admins Protection', 'wp-simple-firewall' ),
786
+ 'desc_protected' => __( 'WordPress admin accounts are protected against tampering from other WordPress admins.', 'wp-simple-firewall' ),
787
+ 'desc_unprotected' => __( "WordPress admin accounts aren't protected against tampering from other WordPress admins.", 'wp-simple-firewall' ),
788
+ 'href' => $modSecAdmin->isModOptEnabled() ?
789
+ $modSecAdmin->getUrl_DirectLinkToOption( 'admin_access_restrict_admin_users' )
790
+ : $modSecAdmin->getUrl_DirectLinkToOption( 'enable_admin_access_restriction' ),
791
+ 'protected' => $modSecAdmin->isModOptEnabled()
792
+ && $secAdminCon->isEnabledSecAdmin() && $optsSecAdmin->isSecAdminRestrictUsersEnabled(),
793
+ 'weight' => 20,
794
+ ];
795
+ },
796
+ 'secadmin_options' => function () {
797
+ $modSecAdmin = $this->getCon()->getModule_SecAdmin();
798
+ /** @var SecurityAdmin\Options $optsSecAdmin */
799
+ $optsSecAdmin = $modSecAdmin->getOptions();
800
+ $secAdminCon = $modSecAdmin->getSecurityAdminController();
801
+ return [
802
+ 'title' => __( 'WordPress Settings Protection', 'wp-simple-firewall' ),
803
+ 'desc_protected' => __( 'Critical WordPress settings are protected against tampering from other WordPress admins.', 'wp-simple-firewall' ),
804
+ 'desc_unprotected' => __( "Critical WordPress settings aren't protected against tampering from other WordPress admins.", 'wp-simple-firewall' ),
805
+ 'href' => $modSecAdmin->isModOptEnabled() ?
806
+ $modSecAdmin->getUrl_DirectLinkToOption( 'admin_access_restrict_options' )
807
+ : $modSecAdmin->getUrl_DirectLinkToOption( 'enable_admin_access_restriction' ),
808
+ 'protected' => $modSecAdmin->isModOptEnabled()
809
+ && $secAdminCon->isEnabledSecAdmin() && $optsSecAdmin->isRestrictWpOptions(),
810
+ 'weight' => 20,
811
+ ];
812
+ },
813
+ 'lockdown_xmlrpc' => function () {
814
+ $modLockdown = $this->getCon()->getModule_Lockdown();
815
+ /** @var Lockdown\Options $optsLockdown */
816
+ $optsLockdown = $modLockdown->getOptions();
817
+ return [
818
+ 'title' => __( 'XML-RPC Access', 'wp-simple-firewall' ),
819
+ 'desc_protected' => __( 'Access to XML-RPC is disabled.', 'wp-simple-firewall' ),
820
+ 'desc_unprotected' => __( "Access to XML-RPC is available.", 'wp-simple-firewall' ),
821
+ 'href' => $modLockdown->isModOptEnabled() ?
822
+ $modLockdown->getUrl_DirectLinkToOption( 'disable_xmlrpc' )
823
+ : $modLockdown->getUrl_DirectLinkToOption( 'enable_lockdown' ),
824
+ 'protected' => $modLockdown->isModOptEnabled() && $optsLockdown->isXmlrpcDisabled(),
825
+ 'weight' => 30,
826
+ ];
827
+ },
828
+ 'lockdown_file_editing' => function () {
829
+ $modLockdown = $this->getCon()->getModule_Lockdown();
830
+ /** @var Lockdown\Options $optsLockdown */
831
+ $optsLockdown = $modLockdown->getOptions();
832
+ return [
833
+ 'title' => __( 'WordPress File Editing', 'wp-simple-firewall' ),
834
+ 'desc_protected' => __( 'Editing files from within the WordPress admin area is disabled.', 'wp-simple-firewall' ),
835
+ 'desc_unprotected' => __( "Editing files from within the WordPress admin area is allowed.", 'wp-simple-firewall' ),
836
+ 'href' => $modLockdown->isModOptEnabled() ?
837
+ $modLockdown->getUrl_DirectLinkToOption( 'disable_file_editing' )
838
+ : $modLockdown->getUrl_DirectLinkToOption( 'enable_lockdown' ),
839
+ 'protected' => $modLockdown->isModOptEnabled() && $optsLockdown->isOptFileEditingDisabled(),
840
+ 'weight' => 30,
841
+ ];
842
+ },
843
+ 'author_discovery' => function () {
844
+ $modLockdown = $this->getCon()->getModule_Lockdown();
845
+ /** @var Lockdown\Options $optsLockdown */
846
+ $optsLockdown = $modLockdown->getOptions();
847
+ return [
848
+ 'title' => sprintf( '%s / %s', __( 'Username Fishing', 'wp-simple-firewall' ), __( 'Author Discovery', 'wp-simple-firewall' ) ),
849
+ 'desc_protected' => __( 'The ability to fish for WordPress usernames is disabled.', 'wp-simple-firewall' ),
850
+ 'desc_unprotected' => __( "The ability to fish for WordPress usernames isn't blocked.", 'wp-simple-firewall' ),
851
+ 'href' => $modLockdown->isModOptEnabled() ?
852
+ $modLockdown->getUrl_DirectLinkToOption( 'block_author_discovery' )
853
+ : $modLockdown->getUrl_DirectLinkToOption( 'enable_lockdown' ),
854
+ 'protected' => $modLockdown->isModOptEnabled() && $optsLockdown->isBlockAuthorDiscovery(),
855
+ 'weight' => 30,
856
+ ];
857
+ },
858
+ 'anonymous_rest' => function () {
859
+ $modLockdown = $this->getCon()->getModule_Lockdown();
860
+ /** @var Lockdown\Options $optsLockdown */
861
+ $optsLockdown = $modLockdown->getOptions();
862
+ return [
863
+ 'title' => __( 'Anonymous REST API Access', 'wp-simple-firewall' ),
864
+ 'desc_protected' => __( 'Anonymous/Unauthenticated access to the WordPress REST API is disabled.', 'wp-simple-firewall' ),
865
+ 'desc_unprotected' => __( "Anonymous/Unauthenticated access to the WordPress REST API isn't blocked.", 'wp-simple-firewall' ),
866
+ 'href' => $modLockdown->isModOptEnabled() ?
867
+ $modLockdown->getUrl_DirectLinkToOption( 'disable_anonymous_restapi' )
868
+ : $modLockdown->getUrl_DirectLinkToOption( 'enable_lockdown' ),
869
+ 'protected' => $modLockdown->isModOptEnabled() && $optsLockdown->isRestApiAnonymousAccessDisabled(),
870
+ 'weight' => 20,
871
+ ];
872
+ },
873
+ 'ip_autoblock' => function () {
874
+ $modIPs = $this->getCon()->getModule_IPs();
875
+ /** @var IPs\Options $optsIPs */
876
+ $optsIPs = $modIPs->getOptions();
877
+ return [
878
+ 'title' => __( 'IP Auto-Block', 'wp-simple-firewall' ),
879
+ 'desc_protected' => sprintf( __( 'Auto IP blocking is turned on with an offense limit of %s.', 'wp-simple-firewall' ),
880
+ $optsIPs->getOffenseLimit() ),
881
+ 'desc_unprotected' => __( 'Auto IP blocking is turned of as there is no offense limit provided.', 'wp-simple-firewall' ),
882
+ 'href' => $modIPs->isModOptEnabled() ?
883
+ $modIPs->getUrl_DirectLinkToOption( 'transgression_limit' )
884
+ : $modIPs->getUrl_DirectLinkToOption( 'enable_ips' ),
885
+ 'protected' => $modIPs->isModOptEnabled() && $optsIPs->isEnabledAutoBlackList(),
886
+ 'weight' => 50,
887
+ ];
888
+ },
889
+ 'ip_autoblock_limit' => function () {
890
+ $modIPs = $this->getCon()->getModule_IPs();
891
+ /** @var IPs\Options $optsIPs */
892
+ $optsIPs = $modIPs->getOptions();
893
+ return [
894
+ 'title' => __( 'IP Auto-Block Offense Limit', 'wp-simple-firewall' ),
895
+ 'desc_protected' => sprintf( __( "The maximum allowable offenses allowed before blocking is reasonably low: %s", 'wp-simple-firewall' ),
896
+ $optsIPs->getOffenseLimit() ),
897
+ 'desc_unprotected' => sprintf( __( "Your maximum offense limit before blocking an IP seems high: %s", 'wp-simple-firewall' ),
898
+ $optsIPs->getOffenseLimit() ),
899
+ 'href' => $modIPs->isModOptEnabled() ?
900
+ $modIPs->getUrl_DirectLinkToOption( 'transgression_limit' )
901
+ : $modIPs->getUrl_DirectLinkToOption( 'enable_ips' ),
902
+ 'protected' => $modIPs->isModOptEnabled()
903
+ && $optsIPs->isEnabledAutoBlackList() && $optsIPs->getOffenseLimit() <= 10,
904
+ 'weight' => 30,
905
+ ];
906
+ },
907
+ 'ade_threshold' => function () {
908
+ $modIPs = $this->getCon()->getModule_IPs();
909
+ /** @var IPs\Options $optsIPs */
910
+ $optsIPs = $modIPs->getOptions();
911
+ return [
912
+ 'title' => __( 'AntiBot Detection Engine', 'wp-simple-firewall' ),
913
+ 'desc_protected' => __( 'AntiBot Detection Engine is enabled with a minimum bot-score threshold.', 'wp-simple-firewall' ),
914
+ 'desc_unprotected' => __( 'AntiBot Detection Engine is disabled as there is no minimum bot-score threshold provided.', 'wp-simple-firewall' ),
915
+ 'href' => $modIPs->isModOptEnabled() ?
916
+ $modIPs->getUrl_DirectLinkToOption( 'antibot_minimum' )
917
+ : $modIPs->getUrl_DirectLinkToOption( 'enable_ips' ),
918
+ 'protected' => $optsIPs->isEnabledAntiBotEngine(),
919
+ 'weight' => 30,
920
+ ];
921
+ },
922
+ 'track_404' => function () {
923
+ $modIPs = $this->getCon()->getModule_IPs();
924
+ /** @var IPs\Options $optsIPs */
925
+ $optsIPs = $modIPs->getOptions();
926
+ return [
927
+ 'title' => sprintf( '%s - %s', __( 'Bot Tracking', 'wp-simple-firewall' ), '404s' ),
928
+ 'desc_protected' => __( 'Bots that trigger 404 errors are penalised.', 'wp-simple-firewall' ),
929
+ 'desc_unprotected' => __( "Bots that trigger 404 errors aren't penalised.", 'wp-simple-firewall' ),
930
+ 'href' => $modIPs->isModOptEnabled() ?
931
+ $modIPs->getUrl_DirectLinkToOption( 'track_404' )
932
+ : $modIPs->getUrl_DirectLinkToOption( 'enable_ips' ),
933
+ 'protected' => $modIPs->isModOptEnabled() && $optsIPs->getOffenseCountFor( 'track_404' ) > 0,
934
+ 'weight' => 20,
935
+ ];
936
+ },
937
+ 'track_loginfail' => function () {
938
+ $modIPs = $this->getCon()->getModule_IPs();
939
+ /** @var IPs\Options $optsIPs */
940
+ $optsIPs = $modIPs->getOptions();
941
+ return [
942
+ 'title' => sprintf( '%s - %s', __( 'Bot Tracking', 'wp-simple-firewall' ), __( 'Failed Logins', 'wp-simple-firewall' ) ),
943
+ 'desc_protected' => __( 'Bots that attempt to login and fail are penalised.', 'wp-simple-firewall' ),
944
+ 'desc_unprotected' => __( "Bots that attempt to login and fail aren't penalised.", 'wp-simple-firewall' ),
945
+ 'href' => $modIPs->isModOptEnabled() ?
946
+ $modIPs->getUrl_DirectLinkToOption( 'track_loginfailed' )
947
+ : $modIPs->getUrl_DirectLinkToOption( 'enable_ips' ),
948
+ 'protected' => $modIPs->isModOptEnabled() && $optsIPs->getOffenseCountFor( 'track_loginfailed' ) > 0,
949
+ 'weight' => 30,
950
+ ];
951
+ },
952
+ 'track_logininvalid' => function () {
953
+ $modIPs = $this->getCon()->getModule_IPs();
954
+ /** @var IPs\Options $optsIPs */
955
+ $optsIPs = $modIPs->getOptions();
956
+ return [
957
+ 'title' => sprintf( '%s - %s', __( 'Bot Tracking', 'wp-simple-firewall' ), __( 'Invalid Logins', 'wp-simple-firewall' ) ),
958
+ 'desc_protected' => __( 'Bots that attempt to login with non-existent usernames are penalised.', 'wp-simple-firewall' ),
959
+ 'desc_unprotected' => __( "Bots that attempt to login with non-existent usernames aren't penalised.", 'wp-simple-firewall' ),
960
+ 'href' => $modIPs->isModOptEnabled() ?
961
+ $modIPs->getUrl_DirectLinkToOption( 'track_logininvalid' )
962
+ : $modIPs->getUrl_DirectLinkToOption( 'enable_ips' ),
963
+ 'protected' => $modIPs->isModOptEnabled() && $optsIPs->getOffenseCountFor( 'track_logininvalid' ) > 0,
964
+ 'weight' => 40,
965
+ ];
966
+ },
967
+ 'track_xml' => function () {
968
+ $modIPs = $this->getCon()->getModule_IPs();
969
+ /** @var IPs\Options $optsIPs */
970
+ $optsIPs = $modIPs->getOptions();
971
+ return [
972
+ 'title' => sprintf( '%s - %s', __( 'Bot Tracking', 'wp-simple-firewall' ), 'XML-RPC' ),
973
+ 'desc_protected' => __( 'Bots that attempt to access XML-RPC are penalised.', 'wp-simple-firewall' ),
974
+ 'desc_unprotected' => __( "Bots that attempt to access XML-RPC aren't penalised.", 'wp-simple-firewall' ),
975
+ 'href' => $modIPs->isModOptEnabled() ?
976
+ $modIPs->getUrl_DirectLinkToOption( 'track_xmlrpc' )
977
+ : $modIPs->getUrl_DirectLinkToOption( 'enable_ips' ),
978
+ 'protected' => $modIPs->isModOptEnabled() && $optsIPs->getOffenseCountFor( 'track_xmlrpc' ) > 0,
979
+ 'weight' => 40,
980
+ ];
981
+ },
982
+ 'track_fake' => function () {
983
+ $modIPs = $this->getCon()->getModule_IPs();
984
+ /** @var IPs\Options $optsIPs */
985
+ $optsIPs = $modIPs->getOptions();
986
+ return [
987
+ 'title' => sprintf( '%s - %s', __( 'Bot Tracking', 'wp-simple-firewall' ), __( 'Fake Web Crawlers', 'wp-simple-firewall' ) ),
988
+ 'desc_protected' => __( 'Bots that pretend to be official web crawlers such as Google are penalised.', 'wp-simple-firewall' ),
989
+ 'desc_unprotected' => __( "Bots that pretend to be official web crawlers such as Google aren't penalised.", 'wp-simple-firewall' ),
990
+ 'href' => $modIPs->isModOptEnabled() ?
991
+ $modIPs->getUrl_DirectLinkToOption( 'track_fakewebcrawler' )
992
+ : $modIPs->getUrl_DirectLinkToOption( 'enable_ips' ),
993
+ 'protected' => $modIPs->isModOptEnabled() && $optsIPs->getOffenseCountFor( 'track_fakewebcrawler' ) > 0,
994
+ 'weight' => 30,
995
+ ];
996
+ },
997
+ 'track_cheese' => function () {
998
+ $modIPs = $this->getCon()->getModule_IPs();
999
+ /** @var IPs\Options $optsIPs */
1000
+ $optsIPs = $modIPs->getOptions();
1001
+ return [
1002
+ 'title' => sprintf( '%s - %s', __( 'Bot Tracking', 'wp-simple-firewall' ), __( 'Link-Cheese', 'wp-simple-firewall' ) ),
1003
+ 'desc_protected' => __( 'Bots that trigger the link-cheese bait are penalised.', 'wp-simple-firewall' ),
1004
+ 'desc_unprotected' => __( "Bots that trigger the link-cheese bait aren't penalised.", 'wp-simple-firewall' ),
1005
+ 'href' => $modIPs->isModOptEnabled() ?
1006
+ $modIPs->getUrl_DirectLinkToOption( 'track_linkcheese' )
1007
+ : $modIPs->getUrl_DirectLinkToOption( 'enable_ips' ),
1008
+ 'protected' => $modIPs->isModOptEnabled() && $optsIPs->getOffenseCountFor( 'track_linkcheese' ) > 0,
1009
+ 'weight' => 20,
1010
+ ];
1011
+ },
1012
+ 'track_script' => function () {
1013
+ $modIPs = $this->getCon()->getModule_IPs();
1014
+ /** @var IPs\Options $optsIPs */
1015
+ $optsIPs = $modIPs->getOptions();
1016
+ return [
1017
+ 'title' => sprintf( '%s - %s', __( 'Bot Tracking', 'wp-simple-firewall' ), __( 'Invalid Scripts', 'wp-simple-firewall' ) ),
1018
+ 'desc_protected' => __( 'Bots that attempt to access invalid scripts or WordPress files are penalised.', 'wp-simple-firewall' ),
1019
+ 'desc_unprotected' => __( "Bots that attempt to access invalid scripts or WordPress files aren't penalised.", 'wp-simple-firewall' ),
1020
+ 'href' => $modIPs->isModOptEnabled() ?
1021
+ $modIPs->getUrl_DirectLinkToOption( 'track_invalidscript' )
1022
+ : $modIPs->getUrl_DirectLinkToOption( 'enable_ips' ),
1023
+ 'protected' => $modIPs->isModOptEnabled() && $optsIPs->getOffenseCountFor( 'track_invalidscript' ) > 0,
1024
+ 'weight' => 20,
1025
+ ];
1026
+ },
1027
+ 'plugins_inactive' => function () {
1028
+ $WPP = Services::WpPlugins();
1029
+ $countPluginsInactive = count( $WPP->getPlugins() ) - count( $WPP->getActivePlugins() );
1030
+ return [
1031
+ 'title' => __( 'Inactive Plugins', 'wp-simple-firewall' ),
1032
+ 'desc_protected' => __( 'All installed plugins appear to be active and in-use.', 'wp-simple-firewall' ),
1033
+ 'desc_unprotected' => sprintf( __( 'There are %s inactive and unused plugins.', 'wp-simple-firewall' ), $countPluginsInactive ),
1034
+ 'href' => add_query_arg( [ 'plugin_status' => 'inactive' ],
1035
+ Services::WpGeneral()->getAdminUrl_Plugins( true ) ),
1036
+ 'protected' => $countPluginsInactive === 0,
1037
+ 'weight' => 25,
1038
+ ];
1039
+ },
1040
+ 'plugins_updates' => function () {
1041
+ $WPP = Services::WpPlugins();
1042
+ $countPluginsUpdates = count( $WPP->getUpdates() );
1043
+ return [
1044
+ 'title' => __( 'Plugins With Updates', 'wp-simple-firewall' ),
1045
+ 'desc_protected' => __( "All available plugin updates have been applied.", 'wp-simple-firewall' ),
1046
+ 'desc_unprotected' => sprintf( __( 'There are %s plugin updates waiting to be applied.', 'wp-simple-firewall' ), $countPluginsUpdates ),
1047
+ 'href' => add_query_arg( [ 'plugin_status' => 'upgrade' ],
1048
+ Services::WpGeneral()->getAdminUrl_Plugins( true ) ),
1049
+ 'protected' => $countPluginsUpdates === 0,
1050
+ 'weight' => 45,
1051
+ ];
1052
+ },
1053
+ 'themes_inactive' => function () {
1054
+ $WPT = Services::WpThemes();
1055
+ $countThemesInactive = count( $WPT->getThemes() ) - ( $WPT->isActiveThemeAChild() ? 2 : 1 );
1056
+ return [
1057
+ 'title' => __( 'Inactive Themes', 'wp-simple-firewall' ),
1058
+ 'desc_protected' => __( "All installed themes appear to be active and in-use.", 'wp-simple-firewall' ),
1059
+ 'desc_unprotected' => sprintf( __( 'There are %s inactive and unused themes.', 'wp-simple-firewall' ), $countThemesInactive ),
1060
+ 'href' => add_query_arg( [ 'plugin_status' => 'upgrade' ],
1061
+ Services::WpGeneral()->getAdminUrl_Themes( true ) ),
1062
+ 'protected' => $countThemesInactive === 0,
1063
+ 'weight' => 25,
1064
+ ];
1065
+ },
1066
+ 'themes_updates' => function () {
1067
+ $WPT = Services::WpThemes();
1068
+ $countThemesUpdates = count( $WPT->getUpdates() );
1069
+ return [
1070
+ 'title' => __( 'Themes With Updates', 'wp-simple-firewall' ),
1071
+ 'desc_protected' => __( "All available theme updates have been applied.", 'wp-simple-firewall' ),
1072
+ 'desc_unprotected' => sprintf( __( 'There are %s theme updates waiting to be applied.', 'wp-simple-firewall' ), $countThemesUpdates ),
1073
+ 'href' => Services::WpGeneral()->getAdminUrl_Themes( true ),
1074
+ 'protected' => $countThemesUpdates === 0,
1075
+ 'weight' => 45,
1076
+ ];
1077
+ },
1078
+ 'autoupdate_core' => function () {
1079
+ return [
1080
+ 'title' => __( 'WordPress Core Automatic Updates', 'wp-simple-firewall' ),
1081
+ 'desc_protected' => __( 'WordPress Core is automatically updated when minor upgrades are released.', 'wp-simple-firewall' ),
1082
+ 'desc_unprotected' => __( "WordPress Core isn't automatically updated when minor upgrades are released.", 'wp-simple-firewall' ),
1083
+ 'href' => $this->getCon()->getModule_Autoupdates()->getUrl_AdminPage(),
1084
+ 'protected' => Services::WpGeneral()->canCoreUpdateAutomatically(),
1085
+ 'weight' => 50,
1086
+ ];
1087
+ },
1088
+ 'users_inactive' => function () {
1089
+ $modUM = $this->getCon()->getModule_UserManagement();
1090
+ /** @var UserManagement\Options $optsUM */
1091
+ $optsUM = $modUM->getOptions();
1092
+ return [
1093
+ 'title' => __( 'Inactive User Accounts', 'wp-simple-firewall' ),
1094
+ 'desc_protected' => sprintf( __( 'Inactive user accounts are automatically suspended after %s.', 'wp-simple-firewall' ), $optsUM->getOpt( 'auto_idle_days' ) ),
1095
+ 'desc_unprotected' => __( 'There is currently no control over how inactive user accounts are handled.', 'wp-simple-firewall' ),
1096
+ 'href' => $modUM->isModOptEnabled() ?
1097
+ $modUM->getUrl_DirectLinkToOption( 'auto_idle_days' )
1098
+ : $modUM->getUrl_DirectLinkToOption( 'enable_user_management' ),
1099
+ 'protected' => $modUM->isModOptEnabled() && $optsUM->getOpt( 'auto_idle_days' ) > 0,
1100
+ 'weight' => 20,
1101
+ ];
1102
+ },
1103
+ 'sessions_idle' => function () {
1104
+ $modUM = $this->getCon()->getModule_UserManagement();
1105
+ /** @var UserManagement\Options $optsUM */
1106
+ $optsUM = $modUM->getOptions();
1107
+ return [
1108
+ 'title' => __( 'Idle User Sessions', 'wp-simple-firewall' ),
1109
+ 'desc_protected' => sprintf( 'Idle user sessions are always automatically logged out after %s hours.', $optsUM->getOpt( 'session_idle_timeout_interval' ) ),
1110
+ 'desc_unprotected' => __( 'There is currently no control over how idle user sessions are handled.', 'wp-simple-firewall' ),
1111
+ 'href' => $modUM->isModOptEnabled() ?
1112
+ $modUM->getUrl_DirectLinkToOption( 'session_idle_timeout_interval' )
1113
+ : $modUM->getUrl_DirectLinkToOption( 'enable_user_management' ),
1114
+ 'protected' => $modUM->isModOptEnabled() && $optsUM->hasSessionIdleTimeout(),
1115
+ 'weight' => 20,
1116
+ ];
1117
+ },
1118
+ 'ssl_certificate' => function () {
1119
+ $WP = Services::WpGeneral();
1120
+ $srvSSL = new Ssl();
1121
+
1122
+ $ssl = [
1123
+ 'title' => __( 'SSL Certificate', 'wp-simple-firewall' ),
1124
+ 'desc_protected' => __( 'SSL Certificate remains valid for at least the next 2 weeks', 'wp-simple-firewall' ),
1125
+ 'desc_unprotected' => __( "Visitors aren't protected with a valid SSL Certificate.", 'wp-simple-firewall' ),
1126
+ 'href' => add_query_arg(
1127
+ [
1128
+ 'action' => Services::WpGeneral()->getHomeUrl(),
1129
+ 'run' => 'toolpage'
1130
+ ],
1131
+ 'https://mxtoolbox.com/SuperTool.aspx'
1132
+ ),
1133
+ 'protected' => false,
1134
+ 'weight' => 45,
1135
+ ];
1136
+
1137
+ // SSL Expires
1138
+ $homeURL = $WP->getHomeUrl();
1139
+ $isHomeSsl = strpos( $homeURL, 'https://' ) === 0;
1140
+
1141
+ if ( !$isHomeSsl ) {
1142
+ $ssl[ 'desc_unprotected' ] = __( "Visitors aren't protected with a valid SSL Certificate.", 'wp-simple-firewall' );
1143
+ }
1144
+ elseif ( strpos( $WP->getWpUrl(), 'https://' ) !== 0 ) {
1145
+ $ssl[ 'desc_unprotected' ] = __( "HTTPS setting for Home URL and WP Site URL aren't consistent.", 'wp-simple-firewall' );
1146
+ $ssl[ 'href' ] = $WP->getAdminUrl_Settings();
1147
+ }
1148
+ elseif ( $srvSSL->isEnvSupported() ) {
1149
+ $srvSSL = new Ssl();
1150
+ try {
1151
+ // first verify SSL cert:
1152
+ $srvSSL->getCertDetailsForDomain( $homeURL );
1153
+
1154
+ // If we didn't throw an exception, we got it.
1155
+ $expiresAt = $srvSSL->getExpiresAt( $homeURL );
1156
+ if ( $expiresAt > 0 ) {
1157
+ $timeRemaining = ( $expiresAt - Services::Request()->ts() );
1158
+ $isExpired = $timeRemaining < 0;
1159
+ $daysLeft = $isExpired ? 0 : (int)round( $timeRemaining/DAY_IN_SECONDS, 0, PHP_ROUND_HALF_DOWN );
1160
+
1161
+ if ( $daysLeft < 15 ) {
1162
+ if ( $isExpired ) {
1163
+ $ssl[ 'desc_unprotected' ] = __( 'SSL certificate for this site has expired.', 'wp-simple-firewall' );
1164
+ }
1165
+ else {
1166
+ $ssl[ 'desc_unprotected' ] = sprintf( __( 'SSL certificate will expire soon (%s days)', 'wp-simple-firewall' ), $daysLeft );
1167
+ }
1168
+ }
1169
+ else {
1170
+ $ssl[ 'protected' ] = true;
1171
+ }
1172
+ }
1173
+ }
1174
+ catch ( \Exception $e ) {
1175
+ $ssl[ 'desc_unprotected' ] = sprintf( '%s: %s', __( "Couldn't automatically test and verify your site SSL certificate", 'wp-simple-firewall' ), $e->getMessage() );
1176
+ }
1177
+ }
1178
+ else {
1179
+ $ssl[ 'protected' ] = true;
1180
+ }
1181
+
1182
+ return $ssl;
1183
+ },
1184
+ ]
1185
+ );
1186
+ }
1187
+
1188
+ private function getUrlForScanResults() :string {
1189
+ return $this->getCon()->getModule_Insights()->getUrl_ScansResults();
1190
+ }
1191
+ }
src/lib/src/Modules/Insights/Lib/MeterAnalysis/Handler.php ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\Lib\MeterAnalysis;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
+
7
+ class Handler {
8
+
9
+ use ModConsumer;
10
+
11
+ /**
12
+ * @throws \Exception
13
+ */
14
+ public function renderAnalysis( string $meter ) :string {
15
+ $this->exists( $meter );
16
+ return $this->getMeter( $meter )->render();
17
+ }
18
+
19
+ public function renderDashboardMeters() :string {
20
+ $con = $this->getCon();
21
+ $mod = $this->getMod();
22
+ return $mod->getRenderer()
23
+ ->setTemplate( '/wpadmin_pages/insights/overview/progress_meter/progress_meters.twig' )
24
+ ->setRenderData( [
25
+ 'strings' => [
26
+ 'analysis' => __( 'Analysis', 'wp-simple-firewall' ),
27
+ ],
28
+ 'imgs' => [
29
+ 'svgs' => [
30
+ 'analysis' => $con->svgs->raw( 'bootstrap/clipboard2-data-fill.svg' ),
31
+ ],
32
+ ],
33
+ 'vars' => [
34
+ 'progress_meters' => $this->buildAllMeterComponents()
35
+ ],
36
+ ] )
37
+ ->render();
38
+ }
39
+
40
+ private function buildAllMeterComponents() :array {
41
+ return array_map(
42
+ function ( string $class ) {
43
+ /** @var MeterBase $class */
44
+ try {
45
+ return $this->buildMeterComponents( $class::SLUG );
46
+ }
47
+ catch ( \Exception $e ) {
48
+ return 'meter component error:'.$e->getMessage();
49
+ }
50
+ },
51
+ $this->enumMeters()
52
+ );
53
+ }
54
+
55
+ /**
56
+ * @throws \Exception
57
+ */
58
+ public function buildMeterComponents( string $meter ) :array {
59
+ return $this->getMeter( $meter )->buildMeterComponents();
60
+ }
61
+
62
+ /**
63
+ * @return MeterBase|mixed
64
+ * @throws \Exception
65
+ */
66
+ protected function getMeter( string $meter ) {
67
+ $this->exists( $meter );
68
+ $class = $this->enumMeters()[ $meter ];
69
+ return ( new $class() )->setCon( $this->getCon() );
70
+ }
71
+
72
+ public function enumMeters() :array {
73
+ $meters = [
74
+ // MeterAll::class,
75
+ MeterIntegrity::class,
76
+ MeterAssets::class,
77
+ MeterIpBlocking::class,
78
+ MeterScans::class,
79
+ MeterFirewall::class,
80
+ MeterLockdown::class,
81
+ MeterLoginProtection::class,
82
+ MeterUsers::class,
83
+ MeterSpam::class,
84
+ ];
85
+ $enum = [];
86
+ foreach ( $meters as $meter ) {
87
+ /** @var MeterBase $meter */
88
+ $enum[ $meter::SLUG ] = $meter;
89
+ }
90
+ return $enum;
91
+ }
92
+
93
+ /**
94
+ * @throws \Exception
95
+ */
96
+ protected function exists( string $meter ) :bool {
97
+ if ( empty( $this->enumMeters()[ $meter ] ) ) {
98
+ throw new \Exception( 'No such meter exists: '.sanitize_key( $meter ) );
99
+ }
100
+ return true;
101
+ }
102
+ }
src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterAll.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\Lib\MeterAnalysis;
4
+
5
+ class MeterAll extends MeterBase {
6
+
7
+ const SLUG = 'all';
8
+
9
+ protected function title() :string {
10
+ return __( 'All', 'wp-simple-firewall' );
11
+ }
12
+
13
+ protected function subtitle() :string {
14
+ return __( 'All', 'wp-simple-firewall' );
15
+ }
16
+
17
+ protected function description() :array {
18
+ return [
19
+ __( "This section reviews how your plugins & themes are scanned, where there are unused items, and any particular issues that need to be addressed.", 'wp-simple-firewall' ),
20
+ __( "Generally you should keep all assets updated, remove unused items, and use only plugins that are regularly maintained.", 'wp-simple-firewall' ),
21
+ ];
22
+ }
23
+
24
+ protected function getComponentSlugs() :array {
25
+ $allSlugs = ( new Components() )
26
+ ->setCon( $this->getCon() )
27
+ ->getAllComponentsSlugs();
28
+ return array_diff( $allSlugs, [ 'all' ] );
29
+ }
30
+ }
src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterAssets.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\Lib\MeterAnalysis;
4
+
5
+ class MeterAssets extends MeterBase {
6
+
7
+ const SLUG = 'assets';
8
+
9
+ protected function title() :string {
10
+ return __( 'Plugins, Themes, WordPress Core', 'wp-simple-firewall' );
11
+ }
12
+
13
+ protected function subtitle() :string {
14
+ return __( 'How well core WordPress assets are protected', 'wp-simple-firewall' );
15
+ }
16
+
17
+ protected function description() :array {
18
+ return [
19
+ __( "This section reviews how your plugins & themes are scanned, where there are unused items, and any particular issues that need to be addressed.", 'wp-simple-firewall' ),
20
+ __( "Generally you should keep all assets updated, remove unused items, and use only plugins that are regularly maintained.", 'wp-simple-firewall' ),
21
+ ];
22
+ }
23
+
24
+ protected function getComponentSlugs() :array {
25
+ return [
26
+ 'autoupdate_core',
27
+ 'wpv_scanner',
28
+ 'vuln_autoupdate',
29
+ 'apc_scanner',
30
+ 'plugins_updates',
31
+ 'themes_updates',
32
+ 'plugins_inactive',
33
+ 'themes_inactive',
34
+ ];
35
+ }
36
+ }
src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterBase.php ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\Lib\MeterAnalysis;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Render\BaseTemplateRenderer;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\{
8
+ BaseShield,
9
+ Plugin
10
+ };
11
+
12
+ abstract class MeterBase extends BaseTemplateRenderer {
13
+
14
+ const SLUG = '';
15
+
16
+ /**
17
+ * @return BaseShield\ModCon[]|Plugin\ModCon[]
18
+ */
19
+ protected function getWorkingMods() :array {
20
+ return [];
21
+ }
22
+
23
+ public function buildMeterComponents() :array {
24
+ return $this->postProcessMeter( [
25
+ 'title' => $this->title(),
26
+ 'subtitle' => $this->subtitle(),
27
+ 'warning' => $this->warning(),
28
+ 'description' => $this->description(),
29
+ 'components' => $this->buildComponents(),
30
+ ] );
31
+ }
32
+
33
+ protected function warning() :array {
34
+ $con = $this->getCon();
35
+ $pluginMod = $con->getModule_Plugin();
36
+ /** @var Plugin\Options $pluginOpts */
37
+ $pluginOpts = $pluginMod->getOptions();
38
+ $warning = [];
39
+ if ( $pluginOpts->isPluginGloballyDisabled() ) {
40
+ $warning = [
41
+ 'text' => __( 'The plugin is currently entirely disabled.' ),
42
+ 'href' => $pluginMod->getUrl_DirectLinkToOption( 'global_enable_plugin_features' ),
43
+ ];
44
+ }
45
+ else {
46
+ foreach ( $this->getWorkingMods() as $workingMod ) {
47
+ if ( !$workingMod->isModOptEnabled() ) {
48
+ $warning = [
49
+ 'text' => __( 'A module that manages some of these settings is disabled.' ),
50
+ 'href' => $workingMod->getUrl_DirectLinkToOption( $workingMod->getEnableModOptKey() ),
51
+ ];
52
+ break;
53
+ }
54
+ }
55
+ }
56
+ return $warning;
57
+ }
58
+
59
+ protected function postProcessMeter( array $meter ) :array {
60
+ $hasCritical = false;
61
+ $totalScore = 0;
62
+ $totalWeight = 0;
63
+ foreach ( $meter[ 'components' ] as $key => $component ) {
64
+
65
+ if ( !isset( $component[ 'score' ] ) ) {
66
+ $component[ 'score' ] = $component[ 'protected' ] ? $component[ 'weight' ] : 0;
67
+ }
68
+ $totalScore += $component[ 'score' ];
69
+ $totalWeight += $component[ 'weight' ];
70
+
71
+ if ( !isset( $component[ 'is_critical' ] ) ) {
72
+ $component[ 'is_critical' ] = false;
73
+ }
74
+
75
+ $meter[ 'components' ][ $key ] = $component;
76
+
77
+ $hasCritical = $hasCritical || $component[ 'is_critical' ];
78
+ }
79
+
80
+ foreach ( $meter[ 'components' ] as &$comp ) {
81
+ $comp[ 'score_as_percent' ] = (int)round( 100*$comp[ 'score' ]/$totalWeight );
82
+ $comp[ 'weight_as_percent' ] = (int)round( 100*$comp[ 'weight' ]/$totalWeight );
83
+ }
84
+
85
+ // Put critical components to the top of the list.
86
+ uasort( $meter[ 'components' ], function ( $a, $b ) {
87
+ if ( $a[ 'is_critical' ] === $b[ 'is_critical' ] ) {
88
+ return 0;
89
+ }
90
+ return $a[ 'is_critical' ] ? -1 : 1;
91
+ } );
92
+
93
+ $percentage = (int)round( 100*$totalScore/$totalWeight );
94
+ $meter[ 'totals' ] = [
95
+ 'score' => $totalScore,
96
+ 'max_weight' => $totalWeight,
97
+ 'percentage' => $percentage,
98
+ ];
99
+ $meter[ 'rgbs' ] = [
100
+ ( 100 - $percentage )*128/100,
101
+ ( $percentage )*128/100,
102
+ 0
103
+ ];
104
+
105
+ $meter[ 'has_critical' ] = $hasCritical || !empty( $meter[ 'warning' ] );
106
+
107
+ return $meter;
108
+ }
109
+
110
+ protected function buildComponents() :array {
111
+ return ( new Components() )
112
+ ->setCon( $this->getCon() )
113
+ ->getComponents( $this->getComponentSlugs() );
114
+ }
115
+
116
+ protected function getComponentSlugs() :array {
117
+ return [];
118
+ }
119
+
120
+ protected function getRenderData() :array {
121
+ return Services::DataManipulation()->mergeArraysRecursive(
122
+ $this->getCon()->getModule_Plugin()->getUIHandler()->getBaseDisplayData(),
123
+ [
124
+ 'strings' => [
125
+ 'title' => sprintf( '%s: %s', __( 'Analysis', 'wp-simple-firewall' ), $this->title() ),
126
+ 'scores_footnote1' => __( 'Scores are an approximate weighting for each component.', 'wp-simple-firewall' ),
127
+ 'scores_footnote2' => __( 'As each issue is resolved the overall score will improve, up to 100%.', 'wp-simple-firewall' ),
128
+ ],
129
+ 'vars' => [
130
+ 'components' => $this->buildMeterComponents()[ 'components' ]
131
+ ]
132
+ ],
133
+ $this->getMeterRenderData()
134
+ );
135
+ }
136
+
137
+ protected function getMeterRenderData() :array {
138
+ return [];
139
+ }
140
+
141
+ protected function title() :string {
142
+ return 'no title';
143
+ }
144
+
145
+ protected function subtitle() :string {
146
+ return 'no subtitle';
147
+ }
148
+
149
+ protected function description() :array {
150
+ return [ 'no description' ];
151
+ }
152
+
153
+ protected function getTemplateBaseDir() :string {
154
+ return '/wpadmin_pages/insights/overview/progress_meter/analysis';
155
+ }
156
+
157
+ protected function getTemplateStub() :string {
158
+ return 'standard';
159
+ }
160
+ }
src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterFirewall.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\Lib\MeterAnalysis;
4
+
5
+ class MeterFirewall extends MeterBase {
6
+
7
+ const SLUG = 'firewall';
8
+
9
+ protected function getWorkingMods() :array {
10
+ return [ $this->getCon()->getModule_Firewall() ];
11
+ }
12
+
13
+ protected function title() :string {
14
+ return __( 'Powerful WordPress Firewall', 'wp-simple-firewall' );
15
+ }
16
+
17
+ protected function subtitle() :string {
18
+ return __( 'How malicious requests to your site are handled', 'wp-simple-firewall' );
19
+ }
20
+
21
+ protected function description() :array {
22
+ return [
23
+ __( "The firewall inspects all data sent in every request to your site.", 'wp-simple-firewall' )
24
+ .' '.__( "If malicious data is detected, the request will be quickly terminated before it can be misused.", 'wp-simple-firewall' ),
25
+ __( "The more rules you employ, the better, but you should always monitor your Activity Audit Trail for false positives.", 'wp-simple-firewall' ),
26
+ ];
27
+ }
28
+
29
+ protected function getMeterRenderData() :array {
30
+ return [];
31
+ }
32
+
33
+ protected function getComponentSlugs() :array {
34
+ return array_map(
35
+ function ( string $key ) {
36
+ return 'fwb_'.$key;
37
+ },
38
+ [
39
+ 'dir_traversal',
40
+ 'wordpress_terms',
41
+ 'field_truncation',
42
+ 'php_code',
43
+ 'exe_file_uploads',
44
+ 'leading_schema',
45
+ 'aggressive'
46
+ ]
47
+ );
48
+ }
49
+ }
src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterIntegrity.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\Lib\MeterAnalysis;
4
+
5
+ class MeterIntegrity extends MeterBase {
6
+
7
+ const SLUG = 'integrity';
8
+
9
+ protected function getWorkingMods() :array {
10
+ return array_filter(
11
+ $this->getCon()->modules,
12
+ function ( $mod ) {
13
+ return ( $mod->cfg->properties[ 'show_module_options' ] ?? false )
14
+ && $mod->getSlug() !== 'plugin';
15
+ }
16
+ );
17
+ }
18
+
19
+ protected function title() :string {
20
+ return __( 'Site Security Integrity', 'wp-simple-firewall' );
21
+ }
22
+
23
+ protected function subtitle() :string {
24
+ return __( 'How WordPress security protection is looking overall', 'wp-simple-firewall' );
25
+ }
26
+
27
+ protected function description() :array {
28
+ return [
29
+ __( "There are many components to a well-protected WordPress site.", 'wp-simple-firewall' ),
30
+ __( "This section assesses from an overall perspective and will assist you in managing your WordPress security in the most effective way possible.", 'wp-simple-firewall' ),
31
+ __( "There is an overall score included here that incorporates all other security overview scores.", 'wp-simple-firewall' ),
32
+ ];
33
+ }
34
+
35
+ protected function getComponentSlugs() :array {
36
+ $components = [
37
+ 'all',
38
+ 'ssl_certificate',
39
+ 'db_password',
40
+ 'audit_trail_enabled',
41
+ 'traffic_log_enabled',
42
+ 'report_email',
43
+ ];
44
+ if ( !$this->getCon()->getModule_SecAdmin()->getWhiteLabelController()->isEnabled() ) {
45
+ $components[] = 'shieldpro';
46
+ }
47
+ return $components;
48
+ }
49
+ }
src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterIpBlocking.php ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\Lib\MeterAnalysis;
4
+
5
+ class MeterIpBlocking extends MeterBase {
6
+
7
+ const SLUG = 'ips';
8
+
9
+ protected function getWorkingMods() :array {
10
+ return [ $this->getCon()->getModule_IPs() ];
11
+ }
12
+
13
+ protected function title() :string {
14
+ return __( 'IP Blocking and Bot Detection', 'wp-simple-firewall' );
15
+ }
16
+
17
+ protected function subtitle() :string {
18
+ return __( 'How repeat-offenders and malicious bots are handled', 'wp-simple-firewall' );
19
+ }
20
+
21
+ protected function description() :array {
22
+ return [
23
+ __( "Bot Detection & IP Blocking form the core foundation to reliable, powerful, and long-term WordPress protection.", 'wp-simple-firewall' ),
24
+ __( "Your biggest threat comes from automated bots, so detecting them quickly and blocking them early is your greatest source of protection.", 'wp-simple-firewall' ),
25
+ __( "When the security plugin detects enough bad behaviours it'll block the IP from accessing the site altogether.", 'wp-simple-firewall' ),
26
+ ];
27
+ }
28
+
29
+ protected function getComponentSlugs() :array {
30
+ return [
31
+ 'ip_autoblock',
32
+ 'ip_autoblock_limit',
33
+ 'ade_threshold',
34
+ 'author_discovery',
35
+ 'traffic_rate_limiting',
36
+ 'track_404',
37
+ 'track_loginfail',
38
+ 'track_logininvalid',
39
+ 'track_xml',
40
+ 'track_fake',
41
+ 'track_cheese',
42
+ 'track_script',
43
+ ];
44
+ }
45
+ }
src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterLockdown.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\Lib\MeterAnalysis;
4
+
5
+ class MeterLockdown extends MeterBase {
6
+
7
+ const SLUG = 'lockdown';
8
+
9
+ protected function getWorkingMods() :array {
10
+ return [ $this->getCon()->getModule_Lockdown() ];
11
+ }
12
+
13
+ protected function title() :string {
14
+ return __( 'Site Lockdown', 'wp-simple-firewall' );
15
+ }
16
+
17
+ protected function subtitle() :string {
18
+ return __( 'How various WordPress components are locked-down', 'wp-simple-firewall' );
19
+ }
20
+
21
+ protected function description() :array {
22
+ return [
23
+ __( "This section assesses how, and whether, you've locked down certain WordPress components which, for the vast majority of website, don't need to remain enabled by default.", 'wp-simple-firewall' ),
24
+ ];
25
+ }
26
+
27
+ protected function getComponentSlugs() :array {
28
+ return [
29
+ 'secadmin',
30
+ 'secadmin_admins',
31
+ 'secadmin_options',
32
+ 'lockdown_xmlrpc',
33
+ 'lockdown_file_editing',
34
+ 'author_discovery',
35
+ 'anonymous_rest',
36
+ ];
37
+ }
38
+ }
src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterLoginProtection.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\Lib\MeterAnalysis;
4
+
5
+ class MeterLoginProtection extends MeterBase {
6
+
7
+ const SLUG = 'login';
8
+
9
+ protected function getWorkingMods() :array {
10
+ return [
11
+ $this->getCon()->getModule_LoginGuard(),
12
+ $this->getCon()->getModule_UserManagement()
13
+ ];
14
+ }
15
+
16
+ protected function title() :string {
17
+ return __( 'Login Protection', 'wp-simple-firewall' );
18
+ }
19
+
20
+ protected function subtitle() :string {
21
+ return __( 'How WordPress logins are protected from brute force attacks', 'wp-simple-firewall' );
22
+ }
23
+
24
+ protected function description() :array {
25
+ $name = $this->getCon()->getHumanName();
26
+ return [
27
+ sprintf( __( '%s protects the login/register/lost-password forms in a number of important ways.', 'wp-simple-firewall' ), $name ),
28
+ __( "The most critical is the AntiBot Detection Engine which completely replaces the need for CAPTCHAs, providing a smooth user login experience.", 'wp-simple-firewall' ),
29
+ __( "There are also 2FA options available to help enforce user identify verification.", 'wp-simple-firewall' ),
30
+ ];
31
+ }
32
+
33
+ protected function getComponentSlugs() :array {
34
+ return [
35
+ 'ade_loginguard',
36
+ 'ade_login',
37
+ 'ade_register',
38
+ 'ade_lostpassword',
39
+ 'cooldown',
40
+ 'traffic_rate_limiting',
41
+ 'tp_login_forms',
42
+ '2fa',
43
+ 'pass_policies',
44
+ ];
45
+ }
46
+ }
src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterScans.php ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\Lib\MeterAnalysis;
4
+
5
+ class MeterScans extends MeterBase {
6
+
7
+ const SLUG = 'scans';
8
+
9
+ protected function getWorkingMods() :array {
10
+ return [ $this->getCon()->getModule_HackGuard() ];
11
+ }
12
+
13
+ protected function title() :string {
14
+ return __( 'Site Scanning', 'wp-simple-firewall' );
15
+ }
16
+
17
+ protected function subtitle() :string {
18
+ return __( 'Which types, and how quickly, existing vulnerabilities are discovered', 'wp-simple-firewall' );
19
+ }
20
+
21
+ protected function description() :array {
22
+ return [
23
+ __( "Regular file scanning is important to ensure malicious files are caught before they can be abused.", 'wp-simple-firewall' ),
24
+ __( "Scanning is often marketed as the most important aspect of security, but this thinking is backwards.", 'wp-simple-firewall' )
25
+ .' '.__( "Scanning is remedial and detection of malware, for example, is a symptom of larger problem i.e. that your site is vulnerable to intrusion.", 'wp-simple-firewall' ),
26
+ __( "It is, nevertheless, a critical component of your WordPress security.", 'wp-simple-firewall' ),
27
+ ];
28
+ }
29
+
30
+ protected function getComponentSlugs() :array {
31
+ return [
32
+ 'file_scanner',
33
+ 'malware_scanner',
34
+ 'auto_repair_core',
35
+ 'auto_repair_plugin',
36
+ 'auto_repair_theme',
37
+ 'filelocker_wpconfig',
38
+ 'filelocker_htaccess',
39
+ 'apc_scanner',
40
+ 'scanresults_wcf',
41
+ 'scanresults_wpv',
42
+ 'scanresults_mal',
43
+ 'scanresults_ptg',
44
+ 'scanresults_apc',
45
+ 'wpv_scanner',
46
+ 'vuln_autoupdate',
47
+ 'scan_freq',
48
+ ];
49
+ }
50
+ }
src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterSpam.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\Lib\MeterAnalysis;
4
+
5
+ class MeterSpam extends MeterBase {
6
+
7
+ const SLUG = 'spam';
8
+
9
+ protected function getWorkingMods() :array {
10
+ return [ $this->getCon()->getModule_Comments() ];
11
+ }
12
+
13
+ protected function title() :string {
14
+ return __( 'Comment & Contact Form SPAM Protection', 'wp-simple-firewall' );
15
+ }
16
+
17
+ protected function subtitle() :string {
18
+ return __( 'How all types of WordPress SPAM are handled', 'wp-simple-firewall' );
19
+ }
20
+
21
+ protected function description() :array {
22
+ $desc= [
23
+ __( "WordPress comment SPAM is primarily done via automated Bots.", 'wp-simple-firewall' ),
24
+ __( "With our powerful AntiBot Detection Engine we can thwart nearly 100% of all bot SPAM.", 'wp-simple-firewall' ),
25
+ __( "With our SPAM dictionary, we can identify human SPAM comments without sending any data off your site to 3rd parties.", 'wp-simple-firewall' ),
26
+ ];
27
+
28
+ if (!$this->getCon()->getModule_SecAdmin()->getWhiteLabelController()->isEnabled()) {
29
+ $desc[] = sprintf( __( "With %s we can directly integrate with all the major Contact Form plugins to block Contact Form SPAM from automated Bots.", 'wp-simple-firewall' ), 'ShieldPRO' );
30
+ }
31
+ return $desc;
32
+ }
33
+
34
+ protected function getComponentSlugs() :array {
35
+ return [
36
+ 'comment_spam_antibot',
37
+ 'comment_spam_human',
38
+ 'contact_forms_spam',
39
+ 'comment_approved_minimum',
40
+ ];
41
+ }
42
+ }
src/lib/src/Modules/Insights/Lib/MeterAnalysis/MeterUsers.php ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\Lib\MeterAnalysis;
4
+
5
+ class MeterUsers extends MeterBase {
6
+
7
+ const SLUG = 'users';
8
+
9
+ protected function getWorkingMods() :array {
10
+ return [
11
+ $this->getCon()->getModule_LoginGuard(),
12
+ $this->getCon()->getModule_UserManagement()
13
+ ];
14
+ }
15
+
16
+ protected function title() :string {
17
+ return __( 'Customer And Visitor Protection', 'wp-simple-firewall' );
18
+ }
19
+
20
+ protected function subtitle() :string {
21
+ return __( 'How well customers, users, and visitors are protected', 'wp-simple-firewall' );
22
+ }
23
+
24
+ protected function description() :array {
25
+ return [
26
+ __( "It is important to not only block malicious request, but also protect your existing users and customers.", 'wp-simple-firewall' ),
27
+ __( "This section assesses how well you're protecting existing users, administrator accounts, and passwords.", 'wp-simple-firewall' ),
28
+ __( "Another, often overlooked, component of security is how you communicate to your visitors that you're employing strong security practices and that you take their data and privacy seriously.", 'wp-simple-firewall' ),
29
+ ];
30
+ }
31
+
32
+ protected function getComponentSlugs() :array {
33
+ $components = [
34
+ 'ssl_certificate',
35
+ 'admin_user',
36
+ 'secadmin_admins',
37
+ '2fa',
38
+ 'sessions_idle',
39
+ 'users_inactive',
40
+ 'user_email_validation',
41
+ 'pass_policies',
42
+ 'pass_pwned',
43
+ 'pass_str',
44
+ 'headers',
45
+ ];
46
+ if ( !$this->getCon()->getModule_SecAdmin()->getWhiteLabelController()->isEnabled() ) {
47
+ $components[] = 'plugin_badge';
48
+ }
49
+ return $components;
50
+ }
51
+ }
src/lib/src/Modules/Insights/Lib/{SideMenuBuilder.php → NavMenuBuilder.php} RENAMED
@@ -6,7 +6,7 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\ModCon;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
- class SideMenuBuilder {
10
 
11
  use ModConsumer;
12
 
@@ -49,7 +49,10 @@ class SideMenuBuilder {
49
  }
50
  }
51
 
52
- if ( !empty( $item[ 'sub_items' ] ) ) {
 
 
 
53
  $item[ 'sub_items' ] = array_map( function ( $sub ) {
54
  if ( empty( $sub[ 'classes' ] ) ) {
55
  $sub[ 'classes' ] = [];
@@ -140,12 +143,12 @@ class SideMenuBuilder {
140
  'title' => __( 'Configure', 'wp-simple-firewall' ),
141
  'href' => $con->getModule_AuditTrail()->getUrl_AdminPage(),
142
  ],
143
- [
144
- 'slug' => 'audit-download',
145
- 'title' => sprintf( __( 'Download (%s)', 'wp-simple-firewall' ), 'JSON' ),
146
- 'href' => $con->getModule_AuditTrail()->createFileDownloadLink( 'db_log' ),
147
- 'classes' => [ 'shield_file_download' ],
148
- ],
149
  [
150
  'slug' => 'audit-glossary',
151
  'title' => __( 'Glossary', 'wp-simple-firewall' ),
@@ -276,27 +279,36 @@ class SideMenuBuilder {
276
  }
277
 
278
  private function configuration() :array {
279
- /** @var ModCon $mod */
280
- $mod = $this->getMod();
281
  $slug = 'configuration';
282
 
283
  $subItems = [];
284
- foreach ( $mod->getModulesSummaryData() as $modData ) {
285
- if ( $modData[ 'show_mod_opts' ] ) {
286
- $subItems[] = [
287
- 'slug' => $slug.'-'.$modData[ 'slug' ],
288
- 'title' => $modData[ 'sidebar_name' ] ?? $modData[ 'name' ],
289
- 'href' => $modData[ 'href' ],
290
- 'classes' => [ 'dynamic_body_load' ],
291
- 'data' => [
 
 
292
  'load_type' => $slug,
293
- 'load_variant' => $modData[ 'slug' ],
 
294
  ],
295
- 'active' => Services::Request()->query( 'subnav' ) === $modData[ 'slug' ]
 
296
  ];
297
  }
298
  }
299
 
 
 
 
 
 
 
 
300
  return [
301
  'slug' => $slug,
302
  'title' => __( 'Config', 'wp-simple-firewall' ),
@@ -411,6 +423,12 @@ class SideMenuBuilder {
411
  'href' => $mod->getUrl_SubInsightsPage( 'notes' ),
412
  'active' => $this->getInav() === 'notes'
413
  ],
 
 
 
 
 
 
414
  [
415
  'slug' => $slug.'-debug',
416
  'title' => __( "Debug Info", 'wp-simple-firewall' ),
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
+ class NavMenuBuilder {
10
 
11
  use ModConsumer;
12
 
49
  }
50
  }
51
 
52
+ if ( empty( $item[ 'sub_items' ] ) ) {
53
+ $item[ 'classes' ][] = 'body_content_link';
54
+ }
55
+ else {
56
  $item[ 'sub_items' ] = array_map( function ( $sub ) {
57
  if ( empty( $sub[ 'classes' ] ) ) {
58
  $sub[ 'classes' ] = [];
143
  'title' => __( 'Configure', 'wp-simple-firewall' ),
144
  'href' => $con->getModule_AuditTrail()->getUrl_AdminPage(),
145
  ],
146
+ // [
147
+ // 'slug' => 'audit-download',
148
+ // 'title' => sprintf( __( 'Download (%s)', 'wp-simple-firewall' ), 'JSON' ),
149
+ // 'href' => $con->getModule_AuditTrail()->createFileDownloadLink( 'db_log' ),
150
+ // 'classes' => [ 'shield_file_download' ],
151
+ // ],
152
  [
153
  'slug' => 'audit-glossary',
154
  'title' => __( 'Glossary', 'wp-simple-firewall' ),
279
  }
280
 
281
  private function configuration() :array {
 
 
282
  $slug = 'configuration';
283
 
284
  $subItems = [];
285
+ foreach ( $this->getCon()->modules as $module ) {
286
+ $cfg = $module->cfg;
287
+ if ( $cfg->properties[ 'show_module_options' ] ) {
288
+ $subItems[ $cfg->slug ] = [
289
+ 'mod_slug' => $cfg->slug,
290
+ 'slug' => $slug.'-'.$cfg->slug,
291
+ 'title' => __( $cfg->properties[ 'sidebar_name' ], 'wp-simple-firewall' ),
292
+ 'href' => $module->getUrl_OptionsConfigPage(),
293
+ 'classes' => [ 'dynamic_body_load', 'body_content_link' ],
294
+ 'data' => [
295
  'load_type' => $slug,
296
+ 'load_variant' => $cfg->slug,
297
+ 'target_href' => $module->getUrl_OptionsConfigPage(),
298
  ],
299
+ 'active' => Services::Request()->query( 'subnav' ) === $cfg->slug,
300
+ 'menu_priority' => $cfg->menus[ 'config_menu_priority' ],
301
  ];
302
  }
303
  }
304
 
305
+ uasort( $subItems, function ( $a, $b ) {
306
+ if ( $a[ 'menu_priority' ] == $b[ 'menu_priority' ] ) {
307
+ return 0;
308
+ }
309
+ return ( $a[ 'menu_priority' ] < $b[ 'menu_priority' ] ) ? -1 : 1;
310
+ } );
311
+
312
  return [
313
  'slug' => $slug,
314
  'title' => __( 'Config', 'wp-simple-firewall' ),
423
  'href' => $mod->getUrl_SubInsightsPage( 'notes' ),
424
  'active' => $this->getInav() === 'notes'
425
  ],
426
+ [
427
+ 'slug' => $slug.'-rules',
428
+ 'title' => __( 'Rules', 'wp-simple-firewall' ),
429
+ 'href' => $mod->getUrl_SubInsightsPage( 'rules' ),
430
+ 'active' => $this->getInav() === 'rules'
431
+ ],
432
  [
433
  'slug' => $slug.'-debug',
434
  'title' => __( "Debug Info", 'wp-simple-firewall' ),
src/lib/src/Modules/Insights/Lib/OverviewCards.php DELETED
@@ -1,91 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\Lib;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
-
7
- class OverviewCards {
8
-
9
- use ModConsumer;
10
-
11
- public function buildForStatic() :array {
12
- return $this->buildForShuffle()[ 'sections' ];
13
- }
14
-
15
- public function buildForShuffle() :array {
16
-
17
- $stateDefs = [
18
- -2 => [
19
- 'name' => __( 'Danger', 'wp-simple-firewall' ),
20
- 'slug' => 'danger',
21
- ],
22
- -1 => [
23
- 'name' => __( 'Warning', 'wp-simple-firewall' ),
24
- 'slug' => 'warning',
25
- ],
26
- -0 => [
27
- 'name' => __( 'Info', 'wp-simple-firewall' ),
28
- 'slug' => 'info',
29
- ],
30
- 1 => [
31
- 'name' => __( 'Good', 'wp-simple-firewall' ),
32
- 'slug' => 'good',
33
- ],
34
- ];
35
-
36
- $stateNames = [];
37
- foreach ( $stateDefs as $stateKey => $stateDef ) {
38
- $stateNames[ $stateDef[ 'slug' ] ] = $stateDef[ 'name' ];
39
- }
40
-
41
- $allSections = [];
42
- $modGroups = [];
43
- $allStates = [];
44
- foreach ( $this->getCon()->modules as $mod ) {
45
-
46
- $modSections = $mod->getUIHandler()->getInsightsOverviewCards();
47
-
48
- foreach ( $modSections as $sectionKey => $section ) {
49
- if ( empty( $section[ 'cards' ] ) || !is_array( $section[ 'cards' ] ) ) {
50
- continue;
51
- }
52
-
53
- $section[ 'count' ] = count( $section[ 'cards' ] );
54
-
55
- foreach ( $section[ 'cards' ] as $key => &$card ) {
56
- if ( empty( $card[ 'id' ] ) ) {
57
- $card[ 'id' ] = $key;
58
- }
59
- if ( empty( $card[ 'groups' ] ) || !is_array( $card[ 'groups' ] ) ) {
60
- $card[ 'groups' ] = [];
61
- }
62
- if ( !isset( $card[ 'state' ] ) ) {
63
- $card[ 'state' ] = 0;
64
- }
65
-
66
- $card[ 'mod' ] = $mod->getMainFeatureName();
67
- $card[ 'groups' ][ $mod->getSlug() ] = $mod->getMainFeatureName();
68
-
69
- // Translate state value (numeric) to text.
70
- $nState = $card[ 'state' ];
71
- $card[ 'groups' ][ $stateDefs[ $nState ][ 'slug' ] ] = $stateDefs[ $nState ][ 'name' ];
72
- $card[ 'state' ] = $stateDefs[ $nState ][ 'slug' ];
73
-
74
- $allStates[ $nState ] = $card[ 'state' ];
75
- }
76
-
77
- $allSections[ $sectionKey ] = $section;
78
- $modGroups[ $mod->getSlug() ] = $mod->getMainFeatureName();
79
- }
80
- }
81
-
82
- ksort( $allStates );
83
-
84
- return [
85
- 'sections' => $allSections,
86
- 'mod_groups' => $modGroups,
87
- 'states' => $allStates,
88
- 'state_names' => $stateNames,
89
- ];
90
- }
91
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/Insights/Lib/Requests/DynamicPageLoader.php CHANGED
@@ -4,6 +4,8 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\Lib\Requests;
4
 
5
  use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\ModCon;
 
 
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
 
9
  /**
@@ -17,8 +19,6 @@ class DynamicPageLoader extends DynPropertiesClass {
17
  private $params = [];
18
 
19
  /**
20
- * @param array $params
21
- * @return array
22
  * @throws \Exception
23
  */
24
  public function build( array $params ) :array {
@@ -59,12 +59,17 @@ class DynamicPageLoader extends DynPropertiesClass {
59
  return $content;
60
  }
61
 
 
 
 
62
  private function getPageUrl() :string {
63
  $con = $this->getCon();
 
 
64
 
65
  switch ( $this->load_type ) {
66
  case 'configuration':
67
- $url = $con->getModule( $this->load_variant )->getUrl_AdminPage();
68
  break;
69
 
70
  default:
@@ -94,12 +99,12 @@ class DynamicPageLoader extends DynPropertiesClass {
94
  * @throws \Exception
95
  */
96
  private function renderConfiguration() :string {
97
-
98
  $mod = $this->getCon()->getModule( $this->load_variant );
99
  if ( !$mod instanceof ModCon ) {
100
  throw new \Exception( 'Invalid dynamic page load data (variant)' );
101
  }
102
-
103
- return $mod->renderOptionsForm();
 
104
  }
105
  }
4
 
5
  use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\ModCon;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Options\RenderOptionsForm;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights;
9
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
10
 
11
  /**
19
  private $params = [];
20
 
21
  /**
 
 
22
  * @throws \Exception
23
  */
24
  public function build( array $params ) :array {
59
  return $content;
60
  }
61
 
62
+ /**
63
+ * this is messy! Need to build this properly.
64
+ */
65
  private function getPageUrl() :string {
66
  $con = $this->getCon();
67
+ /** @var Insights\ModCon $mod */
68
+ $mod = $this->getMod();
69
 
70
  switch ( $this->load_type ) {
71
  case 'configuration':
72
+ $url = $mod->getUrl_SubInsightsPage( 'settings', $this->load_variant );
73
  break;
74
 
75
  default:
99
  * @throws \Exception
100
  */
101
  private function renderConfiguration() :string {
 
102
  $mod = $this->getCon()->getModule( $this->load_variant );
103
  if ( !$mod instanceof ModCon ) {
104
  throw new \Exception( 'Invalid dynamic page load data (variant)' );
105
  }
106
+ return ( new RenderOptionsForm() )
107
+ ->setMod( $mod )
108
+ ->render();
109
  }
110
  }
src/lib/src/Modules/Insights/Lib/SummaryCards.php DELETED
@@ -1,182 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\Lib;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\HackGuard\Scan\Controller\Afs;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
-
9
- class SummaryCards {
10
-
11
- use ModConsumer;
12
-
13
- public function build() :array {
14
- $cards = [];
15
- $cards = array_merge(
16
- $cards,
17
- $this->getLoginSummary(),
18
- $this->getFirewallSummary(),
19
- $this->getIPBlockingSummary(),
20
- $this->getSecurityAdminSummary(),
21
- $this->getCommentSpamSummary(),
22
- $this->getHackguardSummary(),
23
- $this->getUserSummary(),
24
- $this->getAuditTrailSummary(),
25
- $this->getPluginSummary()
26
- );
27
-
28
- return array_map(
29
- function ( $card ) {
30
- $card = array_merge(
31
- [
32
- 'enabled' => false
33
- ],
34
- $card
35
- );
36
-
37
- if ( empty( $card[ 'icon' ] ) ) {
38
- $card[ 'icon' ] = $card[ 'enabled' ] ?
39
- $this->getCon()->svgs->raw( 'bootstrap/shield-fill-check.svg' )
40
- : $this->getCon()->svgs->raw( 'bootstrap/shield-fill-exclamation.svg' );
41
- }
42
-
43
- return $card;
44
- },
45
- $cards
46
- );
47
- }
48
-
49
- private function getPluginSummary() :array {
50
- $mod = $this->getCon()->getModule_Plugin();
51
- return [
52
- $mod->getSlug() => [
53
- 'title' => 'Plugin',
54
- 'enabled' => $mod->isModuleEnabled(),
55
- 'href' => $mod->getUrl_DirectLinkToOption( 'global_enable_plugin_features' ),
56
- ]
57
- ];
58
- }
59
-
60
- private function getCommentSpamSummary() :array {
61
- $mod = $this->getCon()->getModule_Comments();
62
- /** @var Modules\CommentsFilter\Options $opts */
63
- $opts = $mod->getOptions();
64
- return [
65
- $mod->getSlug() => [
66
- 'title' => 'Comment SPAM',
67
- 'enabled' => $mod->isModuleEnabled()
68
- && $opts->isEnabledAntiBot(),
69
- 'href' => $mod->getUrl_AdminPage(),
70
- ]
71
- ];
72
- }
73
-
74
- private function getAuditTrailSummary() :array {
75
- $mod = $this->getCon()->getModule_AuditTrail();
76
- return [
77
- $mod->getSlug() => [
78
- 'title' => "Audit Trail",
79
- 'enabled' => $mod->isModuleEnabled(),
80
- 'href' => $mod->getUrl_AdminPage(),
81
- ]
82
- ];
83
- }
84
-
85
- private function getUserSummary() :array {
86
- $mod = $this->getCon()->getModule_UserManagement();
87
- /** @var Modules\UserManagement\Options $opts */
88
- $opts = $mod->getOptions();
89
- return [
90
- $mod->getSlug() => [
91
- 'title' => "'Pwned' Passwords",
92
- 'enabled' => $mod->isModuleEnabled()
93
- && $opts->isPasswordPoliciesEnabled()
94
- && $opts->isPassPreventPwned(),
95
- 'href' => $mod->getUrl_DirectLinkToOption( 'enable_password_policies' ),
96
- ]
97
- ];
98
- }
99
-
100
- private function getHackguardSummary() :array {
101
- $mod = $this->getCon()->getModule_HackGuard();
102
- return [
103
- $mod->getSlug() => [
104
- 'title' => 'Scanners',
105
- 'enabled' => $mod->isModuleEnabled(),
106
- 'href' => $mod->getUrl_DirectLinkToOption( 'enable_hack_protect' ),
107
- ],
108
- 'core_files' => [
109
- 'title' => 'WordPress File Scan',
110
- 'enabled' => $mod->isModuleEnabled()
111
- && $mod->getScanCon( Afs::SCAN_SLUG )->isEnabled(),
112
- 'href' => $mod->getUrl_DirectLinkToOption( 'enable_core_file_integrity_scan' ),
113
- ]
114
- ];
115
- }
116
-
117
- private function getIPBlockingSummary() :array {
118
- $mod = $this->getCon()->getModule_IPs();
119
- /** @var Modules\IPs\Options $opts */
120
- $opts = $mod->getOptions();
121
- return [
122
- $mod->getSlug() => [
123
- 'title' => 'Auto IP Block',
124
- 'enabled' => $mod->isModuleEnabled()
125
- && ( $opts->getOffenseLimit() > 0 ),
126
- 'href' => $mod->getUrl_AdminPage(),
127
- ]
128
- ];
129
- }
130
-
131
- private function getSecurityAdminSummary() :array {
132
- $mod = $this->getCon()->getModule_SecAdmin();
133
- return [
134
- $mod->getSlug() => [
135
- 'title' => $mod->getMainFeatureName(),
136
- 'enabled' => $mod->isModuleEnabled()
137
- && $mod->getSecurityAdminController()->isEnabledSecAdmin(),
138
- 'href' => $mod->getUrl_AdminPage(),
139
- ]
140
- ];
141
- }
142
-
143
- private function getLoginSummary() :array {
144
- $mod = $this->getCon()->getModule_LoginGuard();
145
- /** @var Modules\LoginGuard\Options $opts */
146
- $opts = $mod->getOptions();
147
- return [
148
- $mod->getSlug() => [
149
- 'title' => 'Login',
150
- 'enabled' => $mod->isModuleEnabled() && $opts->isEnabledAntiBot(),
151
- 'href' => $mod->getUrl_AdminPage(),
152
- ]
153
- ];
154
- }
155
-
156
- private function getFirewallSummary() :array {
157
- $mod = $this->getCon()->getModule_Firewall();
158
- /** @var Modules\LoginGuard\Options $opts */
159
- $opts = $mod->getOptions();
160
- return [
161
- $mod->getSlug() => [
162
- 'title' => 'Firewall',
163
- 'enabled' => $mod->isModuleEnabled(),
164
- 'href' => $mod->getUrl_DirectLinkToOption( 'enable_firewall' ),
165
- ]
166
- ];
167
- }
168
-
169
- /**
170
- * @param Modules\Base\ModCon $mod
171
- * @return array[]
172
- */
173
- private function getBasicModSummary( $mod ) :array {
174
- return [
175
- $mod->getSlug() => [
176
- 'title' => $mod->getMainFeatureName(),
177
- 'enabled' => $mod->isModuleEnabled(),
178
- 'href' => $mod->getUrl_AdminPage(),
179
- ]
180
- ];
181
- }
182
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/Insights/ModCon.php CHANGED
@@ -26,7 +26,7 @@ class ModCon extends BaseShield\ModCon {
26
  if ( !$con->isModulePage() && $con->getModule_Plugin()->getActivateLength() < 5 ) {
27
  Services::Response()->redirect( $con->getModule_Plugin()->getUrl_Wizard( 'welcome' ) );
28
  }
29
- elseif ( $this->getAdminPage()->isCurrentPage() && empty( Services::Request()->query( 'inav' ) ) ) {
30
  Services::Response()->redirect( $con->getPluginUrl_DashboardHome() );
31
  }
32
  }
@@ -52,42 +52,44 @@ class ModCon extends BaseShield\ModCon {
52
  return $this->getUrl_SubInsightsPage( 'users' );
53
  }
54
 
55
- public function getUrl_SubInsightsPage( string $subPage ) :string {
56
  return add_query_arg(
57
- [ 'inav' => sanitize_key( $subPage ) ],
 
 
 
58
  $this->getUrl_AdminPage()
59
  );
60
  }
61
 
62
- protected function renderModulePage( array $data = [] ) :string {
63
- /** @var UI $UI */
64
- $UI = $this->getUIHandler();
65
- return $UI->renderPages();
66
  }
67
 
68
  public function getScriptLocalisations() :array {
69
- $con = $this->getCon();
70
- $modPlugin = $con->getModule_Plugin();
71
-
72
  $locals = parent::getScriptLocalisations();
73
- $locals[] = [
74
- 'plugin',
75
- 'icwp_wpsf_vars_insights',
76
- [
77
- 'strings' => [
78
- 'select_action' => __( 'Please select an action to perform.', 'wp-simple-firewall' ),
79
- 'are_you_sure' => __( 'Are you sure?', 'wp-simple-firewall' ),
80
- 'absolutely_sure' => __( 'Are you absolutely sure?', 'wp-simple-firewall' ),
81
- ],
82
- ]
83
- ];
84
 
85
- $locals[] = [
86
- $con->prefix( 'ip_detect' ),
87
- 'icwp_wpsf_vars_ipdetect',
88
- [ 'ajax' => $modPlugin->getAjaxActionData( 'ipdetect' ) ]
 
 
 
 
89
  ];
90
 
 
 
 
 
 
 
 
 
 
 
 
91
  $locals[] = [
92
  'shield/navigation',
93
  'shield_vars_navigation',
@@ -108,7 +110,7 @@ class ModCon extends BaseShield\ModCon {
108
  ];
109
 
110
  $con = $this->getCon();
111
- $inav = Services::Request()->query( 'inav' );
112
  if ( empty( $inav ) ) {
113
  $inav = 'overview';
114
  }
@@ -122,8 +124,6 @@ class ModCon extends BaseShield\ModCon {
122
 
123
  case 'overview':
124
  $enq[ Enqueue::JS ] = [
125
- 'shuffle',
126
- 'shield/shuffle',
127
  'ip_detect'
128
  ];
129
  break;
@@ -157,7 +157,7 @@ class ModCon extends BaseShield\ModCon {
157
  case 'users':
158
  case 'stats':
159
 
160
- $enq[ Enqueue::JS ][] = 'shield/tables';
161
  if ( in_array( $inav, [ 'scans_results', 'scans_run' ] ) ) {
162
  $enq[ Enqueue::JS ][] = 'shield/scans';
163
  }
26
  if ( !$con->isModulePage() && $con->getModule_Plugin()->getActivateLength() < 5 ) {
27
  Services::Response()->redirect( $con->getModule_Plugin()->getUrl_Wizard( 'welcome' ) );
28
  }
29
+ elseif ( $this->getAdminPage()->isCurrentPage() && empty( $this->getCurrentInsightsPage() ) ) {
30
  Services::Response()->redirect( $con->getPluginUrl_DashboardHome() );
31
  }
32
  }
52
  return $this->getUrl_SubInsightsPage( 'users' );
53
  }
54
 
55
+ public function getUrl_SubInsightsPage( string $inavPage, string $subNav = '' ) :string {
56
  return add_query_arg(
57
+ array_filter( [
58
+ 'inav' => sanitize_key( $inavPage ),
59
+ 'subnav' => sanitize_key( $subNav ),
60
+ ] ),
61
  $this->getUrl_AdminPage()
62
  );
63
  }
64
 
65
+ public function getCurrentInsightsPage() :string {
66
+ return (string)Services::Request()->query( 'inav' );
 
 
67
  }
68
 
69
  public function getScriptLocalisations() :array {
 
 
 
70
  $locals = parent::getScriptLocalisations();
 
 
 
 
 
 
 
 
 
 
 
71
 
72
+ $insightsData = [
73
+ 'strings' => [
74
+ 'select_action' => __( 'Please select an action to perform.', 'wp-simple-firewall' ),
75
+ 'are_you_sure' => __( 'Are you sure?', 'wp-simple-firewall' ),
76
+ 'absolutely_sure' => __( 'Are you absolutely sure?', 'wp-simple-firewall' ),
77
+ ],
78
+ 'vars' => [
79
+ ],
80
  ];
81
 
82
+ if ( $this->getCurrentInsightsPage() === 'overview' ) {
83
+ // Supply data for the progress meters
84
+ $insightsData[ 'vars' ][ 'meters' ] = [
85
+ 'ajax' => [
86
+ 'render_meter_analysis' => $this->getAjaxActionData( 'render_meter_analysis' ),
87
+ ]
88
+ ];
89
+ }
90
+
91
+ $locals[] = [ 'plugin', 'icwp_wpsf_vars_insights', $insightsData ];
92
+
93
  $locals[] = [
94
  'shield/navigation',
95
  'shield_vars_navigation',
110
  ];
111
 
112
  $con = $this->getCon();
113
+ $inav = $this->getCurrentInsightsPage();
114
  if ( empty( $inav ) ) {
115
  $inav = 'overview';
116
  }
124
 
125
  case 'overview':
126
  $enq[ Enqueue::JS ] = [
 
 
127
  'ip_detect'
128
  ];
129
  break;
157
  case 'users':
158
  case 'stats':
159
 
160
+ $enq[ Enqueue::JS ][] = 'shield/tables';
161
  if ( in_array( $inav, [ 'scans_results', 'scans_run' ] ) ) {
162
  $enq[ Enqueue::JS ][] = 'shield/scans';
163
  }
src/lib/src/Modules/Insights/Strings.php CHANGED
@@ -17,7 +17,6 @@ class Strings extends Base\Strings {
17
  'suggestion' => ucfirst( __( 'suggestion', 'wp-simple-firewall' ) ),
18
  'box_welcome_title' => sprintf( __( 'Welcome To %s Security Insights Dashboard', 'wp-simple-firewall' ), $name ),
19
  'options' => __( 'Options', 'wp-simple-firewall' ),
20
- 'not_available' => __( 'Sorry, this feature is included with Pro subscriptions.', 'wp-simple-firewall' ),
21
  'not_enabled' => __( "This feature isn't currently enabled.", 'wp-simple-firewall' ),
22
  'please_upgrade' => __( 'You can get this feature (along with loads more) by going Pro.', 'wp-simple-firewall' ),
23
  'please_enable' => __( 'Please turn on this feature in the options.', 'wp-simple-firewall' ),
17
  'suggestion' => ucfirst( __( 'suggestion', 'wp-simple-firewall' ) ),
18
  'box_welcome_title' => sprintf( __( 'Welcome To %s Security Insights Dashboard', 'wp-simple-firewall' ), $name ),
19
  'options' => __( 'Options', 'wp-simple-firewall' ),
 
20
  'not_enabled' => __( "This feature isn't currently enabled.", 'wp-simple-firewall' ),
21
  'please_upgrade' => __( 'You can get this feature (along with loads more) by going Pro.', 'wp-simple-firewall' ),
22
  'please_enable' => __( 'Please turn on this feature in the options.', 'wp-simple-firewall' ),
src/lib/src/Modules/Insights/UI.php CHANGED
@@ -33,13 +33,10 @@ class UI extends BaseShield\UI {
33
 
34
  private function buildInsightsVars_Overview() :array {
35
  return [
36
- 'vars' => [
37
- 'overview_cards' => ( new Lib\OverviewCards() )
38
- ->setMod( $this->getMod() )
39
- ->buildForShuffle(),
40
- 'summary_cards' => ( new Lib\SummaryCards() )
41
  ->setMod( $this->getMod() )
42
- ->build(),
43
  ],
44
  'strings' => [
45
  'click_clear_filter' => __( 'Click To Filter By Security Area or Status', 'wp-simple-firewall' ),
@@ -63,6 +60,7 @@ class UI extends BaseShield\UI {
63
 
64
  $modPlugin = $con->getModule_Plugin();
65
 
 
66
  switch ( $inav ) {
67
 
68
  case 'audit':
@@ -88,12 +86,6 @@ class UI extends BaseShield\UI {
88
  ];
89
  break;
90
 
91
- case 'dashboard':
92
- /** @var Shield\Modules\Plugin\UI $UI */
93
- $UI = $con->getModule_Plugin()->getUIHandler();
94
- $data = $UI->buildInsightsVars_Dashboard();
95
- break;
96
-
97
  case 'debug':
98
  /** @var Shield\Modules\Plugin\UI $UI */
99
  $UI = $con->getModule_Plugin()->getUIHandler();
@@ -117,7 +109,11 @@ class UI extends BaseShield\UI {
117
  case 'license':
118
  /** @var Shield\Modules\License\UI $UILicense */
119
  $UILicense = $con->getModule_License()->getUIHandler();
120
- $data = $UILicense->buildInsightsVars();
 
 
 
 
121
  break;
122
 
123
  case 'notes':
@@ -136,6 +132,14 @@ class UI extends BaseShield\UI {
136
  $data = $UIReporting->buildInsightsVars();
137
  break;
138
 
 
 
 
 
 
 
 
 
139
  case 'scans_results':
140
  /** @var Shield\Modules\HackGuard\UI $UIHackGuard */
141
  $UIHackGuard = $con->getModule_HackGuard()->getUIHandler();
@@ -164,6 +168,7 @@ class UI extends BaseShield\UI {
164
  $data = $UIUsers->buildInsightsVars();
165
  break;
166
 
 
167
  case 'overview':
168
  case 'index':
169
  $data = $this->buildInsightsVars_Overview();
@@ -202,29 +207,24 @@ class UI extends BaseShield\UI {
202
  'notes' => __( 'Admin Notes', 'wp-simple-firewall' ),
203
  'users' => __( 'User Sessions', 'wp-simple-firewall' ),
204
  'license' => __( 'ShieldPRO', 'wp-simple-firewall' ),
205
- 'importexport' => __( 'Import / Export', 'wp-simple-firewall' ),
206
  'reports' => __( 'Reports', 'wp-simple-firewall' ),
207
  'debug' => __( 'Debug', 'wp-simple-firewall' ),
 
208
  'free_trial' => __( 'Free Trial', 'wp-simple-firewall' ),
209
  'wizard' => __( 'Wizard', 'wp-simple-firewall' ),
210
  ];
211
 
212
- $modsToSearch = array_filter(
213
- $mod->getModulesSummaryData(),
214
- function ( $modSummary ) {
215
- return !empty( $modSummary[ 'show_mod_opts' ] );
216
- }
217
- );
218
-
219
  $pageTitle = $availablePages[ $inav ];
220
- if ( !empty( $subNavSection ) ) {
 
221
  $pageTitle = sprintf( '%s: %s',
222
- __( 'Configuration', 'wp-simple-firewall' ), $modsToSearch[ $subNavSection ][ 'name' ] );
223
  }
224
 
225
- if ( $this->getCon()->getModule_SecAdmin()->getWhiteLabelController()->isEnabled() ) {
226
  $dashboardLogo = ( new Shield\Modules\SecurityAdmin\Lib\WhiteLabel\BuildOptions() )
227
- ->setMod( $this->getCon()->getModule_SecAdmin() )
228
  ->build()[ 'url_login2fa_logourl' ];
229
  }
230
  else {
@@ -251,10 +251,9 @@ class UI extends BaseShield\UI {
251
  ],
252
  'vars' => [
253
  'changelog_id' => $con->cfg->meta[ 'announcekit_changelog_id' ],
254
- 'mods' => $this->buildSelectData_ModuleSettings(),
255
  'search_select' => $this->buildSelectData_OptionsSearch(),
256
  'active_module_settings' => $subNavSection,
257
- 'navbar_menu' => ( new Lib\SideMenuBuilder() )
258
  ->setMod( $this->getMod() )
259
  ->build()
260
  ],
@@ -269,14 +268,13 @@ class UI extends BaseShield\UI {
269
 
270
  return $mod->renderTemplate(
271
  sprintf( '/wpadmin_pages/insights/%s/index.twig', $templateDir ),
272
- $data,
273
- true
274
  );
275
  }
276
 
277
  private function renderTabEvents() :string {
278
  $con = $this->getCon();
279
- $srvEvents = $this->getCon()->loadEventsService();
280
 
281
  $eventsSortedByLevel = [
282
  'Alert' => [],
33
 
34
  private function buildInsightsVars_Overview() :array {
35
  return [
36
+ 'content' => [
37
+ 'progress_meters' => ( new Lib\MeterAnalysis\Handler() )
 
 
 
38
  ->setMod( $this->getMod() )
39
+ ->renderDashboardMeters(),
40
  ],
41
  'strings' => [
42
  'click_clear_filter' => __( 'Click To Filter By Security Area or Status', 'wp-simple-firewall' ),
60
 
61
  $modPlugin = $con->getModule_Plugin();
62
 
63
+ $data = [];
64
  switch ( $inav ) {
65
 
66
  case 'audit':
86
  ];
87
  break;
88
 
 
 
 
 
 
 
89
  case 'debug':
90
  /** @var Shield\Modules\Plugin\UI $UI */
91
  $UI = $con->getModule_Plugin()->getUIHandler();
109
  case 'license':
110
  /** @var Shield\Modules\License\UI $UILicense */
111
  $UILicense = $con->getModule_License()->getUIHandler();
112
+ $data = [
113
+ 'content' => [
114
+ 'licensing' => $UILicense->renderLicensePage()
115
+ ],
116
+ ];
117
  break;
118
 
119
  case 'notes':
132
  $data = $UIReporting->buildInsightsVars();
133
  break;
134
 
135
+ case 'rules':
136
+ $data = [
137
+ 'content' => [
138
+ 'rules_summary' => $con->rules->renderSummary()
139
+ ]
140
+ ];
141
+ break;
142
+
143
  case 'scans_results':
144
  /** @var Shield\Modules\HackGuard\UI $UIHackGuard */
145
  $UIHackGuard = $con->getModule_HackGuard()->getUIHandler();
168
  $data = $UIUsers->buildInsightsVars();
169
  break;
170
 
171
+ case 'dashboard':
172
  case 'overview':
173
  case 'index':
174
  $data = $this->buildInsightsVars_Overview();
207
  'notes' => __( 'Admin Notes', 'wp-simple-firewall' ),
208
  'users' => __( 'User Sessions', 'wp-simple-firewall' ),
209
  'license' => __( 'ShieldPRO', 'wp-simple-firewall' ),
210
+ 'importexport' => sprintf( '%s / %s', __( 'Import', 'wp-simple-firewall' ), __( 'Export', 'wp-simple-firewall' ) ),
211
  'reports' => __( 'Reports', 'wp-simple-firewall' ),
212
  'debug' => __( 'Debug', 'wp-simple-firewall' ),
213
+ 'rules' => __( 'Rules', 'wp-simple-firewall' ),
214
  'free_trial' => __( 'Free Trial', 'wp-simple-firewall' ),
215
  'wizard' => __( 'Wizard', 'wp-simple-firewall' ),
216
  ];
217
 
 
 
 
 
 
 
 
218
  $pageTitle = $availablePages[ $inav ];
219
+ if ( $inav === 'settings' && !empty( $subNavSection ) ) {
220
+ $mod = $con->getModule( $subNavSection );
221
  $pageTitle = sprintf( '%s: %s',
222
+ __( 'Configuration', 'wp-simple-firewall' ), empty( $mod ) ? 'Unknown Module' : $mod->getMainFeatureName() );
223
  }
224
 
225
+ if ( $con->getModule_SecAdmin()->getWhiteLabelController()->isEnabled() ) {
226
  $dashboardLogo = ( new Shield\Modules\SecurityAdmin\Lib\WhiteLabel\BuildOptions() )
227
+ ->setMod( $con->getModule_SecAdmin() )
228
  ->build()[ 'url_login2fa_logourl' ];
229
  }
230
  else {
251
  ],
252
  'vars' => [
253
  'changelog_id' => $con->cfg->meta[ 'announcekit_changelog_id' ],
 
254
  'search_select' => $this->buildSelectData_OptionsSearch(),
255
  'active_module_settings' => $subNavSection,
256
+ 'navbar_menu' => ( new Lib\NavMenuBuilder() )
257
  ->setMod( $this->getMod() )
258
  ->build()
259
  ],
268
 
269
  return $mod->renderTemplate(
270
  sprintf( '/wpadmin_pages/insights/%s/index.twig', $templateDir ),
271
+ $data
 
272
  );
273
  }
274
 
275
  private function renderTabEvents() :string {
276
  $con = $this->getCon();
277
+ $srvEvents = $con->loadEventsService();
278
 
279
  $eventsSortedByLevel = [
280
  'Alert' => [],
src/lib/src/Modules/Integrations/Lib/Bots/Common/BaseBotDetectionController.php CHANGED
@@ -3,14 +3,11 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Bots\Common;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Common\ExecOnceModConsumer;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\ModCon;
7
 
8
  abstract class BaseBotDetectionController extends ExecOnceModConsumer {
9
 
10
  protected function canRun() :bool {
11
- /** @var ModCon $mod */
12
- $mod = $this->getMod();
13
- return $this->isEnabled() && !$mod->isVisitorWhitelisted();
14
  }
15
 
16
  protected function run() {
@@ -24,7 +21,8 @@ abstract class BaseBotDetectionController extends ExecOnceModConsumer {
24
  array_flip( $this->getSelectedProviders() )
25
  ),
26
  function ( $provider ) {
27
- return call_user_func( $provider.'::IsProviderInstalled' );
 
28
  }
29
  )
30
  );
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Bots\Common;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Common\ExecOnceModConsumer;
 
6
 
7
  abstract class BaseBotDetectionController extends ExecOnceModConsumer {
8
 
9
  protected function canRun() :bool {
10
+ return !$this->getCon()->this_req->request_bypasses_all_restrictions;
 
 
11
  }
12
 
13
  protected function run() {
21
  array_flip( $this->getSelectedProviders() )
22
  ),
23
  function ( $provider ) {
24
+ /** @var BaseHandler $provider - it's actually FQ class string */
25
+ return $provider::IsProviderInstalled();
26
  }
27
  )
28
  );
src/lib/src/Modules/Integrations/Lib/Bots/Common/BaseHandler.php CHANGED
@@ -7,10 +7,10 @@ use FernleafSystems\Wordpress\Services\Services;
7
 
8
  abstract class BaseHandler extends ExecOnceModConsumer {
9
 
 
 
10
  protected function canRun() :bool {
11
- return static::IsProviderInstalled()
12
- && $this->isEnabled()
13
- && ( $this->getCon()->isPremiumActive() || !$this->isProOnly() );
14
  }
15
 
16
  /**
@@ -47,10 +47,24 @@ abstract class BaseHandler extends ExecOnceModConsumer {
47
  }
48
 
49
  protected function isBot() :bool {
50
- return $this->getCon()
51
- ->getModule_IPs()
52
- ->getBotSignalsController()
53
- ->isBot( Services::IP()->getRequestIp() );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  }
55
 
56
  protected function isSpam_Human() :bool {
@@ -58,7 +72,8 @@ abstract class BaseHandler extends ExecOnceModConsumer {
58
  }
59
 
60
  public function isEnabled() :bool {
61
- return in_array( $this->getHandlerSlug(), $this->getHandlerController()->getSelectedProviders() );
 
62
  }
63
 
64
  public static function IsProviderInstalled() :bool {
7
 
8
  abstract class BaseHandler extends ExecOnceModConsumer {
9
 
10
+ private static $isBot = null;
11
+
12
  protected function canRun() :bool {
13
+ return static::IsProviderInstalled();
 
 
14
  }
15
 
16
  /**
47
  }
48
 
49
  protected function isBot() :bool {
50
+ if ( is_null( self::$isBot ) ) {
51
+ self::$isBot = $this->getCon()
52
+ ->getModule_IPs()
53
+ ->getBotSignalsController()
54
+ ->isBot( Services::IP()->getRequestIp() );
55
+ $this->fireBotEvent();
56
+ }
57
+ return self::$isBot;
58
+ }
59
+
60
+ abstract protected function fireBotEvent();
61
+
62
+ protected function isBotBlockEnabled() :bool {
63
+ return $this->isEnabled();
64
+ }
65
+
66
+ public function isBotBlockRequired() :bool {
67
+ return $this->isBot() && $this->isBotBlockEnabled();
68
  }
69
 
70
  protected function isSpam_Human() :bool {
72
  }
73
 
74
  public function isEnabled() :bool {
75
+ return ( $this->getCon()->isPremiumActive() || !$this->isProOnly() )
76
+ && in_array( $this->getHandlerSlug(), $this->getHandlerController()->getSelectedProviders() );
77
  }
78
 
79
  public static function IsProviderInstalled() :bool {
src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/Base.php CHANGED
@@ -13,17 +13,26 @@ abstract class Base extends BaseHandler {
13
  return $mod->getController_SpamForms();
14
  }
15
 
 
 
 
16
  public function isSpam() :bool {
17
- $isSpam = $this->isBot();
 
 
 
18
  $this->getCon()->fireEvent(
19
- sprintf( 'spam_form_%s', $isSpam ? 'fail' : 'pass' ),
20
  [
21
  'audit_params' => [
22
  'form_provider' => $this->getHandlerName(),
23
  ]
24
  ]
25
  );
26
- return $isSpam;
 
 
 
27
  }
28
 
29
  protected function getCommonSpamMessage() :string {
13
  return $mod->getController_SpamForms();
14
  }
15
 
16
+ /**
17
+ * @deprecated 15.0
18
+ */
19
  public function isSpam() :bool {
20
+ return parent::isBot();
21
+ }
22
+
23
+ protected function fireBotEvent() {
24
  $this->getCon()->fireEvent(
25
+ sprintf( 'spam_form_%s', $this->isBot() ? 'fail' : 'pass' ),
26
  [
27
  'audit_params' => [
28
  'form_provider' => $this->getHandlerName(),
29
  ]
30
  ]
31
  );
32
+ }
33
+
34
+ protected function isBotBlockEnabled() :bool {
35
+ return $this->isEnabled();
36
  }
37
 
38
  protected function getCommonSpamMessage() :string {
src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/ContactForm7.php CHANGED
@@ -7,7 +7,7 @@ class ContactForm7 extends Base {
7
  protected function run() {
8
  add_filter( 'wpcf7_spam', function ( $isSpam, $submission ) {
9
 
10
- if ( !$isSpam && $this->isSpam() ) {
11
  $isSpam = true;
12
  add_filter( 'wpcf7_display_message', function ( $msg, $status ) {
13
  if ( $status === 'spam' ) {
7
  protected function run() {
8
  add_filter( 'wpcf7_spam', function ( $isSpam, $submission ) {
9
 
10
+ if ( !$isSpam && $this->isBotBlockRequired() ) {
11
  $isSpam = true;
12
  add_filter( 'wpcf7_display_message', function ( $msg, $status ) {
13
  if ( $status === 'spam' ) {
src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/ElementorPro.php CHANGED
@@ -7,7 +7,7 @@ class ElementorPro extends Base {
7
  protected function run() {
8
  add_action( 'elementor_pro/forms/validation', function ( $form, $ajax_handler ) {
9
  /** @var \ElementorPro\Modules\Forms\Classes\Ajax_Handler $ajax_handler */
10
- if ( empty( $ajax_handler->errors ) && $this->isSpam() ) {
11
  $msg = $this->getCommonSpamMessage();
12
  $ajax_handler->add_error( 'shield-antibot', $msg );
13
  $ajax_handler->add_error_message( $msg );
7
  protected function run() {
8
  add_action( 'elementor_pro/forms/validation', function ( $form, $ajax_handler ) {
9
  /** @var \ElementorPro\Modules\Forms\Classes\Ajax_Handler $ajax_handler */
10
+ if ( empty( $ajax_handler->errors ) && $this->isBotBlockRequired() ) {
11
  $msg = $this->getCommonSpamMessage();
12
  $ajax_handler->add_error( 'shield-antibot', $msg );
13
  $ajax_handler->add_error_message( $msg );
src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/FluentForms.php CHANGED
@@ -11,7 +11,7 @@ class FluentForms extends Base {
11
  protected function run() {
12
  \FluentForm\App::getApplication()->addAction( 'fluentform_before_insert_submission',
13
  function () {
14
- if ( $this->isSpam() ) {
15
  wp_send_json( [
16
  'errors' => $this->getCommonSpamMessage()
17
  ], 422 );
11
  protected function run() {
12
  \FluentForm\App::getApplication()->addAction( 'fluentform_before_insert_submission',
13
  function () {
14
+ if ( $this->isBotBlockRequired() ) {
15
  wp_send_json( [
16
  'errors' => $this->getCommonSpamMessage()
17
  ], 422 );
src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/FormidableForms.php CHANGED
@@ -7,7 +7,7 @@ class FormidableForms extends Base {
7
  protected function run() {
8
  add_filter( 'frm_validate_entry', function ( $errors ) {
9
  if ( !is_array( $errors ) || empty( $errors[ 'spam' ] ) ) {
10
- if ( $this->isSpam() ) {
11
  if ( !is_array( $errors ) ) {
12
  $errors = [];
13
  }
7
  protected function run() {
8
  add_filter( 'frm_validate_entry', function ( $errors ) {
9
  if ( !is_array( $errors ) || empty( $errors[ 'spam' ] ) ) {
10
+ if ( $this->isBotBlockRequired() ) {
11
  if ( !is_array( $errors ) ) {
12
  $errors = [];
13
  }
src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/Forminator.php CHANGED
@@ -6,7 +6,7 @@ class Forminator extends Base {
6
 
7
  protected function run() {
8
  add_filter( 'forminator_spam_protection', function ( $wasSpam ) {
9
- return $wasSpam || $this->isSpam();
10
  }, 1000 );
11
  }
12
 
6
 
7
  protected function run() {
8
  add_filter( 'forminator_spam_protection', function ( $wasSpam ) {
9
+ return $wasSpam || $this->isBotBlockRequired();
10
  }, 1000 );
11
  }
12
 
src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/GravityForms.php CHANGED
@@ -6,7 +6,7 @@ class GravityForms extends Base {
6
 
7
  protected function run() {
8
  add_filter( 'gform_entry_is_spam', function ( $wasSpam ) {
9
- return $wasSpam || $this->isSpam();
10
  }, 1000 );
11
  }
12
 
6
 
7
  protected function run() {
8
  add_filter( 'gform_entry_is_spam', function ( $wasSpam ) {
9
+ return $wasSpam || $this->isBotBlockRequired();
10
  }, 1000 );
11
  }
12
 
src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/Groundhogg.php CHANGED
@@ -6,7 +6,7 @@ class Groundhogg extends Base {
6
 
7
  protected function run() {
8
  add_filter( 'groundhogg/form/submission_handler/is_spam', function ( $wasSpam ) {
9
- return $wasSpam || $this->isSpam();
10
  }, 1000 );
11
  }
12
 
6
 
7
  protected function run() {
8
  add_filter( 'groundhogg/form/submission_handler/is_spam', function ( $wasSpam ) {
9
+ return $wasSpam || $this->isBotBlockRequired();
10
  }, 1000 );
11
  }
12
 
src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/Helpers/NinjaForms_ShieldSpamAction.php CHANGED
@@ -39,7 +39,7 @@ final class NinjaForms_ShieldSpamAction extends \NF_Abstracts_Action {
39
  * @inheritDoc
40
  */
41
  public function process( $action_settings, $form_id, $data ) {
42
- if ( $this->shieldNinjaFormsHandler->isSpam() ) {
43
  $data[ 'errors' ][ 'form' ][ 'spam' ] = esc_html__( 'There was an error trying to send your message. Please try again later', 'ninja-forms' );
44
  }
45
  return $data;
39
  * @inheritDoc
40
  */
41
  public function process( $action_settings, $form_id, $data ) {
42
+ if ( $this->shieldNinjaFormsHandler->isBotBlockRequired() ) {
43
  $data[ 'errors' ][ 'form' ][ 'spam' ] = esc_html__( 'There was an error trying to send your message. Please try again later', 'ninja-forms' );
44
  }
45
  return $data;
src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/KaliForms.php CHANGED
@@ -6,7 +6,7 @@ class KaliForms extends Base {
6
 
7
  protected function run() {
8
  add_filter( 'kaliforms_before_form_process', function ( $data ) {
9
- if ( is_array( $data ) && empty( $data[ 'error_bag' ] ) && $this->isSpam() ) {
10
  $data[ 'admin_stop_execution' ] = true;
11
  $data[ 'admin_stop_reason' ] = $this->getCommonSpamMessage();
12
  $data[ 'error_bag' ] = [
6
 
7
  protected function run() {
8
  add_filter( 'kaliforms_before_form_process', function ( $data ) {
9
+ if ( is_array( $data ) && empty( $data[ 'error_bag' ] ) && $this->isBotBlockRequired() ) {
10
  $data[ 'admin_stop_execution' ] = true;
11
  $data[ 'admin_stop_reason' ] = $this->getCommonSpamMessage();
12
  $data[ 'error_bag' ] = [
src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/SuperForms.php CHANGED
@@ -6,7 +6,7 @@ class SuperForms extends Base {
6
 
7
  protected function run() {
8
  add_action( 'super_before_sending_email_hook', function ( $formSubmissionData ) {
9
- if ( $this->isSpam() ) {
10
  \SUPER_Common::output_message( true, esc_html( $this->getCommonSpamMessage() ) );
11
  }
12
  }, 1000 );
6
 
7
  protected function run() {
8
  add_action( 'super_before_sending_email_hook', function ( $formSubmissionData ) {
9
+ if ( $this->isBotBlockRequired() ) {
10
  \SUPER_Common::output_message( true, esc_html( $this->getCommonSpamMessage() ) );
11
  }
12
  }, 1000 );
src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/SupportCandy.php CHANGED
@@ -8,7 +8,7 @@ class SupportCandy extends Base {
8
 
9
  protected function run() {
10
  add_filter( 'wpsc_before_create_ticket_args', function ( $args ) {
11
- if ( $this->isSpam() ) {
12
  Services::WpGeneral()->wpDie( $this->getCommonSpamMessage() );
13
  }
14
  return $args;
8
 
9
  protected function run() {
10
  add_filter( 'wpsc_before_create_ticket_args', function ( $args ) {
11
+ if ( $this->isBotBlockRequired() ) {
12
  Services::WpGeneral()->wpDie( $this->getCommonSpamMessage() );
13
  }
14
  return $args;
src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/WPForms.php CHANGED
@@ -17,7 +17,7 @@ class WPForms extends Base {
17
 
18
  add_filter( 'wpforms_process_initial_errors', function ( $errors, $formData ) {
19
 
20
- if ( empty( $errors[ $this->workingFormID ] ) && $this->isSpam() ) {
21
  $errors[ $this->workingFormID ] = [
22
  'header' => $this->getCommonSpamMessage(),
23
  ];
17
 
18
  add_filter( 'wpforms_process_initial_errors', function ( $errors, $formData ) {
19
 
20
+ if ( empty( $errors[ $this->workingFormID ] ) && $this->isBotBlockRequired() ) {
21
  $errors[ $this->workingFormID ] = [
22
  'header' => $this->getCommonSpamMessage(),
23
  ];
src/lib/src/Modules/Integrations/Lib/Bots/Spam/Handlers/WpForo.php CHANGED
@@ -11,7 +11,7 @@ class WpForo extends Base {
11
  // It should be an array, but customer reported fatal error with a boolean passed
12
  if ( is_array( $args ) ) {
13
  $status = $args[ 'status' ] ?? null;
14
- if ( $status !== 1 && $this->isSpam() ) {
15
  if ( !empty( WPF()->current_userid ) ) {
16
  WPF()->moderation->ban_for_spam( WPF()->current_userid );
17
  }
11
  // It should be an array, but customer reported fatal error with a boolean passed
12
  if ( is_array( $args ) ) {
13
  $status = $args[ 'status' ] ?? null;
14
+ if ( $status !== 1 && $this->isBotBlockRequired() ) {
15
  if ( !empty( WPF()->current_userid ) ) {
16
  WPF()->moderation->ban_for_spam( WPF()->current_userid );
17
  }
src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/Base.php CHANGED
@@ -18,8 +18,6 @@ abstract class Base extends Integrations\Lib\Bots\Common\BaseHandler {
18
  */
19
  private $auditUser;
20
 
21
- private static $isBot = null;
22
-
23
  protected function run() {
24
  /** @var LoginGuard\Options $opts */
25
  $opts = $this->getCon()->getModule_LoginGuard()->getOptions();
@@ -62,7 +60,6 @@ abstract class Base extends Integrations\Lib\Bots\Common\BaseHandler {
62
  }
63
 
64
  /**
65
- * @param string $action
66
  * @return $this
67
  */
68
  protected function setAuditAction( string $action ) {
@@ -71,7 +68,6 @@ abstract class Base extends Integrations\Lib\Bots\Common\BaseHandler {
71
  }
72
 
73
  /**
74
- * @param string $user
75
  * @return $this
76
  */
77
  protected function setAuditUser( string $user ) {
@@ -79,21 +75,23 @@ abstract class Base extends Integrations\Lib\Bots\Common\BaseHandler {
79
  return $this;
80
  }
81
 
82
- public function checkIsBot() :bool {
83
- if ( is_null( self::$isBot ) ) {
84
- self::$isBot = $this->isBot();
85
- $this->getCon()->fireEvent(
86
- sprintf( 'user_form_bot_%s', self::$isBot ? 'fail' : 'pass' ),
87
- [
88
- 'audit_params' => [
89
- 'form_provider' => $this->getHandlerName(),
90
- 'action' => $this->getAuditAction(),
91
- 'username' => $this->getAuditUser(),
92
- ]
93
  ]
94
- );
95
- }
96
- return self::$isBot;
 
 
 
 
 
97
  }
98
 
99
  protected function getErrorMessage() :string {
18
  */
19
  private $auditUser;
20
 
 
 
21
  protected function run() {
22
  /** @var LoginGuard\Options $opts */
23
  $opts = $this->getCon()->getModule_LoginGuard()->getOptions();
60
  }
61
 
62
  /**
 
63
  * @return $this
64
  */
65
  protected function setAuditAction( string $action ) {
68
  }
69
 
70
  /**
 
71
  * @return $this
72
  */
73
  protected function setAuditUser( string $user ) {
75
  return $this;
76
  }
77
 
78
+ protected function fireBotEvent() {
79
+ $this->getCon()->fireEvent(
80
+ sprintf( 'user_form_bot_%s', $this->isBot() ? 'fail' : 'pass' ),
81
+ [
82
+ 'audit_params' => [
83
+ 'form_provider' => $this->getHandlerName(),
84
+ 'action' => $this->getAuditAction(),
85
+ 'username' => $this->getAuditUser(),
 
 
 
86
  ]
87
+ ]
88
+ );
89
+ }
90
+
91
+ protected function isBotBlockEnabled() :bool {
92
+ /** @var LoginGuard\Options $loginOpts */
93
+ $loginOpts = $this->getCon()->getModule_LoginGuard()->getOptions();
94
+ return $loginOpts->isEnabledAntiBot();
95
  }
96
 
97
  protected function getErrorMessage() :string {
src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/Buddyboss.php CHANGED
@@ -9,7 +9,7 @@ class Buddyboss extends Base {
9
  }
10
 
11
  public function checkRegister_BP() {
12
- if ( $this->setAuditAction( 'register' )->checkIsBot() ) {
13
  $bp = \buddypress();
14
  if ( is_object( $bp->signup ) ) {
15
  $bp->signup->errors[ 'shield-fail-register' ] = 'Failed AntiBot SPAM Check';
9
  }
10
 
11
  public function checkRegister_BP() {
12
+ if ( $this->setAuditAction( 'register' )->isBotBlockRequired() ) {
13
  $bp = \buddypress();
14
  if ( is_object( $bp->signup ) ) {
15
  $bp->signup->errors[ 'shield-fail-register' ] = 'Failed AntiBot SPAM Check';
src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/Buddypress.php CHANGED
@@ -9,7 +9,7 @@ class Buddypress extends Base {
9
  }
10
 
11
  public function checkRegister_BP() {
12
- if ( $this->setAuditAction( 'register' )->checkIsBot() ) {
13
  wp_die( $this->getErrorMessage() );
14
  }
15
  }
9
  }
10
 
11
  public function checkRegister_BP() {
12
+ if ( $this->setAuditAction( 'register' )->isBotBlockRequired() ) {
13
  wp_die( $this->getErrorMessage() );
14
  }
15
  }
src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/EasyDigitalDownloads.php CHANGED
@@ -9,7 +9,7 @@ class EasyDigitalDownloads extends Base {
9
  }
10
 
11
  public function checkRegister_EDD() {
12
- if ( $this->setAuditAction( 'register' )->checkIsBot() ) {
13
  edd_set_error( $this->getCon()->prefix( rand() ), $this->getErrorMessage() );
14
  }
15
  }
9
  }
10
 
11
  public function checkRegister_EDD() {
12
+ if ( $this->setAuditAction( 'register' )->isBotBlockRequired() ) {
13
  edd_set_error( $this->getCon()->prefix( rand() ), $this->getErrorMessage() );
14
  }
15
  }
src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/LearnPress.php CHANGED
@@ -17,7 +17,7 @@ class LearnPress extends Base {
17
  * @return string|\WP_Error
18
  */
19
  public function checkLogin_LP( $maybeError ) {
20
- if ( !is_wp_error( $maybeError ) && $this->setAuditAction( 'login' )->checkIsBot() ) {
21
  $maybeError = new \WP_Error( 'shield-fail-login', $this->getErrorMessage() );
22
  }
23
  return $maybeError;
@@ -28,7 +28,7 @@ class LearnPress extends Base {
28
  * @return string|\WP_Error
29
  */
30
  public function checkRegister_LP( $maybeError ) {
31
- if ( !is_wp_error( $maybeError ) && $this->setAuditAction( 'register' )->checkIsBot() ) {
32
  $maybeError = new \WP_Error( 'shield-fail-register', $this->getErrorMessage() );
33
  }
34
  return $maybeError;
17
  * @return string|\WP_Error
18
  */
19
  public function checkLogin_LP( $maybeError ) {
20
+ if ( !is_wp_error( $maybeError ) && $this->setAuditAction( 'login' )->isBotBlockRequired() ) {
21
  $maybeError = new \WP_Error( 'shield-fail-login', $this->getErrorMessage() );
22
  }
23
  return $maybeError;
28
  * @return string|\WP_Error
29
  */
30
  public function checkRegister_LP( $maybeError ) {
31
+ if ( !is_wp_error( $maybeError ) && $this->setAuditAction( 'register' )->isBotBlockRequired() ) {
32
  $maybeError = new \WP_Error( 'shield-fail-register', $this->getErrorMessage() );
33
  }
34
  return $maybeError;
src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/LifterLMS.php CHANGED
@@ -20,7 +20,7 @@ class LifterLMS extends Base {
20
  * @return bool|\WP_Error
21
  */
22
  public function checkLogin_LLMS( $valid ) {
23
- if ( !is_wp_error( $valid ) && $this->setAuditAction( 'login' )->checkIsBot() ) {
24
  $valid = new \WP_Error( 'shield-fail-login', $this->getErrorMessage() );
25
  }
26
  return $valid;
@@ -31,7 +31,7 @@ class LifterLMS extends Base {
31
  * @return bool|\WP_Error
32
  */
33
  public function checkRegister_LLMS( $valid ) {
34
- if ( !is_wp_error( $valid ) && $this->setAuditAction( 'register' )->checkIsBot() ) {
35
  $valid = new \WP_Error( 'shield-fail-register', $this->getErrorMessage() );
36
  }
37
  return $valid;
20
  * @return bool|\WP_Error
21
  */
22
  public function checkLogin_LLMS( $valid ) {
23
+ if ( !is_wp_error( $valid ) && $this->setAuditAction( 'login' )->isBotBlockRequired() ) {
24
  $valid = new \WP_Error( 'shield-fail-login', $this->getErrorMessage() );
25
  }
26
  return $valid;
31
  * @return bool|\WP_Error
32
  */
33
  public function checkRegister_LLMS( $valid ) {
34
+ if ( !is_wp_error( $valid ) && $this->setAuditAction( 'register' )->isBotBlockRequired() ) {
35
  $valid = new \WP_Error( 'shield-fail-register', $this->getErrorMessage() );
36
  }
37
  return $valid;
src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/MemberPress.php CHANGED
@@ -24,7 +24,7 @@ class MemberPress extends Base {
24
  * @return array
25
  */
26
  public function checkLogin_MP( $errors ) {
27
- if ( empty( $errors ) && $this->setAuditAction( 'login' )->checkIsBot() ) {
28
  $errors = [
29
  $this->getErrorMessage()
30
  ];
@@ -37,7 +37,7 @@ class MemberPress extends Base {
37
  * @return array
38
  */
39
  public function checkLostPassword_MP( $errors ) {
40
- if ( empty( $errors ) && $this->setAuditAction( 'lostpassword' )->checkIsBot() ) {
41
  $errors = [
42
  $this->getErrorMessage()
43
  ];
@@ -50,7 +50,7 @@ class MemberPress extends Base {
50
  * @return string[]
51
  */
52
  public function checkRegister_MP( $errors ) {
53
- if ( empty( $errors ) && $this->setAuditAction( 'register' )->checkIsBot() ) {
54
  $errors = [
55
  $this->getErrorMessage()
56
  ];
24
  * @return array
25
  */
26
  public function checkLogin_MP( $errors ) {
27
+ if ( empty( $errors ) && $this->setAuditAction( 'login' )->isBotBlockRequired() ) {
28
  $errors = [
29
  $this->getErrorMessage()
30
  ];
37
  * @return array
38
  */
39
  public function checkLostPassword_MP( $errors ) {
40
+ if ( empty( $errors ) && $this->setAuditAction( 'lostpassword' )->isBotBlockRequired() ) {
41
  $errors = [
42
  $this->getErrorMessage()
43
  ];
50
  * @return string[]
51
  */
52
  public function checkRegister_MP( $errors ) {
53
+ if ( empty( $errors ) && $this->setAuditAction( 'register' )->isBotBlockRequired() ) {
54
  $errors = [
55
  $this->getErrorMessage()
56
  ];
src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/PaidMemberSubscriptions.php CHANGED
@@ -9,7 +9,7 @@ class PaidMemberSubscriptions extends Base {
9
  }
10
 
11
  public function checkRegister_PMS() {
12
- if ( $this->setAuditAction( 'register' )->checkIsBot() ) {
13
  \pms_errors()->add( 'shield-fail-register', $this->getErrorMessage() );
14
  }
15
  }
9
  }
10
 
11
  public function checkRegister_PMS() {
12
+ if ( $this->setAuditAction( 'register' )->isBotBlockRequired() ) {
13
  \pms_errors()->add( 'shield-fail-register', $this->getErrorMessage() );
14
  }
15
  }
src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/ProfileBuilder.php CHANGED
@@ -16,7 +16,7 @@ class ProfileBuilder extends Base {
16
  * @return array
17
  */
18
  public function checkRegister_PB( $errors ) {
19
- if ( empty( $errors ) && $this->setAuditAction( 'register' )->checkIsBot() ) {
20
  $errors[ 'shield-fail-register' ] = sprintf( '<span class="wppb-form-error">%s</span>', $this->getErrorMessage() );
21
  }
22
  return $errors;
16
  * @return array
17
  */
18
  public function checkRegister_PB( $errors ) {
19
+ if ( empty( $errors ) && $this->setAuditAction( 'register' )->isBotBlockRequired() ) {
20
  $errors[ 'shield-fail-register' ] = sprintf( '<span class="wppb-form-error">%s</span>', $this->getErrorMessage() );
21
  }
22
  return $errors;
src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/UltimateMember.php CHANGED
@@ -20,19 +20,19 @@ class UltimateMember extends Base {
20
  }
21
 
22
  public function checkLogin_UM() {
23
- if ( $this->setAuditAction( 'login' )->checkIsBot() ) {
24
  \UM()->form()->add_error( 'shield-fail-login', $this->getErrorMessage() );
25
  }
26
  }
27
 
28
  public function checkLostPassword_UM() {
29
- if ( $this->setAuditAction( 'lostpassword' )->checkIsBot() ) {
30
  \UM()->form()->add_error( 'shield-fail-lostpassword', $this->getErrorMessage() );
31
  }
32
  }
33
 
34
  public function checkRegister_UM() {
35
- if ( $this->setAuditAction( 'register' )->checkIsBot() ) {
36
  \UM()->form()->add_error( 'shield-fail-register', $this->getErrorMessage() );
37
  }
38
  }
20
  }
21
 
22
  public function checkLogin_UM() {
23
+ if ( $this->setAuditAction( 'login' )->isBotBlockRequired() ) {
24
  \UM()->form()->add_error( 'shield-fail-login', $this->getErrorMessage() );
25
  }
26
  }
27
 
28
  public function checkLostPassword_UM() {
29
+ if ( $this->setAuditAction( 'lostpassword' )->isBotBlockRequired() ) {
30
  \UM()->form()->add_error( 'shield-fail-lostpassword', $this->getErrorMessage() );
31
  }
32
  }
33
 
34
  public function checkRegister_UM() {
35
+ if ( $this->setAuditAction( 'register' )->isBotBlockRequired() ) {
36
  \UM()->form()->add_error( 'shield-fail-register', $this->getErrorMessage() );
37
  }
38
  }
src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/WPMembers.php CHANGED
@@ -22,7 +22,7 @@ class WPMembers extends Base {
22
  * @return array
23
  */
24
  public function checkLostPassword_WM( array $args ) {
25
- if ( $this->setAuditAction( 'lostpassword' )->checkIsBot() ) {
26
  $args[ 'user' ] = null;
27
  $args[ 'email' ] = null;
28
  }
@@ -33,7 +33,7 @@ class WPMembers extends Base {
33
  * Offers no direct validation filter, so we jump in right before the DB insert.
34
  */
35
  public function checkRegister_WM() {
36
- if ( $this->setAuditAction( 'register' )->checkIsBot() ) {
37
  global $wpmem_themsg;
38
  $wpmem_themsg = $this->getErrorMessage();
39
  }
22
  * @return array
23
  */
24
  public function checkLostPassword_WM( array $args ) {
25
+ if ( $this->setAuditAction( 'lostpassword' )->isBotBlockRequired() ) {
26
  $args[ 'user' ] = null;
27
  $args[ 'email' ] = null;
28
  }
33
  * Offers no direct validation filter, so we jump in right before the DB insert.
34
  */
35
  public function checkRegister_WM() {
36
+ if ( $this->setAuditAction( 'register' )->isBotBlockRequired() ) {
37
  global $wpmem_themsg;
38
  $wpmem_themsg = $this->getErrorMessage();
39
  }
src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/WooCommerce.php CHANGED
@@ -21,7 +21,7 @@ class WooCommerce extends Base {
21
  * @param \WP_Error $wpError
22
  */
23
  public function checkCheckout_Woo( $data, $wpError ) {
24
- if ( empty( $wpError->get_error_code() ) && $this->setAuditAction( 'checkout' )->checkIsBot() ) {
25
  $wpError->add( 'shield-user-checkout', $this->getErrorMessage() );
26
  }
27
  }
@@ -32,7 +32,7 @@ class WooCommerce extends Base {
32
  * @return \WP_User|\WP_Error
33
  */
34
  public function checkLogin_Woo( $wpError, $username ) {
35
- if ( empty( $wpError->get_error_code() ) && $this->setAuditAction( 'login' )->checkIsBot() ) {
36
  $this->setAuditUser( $username );
37
  $wpError->add( 'shield-user-login', $this->getErrorMessage() );
38
  }
@@ -45,7 +45,7 @@ class WooCommerce extends Base {
45
  * @return \WP_Error
46
  */
47
  public function checkRegister_Woo( $wpError, $username ) {
48
- if ( empty( $wpError->get_error_code() ) && $this->setAuditAction( 'register' )->checkIsBot() ) {
49
  $this->setAuditUser( $username );
50
  $wpError->add( 'shield-user-register', $this->getErrorMessage() );
51
  }
21
  * @param \WP_Error $wpError
22
  */
23
  public function checkCheckout_Woo( $data, $wpError ) {
24
+ if ( empty( $wpError->get_error_code() ) && $this->setAuditAction( 'checkout' )->isBotBlockRequired() ) {
25
  $wpError->add( 'shield-user-checkout', $this->getErrorMessage() );
26
  }
27
  }
32
  * @return \WP_User|\WP_Error
33
  */
34
  public function checkLogin_Woo( $wpError, $username ) {
35
+ if ( empty( $wpError->get_error_code() ) && $this->setAuditAction( 'login' )->isBotBlockRequired() ) {
36
  $this->setAuditUser( $username );
37
  $wpError->add( 'shield-user-login', $this->getErrorMessage() );
38
  }
45
  * @return \WP_Error
46
  */
47
  public function checkRegister_Woo( $wpError, $username ) {
48
+ if ( empty( $wpError->get_error_code() ) && $this->setAuditAction( 'register' )->isBotBlockRequired() ) {
49
  $this->setAuditUser( $username );
50
  $wpError->add( 'shield-user-register', $this->getErrorMessage() );
51
  }
src/lib/src/Modules/Integrations/Lib/Bots/UserForms/Handlers/WordPress.php CHANGED
@@ -30,7 +30,7 @@ class WordPress extends Base {
30
  if ( !is_wp_error( $userOrError ) || empty( $userOrError->get_error_codes() ) ) {
31
  $this->setAuditAction( 'login' )
32
  ->setAuditUser( $username );
33
- if ( $this->checkIsBot() ) {
34
  $userOrError = new \WP_Error( 'shield-fail-login', $this->getErrorMessage() );
35
  remove_filter( 'authenticate', 'wp_authenticate_username_password', 20 ); // wp-includes/user.php
36
  remove_filter( 'authenticate', 'wp_authenticate_email_password', 20 ); // wp-includes/user.php
@@ -52,7 +52,7 @@ class WordPress extends Base {
52
  else {
53
  $this->setAuditUser( sanitize_user( Services::Request()->post( 'user_login', '' ) ) );
54
  }
55
- if ( $this->checkIsBot() ) {
56
  $wpError->add( 'shield-fail-lostpassword', $this->getErrorMessage() );
57
  }
58
  }
@@ -67,7 +67,7 @@ class WordPress extends Base {
67
  if ( !is_wp_error( $wpError ) || empty( $wpError->get_error_codes() ) ) {
68
  $this->setAuditAction( 'register' )
69
  ->setAuditUser( $username );
70
- if ( $this->checkIsBot() ) {
71
  $wpError = new \WP_Error( 'shield-fail-login', $this->getErrorMessage() );
72
  }
73
  }
30
  if ( !is_wp_error( $userOrError ) || empty( $userOrError->get_error_codes() ) ) {
31
  $this->setAuditAction( 'login' )
32
  ->setAuditUser( $username );
33
+ if ( $this->isBotBlockRequired() ) {
34
  $userOrError = new \WP_Error( 'shield-fail-login', $this->getErrorMessage() );
35
  remove_filter( 'authenticate', 'wp_authenticate_username_password', 20 ); // wp-includes/user.php
36
  remove_filter( 'authenticate', 'wp_authenticate_email_password', 20 ); // wp-includes/user.php
52
  else {
53
  $this->setAuditUser( sanitize_user( Services::Request()->post( 'user_login', '' ) ) );
54
  }
55
+ if ( $this->isBotBlockRequired() ) {
56
  $wpError->add( 'shield-fail-lostpassword', $this->getErrorMessage() );
57
  }
58
  }
67
  if ( !is_wp_error( $wpError ) || empty( $wpError->get_error_codes() ) ) {
68
  $this->setAuditAction( 'register' )
69
  ->setAuditUser( $username );
70
+ if ( $this->isBotBlockRequired() ) {
71
  $wpError = new \WP_Error( 'shield-fail-login', $this->getErrorMessage() );
72
  }
73
  }
src/lib/src/Modules/Integrations/Lib/Bots/UserForms/UserFormsController.php CHANGED
@@ -3,17 +3,14 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Bots\UserForms;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Options;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class UserFormsController extends Integrations\Lib\Bots\Common\BaseBotDetectionController {
10
 
11
  protected function canRun() :bool {
12
- /** @var Options $loginOpts */
13
- $loginOpts = $this->getCon()->getModule_LoginGuard()->getOptions();
14
- return parent::canRun() && Services::Request()->isPost()
15
- && !Services::WpUsers()->isUserLoggedIn()
16
- && $loginOpts->isEnabledAntiBot();
17
  }
18
 
19
  public function getSelectedProvidersOptKey() :string {
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations\Lib\Bots\UserForms;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Integrations;
 
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  class UserFormsController extends Integrations\Lib\Bots\Common\BaseBotDetectionController {
9
 
10
  protected function canRun() :bool {
11
+ return parent::canRun()
12
+ && Services::Request()->isPost()
13
+ && !Services::WpUsers()->isUserLoggedIn();
 
 
14
  }
15
 
16
  public function getSelectedProvidersOptKey() :string {
src/lib/src/Modules/Integrations/Processor.php CHANGED
@@ -12,7 +12,7 @@ class Processor extends BaseShield\Processor {
12
  $mod = $this->getMod();
13
  $mod->getControllerMWP()->execute();
14
 
15
- if ( !empty( Services::IP()->getRequestIp() ) ) {
16
  $mod->getController_SpamForms()->execute();
17
 
18
  add_action( 'init', function () use ( $mod ) {
12
  $mod = $this->getMod();
13
  $mod->getControllerMWP()->execute();
14
 
15
+ if ( !$this->getCon()->this_req->request_bypasses_all_restrictions ) {
16
  $mod->getController_SpamForms()->execute();
17
 
18
  add_action( 'init', function () use ( $mod ) {
src/lib/src/Modules/License/AjaxHandler.php CHANGED
@@ -20,7 +20,6 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
20
  }
21
 
22
  public function ajaxExec_ConnectionDebug() :array {
23
-
24
  $success = ( new Keyless\Ping() )->ping();
25
  $host = wp_parse_url( Keyless\Base::DEFAULT_URL_STUB, PHP_URL_HOST );
26
 
@@ -68,7 +67,7 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
68
  }
69
  else {
70
  try {
71
- $success = $licHandler->verify( true )->hasValidWorkingLicense();
72
  $msg = $success ? __( 'Valid license found.', 'wp-simple-firewall' ) : __( "Valid license couldn't be found.", 'wp-simple-firewall' );
73
  }
74
  catch ( \Exception $e ) {
20
  }
21
 
22
  public function ajaxExec_ConnectionDebug() :array {
 
23
  $success = ( new Keyless\Ping() )->ping();
24
  $host = wp_parse_url( Keyless\Base::DEFAULT_URL_STUB, PHP_URL_HOST );
25
 
67
  }
68
  else {
69
  try {
70
+ $success = $licHandler->verify()->hasValidWorkingLicense();
71
  $msg = $success ? __( 'Valid license found.', 'wp-simple-firewall' ) : __( "Valid license couldn't be found.", 'wp-simple-firewall' );
72
  }
73
  catch ( \Exception $e ) {
src/lib/src/Modules/License/ModCon.php CHANGED
@@ -4,7 +4,6 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\Lib\UpgradeReqLogsTable;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
10
  class ModCon extends BaseShield\ModCon {
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
 
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class ModCon extends BaseShield\ModCon {
src/lib/src/Modules/License/UI.php CHANGED
@@ -1,16 +1,24 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
 
 
 
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
8
  class UI extends BaseShield\UI {
9
 
10
- /**
11
- * @return array
12
- */
13
- public function buildInsightsVars() {
 
 
 
 
 
14
  $con = $this->getCon();
15
  /** @var ModCon $mod */
16
  $mod = $this->getMod();
@@ -48,20 +56,18 @@ class UI extends BaseShield\UI {
48
  );
49
  }
50
 
 
 
51
  return [
52
  'vars' => [
53
  'license_table' => [
54
- 'product_name' => $lic->is_central ?
55
- $opts->getDef( 'license_item_name_sc' ) :
56
- $opts->getDef( 'license_item_name' ),
57
- 'license_active' => $mod->getLicenseHandler()->hasValidWorkingLicense() ?
58
- __( '&#10004;', 'wp-simple-firewall' ) : __( '&#10006;', 'wp-simple-firewall' ),
59
  'license_expires' => $expiresAtHuman,
60
  'license_email' => $lic->customer_email,
61
  'last_checked' => $checked,
62
  'last_errors' => $mod->hasLastErrors() ? $mod->getLastErrors( true ) : '',
63
- 'wphashes_token' => $mod->getWpHashesTokenManager()->hasToken() ?
64
- __( '&#10004;', 'wp-simple-firewall' ) : __( '&#10006;', 'wp-simple-firewall' ),
65
  'installation_id' => $con->getSiteInstallationId(),
66
  ],
67
  'activation_url' => $WP->getHomeUrl(),
@@ -77,11 +83,16 @@ class UI extends BaseShield\UI {
77
  'license_action' => $mod->getAjaxActionData( 'license_action' ),
78
  'connection_debug' => $mod->getAjaxActionData( 'connection_debug' )
79
  ],
80
- 'aHrefs' => [
81
  'shield_pro_url' => 'https://shsec.io/shieldpro',
82
  'iframe_url' => $opts->getDef( 'landing_page_url' ),
83
  'keyless_cp' => $opts->getDef( 'keyless_cp' ),
84
  ],
 
 
 
 
 
85
  'flags' => [
86
  'show_ads' => false,
87
  'button_enabled_check' => true,
@@ -90,13 +101,107 @@ class UI extends BaseShield\UI {
90
  'is_pro' => $con->isPremiumActive(),
91
  'has_error' => $mod->hasLastErrors()
92
  ],
93
- 'strings' => $mod->getStrings()->getDisplayStrings(),
94
  ];
95
  }
96
 
97
- public function isEnabledForUiSummary() :bool {
98
- /** @var ModCon $mod */
99
- $mod = $this->getMod();
100
- return $mod->getLicenseHandler()->hasValidWorkingLicense();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  }
102
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\License;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\{
6
+ BaseShield,
7
+ Integrations
8
+ };
9
  use FernleafSystems\Wordpress\Services\Services;
10
 
11
  class UI extends BaseShield\UI {
12
 
13
+ public function renderLicensePage() :string {
14
+ return $this->getMod()
15
+ ->getRenderer()
16
+ ->setTemplate( '/wpadmin_pages/insights/license/license.twig' )
17
+ ->setRenderData( $this->buildLicensingPageData() )
18
+ ->render();
19
+ }
20
+
21
+ private function buildLicensingPageData() :array {
22
  $con = $this->getCon();
23
  /** @var ModCon $mod */
24
  $mod = $this->getMod();
56
  );
57
  }
58
 
59
+ $strings = $mod->getStrings()->getDisplayStrings();
60
+ $strings[ 'pro_features' ] = $this->getProFeatureStrings();
61
  return [
62
  'vars' => [
63
  'license_table' => [
64
+ 'product_name' => $opts->getDef( $lic->is_central ? 'license_item_name_sc' : 'license_item_name' ),
65
+ 'license_active' => $mod->getLicenseHandler()->hasValidWorkingLicense() ? '&#10004;' : '&#10006;',
 
 
 
66
  'license_expires' => $expiresAtHuman,
67
  'license_email' => $lic->customer_email,
68
  'last_checked' => $checked,
69
  'last_errors' => $mod->hasLastErrors() ? $mod->getLastErrors( true ) : '',
70
+ 'wphashes_token' => $mod->getWpHashesTokenManager()->hasToken() ? '&#10004;' : '&#10006;',
 
71
  'installation_id' => $con->getSiteInstallationId(),
72
  ],
73
  'activation_url' => $WP->getHomeUrl(),
83
  'license_action' => $mod->getAjaxActionData( 'license_action' ),
84
  'connection_debug' => $mod->getAjaxActionData( 'connection_debug' )
85
  ],
86
+ 'hrefs' => [
87
  'shield_pro_url' => 'https://shsec.io/shieldpro',
88
  'iframe_url' => $opts->getDef( 'landing_page_url' ),
89
  'keyless_cp' => $opts->getDef( 'keyless_cp' ),
90
  ],
91
+ 'imgs' => [
92
+ 'svgs' => [
93
+ 'thumbs_up' => $con->svgs->raw( 'bootstrap/hand-thumbs-up.svg' )
94
+ ],
95
+ ],
96
  'flags' => [
97
  'show_ads' => false,
98
  'button_enabled_check' => true,
101
  'is_pro' => $con->isPremiumActive(),
102
  'has_error' => $mod->hasLastErrors()
103
  ],
104
+ 'strings' => $strings,
105
  ];
106
  }
107
 
108
+ private function getProFeatureStrings() :array {
109
+ return [
110
+ [
111
+ 'title' => sprintf( __( 'Protect your %s files', 'wp-simple-firewall' ), '<code>wp-config.php, .htaccess</code>' ),
112
+ 'lines' => [
113
+ sprintf( __( 'The only WordPress security plugin to add monitoring of your %s files with automatic rollback and recovery.', 'wp-simple-firewall' ), '<code>wp-config.php</code>' ),
114
+ ],
115
+ ],
116
+ [
117
+ 'title' => __( 'Support for WooCommerce, Contact Form 7, Elementor PRO, Ninja Form & more', 'wp-simple-firewall' ),
118
+ 'lines' => [
119
+ __( 'Provide tighter security for your WooCommerce customers and protect against Contact Form SPAM.', 'wp-simple-firewall' ),
120
+ __( 'Includes protection for: ', 'wp-simple-firewall' ).implode( ', ', $this->getAllIntegrationNames() )
121
+ ],
122
+ ],
123
+ [
124
+ 'title' => sprintf( '%s: %s', __( 'Malware Scanner', 'wp-simple-firewall' ), __( 'Auto-learning and Detects Never-Before-Seen Malware', 'wp-simple-firewall' ) ),
125
+ 'lines' => [
126
+ __( 'Detects common and uncommon malware patterns in PHP files and alerts you immediately.', 'wp-simple-firewall' ),
127
+ __( 'With ShieldNET crowd-sourcing intelligence, Shield automatically hides false-positives so you can focus on risks that matter, and can ignore the noise that wastes your time.', 'wp-simple-firewall' ),
128
+ ],
129
+ ],
130
+ [
131
+ 'title' => __( 'Plugin and Theme Vulnerability Scanner', 'wp-simple-firewall' ),
132
+ 'lines' => [
133
+ __( 'Alerts to plugin/theme vulnerabilities. Shield can then automatically upgrade as updates become available.', 'wp-simple-firewall' ),
134
+ ],
135
+ ],
136
+ [
137
+ 'title' => __( 'Catch Plugin & Theme Hacks Immediately', 'wp-simple-firewall' ),
138
+ 'lines' => [
139
+ __( 'Be alerted to ANY unauthorized changes to plugins/themes.', 'wp-simple-firewall' ),
140
+ ],
141
+ ],
142
+ [
143
+ 'title' => __( 'Traffic Rate Limiting', 'wp-simple-firewall' ),
144
+ 'lines' => [
145
+ __( 'Prevent abuse of your web hosting resources by detecting and blocking bots that send too many requests to your site.', 'wp-simple-firewall' ),
146
+ ],
147
+ ],
148
+ [
149
+ 'title' => sprintf( '%s: %s', __( 'Intelligence From The Collective', 'wp-simple-firewall' ), 'ShieldNET' ),
150
+ 'lines' => [
151
+ __( 'Take advantage of the intelligence gathered throughout the entire Shield network to better protect your WordPress sites', 'wp-simple-firewall' ),
152
+ ],
153
+ ],
154
+ [
155
+ 'title' => __( 'Easiest, Frustration-Free WP Pro-Upgrade Anywhere', 'wp-simple-firewall' ),
156
+ 'lines' => [
157
+ __( 'No more license keys to remember/copy-paste! Simply activate your site URL in your ShieldPRO control panel and get Pro features enabled on your site automatically.', 'wp-simple-firewall' ),
158
+ ],
159
+ ],
160
+ [
161
+ 'title' => __( 'MainWP Integration', 'wp-simple-firewall' ).' ('.__( 'No extra extension plugins required', 'wp-simple-firewall' ).')',
162
+ 'lines' => [
163
+ __( 'Use MainWP to manage and monitor the security of all your WordPress sites.', 'wp-simple-firewall' ),
164
+ ],
165
+ ],
166
+ [
167
+ 'title' => __( 'Powerful User Password Policies', 'wp-simple-firewall' ),
168
+ 'lines' => [
169
+ __( 'Ensures that all users maintain strong passwords.', 'wp-simple-firewall' ),
170
+ ],
171
+ ],
172
+ [
173
+ 'title' => __( 'Exclusive Customer Support', 'wp-simple-firewall' ),
174
+ 'lines' => [
175
+ __( 'Technical support for Shield is exclusive to Pro customers.', 'wp-simple-firewall' ),
176
+ ],
177
+ ],
178
+ [
179
+ 'title' => __( 'Unlimited Audit Trail', 'wp-simple-firewall' ),
180
+ 'lines' => [
181
+ __( 'Retain logs for as long as you need without limits.', 'wp-simple-firewall' ),
182
+ ],
183
+ ],
184
+ [
185
+ 'title' => __( 'White Label', 'wp-simple-firewall' ),
186
+ 'lines' => [
187
+ __( 'Re-Brand Shield Security as your own!', 'wp-simple-firewall' ),
188
+ ],
189
+ ],
190
+ ];
191
+ }
192
+
193
+ private function getAllIntegrationNames() :array {
194
+ $modIntegrations = $this->getCon()->getModule_Integrations();
195
+ return array_map(
196
+ function ( $providerClass ) use ( $modIntegrations ) {
197
+ /** @var Integrations\Lib\Bots\Common\BaseHandler $provider */
198
+ $provider = ( new $providerClass() )->setMod( $modIntegrations );
199
+ return $provider->getHandlerName();
200
+ },
201
+ array_merge(
202
+ $modIntegrations->getController_UserForms()->enumProviders(),
203
+ $modIntegrations->getController_SpamForms()->enumProviders()
204
+ )
205
+ );
206
  }
207
  }
src/lib/src/Modules/Lockdown/Insights/OverviewCards.php DELETED
@@ -1,74 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Lockdown\Insights;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules;
6
-
7
- class OverviewCards extends Modules\Base\Insights\OverviewCards {
8
-
9
- protected function buildModCards() :array {
10
- /** @var Modules\Lockdown\ModCon $mod */
11
- $mod = $this->getMod();
12
- /** @var Modules\Lockdown\Options $opts */
13
- $opts = $this->getOptions();
14
-
15
- $cards = [];
16
-
17
- if ( $mod->isModOptEnabled() ) {
18
- $bUserCanEdit = current_user_can( 'edit_plugins' );
19
-
20
- if ( !$bUserCanEdit ) {
21
- $cards[ 'editing' ] = [
22
- 'name' => __( 'File Editing via WP', 'wp-simple-firewall' ),
23
- 'state' => 1,
24
- 'summary' => __( 'File editing from within WordPress admin is disabled', 'wp-simple-firewall' ),
25
- 'href' => $mod->getUrl_DirectLinkToOption( 'disable_file_editing' ),
26
- ];
27
- }
28
- else {
29
- $bEditOptSet = $opts->isOptFileEditingDisabled();
30
- $cards[ 'editing' ] = [
31
- 'name' => __( 'File Editing via WP', 'wp-simple-firewall' ),
32
- 'state' => $bEditOptSet ? -2 : -1,
33
- 'summary' => $bEditOptSet ?
34
- __( "File editing is allowed even though you've switched it off", 'wp-simple-firewall' )
35
- : __( "File editing from within the WP admin should be disabled", 'wp-simple-firewall' ),
36
- 'href' => $mod->getUrl_DirectLinkToOption( 'disable_file_editing' ),
37
- 'help' => $bEditOptSet ?
38
- __( 'Another plugin or theme is interfering with this setting.', 'wp-simple-firewall' )
39
- : __( 'WP Plugin file editing should be disabled wherever possible.', 'wp-simple-firewall' )
40
- ];
41
- }
42
-
43
- $bXml = $opts->isXmlrpcDisabled();
44
- $cards[ 'xml' ] = [
45
- 'name' => __( 'XML-RPC', 'wp-simple-firewall' ),
46
- 'state' => $bXml ? 1 : -1,
47
- 'summary' => $bXml ?
48
- __( 'XML-RPC is disabled', 'wp-simple-firewall' )
49
- : __( "XML-RPC access is allowed", 'wp-simple-firewall' ),
50
- 'href' => $mod->getUrl_DirectLinkToOption( 'disable_xmlrpc' ),
51
- ];
52
-
53
- $bApi = $opts->isRestApiAnonymousAccessDisabled();
54
- $cards[ 'api' ] = [
55
- 'name' => __( 'REST API', 'wp-simple-firewall' ),
56
- 'state' => $bApi ? 1 : -1,
57
- 'summary' => $bApi ?
58
- __( 'Anonymous REST API is disabled', 'wp-simple-firewall' )
59
- : __( "Anonymous REST API is allowed", 'wp-simple-firewall' ),
60
- 'href' => $mod->getUrl_DirectLinkToOption( 'disable_anonymous_restapi' ),
61
- ];
62
- }
63
-
64
- return $cards;
65
- }
66
-
67
- protected function getSectionTitle() :string {
68
- return __( 'WordPress Lockdown', 'wp-simple-firewall' );
69
- }
70
-
71
- protected function getSectionSubTitle() :string {
72
- return __( 'Restrict WP Functionality e.g. XMLRPC & REST API', 'wp-simple-firewall' );
73
- }
74
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/Lockdown/ModCon.php CHANGED
@@ -6,6 +6,18 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
 
7
  class ModCon extends BaseShield\ModCon {
8
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  /**
10
  * @param string $namespace
11
  * @return bool
@@ -25,7 +37,7 @@ class ModCon extends BaseShield\ModCon {
25
  $opts = $this->getOptions();
26
  $opts->setOpt(
27
  'api_namespace_exclusions',
28
- $this->cleanStringArray( $opts->getRestApiAnonymousExclusions(), '#[^a-z0-9_-]#i' )
29
  );
30
  }
31
  }
6
 
7
  class ModCon extends BaseShield\ModCon {
8
 
9
+ protected function enumRuleBuilders() :array {
10
+ /** @var Options $opts */
11
+ $opts = $this->getOptions();
12
+ return [
13
+ $opts->isXmlrpcDisabled() ? Rules\Build\DisableXmlrpc::class : null,
14
+ $opts->isOptFileEditingDisabled() ? Rules\Build\DisableFileEditing::class : null,
15
+ $opts->isBlockAuthorDiscovery() ? Rules\Build\IsRequestAuthorDiscovery::class : null,
16
+ $opts->isOpt( 'hide_wordpress_generator_tag', 'Y' ) ? Rules\Build\HideGeneratorTag::class : null,
17
+ ( $opts->isOpt( 'force_ssl_admin', 'Y' ) && function_exists( 'force_ssl_admin' ) ) ? Rules\Build\ForceSslAdmin::class : null,
18
+ ];
19
+ }
20
+
21
  /**
22
  * @param string $namespace
23
  * @return bool
37
  $opts = $this->getOptions();
38
  $opts->setOpt(
39
  'api_namespace_exclusions',
40
+ $this->cleanStringArray( $opts->getRestApiAnonymousExclusions(), '#[^\da-z_-]#i' )
41
  );
42
  }
43
  }
src/lib/src/Modules/Lockdown/Options.php CHANGED
@@ -11,13 +11,17 @@ class Options extends BaseShield\Options {
11
  */
12
  public function getRestApiAnonymousExclusions() :array {
13
  $exc = apply_filters( 'shield/anonymous_rest_api_exclusions', $this->getOpt( 'api_namespace_exclusions' ) );
14
- return $this->getMod()->cleanStringArray( $exc, '#[^a-z0-9_-]#i' );
15
  }
16
 
17
  public function isOptFileEditingDisabled() :bool {
18
  return $this->isOpt( 'disable_file_editing', 'Y' );
19
  }
20
 
 
 
 
 
21
  public function isRestApiAnonymousAccessDisabled() :bool {
22
  return $this->isOpt( 'disable_anonymous_restapi', 'Y' );
23
  }
11
  */
12
  public function getRestApiAnonymousExclusions() :array {
13
  $exc = apply_filters( 'shield/anonymous_rest_api_exclusions', $this->getOpt( 'api_namespace_exclusions' ) );
14
+ return $this->getMod()->cleanStringArray( $exc, '#[^\da-z_-]#i' );
15
  }
16
 
17
  public function isOptFileEditingDisabled() :bool {
18
  return $this->isOpt( 'disable_file_editing', 'Y' );
19
  }
20
 
21
+ public function isBlockAuthorDiscovery() :bool {
22
+ return $this->isOpt( 'block_author_discovery', 'Y' );
23
+ }
24
+
25
  public function isRestApiAnonymousAccessDisabled() :bool {
26
  return $this->isOpt( 'disable_anonymous_restapi', 'Y' );
27
  }
src/lib/src/Modules/Lockdown/Processor.php CHANGED
@@ -9,31 +9,6 @@ class Processor extends BaseShield\Processor {
9
 
10
  private $xmlProcessed = false;
11
 
12
- protected function run() {
13
- /** @var Options $opts */
14
- $opts = $this->getOptions();
15
-
16
- if ( $opts->isOptFileEditingDisabled() ) {
17
- $this->blockFileEditing();
18
- }
19
-
20
- if ( $opts->isOpt( 'force_ssl_admin', 'Y' ) && function_exists( 'force_ssl_admin' ) ) {
21
- if ( !defined( 'FORCE_SSL_ADMIN' ) ) {
22
- define( 'FORCE_SSL_ADMIN', true );
23
- }
24
- force_ssl_admin( true );
25
- }
26
-
27
- if ( $opts->isOpt( 'hide_wordpress_generator_tag', 'Y' ) ) {
28
- remove_action( 'wp_head', 'wp_generator' );
29
- }
30
-
31
- if ( $opts->isXmlrpcDisabled() ) {
32
- add_filter( 'xmlrpc_enabled', [ $this, 'disableXmlrpc' ], 1000, 0 );
33
- add_filter( 'xmlrpc_methods', [ $this, 'disableXmlrpc' ], 1000, 0 );
34
- }
35
- }
36
-
37
  public function runDailyCron() {
38
  ( new Lib\CleanRubbish() )
39
  ->setMod( $this->getMod() )
@@ -68,44 +43,12 @@ class Processor extends BaseShield\Processor {
68
  $opts = $this->getOptions();
69
 
70
  if ( !Services::WpUsers()->isUserLoggedIn() ) {
71
- $this->interceptCanonicalRedirects();
72
  if ( $opts->isRestApiAnonymousAccessDisabled() ) {
73
  add_filter( 'rest_authentication_errors', [ $this, 'disableAnonymousRestApi' ], 99 );
74
  }
75
  }
76
  }
77
 
78
- /**
79
- * @return array|false
80
- */
81
- public function disableXmlrpc() {
82
- if ( !$this->xmlProcessed ) {
83
- $this->xmlProcessed = true;
84
- $this->getCon()->fireEvent( 'block_xml' );
85
- }
86
- return ( current_filter() == 'xmlrpc_enabled' ) ? false : [];
87
- }
88
-
89
- /**
90
- * @uses wp_die()
91
- */
92
- private function interceptCanonicalRedirects() {
93
-
94
- if ( $this->getOptions()->isOpt( 'block_author_discovery', 'Y' ) ) {
95
- $author = Services::Request()->query( 'author', '' );
96
- if ( !empty( $author ) ) {
97
- Services::WpGeneral()->wpDie( sprintf(
98
- __( 'The "author" query parameter has been blocked by %s to protect against user login name fishing.', 'wp-simple-firewall' )
99
- .sprintf( '<br /><a href="%s" target="_blank">%s</a>',
100
- 'https://shsec.io/7l',
101
- __( 'Learn More.', 'wp-simple-firewall' )
102
- ),
103
- $this->getCon()->getHumanName()
104
- ) );
105
- }
106
- }
107
- }
108
-
109
  /**
110
  * Understand that if $mCurrentStatus is null, no check has been made. If true, something has
111
  * authenticated the request, and if WP_Error, then an error is already present
9
 
10
  private $xmlProcessed = false;
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  public function runDailyCron() {
13
  ( new Lib\CleanRubbish() )
14
  ->setMod( $this->getMod() )
43
  $opts = $this->getOptions();
44
 
45
  if ( !Services::WpUsers()->isUserLoggedIn() ) {
 
46
  if ( $opts->isRestApiAnonymousAccessDisabled() ) {
47
  add_filter( 'rest_authentication_errors', [ $this, 'disableAnonymousRestApi' ], 99 );
48
  }
49
  }
50
  }
51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  /**
53
  * Understand that if $mCurrentStatus is null, no check has been made. If true, something has
54
  * authenticated the request, and if WP_Error, then an error is already present
src/lib/src/Modules/Lockdown/Rules/Build/DisableFileEditing.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Lockdown\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
7
+ Build\BuildRuleCoreShieldBase,
8
+ Responses
9
+ };
10
+
11
+ class DisableFileEditing extends BuildRuleCoreShieldBase {
12
+
13
+ const SLUG = 'shield/disable_file_editing';
14
+
15
+ protected function getName() :string {
16
+ return 'Disable File Editing';
17
+ }
18
+
19
+ protected function getDescription() :string {
20
+ return 'Disable File editing from within the WP admin dashboard.';
21
+ }
22
+
23
+ protected function getConditions() :array {
24
+ return [
25
+ 'logic' => static::LOGIC_AND,
26
+ 'group' => [
27
+ [
28
+ 'rule' => Shield\Modules\Plugin\Rules\Build\RequestBypassesAllRestrictions::SLUG,
29
+ 'invert_match' => true
30
+ ],
31
+ ]
32
+ ];
33
+ }
34
+
35
+ protected function getResponses() :array {
36
+ return [
37
+ [
38
+ 'response' => Responses\DisableFileEditing::SLUG,
39
+ ],
40
+ ];
41
+ }
42
+ }
src/lib/src/Modules/Lockdown/Rules/Build/DisableXmlrpc.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Lockdown\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
7
+ Build\BuildRuleCoreShieldBase,
8
+ Conditions,
9
+ Responses
10
+ };
11
+
12
+ class DisableXmlrpc extends BuildRuleCoreShieldBase {
13
+
14
+ const SLUG = 'shield/disable_xmlrpc';
15
+
16
+ protected function getName() :string {
17
+ return 'Disable XMLRPC';
18
+ }
19
+
20
+ protected function getDescription() :string {
21
+ return 'Disable XML-RPC if required.';
22
+ }
23
+
24
+ protected function getConditions() :array {
25
+ return [
26
+ 'logic' => static::LOGIC_AND,
27
+ 'group' => [
28
+ [
29
+ 'rule' => Shield\Modules\Plugin\Rules\Build\RequestBypassesAllRestrictions::SLUG,
30
+ 'invert_match' => true
31
+ ],
32
+ [
33
+ 'condition' => Conditions\WpIsXmlrpc::SLUG,
34
+ ],
35
+ ]
36
+ ];
37
+ }
38
+
39
+ protected function getResponses() :array {
40
+ return [
41
+ [
42
+ 'response' => Responses\DisableXmlrpc::SLUG,
43
+ ],
44
+ ];
45
+ }
46
+ }
src/lib/src/Modules/Lockdown/Rules/Build/ForceSslAdmin.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Lockdown\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
7
+ Build\BuildRuleCoreShieldBase,
8
+ Responses
9
+ };
10
+
11
+ class ForceSslAdmin extends BuildRuleCoreShieldBase {
12
+
13
+ const SLUG = 'shield/force_ssl_admin';
14
+
15
+ protected function getName() :string {
16
+ return 'Force SSL Admin';
17
+ }
18
+
19
+ protected function getDescription() :string {
20
+ return 'Force SSL Admin.';
21
+ }
22
+
23
+ protected function getResponses() :array {
24
+ return [
25
+ [
26
+ 'response' => Responses\ForceSslAdmin::SLUG,
27
+ ],
28
+ ];
29
+ }
30
+ }
src/lib/src/Modules/Lockdown/Rules/Build/HideGeneratorTag.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Lockdown\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
7
+ Build\BuildRuleCoreShieldBase,
8
+ Responses
9
+ };
10
+
11
+ class HideGeneratorTag extends BuildRuleCoreShieldBase {
12
+
13
+ const SLUG = 'shield/hide_generator_tag';
14
+
15
+ protected function getName() :string {
16
+ return 'Hide Generator Tag';
17
+ }
18
+
19
+ protected function getDescription() :string {
20
+ return 'Hide Generator Tag.';
21
+ }
22
+
23
+ protected function getWpHookLevel() :int {
24
+ return Shield\Rules\WPHooksOrder::WP;
25
+ }
26
+
27
+ protected function getConditions() :array {
28
+ return [
29
+ 'logic' => static::LOGIC_AND,
30
+ 'group' => [
31
+ [
32
+ 'rule' => Shield\Modules\Plugin\Rules\Build\RequestBypassesAllRestrictions::SLUG,
33
+ 'invert_match' => true
34
+ ],
35
+ ]
36
+ ];
37
+ }
38
+
39
+ protected function getResponses() :array {
40
+ return [
41
+ [
42
+ 'response' => Responses\HideGeneratorTag::SLUG,
43
+ ],
44
+ ];
45
+ }
46
+ }
src/lib/src/Modules/Lockdown/Rules/Build/IsRequestAuthorDiscovery.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Lockdown\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
7
+ Build\BuildRuleCoreShieldBase,
8
+ Conditions,
9
+ Responses
10
+ };
11
+
12
+ class IsRequestAuthorDiscovery extends BuildRuleCoreShieldBase {
13
+
14
+ const SLUG = 'shield/is_request_author_discovery';
15
+
16
+ protected function getName() :string {
17
+ return 'Detect Author Discovery';
18
+ }
19
+
20
+ protected function getDescription() :string {
21
+ return 'Detect and block Author Discovery requests via ?author=x query.';
22
+ }
23
+
24
+ protected function getConditions() :array {
25
+ return [
26
+ 'logic' => static::LOGIC_AND,
27
+ 'group' => [
28
+ [
29
+ 'rule' => Shield\Modules\Plugin\Rules\Build\RequestBypassesAllRestrictions::SLUG,
30
+ 'invert_match' => true
31
+ ],
32
+ [
33
+ 'condition' => Conditions\IsNotLoggedInNormal::SLUG,
34
+ ],
35
+ [
36
+ 'condition' => Conditions\RequestQueryParamIs::SLUG,
37
+ 'params' => [
38
+ 'match_param' => 'author',
39
+ 'match_patterns' => [
40
+ '^\d+$'
41
+ ],
42
+ ],
43
+ ],
44
+ ]
45
+ ];
46
+ }
47
+
48
+ protected function getResponses() :array {
49
+ return [
50
+ [
51
+ 'response' => Responses\EventFire::SLUG,
52
+ 'params' => [
53
+ 'event' => 'block_author_fishing',
54
+ 'offense_count' => 1,
55
+ 'block' => false,
56
+ ],
57
+ ],
58
+ [
59
+ 'response' => Responses\BlockAuthorFishing::SLUG,
60
+ ],
61
+ ];
62
+ }
63
+ }
src/lib/src/Modules/Lockdown/Strings.php CHANGED
@@ -1,4 +1,4 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Lockdown;
4
 
@@ -17,6 +17,12 @@ class Strings extends Base\Strings {
17
  __( 'Blocked Anonymous API Access through "{{namespace}}" namespace.', 'wp-simple-firewall' ),
18
  ],
19
  ],
 
 
 
 
 
 
20
  'block_xml' => [
21
  'name' => sprintf( '%s: %s', __( 'Blocked', 'wp-simple-firewall' ), __( 'XML-RPC' ) ),
22
  'audit' => [
@@ -119,7 +125,7 @@ class Strings extends Base\Strings {
119
  __( "Please contact the developer of a plugin to ask them for their REST API namespace if you need it." ),
120
  __( 'Some common namespaces' ).':',
121
  ];
122
-
123
  $defaultEx = [
124
  'contact-form-7' => 'Contact Form 7',
125
  'tribe' => 'The Events Calendar',
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Lockdown;
4
 
17
  __( 'Blocked Anonymous API Access through "{{namespace}}" namespace.', 'wp-simple-firewall' ),
18
  ],
19
  ],
20
+ 'block_author_fishing' => [
21
+ 'name' => sprintf( '%s: %s', __( 'Blocked', 'wp-simple-firewall' ), __( 'Author Fishing' ) ),
22
+ 'audit' => [
23
+ __( 'Blocked Author Discovery via username fishing technique.', 'wp-simple-firewall' ),
24
+ ],
25
+ ],
26
  'block_xml' => [
27
  'name' => sprintf( '%s: %s', __( 'Blocked', 'wp-simple-firewall' ), __( 'XML-RPC' ) ),
28
  'audit' => [
125
  __( "Please contact the developer of a plugin to ask them for their REST API namespace if you need it." ),
126
  __( 'Some common namespaces' ).':',
127
  ];
128
+
129
  $defaultEx = [
130
  'contact-form-7' => 'Contact Form 7',
131
  'tribe' => 'The Events Calendar',
src/lib/src/Modules/LoginGuard/AdminNotices.php CHANGED
@@ -44,7 +44,7 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
44
  ],
45
  'ajax' => [
46
  'resend_verification_email' => $mod->getAjaxActionData( 'resend_verification_email', true ),
47
- 'profile_email2fa_disable' => $mod->getAjaxActionData( 'profile_email2fa_disable', true ),
48
  ]
49
  ];
50
  }
44
  ],
45
  'ajax' => [
46
  'resend_verification_email' => $mod->getAjaxActionData( 'resend_verification_email', true ),
47
+ 'profile_email2fa_disable' => $mod->getAjaxActionData( 'profile_email2fa_disable', true ),
48
  ]
49
  ];
50
  }
src/lib/src/Modules/LoginGuard/AjaxHandler.php CHANGED
@@ -134,9 +134,9 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
134
  }
135
 
136
  public function ajaxExec_Profile2faEmailDisable() :array {
137
- /** @var ModCon $mod */
138
- $mod = $this->getMod();
139
- $mod->setEnabled2FaEmail( false );
140
  return [
141
  'success' => true,
142
  'message' => __( '2FA by email has been disabled', 'wp-simple-firewall' ),
134
  }
135
 
136
  public function ajaxExec_Profile2faEmailDisable() :array {
137
+ /** @var Options $opts */
138
+ $opts = $this->getOptions();
139
+ $opts->setOpt( 'enable_email_authentication', 'N' );
140
  return [
141
  'success' => true,
142
  'message' => __( '2FA by email has been disabled', 'wp-simple-firewall' ),
src/lib/src/Modules/LoginGuard/Insights/OverviewCards.php DELETED
@@ -1,72 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard\Insights;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
7
-
8
- class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
9
-
10
- protected function buildModCards() :array {
11
- /** @var LoginGuard\ModCon $mod */
12
- $mod = $this->getMod();
13
- /** @var LoginGuard\Options $opts */
14
- $opts = $this->getOptions();
15
-
16
- $cards = [];
17
-
18
- if ( $mod->isModOptEnabled() ) {
19
-
20
- $hasBotCheck = $opts->isEnabledAntiBot() || $opts->isEnabledGaspCheck() || $mod->isEnabledCaptcha();
21
-
22
- $boLogin = $hasBotCheck && $opts->isProtectLogin();
23
- $botReg = $hasBotCheck && $opts->isProtectRegister();
24
- $botPassword = $hasBotCheck && $opts->isProtectLostPassword();
25
- $cards[ 'bot_login' ] = [
26
- 'name' => __( 'Brute Force Login', 'wp-simple-firewall' ),
27
- 'state' => $boLogin ? 1 : -1,
28
- 'summary' => $boLogin ?
29
- __( 'Login forms are protected against bot attacks', 'wp-simple-firewall' )
30
- : __( "Login forms aren't protected against brute force bot attacks", 'wp-simple-firewall' ),
31
- 'href' => $mod->getUrl_DirectLinkToOption( 'bot_protection_locations' ),
32
- ];
33
- $cards[ 'bot_register' ] = [
34
- 'name' => __( 'Bot User Register', 'wp-simple-firewall' ),
35
- 'state' => $botReg ? 1 : -1,
36
- 'summary' => $botReg ?
37
- __( 'Registration forms are protected against bot attacks', 'wp-simple-firewall' )
38
- : __( "Registration forms aren't protected against automated bots", 'wp-simple-firewall' ),
39
- 'href' => $mod->getUrl_DirectLinkToOption( 'bot_protection_locations' ),
40
- ];
41
- $cards[ 'bot_password' ] = [
42
- 'name' => __( 'Brute Force Lost Password', 'wp-simple-firewall' ),
43
- 'state' => $botPassword ? 1 : -1,
44
- 'summary' => $botPassword ?
45
- __( 'Lost Password forms are protected against bot attacks', 'wp-simple-firewall' )
46
- : __( "Lost Password forms aren't protected against automated bots", 'wp-simple-firewall' ),
47
- 'href' => $mod->getUrl_DirectLinkToOption( 'bot_protection_locations' ),
48
- ];
49
-
50
- $bHas2Fa = $opts->isEmailAuthenticationActive()
51
- || $opts->isEnabledGoogleAuthenticator() || $opts->isEnabledYubikey();
52
- $cards[ '2fa' ] = [
53
- 'name' => __( 'Identity Verification', 'wp-simple-firewall' ),
54
- 'state' => $bHas2Fa ? 1 : -1,
55
- 'summary' => $bHas2Fa ?
56
- __( 'At least one 2FA option is available', 'wp-simple-firewall' )
57
- : __( 'No 2FA options, such as Google Authenticator, are active.', 'wp-simple-firewall' ),
58
- 'href' => $mod->getUrl_DirectLinkToSection( 'section_2fa_email' ),
59
- ];
60
- }
61
-
62
- return $cards;
63
- }
64
-
65
- protected function getSectionTitle() :string {
66
- return __( 'Login Guard', 'wp-simple-firewall' );
67
- }
68
-
69
- protected function getSectionSubTitle() :string {
70
- return __( 'Brute Force Protection & Identity Verification', 'wp-simple-firewall' );
71
- }
72
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/LoginGuard/Lib/AntiBot/AntibotSetup.php CHANGED
@@ -11,9 +11,7 @@ use FernleafSystems\Wordpress\Services\Services;
11
  class AntibotSetup extends ExecOnceModConsumer {
12
 
13
  protected function canRun() :bool {
14
- /** @var LoginGuard\ModCon $mod */
15
- $mod = $this->getMod();
16
- return !$mod->isVisitorWhitelisted() && !Services::WpUsers()->isUserLoggedIn();
17
  }
18
 
19
  protected function run() {
11
  class AntibotSetup extends ExecOnceModConsumer {
12
 
13
  protected function canRun() :bool {
14
+ return !$this->getCon()->this_req->request_bypasses_all_restrictions && !Services::WpUsers()->isUserLoggedIn();
 
 
15
  }
16
 
17
  protected function run() {
src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/AntiBot.php CHANGED
@@ -23,7 +23,7 @@ class AntiBot extends BaseProtectionProvider {
23
  }
24
  }
25
 
26
- public function buildFormInsert( $oFormProvider ) {
27
  return '';
28
  }
29
  }
23
  }
24
  }
25
 
26
+ public function buildFormInsert( $formProvider ) {
27
  return '';
28
  }
29
  }
src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/BaseProtectionProvider.php CHANGED
@@ -31,10 +31,10 @@ abstract class BaseProtectionProvider {
31
  }
32
 
33
  /**
34
- * @param LoginGuard\Lib\AntiBot\FormProviders\BaseFormProvider $oFormProvider
35
  * @return string
36
  */
37
- abstract public function buildFormInsert( $oFormProvider );
38
 
39
  public function setAsInsertBuilt() :self {
40
  $this->factorBuilt = true;
31
  }
32
 
33
  /**
34
+ * @param LoginGuard\Lib\AntiBot\FormProviders\BaseFormProvider $formProvider
35
  * @return string
36
  */
37
+ abstract public function buildFormInsert( $formProvider );
38
 
39
  public function setAsInsertBuilt() :self {
40
  $this->factorBuilt = true;
src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/CoolDown.php CHANGED
@@ -18,15 +18,15 @@ class CoolDown extends BaseProtectionProvider {
18
  // And finally return a WP_Error which will be reflected back to the user.
19
  $cooldown = ( new CooldownFlagFile() )->setMod( $this->getMod() );
20
  if ( $cooldown->isWithinCooldownPeriod() ) {
21
- $sErrorString = __( "Request Cooldown in effect.", 'wp-simple-firewall' ).' '
22
- .sprintf(
23
- __( "You must wait %s seconds before attempting this action again.", 'wp-simple-firewall' ),
24
- $cooldown->getCooldownRemaining()
25
- );
26
 
27
  $this->getCon()->fireEvent( 'cooldown_fail' );
28
  $this->processFailure();
29
- throw new \Exception( $sErrorString );
30
  }
31
 
32
  $cooldown->updateCooldownFlag();
@@ -36,7 +36,7 @@ class CoolDown extends BaseProtectionProvider {
36
  /**
37
  * @inheritDoc
38
  */
39
- public function buildFormInsert( $oFormProvider ) {
40
  return '';
41
  }
42
  }
18
  // And finally return a WP_Error which will be reflected back to the user.
19
  $cooldown = ( new CooldownFlagFile() )->setMod( $this->getMod() );
20
  if ( $cooldown->isWithinCooldownPeriod() ) {
21
+ $error = __( "Request Cooldown in effect.", 'wp-simple-firewall' ).' '
22
+ .sprintf(
23
+ __( "You must wait %s seconds before attempting this action again.", 'wp-simple-firewall' ),
24
+ $cooldown->getCooldownRemaining()
25
+ );
26
 
27
  $this->getCon()->fireEvent( 'cooldown_fail' );
28
  $this->processFailure();
29
+ throw new \Exception( $error );
30
  }
31
 
32
  $cooldown->updateCooldownFlag();
36
  /**
37
  * @inheritDoc
38
  */
39
+ public function buildFormInsert( $formProvider ) {
40
  return '';
41
  }
42
  }
src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/GaspJs.php CHANGED
@@ -25,17 +25,12 @@ class GaspJs extends BaseProtectionProvider {
25
  /** @var LoginGuard\Options $opts */
26
  $opts = $this->getOptions();
27
 
28
- $ts = Services::Request()->ts();
29
- $nonce = $mod->getAjaxActionData( 'comment_token'.Services::IP()->getRequestIp() );
30
- $nonce[ 'ts' ] = $ts;
31
- $nonce[ 'post_id' ] = Services::WpPost()->getCurrentPostId();
32
-
33
  $localz[] = [
34
  'shield/loginbot',
35
  'icwp_wpsf_vars_lpantibot',
36
  [
37
  'form_selectors' => implode( ',', $opts->getAntiBotFormSelectors() ),
38
- 'uniq' => preg_replace( '#[^a-zA-Z0-9]#', '', apply_filters( 'icwp_shield_lp_gasp_uniqid', uniqid() ) ),
39
  'cbname' => $mod->getGaspKey(),
40
  'strings' => [
41
  'label' => $mod->getTextImAHuman(),
@@ -113,7 +108,7 @@ class GaspJs extends BaseProtectionProvider {
113
  /**
114
  * @inheritDoc
115
  */
116
- public function buildFormInsert( $oFormProvider ) {
117
  return $this->getMod()->renderTemplate(
118
  '/snippets/anti_bot/gasp_js.twig',
119
  [
25
  /** @var LoginGuard\Options $opts */
26
  $opts = $this->getOptions();
27
 
 
 
 
 
 
28
  $localz[] = [
29
  'shield/loginbot',
30
  'icwp_wpsf_vars_lpantibot',
31
  [
32
  'form_selectors' => implode( ',', $opts->getAntiBotFormSelectors() ),
33
+ 'uniq' => preg_replace( '#[^\da-zA-Z]#', '', apply_filters( 'icwp_shield_lp_gasp_uniqid', uniqid() ) ),
34
  'cbname' => $mod->getGaspKey(),
35
  'strings' => [
36
  'label' => $mod->getTextImAHuman(),
108
  /**
109
  * @inheritDoc
110
  */
111
+ public function buildFormInsert( $formProvider ) {
112
  return $this->getMod()->renderTemplate(
113
  '/snippets/anti_bot/gasp_js.twig',
114
  [
src/lib/src/Modules/LoginGuard/Lib/AntiBot/ProtectionProviders/GoogleRecaptcha.php CHANGED
@@ -43,7 +43,7 @@ class GoogleRecaptcha extends BaseProtectionProvider {
43
  /**
44
  * @inheritDoc
45
  */
46
- public function buildFormInsert( $oFormProvider ) {
47
  return $this->getCaptchaHtml();
48
  }
49
 
43
  /**
44
  * @inheritDoc
45
  */
46
+ public function buildFormInsert( $formProvider ) {
47
  return $this->getCaptchaHtml();
48
  }
49
 
src/lib/src/Modules/LoginGuard/Lib/CooldownFlagFile.php CHANGED
@@ -13,27 +13,20 @@ class CooldownFlagFile {
13
  return $this->getCooldownRemaining() > 0;
14
  }
15
 
16
- /**
17
- * @return int
18
- */
19
- public function getCooldownRemaining() {
20
  /** @var Modules\LoginGuard\Options $opts */
21
  $opts = $this->getOptions();
22
- return max( 0, $opts->getCooldownInterval() - $this->getSecondsSinceLastLogin() );
23
  }
24
 
25
  public function getFlagFilePath() :string {
26
  return $this->getCon()->paths->forCacheItem( 'mode.login_throttled' );
27
  }
28
 
29
- /**
30
- * @return int
31
- */
32
- public function getSecondsSinceLastLogin() {
33
  $FS = Services::WpFs();
34
  $file = $this->getFlagFilePath();
35
- $lastLogin = $FS->exists( $file ) ? $FS->getModifiedTime( $file ) : 0;
36
- return Services::Request()->ts() - $lastLogin;
37
  }
38
 
39
  /**
13
  return $this->getCooldownRemaining() > 0;
14
  }
15
 
16
+ public function getCooldownRemaining() :int {
 
 
 
17
  /** @var Modules\LoginGuard\Options $opts */
18
  $opts = $this->getOptions();
19
+ return (int)max( 0, $opts->getCooldownInterval() - $this->getSecondsSinceLastLogin() );
20
  }
21
 
22
  public function getFlagFilePath() :string {
23
  return $this->getCon()->paths->forCacheItem( 'mode.login_throttled' );
24
  }
25
 
26
+ public function getSecondsSinceLastLogin() :int {
 
 
 
27
  $FS = Services::WpFs();
28
  $file = $this->getFlagFilePath();
29
+ return Services::Request()->ts() - ( $FS->exists( $file ) ? $FS->getModifiedTime( $file ) : 0 );
 
30
  }
31
 
32
  /**
src/lib/src/Modules/LoginGuard/Lib/Rename/RenameLogin.php CHANGED
@@ -14,13 +14,11 @@ class RenameLogin {
14
  use ExecOnce;
15
 
16
  protected function canRun() :bool {
17
- /** @var LoginGuard\ModCon $mod */
18
- $mod = $this->getMod();
19
  /** @var Options $opts */
20
  $opts = $this->getOptions();
21
  return !Services::IP()->isLoopback()
22
  && !empty( $opts->getCustomLoginPath() )
23
- && !$mod->isVisitorWhitelisted()
24
  && !$this->hasPluginConflict() && !$this->hasUnsupportedConfiguration();
25
  }
26
 
@@ -163,16 +161,14 @@ class RenameLogin {
163
  /** @var LoginGuard\Options $opts */
164
  $opts = $this->getOptions();
165
 
166
- $redirectPath = parse_url( $location, PHP_URL_PATH );
167
  if ( strpos( $redirectPath, 'wp-login.php' ) !== false ) {
168
 
169
- $loginUrl = home_url( $opts->getCustomLoginPath() );
170
  $queryArgs = explode( '?', $location );
 
171
  if ( !empty( $queryArgs[ 1 ] ) ) {
172
- parse_str( $queryArgs[ 1 ], $newQueryArgs );
173
- $loginUrl = add_query_arg( $newQueryArgs, $loginUrl );
174
  }
175
- $location = $loginUrl;
176
  }
177
 
178
  return $location;
14
  use ExecOnce;
15
 
16
  protected function canRun() :bool {
 
 
17
  /** @var Options $opts */
18
  $opts = $this->getOptions();
19
  return !Services::IP()->isLoopback()
20
  && !empty( $opts->getCustomLoginPath() )
21
+ && !$this->getCon()->this_req->is_ip_whitelisted
22
  && !$this->hasPluginConflict() && !$this->hasUnsupportedConfiguration();
23
  }
24
 
161
  /** @var LoginGuard\Options $opts */
162
  $opts = $this->getOptions();
163
 
164
+ $redirectPath = wp_parse_url( $location, PHP_URL_PATH );
165
  if ( strpos( $redirectPath, 'wp-login.php' ) !== false ) {
166
 
 
167
  $queryArgs = explode( '?', $location );
168
+ $location = home_url( $opts->getCustomLoginPath() );
169
  if ( !empty( $queryArgs[ 1 ] ) ) {
170
+ $location .= '?'.$queryArgs[ 1 ];
 
171
  }
 
172
  }
173
 
174
  return $location;
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/LoginIntentRequestCapture.php CHANGED
@@ -103,7 +103,7 @@ class LoginIntentRequestCapture extends Shield\Modules\Base\Common\ExecOnceModCo
103
  }
104
 
105
  $nonce = (string)$req->post( 'login_nonce' );
106
- if ( !preg_match( '#^[a-z0-9]{10}$#i', $nonce ) ) {
107
  throw new NoLoginIntentForUserException();
108
  }
109
 
103
  }
104
 
105
  $nonce = (string)$req->post( 'login_nonce' );
106
+ if ( !preg_match( '#^[\da-z]{10}$#i', $nonce ) ) {
107
  throw new NoLoginIntentForUserException();
108
  }
109
 
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/MfaController.php CHANGED
@@ -162,9 +162,8 @@ class MfaController extends Shield\Modules\Base\Common\ExecOnceModConsumer {
162
  }
163
 
164
  public function isSubjectToLoginIntent( \WP_User $user ) :bool {
165
- /** @var LoginGuard\ModCon $mod */
166
- $mod = $this->getMod();
167
- return !$mod->isVisitorWhitelisted() && count( $this->getProvidersForUser( $user, true ) ) > 0;
168
  }
169
 
170
  public function removeAllFactorsForUser( int $userID ) :StdResponse {
162
  }
163
 
164
  public function isSubjectToLoginIntent( \WP_User $user ) :bool {
165
+ return !$this->getCon()->this_req->request_bypasses_all_restrictions
166
+ && count( $this->getProvidersForUser( $user, true ) ) > 0;
 
167
  }
168
 
169
  public function removeAllFactorsForUser( int $userID ) :StdResponse {
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/GoogleAuth.php CHANGED
@@ -150,7 +150,7 @@ class GoogleAuth extends BaseProvider {
150
  protected function processOtp( string $otp, string $loginNonce = '' ) :bool {
151
  $valid = false;
152
  try {
153
- $valid = preg_match( '#^[0-9]{6}$#', $otp )
154
  && ( new GoogleAuthenticator() )->authenticate( $this->getSecret(), $otp );
155
  }
156
  catch ( \Exception $e ) {
@@ -177,7 +177,7 @@ class GoogleAuth extends BaseProvider {
177
  $this->oWorkingSecret = ( new SecretFactory() )
178
  ->create(
179
  sanitize_user( $this->getUser()->user_login ),
180
- preg_replace( '#[^0-9a-z]#i', '', Services::WpGeneral()->getSiteName() )
181
  );
182
  }
183
  return $this->oWorkingSecret;
150
  protected function processOtp( string $otp, string $loginNonce = '' ) :bool {
151
  $valid = false;
152
  try {
153
+ $valid = preg_match( '#^\d{6}$#', $otp )
154
  && ( new GoogleAuthenticator() )->authenticate( $this->getSecret(), $otp );
155
  }
156
  catch ( \Exception $e ) {
177
  $this->oWorkingSecret = ( new SecretFactory() )
178
  ->create(
179
  sanitize_user( $this->getUser()->user_login ),
180
+ preg_replace( '#[^\da-z]#i', '', Services::WpGeneral()->getSiteName() )
181
  );
182
  }
183
  return $this->oWorkingSecret;
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Provider/Sms.php CHANGED
@@ -60,7 +60,7 @@ class Sms extends BaseProvider {
60
 
61
  $country = strtoupper( $country );
62
 
63
- if ( !preg_match( '#^[0-9]{7,15}$#', $phone ) ) {
64
  throw new \Exception( 'Phone numbers should contain only digits (0-9) and be no more than 15 digits in length.' );
65
  }
66
  if ( !preg_match( '#^[A-Z]{2}$#', $country ) ) {
60
 
61
  $country = strtoupper( $country );
62
 
63
+ if ( !preg_match( '#^\d{7,15}$#', $phone ) ) {
64
  throw new \Exception( 'Phone numbers should contain only digits (0-9) and be no more than 15 digits in length.' );
65
  }
66
  if ( !preg_match( '#^[A-Z]{2}$#', $country ) ) {
src/lib/src/Modules/LoginGuard/Lib/TwoFactor/Render/RenderLoginIntentPage.php CHANGED
@@ -61,7 +61,7 @@ class RenderLoginIntentPage extends RenderBase {
61
 
62
  return $mod->renderTemplate( '/pages/login_intent/index.twig',
63
  Services::DataManipulation()->mergeArraysRecursive(
64
- $mod->getUIHandler()->getBaseDisplayData(), $data ), true );
65
  }
66
 
67
  private function renderForm() :string {
61
 
62
  return $mod->renderTemplate( '/pages/login_intent/index.twig',
63
  Services::DataManipulation()->mergeArraysRecursive(
64
+ $mod->getUIHandler()->getBaseDisplayData(), $data ) );
65
  }
66
 
67
  private function renderForm() :string {
src/lib/src/Modules/LoginGuard/ModCon.php CHANGED
@@ -14,29 +14,31 @@ class ModCon extends BaseShield\ModCon {
14
  */
15
  private $mfaCon;
16
 
 
 
 
 
 
 
 
17
  protected function preProcessOptions() {
 
18
  /** @var Options $opts */
19
  $opts = $this->getOptions();
20
- /**
21
- * $oWp = $this->loadWpFunctionsProcessor();
22
- * $sCustomLoginPath = $this->cleanLoginUrlPath();
23
- * if ( !empty( $sCustomLoginPath ) && $oWp->getIsPermalinksEnabled() ) {
24
- * $oWp->resavePermalinks();
25
- * }
26
- */
27
- if ( $this->isModuleOptionsRequest() && $opts->isEnabledEmailAuth() && !$opts->getIfCanSendEmailVerified() ) {
28
  $this->setIfCanSendEmail( false )
29
  ->sendEmailVerifyCanSend();
30
  }
31
 
32
  $IDs = $opts->getOpt( 'antibot_form_ids', [] );
33
- foreach ( $IDs as $nKey => $id ) {
34
  $id = trim( strip_tags( $id ) );
35
  if ( empty( $id ) ) {
36
- unset( $IDs[ $nKey ] );
37
  }
38
  else {
39
- $IDs[ $nKey ] = $id;
40
  }
41
  }
42
  $opts->setOpt( 'antibot_form_ids', array_values( array_unique( $IDs ) ) );
@@ -51,12 +53,11 @@ class ModCon extends BaseShield\ModCon {
51
 
52
  $opts->setOpt( 'two_factor_auth_user_roles', $opts->getEmail2FaRoles() );
53
 
54
- if ( !$opts->isOpt( 'mfa_verify_page', 'custom_shield' )
55
- && !Services::WpGeneral()->getWordpressIsAtLeastVersion( '4.0' ) ) {
56
- $opts->resetOptToDefault( 'mfa_verify_page' );
57
  }
58
 
59
- $redirect = preg_replace( '#[^0-9a-z_\-/.]#i', '', (string)$opts->getOpt( 'rename_wplogin_redirect' ) );
60
  if ( !empty( $redirect ) ) {
61
 
62
  $redirect = preg_replace( '#^http(s)?//.*/#iU', '', $redirect );
@@ -123,15 +124,10 @@ class ModCon extends BaseShield\ModCon {
123
  }
124
  }
125
 
126
- /**
127
- * @param string $to
128
- * @param bool $bSendAsLink
129
- * @return bool
130
- */
131
- public function sendEmailVerifyCanSend( $to = null, $bSendAsLink = true ) {
132
 
133
  if ( !Services::Data()->validEmail( $to ) ) {
134
- $to = get_bloginfo( 'admin_email' );
135
  }
136
 
137
  $msg = [
@@ -140,7 +136,7 @@ class ModCon extends BaseShield\ModCon {
140
  ''
141
  ];
142
 
143
- if ( $bSendAsLink ) {
144
  $msg[] = sprintf(
145
  __( 'Click the verify link: %s', 'wp-simple-firewall' ),
146
  add_query_arg( $this->getModActionParams( 'email_send_verify' ), Services::WpGeneral()->getHomeUrl() )
@@ -150,12 +146,11 @@ class ModCon extends BaseShield\ModCon {
150
  $msg[] = sprintf( __( "Here's your code for the guided wizard: %s", 'wp-simple-firewall' ), $this->getCanEmailVerifyCode() );
151
  }
152
 
153
- return $this->getEmailProcessor()
154
- ->sendEmailWithWrap(
155
- $to,
156
- __( 'Email Sending Verification', 'wp-simple-firewall' ),
157
- $msg
158
- );
159
  }
160
 
161
  private function cleanLoginUrlPath() {
@@ -163,7 +158,7 @@ class ModCon extends BaseShield\ModCon {
163
  $opts = $this->getOptions();
164
  $path = $opts->getCustomLoginPath();
165
  if ( !empty( $path ) ) {
166
- $path = preg_replace( '#[^0-9a-zA-Z-]#', '', trim( $path, '/' ) );
167
  $this->getOptions()->setOpt( 'rename_wplogin_path', $path );
168
  }
169
  }
@@ -171,12 +166,12 @@ class ModCon extends BaseShield\ModCon {
171
  public function getGaspKey() :string {
172
  /** @var Options $opts */
173
  $opts = $this->getOptions();
174
- $sKey = $opts->getOpt( 'gasp_key' );
175
- if ( empty( $sKey ) ) {
176
- $sKey = uniqid();
177
- $opts->setOpt( 'gasp_key', $sKey );
178
  }
179
- return $this->prefix( $sKey );
180
  }
181
 
182
  public function getTextImAHuman() :string {
@@ -209,30 +204,14 @@ class ModCon extends BaseShield\ModCon {
209
  return $cfg;
210
  }
211
 
212
- public function getMfaController() :Lib\TwoFactor\MfaController {
213
- if ( !isset( $this->mfaCon ) ) {
214
- $this->mfaCon = ( new Lib\TwoFactor\MfaController() )->setMod( $this );
215
- }
216
- return $this->mfaCon;
217
- }
218
-
219
  /**
220
- * @param bool $bCan
221
  * @return $this
222
  */
223
- public function setIfCanSendEmail( $bCan ) {
224
- $this->getOptions()->setOpt( 'email_can_send_verified_at', $bCan ? Services::Request()->ts() : 0 );
225
  return $this;
226
  }
227
 
228
- public function setEnabled2FaEmail( bool $enable ) {
229
- $this->getOptions()->setOpt( 'enable_email_authentication', $enable ? 'Y' : 'N' );
230
- }
231
-
232
- public function setEnabled2FaGoogleAuthenticator( bool $enable ) {
233
- $this->getOptions()->setOpt( 'enable_google_authenticator', $enable ? 'Y' : 'N' );
234
- }
235
-
236
  public function getTextOptDefault( string $key ) :string {
237
 
238
  switch ( $key ) {
@@ -251,10 +230,6 @@ class ModCon extends BaseShield\ModCon {
251
  return $text;
252
  }
253
 
254
- public function setEnabledAntiBotDetection( bool $enable ) {
255
- $this->getOptions()->setOpt( 'enable_antibot_check', $enable ? 'Y' : 'N' );
256
- }
257
-
258
  public function getScriptLocalisations() :array {
259
  $locals = parent::getScriptLocalisations();
260
  $locals[] = [
@@ -271,15 +246,15 @@ class ModCon extends BaseShield\ModCon {
271
  }
272
 
273
  public function getCustomScriptEnqueues() :array {
274
- $enqs = [];
275
  if ( is_admin() || is_network_admin() ) {
276
- $enqs[ Enqueue::CSS ] = [
277
  'wp-wp-jquery-ui-dialog'
278
  ];
279
- $enqs[ Enqueue::JS ] = [
280
  'wp-jquery-ui-dialog'
281
  ];
282
  }
283
- return $enqs;
284
  }
285
  }
14
  */
15
  private $mfaCon;
16
 
17
+ public function getMfaController() :Lib\TwoFactor\MfaController {
18
+ if ( !isset( $this->mfaCon ) ) {
19
+ $this->mfaCon = ( new Lib\TwoFactor\MfaController() )->setMod( $this );
20
+ }
21
+ return $this->mfaCon;
22
+ }
23
+
24
  protected function preProcessOptions() {
25
+ $WP = Services::WpGeneral();
26
  /** @var Options $opts */
27
  $opts = $this->getOptions();
28
+ if ( $opts->isEnabledEmailAuth() && $opts->isOptChanged( 'enable_email_authentication' )
29
+ && !$opts->getIfCanSendEmailVerified() ) {
 
 
 
 
 
 
30
  $this->setIfCanSendEmail( false )
31
  ->sendEmailVerifyCanSend();
32
  }
33
 
34
  $IDs = $opts->getOpt( 'antibot_form_ids', [] );
35
+ foreach ( $IDs as $key => $id ) {
36
  $id = trim( strip_tags( $id ) );
37
  if ( empty( $id ) ) {
38
+ unset( $IDs[ $key ] );
39
  }
40
  else {
41
+ $IDs[ $key ] = $id;
42
  }
43
  }
44
  $opts->setOpt( 'antibot_form_ids', array_values( array_unique( $IDs ) ) );
53
 
54
  $opts->setOpt( 'two_factor_auth_user_roles', $opts->getEmail2FaRoles() );
55
 
56
+ if ( !$opts->isOpt( 'mfa_verify_page', 'custom_shield' ) && !$WP->getWordpressIsAtLeastVersion( '4.0' ) ) {
57
+ $opts->setOpt( 'mfa_verify_page', 'custom_shield' );
 
58
  }
59
 
60
+ $redirect = preg_replace( '#[^\da-z_\-/.]#i', '', (string)$opts->getOpt( 'rename_wplogin_redirect' ) );
61
  if ( !empty( $redirect ) ) {
62
 
63
  $redirect = preg_replace( '#^http(s)?//.*/#iU', '', $redirect );
124
  }
125
  }
126
 
127
+ public function sendEmailVerifyCanSend( string $to = '', bool $sendAsLink = true ) :bool {
 
 
 
 
 
128
 
129
  if ( !Services::Data()->validEmail( $to ) ) {
130
+ $to = Services::WpGeneral()->getSiteAdminEmail();
131
  }
132
 
133
  $msg = [
136
  ''
137
  ];
138
 
139
+ if ( $sendAsLink ) {
140
  $msg[] = sprintf(
141
  __( 'Click the verify link: %s', 'wp-simple-firewall' ),
142
  add_query_arg( $this->getModActionParams( 'email_send_verify' ), Services::WpGeneral()->getHomeUrl() )
146
  $msg[] = sprintf( __( "Here's your code for the guided wizard: %s", 'wp-simple-firewall' ), $this->getCanEmailVerifyCode() );
147
  }
148
 
149
+ return $this->getEmailProcessor()->sendEmailWithWrap(
150
+ $to,
151
+ __( 'Email Sending Verification', 'wp-simple-firewall' ),
152
+ $msg
153
+ );
 
154
  }
155
 
156
  private function cleanLoginUrlPath() {
158
  $opts = $this->getOptions();
159
  $path = $opts->getCustomLoginPath();
160
  if ( !empty( $path ) ) {
161
+ $path = preg_replace( '#[^\da-zA-Z-]#', '', trim( $path, '/' ) );
162
  $this->getOptions()->setOpt( 'rename_wplogin_path', $path );
163
  }
164
  }
166
  public function getGaspKey() :string {
167
  /** @var Options $opts */
168
  $opts = $this->getOptions();
169
+ $key = $opts->getOpt( 'gasp_key' );
170
+ if ( empty( $key ) ) {
171
+ $key = uniqid();
172
+ $opts->setOpt( 'gasp_key', $key );
173
  }
174
+ return $this->getCon()->prefix( $key );
175
  }
176
 
177
  public function getTextImAHuman() :string {
204
  return $cfg;
205
  }
206
 
 
 
 
 
 
 
 
207
  /**
 
208
  * @return $this
209
  */
210
+ public function setIfCanSendEmail( bool $can ) {
211
+ $this->getOptions()->setOpt( 'email_can_send_verified_at', $can ? Services::Request()->ts() : 0 );
212
  return $this;
213
  }
214
 
 
 
 
 
 
 
 
 
215
  public function getTextOptDefault( string $key ) :string {
216
 
217
  switch ( $key ) {
230
  return $text;
231
  }
232
 
 
 
 
 
233
  public function getScriptLocalisations() :array {
234
  $locals = parent::getScriptLocalisations();
235
  $locals[] = [
246
  }
247
 
248
  public function getCustomScriptEnqueues() :array {
249
+ $enq = [];
250
  if ( is_admin() || is_network_admin() ) {
251
+ $enq[ Enqueue::CSS ] = [
252
  'wp-wp-jquery-ui-dialog'
253
  ];
254
+ $enq[ Enqueue::JS ] = [
255
  'wp-jquery-ui-dialog'
256
  ];
257
  }
258
+ return $enq;
259
  }
260
  }
src/lib/src/Modules/LoginGuard/Options.php CHANGED
@@ -124,7 +124,6 @@ class Options extends BaseShield\Options {
124
 
125
  /**
126
  * @param string $location - see config for keys, e.g. login, register, password, checkout_woo
127
- * @return bool
128
  */
129
  public function isProtect( $location ) :bool {
130
  $locs = $this->getOpt( 'bot_protection_locations' );
124
 
125
  /**
126
  * @param string $location - see config for keys, e.g. login, register, password, checkout_woo
 
127
  */
128
  public function isProtect( $location ) :bool {
129
  $locs = $this->getOpt( 'bot_protection_locations' );
src/lib/src/Modules/LoginGuard/Processor.php CHANGED
@@ -3,7 +3,6 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
- use FernleafSystems\Wordpress\Services\Services;
7
 
8
  class Processor extends BaseShield\Processor {
9
 
@@ -12,7 +11,7 @@ class Processor extends BaseShield\Processor {
12
  $mod = $this->getMod();
13
 
14
  // XML-RPC Compatibility
15
- if ( Services::WpGeneral()->isXmlrpc() && $mod->isXmlrpcBypass() ) {
16
  return;
17
  }
18
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\LoginGuard;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
 
6
 
7
  class Processor extends BaseShield\Processor {
8
 
11
  $mod = $this->getMod();
12
 
13
  // XML-RPC Compatibility
14
+ if ( $this->getCon()->this_req->wp_is_xmlrpc && $mod->isXmlrpcBypass() ) {
15
  return;
16
  }
17
 
src/lib/src/Modules/LoginGuard/Strings.php CHANGED
@@ -86,9 +86,9 @@ class Strings extends Base\Strings {
86
  ];
87
  break;
88
 
89
- case 'section_multifactor_authentication' :
90
- $title = __( 'Multi-Factor Authentication', 'wp-simple-firewall' );
91
- $titleShort = __( 'Multi-Factor Auth', 'wp-simple-firewall' );
92
  $summary = [
93
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Verifies the identity of users who log in to your site - i.e. they are who they say they are.', 'wp-simple-firewall' ) ),
94
  __( 'You may combine multiple authentication factors for increased security.', 'wp-simple-firewall' )
@@ -97,7 +97,7 @@ class Strings extends Base\Strings {
97
 
98
  case 'section_2fa_email' :
99
  $title = __( 'Email Two-Factor Authentication', 'wp-simple-firewall' );
100
- $titleShort = __( '2FA Email', 'wp-simple-firewall' );
101
  $summary = [
102
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Verifies the identity of users who log in to your site using email-based one-time-passwords.', 'wp-simple-firewall' ) ),
103
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), __( 'Use of this feature is highly recommend.', 'wp-simple-firewall' ).' '.__( 'However, if your host blocks email sending you may lock yourself out.', 'wp-simple-firewall' ) ),
@@ -190,8 +190,8 @@ class Strings extends Base\Strings {
190
  break;
191
 
192
  case 'mfa_verify_page' :
193
- $name = __( 'MFA Verification Page', 'wp-simple-firewall' );
194
- $summary = __( 'Type Of MFA Verification Page', 'wp-simple-firewall' );
195
  $desc = [
196
  __( 'Choose the type of page provided to users for MFA verification.', 'wp-simple-firewall' ),
197
  sprintf( '%s - %s', __( 'Note', 'wp-simple-firewall' ),
@@ -202,8 +202,8 @@ class Strings extends Base\Strings {
202
  break;
203
 
204
  case 'mfa_user_setup_pages' :
205
- $name = __( 'User 2FA Setup', 'wp-simple-firewall' );
206
- $summary = __( 'User 2FA Setup Page Locations', 'wp-simple-firewall' );
207
  $desc = [
208
  __( 'Specify pages available to users to configure 2FA on their account.', 'wp-simple-firewall' ),
209
  __( 'At least 1 option must be provided and defaults to the user profile page within the WP admin area.', 'wp-simple-firewall' )
@@ -213,13 +213,16 @@ class Strings extends Base\Strings {
213
  case 'mfa_skip' :
214
  $name = __( '2FA Remember Me', 'wp-simple-firewall' );
215
  $summary = __( 'A User Can Bypass 2FA For The Set Number Of Days', 'wp-simple-firewall' );
216
- $desc = __( 'Enter the number of days a user can bypass future MFA after a successful MFA-login. 0 to disable.', 'wp-simple-firewall' );
217
  break;
218
 
219
  case 'allow_backupcodes' :
220
  $name = __( 'Allow Backup Codes', 'wp-simple-firewall' );
221
  $summary = __( 'Allow Users To Generate A Backup Code', 'wp-simple-firewall' );
222
- $desc = __( 'Allow users to generate a backup code that can be used to login if MFA factors are unavailable.', 'wp-simple-firewall' );
 
 
 
223
  break;
224
 
225
  case 'enable_google_authenticator' :
86
  ];
87
  break;
88
 
89
+ case 'section_twofactor_auth' :
90
+ $title = __( 'Two-Factor Authentication', 'wp-simple-firewall' );
91
+ $titleShort = __( 'Two-Factor Auth', 'wp-simple-firewall' );
92
  $summary = [
93
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Verifies the identity of users who log in to your site - i.e. they are who they say they are.', 'wp-simple-firewall' ) ),
94
  __( 'You may combine multiple authentication factors for increased security.', 'wp-simple-firewall' )
97
 
98
  case 'section_2fa_email' :
99
  $title = __( 'Email Two-Factor Authentication', 'wp-simple-firewall' );
100
+ $titleShort = __( '2FA By Email', 'wp-simple-firewall' );
101
  $summary = [
102
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Verifies the identity of users who log in to your site using email-based one-time-passwords.', 'wp-simple-firewall' ) ),
103
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), __( 'Use of this feature is highly recommend.', 'wp-simple-firewall' ).' '.__( 'However, if your host blocks email sending you may lock yourself out.', 'wp-simple-firewall' ) ),
190
  break;
191
 
192
  case 'mfa_verify_page' :
193
+ $name = __( '2FA Verification Page', 'wp-simple-firewall' );
194
+ $summary = __( 'Type Of 2FA Verification Page', 'wp-simple-firewall' );
195
  $desc = [
196
  __( 'Choose the type of page provided to users for MFA verification.', 'wp-simple-firewall' ),
197
  sprintf( '%s - %s', __( 'Note', 'wp-simple-firewall' ),
202
  break;
203
 
204
  case 'mfa_user_setup_pages' :
205
+ $name = __( '2FA Config For Users', 'wp-simple-firewall' );
206
+ $summary = __( '2FA Config Pages For User Control', 'wp-simple-firewall' );
207
  $desc = [
208
  __( 'Specify pages available to users to configure 2FA on their account.', 'wp-simple-firewall' ),
209
  __( 'At least 1 option must be provided and defaults to the user profile page within the WP admin area.', 'wp-simple-firewall' )
213
  case 'mfa_skip' :
214
  $name = __( '2FA Remember Me', 'wp-simple-firewall' );
215
  $summary = __( 'A User Can Bypass 2FA For The Set Number Of Days', 'wp-simple-firewall' );
216
+ $desc = __( 'The number of days a user can bypass 2FA after a successful 2FA. 0 to disable.', 'wp-simple-firewall' );
217
  break;
218
 
219
  case 'allow_backupcodes' :
220
  $name = __( 'Allow Backup Codes', 'wp-simple-firewall' );
221
  $summary = __( 'Allow Users To Generate A Backup Code', 'wp-simple-firewall' );
222
+ $desc = [
223
+ __( "Allow users to generate a backup 2FA login code.", 'wp-simple-firewall' ),
224
+ __( "These may be used by the user when they don't have access to their normal 2FA methods.", 'wp-simple-firewall' )
225
+ ];
226
  break;
227
 
228
  case 'enable_google_authenticator' :
src/lib/src/Modules/Plugin/AdminNotices.php CHANGED
@@ -17,6 +17,10 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
17
 
18
  switch ( $notice->id ) {
19
 
 
 
 
 
20
  case 'plugin-too-old':
21
  $this->buildNotice_PluginTooOld( $notice );
22
  break;
@@ -59,13 +63,32 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
59
  }
60
  }
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  private function buildNotice_PluginTooOld( NoticeVO $notice ) {
63
  $name = $this->getCon()->getHumanName();
64
 
65
  $notice->render_data = [
66
  'notice_attributes' => [],
67
  'strings' => [
68
- 'title' => sprintf( '%s: %s', __( 'Warning', 'wp-simple-firewall' ),
69
  sprintf( __( "%s Plugin Is Too Old", 'wp-simple-firewall' ), $name ) ),
70
  'lines' => [
71
  sprintf(
@@ -261,12 +284,16 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
261
  $needed = false;
262
  break;
263
 
 
 
 
 
264
  case 'plugin-too-old':
265
  $needed = $this->isNeeded_PluginTooOld();
266
  break;
267
 
268
  case 'override-forceoff':
269
- $needed = $con->getIfForceOffActive();
270
  break;
271
 
272
  case 'plugin-disabled':
@@ -292,6 +319,11 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
292
  return $needed;
293
  }
294
 
 
 
 
 
 
295
  private function isNeeded_PluginTooOld() :bool {
296
  $needed = false;
297
  $con = $this->getCon();
@@ -308,10 +340,4 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
308
  }
309
  return $needed;
310
  }
311
-
312
- /**
313
- * @deprecated 14.1
314
- */
315
- public function handleAuthAjax( array $ajaxResponse ) :array {
316
- }
317
  }
17
 
18
  switch ( $notice->id ) {
19
 
20
+ case 'rules-not-running':
21
+ $this->buildNotice_RulesNotRunning( $notice );
22
+ break;
23
+
24
  case 'plugin-too-old':
25
  $this->buildNotice_PluginTooOld( $notice );
26
  break;
63
  }
64
  }
65
 
66
+ private function buildNotice_RulesNotRunning( NoticeVO $notice ) {
67
+ $name = $this->getCon()->getHumanName();
68
+
69
+ $notice->render_data = [
70
+ 'notice_attributes' => [],
71
+ 'strings' => [
72
+ 'title' => sprintf( '%s: %s', __( 'Warning', 'wp-simple-firewall' ),
73
+ sprintf( __( "%s's Rules Engine Isn't Running", 'wp-simple-firewall' ), $name ) ),
74
+ 'lines' => [
75
+ sprintf(
76
+ __( "The Rules Engine that processes requests and protects your site doesn't appear to be operating normally.", 'wp-simple-firewall' ),
77
+ $name
78
+ ),
79
+ __( "This could be a webhosting configuration issue, but please reach out to our support desk for help to isolate the issue.", 'wp-simple-firewall' ),
80
+ ],
81
+ ],
82
+ ];
83
+ }
84
+
85
  private function buildNotice_PluginTooOld( NoticeVO $notice ) {
86
  $name = $this->getCon()->getHumanName();
87
 
88
  $notice->render_data = [
89
  'notice_attributes' => [],
90
  'strings' => [
91
+ 'title' => sprintf( '%s: %s', __( 'asdfasdf', 'wp-simple-firewall' ),
92
  sprintf( __( "%s Plugin Is Too Old", 'wp-simple-firewall' ), $name ) ),
93
  'lines' => [
94
  sprintf(
284
  $needed = false;
285
  break;
286
 
287
+ case 'rules-not-running':
288
+ $needed = $this->isNeeded_RulesNotRunning();
289
+ break;
290
+
291
  case 'plugin-too-old':
292
  $needed = $this->isNeeded_PluginTooOld();
293
  break;
294
 
295
  case 'override-forceoff':
296
+ $needed = $con->this_req->is_force_off;
297
  break;
298
 
299
  case 'plugin-disabled':
319
  return $needed;
320
  }
321
 
322
+ private function isNeeded_RulesNotRunning() :bool {
323
+ $con = $this->getCon();
324
+ return !$con->rules->isRulesEngineReady() || !$con->rules->processComplete;
325
+ }
326
+
327
  private function isNeeded_PluginTooOld() :bool {
328
  $needed = false;
329
  $con = $this->getCon();
340
  }
341
  return $needed;
342
  }
 
 
 
 
 
 
343
  }
src/lib/src/Modules/Plugin/AjaxHandler.php CHANGED
@@ -16,6 +16,7 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
16
  ] );
17
  if ( $isAuth ) {
18
  $map = array_merge( $map, [
 
19
  'bulk_action' => [ $this, 'ajaxExec_BulkItemAction' ],
20
  'delete_forceoff' => [ $this, 'ajaxExec_DeleteForceOff' ],
21
  'import_from_site' => [ $this, 'ajaxExec_ImportFromSite' ],
@@ -28,11 +29,38 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
28
  'set_plugin_tracking' => [ $this, 'ajaxExec_SetPluginTrackingPerm' ],
29
  'sgoptimizer_turnoff' => [ $this, 'ajaxExec_TurnOffSiteGroundOptions' ],
30
  'wizard_step' => [ $this, 'ajaxExec_Wizard' ],
 
31
  ] );
32
  }
33
  return $map;
34
  }
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  public function ajaxExec_Wizard() :array {
37
  $params = FormParams::Retrieve();
38
  // step will be step1, step2 etc.
@@ -97,14 +125,8 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
97
  }
98
 
99
  public function ajaxExec_DeleteForceOff() :array {
100
- $stillActive = $this->getCon()
101
- ->deleteForceOffFile()
102
- ->getIfForceOffActive();
103
- if ( $stillActive ) {
104
- $this->getMod()
105
- ->setFlashAdminNotice( __( 'File could not be automatically removed.', 'wp-simple-firewall' ), null, true );
106
- }
107
- return [ 'success' => !$stillActive ];
108
  }
109
 
110
  public function ajaxExec_RenderTableAdminNotes() :array {
@@ -123,17 +145,17 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
123
  /** @var ModCon $mod */
124
  $mod = $this->getMod();
125
 
126
- $sItemId = Services::Request()->post( 'rid' );
127
- if ( empty( $sItemId ) ) {
128
  $msg = __( 'Note not found.', 'wp-simple-firewall' );
129
  }
130
  else {
131
  try {
132
- $bSuccess = $mod->getDbHandler_Notes()
133
- ->getQueryDeleter()
134
- ->deleteById( $sItemId );
135
 
136
- if ( $bSuccess ) {
137
  $msg = __( 'Note deleted', 'wp-simple-firewall' );
138
  }
139
  else {
@@ -165,17 +187,13 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
165
  $msg = __( 'Please check the box to confirm your intent to overwrite settings', 'wp-simple-firewall' );
166
  }
167
  else {
168
- $sMasterSiteUrl = $formParams[ 'MasterSiteUrl' ];
169
- $sSecretKey = $formParams[ 'MasterSiteSecretKey' ];
170
- $bEnabledNetwork = $formParams[ 'ShieldNetwork' ] === 'Y';
171
- $bDisableNetwork = $formParams[ 'ShieldNetwork' ] === 'N';
172
- $bNetwork = $bEnabledNetwork ? true : ( $bDisableNetwork ? false : null );
173
 
174
  /** @var Shield\Databases\AdminNotes\Insert $oInserter */
175
  try {
176
  $code = ( new Plugin\Lib\ImportExport\Import() )
177
  ->setMod( $this->getMod() )
178
- ->fromSite( $sMasterSiteUrl, $sSecretKey, $bNetwork );
179
  }
180
  catch ( \Exception $e ) {
181
  $code = $e->getCode();
16
  ] );
17
  if ( $isAuth ) {
18
  $map = array_merge( $map, [
19
+ 'mod_options_save' => [ $this, 'ajaxExec_ModOptionsSave' ],
20
  'bulk_action' => [ $this, 'ajaxExec_BulkItemAction' ],
21
  'delete_forceoff' => [ $this, 'ajaxExec_DeleteForceOff' ],
22
  'import_from_site' => [ $this, 'ajaxExec_ImportFromSite' ],
29
  'set_plugin_tracking' => [ $this, 'ajaxExec_SetPluginTrackingPerm' ],
30
  'sgoptimizer_turnoff' => [ $this, 'ajaxExec_TurnOffSiteGroundOptions' ],
31
  'wizard_step' => [ $this, 'ajaxExec_Wizard' ],
32
+ 'render_dashboard_widget' => [ $this, 'ajaxExec_RenderDashboardWidget' ],
33
  ] );
34
  }
35
  return $map;
36
  }
37
 
38
+ public function ajaxExec_ModOptionsSave() :array {
39
+ $name = $this->getCon()->getHumanName();
40
+
41
+ $saver = ( new Shield\Modules\Base\Options\HandleOptionsSaveRequest() )
42
+ ->setMod( $this->getMod() );
43
+ $success = $saver->handleSave();
44
+
45
+ return [
46
+ 'success' => $success,
47
+ 'redirect_to' => $saver->getMod()->getUrl_OptionsConfigPage(),
48
+ 'html' => '', //we reload the page
49
+ 'message' => $success ?
50
+ sprintf( __( '%s Plugin options updated successfully.', 'wp-simple-firewall' ), $name )
51
+ : sprintf( __( 'Failed to update %s plugin options.', 'wp-simple-firewall' ), $name )
52
+ ];
53
+ }
54
+
55
+ public function ajaxExec_RenderDashboardWidget() :array {
56
+ return [
57
+ 'success' => true,
58
+ 'html' => ( new Components\DashboardWidget() )
59
+ ->setMod( $this->getMod() )
60
+ ->render( (bool)Services::Request()->post( 'refresh' ) )
61
+ ];
62
+ }
63
+
64
  public function ajaxExec_Wizard() :array {
65
  $params = FormParams::Retrieve();
66
  // step will be step1, step2 etc.
125
  }
126
 
127
  public function ajaxExec_DeleteForceOff() :array {
128
+ $this->getCon()->deleteForceOffFile();
129
+ return [ 'success' => true ];
 
 
 
 
 
 
130
  }
131
 
132
  public function ajaxExec_RenderTableAdminNotes() :array {
145
  /** @var ModCon $mod */
146
  $mod = $this->getMod();
147
 
148
+ $noteID = Services::Request()->post( 'rid' );
149
+ if ( empty( $noteID ) ) {
150
  $msg = __( 'Note not found.', 'wp-simple-firewall' );
151
  }
152
  else {
153
  try {
154
+ $success = $mod->getDbHandler_Notes()
155
+ ->getQueryDeleter()
156
+ ->deleteById( $noteID );
157
 
158
+ if ( $success ) {
159
  $msg = __( 'Note deleted', 'wp-simple-firewall' );
160
  }
161
  else {
187
  $msg = __( 'Please check the box to confirm your intent to overwrite settings', 'wp-simple-firewall' );
188
  }
189
  else {
190
+ $doNetwork = ( $formParams[ 'ShieldNetwork' ] === 'Y' ) ? true : ( ( $formParams[ 'ShieldNetwork' ] === 'N' ) ? false : null );
 
 
 
 
191
 
192
  /** @var Shield\Databases\AdminNotes\Insert $oInserter */
193
  try {
194
  $code = ( new Plugin\Lib\ImportExport\Import() )
195
  ->setMod( $this->getMod() )
196
+ ->fromSite( (string)$formParams[ 'MasterSiteUrl' ], (string)$formParams[ 'MasterSiteSecretKey' ], $doNetwork );
197
  }
198
  catch ( \Exception $e ) {
199
  $code = $e->getCode();
src/lib/src/Modules/Plugin/Components/DashboardWidget.php ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Components;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Databases\Events\EntryVO;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Insights\Lib\MeterAnalysis\Components;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Collate\RecentStats;
9
+ use FernleafSystems\Wordpress\Services\Services;
10
+ use FernleafSystems\Wordpress\Services\Utilities\Options\Transient;
11
+
12
+ class DashboardWidget {
13
+
14
+ use ModConsumer;
15
+
16
+ public function render( bool $forceRefresh = false ) :string {
17
+ $con = $this->getCon();
18
+ $modInsights = $con->getModule_Insights();
19
+ $labels = $con->getLabels();
20
+
21
+ $vars = $this->getVars( $forceRefresh );
22
+ $vars[ 'generated_at' ] = Services::Request()
23
+ ->carbon()
24
+ ->setTimestamp( $vars[ 'generated_at' ] )
25
+ ->diffForHumans();
26
+
27
+ return $this->getMod()
28
+ ->getRenderer()
29
+ ->setTemplate( '/admin/admin_dashboard_widget.twig' )
30
+ ->setRenderData( [
31
+ 'hrefs' => [
32
+ 'overview' => $modInsights->getUrl_SubInsightsPage( 'overview' ),
33
+ 'logo' => $labels[ 'PluginURI' ],
34
+ 'audit_trail' => $modInsights->getUrl_SubInsightsPage( 'audit_trail' ),
35
+ 'sessions' => $modInsights->getUrl_SubInsightsPage( 'users' ),
36
+ 'ips' => $modInsights->getUrl_SubInsightsPage( 'ips' ),
37
+ ],
38
+ 'flags' => [
39
+ 'show_internal_links' => $con->isPluginAdmin()
40
+ ],
41
+ 'imgs' => [
42
+ 'logo' => $con->urls->forImage( 'pluginlogo_banner-772x250.png' )
43
+ ],
44
+ 'strings' => [
45
+ 'security_progress' => __( 'Overall Security Progress', 'wp-simple-firewall' ),
46
+ 'progress_overview' => __( 'Go To Overview', 'wp-simple-firewall' ),
47
+ 'recent_blocked' => __( 'Recently Blocked', 'wp-simple-firewall' ),
48
+ 'recent_offenses' => __( 'Recent Offenses', 'wp-simple-firewall' ),
49
+ 'recent_sessions' => __( 'Recent Sessions', 'wp-simple-firewall' ),
50
+ 'recent_activity' => __( 'Recent Activity', 'wp-simple-firewall' ),
51
+ 'view_all' => __( 'View All', 'wp-simple-firewall' ),
52
+ 'no_offenses' => __( "No offenses recorded by IPs that haven't already been blocked.", 'wp-simple-firewall' ),
53
+ 'no_blocked' => __( 'No IP blocks recorded yet.', 'wp-simple-firewall' ),
54
+ 'no_sessions' => __( 'No new session activity recorded yet.', 'wp-simple-firewall' ),
55
+ 'no_activity' => __( 'No site activity recorded yet.', 'wp-simple-firewall' ),
56
+ 'generated' => __( 'Summary Generated', 'wp-simple-firewall' ),
57
+ 'refresh' => __( 'Refresh', 'wp-simple-firewall' ),
58
+ 'events' => __( 'Events', 'wp-simple-firewall' ),
59
+ 'time' => __( 'Time', 'wp-simple-firewall' ),
60
+ 'user' => __( 'User', 'wp-simple-firewall' ),
61
+ 'session_started' => __( 'Session Started', 'wp-simple-firewall' ),
62
+ 'last_offense' => __( 'Last Offense', 'wp-simple-firewall' ),
63
+ 'blocked' => __( 'Blocked', 'wp-simple-firewall' ),
64
+ ],
65
+ 'vars' => $vars,
66
+ ] )
67
+ ->render();
68
+ }
69
+
70
+ private function getVars( bool $refresh ) :array {
71
+ $con = $this->getCon();
72
+ $modInsights = $con->getModule_Insights();
73
+ $recent = ( new RecentStats() )->setCon( $this->getCon() );
74
+
75
+ $vars = Transient::Get( $con->prefix( 'dashboard-widget-vars' ) );
76
+ if ( $refresh || empty( $vars ) ) {
77
+ $vars = [
78
+ 'generated_at' => Services::Request()->ts(),
79
+ 'security_progress' => ( new Components() )
80
+ ->setCon( $this->getCon() )
81
+ ->getComponent( 'all' )[ 'original_score' ],
82
+ 'jump_links' => [
83
+ [
84
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'overview' ),
85
+ 'text' => __( 'Overview', 'wp-simple-firewall' ),
86
+ ],
87
+ [
88
+ 'href' => $modInsights->getUrl_IPs(),
89
+ 'text' => __( 'IPs', 'wp-simple-firewall' ),
90
+ ],
91
+ [
92
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'audit_trail' ),
93
+ 'text' => __( 'Activity', 'wp-simple-firewall' ),
94
+ ],
95
+ [
96
+ 'href' => $modInsights->getUrl_SubInsightsPage( 'traffic' ),
97
+ 'text' => __( 'Traffic', 'wp-simple-firewall' ),
98
+ ],
99
+ [
100
+ 'href' => $con->getModule_Plugin()->getUrl_AdminPage(),
101
+ 'text' => __( 'Config', 'wp-simple-firewall' ),
102
+ ],
103
+ ],
104
+ 'recent_events' => array_map(
105
+ function ( $evt ) {
106
+ /** @var EntryVO $evt */
107
+ return [
108
+ 'name' => $this->getCon()->loadEventsService()->getEventName( $evt->event ),
109
+ 'at' => Services::Request()
110
+ ->carbon()
111
+ ->setTimestamp( $evt->created_at )
112
+ ->diffForHumans(),
113
+ ];
114
+ },
115
+ array_filter(
116
+ $recent->getRecentEvents(),
117
+ function ( $evt ) {
118
+ return in_array( $evt->event, [
119
+ 'login_block',
120
+ 'firewall_block',
121
+ 'ip_blocked',
122
+ 'ip_offense',
123
+ 'bottrack_fakewebcrawler',
124
+ 'bottrack_logininvalid',
125
+ 'bottrack_loginfailed',
126
+ 'bottrack_xmlrpc',
127
+ 'bottrack_404',
128
+ 'spam_block_antibot',
129
+ ] );
130
+ }
131
+ )
132
+ ),
133
+ 'recent_ips_blocked' => array_map(
134
+ function ( $ip ) use ( $modInsights ) {
135
+ return [
136
+ 'ip' => $ip->ip,
137
+ 'ip_href' => $modInsights->getUrl_IpAnalysis( $ip->ip ),
138
+ 'at' => Services::Request()
139
+ ->carbon()
140
+ ->setTimestamp( $ip->blocked_at )
141
+ ->diffForHumans()
142
+ ];
143
+ },
144
+ $recent->getRecentlyBlockedIPs()
145
+ ),
146
+ 'recent_ips_offense' => array_map(
147
+ function ( $ip ) use ( $modInsights ) {
148
+ return [
149
+ 'ip' => $ip->ip,
150
+ 'ip_href' => $modInsights->getUrl_IpAnalysis( $ip->ip ),
151
+ 'at' => Services::Request()
152
+ ->carbon()
153
+ ->setTimestamp( $ip->last_access_at )
154
+ ->diffForHumans()
155
+ ];
156
+ },
157
+ $recent->getRecentlyOffendedIPs()
158
+ ),
159
+ 'recent_users' => array_map(
160
+ function ( $sess ) use ( $modInsights ) {
161
+ return [
162
+ 'user' => $sess[ 'user_login' ],
163
+ 'user_href' => Services::WpUsers()
164
+ ->getAdminUrl_ProfileEdit( $sess[ 'user_id' ] ),
165
+ 'ip' => $sess[ 'ip' ],
166
+ 'ip_href' => $modInsights->getUrl_IpAnalysis( $sess[ 'ip' ] ),
167
+ 'at' => Services::Request()
168
+ ->carbon()
169
+ ->setTimestamp( (int)$sess[ 'last_login_at' ] )
170
+ ->diffForHumans()
171
+ ];
172
+ },
173
+ $recent->getRecentUserSessions()
174
+ ),
175
+ ];
176
+ Transient::Set( $con->prefix( 'dashboard-widget-vars' ), $vars, 30 );
177
+ }
178
+
179
+ return $vars;
180
+ }
181
+ }
src/lib/src/Modules/Plugin/Components/PluginBadge.php CHANGED
@@ -57,6 +57,7 @@ class PluginBadge extends Modules\Base\Common\ExecOnceModConsumer {
57
 
58
  public function render( bool $isFloating = false ) :string {
59
  $con = $this->getCon();
 
60
  $wlCon = $con->getModule_SecAdmin()->getWhiteLabelController();
61
 
62
  if ( $wlCon->isEnabled() && $wlCon->isReplacePluginBadge() ) {
@@ -93,32 +94,30 @@ class PluginBadge extends Modules\Base\Common\ExecOnceModConsumer {
93
  $badgeAttrs = apply_filters( 'icwp_shield_plugin_badge_attributes', $badgeAttrs, $isFloating );
94
  }
95
 
96
- return $this->getMod()
97
- ->renderTemplate(
98
- 'snippets/plugin_badge_widget',
99
- [
100
- 'ajax' => [
101
- 'plugin_badge_close' => $this->getMod()
102
- ->getAjaxActionData( 'plugin_badge_close', true ),
103
- ],
104
- 'content' => [
105
- 'custom_css' => esc_js( $badgeAttrs[ 'custom_css' ] ),
106
- ],
107
- 'flags' => [
108
- 'nofollow' => apply_filters( 'icwp_shield_badge_relnofollow', false ),
109
- 'is_floating' => $isFloating
110
- ],
111
- 'hrefs' => [
112
- 'badge' => $badgeAttrs[ 'url' ],
113
- 'logo' => $badgeAttrs[ 'logo' ],
114
- ],
115
- 'strings' => [
116
- 'protected' => $badgeAttrs[ 'protected_by' ],
117
- 'name' => $badgeAttrs[ 'name' ],
118
- ],
119
- ],
120
- true
121
- );
122
  }
123
 
124
  public function setBadgeStateClosed() :bool {
57
 
58
  public function render( bool $isFloating = false ) :string {
59
  $con = $this->getCon();
60
+ $mod = $this->getMod();
61
  $wlCon = $con->getModule_SecAdmin()->getWhiteLabelController();
62
 
63
  if ( $wlCon->isEnabled() && $wlCon->isReplacePluginBadge() ) {
94
  $badgeAttrs = apply_filters( 'icwp_shield_plugin_badge_attributes', $badgeAttrs, $isFloating );
95
  }
96
 
97
+ return $mod->renderTemplate(
98
+ 'snippets/plugin_badge_widget',
99
+ [
100
+ 'ajax' => [
101
+ 'plugin_badge_close' => $mod->getAjaxActionData( 'plugin_badge_close', true ),
102
+ ],
103
+ 'content' => [
104
+ 'custom_css' => esc_js( $badgeAttrs[ 'custom_css' ] ),
105
+ ],
106
+ 'flags' => [
107
+ 'nofollow' => apply_filters( 'icwp_shield_badge_relnofollow', false ),
108
+ 'is_floating' => $isFloating
109
+ ],
110
+ 'hrefs' => [
111
+ 'badge' => $badgeAttrs[ 'url' ],
112
+ 'logo' => $badgeAttrs[ 'logo' ],
113
+ ],
114
+ 'strings' => [
115
+ 'protected' => $badgeAttrs[ 'protected_by' ],
116
+ 'name' => $badgeAttrs[ 'name' ],
117
+ ],
118
+ ],
119
+ true
120
+ );
 
 
121
  }
122
 
123
  public function setBadgeStateClosed() :bool {
src/lib/src/Modules/Plugin/Components/SiteGroundPluginCompatibility.php CHANGED
@@ -4,16 +4,13 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Components;
4
 
5
  class SiteGroundPluginCompatibility {
6
 
7
- /**
8
- * @return bool
9
- */
10
- public function testIsIncompatible() {
11
- $bIncompatExist = false;
12
  if ( $this->isSGOptimizerPluginAsExpected() ) {
13
  try {
14
  foreach ( $this->getIncompatOptions() as $sOption ) {
15
  if ( \SiteGround_Optimizer\Options\Options::is_enabled( $sOption ) ) {
16
- $bIncompatExist = true;
17
  break;
18
  }
19
  }
@@ -21,7 +18,7 @@ class SiteGroundPluginCompatibility {
21
  catch ( \Exception $e ) {
22
  }
23
  }
24
- return $bIncompatExist;
25
  }
26
 
27
  public function isSGOptimizerPluginAsExpected() :bool {
@@ -36,10 +33,7 @@ class SiteGroundPluginCompatibility {
36
  return $isExpected;
37
  }
38
 
39
- /**
40
- * @return bool
41
- */
42
- public function switchOffOptions() {
43
  $success = false;
44
  if ( $this->isSGOptimizerPluginAsExpected() ) {
45
  try {
4
 
5
  class SiteGroundPluginCompatibility {
6
 
7
+ public function testIsIncompatible() :bool {
8
+ $incompat = false;
 
 
 
9
  if ( $this->isSGOptimizerPluginAsExpected() ) {
10
  try {
11
  foreach ( $this->getIncompatOptions() as $sOption ) {
12
  if ( \SiteGround_Optimizer\Options\Options::is_enabled( $sOption ) ) {
13
+ $incompat = true;
14
  break;
15
  }
16
  }
18
  catch ( \Exception $e ) {
19
  }
20
  }
21
+ return $incompat;
22
  }
23
 
24
  public function isSGOptimizerPluginAsExpected() :bool {
33
  return $isExpected;
34
  }
35
 
36
+ public function switchOffOptions() :bool {
 
 
 
37
  $success = false;
38
  if ( $this->isSGOptimizerPluginAsExpected() ) {
39
  try {
src/lib/src/Modules/Plugin/Components/TestForCloudflareAPO.php CHANGED
@@ -3,14 +3,14 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Components;
4
 
5
  use FernleafSystems\Wordpress\Services\Services;
 
 
6
 
7
  class TestForCloudflareAPO {
8
 
9
  public function run() :bool {
10
- $req = Services::Request();
11
- $srvIP = Services::IP();
12
- $visitorIP = $srvIP->getRequestIp();
13
- $cfIP = $req->server( 'HTTP_CF_CONNECTING_IP' );
14
- return empty( $visitorIP ) && Services::ServiceProviders()->isIp_Cloudflare( $cfIP );
15
  }
16
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Components;
4
 
5
  use FernleafSystems\Wordpress\Services\Services;
6
+ use FernleafSystems\Wordpress\Services\Utilities\Net\IpID;
7
+ use FernleafSystems\Wordpress\Services\Utilities\ServiceProviders;
8
 
9
  class TestForCloudflareAPO {
10
 
11
  public function run() :bool {
12
+ $visitorIP = Services::IP()->getRequestIp();
13
+ $cfIP = (string)Services::Request()->server( 'HTTP_CF_CONNECTING_IP' );
14
+ return empty( $visitorIP ) && IpID::IsIpInServiceCollection( $cfIP, ServiceProviders::PROVIDER_CLOUDFLARE );
 
 
15
  }
16
  }
src/lib/src/Modules/Plugin/Debug.php CHANGED
@@ -5,14 +5,32 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Bots\ShieldNET\BuildData;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Tests\RunTests;
 
 
8
 
9
  class Debug extends Modules\Base\Debug {
10
 
11
  public function run() {
12
- $this->dbIntegrity();
13
  die( 'finish' );
14
  }
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  private function dbIntegrity() {
17
  ( new Lib\Ops\VerifyDatabaseIntegrity() )
18
  ->setCon( $this->getCon() )
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Bots\ShieldNET\BuildData;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Tests\RunTests;
8
+ use FernleafSystems\Wordpress\Services\Utilities\Integrations\WpHashes\Verify\Email;
9
+ use FernleafSystems\Wordpress\Services\Utilities\Net\IpID;
10
 
11
  class Debug extends Modules\Base\Debug {
12
 
13
  public function run() {
14
+ $this->checkIP( '66.249.79.9' );
15
  die( 'finish' );
16
  }
17
 
18
+ private function checkIP( string $ip ) {
19
+ $id = ( new IpID( $ip, 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' ) )->run();
20
+ var_dump( $id );
21
+ }
22
+
23
+ private function checkEmail( string $email ) {
24
+ $apiToken = $this->getCon()
25
+ ->getModule_License()
26
+ ->getWpHashesTokenManager()
27
+ ->getToken();
28
+ if ( !empty( $apiToken ) ) {
29
+ $verifys = ( new Email( $apiToken ) )->getEmailVerification( $email );
30
+ var_dump( $verifys );
31
+ }
32
+ }
33
+
34
  private function dbIntegrity() {
35
  ( new Lib\Ops\VerifyDatabaseIntegrity() )
36
  ->setCon( $this->getCon() )
src/lib/src/Modules/Plugin/Insights/DashboardCards.php DELETED
@@ -1,433 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Insights;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\AdminNotes;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
8
-
9
- class DashboardCards {
10
-
11
- use Shield\Modules\ModConsumer;
12
-
13
- /**
14
- * @return array
15
- * @throws \Exception
16
- */
17
- public function renderAll() :array {
18
- $cards = array_merge(
19
- [
20
- 'settings' => $this->renderSettingsCard()
21
- ],
22
- array_map(
23
- function ( $card ) {
24
- return $this->renderStandardCard( $card );
25
- },
26
- array_filter(
27
- $this->buildStandardCards(),
28
- function ( $card ) {
29
- return empty( $card[ 'hidden' ] );
30
- }
31
- )
32
- )
33
- );
34
-
35
- if ( !empty( array_diff_key( $cards, array_flip( $this->getAllCardSlugs() ) ) ) ) {
36
- throw new \Exception( 'Card(s) with unrecognised slug' );
37
- }
38
-
39
- // Merge ensures the order is as we want it, and the intersect ensure hidden cards are not included
40
- return array_merge(
41
- array_flip( array_intersect( $this->getAllCardSlugs(), array_keys( $cards ) ) ),
42
- $cards
43
- );
44
- }
45
-
46
- public function renderSettingsCard() :string {
47
- $con = $this->getCon();
48
- /** @var Plugin\ModCon $mod */
49
- $mod = $this->getMod();
50
-
51
- $name = $con->getHumanName();
52
-
53
- return $mod->renderTemplate(
54
- '/wpadmin_pages/insights/dashboard/card_settings.twig',
55
- [
56
- 'c' => [
57
- 'title' => __( 'Shield Settings', 'wp-simple-firewall' ),
58
- 'img' => $con->urls->forImage( 'bootstrap/sliders.svg' ),
59
- 'paras' => [
60
- sprintf( __( "%s settings are arranged into modules.", 'wp-simple-firewall' ), $name )
61
- .' '.__( 'Choose the module you need from the dropdown.', 'wp-simple-firewall' )
62
- ],
63
- 'actions' => [
64
- [
65
- 'text' => __( "Go To General Settings", 'wp-simple-firewall' ),
66
- 'href' => $mod->getUrl_AdminPage(),
67
- ],
68
- [
69
- 'text' => __( "Scans & Hack Guard Settings", 'wp-simple-firewall' ),
70
- 'href' => $con->getModule_HackGuard()->getUrl_AdminPage(),
71
- ],
72
- ],
73
- ],
74
- 'strings' => [
75
- 'select' => __( "Select Module", 'wp-simple-firewall' )
76
- ],
77
- 'vars' => [
78
- 'mods' => $mod->getUIHandler()->buildSelectData_ModuleSettings(),
79
- 'search_select' => $mod->getUIHandler()->buildSelectData_OptionsSearch()
80
- ]
81
- ],
82
- true
83
- );
84
- }
85
-
86
- protected function renderStandardCard( array $card ) :string {
87
- /** @var Plugin\ModCon $mod */
88
- $mod = $this->getMod();
89
- return $mod->renderTemplate(
90
- '/wpadmin_pages/insights/dashboard/card_std.twig',
91
- [ 'c' => $card ],
92
- true
93
- );
94
- }
95
-
96
- private function buildStandardCards() :array {
97
- $con = $this->getCon();
98
- $modComments = $con->getModule_Comments();
99
- $modInsights = $con->getModule_Insights();
100
- $modPlugin = $con->getModule_Plugin();
101
-
102
- $name = $con->getHumanName();
103
-
104
- /** @var AdminNotes\EntryVO $note */
105
- $note = $modPlugin->getDbHandler_Notes()->getQuerySelector()->first();
106
- $latestNote = $note instanceof AdminNotes\EntryVO ?
107
- sprintf( 'Your most recent note: "%s"', $note->note ) :
108
- __( 'No notes made yet.', 'wp-simple-firewall' );
109
-
110
- return [
111
-
112
- 'overview' => [
113
- 'title' => __( 'Security Overview', 'wp-simple-firewall' ),
114
- 'img' => $con->urls->forImage( 'bootstrap/binoculars.svg' ),
115
- 'paras' => [
116
- sprintf( __( "Review your entire %s security configuration at a glance to see what's working and what's not.", 'wp-simple-firewall' ), $name ),
117
- ],
118
- 'actions' => [
119
- [
120
- 'text' => __( "See My Security Overview", 'wp-simple-firewall' ),
121
- 'href' => $modInsights->getUrl_SubInsightsPage( 'overview' ),
122
- ],
123
- ]
124
- ],
125
-
126
- 'scans' => [
127
- 'title' => __( 'Scans and Protection', 'wp-simple-firewall' ),
128
- 'img' => $con->urls->forImage( 'bootstrap/shield-shaded.svg' ),
129
- 'paras' => [
130
- sprintf( __( "Use %s Scans to automatically detect and repair intrusions on your site.", 'wp-simple-firewall' ), $name ),
131
- sprintf( __( "%s scans WordPress core files, plugins, themes and will detect Malware (ShieldPRO).", 'wp-simple-firewall' ), $name ),
132
- ],
133
- 'actions' => [
134
- [
135
- 'text' => __( "Run Scans", 'wp-simple-firewall' ),
136
- 'href' => $modInsights->getUrl_ScansResults(),
137
- ],
138
- [
139
- 'text' => __( "Scans & Hack Guard Settings", 'wp-simple-firewall' ),
140
- 'href' => $con->getModule_HackGuard()->getUrl_AdminPage(),
141
- ],
142
- ]
143
- ],
144
-
145
- 'sec_admin' => [
146
- 'title' => __( 'Security Admin', 'wp-simple-firewall' ),
147
- 'img' => $con->urls->forImage( 'bootstrap/person-badge.svg' ),
148
- 'introjs' => sprintf( __( "Lock down access to %s itself to specific WP Administrators.", 'wp-simple-firewall' ), $name ),
149
- 'paras' => [
150
- sprintf( __( "Restrict access to %s itself and prevent unwanted changes to your site by other administrators.", 'wp-simple-firewall' ), $name ),
151
- ],
152
- 'actions' => [
153
- [
154
- 'text' => __( "Security Admin Settings", 'wp-simple-firewall' ),
155
- 'href' => $con->getModule_SecAdmin()->getUrl_AdminPage(),
156
- ],
157
- ]
158
- ],
159
-
160
- 'reports' => [
161
- 'title' => __( 'Reports and Stats', 'wp-simple-firewall' ),
162
- 'img' => $con->urls->forImage( 'bootstrap/graph-up.svg' ),
163
- 'introjs' => sprintf( __( "See the effect on your site security by %s in numbers", 'wp-simple-firewall' ), $name ),
164
- 'paras' => [
165
- sprintf( __( "Display charts to see how %s is performing over time and in which areas your site has been most impacted.", 'wp-simple-firewall' ), $name ),
166
- ],
167
- 'actions' => [
168
- [
169
- 'text' => __( "View Reports and Stats", 'wp-simple-firewall' ),
170
- 'href' => $modInsights->getUrl_SubInsightsPage( 'reports' ),
171
- ],
172
- ]
173
- ],
174
-
175
- 'free_trial' => [
176
- 'title' => __( 'Free ShieldPRO Trial', 'wp-simple-firewall' ),
177
- 'img' => $con->urls->forImage( 'bootstrap/emoji-smile.svg' ),
178
- 'paras' => [
179
- __( "Full, unrestricted access to ShieldPRO with no obligation.", 'wp-simple-firewall' ),
180
- __( "Turn-on the ShieldPRO trial within 60 seconds.", 'wp-simple-firewall' )
181
- ],
182
- 'actions' => [
183
- [
184
- 'text' => __( "Get The Free Trial", 'wp-simple-firewall' ),
185
- 'href' => $modInsights->getUrl_SubInsightsPage( 'free_trial' ),
186
- ],
187
- ],
188
- 'classes' => $con->isPremiumActive() ? [] : [ 'highlighted', 'text-white', 'bg-primary' ],
189
- 'hidden' => $con->isPremiumActive()
190
- ],
191
-
192
- 'ips' => [
193
- 'title' => __( 'IP Blocking and Bypass', 'wp-simple-firewall' ),
194
- 'img' => $con->urls->forImage( 'bootstrap/diagram-3.svg' ),
195
- 'paras' => [
196
- __( "Shield automatically detects and blocks bad IP addresses based on your security settings.", 'wp-simple-firewall' ),
197
- __( "The IP Analysis Tool shows you all information for a given IP as it relates to your site.", 'wp-simple-firewall' ),
198
- ],
199
- 'actions' => [
200
- [
201
- 'text' => __( "Analyse & Manage IPs", 'wp-simple-firewall' ),
202
- 'href' => $modInsights->getUrl_SubInsightsPage( 'ips' ),
203
- ],
204
- [
205
- 'text' => __( "IP Blocking Settings", 'wp-simple-firewall' ),
206
- 'href' => $con->getModule_IPs()->getUrl_AdminPage(),
207
- ],
208
- ]
209
- ],
210
-
211
- 'audit_trail' => [
212
- 'title' => __( 'Audit Trail', 'wp-simple-firewall' ),
213
- 'img' => $con->urls->forImage( 'bootstrap/person-lines-fill.svg' ),
214
- 'paras' => [
215
- __( "Provides in-depth logging for all major WordPress events.", 'wp-simple-firewall' ),
216
- ],
217
- 'actions' => [
218
- [
219
- 'text' => __( "View Audit Log", 'wp-simple-firewall' ),
220
- 'href' => $modInsights->getUrl_SubInsightsPage( 'audit_trail' ),
221
- ],
222
- [
223
- 'text' => __( "Audit Trail Settings", 'wp-simple-firewall' ),
224
- 'href' => $con->getModule_AuditTrail()->getUrl_AdminPage(),
225
- ],
226
- ]
227
- ],
228
-
229
- 'traffic' => [
230
- 'title' => __( 'Traffic Logging', 'wp-simple-firewall' ),
231
- 'img' => $con->urls->forImage( 'bootstrap/stoplights.svg' ),
232
- 'paras' => [
233
- __( "Use traffic logging to monitor visitor requests to your site.", 'wp-simple-firewall' ),
234
- __( "Traffic Rate Limiting lets you throttle requests from any single visitor.", 'wp-simple-firewall' ),
235
- ],
236
- 'actions' => [
237
- [
238
- 'text' => __( "View Traffic Log", 'wp-simple-firewall' ),
239
- 'href' => $modInsights->getUrl_SubInsightsPage( 'traffic' ),
240
- ],
241
- [
242
- 'text' => __( "Traffic Log Settings", 'wp-simple-firewall' ),
243
- 'href' => $con->getModule_Traffic()->getUrl_AdminPage(),
244
- ],
245
- ]
246
- ],
247
-
248
- 'users' => [
249
- 'title' => __( 'WordPress Users', 'wp-simple-firewall' ),
250
- 'img' => $con->urls->forImage( 'bootstrap/people.svg' ),
251
- 'paras' => [
252
- __( "Adds fine control over user sessions, account re-use, password strength and expiration, and user suspension.", 'wp-simple-firewall' ),
253
- ],
254
- 'actions' => [
255
- [
256
- 'text' => __( 'View User Sessions', 'wp-simple-firewall' ),
257
- 'href' => $modInsights->getUrl_SubInsightsPage( 'users' ),
258
- ],
259
- [
260
- 'text' => __( 'User Settings', 'wp-simple-firewall' ),
261
- 'href' => $con->getModule_UserManagement()->getUrl_AdminPage(),
262
- ],
263
- ]
264
- ],
265
-
266
- 'comments' => [
267
- 'title' => __( 'Comment SPAM', 'wp-simple-firewall' ),
268
- 'img' => $con->urls->forImage( 'bootstrap/chat-right-dots-fill.svg' ),
269
- 'introjs' => __( "Block all Comment SPAM from bots and even detect SPAMMY human comments.", 'wp-simple-firewall' ),
270
- 'paras' => [
271
- __( "Shield blocks 100% of all automated comments by bots (the most common type of SPAM).", 'wp-simple-firewall' ).
272
- ' '.__( "The Human SPAM filter will look for common spam words and content.", 'wp-simple-firewall' ),
273
- sprintf( '%s: %s',
274
- __( "Privacy Note", 'wp-simple-firewall' ),
275
- __( "Unlike Akismet, your comments and data are never sent off-site for analysis.", 'wp-simple-firewall' )
276
- )
277
- ],
278
- 'actions' => [
279
- [
280
- 'text' => __( "Bot SPAM Settings", 'wp-simple-firewall' ),
281
- 'href' => $modComments->getUrl_DirectLinkToSection( 'section_bot_comment_spam_protection_filter' ),
282
- ],
283
- [
284
- 'text' => __( "Human SPAM Settings", 'wp-simple-firewall' ),
285
- 'href' => $modComments->getUrl_DirectLinkToSection( 'section_human_spam_filter' ),
286
- ],
287
- ]
288
- ],
289
-
290
- 'import' => [
291
- 'title' => __( 'Import/Export', 'wp-simple-firewall' ),
292
- 'img' => $con->urls->forImage( 'bootstrap/arrow-down-up.svg' ),
293
- 'paras' => [
294
- __( "Use the import/export feature to quickly setup a new site based on the settings of another site.", 'wp-simple-firewall' ),
295
- __( "You can also setup automatic syncing of settings between sites.", 'wp-simple-firewall' ),
296
- ],
297
- 'actions' => [
298
- [
299
- 'text' => __( "Run Import/Export", 'wp-simple-firewall' ),
300
- 'href' => $modInsights->getUrl_SubInsightsPage( 'importexport' ),
301
- ],
302
- [
303
- 'text' => __( "Import/Export Settings", 'wp-simple-firewall' ),
304
- 'href' => $modPlugin->getUrl_DirectLinkToSection( 'section_importexport' ),
305
- ],
306
- ]
307
- ],
308
-
309
- 'license' => [
310
- 'title' => __( 'Go PRO!', 'wp-simple-firewall' ),
311
- 'img' => $con->urls->forImage( 'bootstrap/award.svg' ),
312
- 'paras' => [
313
- __( "By upgrading to ShieldPRO, you support ongoing Shield development and get access to exclusive PRO features.", 'wp-simple-firewall' ),
314
- ],
315
- 'actions' => [
316
- [
317
- 'text' => $con->isPremiumActive() ? __( "Manage PRO", 'wp-simple-firewall' ) : __( "Go PRO!", 'wp-simple-firewall' ),
318
- 'href' => $modInsights->getUrl_SubInsightsPage( 'license' ),
319
- ],
320
- [
321
- 'text' => __( "See Exclusive ShieldPRO Features", 'wp-simple-firewall' ),
322
- 'href' => 'https://shsec.io/gp',
323
- 'new' => true,
324
- ],
325
- ],
326
- 'classes' => $con->isPremiumActive() ? [] : [ 'highlighted', 'text-white', 'bg-success' ]
327
- ],
328
-
329
- 'notes' => [
330
- 'title' => __( 'Admin Notes', 'wp-simple-firewall' ),
331
- 'img' => $con->urls->forImage( 'bootstrap/pencil-square.svg' ),
332
- 'introjs' => __( "Review and Analyse all visitor IPs that have an impact on your site.", 'wp-simple-firewall' ),
333
- 'paras' => [
334
- __( "Use these to keep note of important items or to-dos.", 'wp-simple-firewall' ),
335
- $latestNote
336
- ],
337
- 'actions' => [
338
- [
339
- 'text' => __( "Manage Admin Notes", 'wp-simple-firewall' ),
340
- 'href' => $modInsights->getUrl_SubInsightsPage( 'notes' ),
341
- ],
342
- ]
343
- ],
344
-
345
- 'whitelabel' => [
346
- 'title' => __( 'Whitelabel', 'wp-simple-firewall' ),
347
- 'img' => $con->urls->forImage( 'bootstrap/sticky.svg' ),
348
- 'paras' => [
349
- __( "Re-brand the Shield Security plugin your image.", 'wp-simple-firewall' ),
350
- __( "Use this to enhance and solidify your brand with your clients and visitors.", 'wp-simple-firewall' ),
351
- ],
352
- 'actions' => [
353
- [
354
- 'text' => __( "Manage White Label", 'wp-simple-firewall' ),
355
- 'href' => $con->getModule_SecAdmin()
356
- ->getUrl_DirectLinkToSection( 'section_whitelabel' ),
357
- ],
358
- ]
359
- ],
360
-
361
- 'integrations' => [
362
- 'title' => __( 'Integrations', 'wp-simple-firewall' ),
363
- 'img' => $con->urls->forImage( 'bootstrap/link-45deg.svg' ),
364
- 'paras' => [
365
- __( "Shield integrates with 3rd party plugins and services.", 'wp-simple-firewall' ),
366
- __( "Determine what integrations Shield should use and manage the settings for them.", 'wp-simple-firewall' ),
367
- ],
368
- 'actions' => [
369
- [
370
- 'text' => __( "Manage Integrations", 'wp-simple-firewall' ),
371
- 'href' => $con->getModule_Integrations()->getUrl_AdminPage(),
372
- ],
373
- ]
374
- ],
375
-
376
- 'docs' => [
377
- 'title' => __( 'Docs', 'wp-simple-firewall' ),
378
- 'img' => $con->urls->forImage( 'bootstrap/book-half.svg' ),
379
- 'paras' => [
380
- sprintf( __( "Important information about %s releases and changes.", 'wp-simple-firewall' ), $name ),
381
- ],
382
- 'actions' => [
383
- [
384
- 'text' => __( "View Docs", 'wp-simple-firewall' ),
385
- 'href' => $modInsights->getUrl_SubInsightsPage( 'docs' ),
386
- ],
387
- ]
388
- ],
389
-
390
- 'debug' => [
391
- 'title' => __( 'Debug Info', 'wp-simple-firewall' ),
392
- 'img' => $con->urls->forImage( 'bootstrap/bug.svg' ),
393
- 'paras' => [
394
- __( "If you contact support, they may ask you to show them your Debug Information page.", 'wp-simple-firewall' ),
395
- __( "It's also an interesting place to see a summary of your WordPress configuration in 1 place.", 'wp-simple-firewall' ),
396
- ],
397
- 'actions' => [
398
- [
399
- 'text' => __( "View Debug Info", 'wp-simple-firewall' ),
400
- 'href' => $modInsights->getUrl_SubInsightsPage( 'debug' ),
401
- ],
402
- ]
403
- ],
404
-
405
- ];
406
- }
407
-
408
- /**
409
- * Allows us to order Dashboard cards
410
- */
411
- private function getAllCardSlugs() :array {
412
- return [
413
- 'overview',
414
- 'settings',
415
- 'scans',
416
- 'free_trial',
417
- 'sec_admin',
418
- 'reports',
419
- 'ips',
420
- 'audit_trail',
421
- 'traffic',
422
- 'users',
423
- 'comments',
424
- 'import',
425
- 'license',
426
- 'integrations',
427
- 'notes',
428
- 'whitelabel',
429
- 'docs',
430
- 'debug',
431
- ];
432
- }
433
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/Plugin/Insights/OverviewCards.php DELETED
@@ -1,181 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Insights;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
7
- use FernleafSystems\Wordpress\Services\Services;
8
- use FernleafSystems\Wordpress\Services\Utilities\Ssl;
9
- use ZxcvbnPhp\Zxcvbn;
10
-
11
- class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
12
-
13
- protected function buildModCards() :array {
14
- /** @var Plugin\ModCon $mod */
15
- $mod = $this->getMod();
16
- /** @var Plugin\Options $opts */
17
- $opts = $this->getOptions();
18
-
19
- $cards = [];
20
-
21
- if ( $mod->isModOptEnabled() ) {
22
- $bHasSupportEmail = Services::Data()->validEmail( $opts->getOpt( 'block_send_email_address' ) );
23
- $cards[ 'reports' ] = [
24
- 'name' => __( 'Reporting Email', 'wp-simple-firewall' ),
25
- 'state' => $bHasSupportEmail ? 1 : 0,
26
- 'summary' => $bHasSupportEmail ?
27
- sprintf( __( 'Email address for reports set to: %s', 'wp-simple-firewall' ), $mod->getPluginReportEmail() )
28
- : sprintf( __( 'No reporting address provided - defaulting to: %s', 'wp-simple-firewall' ), $mod->getPluginReportEmail() ),
29
- 'href' => $mod->getUrl_DirectLinkToOption( 'block_send_email_address' ),
30
- ];
31
-
32
- $cards[ 'editing' ] = [
33
- 'name' => __( 'Visitor IP Detection', 'wp-simple-firewall' ),
34
- 'state' => 0,
35
- 'summary' => sprintf( __( 'Visitor IP address source is: %s', 'wp-simple-firewall' ),
36
- __( $opts->getSelectOptionValueText( 'visitor_address_source' ), 'wp-simple-firewall' ) ),
37
- 'href' => $mod->getUrl_DirectLinkToOption( 'visitor_address_source' ),
38
- ];
39
-
40
- $captchaReady = $mod->getCaptchaCfg()->ready;
41
- $cards[ 'recap' ] = [
42
- 'name' => __( 'CAPTCHA', 'wp-simple-firewall' ),
43
- 'state' => $captchaReady ? 1 : 0,
44
- 'summary' => $captchaReady ?
45
- __( 'CAPTCHA keys have been provided', 'wp-simple-firewall' )
46
- : __( "CAPTCHA keys haven't been provided", 'wp-simple-firewall' ),
47
- 'href' => $mod->getUrl_DirectLinkToSection( 'section_third_party_captcha' ),
48
- ];
49
- }
50
-
51
- return array_merge(
52
- $cards,
53
- $this->getNoticesSsl(),
54
- $this->getNoticesDb()
55
- );
56
- }
57
-
58
- protected function getSectionTitle() :string {
59
- return __( 'General Settings', 'wp-simple-firewall' );
60
- }
61
-
62
- protected function getSectionSubTitle() :string {
63
- return sprintf( __( 'General %s Settings', 'wp-simple-firewall' ),
64
- $this->getCon()->getHumanName() );
65
- }
66
-
67
- private function getNoticesDb() :array {
68
- $cards = [];
69
-
70
- // db password strength
71
- $strong = ( new Zxcvbn() )->passwordStrength( DB_PASSWORD )[ 'score' ] >= 4;
72
- $cards[ 'db_strength' ] = [
73
- 'name' => __( 'DB Password', 'wp-simple-firewall' ),
74
- 'state' => $strong >= 4 ? 1 : -1,
75
- 'summary' => $strong ?
76
- __( 'WP Database password is very strong', 'wp-simple-firewall' )
77
- : __( "WP Database password appears to be weak", 'wp-simple-firewall' ),
78
- 'href' => '',
79
- 'help' => __( 'The database password should be strong.', 'wp-simple-firewall' )
80
- ];
81
- return $cards;
82
- }
83
-
84
- private function getNoticesSsl() :array {
85
- $WP = Services::WpGeneral();
86
- $srvSSL = new Ssl();
87
- $cards = [];
88
-
89
- // SSL Expires
90
- $sHomeUrl = $WP->getHomeUrl();
91
- $sSiteUrl = $WP->getWpUrl();
92
- $bHomeSsl = strpos( $sHomeUrl, 'https://' ) === 0;
93
- $bSiteSsl = strpos( $sSiteUrl, 'https://' ) === 0;
94
-
95
- if ( !$bHomeSsl ) {
96
- $cards[ 'site_https' ] = [
97
- 'name' => __( 'HTTPS', 'wp-simple-firewall' ),
98
- 'state' => -1,
99
- 'summary' => __( "Site visitor traffic isn't protected by HTTPS", 'wp-simple-firewall' ),
100
- 'href' => $WP->getAdminUrl_Settings(),
101
- 'help' => __( "It's recommended that an SSL certificate is installed on your site.", 'wp-simple-firewall' )
102
- ];
103
- }
104
- elseif ( !$bSiteSsl ) {
105
- $cards[ 'site_https' ] = [
106
- 'name' => __( 'HTTPS', 'wp-simple-firewall' ),
107
- 'state' => -1,
108
- 'summary' => __( "Site visitor traffic isn't protected by HTTPS", 'wp-simple-firewall' ),
109
- 'href' => $WP->getAdminUrl_Settings(),
110
- 'help' => __( "HTTPS setting for Home URL and Site URL are not consistent.", 'wp-simple-firewall' )
111
- ];
112
- }
113
- elseif ( !$srvSSL->isEnvSupported() ) {
114
- // If we can't test our SSL certificate, we just assume it's okay.
115
- $cards[ 'site_https' ] = [
116
- 'name' => __( 'HTTPS', 'wp-simple-firewall' ),
117
- 'state' => 1,
118
- 'summary' => __( "Site visitor traffic set to use HTTPS", 'wp-simple-firewall' ),
119
- 'href' => $WP->getAdminUrl_Settings(),
120
- 'help' => __( "It's recommended that an SSL certificate is installed on your site.", 'wp-simple-firewall' )
121
- ];
122
- }
123
- else {
124
-
125
- try {
126
- // first verify SSL cert:
127
- $srvSSL->getCertDetailsForDomain( $sHomeUrl );
128
-
129
- // If we didn't throw an exception, we got it.
130
- $nExpiresAt = $srvSSL->getExpiresAt( $sHomeUrl );
131
- if ( $nExpiresAt > 0 ) {
132
- $nTimeLeft = ( $nExpiresAt - Services::Request()->ts() );
133
- $bExpired = $nTimeLeft < 0;
134
- $nDaysLeft = $bExpired ? 0 : (int)round( $nTimeLeft/DAY_IN_SECONDS, 0, PHP_ROUND_HALF_DOWN );
135
-
136
- $cards[ 'site_ssl' ] = [
137
- 'name' => __( 'SSL Certificate', 'wp-simple-firewall' ),
138
- 'state' => 1,
139
- 'summary' => __( 'SSL Certificate remains valid for at least the next 2 weeks', 'wp-simple-firewall' ),
140
- 'href' => $this->getUrlSslCheck(),
141
- 'help' => __( "It's recommended to keep a valid SSL certificate installed at all times.", 'wp-simple-firewall' )
142
- ];
143
- if ( $nDaysLeft < 15 ) {
144
-
145
- $cards[ 'site_ssl' ][ 'state' ] = $bExpired ? -2 : -1;
146
-
147
- if ( $bExpired ) {
148
- $cards[ 'site_ssl' ][ 'summary' ] = __( 'SSL certificate for this site has expired.', 'wp-simple-firewall' );
149
- $cards[ 'site_ssl' ][ 'help' ] = __( "Renew your SSL certificate.", 'wp-simple-firewall' );
150
- }
151
- else {
152
- $cards[ 'site_ssl' ][ 'summary' ] = sprintf( __( 'SSL certificate will expire soon (%s days)', 'wp-simple-firewall' ), $nDaysLeft );
153
- $cards[ 'site_ssl' ][ 'help' ] = __( "Check or renew your SSL certificate.", 'wp-simple-firewall' );
154
- }
155
- }
156
- }
157
- }
158
- catch ( \Exception $e ) {
159
- $cards[ 'site_ssl' ] = [
160
- 'name' => __( 'SSL Certificate', 'wp-simple-firewall' ),
161
- 'state' => 0,
162
- 'summary' => __( "Couldn't automatically test and verify your site SSL certificate", 'wp-simple-firewall' ),
163
- 'href' => $this->getUrlSslCheck(),
164
- 'help' => sprintf( '%s: %s', __( 'Error message', 'wp-simple-firewall' ), $e->getMessage() )
165
- ];
166
- }
167
- }
168
-
169
- return $cards;
170
- }
171
-
172
- private function getUrlSslCheck() :string {
173
- return add_query_arg(
174
- [
175
- 'action' => Services::WpGeneral()->getHomeUrl(),
176
- 'run' => 'toolpage'
177
- ],
178
- 'https://mxtoolbox.com/SuperTool.aspx'
179
- );
180
- }
181
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/Plugin/Lib/AllowBetaUpgrades.php ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Crons\PluginCronsConsumer;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Common\ExecOnceModConsumer;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
+ use FernleafSystems\Wordpress\Services\Utilities\WpOrg\Plugin\Versions;
9
+
10
+ /**
11
+ * Allows the plugin to access WordPress.org SVN updates/tags that haven't actually been released.
12
+ * This way we can more easily test upgrades to ensure there are no upgrade errors etc. and make it easier for testers.
13
+ */
14
+ class AllowBetaUpgrades extends ExecOnceModConsumer {
15
+
16
+ use PluginCronsConsumer;
17
+
18
+ protected function canRun() :bool {
19
+ $con = $this->getCon();
20
+ return $con->isPremiumActive() && apply_filters( 'shield/allow_beta_upgrades', $con->is_mode_debug );
21
+ }
22
+
23
+ protected function run() {
24
+ add_filter( 'pre_set_site_transient_update_plugins', function ( $updates ) {
25
+ $con = $this->getCon();
26
+ // only offer "betas" when there is no "normal" upgrade already available
27
+ if ( is_object( $updates ) && isset( $updates->response )
28
+ && is_array( $updates->response ) && empty( $updates->response[ $con->base_file ] ) ) {
29
+
30
+ $thisPlugin = Services::WpPlugins()->getPluginAsVo( $con->base_file );
31
+ $versionsLookup = ( new Versions() )->setWorkingSlug( $thisPlugin->slug );
32
+ $versions = $versionsLookup->all();
33
+ $betas = array_filter(
34
+ is_array( $versions ) ? $versions : [],
35
+ function ( $beta ) {
36
+ return preg_match( '#\d\.#', (string)$beta )
37
+ && version_compare( (string)$beta, $this->getCon()->getVersion(), '>=' );
38
+ }
39
+ );
40
+ if ( !empty( $betas ) ) {
41
+ natsort( $betas );
42
+ $beta = array_pop( $betas );
43
+ $versionsLookup->setWorkingVersion( $beta );
44
+ $url = $versionsLookup->allVersionsUrls()[ $beta ] ?? '';
45
+ if ( !empty( $url ) ) {
46
+ $update = new \stdClass();
47
+ $update->id = $thisPlugin->id;
48
+ $update->slug = $thisPlugin->slug;
49
+ $update->plugin = $con->base_file;
50
+ $update->new_version = $beta;
51
+ $update->package = $url;
52
+ $updates->response[ $con->base_file ] = $update;
53
+ }
54
+ }
55
+ }
56
+ return $updates;
57
+ } );
58
+ }
59
+ }
src/lib/src/Modules/Plugin/Lib/Debug/Collate.php CHANGED
@@ -159,11 +159,6 @@ class Collate {
159
  $con = $this->getCon();
160
  $data = [];
161
 
162
- $dbh = $con->getModule_Sessions()->getDbHandler_Sessions();
163
- $data[ 'DB Table: Sessions' ] = $dbh->isReady() ?
164
- sprintf( '%s (rows: ~%s)', 'Ready', $dbh->getQuerySelector()->count() )
165
- : 'Missing';
166
-
167
  $dbh = $con->getModule_AuditTrail()->getDbH_Logs();
168
  $data[ 'DB Table: Audit Trail' ] = $dbh->isReady() ?
169
  sprintf( '%s (rows: ~%s)', 'Ready', $dbh->getQuerySelector()->count() )
159
  $con = $this->getCon();
160
  $data = [];
161
 
 
 
 
 
 
162
  $dbh = $con->getModule_AuditTrail()->getDbH_Logs();
163
  $data[ 'DB Table: Audit Trail' ] = $dbh->isReady() ?
164
  sprintf( '%s (rows: ~%s)', 'Ready', $dbh->getQuerySelector()->count() )
src/lib/src/Modules/Plugin/Lib/ImportExport/Export.php CHANGED
@@ -10,12 +10,9 @@ class Export {
10
 
11
  use ModConsumer;
12
 
13
- /**
14
- * @param string $sMethod
15
- */
16
- public function run( $sMethod ) {
17
  try {
18
- switch ( $sMethod ) {
19
  case 'file':
20
  $this->toFile();
21
  break;
@@ -34,59 +31,56 @@ class Export {
34
  public function toJson() {
35
  /** @var Plugin\ModCon $mod */
36
  $mod = $this->getMod();
 
37
  $req = Services::Request();
38
 
39
- $sSecretKey = $req->query( 'secret', '' );
40
-
41
- $sNetworkOpt = $req->query( 'network', '' );
42
- $bDoNetwork = !empty( $sNetworkOpt );
43
- $url = Services::Data()->validateSimpleHttpUrl( $req->query( 'url', '' ) );
44
-
45
- if ( !$mod->isImportExportSecretKey( $sSecretKey ) && !$this->isUrlOnWhitelist( $url ) ) {
46
  return; // we show no signs of responding to invalid secret keys or unwhitelisted URLs
47
  }
48
 
49
- $bSuccess = false;
50
- $aData = [];
51
 
52
  if ( !$this->verifyUrlWithHandshake( $url ) ) {
53
- $nCode = 3;
54
- $sMessage = __( 'Handshake verification failed.', 'wp-simple-firewall' );
55
  }
56
  else {
57
- $nCode = 0;
58
- $bSuccess = true;
59
- $aData = $this->getExportData();
60
- $sMessage = 'Options Exported Successfully';
61
 
62
  $this->getCon()->fireEvent(
63
  'options_exported',
64
  [ 'audit_params' => [ 'site' => $url ] ]
65
  );
66
 
67
- if ( $bDoNetwork ) {
68
- if ( $sNetworkOpt === 'Y' ) {
69
- $mod->addUrlToImportExportWhitelistUrls( $url );
70
- $this->getCon()->fireEvent(
71
- 'whitelist_site_added',
72
- [ 'audit_params' => [ 'site' => $url ] ]
73
- );
74
- }
75
- else {
76
- $mod->removeUrlFromImportExportWhitelistUrls( $url );
77
- $this->getCon()->fireEvent(
78
- 'whitelist_site_removed',
79
- [ 'audit_params' => [ 'site' => $url ] ]
80
- );
81
- }
 
82
  }
83
  }
84
 
85
  echo json_encode( [
86
- 'success' => $bSuccess,
87
- 'code' => $nCode,
88
- 'message' => $sMessage,
89
- 'data' => $aData,
90
  ] );
91
  die();
92
  }
@@ -141,7 +135,6 @@ class Export {
141
 
142
  /**
143
  * @param string $url
144
- * @return bool
145
  */
146
  private function isUrlOnWhitelist( $url ) :bool {
147
  /** @var Plugin\Options $opts */
10
 
11
  use ModConsumer;
12
 
13
+ public function run( string $method ) {
 
 
 
14
  try {
15
+ switch ( $method ) {
16
  case 'file':
17
  $this->toFile();
18
  break;
31
  public function toJson() {
32
  /** @var Plugin\ModCon $mod */
33
  $mod = $this->getMod();
34
+ $ieCon = $mod->getImpExpController();
35
  $req = Services::Request();
36
 
37
+ $url = Services::Data()->validateSimpleHttpUrl( (string)$req->query( 'url', '' ) );
38
+ if ( !$ieCon->verifySecretKey( (string)$req->query( 'secret', '' ) ) && !$this->isUrlOnWhitelist( $url ) ) {
 
 
 
 
 
39
  return; // we show no signs of responding to invalid secret keys or unwhitelisted URLs
40
  }
41
 
42
+ $success = false;
43
+ $data = [];
44
 
45
  if ( !$this->verifyUrlWithHandshake( $url ) ) {
46
+ $code = 3;
47
+ $msg = __( 'Handshake verification failed.', 'wp-simple-firewall' );
48
  }
49
  else {
50
+ $code = 0;
51
+ $success = true;
52
+ $data = $this->getExportData();
53
+ $msg = 'Options Exported Successfully';
54
 
55
  $this->getCon()->fireEvent(
56
  'options_exported',
57
  [ 'audit_params' => [ 'site' => $url ] ]
58
  );
59
 
60
+ // Only setup the network if we have a valid URL
61
+ $networkOpt = empty( $url ) ? false : $req->query( 'network', '' );
62
+
63
+ if ( $networkOpt === 'Y' ) {
64
+ $ieCon->addUrlToImportExportWhitelistUrls( $url );
65
+ $this->getCon()->fireEvent(
66
+ 'whitelist_site_added',
67
+ [ 'audit_params' => [ 'site' => $url ] ]
68
+ );
69
+ }
70
+ elseif ( !empty( $networkOpt ) ) {
71
+ $ieCon->removeUrlFromImportExportWhitelistUrls( $url );
72
+ $this->getCon()->fireEvent(
73
+ 'whitelist_site_removed',
74
+ [ 'audit_params' => [ 'site' => $url ] ]
75
+ );
76
  }
77
  }
78
 
79
  echo json_encode( [
80
+ 'success' => $success,
81
+ 'code' => $code,
82
+ 'message' => $msg,
83
+ 'data' => $data,
84
  ] );
85
  die();
86
  }
135
 
136
  /**
137
  * @param string $url
 
138
  */
139
  private function isUrlOnWhitelist( $url ) :bool {
140
  /** @var Plugin\Options $opts */
src/lib/src/Modules/Plugin/Lib/ImportExport/Import.php CHANGED
@@ -100,76 +100,67 @@ class Import {
100
  }
101
 
102
  /**
103
- * @param string $masterSiteURL
104
- * @param string $sSecretKey
105
- * @param bool|null $bEnableNetwork
106
  * @return int
107
  * @throws \Exception
108
  */
109
- public function fromSite( $masterSiteURL = '', $sSecretKey = '', $bEnableNetwork = null ) {
110
  /** @var Plugin\Options $opts */
111
  $opts = $this->getOptions();
112
  /** @var Plugin\ModCon $mod */
113
  $mod = $this->getMod();
114
  $DP = Services::Data();
115
 
116
- if ( empty( $masterSiteURL ) ) {
117
- $masterSiteURL = $opts->getImportExportMasterImportUrl();
118
  }
119
 
120
- $sOriginalMasterSiteUrl = $opts->getImportExportMasterImportUrl();
121
- $bHadMasterSiteUrl = $opts->hasImportExportMasterImportUrl();
122
- $bCheckKeyFormat = !$bHadMasterSiteUrl;
123
- $sSecretKey = sanitize_key( $sSecretKey );
124
 
125
- if ( $bCheckKeyFormat ) {
126
- if ( empty( $sSecretKey ) ) {
127
  throw new \Exception( 'Empty secret key', 1 );
128
  }
129
- if ( strlen( $sSecretKey ) != 40 ) {
130
  throw new \Exception( "Secret key isn't of the correct format", 2 );
131
  }
132
  }
133
 
134
  // Ensure we have entries for 'scheme' and 'host'
135
- $aUrlParts = wp_parse_url( $masterSiteURL );
136
- $bHasParts = !empty( $aUrlParts )
137
  && count(
138
  array_filter( array_intersect_key(
139
- $aUrlParts,
140
  array_flip( [ 'scheme', 'host' ] )
141
  ) )
142
  ) === 2;
143
- if ( !$bHasParts ) {
144
- throw new \Exception( "Couldn't parse the URL into its parts", 4 );
145
  }
146
- $masterSiteURL = $DP->validateSimpleHttpUrl( $masterSiteURL ); // final clean
147
- if ( empty( $masterSiteURL ) ) {
148
  throw new \Exception( "Couldn't validate the URL.", 4 );
149
  }
150
 
151
  // Begin the handshake process.
152
- $opts->setOpt(
153
- 'importexport_handshake_expires_at',
154
- Services::Request()->ts() + 30
155
- );
156
- $this->getMod()->saveModOptions();
157
 
158
  $data = [
159
  'shield_action' => 'importexport_export',
160
- 'secret' => $sSecretKey,
161
  'url' => Services::WpGeneral()->getHomeUrl()
162
  ];
163
  // Don't send the network setup request if it's the cron.
164
- if ( !is_null( $bEnableNetwork ) && !Services::WpGeneral()->isCron() ) {
165
- $data[ 'network' ] = $bEnableNetwork ? 'Y' : 'N';
166
  }
167
 
168
  { // Make the request
169
- $sFinalUrl = add_query_arg( $data, $masterSiteURL );
170
- $sResponse = Services::HttpRequest()->getContent( $sFinalUrl );
171
- $response = @json_decode( $sResponse, true );
172
-
173
  if ( empty( $response ) ) {
174
  throw new \Exception( "Request failed as we couldn't parse the response.", 5 );
175
  }
@@ -189,25 +180,27 @@ class Import {
189
  throw new \Exception( "Response data was empty", 8 );
190
  }
191
 
192
- $this->processDataImport( $response[ 'data' ], $masterSiteURL );
193
 
194
  // Fix for the overwriting of the Master Site URL with an empty string.
195
  // Only do so if we're not turning it off. i.e on or no-change
196
- if ( is_null( $bEnableNetwork ) ) {
197
- if ( $bHadMasterSiteUrl && !$opts->hasImportExportMasterImportUrl() ) {
198
- $mod->setImportExportMasterImportUrl( $sOriginalMasterSiteUrl );
199
  }
200
  }
201
- elseif ( $bEnableNetwork === true ) {
202
- $mod->setImportExportMasterImportUrl( $masterSiteURL );
203
  $this->getCon()->fireEvent(
204
  'master_url_set',
205
- [ 'audit_params' => [ 'site' => $masterSiteURL ] ]
206
  );
207
  }
208
- elseif ( $bEnableNetwork === false ) {
209
- $mod->setImportExportMasterImportUrl( '' );
210
  }
 
 
211
 
212
  return 0;
213
  }
100
  }
101
 
102
  /**
103
+ * @param bool|null $isEnableNetwork
 
 
104
  * @return int
105
  * @throws \Exception
106
  */
107
+ public function fromSite( string $masterURL = '', string $secretKey = '', $isEnableNetwork = null ) {
108
  /** @var Plugin\Options $opts */
109
  $opts = $this->getOptions();
110
  /** @var Plugin\ModCon $mod */
111
  $mod = $this->getMod();
112
  $DP = Services::Data();
113
 
114
+ if ( empty( $masterURL ) ) {
115
+ $masterURL = $opts->getImportExportMasterImportUrl();
116
  }
117
 
118
+ $originalMasterSiteURL = $opts->getImportExportMasterImportUrl();
119
+ $hadMasterSiteUrl = $opts->hasImportExportMasterImportUrl();
120
+ $secretKey = sanitize_key( $secretKey );
 
121
 
122
+ if ( !$hadMasterSiteUrl ) {
123
+ if ( empty( $secretKey ) ) {
124
  throw new \Exception( 'Empty secret key', 1 );
125
  }
126
+ if ( strlen( $secretKey ) !== 40 ) {
127
  throw new \Exception( "Secret key isn't of the correct format", 2 );
128
  }
129
  }
130
 
131
  // Ensure we have entries for 'scheme' and 'host'
132
+ $urlParts = wp_parse_url( $masterURL );
133
+ $hasParts = !empty( $urlParts )
134
  && count(
135
  array_filter( array_intersect_key(
136
+ $urlParts,
137
  array_flip( [ 'scheme', 'host' ] )
138
  ) )
139
  ) === 2;
140
+ if ( !$hasParts ) {
141
+ throw new \Exception( "Couldn't parse the URL.", 4 );
142
  }
143
+ $masterURL = $DP->validateSimpleHttpUrl( $masterURL ); // final clean
144
+ if ( empty( $masterURL ) ) {
145
  throw new \Exception( "Couldn't validate the URL.", 4 );
146
  }
147
 
148
  // Begin the handshake process.
149
+ $opts->setOpt( 'importexport_handshake_expires_at', Services::Request()->ts() + 30 );
150
+ $mod->saveModOptions();
 
 
 
151
 
152
  $data = [
153
  'shield_action' => 'importexport_export',
154
+ 'secret' => $secretKey,
155
  'url' => Services::WpGeneral()->getHomeUrl()
156
  ];
157
  // Don't send the network setup request if it's the cron.
158
+ if ( !is_null( $isEnableNetwork ) && !Services::WpGeneral()->isCron() ) {
159
+ $data[ 'network' ] = $isEnableNetwork ? 'Y' : 'N';
160
  }
161
 
162
  { // Make the request
163
+ $response = @json_decode( Services::HttpRequest()->getContent( add_query_arg( $data, $masterURL ) ), true );
 
 
 
164
  if ( empty( $response ) ) {
165
  throw new \Exception( "Request failed as we couldn't parse the response.", 5 );
166
  }
180
  throw new \Exception( "Response data was empty", 8 );
181
  }
182
 
183
+ $this->processDataImport( $response[ 'data' ], $masterURL );
184
 
185
  // Fix for the overwriting of the Master Site URL with an empty string.
186
  // Only do so if we're not turning it off. i.e on or no-change
187
+ if ( is_null( $isEnableNetwork ) ) {
188
+ if ( $hadMasterSiteUrl && !$opts->hasImportExportMasterImportUrl() ) {
189
+ $opts->setOpt( 'importexport_masterurl', $originalMasterSiteURL );
190
  }
191
  }
192
+ elseif ( $isEnableNetwork === true ) {
193
+ $opts->setOpt( 'importexport_masterurl', $masterURL );
194
  $this->getCon()->fireEvent(
195
  'master_url_set',
196
+ [ 'audit_params' => [ 'site' => $masterURL ] ]
197
  );
198
  }
199
+ elseif ( $isEnableNetwork === false ) {
200
+ $opts->setOpt( 'importexport_masterurl', '' );
201
  }
202
+ // store & clean the master URL
203
+ $mod->saveModOptions();
204
 
205
  return 0;
206
  }
src/lib/src/Modules/Plugin/Lib/ImportExport/ImportExportController.php CHANGED
@@ -2,25 +2,22 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\ImportExport;
4
 
5
- use FernleafSystems\Utilities\Logic\ExecOnce;
6
  use FernleafSystems\Wordpress\Plugin\Shield;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
9
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Options;
10
  use FernleafSystems\Wordpress\Services\Services;
11
 
12
- class ImportExportController {
13
 
14
- use ExecOnce;
15
- use ModConsumer;
16
  use Shield\Crons\PluginCronsConsumer;
17
 
 
 
 
 
18
  protected function run() {
19
- $con = $this->getCon();
20
- if ( $con->isPremiumActive() ) {
21
- $this->setupHooks();
22
- $this->setupCronHooks();
23
- }
24
  }
25
 
26
  private function setupHooks() {
@@ -28,11 +25,13 @@ class ImportExportController {
28
  /** @var Plugin\Options $opts */
29
  $opts = $this->getOptions();
30
 
 
 
31
  // Cron
32
  add_action( $con->prefix( 'importexport_notify' ), function () {
33
  ( new NotifyWhitelist() )
34
  ->setMod( $this->getMod() )
35
- ->run();
36
  } );
37
 
38
  add_action( 'shield/plugin_activated', function () {
@@ -53,13 +52,13 @@ class ImportExportController {
53
  case 'importexport_export':
54
  ( new Export() )
55
  ->setMod( $this->getMod() )
56
- ->run( Services::Request()->query( 'method' ) );
57
  break;
58
 
59
  case 'importexport_import':
60
  ( new Import() )
61
  ->setMod( $this->getMod() )
62
- ->run( Services::Request()->query( 'method' ) );
63
  break;
64
 
65
  case 'importexport_handshake':
@@ -73,6 +72,53 @@ class ImportExportController {
73
  } );
74
  }
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  private function importFromFlag() {
77
  try {
78
  ( new Import() )
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\ImportExport;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
 
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Options;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
10
+ class ImportExportController extends Shield\Modules\Base\Common\ExecOnceModConsumer {
11
 
 
 
12
  use Shield\Crons\PluginCronsConsumer;
13
 
14
+ protected function canRun() :bool {
15
+ return $this->getOptions()->isOpt( 'importexport_enable', 'Y' ) && $this->getCon()->isPremiumActive();
16
+ }
17
+
18
  protected function run() {
19
+ $this->setupHooks();
20
+ $this->setupCronHooks();
 
 
 
21
  }
22
 
23
  private function setupHooks() {
25
  /** @var Plugin\Options $opts */
26
  $opts = $this->getOptions();
27
 
28
+ $this->getImportExportSecretKey();
29
+
30
  // Cron
31
  add_action( $con->prefix( 'importexport_notify' ), function () {
32
  ( new NotifyWhitelist() )
33
  ->setMod( $this->getMod() )
34
+ ->execute();
35
  } );
36
 
37
  add_action( 'shield/plugin_activated', function () {
52
  case 'importexport_export':
53
  ( new Export() )
54
  ->setMod( $this->getMod() )
55
+ ->run( (string)Services::Request()->query( 'method' ) );
56
  break;
57
 
58
  case 'importexport_import':
59
  ( new Import() )
60
  ->setMod( $this->getMod() )
61
+ ->run( (string)Services::Request()->query( 'method' ) );
62
  break;
63
 
64
  case 'importexport_handshake':
72
  } );
73
  }
74
 
75
+ public function addUrlToImportExportWhitelistUrls( string $url ) {
76
+ /** @var Plugin\Options $opts */
77
+ $opts = $this->getOptions();
78
+ $url = Services::Data()->validateSimpleHttpUrl( $url );
79
+ if ( $url !== false ) {
80
+ $urls = $opts->getImportExportWhitelist();
81
+ $urls[] = $url;
82
+ $opts->setOpt( 'importexport_whitelist', $urls );
83
+ $this->getMod()->saveModOptions();
84
+ }
85
+ }
86
+
87
+ public function removeUrlFromImportExportWhitelistUrls( string $url ) {
88
+ /** @var Plugin\Options $opts */
89
+ $opts = $this->getOptions();
90
+ $url = Services::Data()->validateSimpleHttpUrl( $url );
91
+ if ( $url !== false ) {
92
+ $urls = $opts->getImportExportWhitelist();
93
+ $key = array_search( $url, $urls );
94
+ if ( $key !== false ) {
95
+ unset( $urls[ $key ] );
96
+ }
97
+ $opts->setOpt( 'importexport_whitelist', $urls );
98
+ $this->getMod()->saveModOptions();
99
+ }
100
+ }
101
+
102
+ protected function getImportExportSecretKey() :string {
103
+ /** @var Plugin\Options $opts */
104
+ $opts = $this->getOptions();
105
+ $ID = $opts->getOpt( 'importexport_secretkey', '' );
106
+ if ( empty( $ID ) || $this->isImportExportSecretKeyExpired() ) {
107
+ $ID = sha1( $this->getCon()->getSiteInstallationId().wp_rand( 0, PHP_INT_MAX ) );
108
+ $opts->setOpt( 'importexport_secretkey', $ID )
109
+ ->setOpt( 'importexport_secretkey_expires_at', Services::Request()->ts() + HOUR_IN_SECONDS );
110
+ }
111
+ return $ID;
112
+ }
113
+
114
+ public function verifySecretKey( string $secret ) :bool {
115
+ return !empty( $secret ) && $this->getImportExportSecretKey() == $secret;
116
+ }
117
+
118
+ protected function isImportExportSecretKeyExpired() :bool {
119
+ return Services::Request()->ts() > $this->getOptions()->getOpt( 'importexport_secretkey_expires_at' );
120
+ }
121
+
122
  private function importFromFlag() {
123
  try {
124
  ( new Import() )
src/lib/src/Modules/Plugin/Lib/ImportExport/NotifyWhitelist.php CHANGED
@@ -1,28 +1,23 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\ImportExport;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\ModCon;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
- class NotifyWhitelist {
10
 
11
- use ModConsumer;
 
 
 
12
 
13
- public function run() {
14
- /** @var ModCon $mod */
15
- $mod = $this->getMod();
16
- $oHttpReq = Services::HttpRequest();
17
-
18
- if ( $mod->hasImportExportWhitelistSites() ) {
19
-
20
- $aQuery = [
21
- 'blocking' => false,
22
- 'body' => [ 'shield_action' => 'importexport_updatenotified' ]
23
- ];
24
- foreach ( $mod->getImportExportWhitelist() as $sUrl ) {
25
- $oHttpReq->get( $sUrl, $aQuery );
26
  }
27
 
28
  $this->getCon()->fireEvent( 'import_notify_sent' );
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\ImportExport;
4
 
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Common\ExecOnceModConsumer;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Options;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
+ class NotifyWhitelist extends ExecOnceModConsumer {
10
 
11
+ protected function run() {
12
+ /** @var Options $opts */
13
+ $opts = $this->getOptions();
14
+ if ( !empty( $opts->getImportExportWhitelist() ) ) {
15
 
16
+ foreach ( $opts->getImportExportWhitelist() as $url ) {
17
+ Services::HttpRequest()->get(
18
+ add_query_arg( [ 'shield_action' => 'importexport_updatenotified' ], $url ),
19
+ [ 'blocking' => false ]
20
+ );
 
 
 
 
 
 
 
 
21
  }
22
 
23
  $this->getCon()->fireEvent( 'import_notify_sent' );
src/lib/src/Modules/Plugin/Lib/PluginTelemetry.php CHANGED
@@ -111,7 +111,6 @@ class PluginTelemetry extends ExecOnceModConsumer {
111
 
112
  /**
113
  * @param ModCon $mod
114
- * @return array
115
  */
116
  private function buildOptionsDataForMod( $mod ) :array {
117
  $data = [];
111
 
112
  /**
113
  * @param ModCon $mod
 
114
  */
115
  private function buildOptionsDataForMod( $mod ) :array {
116
  $data = [];
src/lib/src/Modules/Plugin/Lib/TourManager.php CHANGED
@@ -55,7 +55,6 @@ class TourManager {
55
  }
56
 
57
  /**
58
- * @return array
59
  * @throws \Exception
60
  */
61
  private function loadUserTourStates() :array {
55
  }
56
 
57
  /**
 
58
  * @throws \Exception
59
  */
60
  private function loadUserTourStates() :array {
src/lib/src/Modules/Plugin/ModCon.php CHANGED
@@ -58,6 +58,19 @@ class ModCon extends BaseShield\ModCon {
58
  $this->setVisitorIpSource();
59
  }
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  protected function preProcessOptions() {
62
  ( new Lib\Captcha\CheckCaptchaSettings() )
63
  ->setMod( $this )
@@ -77,28 +90,13 @@ class ModCon extends BaseShield\ModCon {
77
  }
78
  }
79
 
80
- public function onPluginShutdown() {
81
- if ( !$this->getCon()->plugin_deleting ) {
82
- $preferred = Services::IP()->getIpDetector()->getLastSuccessfulSource();
83
- if ( !empty( $preferred ) ) {
84
- $this->getOptions()->setOpt( 'last_ip_detect_source', $preferred );
85
- }
86
- }
87
- parent::onPluginShutdown();
88
- }
89
-
90
- public function onWpInit() {
91
- parent::onWpInit();
92
- $this->getImportExportSecretKey();
93
- }
94
-
95
  /**
96
  * Forcefully sets preferred Visitor IP source in the Data component for use throughout the plugin
97
  */
98
  private function setVisitorIpSource() {
99
  /** @var Options $opts */
100
  $opts = $this->getOptions();
101
- if ( !$opts->isIpSourceAutoDetect() ) {
102
  Services::IP()->setIpDetector(
103
  ( new VisitorIpDetection() )->setPreferredSource( $opts->getIpSource() )
104
  );
@@ -112,6 +110,8 @@ class ModCon extends BaseShield\ModCon {
112
  ->setMod( $this )
113
  ->toFile();
114
  break;
 
 
115
  }
116
  }
117
 
@@ -143,43 +143,30 @@ class ModCon extends BaseShield\ModCon {
143
  }
144
 
145
  /**
146
- * @return bool
147
  * @throws \Exception
148
  */
149
  public function canSiteLoopback() :bool {
150
- $canLoopback = false;
151
  if ( class_exists( '\WP_Site_Health' ) && method_exists( '\WP_Site_Health', 'get_instance' ) ) {
152
- $canLoopback = \WP_Site_Health::get_instance()->get_test_loopback_requests()[ 'status' ] === 'good';
153
  }
154
- if ( !$canLoopback ) {
155
- $canLoopback = Services::HttpRequest()->post( site_url( 'wp-cron.php' ), [
156
  'timeout' => 10
157
  ] );
158
  }
159
- return $canLoopback;
160
  }
161
 
 
 
 
162
  public function getActivePluginFeatures() :array {
163
- $features = $this->getOptions()->getDef( 'active_plugin_features' );
164
-
165
- $available = [];
166
- if ( is_array( $features ) ) {
167
-
168
- foreach ( $features as $feature ) {
169
- if ( isset( $feature[ 'hidden' ] ) && $feature[ 'hidden' ] ) {
170
- continue;
171
- }
172
- $available[ $feature[ 'slug' ] ] = $feature;
173
- }
174
- }
175
- return $available;
176
  }
177
 
178
  public function getLinkToTrackingDataDump() :string {
179
- return add_query_arg(
180
- [ 'shield_action' => 'dump_tracking_data' ],
181
- Services::WpGeneral()->getAdminUrl()
182
- );
183
  }
184
 
185
  public function getPluginReportEmail() :string {
@@ -195,13 +182,13 @@ class ModCon extends BaseShield\ModCon {
195
  * This is the point where you would want to do any options verification
196
  */
197
  protected function doPrePluginOptionsSave() {
198
- /** @var Options $oOpts */
199
- $oOpts = $this->getOptions();
200
 
201
  $this->storeRealInstallDate();
202
 
203
- if ( $oOpts->isTrackingEnabled() && !$oOpts->isTrackingPermissionSet() ) {
204
- $oOpts->setOpt( 'tracking_permission_set_at', Services::Request()->ts() );
205
  }
206
 
207
  $this->cleanRecaptchaKey( 'google_recaptcha_site_key' );
@@ -279,9 +266,9 @@ class ModCon extends BaseShield\ModCon {
279
  $WP = Services::WpGeneral();
280
  $ts = Services::Request()->ts();
281
 
282
- $sOptKey = $this->getCon()->prefixOption( 'install_date' );
283
 
284
- $nWpDate = $WP->getOption( $sOptKey );
285
  if ( empty( $nWpDate ) ) {
286
  $nWpDate = $ts;
287
  }
@@ -292,7 +279,7 @@ class ModCon extends BaseShield\ModCon {
292
  }
293
 
294
  $nFinal = min( $nPluginDate, $nWpDate );
295
- $WP->updateOption( $sOptKey, $nFinal );
296
  $this->getOptions()->setOpt( 'installation_time', $nPluginDate );
297
 
298
  return $nFinal;
@@ -308,7 +295,7 @@ class ModCon extends BaseShield\ModCon {
308
  if ( $nSpacePos !== false ) {
309
  $sCaptchaKey = substr( $sCaptchaKey, 0, $nSpacePos + 1 ); // cut off the string if there's spaces
310
  }
311
- $sCaptchaKey = preg_replace( '#[^0-9a-zA-Z_-]#', '', $sCaptchaKey ); // restrict character set
312
  // if ( strlen( $sCaptchaKey ) != 40 ) {
313
  // $sCaptchaKey = ''; // need to verify length is 40.
314
  // }
@@ -363,126 +350,37 @@ class ModCon extends BaseShield\ModCon {
363
  );
364
  }
365
 
366
- public function hasImportExportWhitelistSites() :bool {
367
- return count( $this->getImportExportWhitelist() ) > 0;
368
- }
369
-
370
- /**
371
- * @return string[]
372
- */
373
- public function getImportExportWhitelist() :array {
374
- $list = $this->getOptions()->getOpt( 'importexport_whitelist', [] );
375
- return is_array( $list ) ? $list : [];
376
- }
377
-
378
- /**
379
- * @return string
380
- */
381
- protected function getImportExportSecretKey() {
382
  $opts = $this->getOptions();
383
- $ID = $opts->getOpt( 'importexport_secretkey', '' );
384
- if ( empty( $ID ) || $this->isImportExportSecretKeyExpired() ) {
385
- $ID = sha1( $this->getCon()->getSiteInstallationId().wp_rand( 0, PHP_INT_MAX ) );
386
- $opts->setOpt( 'importexport_secretkey', $ID )
387
- ->setOpt( 'importexport_secretkey_expires_at', Services::Request()->ts() + HOUR_IN_SECONDS );
388
- }
389
- return $ID;
390
- }
391
-
392
- protected function isImportExportSecretKeyExpired() :bool {
393
- return Services::Request()->ts() >
394
- $this->getOptions()->getOpt( 'importexport_secretkey_expires_at' );
395
- }
396
-
397
- public function isImportExportWhitelistNotify() :bool {
398
- return $this->getOptions()->isOpt( 'importexport_whitelist_notify', 'Y' );
399
- }
400
-
401
- /**
402
- * @param string $sUrl
403
- * @return $this
404
- */
405
- public function addUrlToImportExportWhitelistUrls( $sUrl ) {
406
- $sUrl = Services::Data()->validateSimpleHttpUrl( $sUrl );
407
- if ( $sUrl !== false ) {
408
- $aWhitelistUrls = $this->getImportExportWhitelist();
409
- $aWhitelistUrls[] = $sUrl;
410
- $this->getOptions()->setOpt( 'importexport_whitelist', $aWhitelistUrls );
411
- $this->saveModOptions();
412
- }
413
- return $this;
414
- }
415
-
416
- /**
417
- * @param string $url
418
- * @return $this
419
- */
420
- public function removeUrlFromImportExportWhitelistUrls( $url ) {
421
- $url = Services::Data()->validateSimpleHttpUrl( $url );
422
- if ( $url !== false ) {
423
- $aWhitelistUrls = $this->getImportExportWhitelist();
424
- $sKey = array_search( $url, $aWhitelistUrls );
425
- if ( $sKey !== false ) {
426
- unset( $aWhitelistUrls[ $sKey ] );
427
- }
428
- $this->getOptions()->setOpt( 'importexport_whitelist', $aWhitelistUrls );
429
- $this->saveModOptions();
430
- }
431
- return $this;
432
- }
433
-
434
- /**
435
- * @param string $sKey
436
- * @return bool
437
- */
438
- public function isImportExportSecretKey( $sKey ) :bool {
439
- return !empty( $sKey ) && $this->getImportExportSecretKey() == $sKey;
440
- }
441
-
442
- protected function cleanImportExportWhitelistUrls() {
443
- $oDP = Services::Data();
444
-
445
- $aCleaned = [];
446
- $aWhitelistUrls = $this->getImportExportWhitelist();
447
- foreach ( $aWhitelistUrls as $nKey => $sUrl ) {
448
 
449
- $sUrl = $oDP->validateSimpleHttpUrl( $sUrl );
450
- if ( $sUrl !== false ) {
451
- $aCleaned[] = $sUrl;
452
  }
453
  }
454
- $this->getOptions()->setOpt( 'importexport_whitelist', array_unique( $aCleaned ) );
455
- }
456
-
457
- protected function cleanImportExportMasterImportUrl() {
458
- /** @var Options $oOpts */
459
- $oOpts = $this->getOptions();
460
- $url = Services::Data()->validateSimpleHttpUrl( $oOpts->getImportExportMasterImportUrl() );
461
- if ( $url === false ) {
462
- $url = '';
463
- }
464
- $this->getOptions()->setOpt( 'importexport_masterurl', $url );
465
  }
466
 
467
- /**
468
- * @param string $url
469
- * @return $this
470
- */
471
- public function setImportExportMasterImportUrl( $url ) {
472
- $this->getOptions()->setOpt( 'importexport_masterurl', $url ); //saving will clean the URL
473
- return $this->saveModOptions();
474
  }
475
 
476
  /**
477
- * @param string $sId
478
- * @return bool
479
  */
480
- protected function isValidInstallId( $sId ) :bool {
481
- return !empty( $sId ) && is_string( $sId ) && strlen( $sId ) == 40;
482
  }
483
 
484
  public function isXmlrpcBypass() :bool {
485
- return $this->getOptions()->isOpt( 'enable_xmlrpc_compatibility', 'Y' );
486
  }
487
 
488
  public function getCanAdminNotes() :bool {
@@ -491,6 +389,7 @@ class ModCon extends BaseShield\ModCon {
491
 
492
  public function getScriptLocalisations() :array {
493
  $locals = parent::getScriptLocalisations();
 
494
 
495
  $tourManager = $this->getTourManager();
496
  $locals[] = [
@@ -507,13 +406,51 @@ class ModCon extends BaseShield\ModCon {
507
  'plugin',
508
  'icwp_wpsf_vars_plugin',
509
  [
510
- 'strings' => [
 
 
 
 
 
 
 
 
 
 
 
511
  'downloading_file' => __( 'Downloading file, please wait...', 'wp-simple-firewall' ),
512
  'downloading_file_problem' => __( 'There was a problem downloading the file.', 'wp-simple-firewall' ),
513
  ],
514
  ]
515
  ];
516
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
  return $locals;
518
  }
519
 
@@ -541,7 +478,21 @@ class ModCon extends BaseShield\ModCon {
541
  return $this->oCaptchaEnqueue;
542
  }
543
 
 
 
 
 
 
 
544
  protected function getNamespaceBase() :string {
545
  return 'Plugin';
546
  }
 
 
 
 
 
 
 
 
547
  }
58
  $this->setVisitorIpSource();
59
  }
60
 
61
+ protected function enumRuleBuilders() :array {
62
+ return [
63
+ Rules\Build\RequestStatusIsAdmin::class,
64
+ Rules\Build\RequestStatusIsAjax::class,
65
+ Rules\Build\RequestStatusIsXmlRpc::class,
66
+ Rules\Build\RequestStatusIsWpCli::class,
67
+ Rules\Build\IsServerLoopback::class,
68
+ Rules\Build\IsTrustedBot::class,
69
+ Rules\Build\IsPublicWebRequest::class,
70
+ Rules\Build\RequestBypassesAllRestrictions::class,
71
+ ];
72
+ }
73
+
74
  protected function preProcessOptions() {
75
  ( new Lib\Captcha\CheckCaptchaSettings() )
76
  ->setMod( $this )
90
  }
91
  }
92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  /**
94
  * Forcefully sets preferred Visitor IP source in the Data component for use throughout the plugin
95
  */
96
  private function setVisitorIpSource() {
97
  /** @var Options $opts */
98
  $opts = $this->getOptions();
99
+ if ( $opts->getIpSource() !== 'AUTO_DETECT_IP' ) {
100
  Services::IP()->setIpDetector(
101
  ( new VisitorIpDetection() )->setPreferredSource( $opts->getIpSource() )
102
  );
110
  ->setMod( $this )
111
  ->toFile();
112
  break;
113
+ default:
114
+ break;
115
  }
116
  }
117
 
143
  }
144
 
145
  /**
 
146
  * @throws \Exception
147
  */
148
  public function canSiteLoopback() :bool {
149
+ $can = false;
150
  if ( class_exists( '\WP_Site_Health' ) && method_exists( '\WP_Site_Health', 'get_instance' ) ) {
151
+ $can = \WP_Site_Health::get_instance()->get_test_loopback_requests()[ 'status' ] === 'good';
152
  }
153
+ if ( !$can ) {
154
+ $can = Services::HttpRequest()->post( site_url( 'wp-cron.php' ), [
155
  'timeout' => 10
156
  ] );
157
  }
158
+ return $can;
159
  }
160
 
161
+ /**
162
+ * @deprecated 15.0
163
+ */
164
  public function getActivePluginFeatures() :array {
165
+ return $this->getOptions()->getDef( 'active_plugin_features' );
 
 
 
 
 
 
 
 
 
 
 
 
166
  }
167
 
168
  public function getLinkToTrackingDataDump() :string {
169
+ return add_query_arg( [ 'shield_action' => 'dump_tracking_data' ], Services::WpGeneral()->getAdminUrl() );
 
 
 
170
  }
171
 
172
  public function getPluginReportEmail() :string {
182
  * This is the point where you would want to do any options verification
183
  */
184
  protected function doPrePluginOptionsSave() {
185
+ /** @var Options $opts */
186
+ $opts = $this->getOptions();
187
 
188
  $this->storeRealInstallDate();
189
 
190
+ if ( $opts->isTrackingEnabled() && !$opts->isTrackingPermissionSet() ) {
191
+ $opts->setOpt( 'tracking_permission_set_at', Services::Request()->ts() );
192
  }
193
 
194
  $this->cleanRecaptchaKey( 'google_recaptcha_site_key' );
266
  $WP = Services::WpGeneral();
267
  $ts = Services::Request()->ts();
268
 
269
+ $key = $this->getCon()->prefixOption( 'install_date' );
270
 
271
+ $nWpDate = $WP->getOption( $key );
272
  if ( empty( $nWpDate ) ) {
273
  $nWpDate = $ts;
274
  }
279
  }
280
 
281
  $nFinal = min( $nPluginDate, $nWpDate );
282
+ $WP->updateOption( $key, $nFinal );
283
  $this->getOptions()->setOpt( 'installation_time', $nPluginDate );
284
 
285
  return $nFinal;
295
  if ( $nSpacePos !== false ) {
296
  $sCaptchaKey = substr( $sCaptchaKey, 0, $nSpacePos + 1 ); // cut off the string if there's spaces
297
  }
298
+ $sCaptchaKey = preg_replace( '#[^\da-zA-Z_-]#', '', $sCaptchaKey ); // restrict character set
299
  // if ( strlen( $sCaptchaKey ) != 40 ) {
300
  // $sCaptchaKey = ''; // need to verify length is 40.
301
  // }
350
  );
351
  }
352
 
353
+ private function cleanImportExportWhitelistUrls() {
354
+ /** @var Options $opts */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
355
  $opts = $this->getOptions();
356
+ $cleaned = [];
357
+ $whitelist = $opts->getImportExportWhitelist();
358
+ foreach ( $whitelist as $url ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
 
360
+ $url = Services::Data()->validateSimpleHttpUrl( $url );
361
+ if ( $url !== false ) {
362
+ $cleaned[] = $url;
363
  }
364
  }
365
+ $opts->setOpt( 'importexport_whitelist', array_unique( $cleaned ) );
 
 
 
 
 
 
 
 
 
 
366
  }
367
 
368
+ private function cleanImportExportMasterImportUrl() {
369
+ /** @var Options $opts */
370
+ $opts = $this->getOptions();
371
+ $url = Services::Data()->validateSimpleHttpUrl( $opts->getImportExportMasterImportUrl() );
372
+ $opts->setOpt( 'importexport_masterurl', $url === false ? '' : $url );
 
 
373
  }
374
 
375
  /**
376
+ * @param string $id
 
377
  */
378
+ protected function isValidInstallId( $id ) :bool {
379
+ return !empty( $id ) && is_string( $id ) && strlen( $id ) == 40;
380
  }
381
 
382
  public function isXmlrpcBypass() :bool {
383
+ return (bool)apply_filters( 'shield/allow_xmlrpc_login_bypass', false );
384
  }
385
 
386
  public function getCanAdminNotes() :bool {
389
 
390
  public function getScriptLocalisations() :array {
391
  $locals = parent::getScriptLocalisations();
392
+ $con = $this->getCon();
393
 
394
  $tourManager = $this->getTourManager();
395
  $locals[] = [
406
  'plugin',
407
  'icwp_wpsf_vars_plugin',
408
  [
409
+ 'components' => [
410
+ 'helpscout' => [
411
+ 'beacon_id' => $con->isPremiumActive() ? 'db2ff886-2329-4029-9452-44587df92c8c' : 'aded6929-af83-452d-993f-a60c03b46568',
412
+ 'visible' => $con->isModulePage()
413
+ ],
414
+ 'mod_options' => [
415
+ 'ajax' => [
416
+ 'mod_options_save' => $this->getAjaxActionData( 'mod_options_save' )
417
+ ]
418
+ ],
419
+ ],
420
+ 'strings' => [
421
  'downloading_file' => __( 'Downloading file, please wait...', 'wp-simple-firewall' ),
422
  'downloading_file_problem' => __( 'There was a problem downloading the file.', 'wp-simple-firewall' ),
423
  ],
424
  ]
425
  ];
426
 
427
+ $locals[] = [
428
+ 'global-plugin',
429
+ 'icwp_wpsf_vars_globalplugin',
430
+ [
431
+ 'vars' => [
432
+ 'dashboard_widget' => [
433
+ 'ajax' => [
434
+ 'render_dashboard_widget' => $this->getAjaxActionData( 'render_dashboard_widget' )
435
+ ]
436
+ ]
437
+ ],
438
+ ]
439
+ ];
440
+
441
+ $opts = $this->getOptions();
442
+ if ( Services::Request()->ts() - $opts->getOpt( 'ipdetect_at' ) > WEEK_IN_SECONDS*4 ) {
443
+ $opts->setOpt( 'ipdetect_at', Services::Request()->ts() );
444
+ $locals[] = [
445
+ 'shield/ip_detect',
446
+ 'icwp_wpsf_vars_ipdetect',
447
+ [
448
+ 'url' => 'https://net.getshieldsecurity.com/wp-json/apto-snapi/v2/tools/what_is_my_ip',
449
+ 'ajax' => $this->getAjaxActionData( 'ipdetect' ),
450
+ ]
451
+ ];
452
+ }
453
+
454
  return $locals;
455
  }
456
 
478
  return $this->oCaptchaEnqueue;
479
  }
480
 
481
+ public function isModOptEnabled() :bool {
482
+ /** @var Options $opts */
483
+ $opts = $this->getOptions();
484
+ return !$opts->isPluginGloballyDisabled();
485
+ }
486
+
487
  protected function getNamespaceBase() :string {
488
  return 'Plugin';
489
  }
490
+
491
+ /**
492
+ * @deprecated 15.0
493
+ */
494
+ public function getImportExportWhitelist() :array {
495
+ $list = $this->getOptions()->getOpt( 'importexport_whitelist', [] );
496
+ return is_array( $list ) ? $list : [];
497
+ }
498
  }
src/lib/src/Modules/Plugin/Options.php CHANGED
@@ -16,28 +16,29 @@ class Options extends BaseShield\Options {
16
  ];
17
  }
18
 
19
- /**
20
- * @return string
21
- */
22
- public function getImportExportMasterImportUrl() {
23
- return $this->getOpt( 'importexport_masterurl', '' );
24
  }
25
 
26
  /**
27
- * @return string
28
  */
29
- public function getIpSource() {
30
- return $this->getOpt( 'visitor_address_source' );
 
 
 
 
 
31
  }
32
 
33
- /**
34
- * @return bool
35
- */
36
  public function hasImportExportMasterImportUrl() :bool {
37
- $sMaster = $this->getImportExportMasterImportUrl();
38
- return !empty( $sMaster );
39
  }
40
 
 
 
 
41
  public function isIpSourceAutoDetect() :bool {
42
  return $this->getIpSource() == 'AUTO_DETECT_IP';
43
  }
@@ -59,14 +60,6 @@ class Options extends BaseShield\Options {
59
  return !$this->isOpt( 'tracking_permission_set_at', 0 );
60
  }
61
 
62
- /**
63
- * @return string[]
64
- */
65
- public function getImportExportWhitelist() :array {
66
- $whitelist = $this->getOpt( 'importexport_whitelist', [] );
67
- return is_array( $whitelist ) ? $whitelist : [];
68
- }
69
-
70
  public function isEnabledShieldNET() :bool {
71
  return $this->isOpt( 'enable_shieldnet', 'Y' );
72
  }
@@ -80,10 +73,9 @@ class Options extends BaseShield\Options {
80
  }
81
 
82
  /**
83
- * @param string $sSource
84
  * @return $this
85
  */
86
- public function setVisitorAddressSource( $sSource ) {
87
- return $this->setOpt( 'visitor_address_source', $sSource );
88
  }
89
  }
16
  ];
17
  }
18
 
19
+ public function getImportExportMasterImportUrl() :string {
20
+ return (string)$this->getOpt( 'importexport_masterurl', '' );
 
 
 
21
  }
22
 
23
  /**
24
+ * @return string[]
25
  */
26
+ public function getImportExportWhitelist() :array {
27
+ $list = $this->getOpt( 'importexport_whitelist', [] );
28
+ return is_array( $list ) ? $list : [];
29
+ }
30
+
31
+ public function getIpSource() :string {
32
+ return (string)$this->getOpt( 'visitor_address_source' );
33
  }
34
 
 
 
 
35
  public function hasImportExportMasterImportUrl() :bool {
36
+ return !empty( $this->getImportExportMasterImportUrl() );
 
37
  }
38
 
39
+ /**
40
+ * @deprecated 15.0
41
+ */
42
  public function isIpSourceAutoDetect() :bool {
43
  return $this->getIpSource() == 'AUTO_DETECT_IP';
44
  }
60
  return !$this->isOpt( 'tracking_permission_set_at', 0 );
61
  }
62
 
 
 
 
 
 
 
 
 
63
  public function isEnabledShieldNET() :bool {
64
  return $this->isOpt( 'enable_shieldnet', 'Y' );
65
  }
73
  }
74
 
75
  /**
 
76
  * @return $this
77
  */
78
+ public function setVisitorAddressSource( string $source ) {
79
+ return $this->setOpt( 'visitor_address_source', $source );
80
  }
81
  }
src/lib/src/Modules/Plugin/Processor.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
 
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\PluginTelemetry;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Options\CleanStorage;
8
  use FernleafSystems\Wordpress\Services\Services;
@@ -13,8 +14,6 @@ class Processor extends BaseShield\Processor {
13
  $con = $this->getCon();
14
  /** @var ModCon $mod */
15
  $mod = $this->getMod();
16
- /** @var Options $opts */
17
- $opts = $this->getOptions();
18
 
19
  $this->removePluginConflicts();
20
  ( new Lib\OverrideLocale() )
@@ -27,37 +26,24 @@ class Processor extends BaseShield\Processor {
27
  ( new PluginTelemetry() )
28
  ->setMod( $this->getMod() )
29
  ->execute();
 
 
 
30
 
31
- if ( $opts->isOpt( 'importexport_enable', 'Y' ) ) {
32
  $mod->getImpExpController()->execute();
33
  }
34
 
35
  add_filter( $con->prefix( 'delete_on_deactivate' ), function ( $isDelete ) {
36
  return $isDelete || $this->getOptions()->isOpt( 'delete_on_deactivate', 'Y' );
37
  } );
38
-
39
- add_action( $con->prefix( 'dashboard_widget_content' ), function () {
40
- $this->printDashboardWidget();
41
- }, 11 );
42
  }
43
 
 
 
 
44
  private function printDashboardWidget() {
45
- $con = $this->getCon();
46
- /** @var Options $opts */
47
- $opts = $this->getOptions();
48
- $labels = $con->getLabels();
49
-
50
- echo $this->getMod()->renderTemplate(
51
- 'snippets/widget_dashboard_plugin.php',
52
- [
53
- 'install_days' => sprintf( __( 'Days Installed: %s', 'wp-simple-firewall' ), $opts->getInstallationDays() ),
54
- 'footer' => sprintf( __( '%s is provided by %s', 'wp-simple-firewall' ), $con->getHumanName(),
55
- sprintf( '<a href="%s" target="_blank">%s</a>', $labels[ 'AuthorURI' ], $labels[ 'Author' ] )
56
- ),
57
- 'ip_address' => sprintf( __( 'Your IP address is: %s', 'wp-simple-firewall' ),
58
- Services::IP()->getRequestIp() )
59
- ]
60
- );
61
  }
62
 
63
  public function runDailyCron() {
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\AllowBetaUpgrades;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\PluginTelemetry;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Options\CleanStorage;
9
  use FernleafSystems\Wordpress\Services\Services;
14
  $con = $this->getCon();
15
  /** @var ModCon $mod */
16
  $mod = $this->getMod();
 
 
17
 
18
  $this->removePluginConflicts();
19
  ( new Lib\OverrideLocale() )
26
  ( new PluginTelemetry() )
27
  ->setMod( $this->getMod() )
28
  ->execute();
29
+ ( new AllowBetaUpgrades() )
30
+ ->setMod( $this->getMod() )
31
+ ->execute();
32
 
33
+ if ( $this->getOptions()->isOpt( 'importexport_enable', 'Y' ) ) {
34
  $mod->getImpExpController()->execute();
35
  }
36
 
37
  add_filter( $con->prefix( 'delete_on_deactivate' ), function ( $isDelete ) {
38
  return $isDelete || $this->getOptions()->isOpt( 'delete_on_deactivate', 'Y' );
39
  } );
 
 
 
 
40
  }
41
 
42
+ /**
43
+ * @deprecated 15.0
44
+ */
45
  private function printDashboardWidget() {
46
+ echo '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  }
48
 
49
  public function runDailyCron() {
src/lib/src/Modules/Plugin/Rules/Build/IsPublicWebRequest.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
7
+ Build\BuildRuleCoreShieldBase,
8
+ Conditions
9
+ };
10
+
11
+ class IsPublicWebRequest extends BuildRuleCoreShieldBase {
12
+
13
+ const SLUG = 'shield/is_public_web_request';
14
+
15
+ protected function getName() :string {
16
+ return 'Is Public Web Request';
17
+ }
18
+
19
+ protected function getDescription() :string {
20
+ return 'Is a public web request.';
21
+ }
22
+
23
+ protected function getConditions() :array {
24
+ return [
25
+ 'logic' => static::LOGIC_AND,
26
+ 'group' => [
27
+ [
28
+ 'condition' => Conditions\WpIsWpcli::SLUG,
29
+ 'invert_match' => true,
30
+ ],
31
+ [
32
+ 'condition' => Conditions\IsIpValidPublic::SLUG,
33
+ ],
34
+ [
35
+ 'rule' => IsServerLoopback::SLUG,
36
+ 'invert_match' => true,
37
+ ],
38
+ ]
39
+ ];
40
+ }
41
+ }
src/lib/src/Modules/Plugin/Rules/Build/IsServerLoopback.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
7
+ Build\BuildRuleCoreShieldBase,
8
+ Conditions
9
+ };
10
+ use FernleafSystems\Wordpress\Services\Services;
11
+
12
+ class IsServerLoopback extends BuildRuleCoreShieldBase {
13
+
14
+ const SLUG = 'shield/is_server_loopback';
15
+
16
+ protected function getName() :string {
17
+ return 'Is Server Loopback';
18
+ }
19
+
20
+ protected function getDescription() :string {
21
+ return 'Is Server Loopback request.';
22
+ }
23
+
24
+ protected function getConditions() :array {
25
+ return [
26
+ 'logic' => static::LOGIC_AND,
27
+ 'group' => [
28
+ [
29
+ 'condition' => Conditions\MatchRequestIp::SLUG,
30
+ 'params' => [
31
+ 'match_ips' => Services::IP()->getServerPublicIPs(),
32
+ ],
33
+ ],
34
+ ]
35
+ ];
36
+ }
37
+ }
src/lib/src/Modules/Plugin/Rules/Build/IsTrustedBot.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
7
+ Build\BuildRuleCoreShieldBase,
8
+ Conditions,
9
+ Responses
10
+ };
11
+ use FernleafSystems\Wordpress\Services\Utilities\Net\IpID;
12
+
13
+ class IsTrustedBot extends BuildRuleCoreShieldBase {
14
+
15
+ const SLUG = 'shield/is_trusted_bot';
16
+
17
+ protected function getName() :string {
18
+ return 'Is Trusted Bot';
19
+ }
20
+
21
+ protected function getDescription() :string {
22
+ return 'Test whether the visitor is a trusted bot.';
23
+ }
24
+
25
+ protected function getConditions() :array {
26
+ return [
27
+ 'logic' => static::LOGIC_AND,
28
+ 'group' => [
29
+ [
30
+ 'rule' => IsServerLoopback::SLUG,
31
+ 'invert_match' => true,
32
+ ],
33
+ [
34
+ 'condition' => Conditions\MatchRequestIpIdentity::SLUG,
35
+ 'params' => [
36
+ 'match_not_ip_ids' => (array)apply_filters( 'shield/untrusted_service_providers', [
37
+ IpID::UNKNOWN,
38
+ IpID::THIS_SERVER,
39
+ IpID::VISITOR,
40
+ ] ),
41
+ ],
42
+ ],
43
+ ]
44
+ ];
45
+ }
46
+
47
+ protected function getResponses() :array {
48
+ return [
49
+ [
50
+ 'response' => Responses\SetIsTrustedBot::SLUG,
51
+ ],
52
+ ];
53
+ }
54
+ }
src/lib/src/Modules/Plugin/Rules/Build/RequestBypassesAllRestrictions.php ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
7
+ Build\BuildRuleCoreShieldBase,
8
+ Conditions,
9
+ Responses
10
+ };
11
+
12
+ class RequestBypassesAllRestrictions extends BuildRuleCoreShieldBase {
13
+
14
+ const SLUG = 'shield/request_bypasses_all_restrictions';
15
+
16
+ protected function getName() :string {
17
+ return 'A Request That Bypasses Restrictions';
18
+ }
19
+
20
+ protected function getDescription() :string {
21
+ return 'Does the request bypass all plugin restrictions.';
22
+ }
23
+
24
+ protected function getConditions() :array {
25
+ return [
26
+ 'logic' => static::LOGIC_OR,
27
+ 'group' => [
28
+ [
29
+ 'condition' => Conditions\IsForceOff::SLUG,
30
+ ],
31
+ [
32
+ 'rule' => IsPublicWebRequest::SLUG,
33
+ 'invert_match' => true,
34
+ ],
35
+ [
36
+ 'rule' => IsTrustedBot::SLUG,
37
+ ],
38
+ [
39
+ 'rule' => Shield\Modules\IPs\Rules\Build\IsPathWhitelisted::SLUG,
40
+ ],
41
+ [
42
+ 'rule' => Shield\Modules\IPs\Rules\Build\IpWhitelisted::SLUG,
43
+ ],
44
+ ]
45
+ ];
46
+ }
47
+
48
+ protected function getResponses() :array {
49
+ return [
50
+ [
51
+ 'response' => Responses\SetRequestBypassesAllRestrictions::SLUG,
52
+ ],
53
+ ];
54
+ }
55
+
56
+ protected function isInstantExecResponse() :bool {
57
+ return true;
58
+ }
59
+ }
src/lib/src/Modules/Plugin/Rules/Build/RequestStatusBase.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Build\BuildRuleCoreShieldBase;
6
+
7
+ abstract class RequestStatusBase extends BuildRuleCoreShieldBase {
8
+
9
+ protected function getDescription() :string {
10
+ return sprintf( '%s - %s', __( 'Request Status' ), $this->getName() );
11
+ }
12
+ }
src/lib/src/Modules/Plugin/Rules/Build/RequestStatusIsAdmin.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
7
+ Conditions
8
+ };
9
+
10
+ class RequestStatusIsAdmin extends RequestStatusBase {
11
+
12
+ const SLUG = 'shield/request_status_is_admin';
13
+
14
+ protected function getName() :string {
15
+ return 'Is Admin?';
16
+ }
17
+
18
+ protected function getConditions() :array {
19
+ return [
20
+ 'logic' => static::LOGIC_OR,
21
+ 'group' => [
22
+ [
23
+ 'condition' => Conditions\WpIsAdmin::SLUG,
24
+ ],
25
+ ]
26
+ ];
27
+ }
28
+ }
src/lib/src/Modules/Plugin/Rules/Build/RequestStatusIsAjax.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
7
+ Conditions
8
+ };
9
+
10
+ class RequestStatusIsAjax extends RequestStatusBase {
11
+
12
+ const SLUG = 'shield/request_status_is_ajax';
13
+
14
+ protected function getName() :string {
15
+ return 'Is AJAX?';
16
+ }
17
+
18
+ protected function getConditions() :array {
19
+ return [
20
+ 'logic' => static::LOGIC_OR,
21
+ 'group' => [
22
+ [
23
+ 'condition' => Conditions\WpIsAjax::SLUG,
24
+ ],
25
+ ]
26
+ ];
27
+ }
28
+ }
src/lib/src/Modules/Plugin/Rules/Build/RequestStatusIsWpCli.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
7
+ Conditions
8
+ };
9
+
10
+ class RequestStatusIsWpCli extends RequestStatusBase {
11
+
12
+ const SLUG = 'shield/request_status_is_wpcli';
13
+
14
+ protected function getName() :string {
15
+ return 'Is WP-CLI?';
16
+ }
17
+
18
+ protected function getConditions() :array {
19
+ return [
20
+ 'logic' => static::LOGIC_OR,
21
+ 'group' => [
22
+ [
23
+ 'condition' => Conditions\WpIsWpcli::SLUG,
24
+ ],
25
+ ]
26
+ ];
27
+ }
28
+ }
src/lib/src/Modules/Plugin/Rules/Build/RequestStatusIsXmlRpc.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
7
+ Conditions
8
+ };
9
+
10
+ class RequestStatusIsXmlRpc extends RequestStatusBase {
11
+
12
+ const SLUG = 'shield/request_status_is_xmlrpc';
13
+
14
+ protected function getName() :string {
15
+ return 'Is XML-RPC?';
16
+ }
17
+
18
+ protected function getConditions() :array {
19
+ return [
20
+ 'logic' => static::LOGIC_OR,
21
+ 'group' => [
22
+ [
23
+ 'condition' => Conditions\WpIsXmlrpc::SLUG,
24
+ ],
25
+ ]
26
+ ];
27
+ }
28
+ }
src/lib/src/Modules/Plugin/Strings.php CHANGED
@@ -1,4 +1,4 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
4
 
@@ -275,8 +275,11 @@ class Strings extends Base\Strings {
275
 
276
  case 'enable_upgrade_admin_notice' :
277
  $name = __( 'In-Plugin Notices', 'wp-simple-firewall' );
278
- $summary = __( 'Display Plugin Specific Notices', 'wp-simple-firewall' );
279
- $desc = __( 'Disable this option to hide certain plugin admin notices about available updates and post-update notices.', 'wp-simple-firewall' );
 
 
 
280
  break;
281
 
282
  case 'display_plugin_badge' :
@@ -314,12 +317,6 @@ class Strings extends Base\Strings {
314
  ];
315
  break;
316
 
317
- case 'enable_xmlrpc_compatibility' :
318
- $name = __( 'XML-RPC Compatibility', 'wp-simple-firewall' );
319
- $summary = __( 'Allow Login Through XML-RPC To Bypass Accounts Management Rules', 'wp-simple-firewall' );
320
- $desc = __( 'Enable this if you need XML-RPC functionality e.g. if you use the WordPress iPhone/Android App.', 'wp-simple-firewall' );
321
- break;
322
-
323
  case 'importexport_enable' :
324
  $name = __( 'Allow Import/Export', 'wp-simple-firewall' );
325
  $summary = __( 'Allow Import And Export Of Options On This Site', 'wp-simple-firewall' );
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin;
4
 
275
 
276
  case 'enable_upgrade_admin_notice' :
277
  $name = __( 'In-Plugin Notices', 'wp-simple-firewall' );
278
+ $summary = __( 'Display Non-Essential Plugin Notices And Admin Bar Menu', 'wp-simple-firewall' );
279
+ $desc = [
280
+ __( 'By default Shield displays non-essential notices in the admin area and admin bar.', 'wp-simple-firewall' ),
281
+ __( 'These notices can be hidden by switching off this option.', 'wp-simple-firewall' ),
282
+ ];
283
  break;
284
 
285
  case 'display_plugin_badge' :
317
  ];
318
  break;
319
 
 
 
 
 
 
 
320
  case 'importexport_enable' :
321
  $name = __( 'Allow Import/Export', 'wp-simple-firewall' );
322
  $summary = __( 'Allow Import And Export Of Options On This Site', 'wp-simple-firewall' );
src/lib/src/Modules/Plugin/UI.php CHANGED
@@ -6,20 +6,9 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\Captcha\CheckCaptchaSettings;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\Debug\Collate;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\Debug\RecentEvents;
9
- use FernleafSystems\Wordpress\Services\Services;
10
 
11
  class UI extends BaseShield\UI {
12
 
13
- public function buildInsightsVars_Dashboard() :array {
14
- return [
15
- 'content' => [
16
- 'dashboard_cards' => ( new Insights\DashboardCards() )
17
- ->setMod( $this->getMod() )
18
- ->renderAll(),
19
- ],
20
- ];
21
- }
22
-
23
  public function buildInsightsVars_Debug() :array {
24
  return [
25
  'strings' => [
@@ -65,17 +54,6 @@ class UI extends BaseShield\UI {
65
  return $data;
66
  }
67
 
68
- /**
69
- * @param array $option
70
- * @return array
71
- */
72
- protected function buildOptionForUi( $option ) {
73
- $option = parent::buildOptionForUi( $option );
74
- if ( $option[ 'key' ] === 'visitor_address_source' ) {
75
- }
76
- return $option;
77
- }
78
-
79
  public function getSectionWarnings( string $section ) :array {
80
  /** @var ModCon $mod */
81
  $mod = $this->getMod();
@@ -96,6 +74,8 @@ class UI extends BaseShield\UI {
96
  }
97
  }
98
  break;
 
 
99
  }
100
 
101
  return $warnings;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\Captcha\CheckCaptchaSettings;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\Debug\Collate;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Lib\Debug\RecentEvents;
 
9
 
10
  class UI extends BaseShield\UI {
11
 
 
 
 
 
 
 
 
 
 
 
12
  public function buildInsightsVars_Debug() :array {
13
  return [
14
  'strings' => [
54
  return $data;
55
  }
56
 
 
 
 
 
 
 
 
 
 
 
 
57
  public function getSectionWarnings( string $section ) :array {
58
  /** @var ModCon $mod */
59
  $mod = $this->getMod();
74
  }
75
  }
76
  break;
77
+ default:
78
+ break;
79
  }
80
 
81
  return $warnings;
src/lib/src/Modules/Plugin/Upgrade.php CHANGED
@@ -15,19 +15,13 @@ class Upgrade extends Base\Upgrade {
15
 
16
  protected function upgrade_1200() {
17
  // remove old tables that have somehow been missed in the past.
18
- $tables = [
19
- 'geoip',
20
- 'reporting',
21
- 'spambot_comments_filter',
22
- 'statistics',
23
- ];
24
-
25
  $WPDB = Services::WpDb();
26
- foreach ( $tables as $table ) {
27
  $table = sprintf( '%s%s%s', $WPDB->getPrefix(), $this->getCon()->getOptionStoragePrefix(), $table );
28
- if ( $WPDB->getIfTableExists( $table ) ) {
29
  $WPDB->doDropTable( $table );
30
  }
31
  }
 
32
  }
33
  }
15
 
16
  protected function upgrade_1200() {
17
  // remove old tables that have somehow been missed in the past.
 
 
 
 
 
 
 
18
  $WPDB = Services::WpDb();
19
+ foreach ( [ 'geoip', 'reporting', 'spambot_comments_filter', 'statistics', ] as $table ) {
20
  $table = sprintf( '%s%s%s', $WPDB->getPrefix(), $this->getCon()->getOptionStoragePrefix(), $table );
21
+ if ( $WPDB->tableExists( $table ) ) {
22
  $WPDB->doDropTable( $table );
23
  }
24
  }
25
+ $WPDB->clearResultShowTables();
26
  }
27
  }
src/lib/src/Modules/Plugin/WpCli/Export.php CHANGED
@@ -35,8 +35,6 @@ class Export extends Base\WpCli\BaseWpCliCmd {
35
  }
36
 
37
  /**
38
- * @param array $null
39
- * @param array $args
40
  * @throws WP_CLI\ExitException
41
  */
42
  public function cmdExport( array $null, array $args ) {
35
  }
36
 
37
  /**
 
 
38
  * @throws WP_CLI\ExitException
39
  */
40
  public function cmdExport( array $null, array $args ) {
src/lib/src/Modules/Plugin/WpCli/ForceOff.php CHANGED
@@ -1,4 +1,4 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\WpCli;
4
 
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\WpCli;
4
 
src/lib/src/Modules/Plugin/WpCli/Import.php CHANGED
@@ -58,8 +58,6 @@ class Import extends Base\WpCli\BaseWpCliCmd {
58
  }
59
 
60
  /**
61
- * @param array $null
62
- * @param array $args
63
  * @throws WP_CLI\ExitException
64
  */
65
  public function cmdImport( array $null, array $args ) {
@@ -123,8 +121,8 @@ class Import extends Base\WpCli\BaseWpCliCmd {
123
  ( new Lib\ImportExport\Import() )
124
  ->setMod( $this->getMod() )
125
  ->fromSite(
126
- $args[ 'source' ],
127
- $secret,
128
  $slave === 'add' ? true : ( $slave === 'remove' ? false : null )
129
  );
130
  }
58
  }
59
 
60
  /**
 
 
61
  * @throws WP_CLI\ExitException
62
  */
63
  public function cmdImport( array $null, array $args ) {
121
  ( new Lib\ImportExport\Import() )
122
  ->setMod( $this->getMod() )
123
  ->fromSite(
124
+ (string)$args[ 'source' ],
125
+ (string)$secret,
126
  $slave === 'add' ? true : ( $slave === 'remove' ? false : null )
127
  );
128
  }
src/lib/src/Modules/PluginControllerConsumer.php CHANGED
@@ -19,11 +19,11 @@ trait PluginControllerConsumer {
19
  }
20
 
21
  /**
22
- * @param Controller $oCon
23
  * @return $this
24
  */
25
- public function setCon( $oCon ) {
26
- $this->oPlugCon = $oCon;
27
  return $this;
28
  }
29
  }
19
  }
20
 
21
  /**
22
+ * @param Controller $con
23
  * @return $this
24
  */
25
+ public function setCon( $con ) {
26
+ $this->oPlugCon = $con;
27
  return $this;
28
  }
29
  }
src/lib/src/Modules/Reporting/Insights/OverviewCards.php DELETED
@@ -1,9 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Reporting\Insights;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
-
7
- class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
8
-
9
- }
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/Reporting/UI.php CHANGED
@@ -74,12 +74,6 @@ class UI extends BaseShield\UI {
74
  number_format( $eventSelector->clearWheres()->sumEvent( 'login_block' ) ) ),
75
  'tooltip_p' => __( 'Total login attempts blocked.', 'wp-simple-firewall' ),
76
  ],
77
- // 'firewall' => [
78
- // 'id' => 'firewall_block',
79
- // 'title' => __( 'Firewall Blocks', 'wp-simple-firewall' ),
80
- // 'val' => $eventSelector->clearWheres()->sumEvent( 'firewall_block' ),
81
- // 'tooltip' => __( 'Total requests blocked by firewall rules.', 'wp-simple-firewall' )
82
- // ],
83
  'bot_blocks' => [
84
  'id' => 'bot_blocks',
85
  'title' => __( 'Bot Detection', 'wp-simple-firewall' ),
74
  number_format( $eventSelector->clearWheres()->sumEvent( 'login_block' ) ) ),
75
  'tooltip_p' => __( 'Total login attempts blocked.', 'wp-simple-firewall' ),
76
  ],
 
 
 
 
 
 
77
  'bot_blocks' => [
78
  'id' => 'bot_blocks',
79
  'title' => __( 'Bot Detection', 'wp-simple-firewall' ),
src/lib/src/Modules/SecurityAdmin/AdminNotices.php CHANGED
@@ -30,7 +30,7 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
30
  }
31
 
32
  private function buildNotice_CertainOptionsRestricted( NoticeVO $notice ) {
33
- $oMod = $this->getMod();
34
  $sName = $this->getCon()->getHumanName();
35
 
36
  $notice->render_data = [
@@ -43,7 +43,7 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
43
  'hrefs' => [
44
  'setting_page' => sprintf(
45
  '<a href="%s" title="%s">%s</a>',
46
- $oMod->getUrl_AdminPage(),
47
  __( 'Admin Access Login', 'wp-simple-firewall' ),
48
  sprintf( __( 'Go here to manage settings and authenticate with the %s plugin.', 'wp-simple-firewall' ), $sName )
49
  )
@@ -52,7 +52,7 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
52
  }
53
 
54
  private function buildNotice_AdminUsersRestricted( NoticeVO $notice ) {
55
- $oMod = $this->getMod();
56
  $sName = $this->getCon()->getHumanName();
57
 
58
  $notice->render_data = [
@@ -71,7 +71,7 @@ class AdminNotices extends Shield\Modules\Base\AdminNotices {
71
  'hrefs' => [
72
  'setting_page' => sprintf(
73
  '<a href="%s" title="%s">%s</a>',
74
- $oMod->getUrl_AdminPage(),
75
  __( 'Security Admin Login', 'wp-simple-firewall' ),
76
  sprintf( __( 'Go here to manage settings and authenticate with the %s plugin.', 'wp-simple-firewall' ), $sName )
77
  )
30
  }
31
 
32
  private function buildNotice_CertainOptionsRestricted( NoticeVO $notice ) {
33
+ $mod = $this->getMod();
34
  $sName = $this->getCon()->getHumanName();
35
 
36
  $notice->render_data = [
43
  'hrefs' => [
44
  'setting_page' => sprintf(
45
  '<a href="%s" title="%s">%s</a>',
46
+ $mod->getUrl_AdminPage(),
47
  __( 'Admin Access Login', 'wp-simple-firewall' ),
48
  sprintf( __( 'Go here to manage settings and authenticate with the %s plugin.', 'wp-simple-firewall' ), $sName )
49
  )
52
  }
53
 
54
  private function buildNotice_AdminUsersRestricted( NoticeVO $notice ) {
55
+ $mod = $this->getMod();
56
  $sName = $this->getCon()->getHumanName();
57
 
58
  $notice->render_data = [
71
  'hrefs' => [
72
  'setting_page' => sprintf(
73
  '<a href="%s" title="%s">%s</a>',
74
+ $mod->getUrl_AdminPage(),
75
  __( 'Security Admin Login', 'wp-simple-firewall' ),
76
  sprintf( __( 'Go here to manage settings and authenticate with the %s plugin.', 'wp-simple-firewall' ), $sName )
77
  )
src/lib/src/Modules/SecurityAdmin/AjaxHandler.php CHANGED
@@ -26,7 +26,7 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
26
  $secAdminCon = $mod->getSecurityAdminController();
27
  return [
28
  'time_remaining' => $secAdminCon->getSecAdminTimeRemaining(),
29
- 'success' => $secAdminCon->isCurrentlySecAdmin()
30
  ];
31
  }
32
 
26
  $secAdminCon = $mod->getSecurityAdminController();
27
  return [
28
  'time_remaining' => $secAdminCon->getSecAdminTimeRemaining(),
29
+ 'success' => $this->getCon()->this_req->is_security_admin
30
  ];
31
  }
32
 
src/lib/src/Modules/SecurityAdmin/Insights/OverviewCards.php DELETED
@@ -1,66 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin\Insights;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
-
7
- class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
8
-
9
- protected function buildModCards() :array {
10
- /** @var Shield\Modules\SecurityAdmin\ModCon $mod */
11
- $mod = $this->getMod();
12
- /** @var Shield\Modules\SecurityAdmin\Options $opts */
13
- $opts = $this->getOptions();
14
-
15
- $cards = [];
16
-
17
- $enabled = $mod->getSecurityAdminController()->isEnabledSecAdmin();
18
- if ( !$enabled ) {
19
- $cards[ 'mod' ] = [
20
- 'name' => __( 'Security Admin', 'wp-simple-firewall' ),
21
- 'state' => -1,
22
- 'summary' => __( 'Security plugin is vulnerable to tampering', 'wp-simple-firewall' ),
23
- 'href' => $mod->getUrl_DirectLinkToOption( 'admin_access_key' ),
24
- ];
25
- }
26
- else {
27
- $cards[ 'mod' ] = [
28
- 'name' => __( 'Security Admin', 'wp-simple-firewall' ),
29
- 'state' => 1,
30
- 'summary' => __( 'Security plugin is protected against tampering', 'wp-simple-firewall' ),
31
- 'href' => $mod->getUrl_DirectLinkToOption( 'admin_access_key' ),
32
- ];
33
-
34
- $isWPOptsRestricted = $opts->getAdminAccessArea_Options();
35
- $cards[ 'wpopts' ] = [
36
- 'name' => __( 'Important Options', 'wp-simple-firewall' ),
37
- 'state' => $isWPOptsRestricted ? 1 : -1,
38
- 'summary' => $isWPOptsRestricted ?
39
- __( 'Important WP options are protected against tampering', 'wp-simple-firewall' )
40
- : __( "Important WP options aren't protected against tampering", 'wp-simple-firewall' ),
41
- 'href' => $mod->getUrl_DirectLinkToOption( 'admin_access_restrict_options' ),
42
- ];
43
-
44
- $bUsers = $opts->isSecAdminRestrictUsersEnabled();
45
- $cards[ 'adminusers' ] = [
46
- 'name' => __( 'WP Admins', 'wp-simple-firewall' ),
47
- 'state' => $bUsers ? 1 : -1,
48
- 'summary' => $bUsers ?
49
- __( 'Admin users are protected against tampering', 'wp-simple-firewall' )
50
- : __( "Admin users aren't protected against tampering", 'wp-simple-firewall' ),
51
- 'href' => $mod->getUrl_DirectLinkToOption( 'admin_access_restrict_admin_users' ),
52
- ];
53
- }
54
-
55
- return $cards;
56
- }
57
-
58
- protected function getSectionTitle() :string {
59
- return __( 'Security Admin', 'wp-simple-firewall' );
60
- }
61
-
62
- protected function getSectionSubTitle() :string {
63
- return sprintf( __( 'Prevent Tampering With %s Settings', 'wp-simple-firewall' ),
64
- $this->getCon()->getHumanName() );
65
- }
66
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/SecurityAdmin/Lib/SecurityAdmin/Ops/ToggleSecAdminStatus.php CHANGED
@@ -2,49 +2,29 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin\Lib\SecurityAdmin\Ops;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Session\EntryVO;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Session\Update;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
8
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin\ModCon;
9
 
10
  class ToggleSecAdminStatus {
11
 
12
  use ModConsumer;
13
 
14
  public function turnOn() :bool {
15
- try {
16
- $success = $this->toggle( true );
17
- }
18
- catch ( \Exception $e ) {
19
- $success = false;
20
- }
21
- return $success;
22
  }
23
 
24
  public function turnOff() :bool {
25
- try {
26
- $success = $this->toggle( false );
27
- }
28
- catch ( \Exception $e ) {
29
- $success = false;
30
- }
31
- return $success;
32
  }
33
 
34
- /**
35
- * @param bool $onOrOff
36
- * @return bool
37
- * @throws \Exception
38
- */
39
  private function toggle( bool $onOrOff ) :bool {
40
- /** @var ModCon $mod */
41
- $mod = $this->getMod();
42
- $session = $this->getMod()->getSession();
43
- if ( !$session instanceof EntryVO ) {
44
- throw new \Exception( 'No session' );
 
45
  }
46
- /** @var Update $updater */
47
- $updater = $mod->getDbHandler_Sessions()->getQueryUpdater();
48
- return $onOrOff ? $updater->startSecurityAdmin( $session ) : $updater->terminateSecurityAdmin( $session );
49
  }
50
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin\Lib\SecurityAdmin\Ops;
4
 
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
 
8
  class ToggleSecAdminStatus {
9
 
10
  use ModConsumer;
11
 
12
  public function turnOn() :bool {
13
+ return $this->toggle( true );
 
 
 
 
 
 
14
  }
15
 
16
  public function turnOff() :bool {
17
+ return $this->toggle( false );
 
 
 
 
 
 
18
  }
19
 
 
 
 
 
 
20
  private function toggle( bool $onOrOff ) :bool {
21
+ $session = $this->getMod()->getSessionWP();
22
+ if ( $session->valid ) {
23
+ $this->getCon()
24
+ ->getModule_Sessions()
25
+ ->getSessionCon()
26
+ ->updateSessionParameter( 'secadmin_at', $onOrOff ? Services::Request()->ts() : 0 );
27
  }
28
+ return (bool)$session->valid;
 
 
29
  }
30
  }
src/lib/src/Modules/SecurityAdmin/Lib/SecurityAdmin/Restrictions/WpOptions.php CHANGED
@@ -10,8 +10,7 @@ class WpOptions extends Base {
10
  protected function canRun() :bool {
11
  /** @var Options $opts */
12
  $opts = $this->getOptions();
13
- return $opts->getAdminAccessArea_Options()
14
- && !$this->getMod()->isUpgrading() && !Services::WpGeneral()->isLoginRequest();
15
  }
16
 
17
  protected function run() {
10
  protected function canRun() :bool {
11
  /** @var Options $opts */
12
  $opts = $this->getOptions();
13
+ return $opts->isRestrictWpOptions() && !Services::WpGeneral()->isLoginRequest();
 
14
  }
15
 
16
  protected function run() {
src/lib/src/Modules/SecurityAdmin/Lib/SecurityAdmin/SecurityAdminController.php CHANGED
@@ -22,30 +22,27 @@ class SecurityAdminController extends ExecOnceModConsumer {
22
  add_action( 'admin_init', function () {
23
  $this->enqueueJS();
24
  } );
25
- add_action( 'init', function () {
26
- if ( !$this->getCon()->isPluginAdmin() ) {
27
 
28
- foreach ( $this->getAllRestrictionZones() as $zone ) {
29
- $zone->setMod( $this->getMod() )->execute();
30
- }
31
-
32
- if ( !$this->getCon()->isThisPluginModuleRequest() ) {
33
- add_action( 'admin_footer', [ $this, 'printPinLoginForm' ] );
34
- }
35
  }
36
- } );
 
 
 
37
  }
38
 
39
  /**
40
  * @return Restrictions\Base[]
41
  */
42
- private function getAllRestrictionZones() :array {
43
  return [
44
- new Restrictions\WpOptions(),
45
- new Restrictions\Plugins(),
46
- new Restrictions\Themes(),
47
- new Restrictions\Posts(),
48
- new Restrictions\Users(),
49
  ];
50
  }
51
 
@@ -53,7 +50,7 @@ class SecurityAdminController extends ExecOnceModConsumer {
53
  /** @var Options $opts */
54
  $opts = $this->getOptions();
55
  return $this->getMod()->isModOptEnabled() &&
56
- $opts->hasSecurityPIN() && $this->getSecAdminTimeout() > 2;
57
  }
58
 
59
  private function enqueueJS() {
@@ -66,7 +63,7 @@ class SecurityAdminController extends ExecOnceModConsumer {
66
  /** @var Options $opts */
67
  $opts = $this->getOptions();
68
 
69
- $isCurrentlySecAdmin = $this->isCurrentlySecAdmin();
70
  $localz[] = [
71
  'shield/secadmin',
72
  'shield_vars_secadmin',
@@ -77,8 +74,8 @@ class SecurityAdminController extends ExecOnceModConsumer {
77
  'req_email_remove' => $mod->getAjaxActionData( 'req_email_remove' ),
78
  ],
79
  'flags' => [
80
- 'restrict_options' => !$isCurrentlySecAdmin && $opts->getAdminAccessArea_Options(),
81
- 'run_checks' => $this->getCon()->getIsPage_PluginAdmin() && $isCurrentlySecAdmin
82
  && !$this->isCurrentUserRegisteredSecAdmin(),
83
  ],
84
  'strings' => [
@@ -116,13 +113,10 @@ class SecurityAdminController extends ExecOnceModConsumer {
116
  */
117
  public function getSecAdminTimeRemaining() :int {
118
  $remaining = 0;
119
- if ( $this->getCon()->getModule_Sessions()->getSessionCon()->hasSession() ) {
120
-
121
- $secAdminAt = $this->getMod()->getSession()->getSecAdminAt();
122
- if ( $this->isRegisteredSecAdminUser() ) {
123
- $remaining = 0;
124
- }
125
- elseif ( $secAdminAt > 0 ) {
126
  $remaining = $this->getSecAdminTimeout() - ( Services::Request()->ts() - $secAdminAt );
127
  }
128
  }
@@ -154,7 +148,7 @@ class SecurityAdminController extends ExecOnceModConsumer {
154
 
155
  public function adjustUserAdminPermissions( $isPluginAdmin = true ) :bool {
156
  return $isPluginAdmin &&
157
- ( $this->isCurrentlySecAdmin() || $this->verifyPinRequest() );
158
  }
159
 
160
  public function renderPinLoginForm() :string {
22
  add_action( 'admin_init', function () {
23
  $this->enqueueJS();
24
  } );
 
 
25
 
26
+ if ( !$this->getCon()->isPluginAdmin() ) {
27
+ foreach ( $this->enumRestrictionZones() as $zone ) {
28
+ ( new $zone() )->setMod( $this->getMod() )->execute();
 
 
 
 
29
  }
30
+ if ( !$this->getCon()->isThisPluginModuleRequest() ) {
31
+ add_action( 'admin_footer', [ $this, 'printPinLoginForm' ] );
32
+ }
33
+ }
34
  }
35
 
36
  /**
37
  * @return Restrictions\Base[]
38
  */
39
+ private function enumRestrictionZones() :array {
40
  return [
41
+ Restrictions\WpOptions::class,
42
+ Restrictions\Plugins::class,
43
+ Restrictions\Themes::class,
44
+ Restrictions\Posts::class,
45
+ Restrictions\Users::class,
46
  ];
47
  }
48
 
50
  /** @var Options $opts */
51
  $opts = $this->getOptions();
52
  return $this->getMod()->isModOptEnabled() &&
53
+ $opts->hasSecurityPIN() && $this->getSecAdminTimeout() > 0;
54
  }
55
 
56
  private function enqueueJS() {
63
  /** @var Options $opts */
64
  $opts = $this->getOptions();
65
 
66
+ $isSecAdmin = $this->getCon()->this_req->is_security_admin;
67
  $localz[] = [
68
  'shield/secadmin',
69
  'shield_vars_secadmin',
74
  'req_email_remove' => $mod->getAjaxActionData( 'req_email_remove' ),
75
  ],
76
  'flags' => [
77
+ 'restrict_options' => !$isSecAdmin && $opts->getAdminAccessArea_Options(),
78
+ 'run_checks' => $this->getCon()->getIsPage_PluginAdmin() && $isSecAdmin
79
  && !$this->isCurrentUserRegisteredSecAdmin(),
80
  ],
81
  'strings' => [
113
  */
114
  public function getSecAdminTimeRemaining() :int {
115
  $remaining = 0;
116
+ $session = $this->getMod()->getSessionWP();
117
+ if ( $session->valid ) {
118
+ $secAdminAt = $session->shield[ 'secadmin_at' ] ?? 0;
119
+ if ( !$this->isRegisteredSecAdminUser() && $secAdminAt > 0 ) {
 
 
 
120
  $remaining = $this->getSecAdminTimeout() - ( Services::Request()->ts() - $secAdminAt );
121
  }
122
  }
148
 
149
  public function adjustUserAdminPermissions( $isPluginAdmin = true ) :bool {
150
  return $isPluginAdmin &&
151
+ ( $this->getCon()->this_req->is_security_admin || $this->verifyPinRequest() );
152
  }
153
 
154
  public function renderPinLoginForm() :string {
src/lib/src/Modules/SecurityAdmin/Lib/SecurityAdmin/VerifySecurityAdminList.php CHANGED
@@ -13,7 +13,7 @@ class VerifySecurityAdminList {
13
  $WPU = Services::WpUsers();
14
 
15
  $filtered = [];
16
- foreach ( array_map( 'trim', $users ) as $key => $usernameOrEmail ) {
17
  $user = null;
18
 
19
  if ( !empty( $usernameOrEmail ) ) {
13
  $WPU = Services::WpUsers();
14
 
15
  $filtered = [];
16
+ foreach ( array_map( 'trim', $users ) as $usernameOrEmail ) {
17
  $user = null;
18
 
19
  if ( !empty( $usernameOrEmail ) ) {
src/lib/src/Modules/SecurityAdmin/ModCon.php CHANGED
@@ -20,8 +20,14 @@ class ModCon extends BaseShield\ModCon {
20
  */
21
  private $securityAdminCon;
22
 
 
 
 
 
 
 
23
  protected function setupCustomHooks() {
24
- add_action( $this->prefix( 'pre_deactivate_plugin' ), [ $this, 'preDeactivatePlugin' ] );
25
  }
26
 
27
  public function getWhiteLabelController() :Lib\WhiteLabel\WhitelabelController {
@@ -102,7 +108,6 @@ class ModCon extends BaseShield\ModCon {
102
 
103
  /**
104
  * Used by Wizard. TODO: sort out the wizard requests!
105
- * @param string $pin
106
  * @return $this
107
  * @throws \Exception
108
  */
20
  */
21
  private $securityAdminCon;
22
 
23
+ protected function enumRuleBuilders() :array {
24
+ return [
25
+ $this->getSecurityAdminController()->isEnabledSecAdmin() ? Rules\Build\IsSecurityAdmin::class : null,
26
+ ];
27
+ }
28
+
29
  protected function setupCustomHooks() {
30
+ add_action( $this->getCon()->prefix( 'pre_deactivate_plugin' ), [ $this, 'preDeactivatePlugin' ] );
31
  }
32
 
33
  public function getWhiteLabelController() :Lib\WhiteLabel\WhitelabelController {
108
 
109
  /**
110
  * Used by Wizard. TODO: sort out the wizard requests!
 
111
  * @return $this
112
  * @throws \Exception
113
  */
src/lib/src/Modules/SecurityAdmin/Options.php CHANGED
@@ -11,14 +11,16 @@ class Options extends BaseShield\Options {
11
  return $this->setOpt( 'admin_access_key', '' );
12
  }
13
 
 
 
 
14
  public function getAdminAccessArea_Options() :bool {
15
  return $this->isOpt( 'admin_access_restrict_options', 'Y' );
16
  }
17
 
18
  /**
19
- * @param string $area one of plugins, themes
20
- * @return array
21
  * @since 11.1
 
22
  */
23
  public function getSecAdminAreaCaps( $area = 'plugins' ) :array {
24
  $d = $this->getOpt( 'admin_access_restrict_'.$area, [] );
@@ -75,4 +77,8 @@ class Options extends BaseShield\Options {
75
  public function isSecAdminRestrictUsersEnabled() :bool {
76
  return $this->isOpt( 'admin_access_restrict_admin_users', 'Y' );
77
  }
 
 
 
 
78
  }
11
  return $this->setOpt( 'admin_access_key', '' );
12
  }
13
 
14
+ /**
15
+ * @deprecated 15.0 - see isRestrictWpOptions()
16
+ */
17
  public function getAdminAccessArea_Options() :bool {
18
  return $this->isOpt( 'admin_access_restrict_options', 'Y' );
19
  }
20
 
21
  /**
 
 
22
  * @since 11.1
23
+ * @param string $area one of plugins, themes
24
  */
25
  public function getSecAdminAreaCaps( $area = 'plugins' ) :array {
26
  $d = $this->getOpt( 'admin_access_restrict_'.$area, [] );
77
  public function isSecAdminRestrictUsersEnabled() :bool {
78
  return $this->isOpt( 'admin_access_restrict_admin_users', 'Y' );
79
  }
80
+
81
+ public function isRestrictWpOptions() :bool {
82
+ return $this->isOpt( 'admin_access_restrict_options', 'Y' );
83
+ }
84
  }
src/lib/src/Modules/SecurityAdmin/Processor.php CHANGED
@@ -6,10 +6,10 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
 
7
  class Processor extends BaseShield\Processor {
8
 
9
- protected function run() {
10
  /** @var ModCon $mod */
11
  $mod = $this->getMod();
12
- $mod->getWhiteLabelController()->execute();
13
  $mod->getSecurityAdminController()->execute();
 
14
  }
15
  }
6
 
7
  class Processor extends BaseShield\Processor {
8
 
9
+ public function onWpInit() {
10
  /** @var ModCon $mod */
11
  $mod = $this->getMod();
 
12
  $mod->getSecurityAdminController()->execute();
13
+ $mod->getWhiteLabelController()->execute();
14
  }
15
  }
src/lib/src/Modules/SecurityAdmin/Rules/Build/IsSecurityAdmin.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\SecurityAdmin\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
7
+ Build\BuildRuleCoreShieldBase,
8
+ Conditions,
9
+ Responses
10
+ };
11
+
12
+ class IsSecurityAdmin extends BuildRuleCoreShieldBase {
13
+
14
+ const SLUG = 'shield/is_security_admin';
15
+
16
+ protected function getName() :string {
17
+ return 'Is Security Admin';
18
+ }
19
+
20
+ protected function getDescription() :string {
21
+ return 'Is Security Admin.';
22
+ }
23
+
24
+ protected function getConditions() :array {
25
+ return [
26
+ 'logic' => static::LOGIC_OR,
27
+ 'group' => [
28
+ [
29
+ 'condition' => Conditions\WpIsWpcli::SLUG,
30
+ ],
31
+ [
32
+ 'logic' => static::LOGIC_AND,
33
+ 'group' => [
34
+ [
35
+ 'condition' => Conditions\IsUserAdminNormal::SLUG,
36
+ ],
37
+ [
38
+ 'condition' => Conditions\IsSecurityAdmin::SLUG,
39
+ ],
40
+ ]
41
+ ]
42
+ ]
43
+ ];
44
+ }
45
+
46
+ protected function getResponses() :array {
47
+ return [
48
+ [
49
+ 'response' => Responses\SetSecurityAdmin::SLUG,
50
+ ],
51
+ ];
52
+ }
53
+ }
src/lib/src/Modules/SecurityAdmin/UI.php CHANGED
@@ -22,10 +22,4 @@ class UI extends BaseShield\UI {
22
 
23
  return $warning;
24
  }
25
-
26
- public function isEnabledForUiSummary() :bool {
27
- /** @var ModCon $mod */
28
- $mod = $this->getMod();
29
- return $mod->getSecurityAdminController()->isEnabledSecAdmin();
30
- }
31
  }
22
 
23
  return $warning;
24
  }
 
 
 
 
 
 
25
  }
src/lib/src/Modules/Sessions/Lib/Ops/Terminate.php DELETED
@@ -1,44 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Sessions\Lib\Ops;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Session\Delete;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Sessions\ModCon;
8
-
9
- class Terminate {
10
-
11
- use ModConsumer;
12
-
13
- /**
14
- * @return bool
15
- */
16
- public function all() {
17
- /** @var ModCon $mod */
18
- $mod = $this->getMod();
19
- return $mod->getDbHandler_Sessions()->tableDelete( true );
20
- }
21
-
22
- /**
23
- * @param int $id
24
- * @return bool
25
- */
26
- public function byRecordId( int $id ) {
27
- $this->getCon()->fireEvent( 'session_terminate' );
28
- return $this->getDeleter()
29
- ->setIsSoftDelete()
30
- ->deleteById( $id );
31
- }
32
-
33
- public function byUsername( string $username ) :bool {
34
- return $this->getDeleter()
35
- ->setIsSoftDelete()
36
- ->forUsername( $username ) !== false;
37
- }
38
-
39
- private function getDeleter() :Delete {
40
- /** @var ModCon $mod */
41
- $mod = $this->getMod();
42
- return $mod->getDbHandler_Sessions()->getQueryDeleter();
43
- }
44
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/Sessions/Lib/SessionController.php CHANGED
@@ -3,122 +3,197 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Sessions\Lib;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Session;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
 
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Sessions\ModCon;
 
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
10
- class SessionController {
11
 
12
- use ModConsumer;
 
 
 
 
 
13
 
14
  /**
15
  * @var Session\EntryVO
 
16
  */
17
  private $current;
18
 
19
  /**
20
  * @var ?string
 
21
  */
22
  private $sessionID;
23
 
24
- /**
25
- * @return Session\EntryVO|null
26
- */
27
- public function getCurrent() {
28
- $con = $this->getCon();
29
- if ( empty( $this->current ) && did_action( 'init' ) ) {
30
- if ( $this->hasSessionID() ) {
31
- $this->current = $this->queryGetSession( $this->getSessionID() );
32
- }
33
- if ( $con->hasSessionId() ) {
34
- if ( empty( $this->current ) ) {
35
- $this->current = $this->queryGetSession( $con->getSessionId() );
36
- }
37
- else {
38
- $con->clearSession();
39
- }
40
- }
41
  }
42
- return $this->current;
43
- }
44
 
45
- public function hasSession() :bool {
46
- $s = $this->getCurrent();
47
- return $s instanceof Session\EntryVO && $s->id > 0;
48
  }
49
 
50
- public function terminateCurrentSession() :bool {
51
- $current = $this->getCurrent();
 
 
 
 
52
 
53
- $success = !empty( $current )
54
- && ( new Ops\Terminate() )
55
- ->setMod( $this->getMod() )
56
- ->byRecordId( $current->id );
57
 
58
- if ( $success ) {
59
- $this->getCon()->fireEvent( 'session_terminate_current', [
60
- 'audit_params' => [
61
- 'user_login' => $current->wp_username,
62
- 'session_id' => $current->session_id,
63
- ]
64
- ] );
65
  }
66
 
67
- $this->current = null;
68
- $this->getCon()->clearSession();
69
 
70
- return $success;
71
- }
72
-
73
- public function createSession( \WP_User $user, string $sessionID = '' ) :bool {
74
- /** @var ModCon $mod */
75
- $mod = $this->getMod();
 
 
 
 
 
76
 
77
- $success = false;
78
- if ( empty( $sessionID ) ) {
79
- $sessionID = $this->getSessionID();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  }
81
 
82
- if ( !empty( $sessionID ) && !empty( $user->user_login ) ) {
 
83
 
84
- if ( !preg_match( '#^[a-z0-9]{32}$#i', $sessionID ) ) {
85
- $sessionID = md5( $sessionID );
 
 
 
 
 
 
 
 
 
 
 
86
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
- $this->sessionID = $sessionID;
 
 
 
 
 
89
 
90
- /** @var Session\Insert $insert */
91
- $insert = $mod->getDbHandler_Sessions()->getQueryInserter();
92
- $success = $insert->create( $sessionID, $user->user_login );
93
 
94
- $this->getCon()->fireEvent( 'session_start', [
 
 
 
95
  'audit_params' => [
96
  'user_login' => $user->user_login,
97
- 'session_id' => $sessionID
98
  ]
99
  ] );
100
  }
101
- return $success;
 
 
 
102
  }
103
 
 
 
 
104
  public function hasSessionID() :bool {
105
  return !empty( $this->getSessionID() );
106
  }
107
 
108
  public function getSessionID() :string {
109
- if ( empty( $this->sessionID ) ) {
110
- $cookie = Services::Request()->cookie( LOGGED_IN_COOKIE );
111
- if ( !empty( $cookie ) ) {
112
- $this->sessionID = md5( $cookie );
113
- }
114
- }
115
- return (string)$this->sessionID;
116
  }
117
 
118
  /**
119
- * @param string $username
120
  * @param string $sessionID
121
  * @return Session\EntryVO|null
 
122
  */
123
  private function queryGetSession( string $sessionID, $username = '' ) {
124
  /** @var ModCon $mod */
@@ -127,4 +202,19 @@ class SessionController {
127
  $sel = $mod->getDbHandler_Sessions()->getQuerySelector();
128
  return $sel->retrieveUserSession( $sessionID, (string)$username );
129
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Sessions\Lib;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Session;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Common\ExecOnceModConsumer;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\IPs\IPRecords;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Sessions\ModCon;
9
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Consumer\WpLoginCapture;
10
  use FernleafSystems\Wordpress\Services\Services;
11
 
12
+ class SessionController extends ExecOnceModConsumer {
13
 
14
+ use WpLoginCapture;
15
+
16
+ /**
17
+ * @var SessionVO
18
+ */
19
+ private $currentWP;
20
 
21
  /**
22
  * @var Session\EntryVO
23
+ * @deprecated 15.0
24
  */
25
  private $current;
26
 
27
  /**
28
  * @var ?string
29
+ * @deprecated 15.0
30
  */
31
  private $sessionID;
32
 
33
+ protected function run() {
34
+ if ( !Services::WpUsers()->isProfilePage() && !Services::IP()->isLoopback() ) { // only on logout
35
+ add_action( 'clear_auth_cookie', function () {
36
+ $this->getCurrentWP();
37
+ }, 0 );
 
 
 
 
 
 
 
 
 
 
 
 
38
  }
 
 
39
 
40
+ $this->setToCaptureApplicationLogin( true )
41
+ ->setAllowMultipleCapture( true )
42
+ ->setupLoginCaptureHooks();
43
  }
44
 
45
+ protected function captureLogin( \WP_User $user ) {
46
+ if ( !empty( $this->getLoggedInCookie() ) ) {
47
+ $this->getCurrentWP();
48
+ $this->getCon()->fireEvent( 'login_success' );
49
+ }
50
+ }
51
 
52
+ public function getCurrentWP() :SessionVO {
 
 
 
53
 
54
+ if ( !isset( $this->currentWP ) ) {
55
+ $this->currentWP = new SessionVO();
 
 
 
 
 
56
  }
57
 
58
+ $WPUsers = Services::WpUsers();
59
+ if ( !$this->currentWP->valid ) {
60
 
61
+ if ( !empty( $this->getLoggedInCookie() ) ) {
62
+ $parsed = wp_parse_auth_cookie( $this->getLoggedInCookie() );
63
+ }
64
+ if ( empty( $parsed ) ) {
65
+ foreach ( [ 'logged_in', 'secure_auth', 'auth' ] as $type ) {
66
+ $parsed = wp_parse_auth_cookie( '', $type );
67
+ if ( !empty( $parsed ) ) {
68
+ break;
69
+ }
70
+ }
71
+ }
72
 
73
+ if ( is_array( $parsed ) && !empty( $parsed[ 'token' ] ) ) {
74
+ $manager = \WP_Session_Tokens::get_instance( $WPUsers->getCurrentWpUser()->ID );
75
+
76
+ $session = $manager->get( $parsed[ 'token' ] );
77
+
78
+ if ( is_array( $session ) ) {
79
+
80
+ // Ensure the correct IP is stored
81
+ $srvIP = Services::IP();
82
+ $ip = $srvIP->getRequestIp();
83
+ if ( !empty( $ip ) && ( empty( $session[ 'ip' ] ) || !$srvIP->checkIp( $ip, $session[ 'ip' ] ) ) ) {
84
+ $session[ 'ip' ] = $ip;
85
+ }
86
+
87
+ $shieldSessionMeta = $session[ 'shield' ] ?? [];
88
+
89
+ $shieldSessionMeta[ 'last_activity_at' ] = Services::Request()->ts();
90
+ if ( empty( $shieldSessionMeta[ 'unique' ] ) ) {
91
+ $shieldSessionMeta[ 'unique' ] = uniqid();
92
+ }
93
+
94
+ $session[ 'shield' ] = $shieldSessionMeta;
95
+ $manager->update( $parsed[ 'token' ], $session );
96
+
97
+ // all that follows should not be stored
98
+ $session[ 'token' ] = $parsed[ 'token' ];
99
+ // This is a copy of \WP_Session_Tokens::hash_token(). They made it private, cuz that's helpful.
100
+ $session[ 'hashed_token' ] = function_exists( 'hash' ) ? hash( 'sha256', $parsed[ 'token' ] ) : sha1( $parsed[ 'token' ] );
101
+ $session[ 'valid' ] = true;
102
+ $this->currentWP->applyFromArray( $session );
103
+
104
+ // Update User Last Seen IP.
105
+ try {
106
+ $this->getCon()->getCurrentUserMeta()->record->ip_ref = ( new IPRecords() )
107
+ ->setMod( $this->getCon()->getModule_Data() )
108
+ ->loadIP( $session[ 'ip' ], true )
109
+ ->id;
110
+ }
111
+ catch ( \Exception $e ) {
112
+ }
113
+ }
114
+ }
115
  }
116
 
117
+ return $this->currentWP;
118
+ }
119
 
120
+ /**
121
+ * This is a hack to directly access and set the raw data for the user sessions by removing 1 of the array entries.
122
+ */
123
+ public function removeSessionBasedOnUniqueID( int $userID, string $uniqueID ) {
124
+ $manager = \WP_Session_Tokens::get_instance( $userID );
125
+ if ( $manager instanceof \WP_User_Meta_Session_Tokens ) {
126
+ $raw = get_user_meta( $userID, 'session_tokens', true );
127
+ foreach ( $raw as $hash => $session ) {
128
+ if ( is_array( $session ) && $uniqueID === ( $session[ 'shield' ][ 'unique' ] ?? '' ) ) {
129
+ unset( $raw[ $hash ] );
130
+ update_user_meta( $userID, 'session_tokens', $raw );
131
+ break;
132
+ }
133
  }
134
+ }
135
+ }
136
+
137
+ public function updateSessionParameter( string $key, $value ) {
138
+ $WPUsers = Services::WpUsers();
139
+ $current = $this->getCurrentWP();
140
+ if ( $current->valid ) {
141
+ $shield = $current->shield;
142
+ $shield[ $key ] = $value;
143
+ $current->shield = $shield;
144
+ \WP_Session_Tokens::get_instance( $WPUsers->getCurrentWpUserId() )
145
+ ->update(
146
+ $current->token,
147
+ array_diff_key( $current->getRawData(), array_flip( [
148
+ 'token',
149
+ 'hashed_token',
150
+ 'valid'
151
+ ] ) )
152
+ );
153
+ }
154
+ }
155
 
156
+ /**
157
+ * @deprecated 15.0
158
+ */
159
+ public function hasSession() :bool {
160
+ return (bool)$this->getCurrentWP()->valid;
161
+ }
162
 
163
+ public function terminateCurrentSession() :bool {
164
+ $current = $this->getCurrentWP();
 
165
 
166
+ if ( $current->valid ) {
167
+ $user = Services::WpUsers()->getCurrentWpUser();
168
+ \WP_Session_Tokens::get_instance( $user->ID )->destroy( $current->token );
169
+ $this->getCon()->fireEvent( 'session_terminate_current', [
170
  'audit_params' => [
171
  'user_login' => $user->user_login,
172
+ 'session_id' => $current->token,
173
  ]
174
  ] );
175
  }
176
+
177
+ unset( $this->currentWP );
178
+
179
+ return true;
180
  }
181
 
182
+ /**
183
+ * @deprecated 15.0
184
+ */
185
  public function hasSessionID() :bool {
186
  return !empty( $this->getSessionID() );
187
  }
188
 
189
  public function getSessionID() :string {
190
+ return '';
 
 
 
 
 
 
191
  }
192
 
193
  /**
 
194
  * @param string $sessionID
195
  * @return Session\EntryVO|null
196
+ * @deprecated 15.0
197
  */
198
  private function queryGetSession( string $sessionID, $username = '' ) {
199
  /** @var ModCon $mod */
202
  $sel = $mod->getDbHandler_Sessions()->getQuerySelector();
203
  return $sel->retrieveUserSession( $sessionID, (string)$username );
204
  }
205
+
206
+ /**
207
+ * @deprecated 15.0
208
+ */
209
+ public function createSession( \WP_User $user, string $sessionID = '' ) :bool {
210
+ return false;
211
+ }
212
+
213
+ /**
214
+ * @return Session\EntryVO|null
215
+ * @deprecated 15.0
216
+ */
217
+ public function getCurrent() {
218
+ return $this->current ?? null;
219
+ }
220
  }
src/lib/src/Modules/Sessions/Lib/SessionVO.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Sessions\Lib;
4
+
5
+ use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
+
7
+ /**
8
+ * @property string $ip
9
+ * @property string $ua
10
+ * @property int $expiration
11
+ * @property int $login
12
+ * @property array $shield
13
+ * /** Not Stored:
14
+ * @property bool $valid
15
+ * @property string $token
16
+ * @property string $hashed_token
17
+ */
18
+ class SessionVO extends DynPropertiesClass {
19
+
20
+ }
src/lib/src/Modules/Sessions/ModCon.php CHANGED
@@ -4,7 +4,6 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Sessions;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
- use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class ModCon extends BaseShield\ModCon {
10
 
@@ -15,34 +14,15 @@ class ModCon extends BaseShield\ModCon {
15
 
16
  public function getSessionCon() :Lib\SessionController {
17
  if ( !isset( $this->sessionCon ) ) {
18
- $this->sessionCon = ( new Lib\SessionController() )
19
- ->setMod( $this );
20
  }
21
  return $this->sessionCon;
22
  }
23
 
24
- public function getDbHandler_Sessions() :Databases\Session\Handler {
25
- return $this->getDbH( 'sessions' );
26
- }
27
-
28
- public function isAutoAddSessions() :bool {
29
- $opts = $this->getOptions();
30
- $req = Services::Request();
31
- $nStartedAt = $opts->getOpt( 'autoadd_sessions_started_at', 0 );
32
- if ( $nStartedAt < 1 ) {
33
- $nStartedAt = $req->ts();
34
- $opts->setOpt( 'autoadd_sessions_started_at', $nStartedAt );
35
- }
36
- return ( $req->ts() - $nStartedAt ) < 20;
37
- }
38
-
39
  /**
40
- * @return bool
41
- * @throws \Exception
42
  */
43
- protected function isReadyToExecute() :bool {
44
- return ( $this->getDbHandler_Sessions() instanceof Databases\Session\Handler )
45
- && $this->getDbHandler_Sessions()->isReady()
46
- && parent::isReadyToExecute();
47
  }
48
  }
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
 
7
 
8
  class ModCon extends BaseShield\ModCon {
9
 
14
 
15
  public function getSessionCon() :Lib\SessionController {
16
  if ( !isset( $this->sessionCon ) ) {
17
+ $this->sessionCon = ( new Lib\SessionController() )->setMod( $this );
 
18
  }
19
  return $this->sessionCon;
20
  }
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  /**
23
+ * @deprecated 15.0
 
24
  */
25
+ public function getDbHandler_Sessions() :Databases\Session\Handler {
26
+ return $this->getDbH( 'sessions' );
 
 
27
  }
28
  }
src/lib/src/Modules/Sessions/Processor.php CHANGED
@@ -4,70 +4,31 @@ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Sessions;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Session;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Consumer\WpLoginCapture;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
10
  class Processor extends BaseShield\Processor {
11
 
12
- use WpLoginCapture;
13
-
14
  /**
15
  * @var Session\EntryVO
 
16
  */
17
  private $current;
18
 
19
  protected function run() {
20
- if ( !Services::WpUsers()->isProfilePage() && !Services::IP()->isLoopback() ) { // only on logout
21
- add_action( 'clear_auth_cookie', function () {
22
- /** @var ModCon $mod */
23
- $mod = $this->getMod();
24
- $mod->getSessionCon()->terminateCurrentSession();
25
- }, 0 );
26
- }
27
-
28
- add_filter( 'login_message', [ $this, 'printLinkToAdmin' ] );
29
-
30
- $this->setupLoginCaptureHooks();
31
- $this->setToCaptureApplicationLogin( true )
32
- ->setAllowMultipleCapture( true );
33
- }
34
-
35
- protected function captureLogin( \WP_User $user ) {
36
- if ( !empty( $this->getLoggedInCookie() ) ) {
37
- $sessonCon = $this->getCon()->getModule_Sessions()->getSessionCon();
38
- $sessonCon->terminateCurrentSession();
39
- $sessonCon->createSession( $user, $this->getLoggedInCookie() );
40
- $this->getCon()->fireEvent( 'login_success' );
41
- }
42
- }
43
-
44
- public function onWpInit() {
45
- $this->autoAddSession();
46
- }
47
-
48
- public function onWpLoaded() {
49
  /** @var ModCon $mod */
50
  $mod = $this->getMod();
51
-
52
- if ( $mod->getSessionCon()->hasSession() ) {
53
- /** @var Session\Update $update */
54
- $update = $mod->getDbHandler_Sessions()->getQueryUpdater();
55
- $update->updateLastActivity( $mod->getSessionCon()->getCurrent() );
56
- }
57
  }
58
 
59
- private function autoAddSession() {
60
- /** @var ModCon $mod */
61
- $mod = $this->getMod();
62
- $sessCon = $mod->getSessionCon();
63
- $user = Services::WpUsers()->getCurrentWpUser();
64
- if ( $user instanceof \WP_User && !$sessCon->hasSession() ) {
65
- $sessCon->createSession( $user );
66
- }
67
  }
68
 
69
  /**
70
- * Only show Go To Admin link for Authors and above.
71
  * @param string $msg
72
  * @return string
73
  * @throws \Exception
@@ -77,30 +38,15 @@ class Processor extends BaseShield\Processor {
77
  $mod = $this->getMod();
78
  $user = Services::WpUsers()->getCurrentWpUser();
79
 
80
- if ( in_array( Services::Request()->query( 'action' ), [ '', 'login' ] )
81
- && ( $user instanceof \WP_User ) && $mod->getSessionCon()->hasSession() ) {
82
- $msg .= sprintf( '<p class="message">%s<br />%s</p>',
83
- __( "You're already logged-in.", 'wp-simple-firewall' )
84
- .sprintf( ' <span style="white-space: nowrap">(%s)</span>', $user->user_login ),
85
  ( $user->user_level >= 2 ) ? sprintf( '<a href="%s">%s</a>',
86
  Services::WpGeneral()->getAdminUrl(),
87
  __( "Go To Admin", 'wp-simple-firewall' ).' &rarr;' ) : '' );
88
  }
89
  return $msg;
90
  }
91
-
92
- protected function getWpHookPriority( string $hook ) :int {
93
- switch ( $hook ) {
94
- case 'init':
95
- $pri = 1;
96
- break;
97
- default:
98
- $pri = parent::getWpHookPriority( $hook );
99
- }
100
- return $pri;
101
- }
102
-
103
- protected function getHookPriority() :int {
104
- return 100;
105
- }
106
  }
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Session;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
 
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
  class Processor extends BaseShield\Processor {
10
 
 
 
11
  /**
12
  * @var Session\EntryVO
13
+ * @deprecated 15.0
14
  */
15
  private $current;
16
 
17
  protected function run() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  /** @var ModCon $mod */
19
  $mod = $this->getMod();
20
+ $mod->getSessionCon()->execute();
21
+ add_filter( 'login_message', [ $this, 'printLinkToAdmin' ] );
 
 
 
 
22
  }
23
 
24
+ /**
25
+ * @deprecated 15.0
26
+ */
27
+ protected function captureLogin( \WP_User $user ) {
 
 
 
 
28
  }
29
 
30
  /**
31
+ * Only show Go To Admin link for Authors+
32
  * @param string $msg
33
  * @return string
34
  * @throws \Exception
38
  $mod = $this->getMod();
39
  $user = Services::WpUsers()->getCurrentWpUser();
40
 
41
+ if ( in_array( Services::Request()->query( 'action' ), [ '', 'login' ] ) && $mod->getSessionWP()->valid
42
+ && $user instanceof \WP_User ) {
43
+ $msg .= sprintf( '<p class="message">%s %s<br />%s</p>',
44
+ __( "You're already logged-in.", 'wp-simple-firewall' ),
45
+ sprintf( '<span style="white-space: nowrap">(%s)</span>', $user->user_login ),
46
  ( $user->user_level >= 2 ) ? sprintf( '<a href="%s">%s</a>',
47
  Services::WpGeneral()->getAdminUrl(),
48
  __( "Go To Admin", 'wp-simple-firewall' ).' &rarr;' ) : '' );
49
  }
50
  return $msg;
51
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  }
src/lib/src/Modules/Traffic/Insights/OverviewCards.php DELETED
@@ -1,9 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic\Insights;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
-
7
- class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
8
-
9
- }
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/Traffic/Lib/Limit/Limiter.php CHANGED
@@ -6,6 +6,9 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Common\ExecOnceModConsu
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
 
 
 
9
  class Limiter extends ExecOnceModConsumer {
10
 
11
  protected function canRun() :bool {
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
9
+ /**
10
+ * @deprecated 15.0
11
+ */
12
  class Limiter extends ExecOnceModConsumer {
13
 
14
  protected function canRun() :bool {
src/lib/src/Modules/Traffic/Lib/Limit/TestIpLimit.php CHANGED
@@ -1,4 +1,4 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic\Lib\Limit;
4
 
@@ -13,7 +13,7 @@ class TestIpLimit {
13
  use Shield\Modules\IPs\Components\IpAddressConsumer;
14
 
15
  /**
16
- * @return bool - true if request is allowed (i.e. request limit has not been exceeded)
17
  * @throws \Exception|RateLimitExceededException
18
  */
19
  public function run() :bool {
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic\Lib\Limit;
4
 
13
  use Shield\Modules\IPs\Components\IpAddressConsumer;
14
 
15
  /**
16
+ * returns true if request is allowed (i.e. request limit has not been exceeded)
17
  * @throws \Exception|RateLimitExceededException
18
  */
19
  public function run() :bool {
src/lib/src/Modules/Traffic/Lib/LogHandlers/LocalDbWriter.php CHANGED
@@ -45,7 +45,7 @@ class LocalDbWriter extends AbstractProcessingHandler {
45
  );
46
 
47
  $updateData = [];
48
- foreach ( [ 'verb', 'code', 'path', 'type', 'uid' ] as $item ) {
49
  if ( !empty( $meta[ $item ] ) ) {
50
  $updateData[ $item ] = $meta[ $item ];
51
  unset( $meta[ $item ] );
45
  );
46
 
47
  $updateData = [];
48
+ foreach ( [ 'verb', 'code', 'path', 'type', 'uid', 'offense' ] as $item ) {
49
  if ( !empty( $meta[ $item ] ) ) {
50
  $updateData[ $item ] = $meta[ $item ];
51
  unset( $meta[ $item ] );
src/lib/src/Modules/Traffic/Lib/RequestLogger.php CHANGED
@@ -69,21 +69,19 @@ class RequestLogger extends ExecOnceModConsumer {
69
 
70
  private function isRequestTypeExcluded() :bool {
71
  $srvProviders = Services::ServiceProviders();
72
- $ipIdentity = Services::IP()->getIpDetector()->getIPIdentity();
73
  /** @var Traffic\Options $opts */
74
  $opts = $this->getOptions();
75
  $excl = $opts->getReqTypeExclusions();
76
  $isLoggedIn = Services::WpUsers()->isUserLoggedIn();
77
 
78
- $exclude = ( in_array( 'simple', $excl ) && count( Services::Request()->getRawRequestParams( false ) ) == 0 )
79
- || ( in_array( 'logged_in', $excl ) && $isLoggedIn )
80
- || ( in_array( 'ajax', $excl ) && Services::WpGeneral()->isAjax() )
81
  || ( in_array( 'cron', $excl ) && Services::WpGeneral()->isCron() )
82
- || ( in_array( 'server', $excl ) && $ipIdentity === IpID::THIS_SERVER );
83
 
84
  if ( !$exclude && !$isLoggedIn ) {
85
- $exclude = ( in_array( 'search', $excl ) && in_array( $ipIdentity, $srvProviders->getSearchProviders() ) )
86
- || ( in_array( 'uptime', $excl ) && in_array( $ipIdentity, $srvProviders->getUptimeProviders() ) );
87
  }
88
 
89
  return $exclude;
@@ -101,31 +99,9 @@ class RequestLogger extends ExecOnceModConsumer {
101
  foreach ( $opts->getCustomExclusions() as $excl ) {
102
  if ( stripos( $agent, $excl ) !== false || stripos( $path, $excl ) !== false ) {
103
  $exclude = true;
 
104
  }
105
  }
106
  return $exclude;
107
  }
108
-
109
- /**
110
- * @deprecated 14.1
111
- */
112
- private function isServiceIp_Search() :bool {
113
- return in_array( Services::IP()->getIpDetector()->getIPIdentity(),
114
- Services::ServiceProviders()->getSearchProviders() );
115
- }
116
-
117
- /**
118
- * @deprecated 14.1
119
- */
120
- private function isServiceIp_Uptime() :bool {
121
- $IP = Services::IP();
122
- return in_array( $IP->getIpDetector()->getIPIdentity(), Services::ServiceProviders()->getUptimeProviders() );
123
- }
124
-
125
- /**
126
- * @deprecated 14.1
127
- */
128
- private function isThisServer() :bool {
129
- return Services::IP()->getIpDetector()->getIPIdentity() === IpID::THIS_SERVER;
130
- }
131
  }
69
 
70
  private function isRequestTypeExcluded() :bool {
71
  $srvProviders = Services::ServiceProviders();
72
+ $ipID = Services::IP()->getIpDetector()->getIPIdentity();
73
  /** @var Traffic\Options $opts */
74
  $opts = $this->getOptions();
75
  $excl = $opts->getReqTypeExclusions();
76
  $isLoggedIn = Services::WpUsers()->isUserLoggedIn();
77
 
78
+ $exclude = ( in_array( 'logged_in', $excl ) && $isLoggedIn )
 
 
79
  || ( in_array( 'cron', $excl ) && Services::WpGeneral()->isCron() )
80
+ || ( in_array( 'server', $excl ) && $ipID === IpID::THIS_SERVER );
81
 
82
  if ( !$exclude && !$isLoggedIn ) {
83
+ $exclude = ( in_array( 'search', $excl ) && in_array( $ipID, $srvProviders->getSearchProviders() ) )
84
+ || ( in_array( 'uptime', $excl ) && in_array( $ipID, $srvProviders->getUptimeProviders() ) );
85
  }
86
 
87
  return $exclude;
99
  foreach ( $opts->getCustomExclusions() as $excl ) {
100
  if ( stripos( $agent, $excl ) !== false || stripos( $path, $excl ) !== false ) {
101
  $exclude = true;
102
+ break;
103
  }
104
  }
105
  return $exclude;
106
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  }
src/lib/src/Modules/Traffic/Lib/TrafficTable/DelegateAjaxHandler.php DELETED
@@ -1,46 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic\Lib\TrafficTable;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\Lib\UpgradeReqLogsTable;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Tables\DataTables;
8
- use FernleafSystems\Wordpress\Services\Services;
9
-
10
- class DelegateAjaxHandler {
11
-
12
- use Shield\Modules\ModConsumer;
13
-
14
- /**
15
- * @throws \Exception
16
- */
17
- public function processAjaxAction() :array {
18
- $action = Services::Request()->post( 'sub_action' );
19
- switch ( $action ) {
20
-
21
- case 'retrieve_table_data':
22
- $response = $this->retrieveTableData();
23
- break;
24
-
25
- default:
26
- throw new \Exception( 'Not a supported Audit Trail table sub_action: '.$action );
27
- }
28
- return $response;
29
- }
30
-
31
- /**
32
- * @throws \Exception
33
- */
34
- private function retrieveTableData() :array {
35
- ( new UpgradeReqLogsTable() )
36
- ->setMod( $this->getCon()->getModule_Data() )
37
- ->execute();
38
-
39
- $builder = ( new BuildTrafficTableData() )->setMod( $this->getMod() );
40
- $builder->table_data = Services::Request()->post( 'table_data', [] );
41
- return [
42
- 'success' => true,
43
- 'datatable_data' => $builder->build(),
44
- ];
45
- }
46
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/Traffic/Lib/TrafficTable/LoadRawTableData.php DELETED
@@ -1,233 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic\Lib\TrafficTable;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\IPs\IPGeoVO;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\Lib\GeoIP\Lookup;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Ops\LookupIpOnList;
8
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\ModCon;
9
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\ReqLogs\LoadLogs;
10
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\ReqLogs\LogRecord;
11
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
12
- use FernleafSystems\Wordpress\Plugin\Shield\Tables\DataTables\Build\Traffic\ForTraffic;
13
- use FernleafSystems\Wordpress\Plugin\Shield\Tables\DataTables\LoadData\BaseLoadTableData;
14
- use FernleafSystems\Wordpress\Services\Services;
15
-
16
- class LoadRawTableData extends BaseLoadTableData {
17
-
18
- use ModConsumer;
19
-
20
- /**
21
- * @var LogRecord
22
- */
23
- private $log;
24
-
25
- /**
26
- * @var Lookup
27
- */
28
- private $geoLookup;
29
-
30
- private $users = [];
31
-
32
- private $ipInfo = [];
33
-
34
- /**
35
- * @param LogRecord[] $records
36
- */
37
- protected function buildTableRowsFromRawLogs( array $records ) :array {
38
- $this->users = [ 0 => __( 'No', 'wp-simple-firewall' ) ];
39
-
40
- return array_values( array_filter( array_map(
41
- function ( $log ) {
42
- $WPU = Services::WpUsers();
43
-
44
- $log->meta = array_merge(
45
- [
46
- 'path' => '',
47
- 'code' => '200',
48
- 'ua' => 'Unknown',
49
- 'verb' => 'Unknown',
50
- 'offense' => false,
51
- 'uid' => 0
52
- ],
53
- $log->meta
54
- );
55
-
56
- $this->log = $log;
57
-
58
- $data = $log->getRawData();
59
-
60
- $data[ 'ip' ] = $this->log->ip;
61
- $data[ 'code' ] = $this->log->meta[ 'code' ];
62
- $data[ 'offense' ] = $this->log->meta[ 'offense' ] ? 'Offense' : 'Not Offense';
63
- $data[ 'rid' ] = $this->log->rid ?? __( 'Unknown', 'wp-simple-firewall' );
64
- $data[ 'path' ] = empty( $this->log->meta[ 'path' ] ) ? '-'
65
- : explode( '?', $this->log->meta[ 'path' ], 2 )[ 0 ];
66
-
67
- $geo = $this->getCountryIP( $this->log->ip );
68
- $data[ 'country' ] = empty( $geo->countryCode ) ?
69
- __( 'Unknown', 'wp-simple-firewall' ) : $geo->countryName;
70
-
71
- $userID = $this->log->meta[ 'uid' ] ?? 0;
72
- if ( $userID > 0 ) {
73
- if ( !isset( $users[ $userID ] ) ) {
74
- $user = $WPU->getUserById( $userID );
75
- $this->users[ $userID ] = empty( $user ) ? __( 'Unknown', 'wp-simple-firewall' ) :
76
- sprintf( '<a href="%s" target="_blank" title="Go To Profile">%s</a>',
77
- $WPU->getAdminUrl_ProfileEdit( $user ), $user->user_login );
78
- }
79
- }
80
-
81
- $data[ 'page' ] = $this->getColumnContent_Page();
82
- $data[ 'details' ] = $this->getColumnContent_Details();
83
- $data[ 'response' ] = $this->getColumnContent_Response();
84
- $data[ 'created_since' ] = $this->getColumnContent_Date( $this->log->created_at );
85
- return $data;
86
- },
87
- $records
88
- ) ) );
89
- }
90
-
91
- protected function getSearchableColumns() :array {
92
- // Use the DataTables definition builder to locate searchable columns
93
- return array_filter( array_map(
94
- function ( $column ) {
95
- return ( $column[ 'searchable' ] ?? false ) ? $column[ 'data' ] : '';
96
- },
97
- ( new ForTraffic() )
98
- ->setMod( $this->getMod() )
99
- ->buildRaw()[ 'columns' ]
100
- ) );
101
- }
102
-
103
- /**
104
- * @return LogRecord[]
105
- */
106
- protected function getRecords( int $offset = 0, int $limit = 0 ) :array {
107
- $loader = ( new LoadLogs() )->setMod( $this->getCon()->getModule_Data() );
108
- $loader->limit = $limit;
109
- $loader->offset = $offset;
110
- $loader->order_dir = $this->getOrderDirection();
111
- return $loader->run();
112
- }
113
-
114
- private function getColumnContent_Details() :string {
115
- $geo = $this->getCountryIP( $this->log->ip );
116
- if ( empty( $geo->countryCode ) ) {
117
- $country = __( 'Unknown', 'wp-simple-firewall' );
118
- }
119
- else {
120
- $country = sprintf(
121
- '<img class="icon-flag" src="%s" alt="%s" width="24px"/> %s',
122
- sprintf( 'https://api.aptoweb.com/api/v1/country/flag/%s.svg', strtolower( $geo->countryCode ) ),
123
- $geo->countryCode,
124
- $geo->countryName
125
- );
126
- }
127
-
128
- if ( $this->isWpCli() ) {
129
- $content = 'WP-CLI';
130
- }
131
- else {
132
- $content = sprintf( '<div>%s</div>', implode( '</div><div>', [
133
- sprintf( '%s: %s', __( 'IP', 'wp-simple-firewall' ), $this->getIpAnalysisLink( $this->log->ip ) ),
134
- sprintf( '%s: %s', __( 'IP Status', 'wp-simple-firewall' ), $this->getIpInfo( $this->log->ip ) ),
135
- sprintf( '%s: %s', __( 'Logged-In', 'wp-simple-firewall' ), $this->users[ $this->log->meta[ 'uid' ] ] ),
136
- sprintf( '%s: %s', __( 'Location', 'wp-simple-firewall' ), $country ),
137
- esc_html( esc_js( sprintf( '%s - %s', __( 'User Agent', 'wp-simple-firewall' ), $this->log->meta[ 'ua' ] ) ) ),
138
- ] ) );
139
- }
140
-
141
- return $content;
142
- }
143
-
144
- private function getColumnContent_Response() :string {
145
- if ( $this->log->meta[ 'code' ] >= 400 ) {
146
- $codeType = 'danger';
147
- }
148
- elseif ( $this->log->meta[ 'code' ] >= 300 ) {
149
- $codeType = 'warning';
150
- }
151
- else {
152
- $codeType = 'success';
153
- }
154
-
155
- return sprintf( '<div>%s</div>', implode( '</div><div>', [
156
- sprintf( '%s: %s', __( 'Response', 'wp-simple-firewall' ),
157
- sprintf( '<span class="badge bg-%s">%s</span>', $codeType, $this->log->meta[ 'code' ] ) ),
158
- sprintf( '%s: %s', __( 'Offense', 'wp-simple-firewall' ),
159
- sprintf(
160
- '<span class="badge bg-%s">%s</span>',
161
- @$this->log->meta[ 'offense' ] ? 'danger' : 'info',
162
- @$this->log->meta[ 'offense' ] ? __( 'Yes', 'wp-simple-firewall' ) : __( 'No', 'wp-simple-firewall' )
163
- )
164
- ),
165
- ] ) );
166
- }
167
-
168
- private function getColumnContent_Page() :string {
169
- if ( $this->isWpCli() ) {
170
- $content = sprintf( '<code>:> %s</code>', esc_html( $this->log->meta[ 'path' ] ) );
171
- }
172
- else {
173
- list( $preQuery, $query ) = explode( '?', $this->log->meta[ 'path' ].'?', 2 );
174
- $content = strtoupper( $this->log->meta[ 'verb' ] ).': <code>'.$preQuery
175
- .( empty( $query ) ? '' : '?<br/>'.rtrim( $query, '?' ) ).'</code>';
176
- }
177
- return $content;
178
- }
179
-
180
- private function getIpInfo( string $ip ) {
181
-
182
- if ( !isset( $this->ipInfo[ $ip ] ) ) {
183
-
184
- if ( empty( $ip ) ) {
185
- $this->ipInfo[ '' ] = 'n/a';
186
- }
187
- else {
188
- $badgeTemplate = '<span class="badge bg-%s">%s</span>';
189
- $status = __( 'No Record', 'wp-simple-firewall' );
190
-
191
- $record = ( new LookupIpOnList() )
192
- ->setDbHandler( $this->getCon()->getModule_IPs()->getDbHandler_IPs() )
193
- ->setIP( $ip )
194
- ->lookup();
195
-
196
- if ( empty( $record ) ) {
197
- $status = __( 'No Record', 'wp-simple-firewall' );
198
- }
199
- elseif ( $record->blocked_at > 0 || $record->list === ModCon::LIST_MANUAL_BLACK ) {
200
- $status = sprintf( $badgeTemplate, 'danger', __( 'Blocked', 'wp-simple-firewall' ) );
201
- }
202
- elseif ( $record->list === ModCon::LIST_AUTO_BLACK ) {
203
- $status = sprintf( $badgeTemplate,
204
- 'warning',
205
- sprintf( _n( '%s offense', '%s offenses', $record->transgressions, 'wp-simple-firewall' ), $record->transgressions )
206
- );
207
- }
208
- elseif ( $record->list === ModCon::LIST_MANUAL_WHITE ) {
209
- $status = sprintf( $badgeTemplate,
210
- 'success',
211
- __( 'Bypass', 'wp-simple-firewall' )
212
- );
213
- }
214
- $this->ipInfo[ $ip ] = $status;
215
- }
216
- }
217
-
218
- return $this->ipInfo[ $ip ];
219
- }
220
-
221
- private function getCountryIP( string $ip ) :IPGeoVO {
222
- if ( empty( $this->geoLookup ) ) {
223
- $this->geoLookup = ( new Lookup() )->setCon( $this->getCon() );
224
- }
225
- return $this->geoLookup
226
- ->setIP( $ip )
227
- ->lookupIp();
228
- }
229
-
230
- private function isWpCli() :bool {
231
- return $this->log->meta[ 'ua' ] === 'wpcli';
232
- }
233
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/Traffic/ModCon.php CHANGED
@@ -2,8 +2,6 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Databases;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
@@ -21,6 +19,14 @@ class ModCon extends BaseShield\ModCon {
21
  return $this->requestLogger;
22
  }
23
 
 
 
 
 
 
 
 
 
24
  protected function preProcessOptions() {
25
  /** @var Options $opts */
26
  $opts = $this->getOptions();
@@ -30,6 +36,17 @@ class ModCon extends BaseShield\ModCon {
30
  },
31
  $opts->getCustomExclusions()
32
  ) ) );
 
 
 
 
 
 
 
 
 
 
 
33
  }
34
 
35
  protected function isReadyToExecute() :bool {
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic;
4
 
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
  use FernleafSystems\Wordpress\Services\Services;
7
 
19
  return $this->requestLogger;
20
  }
21
 
22
+ protected function enumRuleBuilders() :array {
23
+ /** @var Options $opts */
24
+ $opts = $this->getOptions();
25
+ return [
26
+ $opts->isTrafficLimitEnabled() ? Rules\Build\IsRateLimitExceeded::class : null,
27
+ ];
28
+ }
29
+
30
  protected function preProcessOptions() {
31
  /** @var Options $opts */
32
  $opts = $this->getOptions();
36
  },
37
  $opts->getCustomExclusions()
38
  ) ) );
39
+
40
+ if ( !$this->getCon()->isPremiumActive() && $opts->isOpt( 'enable_limiter', 'Y' ) ) {
41
+ $opts->isOpt( 'enable_limiter', 'N' );
42
+ }
43
+
44
+ if ( $opts->isOpt( 'enable_limiter', 'Y' ) && !$opts->isTrafficLoggerEnabled() ) {
45
+ $opts->setOpt( 'enable_logger', 'Y' );
46
+ if ( $opts->getAutoCleanDays() === 0 ) {
47
+ $opts->resetOptToDefault( 'auto_clean' );
48
+ }
49
+ }
50
  }
51
 
52
  protected function isReadyToExecute() :bool {
src/lib/src/Modules/Traffic/Processor.php CHANGED
@@ -10,9 +10,5 @@ class Processor extends Modules\BaseShield\Processor {
10
  /** @var ModCon $mod */
11
  $mod = $this->getMod();
12
  $mod->getRequestLogger()->execute();
13
-
14
- ( new Lib\Limit\Limiter() )
15
- ->setMod( $this->getMod() )
16
- ->execute();
17
  }
18
  }
10
  /** @var ModCon $mod */
11
  $mod = $this->getMod();
12
  $mod->getRequestLogger()->execute();
 
 
 
 
13
  }
14
  }
src/lib/src/Modules/Traffic/Rules/Build/IsRateLimitExceeded.php ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Plugin\Rules\Build\RequestBypassesAllRestrictions;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Traffic\Options;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\{
9
+ Build\BuildRuleCoreShieldBase,
10
+ Conditions,
11
+ Responses
12
+ };
13
+
14
+ class IsRateLimitExceeded extends BuildRuleCoreShieldBase {
15
+
16
+ const SLUG = 'shield/is_rate_limit_exceeded';
17
+
18
+ protected function getName() :string {
19
+ return 'Rate Limit Exceeded';
20
+ }
21
+
22
+ protected function getDescription() :string {
23
+ return 'Is traffic rate limit exceeded.';
24
+ }
25
+
26
+ protected function getConditions() :array {
27
+ /** @var Options $opts */
28
+ $opts = $this->getOptions();
29
+ return [
30
+ 'logic' => static::LOGIC_AND,
31
+ 'group' => [
32
+ [
33
+ 'rule' => RequestBypassesAllRestrictions::SLUG,
34
+ 'invert_match' => true
35
+ ],
36
+ [
37
+ 'condition' => Conditions\IsRateLimitExceeded::SLUG,
38
+ 'params' => [
39
+ 'limit_count' => $opts->getLimitRequestCount(),
40
+ 'limit_time_span' => $opts->getLimitTimeSpan(),
41
+ ],
42
+ ],
43
+ ]
44
+ ];
45
+ }
46
+
47
+ protected function getResponses() :array {
48
+ return [
49
+ [
50
+ 'response' => Responses\EventFire::SLUG,
51
+ 'params' => [
52
+ 'event' => 'request_limit_exceeded',
53
+ 'offense_count' => 1,
54
+ 'block' => false,
55
+ 'audit_params_map' => $this->getCommonAuditParamsMapping(),
56
+ ],
57
+ ],
58
+ [
59
+ 'response' => Responses\TrafficRateLimitExceeded::SLUG,
60
+ ],
61
+ ];
62
+ }
63
+
64
+ protected function getCommonAuditParamsMapping() :array {
65
+ return array_merge( parent::getCommonAuditParamsMapping(), [
66
+ 'requests' => 'request_count',
67
+ 'count' => 'limit_count',
68
+ 'span' => 'limit_time_span',
69
+ ] );
70
+ }
71
+ }
src/lib/src/Modules/Traffic/Strings.php CHANGED
@@ -21,13 +21,13 @@ class Strings extends Base\Strings {
21
  }
22
 
23
  public function getSectionStrings( string $section ) :array {
24
- $sModName = $this->getMod()->getMainFeatureName();
25
 
26
  switch ( $section ) {
27
 
28
  case 'section_enable_plugin_feature_traffic' :
29
  $short = sprintf( '%s/%s', __( 'On', 'wp-simple-firewall' ), __( 'Off', 'wp-simple-firewall' ) );
30
- $title = sprintf( __( 'Enable Module: %s', 'wp-simple-firewall' ), $sModName );
31
  $summary = [
32
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Monitor and review all requests to your site.', 'wp-simple-firewall' ) ),
33
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), __( 'Required only if you need to review and investigate and monitor requests to your site', 'wp-simple-firewall' ) )
@@ -61,21 +61,21 @@ class Strings extends Base\Strings {
61
  return [
62
  'title' => $title,
63
  'title_short' => $short,
64
- 'summary' => ( isset( $summary ) && is_array( $summary ) ) ? $summary : [],
65
  ];
66
  }
67
 
68
  public function getOptionStrings( string $key ) :array {
69
  /** @var ModCon $mod */
70
  $mod = $this->getMod();
71
- $sModName = $mod->getMainFeatureName();
72
 
73
  switch ( $key ) {
74
 
75
  case 'enable_traffic' :
76
- $name = sprintf( __( 'Enable %s Module', 'wp-simple-firewall' ), $sModName );
77
- $summary = sprintf( __( 'Enable (or Disable) The %s Module', 'wp-simple-firewall' ), $sModName );
78
- $desc = sprintf( __( 'Un-Checking this option will completely disable the %s module.', 'wp-simple-firewall' ), $sModName );
79
  break;
80
 
81
  case 'enable_logger' :
21
  }
22
 
23
  public function getSectionStrings( string $section ) :array {
24
+ $modName = $this->getMod()->getMainFeatureName();
25
 
26
  switch ( $section ) {
27
 
28
  case 'section_enable_plugin_feature_traffic' :
29
  $short = sprintf( '%s/%s', __( 'On', 'wp-simple-firewall' ), __( 'Off', 'wp-simple-firewall' ) );
30
+ $title = sprintf( __( 'Enable Module: %s', 'wp-simple-firewall' ), $modName );
31
  $summary = [
32
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'Monitor and review all requests to your site.', 'wp-simple-firewall' ) ),
33
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), __( 'Required only if you need to review and investigate and monitor requests to your site', 'wp-simple-firewall' ) )
61
  return [
62
  'title' => $title,
63
  'title_short' => $short,
64
+ 'summary' => $summary ?? [],
65
  ];
66
  }
67
 
68
  public function getOptionStrings( string $key ) :array {
69
  /** @var ModCon $mod */
70
  $mod = $this->getMod();
71
+ $modName = $mod->getMainFeatureName();
72
 
73
  switch ( $key ) {
74
 
75
  case 'enable_traffic' :
76
+ $name = sprintf( __( 'Enable %s Module', 'wp-simple-firewall' ), $modName );
77
+ $summary = sprintf( __( 'Enable (or Disable) The %s Module', 'wp-simple-firewall' ), $modName );
78
+ $desc = sprintf( __( 'Un-Checking this option will completely disable the %s module.', 'wp-simple-firewall' ), $modName );
79
  break;
80
 
81
  case 'enable_logger' :
src/lib/src/Modules/UserManagement/AjaxHandler.php CHANGED
@@ -3,7 +3,6 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Sessions;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
@@ -22,21 +21,12 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
22
  }
23
 
24
  public function ajaxExec_BuildTableSessions() :array {
25
- $con = $this->getCon();
26
- /** @var ModCon $mod */
27
- $mod = $this->getMod();
28
-
29
- ( new UserManagement\Lib\CleanExpired() )
30
- ->setMod( $mod )
31
- ->run();
32
-
33
  /** @var Shield\Modules\SecurityAdmin\Options $optsSecAdmin */
34
- $optsSecAdmin = $con->getModule_SecAdmin()->getOptions();
35
  return [
36
  'success' => true,
37
  'html' => ( new Shield\Tables\Build\Sessions() )
38
- ->setMod( $mod )
39
- ->setDbHandler( $con->getModule_Sessions()->getDbHandler_Sessions() )
40
  ->setSecAdminUsers( $optsSecAdmin->getSecurityAdminUsers() )
41
  ->render()
42
  ];
@@ -57,27 +47,26 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
57
  $msg = __( 'Not a supported action.', 'wp-simple-firewall' );
58
  }
59
  else {
60
- $yourId = $mod->getSession()->id;
61
- $includesYourSession = in_array( $yourId, $IDs );
 
62
 
63
- if ( $includesYourSession && ( count( $IDs ) == 1 ) ) {
64
- $msg = __( 'Please logout if you want to delete your own session.', 'wp-simple-firewall' );
65
- }
66
- else {
67
- $success = true;
68
-
69
- $terminator = ( new Sessions\Lib\Ops\Terminate() )
70
- ->setMod( $this->getCon()->getModule_Sessions() );
71
- foreach ( $IDs as $id ) {
72
- if ( is_numeric( $id ) && ( $id != $yourId ) ) {
73
- $terminator->byRecordId( (int)$id );
74
- }
75
- }
76
- $msg = __( 'Selected items were deleted.', 'wp-simple-firewall' );
77
- if ( $includesYourSession ) {
78
- $msg .= ' *'.__( 'Your session was retained', 'wp-simple-firewall' );
79
  }
 
 
80
  }
 
 
 
 
 
 
 
81
  }
82
 
83
  return [
@@ -91,20 +80,22 @@ class AjaxHandler extends Shield\Modules\BaseShield\AjaxHandler {
91
  /** @var ModCon $mod */
92
  $mod = $this->getMod();
93
  $success = false;
94
- $id = Services::Request()->post( 'rid', -1 );
95
- if ( !is_numeric( $id ) || $id < 0 ) {
 
 
96
  $msg = __( 'Invalid session selected', 'wp-simple-firewall' );
97
  }
98
- elseif ( $mod->getSession()->id === $id ) {
99
  $msg = __( 'Please logout if you want to delete your own session.', 'wp-simple-firewall' );
100
  }
101
- elseif ( $con->getModule_Sessions()->getDbHandler_Sessions()->getQueryDeleter()->deleteById( $id ) ) {
 
 
 
102
  $msg = __( 'User session deleted', 'wp-simple-firewall' );
103
  $success = true;
104
  }
105
- else {
106
- $msg = __( "User session wasn't deleted", 'wp-simple-firewall' );
107
- }
108
 
109
  return [
110
  'success' => $success,
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield;
 
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
21
  }
22
 
23
  public function ajaxExec_BuildTableSessions() :array {
 
 
 
 
 
 
 
 
24
  /** @var Shield\Modules\SecurityAdmin\Options $optsSecAdmin */
25
+ $optsSecAdmin = $this->getCon()->getModule_SecAdmin()->getOptions();
26
  return [
27
  'success' => true,
28
  'html' => ( new Shield\Tables\Build\Sessions() )
29
+ ->setMod( $this->getMod() )
 
30
  ->setSecAdminUsers( $optsSecAdmin->getSecurityAdminUsers() )
31
  ->render()
32
  ];
47
  $msg = __( 'Not a supported action.', 'wp-simple-firewall' );
48
  }
49
  else {
50
+ $sessionCon = $this->getCon()->getModule_Sessions()->getSessionCon();
51
+ $yourId = $mod->getSessionWP()->shield[ 'unique' ] ?? '';
52
+ $includesYourSession = false;
53
 
54
+ foreach ( $IDs as $IDunique ) {
55
+ list( $userID, $uniqueID ) = explode( '-', $IDunique );
56
+ if ( $yourId === $uniqueID ) {
57
+ $includesYourSession = true;
58
+ continue;
 
 
 
 
 
 
 
 
 
 
 
59
  }
60
+
61
+ $sessionCon->removeSessionBasedOnUniqueID( $userID, $uniqueID );
62
  }
63
+
64
+ $msg = __( 'Selected items were deleted.', 'wp-simple-firewall' );
65
+ if ( $includesYourSession ) {
66
+ $msg .= ' *'.__( 'Your session was retained', 'wp-simple-firewall' );
67
+ }
68
+
69
+ $success = true;
70
  }
71
 
72
  return [
80
  /** @var ModCon $mod */
81
  $mod = $this->getMod();
82
  $success = false;
83
+
84
+ list( $userID, $uniqueID ) = explode( '-', Services::Request()->post( 'rid', '' ) );
85
+
86
+ if ( empty( $userID ) || !is_numeric( $userID ) || $userID < 0 || empty( $uniqueID ) ) {
87
  $msg = __( 'Invalid session selected', 'wp-simple-firewall' );
88
  }
89
+ elseif ( $mod->getSessionWP()->shield[ 'unique' ] === $uniqueID ) {
90
  $msg = __( 'Please logout if you want to delete your own session.', 'wp-simple-firewall' );
91
  }
92
+ else {
93
+ $con->getModule_Sessions()
94
+ ->getSessionCon()
95
+ ->removeSessionBasedOnUniqueID( $userID, $uniqueID );
96
  $msg = __( 'User session deleted', 'wp-simple-firewall' );
97
  $success = true;
98
  }
 
 
 
99
 
100
  return [
101
  'success' => $success,
src/lib/src/Modules/UserManagement/Insights/OverviewCards.php DELETED
@@ -1,85 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement\Insights;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
7
- use FernleafSystems\Wordpress\Services\Services;
8
-
9
- class OverviewCards extends Shield\Modules\Base\Insights\OverviewCards {
10
-
11
- protected function buildModCards() :array {
12
- /** @var UserManagement\ModCon $mod */
13
- $mod = $this->getMod();
14
- /** @var UserManagement\Options $opts */
15
- $opts = $this->getOptions();
16
-
17
- $cards = [];
18
-
19
- if ( $mod->isModOptEnabled() ) {
20
- $bHasIdle = $opts->hasSessionIdleTimeout();
21
- $cards[ 'idle' ] = [
22
- 'name' => __( 'Idle Users', 'wp-simple-firewall' ),
23
- 'state' => $bHasIdle ? 1 : -1,
24
- 'summary' => $bHasIdle ?
25
- sprintf( __( 'Idle sessions are terminated after %s hours', 'wp-simple-firewall' ), $opts->getOpt( 'session_idle_timeout_interval' ) )
26
- : __( 'Idle sessions wont be terminated', 'wp-simple-firewall' ),
27
- 'href' => $mod->getUrl_DirectLinkToOption( 'session_idle_timeout_interval' ),
28
- ];
29
-
30
- $bLocked = $opts->isLockToIp();
31
- $cards[ 'lockip' ] = [
32
- 'name' => __( 'Lock To IP', 'wp-simple-firewall' ),
33
- 'state' => $bLocked ? 1 : -1,
34
- 'summary' => $bLocked ?
35
- __( 'Sessions are locked to IP address', 'wp-simple-firewall' )
36
- : __( "Sessions aren't locked to IP address", 'wp-simple-firewall' ),
37
- 'href' => $mod->getUrl_DirectLinkToOption( 'session_lock_location' ),
38
- ];
39
-
40
- $bPolicies = $opts->isPasswordPoliciesEnabled();
41
-
42
- $bPwned = $bPolicies && $opts->isPassPreventPwned();
43
- $cards[ 'pwned' ] = [
44
- 'name' => __( 'Pwned Passwords', 'wp-simple-firewall' ),
45
- 'state' => $bPwned ? 1 : -1,
46
- 'summary' => $bPwned ?
47
- __( 'Pwned passwords are blocked on this site', 'wp-simple-firewall' )
48
- : __( 'Pwned passwords are allowed on this site', 'wp-simple-firewall' ),
49
- 'href' => $mod->getUrl_DirectLinkToOption( 'pass_prevent_pwned' ),
50
- ];
51
-
52
- $bIndepthPolices = $bPolicies && $this->getCon()->isPremiumActive();
53
- $cards[ 'policies' ] = [
54
- 'name' => __( 'Password Policies', 'wp-simple-firewall' ),
55
- 'state' => $bIndepthPolices ? 1 : -1,
56
- 'summary' => $bIndepthPolices ?
57
- __( 'Several password policies are active', 'wp-simple-firewall' )
58
- : __( 'Limited or no password polices are active', 'wp-simple-firewall' ),
59
- 'href' => $mod->getUrl_DirectLinkToSection( 'section_passwords' ),
60
- ];
61
- }
62
-
63
- $oAdmin = Services::WpUsers()->getUserByUsername( 'admin' );
64
- $bActiveAdminUser = $oAdmin instanceof \WP_User && user_can( $oAdmin, 'manage_options' );
65
- $cards[ 'admin_active' ] = [
66
- 'name' => __( 'Admin User', 'wp-simple-firewall' ),
67
- 'summary' => $bActiveAdminUser ?
68
- sprintf( __( "Default 'admin' user is still available", 'wp-simple-firewall' ), $opts->getOpt( 'session_idle_timeout_interval' ) )
69
- : __( "The default 'admin' user is no longer available.", 'wp-simple-firewall' ),
70
- 'href' => $mod->getUrl_DirectLinkToOption( 'session_idle_timeout_interval' ),
71
- 'state' => $bActiveAdminUser ? -2 : 1,
72
- 'help' => __( "The default 'admin' user should be deleted or demoted.", 'wp-simple-firewall' )
73
- ];
74
-
75
- return $cards;
76
- }
77
-
78
- protected function getSectionTitle() :string {
79
- return __( 'User Management', 'wp-simple-firewall' );
80
- }
81
-
82
- protected function getSectionSubTitle() :string {
83
- return __( 'Sessions Control & Password Policies', 'wp-simple-firewall' );
84
- }
85
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/UserManagement/Lib/CleanExpired.php DELETED
@@ -1,44 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement\Lib;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Session;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
8
- use FernleafSystems\Wordpress\Services\Services;
9
-
10
- class CleanExpired {
11
-
12
- use ModConsumer;
13
-
14
- public function run() {
15
- /** @var UserManagement\Options $opts */
16
- $opts = $this->getOptions();
17
- /** @var Session\Delete $terminator */
18
- $terminator = $this->getCon()
19
- ->getModule_Sessions()
20
- ->getDbHandler_Sessions()
21
- ->getQueryDeleter();
22
-
23
- // We use 14 as an outside case. If it's 2 days, WP cookie will expire anyway.
24
- // And if User Management is active, then it'll draw in that value.
25
- $terminator->forExpiredLoginAt( $this->getLoginExpiredBoundary() );
26
-
27
- // Default is ZERO, so we don't want to terminate all sessions if it's never set.
28
- if ( $opts->hasSessionIdleTimeout() ) {
29
- $terminator->forExpiredLoginIdle( $this->getLoginIdleExpiredBoundary() );
30
- }
31
- }
32
-
33
- private function getLoginExpiredBoundary() :int {
34
- /** @var UserManagement\Options $opts */
35
- $opts = $this->getOptions();
36
- return Services::Request()->ts() - $opts->getMaxSessionTime();
37
- }
38
-
39
- private function getLoginIdleExpiredBoundary() :int {
40
- /** @var UserManagement\Options $opts */
41
- $opts = $this->getOptions();
42
- return Services::Request()->ts() - $opts->getIdleTimeoutInterval();
43
- }
44
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/UserManagement/Lib/Registration/EmailValidate.php CHANGED
@@ -13,7 +13,7 @@ class EmailValidate extends ExecOnceModConsumer {
13
  protected function run() {
14
  /** @var UserManagement\Options $opts */
15
  $opts = $this->getOptions();
16
- if ( $opts->getValidateEmailOnRegistration() != 'disabled' ) {
17
  add_filter( 'wp_pre_insert_user_data', [ $this, 'validateNewUserEmail' ] );
18
  }
19
  }
13
  protected function run() {
14
  /** @var UserManagement\Options $opts */
15
  $opts = $this->getOptions();
16
+ if ( $opts->isValidateEmailOnRegistration() ) {
17
  add_filter( 'wp_pre_insert_user_data', [ $this, 'validateNewUserEmail' ] );
18
  }
19
  }
src/lib/src/Modules/UserManagement/Lib/RetrieveActive.php DELETED
@@ -1,64 +0,0 @@
1
- <?php
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement\Lib;
4
-
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Session;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
8
- use FernleafSystems\Wordpress\Services\Services;
9
-
10
- class RetrieveActive {
11
-
12
- use ModConsumer;
13
-
14
- /**
15
- * @return int
16
- */
17
- public function count() {
18
- return $this->getSelector()->count();
19
- }
20
-
21
- /**
22
- * @return Session\EntryVO[]
23
- */
24
- public function retrieve() {
25
- return $this->getSelector()->query();
26
- }
27
-
28
- /**
29
- * @return Session\Select
30
- */
31
- private function getSelector() {
32
- /** @var UserManagement\Options $opt */
33
- $opt = $this->getOptions();
34
-
35
- /** @var Session\Select $oSel */
36
- $oSel = $this->getMod()->getDbHandler_Sessions()->getQuerySelector();
37
-
38
- if ( $opt->hasMaxSessionTimeout() ) {
39
- $oSel->filterByLoginNotExpired( $this->getLoginExpiredBoundary() );
40
- }
41
- if ( $opt->hasSessionIdleTimeout() ) {
42
- $oSel->filterByLoginNotIdleExpired( $this->getLoginIdleExpiredBoundary() );
43
- }
44
- return $oSel;
45
- }
46
-
47
- /**
48
- * @return int
49
- */
50
- private function getLoginExpiredBoundary() {
51
- /** @var UserManagement\Options $oOpts */
52
- $oOpts = $this->getOptions();
53
- return Services::Request()->ts() - $oOpts->getMaxSessionTime();
54
- }
55
-
56
- /**
57
- * @return int
58
- */
59
- private function getLoginIdleExpiredBoundary() {
60
- /** @var UserManagement\Options $oOpts */
61
- $oOpts = $this->getOptions();
62
- return Services::Request()->ts() - $oOpts->getIdleTimeoutInterval();
63
- }
64
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Modules/UserManagement/Lib/Session/FindSessions.php ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement\Lib\Session;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ class FindSessions {
9
+
10
+ use ModConsumer;
11
+
12
+ public function mostRecent() :array {
13
+ return $this->lookupFromUserMeta();
14
+ }
15
+
16
+ public function byIP( string $ip ) :array {
17
+ $sessions = [];
18
+ foreach ( $this->lookupFromUserMeta( $ip ) as $userID => $userAtIP ) {
19
+ $sessions[ $userID ] = array_map(
20
+ function ( $sess ) use ( $userAtIP ) {
21
+ $sess[ 'user_login' ] = $userAtIP[ 'user_login' ];
22
+ return $sess;
23
+ },
24
+ \WP_Session_Tokens::get_instance( $userAtIP[ 'user_id' ] )->get_all()
25
+ );
26
+ }
27
+ return $sessions;
28
+ }
29
+
30
+ public function lookupFromUserMeta( string $ip = '', int $limit = 10, string $orderBy = '`user_meta`.last_login_at' ) :array {
31
+ $modData = $this->getCon()->getModule_Data();
32
+
33
+ $wheres = [];
34
+ if ( !empty( $ip ) ) {
35
+ $wheres[] = sprintf( "`ips`.ip=INET6_ATON('%s')", $ip );
36
+ }
37
+
38
+ $DB = Services::WpDb();
39
+ $results = $DB->selectCustom(
40
+ sprintf( 'SELECT `user_meta`.user_id as user_id,
41
+ `user_meta`.last_login_at as last_login_at,
42
+ INET6_NTOA(`ips`.ip) as ip,
43
+ `wp_users`.user_login as user_login
44
+
45
+ FROM `%s` as `user_meta`
46
+ INNER JOIN `%s` as `ips`
47
+ ON `user_meta`.ip_ref = `ips`.id
48
+ INNER JOIN `%s` as `wp_users`
49
+ ON `user_meta`.user_id = `wp_users`.id
50
+ %s
51
+ ORDER BY %s DESC
52
+ %s;',
53
+ $modData->getDbH_UserMeta()->getTableSchema()->table,
54
+ $modData->getDbH_IPs()->getTableSchema()->table,
55
+ $DB->getTable_Users(),
56
+ empty( $wheres ) ? '' : 'WHERE '.implode( ' AND ', $wheres ),
57
+ $orderBy,
58
+ empty( $limit ) ? '' : 'LIMIT '.$limit
59
+ )
60
+ );
61
+
62
+ $byUserIDs = [];
63
+ foreach ( is_array( $results ) ? $results : [] as $result ) {
64
+ $byUserIDs[ $result[ 'user_id' ] ] = $result;
65
+ }
66
+
67
+ return $byUserIDs;
68
+ }
69
+ }
src/lib/src/Modules/UserManagement/Lib/Session/UserSessionHandler.php CHANGED
@@ -11,13 +11,6 @@ class UserSessionHandler extends ExecOnceModConsumer {
11
 
12
  use WpLoginCapture;
13
 
14
- protected function canRun() :bool {
15
- return $this->getCon()
16
- ->getModule_Sessions()
17
- ->getDbHandler_Sessions()
18
- ->isReady();
19
- }
20
-
21
  protected function run() {
22
  $this->setupLoginCaptureHooks();
23
  add_action( 'wp_loaded', [ $this, 'onWpLoaded' ] );
@@ -26,7 +19,6 @@ class UserSessionHandler extends ExecOnceModConsumer {
26
  }
27
 
28
  protected function captureLogin( \WP_User $user ) {
29
- $this->enforceSessionLimits( $user );
30
  }
31
 
32
  public function onWpLoaded() {
@@ -39,7 +31,6 @@ class UserSessionHandler extends ExecOnceModConsumer {
39
  $con = $this->getCon();
40
  $srvIP = Services::IP();
41
 
42
- $user = Services::WpUsers()->getCurrentWpUser();
43
  try {
44
  if ( !empty( $srvIP->isValidIp( $srvIP->getRequestIp() ) ) && !$srvIP->isLoopback() ) {
45
  $this->assessSession();
@@ -54,7 +45,7 @@ class UserSessionHandler extends ExecOnceModConsumer {
54
 
55
  $con->fireEvent( $event, [
56
  'audit_params' => [
57
- 'user_login' => $user->user_login
58
  ]
59
  ] );
60
 
@@ -77,18 +68,18 @@ class UserSessionHandler extends ExecOnceModConsumer {
77
  $sess = $this->getCon()
78
  ->getModule_Sessions()
79
  ->getSessionCon()
80
- ->getCurrent();
81
- if ( empty( $sess ) ) {
82
  throw new \Exception( 'session_notfound' );
83
  }
84
 
85
  $ts = Services::Request()->ts();
86
 
87
- if ( $opts->hasMaxSessionTimeout() && ( $ts - $sess->logged_in_at > $opts->getMaxSessionTime() ) ) {
88
  throw new \Exception( 'session_expired' );
89
  }
90
 
91
- if ( $opts->hasSessionIdleTimeout() && ( $ts - $sess->last_activity_at > $opts->getIdleTimeoutInterval() ) ) {
92
  throw new \Exception( 'session_idle' );
93
  }
94
 
@@ -108,25 +99,6 @@ class UserSessionHandler extends ExecOnceModConsumer {
108
  return $opts->hasMaxSessionTimeout() ? min( $timeout, $opts->getMaxSessionTime() ) : $timeout;
109
  }
110
 
111
- private function enforceSessionLimits( \WP_User $user ) {
112
- /** @var UserManagement\Options $opts */
113
- $opts = $this->getOptions();
114
-
115
- $sessionLimit = (int)$opts->getOpt( 'session_username_concurrent_limit', 1 );
116
- if ( $sessionLimit > 0 ) {
117
- try {
118
- $this->getCon()
119
- ->getModule_Sessions()
120
- ->getDbHandler_Sessions()
121
- ->getQueryDeleter()
122
- ->addWhere( 'wp_username', $user->user_login )
123
- ->deleteExcess( $sessionLimit, 'last_activity_at', true );
124
- }
125
- catch ( \Exception $e ) {
126
- }
127
- }
128
- }
129
-
130
  /**
131
  * @param \WP_Error $error
132
  * @return \WP_Error
11
 
12
  use WpLoginCapture;
13
 
 
 
 
 
 
 
 
14
  protected function run() {
15
  $this->setupLoginCaptureHooks();
16
  add_action( 'wp_loaded', [ $this, 'onWpLoaded' ] );
19
  }
20
 
21
  protected function captureLogin( \WP_User $user ) {
 
22
  }
23
 
24
  public function onWpLoaded() {
31
  $con = $this->getCon();
32
  $srvIP = Services::IP();
33
 
 
34
  try {
35
  if ( !empty( $srvIP->isValidIp( $srvIP->getRequestIp() ) ) && !$srvIP->isLoopback() ) {
36
  $this->assessSession();
45
 
46
  $con->fireEvent( $event, [
47
  'audit_params' => [
48
+ 'user_login' => Services::WpUsers()->getCurrentWpUser()->user_login
49
  ]
50
  ] );
51
 
68
  $sess = $this->getCon()
69
  ->getModule_Sessions()
70
  ->getSessionCon()
71
+ ->getCurrentWP();
72
+ if ( !$sess->valid ) {
73
  throw new \Exception( 'session_notfound' );
74
  }
75
 
76
  $ts = Services::Request()->ts();
77
 
78
+ if ( $opts->hasMaxSessionTimeout() && ( $ts - $sess->login > $opts->getMaxSessionTime() ) ) {
79
  throw new \Exception( 'session_expired' );
80
  }
81
 
82
+ if ( $opts->hasSessionIdleTimeout() && ( $ts - $sess->shield[ 'last_activity_at' ] > $opts->getIdleTimeoutInterval() ) ) {
83
  throw new \Exception( 'session_idle' );
84
  }
85
 
99
  return $opts->hasMaxSessionTimeout() ? min( $timeout, $opts->getMaxSessionTime() ) : $timeout;
100
  }
101
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  /**
103
  * @param \WP_Error $error
104
  * @return \WP_Error
src/lib/src/Modules/UserManagement/Lib/Suspend/UserSuspendController.php CHANGED
@@ -1,10 +1,9 @@
1
- <?php
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement\Lib\Suspend;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Common\ExecOnceModConsumer;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\UserMeta\Ops\Select;
7
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Sessions\Lib\Ops\Terminate;
8
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
9
  use FernleafSystems\Wordpress\Services\Services;
10
 
@@ -17,12 +16,10 @@ class UserSuspendController extends ExecOnceModConsumer {
17
  }
18
 
19
  protected function run() {
20
- /** @var UserManagement\ModCon $mod */
21
- $mod = $this->getMod();
22
  /** @var UserManagement\Options $opts */
23
  $opts = $this->getOptions();
24
 
25
- if ( !$mod->isVisitorWhitelisted() ) {
26
 
27
  if ( $opts->isSuspendManualEnabled() ) {
28
  ( new Suspended() )
@@ -218,10 +215,8 @@ class UserSuspendController extends ExecOnceModConsumer {
218
  $mod = $this->getMod();
219
  $mod->addRemoveHardSuspendUser( $user, $isSuspend );
220
 
221
- if ( $isSuspend ) { // Delete any existing user sessions
222
- ( new Terminate() )
223
- ->setMod( $con->getModule_Sessions() )
224
- ->byUsername( $user->user_login );
225
  }
226
  }
227
  }
1
+ <?php declare( strict_types=1 );
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement\Lib\Suspend;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Common\ExecOnceModConsumer;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\UserMeta\Ops\Select;
 
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
16
  }
17
 
18
  protected function run() {
 
 
19
  /** @var UserManagement\Options $opts */
20
  $opts = $this->getOptions();
21
 
22
+ if ( !$this->getCon()->this_req->is_ip_whitelisted ) {
23
 
24
  if ( $opts->isSuspendManualEnabled() ) {
25
  ( new Suspended() )
215
  $mod = $this->getMod();
216
  $mod->addRemoveHardSuspendUser( $user, $isSuspend );
217
 
218
+ if ( $isSuspend ) {
219
+ \WP_Session_Tokens::get_instance( $user->ID )->destroy_all();
 
 
220
  }
221
  }
222
  }
src/lib/src/Modules/UserManagement/ModCon.php CHANGED
@@ -57,8 +57,8 @@ class ModCon extends BaseShield\ModCon {
57
 
58
  $opts->setOpt( 'auto_idle_roles',
59
  array_unique( array_filter( array_map(
60
- function ( $sRole ) {
61
- return preg_replace( '#[^\sa-z0-9_-]#i', '', trim( strtolower( $sRole ) ) );
62
  },
63
  $opts->getSuspendAutoIdleUserRoles()
64
  ) ) )
57
 
58
  $opts->setOpt( 'auto_idle_roles',
59
  array_unique( array_filter( array_map(
60
+ function ( $role ) {
61
+ return preg_replace( '#[^\s\da-z_-]#i', '', trim( strtolower( $role ) ) );
62
  },
63
  $opts->getSuspendAutoIdleUserRoles()
64
  ) ) )
src/lib/src/Modules/UserManagement/Options.php CHANGED
@@ -31,11 +31,8 @@ class Options extends BaseShield\Options {
31
  return ( $this->isPasswordPoliciesEnabled() && $this->isPremium() ) ? (int)$this->getOpt( 'pass_expire' ) : 0;
32
  }
33
 
34
- /**
35
- * @return int seconds
36
- */
37
- public function getPassExpireTimeout() {
38
- return $this->getPassExpireDays()*DAY_IN_SECONDS;
39
  }
40
 
41
  public function getPassMinLength() :int {
@@ -79,21 +76,22 @@ class Options extends BaseShield\Options {
79
 
80
  public function isSuspendAutoPasswordEnabled() :bool {
81
  return $this->isOpt( 'auto_password', 'Y' )
82
- && $this->isPasswordPoliciesEnabled() && $this->getPassExpireTimeout();
83
  }
84
 
85
  public function isSuspendManualEnabled() :bool {
86
  return $this->isOpt( 'manual_suspend', 'Y' );
87
  }
88
 
89
- /**
90
- * @return string
91
- */
92
- public function getValidateEmailOnRegistration() {
93
- return $this->isPremium() ? $this->getOpt( 'reg_email_validate', 'disabled' ) : 'disabled';
94
  }
95
 
96
  public function getEmailValidationChecks() :array {
97
  return $this->getOpt( 'email_checks', [] );
98
  }
 
 
 
 
99
  }
31
  return ( $this->isPasswordPoliciesEnabled() && $this->isPremium() ) ? (int)$this->getOpt( 'pass_expire' ) : 0;
32
  }
33
 
34
+ public function getPassExpireTimeout() :int {
35
+ return $this->getPassExpireDays()*DAY_IN_SECONDS; /* seconds */
 
 
 
36
  }
37
 
38
  public function getPassMinLength() :int {
76
 
77
  public function isSuspendAutoPasswordEnabled() :bool {
78
  return $this->isOpt( 'auto_password', 'Y' )
79
+ && $this->isPasswordPoliciesEnabled() && ( $this->getPassExpireTimeout() > 0 );
80
  }
81
 
82
  public function isSuspendManualEnabled() :bool {
83
  return $this->isOpt( 'manual_suspend', 'Y' );
84
  }
85
 
86
+ public function getValidateEmailOnRegistration() :string {
87
+ return $this->isPremium() ? (string)$this->getOpt( 'reg_email_validate', 'disabled' ) : 'disabled';
 
 
 
88
  }
89
 
90
  public function getEmailValidationChecks() :array {
91
  return $this->getOpt( 'email_checks', [] );
92
  }
93
+
94
+ public function isValidateEmailOnRegistration() :bool {
95
+ return $this->getValidateEmailOnRegistration() !== 'disabled' && !empty( $this->getEmailValidationChecks() );
96
+ }
97
  }
src/lib/src/Modules/UserManagement/Processor.php CHANGED
@@ -2,9 +2,8 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Session\EntryVO;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Session\Select;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
 
8
  use FernleafSystems\Wordpress\Plugin\Shield\Users\BulkUpdateUserMeta;
9
  use FernleafSystems\Wordpress\Services\Services;
10
 
@@ -25,7 +24,7 @@ class Processor extends BaseShield\Processor {
25
  /** Everything from this point on must consider XMLRPC compatibility **/
26
 
27
  // XML-RPC Compatibility
28
- if ( Services::WpGeneral()->isXmlrpc() && $mod->isXmlrpcBypass() ) {
29
  return;
30
  }
31
 
@@ -41,7 +40,7 @@ class Processor extends BaseShield\Processor {
41
  $this->getCon()->getUserMeta( Services::WpUsers()->getUserById( $userID ) );
42
  } );
43
 
44
- if ( !$mod->isVisitorWhitelisted() ) {
45
  ( new Lib\Session\UserSessionHandler() )
46
  ->setMod( $this->getMod() )
47
  ->execute();
@@ -57,24 +56,28 @@ class Processor extends BaseShield\Processor {
57
  public function addAdminBarMenuGroup( array $groups ) :array {
58
  $con = $this->getCon();
59
  $WPUsers = Services::WpUsers();
60
- /** @var Select $sel */
61
- $sel = $con->getModule_Sessions()->getDbHandler_Sessions()->getQuerySelector();
62
- $sel->filterByLoginNotIdleExpired( Services::Request()->carbon()->subMinutes( 10 )->timestamp )
63
- ->setOrderBy( 'last_activity_at', 'DESC' );
64
 
65
  $thisGroup = [
66
- 'title' => __( 'Recent Sessions', 'wp-simple-firewall' ),
67
  'href' => $con->getModule_Insights()->getUrl_Sessions(),
68
  'items' => [],
69
  ];
70
- /** @var EntryVO $session */
71
- foreach ( $sel->query() as $session ) {
72
- $thisGroup[ 'items' ][] = [
73
- 'id' => $con->prefix( 'session-'.$session->id ),
74
- 'title' => sprintf( '<a href="%s">%s (%s)</a>',
75
- $WPUsers->getAdminUrl_ProfileEdit( $WPUsers->getUserByUsername( $session->wp_username ) ),
76
- $session->wp_username, $session->ip ),
77
- ];
 
 
 
 
78
  }
79
 
80
  if ( !empty( $thisGroup[ 'items' ] ) ) {
@@ -266,10 +269,4 @@ class Processor extends BaseShield\Processor {
266
  ->setCon( $this->getCon() )
267
  ->execute();
268
  }
269
-
270
- public function runDailyCron() {
271
- ( new Lib\CleanExpired() )
272
- ->setMod( $this->getMod() )
273
- ->run();
274
- }
275
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
4
 
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement\Lib\Session\FindSessions;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Users\BulkUpdateUserMeta;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
24
  /** Everything from this point on must consider XMLRPC compatibility **/
25
 
26
  // XML-RPC Compatibility
27
+ if ( $this->getCon()->this_req->wp_is_xmlrpc && $mod->isXmlrpcBypass() ) {
28
  return;
29
  }
30
 
40
  $this->getCon()->getUserMeta( Services::WpUsers()->getUserById( $userID ) );
41
  } );
42
 
43
+ if ( !$this->getCon()->this_req->request_bypasses_all_restrictions ) {
44
  ( new Lib\Session\UserSessionHandler() )
45
  ->setMod( $this->getMod() )
46
  ->execute();
56
  public function addAdminBarMenuGroup( array $groups ) :array {
57
  $con = $this->getCon();
58
  $WPUsers = Services::WpUsers();
59
+
60
+ $recent = ( new FindSessions() )
61
+ ->setMod( $this->getMod() )
62
+ ->mostRecent();
63
 
64
  $thisGroup = [
65
+ 'title' => __( 'Recent Users', 'wp-simple-firewall' ),
66
  'href' => $con->getModule_Insights()->getUrl_Sessions(),
67
  'items' => [],
68
  ];
69
+ if ( !empty( $recent ) ) {
70
+
71
+ foreach ( $recent as $userID => $user ) {
72
+ $thisGroup[ 'items' ][] = [
73
+ 'id' => $con->prefix( 'meta-'.$userID ),
74
+ 'title' => sprintf( '<a href="%s">%s (%s)</a>',
75
+ $WPUsers->getAdminUrl_ProfileEdit( $userID ),
76
+ $user[ 'user_login' ],
77
+ $user[ 'ip' ]
78
+ ),
79
+ ];
80
+ }
81
  }
82
 
83
  if ( !empty( $thisGroup[ 'items' ] ) ) {
269
  ->setCon( $this->getCon() )
270
  ->execute();
271
  }
 
 
 
 
 
 
272
  }
src/lib/src/Modules/UserManagement/Strings.php CHANGED
@@ -96,13 +96,12 @@ class Strings extends Base\Strings {
96
  }
97
 
98
  public function getSectionStrings( string $section ) :array {
99
- $sModName = $this->getMod()->getMainFeatureName();
100
-
101
  switch ( $section ) {
102
 
103
  case 'section_enable_plugin_feature_user_accounts_management' :
104
  $titleShort = sprintf( '%s/%s', __( 'On', 'wp-simple-firewall' ), __( 'Off', 'wp-simple-firewall' ) );
105
- $title = sprintf( __( 'Enable Module: %s', 'wp-simple-firewall' ), $sModName );
 
106
  $summary = [
107
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'User Management offers real user sessions, finer control over user session time-out, and ensures users have logged-in in a correct manner.', 'wp-simple-firewall' ) ),
108
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), sprintf( __( 'Keep the %s feature turned on.', 'wp-simple-firewall' ), __( 'User Management', 'wp-simple-firewall' ) ) )
@@ -167,23 +166,25 @@ class Strings extends Base\Strings {
167
  }
168
 
169
  public function getOptionStrings( string $key ) :array {
170
- $oOpts = $this->getOptions();
171
- $sModName = $this->getMod()->getMainFeatureName();
172
 
173
  switch ( $key ) {
174
 
175
  case 'enable_user_management' :
176
- $name = sprintf( __( 'Enable %s Module', 'wp-simple-firewall' ), $sModName );
177
- $summary = sprintf( __( 'Enable (or Disable) The %s Module', 'wp-simple-firewall' ), $sModName );
178
- $description = sprintf( __( 'Un-Checking this option will completely disable the %s module.', 'wp-simple-firewall' ), $sModName );
179
  break;
180
 
181
  case 'enable_admin_login_email_notification' :
182
  $name = __( 'Admin Login Notification Email', 'wp-simple-firewall' );
183
  $summary = __( 'Send An Notification Email When Administrator Logs In', 'wp-simple-firewall' );
184
- $description = __( 'If you would like to be notified every time an administrator user logs into this WordPress site, enter a notification email address.', 'wp-simple-firewall' )
185
- .'<br />'.__( 'No email address - No Notification.', 'wp-simple-firewall' )
186
- .'<br />'.__( 'Pro customers may provide multiple email address, separated by commas.', 'wp-simple-firewall' );
 
 
187
  break;
188
 
189
  case 'enable_user_login_email_notification' :
@@ -195,17 +196,21 @@ class Strings extends Base\Strings {
195
  case 'session_timeout_interval' :
196
  $name = __( 'Session Timeout', 'wp-simple-firewall' );
197
  $summary = __( 'Specify How Many Days After Login To Automatically Force Re-Login', 'wp-simple-firewall' );
198
- $description = __( 'WordPress default is 2 days, or 14 days if you check the "Remember Me" box.', 'wp-simple-firewall' )
199
- .'<br />'.__( 'Think of this as an absolute maximum possible session length.', 'wp-simple-firewall' )
200
- .'<br />'.sprintf( __( 'This cannot be less than %s.', 'wp-simple-firewall' ), '<strong>1</strong>' )
201
- .' '.sprintf( '%s: %s', __( 'Default', 'wp-simple-firewall' ), '<strong>'.$oOpts->getOptDefault( 'session_timeout_interval' ).'</strong>' );
 
 
202
  break;
203
 
204
  case 'session_idle_timeout_interval' :
205
  $name = __( 'Idle Timeout', 'wp-simple-firewall' );
206
  $summary = __( 'Specify How Many Hours After Inactivity To Automatically Logout User', 'wp-simple-firewall' );
207
- $description = __( 'If the user is inactive for the number of hours specified, they will be forcefully logged out next time they return.', 'wp-simple-firewall' )
208
- .'<br />'.sprintf( __( 'Set to %s to turn off this option.', 'wp-simple-firewall' ), '"<strong>0</strong>"' );
 
 
209
  break;
210
 
211
  case 'session_lock_location' :
@@ -218,8 +223,10 @@ class Strings extends Base\Strings {
218
  case 'session_username_concurrent_limit' :
219
  $name = __( 'Max Simultaneous Sessions', 'wp-simple-firewall' );
220
  $summary = __( 'Limit Simultaneous Sessions For The Same Username', 'wp-simple-firewall' );
221
- $description = __( 'The number provided here is the maximum number of simultaneous, distinct, sessions allowed for any given username.', 'wp-simple-firewall' )
222
- .'<br />'.__( "Zero (0) will allow unlimited simultaneous sessions.", 'wp-simple-firewall' );
 
 
223
  break;
224
 
225
  case 'reg_email_validate' :
@@ -227,7 +234,7 @@ class Strings extends Base\Strings {
227
  $summary = __( 'Validate Email Addresses When User Attempts To Register', 'wp-simple-firewall' );
228
  $description = [
229
  __( 'Validate Email Addresses When User Attempts To Register.', 'wp-simple-firewall' ),
230
- __( 'To validate an email your site sends a request to the WPHashes API and may cause a small delay during the user registration request.', 'wp-simple-firewall' ),
231
  ];
232
  break;
233
 
@@ -308,7 +315,7 @@ class Strings extends Base\Strings {
308
  .'<br/>'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( 'Take a new line for each user role.', 'wp-simple-firewall' ) )
309
  .'<br/>'.sprintf( '%s: %s', __( 'Available Roles', 'wp-simple-firewall' ), implode( ', ', Services::WpUsers()
310
  ->getAvailableUserRoles() ) )
311
- .'<br/>'.sprintf( '%s: %s', __( 'Default', 'wp-simple-firewall' ), implode( ', ', $oOpts->getOptDefault( 'auto_idle_roles' ) ) );
312
  break;
313
 
314
  default:
96
  }
97
 
98
  public function getSectionStrings( string $section ) :array {
 
 
99
  switch ( $section ) {
100
 
101
  case 'section_enable_plugin_feature_user_accounts_management' :
102
  $titleShort = sprintf( '%s/%s', __( 'On', 'wp-simple-firewall' ), __( 'Off', 'wp-simple-firewall' ) );
103
+ $title = sprintf( __( 'Enable Module: %s', 'wp-simple-firewall' ),
104
+ $this->getMod()->getMainFeatureName() );
105
  $summary = [
106
  sprintf( '%s - %s', __( 'Purpose', 'wp-simple-firewall' ), __( 'User Management offers real user sessions, finer control over user session time-out, and ensures users have logged-in in a correct manner.', 'wp-simple-firewall' ) ),
107
  sprintf( '%s - %s', __( 'Recommendation', 'wp-simple-firewall' ), sprintf( __( 'Keep the %s feature turned on.', 'wp-simple-firewall' ), __( 'User Management', 'wp-simple-firewall' ) ) )
166
  }
167
 
168
  public function getOptionStrings( string $key ) :array {
169
+ $opts = $this->getOptions();
170
+ $name = $this->getMod()->getMainFeatureName();
171
 
172
  switch ( $key ) {
173
 
174
  case 'enable_user_management' :
175
+ $name = sprintf( __( 'Enable %s Module', 'wp-simple-firewall' ), $name );
176
+ $summary = sprintf( __( 'Enable (or Disable) The %s Module', 'wp-simple-firewall' ), $name );
177
+ $description = sprintf( __( 'Un-Checking this option will completely disable the %s module.', 'wp-simple-firewall' ), $name );
178
  break;
179
 
180
  case 'enable_admin_login_email_notification' :
181
  $name = __( 'Admin Login Notification Email', 'wp-simple-firewall' );
182
  $summary = __( 'Send An Notification Email When Administrator Logs In', 'wp-simple-firewall' );
183
+ $description = [
184
+ __( 'If you would like to be notified every time an administrator user logs into this WordPress site, enter a notification email address.', 'wp-simple-firewall' ),
185
+ __( 'No email address - No Notification.', 'wp-simple-firewall' ),
186
+ __( 'Pro customers may provide multiple email address, separated by commas.', 'wp-simple-firewall' )
187
+ ];
188
  break;
189
 
190
  case 'enable_user_login_email_notification' :
196
  case 'session_timeout_interval' :
197
  $name = __( 'Session Timeout', 'wp-simple-firewall' );
198
  $summary = __( 'Specify How Many Days After Login To Automatically Force Re-Login', 'wp-simple-firewall' );
199
+ $description = [
200
+ __( 'WordPress default is 2 days, or 14 days if you check the "Remember Me" box.', 'wp-simple-firewall' ),
201
+ __( 'Think of this as an absolute maximum possible session length.', 'wp-simple-firewall' ),
202
+ sprintf( __( 'This cannot be less than %s.', 'wp-simple-firewall' ), '<strong>1</strong>' ),
203
+ sprintf( '%s: %s', __( 'Default', 'wp-simple-firewall' ), '<strong>'.$opts->getOptDefault( 'session_timeout_interval' ).'</strong>' )
204
+ ];
205
  break;
206
 
207
  case 'session_idle_timeout_interval' :
208
  $name = __( 'Idle Timeout', 'wp-simple-firewall' );
209
  $summary = __( 'Specify How Many Hours After Inactivity To Automatically Logout User', 'wp-simple-firewall' );
210
+ $description = [
211
+ __( 'If the user is inactive for the number of hours specified, they will be forcefully logged out next time they return.', 'wp-simple-firewall' ),
212
+ sprintf( __( 'Set to %s to turn off this option.', 'wp-simple-firewall' ), '"<strong>0</strong>"' )
213
+ ];
214
  break;
215
 
216
  case 'session_lock_location' :
223
  case 'session_username_concurrent_limit' :
224
  $name = __( 'Max Simultaneous Sessions', 'wp-simple-firewall' );
225
  $summary = __( 'Limit Simultaneous Sessions For The Same Username', 'wp-simple-firewall' );
226
+ $description = [
227
+ __( 'The number provided here is the maximum number of simultaneous, distinct, sessions allowed for any given username.', 'wp-simple-firewall' ),
228
+ __( "Zero (0) will allow unlimited simultaneous sessions.", 'wp-simple-firewall' )
229
+ ];
230
  break;
231
 
232
  case 'reg_email_validate' :
234
  $summary = __( 'Validate Email Addresses When User Attempts To Register', 'wp-simple-firewall' );
235
  $description = [
236
  __( 'Validate Email Addresses When User Attempts To Register.', 'wp-simple-firewall' ),
237
+ __( 'To validate an email your site sends a request to the ShieldNET API and may cause a small delay during the user registration request.', 'wp-simple-firewall' ),
238
  ];
239
  break;
240
 
315
  .'<br/>'.sprintf( '%s: %s', __( 'Important', 'wp-simple-firewall' ), __( 'Take a new line for each user role.', 'wp-simple-firewall' ) )
316
  .'<br/>'.sprintf( '%s: %s', __( 'Available Roles', 'wp-simple-firewall' ), implode( ', ', Services::WpUsers()
317
  ->getAvailableUserRoles() ) )
318
+ .'<br/>'.sprintf( '%s: %s', __( 'Default', 'wp-simple-firewall' ), implode( ', ', $opts->getOptDefault( 'auto_idle_roles' ) ) );
319
  break;
320
 
321
  default:
src/lib/src/Modules/UserManagement/UI.php CHANGED
@@ -2,20 +2,13 @@
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
4
 
5
- use FernleafSystems\Wordpress\Plugin\Shield\Databases\Session\Select;
6
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
7
 
8
  class UI extends BaseShield\UI {
9
 
10
  public function buildInsightsVars() :array {
11
- $con = $this->getCon();
12
  /** @var ModCon $mod */
13
  $mod = $this->getMod();
14
- /** @var Select $dbSel */
15
- $dbSel = $con->getModule_Sessions()
16
- ->getDbHandler_Sessions()
17
- ->getQuerySelector();
18
-
19
  return [
20
  'ajax' => [
21
  'render_table_sessions' => $mod->getAjaxActionData( 'render_table_sessions', true ),
@@ -32,9 +25,39 @@ class UI extends BaseShield\UI {
32
  'username' => __( 'Username', 'wp-simple-firewall' ),
33
  ],
34
  'vars' => [
35
- 'unique_ips' => $dbSel->getDistinctIps(),
36
- 'unique_users' => $dbSel->getDistinctUsernames(),
37
  ],
38
  ];
39
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  }
2
 
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
4
 
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\BaseShield;
6
 
7
  class UI extends BaseShield\UI {
8
 
9
  public function buildInsightsVars() :array {
 
10
  /** @var ModCon $mod */
11
  $mod = $this->getMod();
 
 
 
 
 
12
  return [
13
  'ajax' => [
14
  'render_table_sessions' => $mod->getAjaxActionData( 'render_table_sessions', true ),
25
  'username' => __( 'Username', 'wp-simple-firewall' ),
26
  ],
27
  'vars' => [
28
+ 'unique_users' => $this->getDistinctUsernames(),
 
29
  ],
30
  ];
31
  }
32
+
33
+ private function getDistinctUsernames() :array {
34
+ /** @var \FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\UserMeta\Ops\Select $metaSelect */
35
+ $metaSelect = $this->getCon()
36
+ ->getModule_Data()
37
+ ->getDbH_UserMeta()
38
+ ->getQuerySelector();
39
+ $results = $metaSelect->setResultsAsVo( false )
40
+ ->setSelectResultsFormat( ARRAY_A )
41
+ ->setColumnsToSelect( [ 'user_id' ] )
42
+ ->setOrderBy( 'updated_at', 'DESC' )
43
+ ->setLimit( 20 )
44
+ ->queryWithResult();
45
+
46
+ $results = array_map(
47
+ function ( $object ) {
48
+ return $object->user_login;
49
+ },
50
+ ( new \WP_User_Query( [
51
+ 'fields' => [ 'user_login' ],
52
+ 'include' => array_map(
53
+ function ( $res ) {
54
+ return (int)$res[ 'user_id' ];
55
+ },
56
+ is_array( $results ) ? $results : []
57
+ )
58
+ ] ) )->get_results()
59
+ );
60
+ asort( $results );
61
+ return $results;
62
+ }
63
  }
src/lib/src/Modules/UserManagement/WpCli.php CHANGED
@@ -8,8 +8,6 @@ use FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement;
8
  class WpCli extends BaseShield\WpCli {
9
 
10
  protected function enumCmdHandlers() :array {
11
- return [
12
- UserManagement\WpCli\SessionTerminate::class
13
- ];
14
  }
15
  }
8
  class WpCli extends BaseShield\WpCli {
9
 
10
  protected function enumCmdHandlers() :array {
11
+ return [];
 
 
12
  }
13
  }
src/lib/src/Modules/UserManagement/WpCli/SessionTerminate.php CHANGED
@@ -3,9 +3,11 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement\WpCli;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\WpCli\BaseWpCliCmd;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\Sessions\Lib\Ops\Terminate;
7
  use WP_CLI;
8
 
 
 
 
9
  class SessionTerminate extends BaseWpCliCmd {
10
 
11
  /**
@@ -57,35 +59,6 @@ class SessionTerminate extends BaseWpCliCmd {
57
  * @throws WP_CLI\ExitException
58
  */
59
  public function cmdTerminate( $null, $aA ) {
60
-
61
- $bShowConfirm = true;
62
- if ( $this->isForceFlag( $aA ) ) {
63
- $bShowConfirm = false;
64
- unset( $aA[ 'force' ] );
65
- }
66
-
67
- if ( WP_CLI\Utils\get_flag_value( $aA, 'all', false ) ) {
68
- if ( $bShowConfirm ) {
69
- WP_CLI::confirm( 'This will logout all users. Are you sure?' );
70
- }
71
- $this->runTerminateAll();
72
- return;
73
- }
74
-
75
- if ( count( $aA ) === 0 ) {
76
- WP_CLI::error( 'Please specify the user for which you want to terminate sessions.' );
77
- }
78
- if ( count( $aA ) > 1 ) {
79
- WP_CLI::error( 'Please specify only 1 way to identify a user.' );
80
- }
81
-
82
- $oU = $this->loadUserFromArgs( $aA );
83
-
84
- if ( $bShowConfirm ) {
85
- WP_CLI::confirm( 'This will logout all session for this user. Are you sure?' );
86
- }
87
-
88
- $this->runTerminateByUser( $oU );
89
  }
90
 
91
  /**
@@ -93,23 +66,8 @@ class SessionTerminate extends BaseWpCliCmd {
93
  * @throws WP_CLI\ExitException
94
  */
95
  private function runTerminateByUser( $oUser ) {
96
- if ( empty( $oUser ) ) {
97
- WP_CLI::error( "User doesn't exist." );
98
- }
99
- ( new Terminate() )
100
- ->setMod( $this->getMod() )
101
- ->byUsername( $oUser->user_login );
102
- WP_CLI::success(
103
- __( 'All sessions for user have been terminated.', 'wp-simple-firewall' )
104
- );
105
  }
106
 
107
  private function runTerminateAll() {
108
- ( new Terminate() )
109
- ->setMod( $this->getMod() )
110
- ->all();
111
- WP_CLI::success(
112
- __( 'All user sessions have been terminated.', 'wp-simple-firewall' )
113
- );
114
  }
115
  }
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\UserManagement\WpCli;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\WpCli\BaseWpCliCmd;
 
6
  use WP_CLI;
7
 
8
+ /**
9
+ * @deprecated 15.0
10
+ */
11
  class SessionTerminate extends BaseWpCliCmd {
12
 
13
  /**
59
  * @throws WP_CLI\ExitException
60
  */
61
  public function cmdTerminate( $null, $aA ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  }
63
 
64
  /**
66
  * @throws WP_CLI\ExitException
67
  */
68
  private function runTerminateByUser( $oUser ) {
 
 
 
 
 
 
 
 
 
69
  }
70
 
71
  private function runTerminateAll() {
 
 
 
 
 
 
72
  }
73
  }
src/lib/src/Render/LocateTemplateDirs.php CHANGED
@@ -13,14 +13,14 @@ class LocateTemplateDirs {
13
  * @return string[]
14
  */
15
  public function run() :array {
16
- $aDirs = array_filter(
17
  $this->getCustomTemplateDirs(),
18
- function ( $sDir ) {
19
- return Services::WpFs()->isDir( $sDir );
20
  }
21
  );
22
- $aDirs[] = path_join( $this->getCon()->getPath_Templates(), 'twig' );
23
- return $aDirs;
24
  }
25
 
26
  /**
13
  * @return string[]
14
  */
15
  public function run() :array {
16
+ $dirs = array_filter(
17
  $this->getCustomTemplateDirs(),
18
+ function ( $dir ) {
19
+ return Services::WpFs()->isDir( $dir );
20
  }
21
  );
22
+ $dirs[] = path_join( $this->getCon()->getPath_Templates(), 'twig' );
23
+ return $dirs;
24
  }
25
 
26
  /**
src/lib/src/Request/ThisRequest.php ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Request;
4
+
5
+ use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
+ use FernleafSystems\Wordpress\Plugin\Shield;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\DB\BotSignal\BotSignalRecord;
8
+ use FernleafSystems\Wordpress\Services\Services;
9
+
10
+ /**
11
+ * @property string $ip
12
+ * @property BotSignalRecord $botsignal_record
13
+ * @property string $ip_id
14
+ * @property bool $is_force_off
15
+ * @property bool $is_security_admin
16
+ * @property bool $is_trusted_bot
17
+ * @property bool $is_ip_blocked
18
+ * @property bool $is_ip_blacklisted
19
+ * @property bool $is_ip_whitelisted
20
+ * @property bool $is_server_loopback
21
+ * @property bool $request_bypasses_all_restrictions
22
+ * @property bool $wp_is_admin
23
+ * @property bool $wp_is_networkadmin
24
+ * @property bool $wp_is_ajax
25
+ * @property bool $wp_is_wpcli
26
+ * @property bool $wp_is_xmlrpc
27
+ */
28
+ class ThisRequest extends DynPropertiesClass {
29
+
30
+ use Shield\Modules\PluginControllerConsumer;
31
+
32
+ public function __construct( Shield\Controller\Controller $con ) {
33
+ $this->setCon( $con );
34
+ }
35
+
36
+ public function __get( string $key ) {
37
+ $value = parent::__get( $key );
38
+ switch ( $key ) {
39
+
40
+ case 'ip':
41
+ if ( empty( $value ) ) {
42
+ $value = Services::IP()->getRequestIp();
43
+ $this->ip = $value;
44
+ }
45
+ break;
46
+
47
+ case 'ip_id':
48
+ if ( is_null( $value ) ) {
49
+ $value = $this->getIpID();
50
+ $this->ip_id = $value;
51
+ }
52
+ break;
53
+
54
+ case 'is_force_off':
55
+ case 'is_ip_blocked':
56
+ case 'is_ip_whitelisted':
57
+ case 'request_bypasses_all_restrictions':
58
+ case 'is_security_admin':
59
+ case 'is_trusted_bot':
60
+ $value = (bool)$value;
61
+ break;
62
+
63
+ default:
64
+ break;
65
+ }
66
+ return $value;
67
+ }
68
+
69
+ private function getIpID() :string {
70
+ return Services::IP()->getIpDetector()->getIPIdentity();
71
+ }
72
+ }
src/lib/src/Rules/Build/BuildRuleBase.php ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\RuleVO;
7
+
8
+ abstract class BuildRuleBase {
9
+
10
+ use Shield\Modules\ModConsumer;
11
+
12
+ const LOGIC_AND = 'AND';
13
+ const LOGIC_OR = 'OR';
14
+
15
+ public function build() :Shield\Rules\RuleVO {
16
+ $rule = new RuleVO();
17
+ $rule->slug = $this->getSlug();
18
+ $rule->name = $this->getName();
19
+ $rule->description = $this->getDescription();
20
+ $rule->wp_hook = '';
21
+ $rule->wp_hook_level = $this->getWpHookLevel();
22
+ $rule->flags = $this->getFlags();
23
+ $rule->conditions = $this->getConditions();
24
+ $rule->responses = $this->getResponses();
25
+ $rule->immediate_exec_response = $this->isInstantExecResponse();
26
+ return $rule;
27
+ }
28
+
29
+ abstract protected function getName() :string;
30
+
31
+ abstract protected function getDescription() :string;
32
+
33
+ protected function getWpHook() :string {
34
+ return ''; // you need a VERY good reason to overload this method as hooks are handled dynamically
35
+ }
36
+
37
+ protected function getWpHookLevel() :int {
38
+ return Shield\Rules\WPHooksOrder::NONE;
39
+ }
40
+
41
+ protected function getConditions() :array {
42
+ return [
43
+ 'logic' => static::LOGIC_AND,
44
+ 'group' => []
45
+ ];
46
+ }
47
+
48
+ protected function getFlags() :array {
49
+ return [];
50
+ }
51
+
52
+ protected function getResponses() :array {
53
+ return [];
54
+ }
55
+
56
+ protected function isInstantExecResponse() :bool {
57
+ return false;
58
+ }
59
+ }
src/lib/src/Rules/Build/BuildRuleCoreShieldBase.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield;
6
+
7
+ abstract class BuildRuleCoreShieldBase extends BuildRuleBase {
8
+
9
+ const SLUG = '';
10
+
11
+ protected function getFlags() :array {
12
+ return [
13
+ 'is_core_shield' => true
14
+ ];
15
+ }
16
+
17
+ protected function getSlug() :string {
18
+ return static::SLUG;
19
+ }
20
+
21
+ protected function getCommonAuditParamsMapping() :array {
22
+ return [
23
+ 'path' => 'matched_path',
24
+ 'script' => 'matched_script_name',
25
+ 'crawler' => 'matched_useragent',
26
+ ];
27
+ }
28
+ }
src/lib/src/Rules/Build/Builder.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions\MutuallyDependentRulesException;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Utility\RulesControllerConsumer;
7
+
8
+ class Builder {
9
+
10
+ use RulesControllerConsumer;
11
+
12
+ public function run() :array {
13
+ $rules = [];
14
+ foreach ( $this->getRuleBuilders() as $builder ) {
15
+ $rule = $builder->build();
16
+ $rules[ $rule->slug ] = $rule;
17
+ }
18
+
19
+ $sorter = ( new SortRulesByDependencies( $rules ) )->setCon( $this->getRulesCon()->getCon() );
20
+ try {
21
+ $rules = $sorter->run();
22
+ }
23
+ catch ( MutuallyDependentRulesException $e ) {
24
+ error_log( $e->getMessage() );
25
+ }
26
+ return $rules;
27
+ }
28
+
29
+ /**
30
+ * @return BuildRuleBase[]
31
+ */
32
+ private function getRuleBuilders() :array {
33
+ return \apply_filters( 'shield/collate_rule_builders', [] );
34
+ }
35
+ }
src/lib/src/Rules/Build/SortRulesByDependencies.php ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Build;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\PluginControllerConsumer;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Base;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions\MutuallyDependentRulesException;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions\NoSuchConditionHandlerException;
9
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\RuleVO;
10
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\WPHooksOrder;
11
+
12
+ class SortRulesByDependencies {
13
+
14
+ use PluginControllerConsumer;
15
+
16
+ /**
17
+ * @var RuleVO[]
18
+ */
19
+ private $rules;
20
+
21
+ private $dependencies = [];
22
+
23
+ private $finalRulesOrder = [];
24
+
25
+ public function __construct( array $rules ) {
26
+ $this->rules = $rules;
27
+ }
28
+
29
+ /**
30
+ * @return RuleVO[]
31
+ * @throws MutuallyDependentRulesException
32
+ */
33
+ public function run() :array {
34
+ $this->buildDependencyGraph();
35
+ $this->assignWpHooks();
36
+ $this->verifyDependencies();
37
+ $this->orderRules();
38
+ return $this->rules;
39
+ }
40
+
41
+ /**
42
+ * @throws MutuallyDependentRulesException
43
+ */
44
+ private function verifyDependencies() {
45
+ // Ensure that there are no mutually dependent rules
46
+ $rulesDependenciesMap = $this->filterDependenciesForRulesOnly();
47
+ foreach ( $rulesDependenciesMap as $primaryRuleSlug => $dep ) {
48
+ foreach ( $dep as $depRuleSlug ) {
49
+ if ( in_array( $primaryRuleSlug, $rulesDependenciesMap[ $depRuleSlug ] ) ) {
50
+ throw new MutuallyDependentRulesException( sprintf( 'The rules "%s" and "%s" are mutually dependent.',
51
+ $primaryRuleSlug, $depRuleSlug ) );
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ private function filterDependenciesForRulesOnly() :array {
58
+ return array_map(
59
+ function ( array $dependencies ) {
60
+ return array_intersect( $dependencies, array_keys( $this->rules ) );
61
+ },
62
+ array_intersect_key( $this->dependencies, $this->rules )
63
+ );
64
+ }
65
+
66
+ private function orderRules() {
67
+ // Filter out everything to only have rules (not conditions).
68
+ $rulesDependenciesMap = $this->filterDependenciesForRulesOnly();
69
+
70
+ $finalRulesOrder = [];
71
+
72
+ $count = 0;
73
+ do {
74
+ $nextRule = $this->findRuleWithZeroDependencies( $rulesDependenciesMap );
75
+
76
+ if ( empty( $nextRule ) ) {
77
+ error_log( 'COULD NOT ORDER RULES!' );
78
+ break;
79
+ }
80
+
81
+ $finalRulesOrder[] = $nextRule;
82
+
83
+ unset( $rulesDependenciesMap[ $nextRule ] );
84
+ foreach ( $rulesDependenciesMap as $ruleSlug => $deps ) {
85
+ $remove = array_search( $nextRule, $deps );
86
+ if ( $remove !== false ) {
87
+ unset( $rulesDependenciesMap[ $ruleSlug ][ $remove ] );
88
+ }
89
+ }
90
+
91
+ if ( $count++ > 1000 ) {
92
+ break;
93
+ }
94
+ } while ( !empty( $rulesDependenciesMap ) );
95
+
96
+ $newRules = [];
97
+ foreach ( $finalRulesOrder as $rule ) {
98
+ $newRules[ $rule ] = $this->rules[ $rule ];
99
+ }
100
+ $this->rules = $newRules;
101
+ }
102
+
103
+ private function findRuleWithZeroDependencies( array $map ) {
104
+ $theRule = '';
105
+ foreach ( $map as $rule => $deps ) {
106
+ if ( count( $deps ) === 0 ) {
107
+ $theRule = $rule;
108
+ break;
109
+ }
110
+ }
111
+ return $theRule;
112
+ }
113
+
114
+ /**
115
+ * Assigns the most appropriate WP Hook to a rule based on its conditions.
116
+ */
117
+ private function assignWpHooks() {
118
+ foreach ( $this->rules as $rule ) {
119
+
120
+ $minimumHook = WPHooksOrder::NONE;
121
+ foreach ( $this->dependencies[ $rule->slug ] as $dependency ) {
122
+ if ( !$this->isRule( $dependency ) ) {
123
+ // error_log( $dependency );
124
+ // only conditions have WP hooks.
125
+ try {
126
+ /** @var Base $class */
127
+ $class = $this->getCon()->rules->locateConditionHandlerClass( $dependency );
128
+ // error_log( $class );
129
+ $minimumHook = max( $minimumHook, $class::FindMinimumHook() );
130
+ // error_log( var_export( $minimumHook, true ) );
131
+ }
132
+ catch ( NoSuchConditionHandlerException $e ) {
133
+ // error_log( $e->getMessage() );
134
+ }
135
+ }
136
+ }
137
+
138
+ $rule->wp_hook_level = max( $minimumHook, $rule->wp_hook_level );
139
+ $rule->wp_hook = WPHooksOrder::HOOK_NAME( $rule->wp_hook_level );
140
+ }
141
+ }
142
+
143
+ private function buildDependencyGraph() {
144
+ foreach ( $this->rules as $rule ) {
145
+ if ( !isset( $dependencies[ $rule->slug ] ) ) {
146
+ $dependencies[ $rule->slug ] = [];
147
+ }
148
+ $this->dependencies[ $rule->slug ] = $this->buildRuleDependencies( $rule );
149
+ }
150
+ }
151
+
152
+ private function isRule( string $ruleSlug ) :bool {
153
+ return isset( $this->rules[ $ruleSlug ] );
154
+ }
155
+
156
+ private function buildRuleDependencies( RuleVO $rule ) :array {
157
+ $deps = [];
158
+
159
+ foreach ( $this->getAllConditionsProperty( $rule->conditions[ 'group' ], 'condition' ) as $conditionSlug ) {
160
+
161
+ // First we add the condition to the list of dependencies.
162
+ $deps[] = $conditionSlug;
163
+
164
+ // Then we get the list of dependencies for this condition.
165
+ if ( !isset( $this->dependencies[ $conditionSlug ] ) ) {
166
+ try {
167
+ /** @var Base $handlerClass */
168
+ $handlerClass = $this->getCon()->rules->locateConditionHandlerClass( $conditionSlug );
169
+ $this->dependencies[ $conditionSlug ] = array_map(
170
+ function ( $className ) {
171
+ /** Base */
172
+ return $className::SLUG;
173
+ },
174
+ $handlerClass::BuildRequiredConditions()
175
+ );
176
+ }
177
+ catch ( NoSuchConditionHandlerException $e ) {
178
+ error_log( 'NO SUCH: '.$conditionSlug );
179
+ continue;
180
+ }
181
+ }
182
+
183
+ $deps = array_merge( $deps, $this->dependencies[ $conditionSlug ] );
184
+ }
185
+
186
+ foreach ( $this->getAllConditionsProperty( $rule->conditions[ 'group' ], 'rule' ) as $ruleSlug ) {
187
+ if ( $this->isRule( $ruleSlug ) ) {
188
+ $deps[] = $ruleSlug;
189
+ $deps = array_merge( $deps, $this->buildRuleDependencies( $this->rules[ $ruleSlug ] ) );
190
+ }
191
+ }
192
+
193
+ return array_unique( $deps );
194
+ }
195
+
196
+ /**
197
+ * This is recursive and essentially allows for infinite nesting of groups of rules with different logic.
198
+ */
199
+ private function getAllConditionsProperty( array $conditions, string $subPropertyToCollect = 'condition' ) :array {
200
+ $collection = [];
201
+
202
+ foreach ( $conditions as $subCondition ) {
203
+ if ( isset( $subCondition[ 'group' ] ) ) {
204
+ $collection = array_merge( $collection, $this->getAllConditionsProperty( $subCondition[ 'group' ] ) );
205
+ }
206
+ elseif ( !empty( $subCondition[ $subPropertyToCollect ] ) ) {
207
+ $collection[] = $subCondition[ $subPropertyToCollect ];
208
+ }
209
+ }
210
+
211
+ return array_unique( $collection );
212
+ }
213
+ }
src/lib/src/Rules/Conditions/Base.php ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\PluginControllerConsumer;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\RuleVO;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\WPHooksOrder;
9
+ use FernleafSystems\Wordpress\Services\Services;
10
+
11
+ abstract class Base extends DynPropertiesClass {
12
+
13
+ use PluginControllerConsumer;
14
+
15
+ const SLUG = '';
16
+
17
+ protected $conditionTriggerMeta = [];
18
+
19
+ /**
20
+ * @var RuleVO
21
+ */
22
+ protected $rule;
23
+
24
+ public function __construct( array $conditionParams = [] ) {
25
+ $this->applyFromArray( $conditionParams );
26
+ }
27
+
28
+ public function setRule( RuleVO $rule ) :self {
29
+ $this->rule = $rule;
30
+ return $this;
31
+ }
32
+
33
+ public static function BuildRequiredConditions() :array {
34
+ $conditions = static::RequiredConditions();
35
+ foreach ( static::RequiredConditions() as $requiredCondition ) {
36
+ /** @var $requiredCondition Base */
37
+ $conditions = array_merge( $conditions, $requiredCondition::BuildRequiredConditions() );
38
+ }
39
+ return array_unique( $conditions );
40
+ }
41
+
42
+ public static function FindMinimumHook() :int {
43
+ $minimum = static::MinimumHook();
44
+ foreach ( static::BuildRequiredConditions() as $requiredCondition ) {
45
+ /** @var $requiredCondition Base */
46
+ $minimum = max( $minimum, $requiredCondition::MinimumHook() );
47
+ }
48
+ return (int)$minimum;
49
+ }
50
+
51
+ public static function RequiredConditions() :array {
52
+ return [];
53
+ }
54
+
55
+ public static function MinimumHook() :int {
56
+ return WPHooksOrder::NONE;
57
+ }
58
+
59
+ public function __get( string $key ) {
60
+ $value = parent::__get( $key );
61
+ switch ( $key ) {
62
+ case 'match_ips':
63
+ case 'match_ip_ids':
64
+ case 'match_not_ip_ids':
65
+ case 'match_useragents':
66
+ if ( !is_array( $value ) ) {
67
+ $value = [];
68
+ }
69
+ break;
70
+ case 'request_ip':
71
+ if ( empty( $value ) ) {
72
+ $value = Services::IP()->getRequestIp();
73
+ }
74
+ break;
75
+ case 'request_useragent':
76
+ if ( empty( $value ) ) {
77
+ $value = Services::Request()->getUserAgent();
78
+ }
79
+ break;
80
+ default:
81
+ break;
82
+ }
83
+ return $value;
84
+ }
85
+
86
+ public function run() :bool {
87
+ try {
88
+ $result = $this->execConditionCheck();
89
+ }
90
+ catch ( \Exception $e ) {
91
+ $result = false;
92
+ }
93
+ return $result;
94
+ }
95
+
96
+ public function getConditionTriggerMetaData() :array {
97
+ return $this->conditionTriggerMeta;
98
+ }
99
+
100
+ /**
101
+ * @throws \Exception
102
+ */
103
+ abstract protected function execConditionCheck() :bool;
104
+
105
+ protected function addConditionTriggerMeta( string $item, $value ) :self {
106
+ $this->conditionTriggerMeta[ $item ] = $value;
107
+ return $this;
108
+ }
109
+
110
+ /**
111
+ * @return mixed|null
112
+ */
113
+ protected function getConditionTriggerMeta( string $item ) {
114
+ return $this->conditionTriggerMeta[ $item ] ?? null;
115
+ }
116
+
117
+ protected function removeTriggerMeta( string $item ) :self {
118
+ unset( $this->conditionTriggerMeta[ $item ] );
119
+ return $this;
120
+ }
121
+ }
src/lib/src/Rules/Conditions/IsForceOff.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits\RequestIP;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ class IsForceOff extends Base {
9
+
10
+ use RequestIP;
11
+
12
+ const SLUG = 'is_force_off';
13
+
14
+ protected function execConditionCheck() :bool {
15
+ $con = $this->getCon();
16
+ if ( !isset( $con->this_req->is_force_off ) ) {
17
+ $con->this_req->is_force_off = $this->findForceOffFile() !== false;
18
+ }
19
+ return $con->this_req->is_force_off;
20
+ }
21
+
22
+ /**
23
+ * @return false|string
24
+ */
25
+ private function findForceOffFile() {
26
+ $con = $this->getCon();
27
+ if ( !isset( $con->file_forceoff ) ) {
28
+ $FS = Services::WpFs();
29
+ $file = $FS->findFileInDir( 'forceoff', $con->getRootDir(), false, false );
30
+ $con->file_forceoff = empty( $file ) ? false : $file;
31
+ }
32
+ return $con->file_forceoff;
33
+ }
34
+ }
src/lib/src/Rules/Conditions/IsIpBlacklisted.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Ops\LookupIpOnList;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits\RequestIP;
7
+
8
+ class IsIpBlacklisted extends Base {
9
+
10
+ use RequestIP;
11
+
12
+ const SLUG = 'is_ip_blacklisted';
13
+
14
+ protected function execConditionCheck() :bool {
15
+ $thisReq = $this->getCon()->this_req;
16
+ if ( !isset( $thisReq->is_ip_blacklisted ) ) {
17
+ $thisReq->is_ip_blacklisted = !empty( ( new LookupIpOnList() )
18
+ ->setDbHandler( $this->getCon()->getModule_IPs()->getDbHandler_IPs() )
19
+ ->setIP( $this->getRequestIP() )
20
+ ->setListTypeBlock()
21
+ ->lookup() );
22
+ }
23
+ return $thisReq->is_ip_blacklisted;
24
+ }
25
+ }
src/lib/src/Rules/Conditions/IsIpBlocked.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Components\QueryIpBlock;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits\RequestIP;
7
+
8
+ class IsIpBlocked extends Base {
9
+
10
+ use RequestIP;
11
+
12
+ const SLUG = 'is_ip_blocked';
13
+
14
+ /**
15
+ * TODO: $con->fireEvent( 'not_conn_kill_high_rep' );
16
+ * @inheritDoc
17
+ */
18
+ protected function execConditionCheck() :bool {
19
+ $thisReq = $this->getCon()->this_req;
20
+ if ( !isset( $thisReq->is_ip_blocked ) ) {
21
+ $thisReq->is_ip_blocked = ( new QueryIpBlock() )
22
+ ->setMod( $this->getCon()->getModule_IPs() )
23
+ ->setIp( $this->getRequestIP() )
24
+ ->run();
25
+ }
26
+ return $thisReq->is_ip_blocked;
27
+ }
28
+ }
src/lib/src/Rules/Conditions/IsIpHighReputation.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\IsHighReputationIP;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits\RequestIP;
7
+
8
+ class IsIpHighReputation extends Base {
9
+
10
+ use RequestIP;
11
+
12
+ const SLUG = 'is_ip_high_reputation';
13
+
14
+ protected function execConditionCheck() :bool {
15
+ return ( new IsHighReputationIP() )
16
+ ->setMod( $this->getCon()->getModule_IPs() )
17
+ ->setIP( $this->getRequestIP() )
18
+ ->query();
19
+ }
20
+ }
src/lib/src/Rules/Conditions/IsIpValidPublic.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits\RequestIP;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ class IsIpValidPublic extends Base {
9
+
10
+ use RequestIP;
11
+
12
+ const SLUG = 'is_ip_valid_public';
13
+
14
+ protected function execConditionCheck() :bool {
15
+ $ip = $this->getRequestIP();
16
+ return !empty( $ip ) && Services::IP()->isValidIp_PublicRemote( $ip );
17
+ }
18
+ }
src/lib/src/Rules/Conditions/IsIpWhitelisted.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\Ops\LookupIpOnList;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits\RequestIP;
7
+
8
+ class IsIpWhitelisted extends Base {
9
+
10
+ use RequestIP;
11
+
12
+ const SLUG = 'is_ip_whitelisted';
13
+
14
+ protected function execConditionCheck() :bool {
15
+ $thisReq = $this->getCon()->this_req;
16
+ if ( !isset( $thisReq->is_ip_whitelisted ) ) {
17
+ $thisReq->is_ip_whitelisted = !empty( ( new LookupIpOnList() )
18
+ ->setDbHandler( $this->getCon()->getModule_IPs()->getDbHandler_IPs() )
19
+ ->setIP( $this->getRequestIP() )
20
+ ->setListTypeBypass()
21
+ ->lookup() );
22
+ }
23
+ return $thisReq->is_ip_whitelisted;
24
+
25
+ }
26
+ }
src/lib/src/Rules/Conditions/IsLoggedInNormal.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\WPHooksOrder;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ class IsLoggedInNormal extends Base {
9
+
10
+ const SLUG = 'is_logged_in_normal';
11
+
12
+ protected function execConditionCheck() :bool {
13
+ return Services::WpUsers()->isUserLoggedIn();
14
+ }
15
+
16
+ public static function MinimumHook() :int {
17
+ return WPHooksOrder::INIT;
18
+ }
19
+ }
src/lib/src/Rules/Conditions/IsNotLoggedInNormal.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\WPHooksOrder;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ class IsNotLoggedInNormal extends Base {
9
+
10
+ const SLUG = 'is_not_logged_in_normal';
11
+
12
+ protected function execConditionCheck() :bool {
13
+ return !Services::WpUsers()->isUserLoggedIn();
14
+ }
15
+
16
+ public static function MinimumHook() :int {
17
+ return WPHooksOrder::INIT;
18
+ }
19
+ }
src/lib/src/Rules/Conditions/IsRateLimitExceeded.php ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\IPs\IPRecords;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\ReqLogs\Ops\Select;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits\RequestIP;
9
+ use FernleafSystems\Wordpress\Services\Services;
10
+
11
+ /**
12
+ * @property int $limit_count
13
+ * @property int $limit_time_span
14
+ */
15
+ class IsRateLimitExceeded extends Conditions\Base {
16
+
17
+ use RequestIP;
18
+
19
+ const SLUG = 'is_rate_limit_exceeded';
20
+
21
+ protected function execConditionCheck() :bool {
22
+ $ip = $this->getRequestIP();
23
+ $ip = ( new IPRecords() )
24
+ ->setMod( $this->getCon()->getModule_Data() )
25
+ ->loadIP( $ip, false );
26
+ $now = Services::Request()->carbon();
27
+ /** @var Select $selector */
28
+ $selector = $this->getCon()
29
+ ->getModule_Data()
30
+ ->getDbH_ReqLogs()
31
+ ->getQuerySelector();
32
+ $count = $selector->filterByIP( $ip->id )
33
+ ->filterByCreatedAt( $now->subSeconds( $this->limit_time_span )->timestamp, '>' )
34
+ ->count();
35
+ $matched = $count > $this->limit_count;
36
+
37
+ if ( $matched ) {
38
+ $this->addConditionTriggerMeta( 'request_count', $count );
39
+ $this->addConditionTriggerMeta( 'limit_count', $this->limit_count );
40
+ $this->addConditionTriggerMeta( 'limit_time_span', $this->limit_time_span );
41
+ }
42
+
43
+ return $matched;
44
+ }
45
+ }
src/lib/src/Rules/Conditions/IsRequestToInvalidPlugin.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits\RequestPath;
6
+
7
+ class IsRequestToInvalidPlugin extends Base {
8
+
9
+ use RequestPath;
10
+
11
+ const SLUG = 'is_request_to_invalid_plugin';
12
+
13
+ protected function execConditionCheck() :bool {
14
+ $asset = ( new IsRequestToPluginAsset() )->setCon( $this->getCon() );
15
+ $asset->request_path = $this->getRequestPath();
16
+ $validAsset = ( new IsRequestToValidPluginAsset() )->setCon( $this->getCon() );
17
+ $validAsset->request_path = $this->getRequestPath();
18
+ return $asset->run() && !$validAsset->run();
19
+ }
20
+
21
+ public static function RequiredConditions() :array {
22
+ return [
23
+ IsRequestToPluginAsset::class,
24
+ IsRequestToValidPluginAsset::class
25
+ ];
26
+ }
27
+ }
src/lib/src/Rules/Conditions/IsRequestToInvalidTheme.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits\RequestPath;
6
+
7
+ class IsRequestToInvalidTheme extends Base {
8
+
9
+ use RequestPath;
10
+
11
+ const SLUG = 'is_request_to_invalid_theme';
12
+
13
+ protected function execConditionCheck() :bool {
14
+ $asset = ( new IsRequestToThemeAsset() )->setCon( $this->getCon() );
15
+ $asset->request_path = $this->getRequestPath();
16
+ $validAsset = ( new IsRequestToValidThemeAsset() )->setCon( $this->getCon() );
17
+ $validAsset->request_path = $this->getRequestPath();
18
+ return $asset->run() && !$validAsset->run();
19
+ }
20
+
21
+ public static function RequiredConditions() :array {
22
+ return [
23
+ IsRequestToThemeAsset::class,
24
+ IsRequestToValidThemeAsset::class
25
+ ];
26
+ }
27
+ }
src/lib/src/Rules/Conditions/IsRequestToPluginAsset.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits\RequestPath;
6
+
7
+ class IsRequestToPluginAsset extends Base {
8
+
9
+ use RequestPath;
10
+
11
+ const SLUG = 'is_request_to_plugin_asset';
12
+
13
+ protected function execConditionCheck() :bool {
14
+ $pathMatcher = ( new MatchRequestPath() )->setCon( $this->getCon() );
15
+ $pathMatcher->request_path = $this->getRequestPath();
16
+ $pathMatcher->is_match_regex = true;
17
+ $pathMatcher->match_paths = [
18
+ sprintf( '^%s/.+/.+', rtrim( wp_parse_url( plugins_url(), PHP_URL_PATH ), '/' ) )
19
+ ];
20
+ return $pathMatcher->run();
21
+ }
22
+
23
+ public static function RequiredConditions() :array {
24
+ return [
25
+ MatchRequestPath::class
26
+ ];
27
+ }
28
+ }
src/lib/src/Rules/Conditions/IsRequestToThemeAsset.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits\RequestPath;
6
+
7
+ class IsRequestToThemeAsset extends Base {
8
+
9
+ use RequestPath;
10
+
11
+ const SLUG = 'is_request_to_theme_asset';
12
+
13
+ protected function execConditionCheck() :bool {
14
+ $pathMatcher = ( new MatchRequestPath() )->setCon( $this->getCon() );
15
+ $pathMatcher->request_path = $this->getRequestPath();
16
+ $pathMatcher->is_match_regex = true;
17
+ $pathMatcher->match_paths = [
18
+ sprintf( '^%s/.+/.+', dirname( wp_parse_url( get_stylesheet_directory_uri(), PHP_URL_PATH ) ) )
19
+ ];
20
+ return $pathMatcher->run();
21
+ }
22
+
23
+ public static function RequiredConditions() :array {
24
+ return [
25
+ MatchRequestPath::class
26
+ ];
27
+ }
28
+ }
src/lib/src/Rules/Conditions/IsRequestToValidPluginAsset.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits\RequestPath;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ class IsRequestToValidPluginAsset extends Base {
9
+
10
+ use RequestPath;
11
+
12
+ const SLUG = 'is_request_to_valid_plugin_asset';
13
+
14
+ protected function execConditionCheck() :bool {
15
+ $pathMatcher = ( new MatchRequestPath() )->setCon( $this->getCon() );
16
+ $pathMatcher->request_path = $this->getRequestPath();
17
+ $pathMatcher->is_match_regex = true;
18
+ $pathMatcher->match_paths = [
19
+ sprintf( '^%s/(%s)/',
20
+ rtrim( wp_parse_url( plugins_url(), PHP_URL_PATH ), '/' ),
21
+ implode( '|', array_filter( array_map(
22
+ function ( $pluginFile ) {
23
+ return preg_quote( dirname( (string)$pluginFile ), '#' );
24
+ },
25
+ Services::WpPlugins()->getInstalledPluginFiles()
26
+ ) ) )
27
+ )
28
+ ];
29
+ return $pathMatcher->run();
30
+ }
31
+
32
+ public static function RequiredConditions() :array {
33
+ return [
34
+ MatchRequestPath::class
35
+ ];
36
+ }
37
+ }
src/lib/src/Rules/Conditions/IsRequestToValidThemeAsset.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits\RequestPath;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ class IsRequestToValidThemeAsset extends Base {
9
+
10
+ use RequestPath;
11
+
12
+ const SLUG = 'is_request_to_valid_theme_asset';
13
+
14
+ protected function execConditionCheck() :bool {
15
+ $pathMatcher = ( new MatchRequestPath() )->setCon( $this->getCon() );
16
+ $pathMatcher->request_path = $this->getRequestPath();
17
+ $pathMatcher->is_match_regex = true;
18
+ $pathMatcher->match_paths = [
19
+ sprintf( '^%s/(%s)/',
20
+ rtrim( dirname( wp_parse_url( get_stylesheet_directory_uri(), PHP_URL_PATH ) ), '/' ),
21
+ implode( '|', array_filter( array_map(
22
+ function ( $themeDir ) {
23
+ return preg_quote( $themeDir, '#' );
24
+ },
25
+ Services::WpThemes()->getInstalledStylesheets()
26
+ ) ) )
27
+ )
28
+ ];
29
+ return $pathMatcher->run();
30
+ }
31
+
32
+ public static function RequiredConditions() :array {
33
+ return [
34
+ MatchRequestPath::class
35
+ ];
36
+ }
37
+ }
src/lib/src/Rules/Conditions/IsSecurityAdmin.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Services\Services;
6
+
7
+ class IsSecurityAdmin extends Base {
8
+
9
+ const SLUG = 'is_security_admin';
10
+
11
+ protected function execConditionCheck() :bool {
12
+ $secAdminCon = $this->getCon()
13
+ ->getModule_SecAdmin()
14
+ ->getSecurityAdminController();
15
+ return $secAdminCon->isRegisteredSecAdminUser( Services::WpUsers()->getCurrentWpUser() )
16
+ || $secAdminCon->getSecAdminTimeRemaining() > 0;
17
+ }
18
+ }
src/lib/src/Rules/Conditions/IsUserAdminNormal.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Services\Services;
6
+
7
+ class IsUserAdminNormal extends Base {
8
+
9
+ const SLUG = 'is_user_admin_normal';
10
+
11
+ protected function execConditionCheck() :bool {
12
+ return ( new IsLoggedInNormal() )->setCon( $this->getCon() )->run()
13
+ && Services::WpUsers()->isUserAdmin();
14
+ }
15
+
16
+ public static function RequiredConditions() :array {
17
+ return [
18
+ IsLoggedInNormal::class,
19
+ ];
20
+ }
21
+ }
src/lib/src/Rules/Conditions/MatchRequestIp.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits\RequestIP;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions\{
7
+ IpsToMatchUnavailableException,
8
+ RequestIpUnavailableException
9
+ };
10
+ use FernleafSystems\Wordpress\Services\Services;
11
+
12
+ class MatchRequestIp extends Base {
13
+
14
+ use RequestIP;
15
+
16
+ const SLUG = 'match_request_ip';
17
+
18
+ /**
19
+ * @throws IpsToMatchUnavailableException
20
+ * @throws RequestIpUnavailableException
21
+ */
22
+ protected function execConditionCheck() :bool {
23
+ if ( empty( $this->match_ips ) ) {
24
+ throw new IpsToMatchUnavailableException();
25
+ }
26
+ return Services::IP()->checkIp( $this->getRequestIP(), $this->match_ips );
27
+ }
28
+ }
src/lib/src/Rules/Conditions/MatchRequestIpIdentity.php ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits\RequestIP;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits\UserAgent;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions\MatchIpIdsUnavailableException;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions\RequestUseragentUnavailableException;
9
+ use FernleafSystems\Wordpress\Services\Utilities\Net\IpID;
10
+
11
+ /**
12
+ * @property string[] $match_ip_ids
13
+ * @property string[] $match_not_ip_ids
14
+ */
15
+ class MatchRequestIpIdentity extends Base {
16
+
17
+ use RequestIP;
18
+ use UserAgent;
19
+
20
+ const SLUG = 'match_request_ip_identity';
21
+
22
+ protected function execConditionCheck() :bool {
23
+ $matchIDs = $this->match_ip_ids;
24
+ $matchNotIDs = $this->match_not_ip_ids;
25
+ if ( empty( $matchIDs ) && empty( $matchNotIDs ) ) {
26
+ throw new MatchIpIdsUnavailableException();
27
+ }
28
+
29
+ try {
30
+ $ua = $this->getUserAgent();
31
+ }
32
+ catch ( RequestUseragentUnavailableException $e ) {
33
+ $ua = '';
34
+ }
35
+
36
+ $match = false;
37
+ $id = ( new IpID( $this->getRequestIP(), $ua ) )->run()[ 0 ];
38
+
39
+ if ( !empty( $matchIDs ) ) {
40
+ $match = in_array( $id, $matchIDs );
41
+ }
42
+ elseif ( !empty( $matchNotIDs ) ) {
43
+ $match = !in_array( $id, $matchNotIDs );
44
+ }
45
+
46
+ if ( $match ) {
47
+ $this->addConditionTriggerMeta( 'ip_id', $id );
48
+ }
49
+
50
+ return $match;
51
+ }
52
+ }
src/lib/src/Rules/Conditions/MatchRequestParam.php ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions\PathsToMatchUnavailableException;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ /**
9
+ * @property bool $is_match_regex
10
+ * @property string $match_category
11
+ * @property string[] $match_patterns
12
+ * @property array $excluded_params
13
+ */
14
+ class MatchRequestParam extends Base {
15
+
16
+ const SLUG = 'match_request_param';
17
+
18
+ protected function execConditionCheck() :bool {
19
+ if ( empty( $this->match_patterns ) ) {
20
+ throw new PathsToMatchUnavailableException();
21
+ }
22
+
23
+ $matched = false;
24
+ foreach ( $this->getFinalRequestParamsToTest() as $param => $value ) {
25
+
26
+ if ( empty( $value ) || !is_string( $value ) ) {
27
+ continue;
28
+ }
29
+
30
+ foreach ( $this->match_patterns as $matchPattern ) {
31
+
32
+ $matched = ( $this->is_match_regex && preg_match( sprintf( '#%s#i', $matchPattern ), $value ) )
33
+ || ( !$this->is_match_regex && ( stripos( $value, $matchPattern ) !== false ) );
34
+
35
+ if ( $matched ) {
36
+ $this->addConditionTriggerMeta( 'match_pattern', $matchPattern );
37
+ $this->addConditionTriggerMeta( 'match_request_param', $param );
38
+ $this->addConditionTriggerMeta( 'match_request_value', $value );
39
+ $this->addConditionTriggerMeta( 'match_type', $this->is_match_regex ? 'regex' : 'simple' );
40
+ $this->addConditionTriggerMeta( 'match_category', $this->match_category ?? 'unset' );
41
+ break 2;
42
+ }
43
+ }
44
+ }
45
+ return $matched;
46
+ }
47
+
48
+ protected function getFinalRequestParamsToTest() :array {
49
+ $finalParams = $this->getRequestParamsToTest();
50
+ $allExcluded = is_array( $this->excluded_params ) ? $this->excluded_params : [];
51
+
52
+ $allPagesExclusions = $allExcluded[ '*' ] ?? [];
53
+
54
+ $finalParams = array_diff_key( $finalParams, array_flip( $allPagesExclusions[ 'simple' ] ?? [] ) );
55
+
56
+ if ( !empty( $finalParams ) ) {
57
+ foreach ( array_keys( $finalParams ) as $paramKey ) {
58
+ foreach ( $allPagesExclusions[ 'regex' ] ?? [] as $exclKeyRegex ) {
59
+ // You can have numeric parameter keys in query: e.g. ?asdf=123&456&
60
+ if ( preg_match( sprintf( '#%s#i', $exclKeyRegex ), (string)$paramKey ) ) {
61
+ unset( $finalParams[ $paramKey ] );
62
+ }
63
+ }
64
+ }
65
+ }
66
+
67
+ if ( !empty( $finalParams ) ) {
68
+ unset( $allExcluded[ '*' ] );
69
+ $thePage = Services::Request()->getPath();
70
+ foreach ( $allExcluded as $pageName => $pageParams ) {
71
+
72
+ if ( strpos( $thePage, $pageName ) !== false ) {
73
+
74
+ if ( empty( $pageParams ) ) {
75
+ $finalParams = [];
76
+ }
77
+ elseif ( !empty( $pageParams[ 'simple' ] ) ) {
78
+ $finalParams = array_diff_key( $finalParams, array_flip( $pageParams[ 'simple' ] ) );
79
+ }
80
+ elseif ( !empty( $pageParams[ 'regex' ] ) ) {
81
+ // TODO
82
+ }
83
+ break;
84
+ }
85
+ }
86
+ }
87
+
88
+ return $finalParams;
89
+ }
90
+
91
+ protected function getRequestParamsToTest() :array {
92
+ return [];
93
+ }
94
+ }
src/lib/src/Rules/Conditions/MatchRequestParamFileUploads.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ class MatchRequestParamFileUploads extends MatchRequestParam {
6
+
7
+ const SLUG = 'match_request_param_file_uploads';
8
+
9
+ protected function getRequestParamsToTest() :array {
10
+ return array_filter( array_map(
11
+ function ( $file ) {
12
+ return $file[ 'name' ] ?? '';
13
+ },
14
+ ( !empty( $_FILES ) && is_array( $_FILES ) ) ? $_FILES : []
15
+ ) );
16
+ }
17
+ }
src/lib/src/Rules/Conditions/MatchRequestParamPost.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Services\Services;
6
+
7
+ class MatchRequestParamPost extends MatchRequestParam {
8
+
9
+ const SLUG = 'match_request_param_post';
10
+
11
+ protected function getRequestParamsToTest() :array {
12
+ return Services::Request()->post;
13
+ }
14
+ }
src/lib/src/Rules/Conditions/MatchRequestParamQuery.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Services\Services;
6
+
7
+ class MatchRequestParamQuery extends MatchRequestParam {
8
+
9
+ const SLUG = 'match_request_param_query';
10
+
11
+ protected function getRequestParamsToTest() :array {
12
+ return Services::Request()->query;
13
+ }
14
+ }
src/lib/src/Rules/Conditions/MatchRequestPath.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions\PathsToMatchUnavailableException;
6
+
7
+ /**
8
+ * @property bool $is_match_regex
9
+ * @property string[] $match_paths
10
+ */
11
+ class MatchRequestPath extends Base {
12
+
13
+ use Traits\RequestPath;
14
+
15
+ const SLUG = 'match_request_path';
16
+
17
+ protected function execConditionCheck() :bool {
18
+ if ( empty( $this->match_paths ) ) {
19
+ throw new PathsToMatchUnavailableException();
20
+ }
21
+ $matched = false;
22
+ $path = $this->getRequestPath();
23
+ $this->addConditionTriggerMeta( 'matched_path', $path );
24
+ foreach ( $this->match_paths as $matchPath ) {
25
+ $matched = $this->is_match_regex ?
26
+ (bool)preg_match( sprintf( '#%s#i', $matchPath ), $path ) : $matchPath == $path;
27
+
28
+ if ( $matched ) {
29
+ break;
30
+ }
31
+ }
32
+ return $matched;
33
+ }
34
+ }
src/lib/src/Rules/Conditions/MatchRequestScriptName.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits\RequestScriptName;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions\ScriptNamesToMatchUnavailableException;
7
+
8
+ /**
9
+ * @property bool $is_match_regex
10
+ * @property string[] $match_script_names
11
+ */
12
+ class MatchRequestScriptName extends Base {
13
+
14
+ use RequestScriptName;
15
+
16
+ const SLUG = 'match_request_script_name';
17
+
18
+ protected function execConditionCheck() :bool {
19
+ if ( empty( $this->match_script_names ) ) {
20
+ throw new ScriptNamesToMatchUnavailableException();
21
+ }
22
+
23
+ $matched = false;
24
+ $scriptName = $this->getRequestScriptName();
25
+
26
+ if ( $this->is_match_regex ) {
27
+ foreach ( $this->match_script_names as $matchScriptName ) {
28
+ if ( preg_match( sprintf( '#%s#i', $matchScriptName ), $scriptName ) ) {
29
+ $matched = true;
30
+ break;
31
+ }
32
+ }
33
+ }
34
+ else {
35
+ $matched = in_array( $scriptName, $this->match_script_names );
36
+ }
37
+
38
+ // always add this incase we need to invert_match
39
+ $this->addConditionTriggerMeta( 'matched_script_name', $scriptName );
40
+ return $matched;
41
+ }
42
+ }
src/lib/src/Rules/Conditions/MatchRequestStatusCode.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions\NoStatusProvidedToCheckException;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions\UnsupportedStatusException;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\WPHooksOrder;
8
+ use FernleafSystems\Wordpress\Services\Services;
9
+
10
+ /**
11
+ * @property string $code
12
+ */
13
+ class MatchRequestStatusCode extends Base {
14
+
15
+ const SLUG = 'match_request_status_code';
16
+
17
+ protected function execConditionCheck() :bool {
18
+ if ( empty( $this->code ) ) {
19
+ throw new NoStatusProvidedToCheckException( 'No status parameter provided to check' );
20
+ }
21
+
22
+ switch ( $this->code ) {
23
+ case '404':
24
+ $match = is_404();
25
+ $this->addConditionTriggerMeta( 'path', Services::Request()->getPath() );
26
+ break;
27
+ default:
28
+ throw new UnsupportedStatusException( 'Status parameter provided is not supported: '.$this->code );
29
+ }
30
+
31
+ return $match;
32
+ }
33
+
34
+ public static function MinimumHook() :int {
35
+ return WPHooksOrder::TEMPLATE_REDIRECT;
36
+ }
37
+ }
src/lib/src/Rules/Conditions/MatchRequestUseragent.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits\UserAgent;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions\MatchUseragentsUnavailableException;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions\RequestUseragentUnavailableException;
8
+
9
+ /**
10
+ * @property string[] $match_useragents
11
+ */
12
+ class MatchRequestUseragent extends Base {
13
+
14
+ use UserAgent;
15
+
16
+ const SLUG = 'match_request_useragent';
17
+
18
+ /**
19
+ * @throws RequestUseragentUnavailableException
20
+ * @throws MatchUseragentsUnavailableException
21
+ */
22
+ protected function matchUserAgent() :bool {
23
+ $uAgents = $this->match_useragents;
24
+ if ( empty( $uAgents ) ) {
25
+ throw new MatchUseragentsUnavailableException();
26
+ }
27
+
28
+ $match = false;
29
+ foreach ( $uAgents as $possibleAgent ) {
30
+ if ( stripos( $this->getUserAgent(), $possibleAgent ) !== false ) {
31
+ $match = true;
32
+ $this->addConditionTriggerMeta( 'matched_useragent', $possibleAgent );
33
+ break;
34
+ }
35
+ }
36
+ return $match;
37
+ }
38
+
39
+ protected function execConditionCheck() :bool {
40
+ try {
41
+ $detected = $this->matchUserAgent();
42
+ }
43
+ catch ( \Exception $e ) {
44
+ $detected = false;
45
+ }
46
+ return $detected;
47
+ }
48
+ }
src/lib/src/Rules/Conditions/NotMatchRequestPath.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ class NotMatchRequestPath extends MatchRequestPath {
6
+
7
+ use Traits\RequestPath;
8
+
9
+ const SLUG = 'not_match_request_path';
10
+
11
+ protected function execConditionCheck() :bool {
12
+ return !parent::execConditionCheck();
13
+ }
14
+ }
src/lib/src/Rules/Conditions/RequestHasParameters.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Services\Services;
6
+
7
+ class RequestHasParameters extends Base {
8
+
9
+ const SLUG = 'request_has_parameters';
10
+
11
+ protected function execConditionCheck() :bool {
12
+ $req = Services::Request();
13
+ return !empty( $req->query ) || ( $req->isPost() && !empty( $req->post ) );
14
+ }
15
+ }
src/lib/src/Rules/Conditions/RequestParamIs.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions\PathsToMatchUnavailableException;
6
+
7
+ /**
8
+ * @property string $match_param
9
+ * @property string[] $match_patterns
10
+ */
11
+ class RequestParamIs extends Base {
12
+
13
+ const SLUG = 'request_param_is';
14
+
15
+ protected function execConditionCheck() :bool {
16
+ if ( empty( $this->match_patterns ) ) {
17
+ throw new PathsToMatchUnavailableException();
18
+ }
19
+ if ( empty( $this->match_param ) ) {
20
+ throw new PathsToMatchUnavailableException();
21
+ }
22
+
23
+ $matched = false;
24
+
25
+ $value = $this->getRequestParamValue();
26
+ if ( is_string( $value ) ) {
27
+ foreach ( $this->match_patterns as $matchPattern ) {
28
+
29
+ if ( preg_match( sprintf( '#%s#i', $matchPattern ), $value ) ) {
30
+ $matched = true;
31
+ $this->addConditionTriggerMeta( 'match_pattern', $matchPattern );
32
+ $this->addConditionTriggerMeta( 'match_request_param', $this->match_param );
33
+ $this->addConditionTriggerMeta( 'match_request_value', $value );
34
+ break;
35
+ }
36
+ }
37
+ }
38
+
39
+ return $matched;
40
+ }
41
+
42
+ /**
43
+ * @return mixed|null
44
+ */
45
+ protected function getRequestParamValue() {
46
+ return null;
47
+ }
48
+ }
src/lib/src/Rules/Conditions/RequestPostParamIs.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Services\Services;
6
+
7
+ class RequestPostParamIs extends RequestParamIs {
8
+
9
+ const SLUG = 'request_post_param_is';
10
+
11
+ /**
12
+ * @return mixed|null
13
+ */
14
+ protected function getRequestParamValue() {
15
+ return Services::Request()->post( $this->match_param );
16
+ }
17
+ }
src/lib/src/Rules/Conditions/RequestQueryParamIs.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Services\Services;
6
+
7
+ class RequestQueryParamIs extends RequestParamIs {
8
+
9
+ const SLUG = 'request_query_param_is';
10
+
11
+ /**
12
+ * @return mixed|null
13
+ */
14
+ protected function getRequestParamValue() {
15
+ return Services::Request()->query( $this->match_param );
16
+ }
17
+ }
src/lib/src/Rules/Conditions/Traits/RequestIP.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions\RequestIpUnavailableException;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ /**
9
+ * @property string $request_ip
10
+ * @property string[] $match_ips
11
+ */
12
+ trait RequestIP {
13
+
14
+ /**
15
+ * @throws RequestIpUnavailableException
16
+ */
17
+ protected function getRequestIP() :string {
18
+ $value = $this->request_ip;
19
+ if ( empty( $value ) ) {
20
+ $value = Services::IP()->getRequestIp();
21
+ }
22
+ if ( empty( $value ) ) {
23
+ throw new RequestIpUnavailableException();
24
+ }
25
+ return $value;
26
+ }
27
+ }
src/lib/src/Rules/Conditions/Traits/RequestPath.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits;
4
+
5
+ use FernleafSystems\Wordpress\Services\Services;
6
+
7
+ /**
8
+ * @property string $request_path
9
+ */
10
+ trait RequestPath {
11
+
12
+ protected function getRequestPath() :string {
13
+ $value = $this->request_path;
14
+ if ( empty( $value ) ) {
15
+ $value = Services::Request()->getPath();
16
+ }
17
+ if ( empty( $value ) ) {
18
+ $value = '/';
19
+ }
20
+ return $value;
21
+ }
22
+ }
src/lib/src/Rules/Conditions/Traits/RequestScriptName.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions\RequestScriptNameUnavailableException;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ /**
9
+ * @property string $request_script_name
10
+ */
11
+ trait RequestScriptName {
12
+
13
+ /**
14
+ * @throws RequestScriptNameUnavailableException
15
+ */
16
+ protected function getRequestScriptName() :string {
17
+ $value = $this->request_script_name;
18
+ if ( empty( $value ) ) {
19
+ $req = Services::Request();
20
+ $possible = array_values( array_unique( array_map( 'basename', array_filter( [
21
+ $req->server( 'SCRIPT_NAME' ),
22
+ $req->server( 'SCRIPT_FILENAME' ),
23
+ $req->server( 'PHP_SELF' )
24
+ ] ) ) ) );
25
+ if ( count( $possible ) === 1 ) {
26
+ $value = current( $possible );
27
+ }
28
+ else {
29
+ throw new RequestScriptNameUnavailableException( 'Request script name is unavailable.' );
30
+ }
31
+ }
32
+ return $value;
33
+ }
34
+ }
src/lib/src/Rules/Conditions/Traits/UserAgent.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions\Traits;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions\RequestUseragentUnavailableException;
6
+ use FernleafSystems\Wordpress\Services\Services;
7
+
8
+ /**
9
+ * @property string $request_useragent
10
+ */
11
+ trait UserAgent {
12
+
13
+ /**
14
+ * @throws RequestUseragentUnavailableException
15
+ */
16
+ protected function getUserAgent() :string {
17
+ $value = $this->request_useragent;
18
+ if ( empty( $value ) ) {
19
+ $value = Services::Request()->getUserAgent();
20
+ }
21
+ if ( empty( $value ) ) {
22
+ throw new RequestUseragentUnavailableException();
23
+ }
24
+ return $value;
25
+ }
26
+ }
src/lib/src/Rules/Conditions/WpIsAdmin.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ class WpIsAdmin extends Base {
6
+
7
+ const SLUG = 'wp_is_admin';
8
+
9
+ protected function execConditionCheck() :bool {
10
+ $thisReq = $this->getCon()->this_req;
11
+ if ( !isset( $thisReq->wp_is_admin ) ) {
12
+ $thisReq->wp_is_admin = ( is_network_admin() || is_admin() );
13
+ $thisReq->wp_is_networkadmin = is_network_admin();
14
+ }
15
+ return $thisReq->wp_is_admin;
16
+ }
17
+ }
src/lib/src/Rules/Conditions/WpIsAjax.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Services\Services;
6
+
7
+ class WpIsAjax extends Base {
8
+
9
+ const SLUG = 'wp_is_ajax';
10
+
11
+ protected function execConditionCheck() :bool {
12
+ $thisReq = $this->getCon()->this_req;
13
+ if ( !isset( $thisReq->wp_is_ajax ) ) {
14
+ $thisReq->wp_is_ajax = Services::WpGeneral()->isAjax();
15
+ }
16
+ return $thisReq->wp_is_ajax;
17
+ }
18
+ }
src/lib/src/Rules/Conditions/WpIsWpcli.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Services\Services;
6
+
7
+ class WpIsWpcli extends Base {
8
+
9
+ const SLUG = 'wp_is_wpcli';
10
+
11
+ protected function execConditionCheck() :bool {
12
+ $thisReq = $this->getCon()->this_req;
13
+ if ( !isset( $thisReq->wp_is_wpcli ) ) {
14
+ $thisReq->wp_is_wpcli = Services::WpGeneral()->isWpCli();
15
+ }
16
+ return $thisReq->wp_is_wpcli;
17
+ }
18
+ }
src/lib/src/Rules/Conditions/WpIsXmlrpc.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Conditions;
4
+
5
+ use FernleafSystems\Wordpress\Services\Services;
6
+
7
+ class WpIsXmlrpc extends Base {
8
+
9
+ const SLUG = 'wp_is_xmlrpc';
10
+
11
+ protected function execConditionCheck() :bool {
12
+ $thisReq = $this->getCon()->this_req;
13
+ if ( !isset( $thisReq->wp_is_xmlrpc ) ) {
14
+ $thisReq->wp_is_xmlrpc = Services::WpGeneral()->isXmlrpc();
15
+ }
16
+ return $thisReq->wp_is_xmlrpc;
17
+ }
18
+ }
src/lib/src/Rules/Exceptions/AttemptToAccessNonExistingRuleException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions;
4
+
5
+ class AttemptToAccessNonExistingRuleException extends \LogicException {
6
+
7
+ }
src/lib/src/Rules/Exceptions/IpsToMatchUnavailableException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions;
4
+
5
+ class IpsToMatchUnavailableException extends \LogicException {
6
+
7
+ }
src/lib/src/Rules/Exceptions/MatchIpIdsUnavailableException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions;
4
+
5
+ class MatchIpIdsUnavailableException extends \LogicException {
6
+
7
+ }
src/lib/src/Rules/Exceptions/MatchUseragentsUnavailableException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions;
4
+
5
+ class MatchUseragentsUnavailableException extends \LogicException {
6
+
7
+ }
src/lib/src/Rules/Exceptions/MutuallyDependentRulesException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions;
4
+
5
+ class MutuallyDependentRulesException extends \LogicException {
6
+
7
+ }
src/lib/src/Rules/Exceptions/NoConditionActionDefinedException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions;
4
+
5
+ class NoConditionActionDefinedException extends \LogicException {
6
+
7
+ }
src/lib/src/Rules/Exceptions/NoResponseActionDefinedException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions;
4
+
5
+ class NoResponseActionDefinedException extends \LogicException {
6
+
7
+ }
src/lib/src/Rules/Exceptions/NoStatusProvidedToCheckException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions;
4
+
5
+ class NoStatusProvidedToCheckException extends \LogicException {
6
+
7
+ }
src/lib/src/Rules/Exceptions/NoSuchConditionHandlerException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions;
4
+
5
+ class NoSuchConditionHandlerException extends \LogicException {
6
+
7
+ }
src/lib/src/Rules/Exceptions/NoSuchResponseHandlerException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions;
4
+
5
+ class NoSuchResponseHandlerException extends \LogicException {
6
+
7
+ }
src/lib/src/Rules/Exceptions/PathsToMatchUnavailableException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions;
4
+
5
+ class PathsToMatchUnavailableException extends \LogicException {
6
+
7
+ }
src/lib/src/Rules/Exceptions/RequestIpUnavailableException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions;
4
+
5
+ class RequestIpUnavailableException extends \LogicException {
6
+
7
+ }
src/lib/src/Rules/Exceptions/RequestScriptNameUnavailableException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions;
4
+
5
+ class RequestScriptNameUnavailableException extends \LogicException {
6
+
7
+ }
src/lib/src/Rules/Exceptions/RequestUseragentUnavailableException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions;
4
+
5
+ class RequestUseragentUnavailableException extends \LogicException {
6
+
7
+ }
src/lib/src/Rules/Exceptions/RuleNotYetRunException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions;
4
+
5
+ class RuleNotYetRunException extends \LogicException {
6
+
7
+ }
src/lib/src/Rules/Exceptions/ScriptNamesToMatchUnavailableException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions;
4
+
5
+ class ScriptNamesToMatchUnavailableException extends \LogicException {
6
+
7
+ }
src/lib/src/Rules/Exceptions/UnsupportedStatusException.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions;
4
+
5
+ class UnsupportedStatusException extends \LogicException {
6
+
7
+ }
src/lib/src/Rules/Processors/BaseProcessor.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Processors;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\PluginControllerConsumer;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\RulesController;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\RuleVO;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Utility\RulesControllerConsumer;
9
+
10
+ class BaseProcessor {
11
+
12
+ use RulesControllerConsumer;
13
+ use PluginControllerConsumer;
14
+
15
+ /**
16
+ * @var RuleVO
17
+ */
18
+ protected $rule;
19
+
20
+ public function __construct( RuleVO $rule, RulesController $rulesCon ) {
21
+ $this->setRulesCon( $rulesCon );
22
+ $this->rule = $rule;
23
+ }
24
+ }
src/lib/src/Rules/Processors/ConditionsProcessor.php ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Processors;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Utility\RulesControllerConsumer;
7
+
8
+ class ConditionsProcessor extends BaseProcessor {
9
+
10
+ private $consolidatedMeta = [];
11
+
12
+ public function getConsolidatedMeta() :array {
13
+ return array_filter( $this->consolidatedMeta );
14
+ }
15
+
16
+ public function runAllRuleConditions() :bool {
17
+ // If there are no conditions, then we're 'true'
18
+ return empty( $this->rule->conditions[ 'group' ] ) ||
19
+ $this->processConditionGroup(
20
+ $this->rule->conditions[ 'group' ],
21
+ ( $this->rule->conditions[ 'logic' ] ?? 'AND' ) === 'AND'
22
+ );
23
+ }
24
+
25
+ /**
26
+ * This is recursive and essentially allows for infinite nesting of groups of rules with different logic.
27
+ */
28
+ private function processConditionGroup( array $conditionGroup, $isLogicAnd = true ) :bool {
29
+ $finalMatch = null;
30
+
31
+ foreach ( $conditionGroup as $subCondition ) {
32
+
33
+ if ( isset( $subCondition[ 'group' ] ) ) {
34
+ $matched = $this->processConditionGroup( $subCondition[ 'group' ], ( $subCondition[ 'logic' ] ?? 'AND' ) === 'AND' );
35
+ }
36
+ elseif ( isset( $subCondition[ 'rule' ] ) ) {
37
+ try {
38
+ $matched = $this->lookupPreviousRule( $subCondition[ 'rule' ] );
39
+ if ( $subCondition[ 'invert_match' ] ?? false ) {
40
+ $matched = !$matched;
41
+ }
42
+ }
43
+ catch ( Exceptions\RuleNotYetRunException $e ) {
44
+ error_log( $e->getMessage() );
45
+ return false;
46
+ }
47
+ catch ( Exceptions\AttemptToAccessNonExistingRuleException $e ) {
48
+ error_log( $e->getMessage() );
49
+ return false;
50
+ }
51
+ }
52
+ else {
53
+ try {
54
+ $handler = $this->rulesCon->getConditionHandler( $subCondition );
55
+ $matched = $handler->setRule( $this->rule )
56
+ ->run();
57
+ if ( $subCondition[ 'invert_match' ] ?? false ) {
58
+ $matched = !$matched;
59
+ }
60
+ $this->consolidatedMeta[ $subCondition[ 'condition' ] ] = $handler->getConditionTriggerMetaData();
61
+ }
62
+ catch ( Exceptions\NoSuchConditionHandlerException $e ) {
63
+ error_log( $e->getMessage() );
64
+ continue;
65
+ }
66
+ catch ( Exceptions\NoConditionActionDefinedException $e ) {
67
+ error_log( $e->getMessage() );
68
+ continue;
69
+ }
70
+ }
71
+
72
+ if ( is_null( $finalMatch ) ) {
73
+ $finalMatch = $matched;
74
+ }
75
+
76
+ if ( $isLogicAnd ) {
77
+ $finalMatch = $finalMatch && $matched;
78
+ if ( !$finalMatch ) {
79
+ break;
80
+ }
81
+ }
82
+ else {
83
+ $finalMatch = $finalMatch || $matched;
84
+ }
85
+ }
86
+
87
+ return (bool)$finalMatch;
88
+ }
89
+
90
+ /**
91
+ * @throws Exceptions\AttemptToAccessNonExistingRuleException
92
+ * @throws Exceptions\RuleNotYetRunException
93
+ */
94
+ private function lookupPreviousRule( string $rule ) :bool {
95
+ $result = $this->rulesCon->getRule( $rule )->result;
96
+ if ( is_null( $result ) ) {
97
+ throw new Exceptions\RuleNotYetRunException( 'Rule not yet run: '.$rule );
98
+ }
99
+ return $result;
100
+ }
101
+ }
src/lib/src/Rules/Processors/ResponseProcessor.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Processors;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\RulesController;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\RuleVO;
8
+
9
+ class ResponseProcessor extends BaseProcessor {
10
+
11
+ /**
12
+ * @var array
13
+ */
14
+ private $triggerMetaData;
15
+
16
+ public function __construct( RuleVO $rule, RulesController $rulesCon, array $triggerMetaData ) {
17
+ parent::__construct( $rule, $rulesCon );
18
+ $this->triggerMetaData = $triggerMetaData;
19
+ }
20
+
21
+ public function run() {
22
+ $eventFireResponseProcessed = false;
23
+ foreach ( $this->rule->responses as $response ) {
24
+ try {
25
+ $this->rulesCon->getResponseHandler( $response )
26
+ ->setConditionTriggerMeta( $this->triggerMetaData )
27
+ ->setRule( $this->rule )
28
+ ->run();
29
+ if ( $response[ 'response' ] === 'event_fire' ) {
30
+ $eventFireResponseProcessed = true;
31
+ }
32
+ }
33
+ catch ( Exceptions\NoResponseActionDefinedException $e ) {
34
+ error_log( $e->getMessage() );
35
+ }
36
+ catch ( Exceptions\NoSuchResponseHandlerException $e ) {
37
+ error_log( $e->getMessage() );
38
+ }
39
+ }
40
+
41
+ // We always fire the default event if an event wasn't fired already
42
+ if ( !$eventFireResponseProcessed ) {
43
+ $this->rulesCon->getDefaultEventFireResponseHandler()
44
+ ->setConditionTriggerMeta( $this->triggerMetaData )
45
+ ->setRule( $this->rule )
46
+ ->run();
47
+ }
48
+ }
49
+ }
src/lib/src/Rules/Render/RenderBase.php ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Render;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Utility\RulesControllerConsumer;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Utilities\Render\BaseTemplateRenderer;
7
+
8
+ abstract class RenderBase extends BaseTemplateRenderer {
9
+
10
+ use RulesControllerConsumer;
11
+
12
+ protected function getTemplateBaseDir() :string {
13
+ return '/wpadmin_pages/insights/rules';
14
+ }
15
+ }
src/lib/src/Rules/Render/RenderSummary.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Render;
4
+
5
+ class RenderSummary extends RenderBase {
6
+
7
+ protected function getTemplateStub() :string {
8
+ return 'summary/summary';
9
+ }
10
+
11
+ protected function getRenderData() :array {
12
+ $rulesCon = $this->getRulesCon();
13
+ $rules = $rulesCon->getRules();
14
+
15
+ $components = [
16
+ 'hooks' => [
17
+ 'immediate'
18
+ ],
19
+ ];
20
+
21
+ $simpleID = 0;
22
+ foreach ( $rules as $rule ) {
23
+ if ( empty( $rule->wp_hook ) ) {
24
+ $rule->wp_hook = 'immediate';
25
+ }
26
+ else {
27
+ $components[ 'hooks' ][] = $rule->wp_hook;
28
+ }
29
+ $rule->simple_id = $simpleID++;
30
+ }
31
+
32
+ $components[ 'hooks' ] = array_unique( $components[ 'hooks' ] );
33
+
34
+ $hooks = array_map(
35
+ function ( $rule ) {
36
+ return $rule->wp_hook;
37
+ },
38
+ $rules
39
+ );
40
+
41
+ return [
42
+ 'vars' => [
43
+ 'components' => $components,
44
+ 'rules' => $rules,
45
+ ]
46
+ ];
47
+ }
48
+ }
src/lib/src/Rules/Responses/Base.php ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Responses;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\PluginControllerConsumer;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\RuleVO;
7
+
8
+ abstract class Base {
9
+
10
+ use PluginControllerConsumer;
11
+
12
+ const SLUG = '';
13
+
14
+ /**
15
+ * @var RuleVO
16
+ */
17
+ protected $rule;
18
+
19
+ /**
20
+ * @var array
21
+ */
22
+ protected $responseParams;
23
+
24
+ /**
25
+ * @var array
26
+ */
27
+ protected $conditionTriggerMeta;
28
+
29
+ public function __construct( array $responseParams ) {
30
+ $this->responseParams = $responseParams;
31
+ }
32
+
33
+ public function setRule( RuleVO $rule ) {
34
+ $this->rule = $rule;
35
+ return $this;
36
+ }
37
+
38
+ public function setConditionTriggerMeta( array $meta ) :self {
39
+ $this->conditionTriggerMeta = $meta;
40
+ return $this;
41
+ }
42
+
43
+ public function run() {
44
+ $con = $this->getCon();
45
+ if ( $this->rule->immediate_exec_response || did_action( $con->prefix( 'after_run_processors' ) ) ) {
46
+ $this->runExecResponse();
47
+ }
48
+ else {
49
+ add_action( $con->prefix( 'after_run_processors' ), function () {
50
+ $this->runExecResponse();
51
+ } );
52
+ }
53
+ }
54
+
55
+ private function runExecResponse() :bool {
56
+ try {
57
+ return $this->execResponse();
58
+ }
59
+ catch ( \Exception $e ) {
60
+ return false;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * @throws \Exception
66
+ */
67
+ abstract protected function execResponse() :bool;
68
+
69
+ protected function getConsolidatedConditionMeta() :array {
70
+ $meta = [];
71
+ foreach ( $this->conditionTriggerMeta as $m ) {
72
+ $meta = array_merge( $meta, $m );
73
+ }
74
+ return $meta;
75
+ }
76
+ }
src/lib/src/Rules/Responses/BlockAuthorFishing.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Responses;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Blocks\RenderBlockPages\RenderBlockAuthorFishing;
6
+
7
+ class BlockAuthorFishing extends Base {
8
+
9
+ const SLUG = 'block_author_fishing';
10
+
11
+ protected function execResponse() :bool {
12
+ ( new RenderBlockAuthorFishing() )
13
+ ->setMod( $this->getCon()->getModule_Lockdown() )
14
+ ->setAuxData( $this->getConsolidatedConditionMeta() )
15
+ ->display();
16
+ return true;
17
+ }
18
+ }
src/lib/src/Rules/Responses/DisableFileEditing.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Responses;
4
+
5
+ class DisableFileEditing extends Base {
6
+
7
+ const SLUG = 'disable_file_editing';
8
+
9
+ protected function execResponse() :bool {
10
+ if ( !defined( 'DISALLOW_FILE_EDIT' ) ) {
11
+ define( 'DISALLOW_FILE_EDIT', true );
12
+ }
13
+
14
+ add_filter( 'user_has_cap',
15
+ /**
16
+ * @param array $allCaps
17
+ * @param array $cap
18
+ * @param array $args
19
+ * @return array
20
+ */
21
+ function ( $allCaps, $cap, $args ) {
22
+ $requestedCapability = $args[ 0 ];
23
+ if ( in_array( $requestedCapability, [ 'edit_themes', 'edit_plugins', 'edit_files' ] ) ) {
24
+ $allCaps[ $requestedCapability ] = false;
25
+ }
26
+ return $allCaps;
27
+ },
28
+ PHP_INT_MAX, 3
29
+ );
30
+
31
+ return true;
32
+ }
33
+ }
src/lib/src/Rules/Responses/DisableXmlrpc.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Responses;
4
+
5
+ class DisableXmlrpc extends Base {
6
+
7
+ const SLUG = 'disable_xmlrpc';
8
+
9
+ private $processed = false;
10
+
11
+ protected function execResponse() :bool {
12
+ add_filter( 'xmlrpc_enabled', [ $this, 'disableXmlrpc' ], 1000, 0 );
13
+ add_filter( 'xmlrpc_methods', [ $this, 'disableXmlrpc' ], 1000, 0 );
14
+ return true;
15
+ }
16
+
17
+ /**
18
+ * @return array|false
19
+ */
20
+ public function disableXmlrpc() {
21
+ if ( !$this->processed ) {
22
+ $this->processed = true;
23
+ $this->getCon()->fireEvent( 'block_xml' );
24
+ }
25
+ return ( current_filter() == 'xmlrpc_enabled' ) ? false : [];
26
+ }
27
+ }
src/lib/src/Rules/Responses/EventFire.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Responses;
4
+
5
+ class EventFire extends Base {
6
+
7
+ const SLUG = 'event_fire';
8
+
9
+ protected function execResponse() :bool {
10
+ $params = $this->responseParams;
11
+ $event = $params[ 'event' ] ?? '';
12
+ if ( !empty( $event ) ) {
13
+ unset( $params[ 'event' ] );
14
+
15
+ // Translate rules condition meta items to audit trail params.
16
+ if ( !empty( $params[ 'audit_params_map' ] ) ) {
17
+ if ( empty( $params[ 'audit_params' ] ) ) {
18
+ $params[ 'audit_params' ] = [];
19
+ }
20
+ $conditionMeta = $this->getConsolidatedConditionMeta();
21
+ foreach ( $params[ 'audit_params_map' ] as $paramKey => $metaKey ) {
22
+ if ( isset( $conditionMeta[ $metaKey ] ) ) {
23
+ $params[ 'audit_params' ][ $paramKey ] = $conditionMeta[ $metaKey ];
24
+ }
25
+ else {
26
+ // error_log( sprintf( 'firing event "%s" but missing condition meta key: %s', $event, $metaKey ) );
27
+ }
28
+ }
29
+ }
30
+
31
+ // error_log( var_export( $conditionMeta, true ) );
32
+ // error_log( var_export( $params, true ) );
33
+ $this->getCon()->fireEvent( $event, $params );
34
+ }
35
+
36
+ $this->getCon()->fireEvent( 'shield/rules/response/'.$this->rule->slug );
37
+
38
+ return true;
39
+ }
40
+ }
src/lib/src/Rules/Responses/FirewallBlock.php ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Responses;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Blocks\RenderBlockPages\RenderBlockFirewall;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Firewall\Options;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
+
9
+ class FirewallBlock extends Base {
10
+
11
+ const SLUG = 'firewall_block';
12
+
13
+ protected function execResponse() :bool {
14
+ $this->runBlock();
15
+ return true;
16
+ }
17
+
18
+ private function runBlock() {
19
+ $mod = $this->getCon()->getModule_Firewall();
20
+
21
+ $this->preBlock();
22
+
23
+ remove_filter( 'wp_robots', 'wp_robots_noindex_search' );
24
+ remove_filter( 'wp_robots', 'wp_robots_noindex_embeds' );
25
+ Services::WpGeneral()->turnOffCache();
26
+ nocache_headers();
27
+
28
+ switch ( $mod->getBlockResponse() ) {
29
+ case 'redirect_die':
30
+ Services::WpGeneral()->wpDie( 'Firewall Triggered' );
31
+ break;
32
+ case 'redirect_die_message':
33
+ ( new RenderBlockFirewall() )
34
+ ->setMod( $mod )
35
+ ->setAuxData( $this->getConsolidatedConditionMeta() )
36
+ ->display();
37
+ break;
38
+ case 'redirect_home':
39
+ Services::Response()->redirectToHome();
40
+ break;
41
+ case 'redirect_404':
42
+ Services::Response()->sendApache404();
43
+ break;
44
+ default:
45
+ break;
46
+ }
47
+ die();
48
+ }
49
+
50
+ private function preBlock() {
51
+ $mod = $this->getCon()->getModule_Firewall();
52
+ /** @var Options $opts */
53
+ $opts = $mod->getOptions();
54
+ if ( $opts->isSendBlockEmail() ) {
55
+ $this->getCon()->fireEvent(
56
+ $this->sendBlockEmail() ? 'fw_email_success' : 'fw_email_fail',
57
+ [ 'audit_params' => [ 'to' => $mod->getPluginReportEmail() ] ]
58
+ );
59
+ }
60
+ }
61
+
62
+ private function sendBlockEmail() :bool {
63
+ $ip = Services::IP()->getRequestIp();
64
+
65
+ $resultData = $this->getConsolidatedConditionMeta();
66
+
67
+ $mod = $this->getCon()->getModule_Firewall();
68
+ return $mod->getEmailProcessor()->sendEmailWithTemplate(
69
+ '/email/firewall_block.twig',
70
+ $mod->getPluginReportEmail(),
71
+ __( 'Firewall Block Alert', 'wp-simple-firewall' ),
72
+ [
73
+ 'strings' => [
74
+ 'shield_blocked' => sprintf( __( '%s Firewall has blocked a request to your WordPress site.', 'wp-simple-firewall' ),
75
+ $this->getCon()->getHumanName() ),
76
+ 'details_below' => __( 'Details for the request are given below:', 'wp-simple-firewall' ),
77
+ 'details' => __( 'Request Details', 'wp-simple-firewall' ),
78
+ 'ip_lookup' => __( 'IP Address Lookup' ),
79
+ 'this_is_info' => __( 'This is for informational purposes only.' ),
80
+ 'already_blocked' => sprintf( __( '%s has already taken the necessary action of blocking the request.' ),
81
+ $this->getCon()->getHumanName() ),
82
+ ],
83
+ 'hrefs' => [
84
+ 'ip_lookup' => add_query_arg( [ 'ip' => $ip ], 'https://shsec.io/botornot' )
85
+ ],
86
+ 'vars' => [
87
+ 'req_details' => [
88
+ __( 'Visitor IP Address', 'wp-simple-firewall' ) => $ip,
89
+ __( 'Firewall Rule', 'wp-simple-firewall' ) => $this->getCon()
90
+ ->getModule_Firewall()
91
+ ->getStrings()
92
+ ->getOptionStrings( 'block_'.$resultData[ 'match_category' ] )[ 'name' ] ?? 'No name',
93
+ __( 'Firewall Pattern', 'wp-simple-firewall' ) => $resultData[ 'match_pattern' ] ?? 'Unavailable',
94
+ __( 'Request Path', 'wp-simple-firewall' ) => Services::Request()->getPath(),
95
+ __( 'Parameter Name', 'wp-simple-firewall' ) => $resultData[ 'match_request_param' ] ?? 'Unavailable',
96
+ __( 'Parameter Value', 'wp-simple-firewall' ) => $resultData[ 'match_request_value' ] ?? 'Unavailable',
97
+ ]
98
+ ]
99
+ ]
100
+ );
101
+ }
102
+ }
src/lib/src/Rules/Responses/ForceSslAdmin.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Responses;
4
+
5
+ class ForceSslAdmin extends Base {
6
+
7
+ const SLUG = 'force_ssl_admin';
8
+
9
+ protected function execResponse() :bool {
10
+ if ( !defined( 'FORCE_SSL_ADMIN' ) ) {
11
+ define( 'FORCE_SSL_ADMIN', true );
12
+ }
13
+ force_ssl_admin( true );
14
+ return true;
15
+ }
16
+ }
src/lib/src/Rules/Responses/HideGeneratorTag.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Responses;
4
+
5
+ class HideGeneratorTag extends Base {
6
+
7
+ const SLUG = 'hide_generator_tag';
8
+
9
+ protected function execResponse() :bool {
10
+ remove_action( 'wp_head', 'wp_generator' );
11
+ return true;
12
+ }
13
+ }
src/lib/src/Rules/Responses/IsBotProbe404.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Responses;
4
+
5
+ class IsBotProbe404 extends Base {
6
+
7
+ const SLUG = 'is_bot_probe_404';
8
+
9
+ protected function execResponse() :bool {
10
+ return true;
11
+ }
12
+ }
src/lib/src/Rules/Responses/SetIpBlocked.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Responses;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\IPs\Lib\BlockRequest;
6
+
7
+ class SetIpBlocked extends Base {
8
+
9
+ const SLUG = 'set_ip_blocked';
10
+
11
+ protected function execResponse() :bool {
12
+ $this->getCon()->this_req->is_ip_blocked = true;
13
+
14
+ add_action( 'init', function () {
15
+ ( new BlockRequest() )
16
+ ->setMod( $this->getCon()->getModule_IPs() )
17
+ ->execute();
18
+ }, -100000 );
19
+
20
+ return true;
21
+ }
22
+ }
src/lib/src/Rules/Responses/SetIpWhitelisted.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Responses;
4
+
5
+ class SetIpWhitelisted extends Base {
6
+
7
+ const SLUG = 'set_ip_whitelisted';
8
+
9
+ protected function execResponse() :bool {
10
+ $this->getCon()->this_req->is_ip_whitelisted = true;
11
+ return true;
12
+ }
13
+ }
src/lib/src/Rules/Responses/SetIsTrustedBot.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Responses;
4
+
5
+ class SetIsTrustedBot extends Base {
6
+
7
+ const SLUG = 'set_is_trusted_bot';
8
+
9
+ protected function execResponse() :bool {
10
+ $this->getCon()->this_req->is_trusted_bot = true;
11
+ return true;
12
+ }
13
+ }
src/lib/src/Rules/Responses/SetRequestBypassesAllRestrictions.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Responses;
4
+
5
+ class SetRequestBypassesAllRestrictions extends Base {
6
+
7
+ const SLUG = 'set_request_bypasses_all_restrictions';
8
+
9
+ protected function execResponse() :bool {
10
+ $this->getCon()->this_req->request_bypasses_all_restrictions = true;
11
+ return true;
12
+ }
13
+ }
src/lib/src/Rules/Responses/SetSecurityAdmin.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Responses;
4
+
5
+ class SetSecurityAdmin extends Base {
6
+
7
+ const SLUG = 'set_security_admin';
8
+
9
+ protected function execResponse() :bool {
10
+ $this->getCon()->this_req->is_security_admin = true;
11
+ return true;
12
+ }
13
+ }
src/lib/src/Rules/Responses/TrafficRateLimitExceeded.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Responses;
4
+
5
+ class TrafficRateLimitExceeded extends Base {
6
+
7
+ const SLUG = 'traffic_rate_limit_exceeded';
8
+
9
+ protected function execResponse() :bool {
10
+ // wp_die( 'Too many requests' );
11
+ return true;
12
+ }
13
+ }
src/lib/src/Rules/RuleVO.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules;
4
+
5
+ use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
+
7
+ /**
8
+ * @property string $slug
9
+ * @property string $name
10
+ * @property string $description
11
+ * @property string $wp_hook
12
+ * @property int $wp_hook_level
13
+ * @property bool $result
14
+ * @property bool $immediate_exec_response
15
+ * @property string[] $flags
16
+ * @property string[] $prerequisites
17
+ * @property array[] $conditions
18
+ * @property array[] $responses
19
+ * @property string[] $all_actions
20
+ */
21
+ class RuleVO extends DynPropertiesClass {
22
+
23
+ public function __get( string $key ) {
24
+ $value = parent::__get( $key );
25
+ switch ( $key ) {
26
+ case 'wp_hook':
27
+ if ( empty( $value ) ) {
28
+ $value = $this->determineWpHook();
29
+ $this->wp_hook = $value;
30
+ }
31
+ break;
32
+
33
+ case 'immediate_exec_response':
34
+ $value = (bool)$value;
35
+ break;
36
+
37
+ case 'flags':
38
+ case 'prerequisites':
39
+ case 'conditions':
40
+ case 'responses':
41
+ if ( !is_array( $value ) ) {
42
+ $value = [];
43
+ }
44
+ $this->{$key} = $value;
45
+ break;
46
+
47
+ default:
48
+ break;
49
+ }
50
+ return $value;
51
+ }
52
+
53
+ private function determineWpHook() :string {
54
+ $hook = '';
55
+ if ( isset( $this->prerequisites[ 'is_logged_in' ] ) ) {
56
+ $hook = 'init';
57
+ }
58
+ return $hook;
59
+ }
60
+ }
src/lib/src/Rules/RulesController.php ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules;
4
+
5
+ use FernleafSystems\Utilities\Logic\ExecOnce;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Crons\PluginCronsConsumer;
7
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\PluginControllerConsumer;
8
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Exceptions\{
9
+ AttemptToAccessNonExistingRuleException,
10
+ NoConditionActionDefinedException,
11
+ NoResponseActionDefinedException,
12
+ NoSuchConditionHandlerException,
13
+ NoSuchResponseHandlerException
14
+ };
15
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Processors\{
16
+ ConditionsProcessor,
17
+ ResponseProcessor
18
+ };
19
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Responses\EventFire;
20
+
21
+ class RulesController {
22
+
23
+ use ExecOnce;
24
+ use PluginCronsConsumer;
25
+ use PluginControllerConsumer;
26
+
27
+ /**
28
+ * @var RuleVO[]
29
+ */
30
+ private $rules;
31
+
32
+ /**
33
+ * @var RulesStorageHandler
34
+ */
35
+ private $storageHandler;
36
+
37
+ public $processComplete;
38
+
39
+ public function __construct() {
40
+ $this->processComplete = false;
41
+ $this->storageHandler = ( new RulesStorageHandler() )->setRulesCon( $this );
42
+ }
43
+
44
+ protected function run() {
45
+
46
+ // Rebuild the rules upon upgrade or settings change
47
+ if ( $this->getCon()->cfg->rebuilt ) {
48
+ $this->storageHandler->buildAndStore();
49
+ }
50
+
51
+ // Rebuild the rules when configuration is updated
52
+ add_action( $this->getCon()->prefix( 'pre_options_store' ), function () {
53
+ $this->storageHandler->buildAndStore();
54
+ } );
55
+
56
+ // Rebuild the rules every hour
57
+ $this->setupCronHooks();
58
+ }
59
+
60
+ public function renderSummary() :string {
61
+ return ( new Render\RenderSummary() )
62
+ ->setCon( $this->getCon() )
63
+ ->setRulesCon( $this )
64
+ ->render();
65
+ }
66
+
67
+ public function runHourlyCron() {
68
+ $this->storageHandler->buildAndStore();
69
+ }
70
+
71
+ public function getRulesResultsSummary() :array {
72
+ return array_map(
73
+ function ( $rule ) {
74
+ return $rule->result;
75
+ },
76
+ $this->getRules()
77
+ );
78
+ }
79
+
80
+ public function isRulesEngineReady() :bool {
81
+ return !empty( $this->getRules() );
82
+ }
83
+
84
+ public function processRules() {
85
+ if ( !$this->processComplete && $this->isRulesEngineReady() ) {
86
+
87
+ foreach ( $this->getImmediateRules() as $rule ) {
88
+ $this->processRule( $rule );
89
+ }
90
+
91
+ $allHooks = array_unique( array_filter( array_map( function ( $rule ) {
92
+ return $rule->wp_hook;
93
+ }, $this->getRules() ) ) );
94
+ foreach ( $allHooks as $wpHook ) {
95
+ add_action( $wpHook, function () use ( $wpHook ) {
96
+ foreach ( $this->getRulesForHook( $wpHook ) as $rule ) {
97
+ $this->processRule( $rule );
98
+ }
99
+ }, PHP_INT_MIN );
100
+ }
101
+
102
+ $this->processComplete = true;
103
+
104
+ add_action( $this->getCon()->prefix( 'plugin_shutdown' ), function () {
105
+ // error_log( var_export( $this->getRulesResultsSummary(), true ) );
106
+ } );
107
+ }
108
+ }
109
+
110
+ private function processRule( RuleVO $rule ) {
111
+ $conditionPro = ( new ConditionsProcessor( $rule, $this ) )->setCon( $this->getCon() );
112
+ if ( !isset( $rule->result ) ) {
113
+ $rule->result = $conditionPro->runAllRuleConditions();
114
+ if ( $rule->result ) {
115
+ ( new ResponseProcessor( $rule, $this, $conditionPro->getConsolidatedMeta() ) )->run();
116
+ }
117
+ }
118
+ }
119
+
120
+ /**
121
+ * @throws AttemptToAccessNonExistingRuleException
122
+ */
123
+ public function getRule( string $slug ) :RuleVO {
124
+ $rules = $this->getRules();
125
+ if ( !isset( $rules[ $slug ] ) ) {
126
+ throw new AttemptToAccessNonExistingRuleException( sprintf( 'Rule "%s" does not exist', $slug ) );
127
+ }
128
+ return $rules[ $slug ];
129
+ }
130
+
131
+ /**
132
+ * @return RuleVO[]
133
+ */
134
+ public function getRules() :array {
135
+ if ( !isset( $this->rules ) ) {
136
+ try {
137
+ $this->rules = array_map(
138
+ function ( $rule ) {
139
+ return ( new RuleVO() )->applyFromArray( $rule );
140
+ },
141
+ ( new RulesStorageHandler() )
142
+ ->setRulesCon( $this )
143
+ ->loadRules()[ 'rules' ]
144
+ );
145
+ }
146
+ catch ( \Exception $e ) {
147
+ $this->rules = [];
148
+ }
149
+ }
150
+ return $this->rules;
151
+ }
152
+
153
+ /**
154
+ * @return RuleVO[]
155
+ */
156
+ private function getImmediateRules() :array {
157
+ return $this->getRulesForHook( '' );
158
+ }
159
+
160
+ /**
161
+ * @return RuleVO[]
162
+ */
163
+ private function getRulesForHook( string $hook ) :array {
164
+ return array_filter( $this->getRules(), function ( $rule ) use ( $hook ) {
165
+ return $rule->wp_hook === $hook;
166
+ } );
167
+ }
168
+
169
+ /**
170
+ * @throws NoConditionActionDefinedException
171
+ * @throws NoSuchConditionHandlerException
172
+ */
173
+ public function getConditionHandler( array $condition ) :Conditions\Base {
174
+ if ( empty( $condition[ 'condition' ] ) ) {
175
+ throw new NoConditionActionDefinedException( 'No Condition Handler available for: '.var_export( $condition, true ) );
176
+ }
177
+ $class = $this->locateConditionHandlerClass( $condition[ 'condition' ] );
178
+ /** @var Conditions\Base $cond */
179
+ $cond = new $class( $condition[ 'params' ] ?? [] );
180
+ return $cond->setCon( $this->getCon() );
181
+ }
182
+
183
+ /**
184
+ * @throws NoSuchConditionHandlerException
185
+ */
186
+ public function locateConditionHandlerClass( string $condition ) :string {
187
+ $theHandlerClass = sprintf( '%s\\Conditions\\%s', __NAMESPACE__,
188
+ implode( '', array_map( 'ucfirst', explode( '_', $condition ) ) ) );
189
+ if ( !class_exists( $theHandlerClass ) ) {
190
+ throw new NoSuchConditionHandlerException( 'No such Condition Handler Class for: '.$theHandlerClass );
191
+ }
192
+ return $theHandlerClass;
193
+ }
194
+
195
+ public function getDefaultEventFireResponseHandler() :Responses\EventFire {
196
+ /** @var Responses\Base $d */
197
+ return ( new EventFire( [] ) )->setCon( $this->getCon() );
198
+ }
199
+
200
+ /**
201
+ * @throws NoResponseActionDefinedException
202
+ * @throws NoSuchResponseHandlerException
203
+ */
204
+ public function getResponseHandler( array $response ) :Responses\Base {
205
+ if ( empty( $response[ 'response' ] ) ) {
206
+ throw new NoResponseActionDefinedException( 'No Response Handler available for: '.var_export( $response, true ) );
207
+ }
208
+ $theResponseClass = $this->locateResponseHandlerClass( $response[ 'response' ] );
209
+ /** @var Responses\Base $d */
210
+ $d = new $theResponseClass( $response[ 'params' ] ?? [] );
211
+ return $d->setCon( $this->getCon() );
212
+ }
213
+
214
+ /**
215
+ * @throws NoSuchResponseHandlerException
216
+ */
217
+ public function locateResponseHandlerClass( string $response ) :string {
218
+ $theHandlerClass = sprintf( '%s\\Responses\\%s', __NAMESPACE__,
219
+ implode( '', array_map( 'ucfirst', explode( '_', $response ) ) ) );
220
+ if ( !class_exists( $theHandlerClass ) ) {
221
+ throw new NoSuchResponseHandlerException( 'No Response Handler Class for: '.$theHandlerClass );
222
+ }
223
+ return $theHandlerClass;
224
+ }
225
+ }
src/lib/src/Rules/RulesStorageHandler.php ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Build\Builder;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\Utility\RulesControllerConsumer;
7
+ use FernleafSystems\Wordpress\Services\Services;
8
+
9
+ class RulesStorageHandler {
10
+
11
+ use RulesControllerConsumer;
12
+
13
+ /**
14
+ * @throws \Exception
15
+ */
16
+ public function loadRules( bool $attemptRebuild = true ) :array {
17
+
18
+ $rules = $this->loadRawFromWP();
19
+ if ( empty( $rules ) ) {
20
+ $rules = $this->loadRawFromFile();
21
+ }
22
+
23
+ if ( $attemptRebuild && ( empty( $rules ) || empty( $rules[ 'rules' ] ) ) ) {
24
+ $this->buildAndStore();
25
+ $rules = $this->loadRules( false );
26
+ }
27
+
28
+ if ( !is_array( $rules[ 'rules' ] ) || empty( $rules[ 'rules' ] ) ) {
29
+ throw new \Exception( 'No rules to load' );
30
+ }
31
+
32
+ return $rules;
33
+ }
34
+
35
+ public function buildAndStore() {
36
+ $this->store(
37
+ ( new Builder() )
38
+ ->setRulesCon( $this->getRulesCon() )
39
+ ->run()
40
+ );
41
+ }
42
+
43
+ public function build() :array {
44
+ return ( new Builder() )
45
+ ->setRulesCon( $this->getRulesCon() )
46
+ ->run();
47
+ }
48
+
49
+ public function store( array $rules ) :bool {
50
+ $WP = Services::WpGeneral();
51
+ $req = Services::Request();
52
+
53
+ $data = [
54
+ 'ts' => $req->ts(),
55
+ 'time' => $WP->getTimeStampForDisplay( $req->ts() ),
56
+ 'rules' => array_map( function ( RuleVO $rule ) {
57
+ return $rule->getRawData();
58
+ }, $rules ),
59
+ ];
60
+
61
+ $WP->updateOption( $this->getWpStorageKey(), $data );
62
+ return Services::WpFs()->putFileContent( $this->getPathToRules(), wp_json_encode( $data ) );
63
+ }
64
+
65
+ private function loadRawFromWP() :array {
66
+ $raw = Services::WpGeneral()->getOption( $this->getWpStorageKey() );
67
+ return is_array( $raw ) ? $raw : [];
68
+ }
69
+
70
+ private function loadRawFromFile() :array {
71
+ $rules = [];
72
+
73
+ $content = Services::WpFs()->getFileContent( $this->getPathToRules() );
74
+ if ( !empty( $content ) ) {
75
+ $decoded = @json_decode( $content, true );
76
+ if ( is_array( $decoded ) ) {
77
+ $rules = $decoded;
78
+ }
79
+ }
80
+ return $rules;
81
+ }
82
+
83
+ private function getPathToRules() :string {
84
+ return path_join( __DIR__, 'rules.json' );
85
+ }
86
+
87
+ private function getWpStorageKey() :string {
88
+ return $this->getRulesCon()->getCon()->prefix( 'rules' );
89
+ }
90
+ }
src/lib/src/Rules/Utility/RulesControllerConsumer.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules\Utility;
4
+
5
+ use FernleafSystems\Wordpress\Plugin\Shield\Rules\RulesController;
6
+
7
+ trait RulesControllerConsumer {
8
+
9
+ /**
10
+ * @var RulesController
11
+ */
12
+ protected $rulesCon;
13
+
14
+ public function getRulesCon() :RulesController {
15
+ return $this->rulesCon;
16
+ }
17
+
18
+ public function setRulesCon( RulesController $rulesCon ) {
19
+ $this->rulesCon = $rulesCon;
20
+ return $this;
21
+ }
22
+ }
src/lib/src/Rules/WPHooksOrder.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php declare( strict_types=1 );
2
+
3
+ namespace FernleafSystems\Wordpress\Plugin\Shield\Rules;
4
+
5
+ class WPHooksOrder {
6
+
7
+ const NONE = 0;
8
+ const PLUGINS_LOADED = 10;
9
+ const INIT = 20;
10
+ const WP_LOADED = 40;
11
+ const TEMPLATE_REDIRECT = 60;
12
+ const WP = 80;
13
+ const SHUTDOWN = 100;
14
+
15
+ public static function HOOK_NAME( int $hookOrder ) :string {
16
+ return [
17
+ self::PLUGINS_LOADED => 'plugins_loaded',
18
+ self::INIT => 'init',
19
+ self::WP_LOADED => 'wp_loaded',
20
+ self::TEMPLATE_REDIRECT => 'template_redirect',
21
+ self::WP => 'wp',
22
+ self::SHUTDOWN => 'shutdown',
23
+ ][ $hookOrder ] ?? '';
24
+ }
25
+ }
src/lib/src/Rules/rules.json ADDED
File without changes
src/lib/src/ShieldNetApi/Common/BaseApi.php CHANGED
@@ -7,6 +7,7 @@ use FernleafSystems\Wordpress\Services\Services;
7
  use FernleafSystems\Wordpress\Services\Utilities\HttpRequest;
8
 
9
  /**
 
10
  * @property string $lookup_url_stub
11
  * @property string $request_method
12
  * @property int $timeout
@@ -23,43 +24,43 @@ abstract class BaseApi extends DynPropertiesClass {
23
  * @return array|null
24
  */
25
  protected function sendReq() {
26
- $oHttpReq = Services::HttpRequest();
27
 
28
- $aReqParams = [
29
  'timeout' => $this->timeout,
30
  ];
31
 
32
  switch ( $this->request_method ) {
33
 
34
  case 'post':
35
- $aReqParams[ 'body' ] = $this->params_body;
36
- $bReqSuccess = $oHttpReq->post( $this->getApiRequestUrl(), $aReqParams );
37
  break;
38
 
39
  case 'get':
40
  default:
41
  // Doing it in the ['body'] on some sites fails with the params not passed through to query string.
42
  // if they're not using the newer WP Request() class. WP 4.6+
43
- $bReqSuccess = $oHttpReq->get(
44
  add_query_arg( $this->params_query, $this->getApiRequestUrl() ),
45
- $aReqParams
46
  );
47
  break;
48
  }
49
 
50
  if ( $bReqSuccess ) {
51
- $aResponse = empty( $oHttpReq->lastResponse->body ) ? [] : @json_decode( $oHttpReq->lastResponse->body, true );
52
  }
53
  else {
54
  $aResponse = null;
55
  }
56
 
57
- $this->last_http_req = $oHttpReq;
58
  return $aResponse;
59
  }
60
 
61
  protected function getApiRequestUrl() :string {
62
- return sprintf( '%s/%s', $this->lookup_url_stub, static::API_ACTION );
63
  }
64
 
65
  /**
@@ -70,7 +71,6 @@ abstract class BaseApi extends DynPropertiesClass {
70
  }
71
 
72
  /**
73
- * @param string $key
74
  * @return mixed
75
  */
76
  public function __get( string $key ) {
@@ -90,6 +90,12 @@ abstract class BaseApi extends DynPropertiesClass {
90
  $value = empty( $value ) ? 'get' : strtolower( $value );
91
  break;
92
 
 
 
 
 
 
 
93
  case 'lookup_url_stub':
94
  if ( empty( $value ) ) {
95
  $value = static::DEFAULT_URL_STUB;
7
  use FernleafSystems\Wordpress\Services\Utilities\HttpRequest;
8
 
9
  /**
10
+ * @property int $api_version
11
  * @property string $lookup_url_stub
12
  * @property string $request_method
13
  * @property int $timeout
24
  * @return array|null
25
  */
26
  protected function sendReq() {
27
+ $httpReq = Services::HttpRequest();
28
 
29
+ $reqParams = [
30
  'timeout' => $this->timeout,
31
  ];
32
 
33
  switch ( $this->request_method ) {
34
 
35
  case 'post':
36
+ $reqParams[ 'body' ] = $this->params_body;
37
+ $bReqSuccess = $httpReq->post( $this->getApiRequestUrl(), $reqParams );
38
  break;
39
 
40
  case 'get':
41
  default:
42
  // Doing it in the ['body'] on some sites fails with the params not passed through to query string.
43
  // if they're not using the newer WP Request() class. WP 4.6+
44
+ $bReqSuccess = $httpReq->get(
45
  add_query_arg( $this->params_query, $this->getApiRequestUrl() ),
46
+ $reqParams
47
  );
48
  break;
49
  }
50
 
51
  if ( $bReqSuccess ) {
52
+ $aResponse = empty( $httpReq->lastResponse->body ) ? [] : @json_decode( $httpReq->lastResponse->body, true );
53
  }
54
  else {
55
  $aResponse = null;
56
  }
57
 
58
+ $this->last_http_req = $httpReq;
59
  return $aResponse;
60
  }
61
 
62
  protected function getApiRequestUrl() :string {
63
+ return sprintf( '%s/v%s/%s', $this->lookup_url_stub, $this->api_version, static::API_ACTION );
64
  }
65
 
66
  /**
71
  }
72
 
73
  /**
 
74
  * @return mixed
75
  */
76
  public function __get( string $key ) {
90
  $value = empty( $value ) ? 'get' : strtolower( $value );
91
  break;
92
 
93
+ case 'api_version':
94
+ if ( empty( $value ) ) {
95
+ $value = 1;
96
+ }
97
+ break;
98
+
99
  case 'lookup_url_stub':
100
  if ( empty( $value ) ) {
101
  $value = static::DEFAULT_URL_STUB;
src/lib/src/ShieldNetApi/Common/BaseShieldNetApi.php CHANGED
@@ -14,7 +14,7 @@ class BaseShieldNetApi extends BaseApi {
14
 
15
  use ModConsumer;
16
 
17
- const DEFAULT_URL_STUB = 'https://net.getshieldsecurity.com/wp-json/apto-snapi/v1';
18
 
19
  /**
20
  * @param string $key
14
 
15
  use ModConsumer;
16
 
17
+ const DEFAULT_URL_STUB = 'https://net.getshieldsecurity.com/wp-json/apto-snapi';
18
 
19
  /**
20
  * @param string $key
src/lib/src/Tables/Build/BaseBuild.php CHANGED
@@ -22,7 +22,8 @@ class BaseBuild {
22
 
23
  public function render() :string {
24
 
25
- if ( !$this->getDbHandler()->isReady() ) {
 
26
  $render = __( 'There was an error retrieving entries.', 'wp-simple-firewall' );
27
  }
28
  else {
@@ -199,10 +200,14 @@ class BaseBuild {
199
 
200
  protected function getIpAnalysisLink( string $ip ) :string {
201
  $srvIP = Services::IP();
202
- return sprintf( '<a href="%s" target="_blank" title="%s" class="ip-whois">%s</a>',
203
- $srvIP->isValidIpRange( $ip ) ? $srvIP->getIpWhoisLookup( $ip ) :
204
- $this->getCon()->getModule_Insights()->getUrl_IpAnalysis( $ip ),
 
 
205
  __( 'IP Analysis' ),
 
 
206
  $ip
207
  );
208
  }
22
 
23
  public function render() :string {
24
 
25
+ $db = $this->getDbHandler();
26
+ if ( $db && !$this->getDbHandler()->isReady() ) {
27
  $render = __( 'There was an error retrieving entries.', 'wp-simple-firewall' );
28
  }
29
  else {
200
 
201
  protected function getIpAnalysisLink( string $ip ) :string {
202
  $srvIP = Services::IP();
203
+ $href = $srvIP->isValidIpRange( $ip ) ? $srvIP->getIpWhoisLookup( $ip ) :
204
+ $this->getCon()->getModule_Insights()->getUrl_IpAnalysis( $ip );
205
+ return sprintf(
206
+ '<a href="%s" target="_blank" title="%s" class="ip-whois %s" data-ip="%s">%s</a>',
207
+ $href,
208
  __( 'IP Analysis' ),
209
+ $srvIP->isValidIpRange( $ip ) ? '' : 'modal_ip_analysis',
210
+ $ip,
211
  $ip
212
  );
213
  }
src/lib/src/Tables/Build/Ip.php CHANGED
@@ -50,23 +50,23 @@ class Ip extends BaseBuild {
50
 
51
  foreach ( $this->getEntriesRaw() as $key => $entry ) {
52
  /** @var IPs\EntryVO $entry */
53
- $aE = $entry->getRawData();
54
  $bBlocked = $entry->blocked_at > 0 || $entry->transgressions >= $nTransLimit;
55
- $aE[ 'last_trans_at' ] = Services::Request()
56
  ->carbon( true )
57
  ->setTimestamp( $entry->last_access_at )
58
  ->diffForHumans();
59
- $aE[ 'last_access_at' ] = $this->formatTimestampField( $entry->last_access_at );
60
- $aE[ 'created_at' ] = $this->formatTimestampField( $entry->created_at );
61
- $aE[ 'blocked' ] = $bBlocked ? __( 'Yes' ) : __( 'No' );
62
- $aE[ 'expires_at' ] = $this->formatTimestampField( $entry->last_access_at + $opts->getAutoExpireTime() );
63
- $aE[ 'is_you' ] = $srvIP->checkIp( $you, $entry->ip );
64
- $aE[ 'ip' ] = sprintf( '%s%s',
65
  $this->getIpAnalysisLink( $entry->ip ),
66
- $aE[ 'is_you' ] ? ' <span class="small">('.__( 'You', 'wp-simple-firewall' ).')</span>' : ''
67
  );
68
 
69
- $entries[ $key ] = $aE;
70
  }
71
  return $entries;
72
  }
50
 
51
  foreach ( $this->getEntriesRaw() as $key => $entry ) {
52
  /** @var IPs\EntryVO $entry */
53
+ $e = $entry->getRawData();
54
  $bBlocked = $entry->blocked_at > 0 || $entry->transgressions >= $nTransLimit;
55
+ $e[ 'last_trans_at' ] = Services::Request()
56
  ->carbon( true )
57
  ->setTimestamp( $entry->last_access_at )
58
  ->diffForHumans();
59
+ $e[ 'last_access_at' ] = $this->formatTimestampField( $entry->last_access_at );
60
+ $e[ 'created_at' ] = $this->formatTimestampField( $entry->created_at );
61
+ $e[ 'blocked' ] = $bBlocked ? __( 'Yes' ) : __( 'No' );
62
+ $e[ 'expires_at' ] = $this->formatTimestampField( $entry->last_access_at + $opts->getAutoExpireTime() );
63
+ $e[ 'is_you' ] = $srvIP->checkIp( $you, $entry->ip );
64
+ $e[ 'ip' ] = sprintf( '%s%s',
65
  $this->getIpAnalysisLink( $entry->ip ),
66
+ $e[ 'is_you' ] ? ' <span class="small">('.__( 'You', 'wp-simple-firewall' ).')</span>' : ''
67
  );
68
 
69
+ $entries[ $key ] = $e;
70
  }
71
  return $entries;
72
  }
src/lib/src/Tables/Build/Sessions.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Tables\Build;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Session;
 
6
  use FernleafSystems\Wordpress\Plugin\Shield\Tables;
7
  use FernleafSystems\Wordpress\Services\Services;
8
 
@@ -13,6 +14,91 @@ class Sessions extends BaseBuild {
13
  */
14
  private $aSecAdminUsers;
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  /**
17
  * Override this to apply table-specific query filters.
18
  * @return $this
@@ -21,18 +107,11 @@ class Sessions extends BaseBuild {
21
  /** @var Session\Select $oSelector */
22
  $oSelector = $this->getWorkingSelector();
23
 
24
- $aParams = $this->getParams();
25
 
26
  // If an IP is specified, it takes priority
27
- if ( Services::IP()->isValidIp( $aParams[ 'fIp' ] ) ) {
28
- $oSelector->filterByIp( $aParams[ 'fIp' ] );
29
- }
30
-
31
- if ( !empty( $aParams[ 'fUsername' ] ) ) {
32
- $oUser = Services::WpUsers()->getUserByUsername( $aParams[ 'fUsername' ] );
33
- if ( !empty( $oUser ) ) {
34
- $oSelector->filterByUsername( $oUser->user_login );
35
- }
36
  }
37
 
38
  $oSelector->setOrderBy( 'last_activity_at', 'DESC', true );
@@ -51,37 +130,36 @@ class Sessions extends BaseBuild {
51
  * @return array[]
52
  */
53
  public function getEntriesFormatted() :array {
54
- $aEntries = [];
55
 
56
  $srvIP = Services::IP();
 
57
  $you = $srvIP->getRequestIp();
58
- foreach ( $this->getEntriesRaw() as $nKey => $entry ) {
59
- /** @var Session\EntryVO $entry */
60
- $e = $entry->getRawData();
61
- $e[ 'is_secadmin' ] = $this->isSecAdminSession( $entry ) ? __( 'Yes' ) : __( 'No' );
62
- $e[ 'last_activity_at' ] = $this->formatTimestampField( $entry->last_activity_at );
63
- $e[ 'logged_in_at' ] = $this->formatTimestampField( $entry->logged_in_at );
64
 
65
  try {
66
- $e[ 'is_you' ] = $srvIP->checkIp( $you, $entry->ip );
67
  }
68
  catch ( \Exception $ex ) {
69
- $e[ 'is_you' ] = false;
70
  }
71
- $e[ 'ip' ] = sprintf( '%s%s',
72
- $this->getIpAnalysisLink( $entry->ip ),
73
- $e[ 'is_you' ] ? ' <small>('.__( 'You', 'wp-simple-firewall' ).')</small>' : ''
74
- );
75
-
76
- $WPU = Services::WpUsers();
77
- $e[ 'wp_username' ] = sprintf(
78
- '<a href="%s">%s</a>',
79
- $WPU->getAdminUrl_ProfileEdit( $WPU->getUserByUsername( $e[ 'wp_username' ] ?? '' ) ),
80
- $e[ 'wp_username' ]
81
- );
82
- $aEntries[ $nKey ] = $e;
 
 
 
83
  }
84
- return $aEntries;
85
  }
86
 
87
  /**
3
  namespace FernleafSystems\Wordpress\Plugin\Shield\Tables\Build;
4
 
5
  use FernleafSystems\Wordpress\Plugin\Shield\Databases\Session;
6
+ use FernleafSystems\Wordpress\Plugin\Shield\Modules\Data\DB\UserMeta\Ops\Select;
7
  use FernleafSystems\Wordpress\Plugin\Shield\Tables;
8
  use FernleafSystems\Wordpress\Services\Services;
9
 
14
  */
15
  private $aSecAdminUsers;
16
 
17
+ /**
18
+ * @var array
19
+ */
20
+ private $sessions;
21
+
22
+ /**
23
+ * @return int
24
+ */
25
+ public function countTotal() {
26
+ return array_sum( array_map( function ( $sessions ) {
27
+ return count( $sessions );
28
+ }, $this->loadSessions() ) );
29
+ }
30
+
31
+ private function loadSessions() :array {
32
+ if ( !isset( $this->sessions ) ) {
33
+
34
+ $params = $this->getParams();
35
+ if ( !empty( $params[ 'fUsername' ] ) ) {
36
+ $user = Services::WpUsers()->getUserByUsername( $params[ 'fUsername' ] );
37
+ if ( !empty( $user ) ) {
38
+ $UIDs = [ $user->ID ];
39
+ }
40
+ }
41
+
42
+ if ( empty( $UIDs ) ) {
43
+ // Select the most recently active based on updated Shield User Meta
44
+ /** @var Select $metaSelect */
45
+ $metaSelect = $this->getCon()
46
+ ->getModule_Data()
47
+ ->getDbH_UserMeta()
48
+ ->getQuerySelector();
49
+ $results = $metaSelect->setResultsAsVo( false )
50
+ ->setSelectResultsFormat( ARRAY_A )
51
+ ->setColumnsToSelect( [ 'user_id' ] )
52
+ ->setOrderBy( 'updated_at', 'DESC' )
53
+ ->setLimit( 20 )
54
+ ->queryWithResult();
55
+ $UIDs = array_map(
56
+ function ( $res ) {
57
+ return (int)$res[ 'user_id' ];
58
+ },
59
+ is_array( $results ) ? $results : []
60
+ );
61
+ }
62
+
63
+ $this->sessions = [];
64
+ foreach ( $UIDs as $UID ) {
65
+ $manager = \WP_Session_Tokens::get_instance( $UID );
66
+ $this->sessions[ $UID ] = $manager->get_all();
67
+ }
68
+ }
69
+ return $this->sessions;
70
+ }
71
+
72
+ /**
73
+ * @return array[]
74
+ */
75
+ protected function getEntriesRaw() :array {
76
+ $allSessions = [];
77
+ foreach ( $this->loadSessions() as $uid => $sessions ) {
78
+ $user = Services::WpUsers()->getUserById( $uid );
79
+ foreach ( $sessions as $session ) {
80
+ $session[ 'last_activity_at' ] = $session[ 'shield' ][ 'last_activity_at' ] ?? $session[ 'login' ];
81
+ $session[ 'secadmin_at' ] = $session[ 'shield' ][ 'secadmin_at' ] ?? 0;
82
+ $session[ 'user' ] = $user;
83
+ $session[ 'user_id' ] = $user->ID;
84
+ $allSessions[] = $session;
85
+ }
86
+ }
87
+
88
+ $allSessions = array_filter( $allSessions );
89
+
90
+ usort( $allSessions, function ( $a, $b ) {
91
+ $a = $a[ 'last_activity_at' ] ?? $a[ 'login' ];
92
+ $b = $b[ 'last_activity_at' ] ?? $b[ 'login' ];
93
+ if ( $a == $b ) {
94
+ return 0;
95
+ }
96
+ return ( $a < $b ) ? 1 : -1;
97
+ } );
98
+
99
+ return $allSessions;
100
+ }
101
+
102
  /**
103
  * Override this to apply table-specific query filters.
104
  * @return $this
107
  /** @var Session\Select $oSelector */
108
  $oSelector = $this->getWorkingSelector();
109
 
110
+ $params = $this->getParams();
111
 
112
  // If an IP is specified, it takes priority
113
+ if ( Services::IP()->isValidIp( $params[ 'fIp' ] ) ) {
114
+ $oSelector->filterByIp( $params[ 'fIp' ] );
 
 
 
 
 
 
 
115
  }
116
 
117
  $oSelector->setOrderBy( 'last_activity_at', 'DESC', true );
130
  * @return array[]
131
  */
132
  public function getEntriesFormatted() :array {
133
+ $entries = [];
134
 
135
  $srvIP = Services::IP();
136
+ $WPU = Services::WpUsers();
137
  $you = $srvIP->getRequestIp();
138
+ foreach ( $this->getEntriesRaw() as $key => $e ) {
 
 
 
 
 
139
 
140
  try {
141
+ $isYou = $srvIP->checkIp( $you, $e[ 'ip' ] );
142
  }
143
  catch ( \Exception $ex ) {
144
+ $isYou = false;
145
  }
146
+
147
+ $entries[ $key ] = array_merge( $e, [
148
+ 'is_secadmin' => $e[ 'secadmin_at' ] ? __( 'Yes' ) : __( 'No' ),
149
+ 'last_activity_at' => $this->formatTimestampField( $e[ 'last_activity_at' ] ),
150
+ 'logged_in_at' => $this->formatTimestampField( $e[ 'login' ] ),
151
+ 'ip' => sprintf( '%s <small>%s</small>',
152
+ $this->getIpAnalysisLink( $e[ 'ip' ] ),
153
+ $isYou ? __( 'You', 'wp-simple-firewall' ) : ''
154
+ ),
155
+ 'is_you' => $isYou,
156
+ 'wp_username' => sprintf( '<a href="%s">%s</a>',
157
+ $WPU->getAdminUrl_ProfileEdit( $e[ 'user' ] ),
158
+ $e[ 'user' ]->user_login
159
+ ),
160
+ ] );
161
  }
162
+ return $entries;
163
  }
164
 
165
  /**
src/lib/src/Tables/DataTables/LoadData/BaseBuildTableData.php CHANGED
@@ -151,9 +151,12 @@ abstract class BaseBuildTableData extends DynPropertiesClass {
151
  );
152
  }
153
  elseif ( Services::IP()->isValidIp( $ip ) ) {
154
- $content = sprintf( '<a href="%s" target="_blank" title="%s">%s</a>',
 
155
  $this->getCon()->getModule_Insights()->getUrl_IpAnalysis( $ip ),
156
  __( 'IP Analysis', 'wp-simple-firewall' ),
 
 
157
  $ip
158
  );
159
  }
151
  );
152
  }
153
  elseif ( Services::IP()->isValidIp( $ip ) ) {
154
+ $content = sprintf(
155
+ '<a href="%s" target="_blank" title="%s" class="%s" data-ip="%s">%s</a>',
156
  $this->getCon()->getModule_Insights()->getUrl_IpAnalysis( $ip ),
157
  __( 'IP Analysis', 'wp-simple-firewall' ),
158
+ 'modal_ip_analysis',
159
+ $ip,
160
  $ip
161
  );
162
  }
src/lib/src/Tables/DataTables/LoadData/BaseLoadTableData.php DELETED
@@ -1,132 +0,0 @@
1
- <?php declare( strict_types=1 );
2
-
3
- namespace FernleafSystems\Wordpress\Plugin\Shield\Tables\DataTables\LoadData;
4
-
5
- use FernleafSystems\Utilities\Data\Adapter\DynPropertiesClass;
6
- use FernleafSystems\Wordpress\Plugin\Shield\Modules\ModConsumer;
7
- use FernleafSystems\Wordpress\Services\Services;
8
-
9
- /**
10
- * @property string $order_by
11
- * @property string $order_dir
12
- * @property array $table_data
13
- */
14
- abstract class BaseLoadTableData extends DynPropertiesClass {
15
-
16
- use ModConsumer;
17
-
18
- public function loadForLogs() :array {
19
- $start = (int)$this->table_data[ 'start' ];
20
- $length = (int)$this->table_data[ 'length' ];
21
- $search = (string)$this->table_data[ 'search' ][ 'value' ] ?? '';
22
-
23
- $searchableColumns = array_flip( $this->getSearchableColumns() );
24
-
25
- // We keep building logs and filtering by the search string until we have
26
- // enough records built to return in order to satisfy the start + length.
27
- $results = [];
28
- $page = 0;
29
- $pageLength = 100;
30
- do {
31
- $interimResults = $this->buildTableRowsFromRawLogs(
32
- $this->getRecords( $page*$pageLength, $pageLength )
33
- );
34
- // no more table results to process, so go with what we have.
35
- if ( empty( $interimResults ) ) {
36
- break;
37
- }
38
-
39
- foreach ( $interimResults as $result ) {
40
-
41
- if ( empty( $search ) ) {
42
- $results[] = $result;
43
- }
44
- else {
45
- $searchable = array_intersect_key( $result, $searchableColumns );
46
- foreach ( $searchable as $value ) {
47
- $value = wp_strip_all_tags( $value );
48
- if (!is_string($search)) {
49
- error_log( var_export( $search, true ) );
50
- }
51
- if ( stripos( $value, $search ) !== false ) {
52
- $results[] = $result;
53
- break;
54
- }
55
- }
56
- }
57
- }
58
-
59
- $page++;
60
- } while ( count( $results ) < $start + $length );
61
-
62
- $results = array_values( $results );
63
- if ( count( $results ) < $start ) {
64
- $results = [];
65
- }
66
- else {
67
- $results = array_splice( $results, $start, $length );
68
- }
69
-
70
- return array_values( $results );
71
- }
72
-
73
- protected function getOrderDirection() :string {
74
- if ( !isset( $this->order_dir ) ) {
75
- $dir = 'DESC';
76
- if ( !empty( $this->table_data[ 'order' ] ) ) {
77
- $col = $this->table_data[ 'order' ][ 0 ][ 'column' ];
78
- $sortCol = $this->table_data[ 'columns' ][ $col ][ 'data' ];
79
- $this->order_by = is_array( $sortCol ) ? $sortCol[ 'sort' ] : $sortCol;
80
- $dir = strtoupper( $this->table_data[ 'order' ][ 0 ][ 'dir' ] );
81
- if ( !in_array( $dir, [ 'ASC', 'DESC' ] ) ) {
82
- $dir = 'DESC';
83
- }
84
- }
85
- $this->order_dir = $dir;
86
- }
87
- return $this->order_dir;
88
- }
89
-
90
- protected function getRecords( int $offset = 0, int $limit = 0 ) :array {
91
- return [];
92
- }
93
-
94
- abstract protected function buildTableRowsFromRawLogs( array $records ) :array;
95
-
96
- protected function getColumnContent_Date( int $ts ) :string {
97
- return sprintf( '%s<br /><small>%s</small>',
98
- Services::Request()
99
- ->carbon( true )
100
- ->setTimestamp( $ts )
101
- ->diffForHumans(),
102
- Services::WpGeneral()->getTimeStringForDisplay( $ts )
103
- );
104
- }
105
-
106
- protected function getIpAnalysisLink( string $ip ) :string {
107
- $srvIP = Services::IP();
108
-
109
- if ( $srvIP->isValidIpRange( $ip ) ) {
110
- $content = sprintf( '<a href="%s" target="_blank" title="%s">%s</a>',
111
- $srvIP->getIpWhoisLookup( $ip ),
112
- __( 'IP Analysis', 'wp-simple-firewall' ),
113
- $ip
114
- );
115
- }
116
- elseif ( Services::IP()->isValidIp( $ip ) ) {
117
- $content = sprintf( '<a href="%s" target="_blank" title="%s">%s</a>',
118
- $this->getCon()->getModule_Insights()->getUrl_IpAnalysis( $ip ),
119
- __( 'IP Analysis', 'wp-simple-firewall' ),
120
- $ip
121
- );
122
- }
123
- else {
124
- $content = __( 'IP Unavailable', 'wp-simple-firewall' );
125
- }
126
- return $content;
127
- }
128
-
129
- protected function getSearchableColumns() :array {
130
- return [];
131
- }
132
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/src/Tables/Render/{WpListTable/Base.php → WpListTa} RENAMED
File without changes